aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/fts3/fts3.h1
-rw-r--r--ext/fts3/unicode/mkunicode.tcl60
-rw-r--r--ext/fts5/extract_api_docs.tcl237
-rw-r--r--ext/fts5/fts5.c1968
-rw-r--r--ext/fts5/fts5.h319
-rw-r--r--ext/fts5/fts5Int.h575
-rw-r--r--ext/fts5/fts5_aux.c556
-rw-r--r--ext/fts5/fts5_buffer.c299
-rw-r--r--ext/fts5/fts5_config.c795
-rw-r--r--ext/fts5/fts5_expr.c1701
-rw-r--r--ext/fts5/fts5_hash.c448
-rw-r--r--ext/fts5/fts5_index.c5337
-rw-r--r--ext/fts5/fts5_storage.c992
-rw-r--r--ext/fts5/fts5_tcl.c867
-rw-r--r--ext/fts5/fts5_tokenize.c1230
-rw-r--r--ext/fts5/fts5_unicode2.c363
-rw-r--r--ext/fts5/fts5parse.y155
-rw-r--r--ext/fts5/mkportersteps.tcl222
-rw-r--r--ext/fts5/test/fts5_common.tcl148
-rw-r--r--ext/fts5/test/fts5aa.test384
-rw-r--r--ext/fts5/test/fts5ab.test267
-rw-r--r--ext/fts5/test/fts5ac.test447
-rw-r--r--ext/fts5/test/fts5ad.test235
-rw-r--r--ext/fts5/test/fts5ae.test281
-rw-r--r--ext/fts5/test/fts5af.test144
-rw-r--r--ext/fts5/test/fts5ag.test138
-rw-r--r--ext/fts5/test/fts5ah.test108
-rw-r--r--ext/fts5/test/fts5ai.test55
-rw-r--r--ext/fts5/test/fts5aj.test69
-rw-r--r--ext/fts5/test/fts5ak.test143
-rw-r--r--ext/fts5/test/fts5al.test273
-rw-r--r--ext/fts5/test/fts5auxdata.test109
-rw-r--r--ext/fts5/test/fts5bigpl.test58
-rw-r--r--ext/fts5/test/fts5content.test190
-rw-r--r--ext/fts5/test/fts5corrupt.test74
-rw-r--r--ext/fts5/test/fts5dlidx.test80
-rw-r--r--ext/fts5/test/fts5ea.test83
-rw-r--r--ext/fts5/test/fts5eb.test53
-rw-r--r--ext/fts5/test/fts5fault1.test353
-rw-r--r--ext/fts5/test/fts5near.test65
-rw-r--r--ext/fts5/test/fts5optimize.test60
-rw-r--r--ext/fts5/test/fts5porter.test11800
-rw-r--r--ext/fts5/test/fts5prefix.test60
-rw-r--r--ext/fts5/test/fts5rebuild.test50
-rw-r--r--ext/fts5/test/fts5rowid.test183
-rw-r--r--ext/fts5/test/fts5tokenizer.test97
-rw-r--r--ext/fts5/test/fts5unicode.test56
-rw-r--r--ext/fts5/test/fts5unicode2.test564
-rw-r--r--ext/fts5/tool/loadfts5.tcl121
-rw-r--r--main.mk81
-rw-r--r--manifest80
-rw-r--r--manifest.uuid2
-rw-r--r--src/main.c9
-rw-r--r--src/tclsqlite.c2
-rw-r--r--src/test_config.c6
-rw-r--r--src/vtab.c7
-rw-r--r--test/malloc_common.tcl14
-rw-r--r--test/permutations.test4
-rw-r--r--tool/loadfts.c238
-rw-r--r--tool/mksqlite3c.tcl15
60 files changed, 33291 insertions, 40 deletions
diff --git a/ext/fts3/fts3.h b/ext/fts3/fts3.h
index c1aa8caf0..e99457eeb 100644
--- a/ext/fts3/fts3.h
+++ b/ext/fts3/fts3.h
@@ -20,6 +20,7 @@ extern "C" {
#endif /* __cplusplus */
int sqlite3Fts3Init(sqlite3 *db);
+int sqlite3Fts5Init(sqlite3 *db);
#ifdef __cplusplus
} /* extern "C" */
diff --git a/ext/fts3/unicode/mkunicode.tcl b/ext/fts3/unicode/mkunicode.tcl
index c3083ee36..692ba72bf 100644
--- a/ext/fts3/unicode/mkunicode.tcl
+++ b/ext/fts3/unicode/mkunicode.tcl
@@ -117,7 +117,7 @@ proc print_rd {map} {
puts "** E\"). The resuls of passing a codepoint that corresponds to an"
puts "** uppercase letter are undefined."
puts "*/"
- puts "static int remove_diacritic(int c)\{"
+ puts "static int ${::remove_diacritic}(int c)\{"
puts " unsigned short aDia\[\] = \{"
puts -nonewline " 0, "
set i 1
@@ -626,7 +626,7 @@ proc print_fold {zFunc} {
tl_print_table_footer toggle
tl_print_ioff_table $liOff
- puts {
+ puts [subst -nocommands {
int ret = c;
assert( c>=0 );
@@ -659,9 +659,9 @@ proc print_fold {zFunc} {
}
}
- if( bRemoveDiacritic ) ret = remove_diacritic(ret);
- }
+ if( bRemoveDiacritic ) ret = ${::remove_diacritic}(ret);
}
+ }]
foreach entry $lHigh {
tl_print_if_entry $entry
@@ -732,8 +732,12 @@ proc print_fileheader {} {
*/
}]
puts ""
- puts "#ifndef SQLITE_DISABLE_FTS3_UNICODE"
- puts "#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)"
+ if {$::generate_fts5_code} {
+ puts "#if defined(SQLITE_ENABLE_FTS5)"
+ } else {
+ puts "#ifndef SQLITE_DISABLE_FTS3_UNICODE"
+ puts "#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)"
+ }
puts ""
puts "#include <assert.h>"
puts ""
@@ -760,22 +764,40 @@ proc print_test_main {} {
# our liking.
#
proc usage {} {
- puts -nonewline stderr "Usage: $::argv0 ?-test? "
+ puts -nonewline stderr "Usage: $::argv0 ?-test? ?-fts5? "
puts stderr "<CaseFolding.txt file> <UnicodeData.txt file>"
exit 1
}
-if {[llength $argv]!=2 && [llength $argv]!=3} usage
-if {[llength $argv]==3 && [lindex $argv 0]!="-test"} usage
+if {[llength $argv]<2} usage
set unicodedata.txt [lindex $argv end]
set casefolding.txt [lindex $argv end-1]
-set generate_test_code [expr {[llength $argv]==3}]
+
+set remove_diacritic remove_diacritic
+set generate_test_code 0
+set generate_fts5_code 0
+set function_prefix "sqlite3Fts"
+for {set i 0} {$i < [llength $argv]-2} {incr i} {
+ switch -- [lindex $argv $i] {
+ -test {
+ set generate_test_code 1
+ }
+ -fts5 {
+ set function_prefix sqlite3Fts5
+ set generate_fts5_code 1
+ set remove_diacritic fts5_remove_diacritic
+ }
+ default {
+ usage
+ }
+ }
+}
print_fileheader
# Print the isalnum() function to stdout.
#
set lRange [an_load_separator_ranges]
-print_isalnum sqlite3FtsUnicodeIsalnum $lRange
+print_isalnum ${function_prefix}UnicodeIsalnum $lRange
# Leave a gap between the two generated C functions.
#
@@ -790,22 +812,26 @@ set mappings [rd_load_unicodedata_text ${unicodedata.txt}]
print_rd $mappings
puts ""
puts ""
-print_isdiacritic sqlite3FtsUnicodeIsdiacritic $mappings
+print_isdiacritic ${function_prefix}UnicodeIsdiacritic $mappings
puts ""
puts ""
# Print the fold() function to stdout.
#
-print_fold sqlite3FtsUnicodeFold
+print_fold ${function_prefix}UnicodeFold
# Print the test routines and main() function to stdout, if -test
# was specified.
#
if {$::generate_test_code} {
- print_test_isalnum sqlite3FtsUnicodeIsalnum $lRange
- print_fold_test sqlite3FtsUnicodeFold $mappings
+ print_test_isalnum ${function_prefix}UnicodeIsalnum $lRange
+ print_fold_test ${function_prefix}UnicodeFold $mappings
print_test_main
}
-puts "#endif /* defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) */"
-puts "#endif /* !defined(SQLITE_DISABLE_FTS3_UNICODE) */"
+if {$generate_fts5_code} {
+ puts "#endif /* defined(SQLITE_ENABLE_FTS5) */"
+} else {
+ puts "#endif /* defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) */"
+ puts "#endif /* !defined(SQLITE_DISABLE_FTS3_UNICODE) */"
+}
diff --git a/ext/fts5/extract_api_docs.tcl b/ext/fts5/extract_api_docs.tcl
new file mode 100644
index 000000000..27f136a99
--- /dev/null
+++ b/ext/fts5/extract_api_docs.tcl
@@ -0,0 +1,237 @@
+#
+# 2014 August 24
+#
+# 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 script extracts the documentation for the API used by fts5 auxiliary
+# functions from header file fts5.h. It outputs html text on stdout that
+# is included in the documentation on the web.
+#
+
+set ::fts5_docs_output ""
+if {[info commands hd_putsnl]==""} {
+ if {[llength $argv]>0} { set ::extract_api_docs_mode [lindex $argv 0] }
+ proc output {text} {
+ puts $text
+ }
+} else {
+ proc output {text} {
+ append ::fts5_docs_output "$text\n"
+ }
+}
+if {[info exists ::extract_api_docs_mode]==0} {set ::extract_api_docs_mode api}
+
+
+set input_file [file join [file dir [info script]] fts5.h]
+set fd [open $input_file]
+set data [read $fd]
+close $fd
+
+
+# Argument $data is the entire text of the fts5.h file. This function
+# extracts the definition of the Fts5ExtensionApi structure from it and
+# returns a key/value list of structure member names and definitions. i.e.
+#
+# iVersion {int iVersion} xUserData {void *(*xUserData)(Fts5Context*)} ...
+#
+proc get_struct_members {data} {
+
+ # Extract the structure definition from the fts5.h file.
+ regexp "struct Fts5ExtensionApi {(.*?)};" $data -> defn
+
+ # Remove all comments from the structure definition
+ regsub -all {/[*].*?[*]/} $defn {} defn2
+
+ set res [list]
+ foreach member [split $defn2 {;}] {
+
+ set member [string trim $member]
+ if {$member!=""} {
+ catch { set name [lindex $member end] }
+ regexp {.*?[(][*]([^)]*)[)]} $member -> name
+ lappend res $name $member
+ }
+ }
+
+ set res
+}
+
+proc get_struct_docs {data names} {
+ # Extract the structure definition from the fts5.h file.
+ regexp {EXTENSION API FUNCTIONS(.*?)[*]/} $data -> docs
+
+ set current_doc ""
+ set current_header ""
+
+ foreach line [split $docs "\n"] {
+ regsub {[*]*} $line {} line
+ if {[regexp {^ } $line]} {
+ append current_doc "$line\n"
+ } elseif {[string trim $line]==""} {
+ if {$current_header!=""} { append current_doc "\n" }
+ } else {
+ if {$current_doc != ""} {
+ lappend res $current_header $current_doc
+ set current_doc ""
+ }
+ set subject n/a
+ regexp {^ *([[:alpha:]]*)} $line -> subject
+ if {[lsearch $names $subject]>=0} {
+ set current_header $subject
+ } else {
+ set current_header [string trim $line]
+ }
+ }
+ }
+
+ if {$current_doc != ""} {
+ lappend res $current_header $current_doc
+ }
+
+ set res
+}
+
+proc get_tokenizer_docs {data} {
+ regexp {(xCreate:.*?)[*]/} $data -> docs
+
+ set res "<dl>\n"
+ foreach line [split [string trim $docs] "\n"] {
+ regexp {[*][*](.*)} $line -> line
+ if {[regexp {^ ?x.*:} $line]} {
+ append res "<dt><b>$line</b></dt><dd><p style=margin-top:0>\n"
+ continue
+ }
+ if {[string trim $line] == ""} {
+ append res "<p>\n"
+ } else {
+ append res "$line\n"
+ }
+ }
+ append res "</dl>\n"
+
+ set res
+}
+
+proc get_api_docs {data} {
+ # Initialize global array M as a map from Fts5StructureApi member name
+ # to member definition. i.e.
+ #
+ # iVersion -> {int iVersion}
+ # xUserData -> {void *(*xUserData)(Fts5Context*)}
+ # ...
+ #
+ array set M [get_struct_members $data]
+
+ # Initialize global list D as a map from section name to documentation
+ # text. Most (all?) section names are structure member names.
+ #
+ set D [get_struct_docs $data [array names M]]
+
+ foreach {sub docs} $D {
+ if {[info exists M($sub)]} {
+ set hdr $M($sub)
+ set link " id=$sub"
+ } else {
+ set link ""
+ }
+
+ output "<hr color=#eeeee style=\"margin:1em 8.4ex 0 8.4ex;\"$link>"
+ set style "padding-left:6ex;font-size:1.4em;display:block"
+ output "<h style=\"$style\"><pre>$hdr</pre></h>"
+
+ set mode ""
+ set bEmpty 1
+ foreach line [split [string trim $docs] "\n"] {
+ if {[string trim $line]==""} {
+ if {$mode != ""} {output "</$mode>"}
+ set mode ""
+ } elseif {$mode == ""} {
+ if {[regexp {^ } $line]} {
+ set mode codeblock
+ } else {
+ set mode p
+ }
+ output "<$mode>"
+ }
+ output $line
+ }
+ if {$mode != ""} {output "</$mode>"}
+ }
+}
+
+proc get_fts5_struct {data start end} {
+ set res ""
+ set bOut 0
+ foreach line [split $data "\n"] {
+ if {$bOut==0} {
+ if {[regexp $start $line]} {
+ set bOut 1
+ }
+ }
+
+ if {$bOut} {
+ append res "$line\n"
+ }
+
+ if {$bOut} {
+ if {[regexp $end $line]} {
+ set bOut 0
+ }
+ }
+ }
+
+ set map [list /* <i>/* */ */</i>]
+ string map $map $res
+}
+
+proc main {data} {
+ switch $::extract_api_docs_mode {
+ fts5_api {
+ output [get_fts5_struct $data "typedef struct fts5_api" "^\};"]
+ }
+
+ fts5_tokenizer {
+ output [get_fts5_struct $data "typedef struct Fts5Tokenizer" "^\};"]
+ }
+
+ fts5_extension {
+ output [get_fts5_struct $data "typedef.*Fts5ExtensionApi" "^.;"]
+ }
+
+ Fts5ExtensionApi {
+ set struct [get_fts5_struct $data "^struct Fts5ExtensionApi" "^.;"]
+ set map [list]
+ foreach {k v} [get_struct_members $data] {
+ if {[string match x* $k]==0} continue
+ lappend map $k "<a href=#$k>$k</a>"
+ }
+ output [string map $map $struct]
+ }
+
+ api {
+ get_api_docs $data
+ }
+
+ tokenizer_api {
+ output [get_tokenizer_docs $data]
+ }
+
+ default {
+ }
+ }
+}
+main $data
+
+set ::fts5_docs_output
+
+
+
+
+
diff --git a/ext/fts5/fts5.c b/ext/fts5/fts5.c
new file mode 100644
index 000000000..f8450aab1
--- /dev/null
+++ b/ext/fts5/fts5.c
@@ -0,0 +1,1968 @@
+/*
+** 2014 Jun 09
+**
+** 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 an SQLite module implementing full-text search.
+*/
+
+#if defined(SQLITE_ENABLE_FTS5)
+
+#include "fts5Int.h"
+
+
+typedef struct Fts5Table Fts5Table;
+typedef struct Fts5Cursor Fts5Cursor;
+typedef struct Fts5Global Fts5Global;
+typedef struct Fts5Auxiliary Fts5Auxiliary;
+typedef struct Fts5Auxdata Fts5Auxdata;
+
+typedef struct Fts5TokenizerModule Fts5TokenizerModule;
+
+/*
+** NOTES ON TRANSACTIONS:
+**
+** SQLite invokes the following virtual table methods as transactions are
+** opened and closed by the user:
+**
+** xBegin(): Start of a new transaction.
+** xSync(): Initial part of two-phase commit.
+** xCommit(): Final part of two-phase commit.
+** xRollback(): Rollback the transaction.
+**
+** Anything that is required as part of a commit that may fail is performed
+** in the xSync() callback. Current versions of SQLite ignore any errors
+** returned by xCommit().
+**
+** And as sub-transactions are opened/closed:
+**
+** xSavepoint(int S): Open savepoint S.
+** xRelease(int S): Commit and close savepoint S.
+** xRollbackTo(int S): Rollback to start of savepoint S.
+**
+** During a write-transaction the fts5_index.c module may cache some data
+** in-memory. It is flushed to disk whenever xSync(), xRelease() or
+** xSavepoint() is called. And discarded whenever xRollback() or xRollbackTo()
+** is called.
+**
+** Additionally, if SQLITE_DEBUG is defined, an instance of the following
+** structure is used to record the current transaction state. This information
+** is not required, but it is used in the assert() statements executed by
+** function fts5CheckTransactionState() (see below).
+*/
+struct Fts5TransactionState {
+ int eState; /* 0==closed, 1==open, 2==synced */
+ int iSavepoint; /* Number of open savepoints (0 -> none) */
+};
+
+/*
+** A single object of this type is allocated when the FTS5 module is
+** registered with a database handle. It is used to store pointers to
+** all registered FTS5 extensions - tokenizers and auxiliary functions.
+*/
+struct Fts5Global {
+ fts5_api api; /* User visible part of object (see fts5.h) */
+ sqlite3 *db; /* Associated database connection */
+ i64 iNextId; /* Used to allocate unique cursor ids */
+ Fts5Auxiliary *pAux; /* First in list of all aux. functions */
+ Fts5TokenizerModule *pTok; /* First in list of all tokenizer modules */
+ Fts5TokenizerModule *pDfltTok; /* Default tokenizer module */
+ Fts5Cursor *pCsr; /* First in list of all open cursors */
+};
+
+/*
+** Each auxiliary function registered with the FTS5 module is represented
+** by an object of the following type. All such objects are stored as part
+** of the Fts5Global.pAux list.
+*/
+struct Fts5Auxiliary {
+ Fts5Global *pGlobal; /* Global context for this function */
+ char *zFunc; /* Function name (nul-terminated) */
+ void *pUserData; /* User-data pointer */
+ fts5_extension_function xFunc; /* Callback function */
+ void (*xDestroy)(void*); /* Destructor function */
+ Fts5Auxiliary *pNext; /* Next registered auxiliary function */
+};
+
+/*
+** Each tokenizer module registered with the FTS5 module is represented
+** by an object of the following type. All such objects are stored as part
+** of the Fts5Global.pTok list.
+*/
+struct Fts5TokenizerModule {
+ char *zName; /* Name of tokenizer */
+ void *pUserData; /* User pointer passed to xCreate() */
+ fts5_tokenizer x; /* Tokenizer functions */
+ void (*xDestroy)(void*); /* Destructor function */
+ Fts5TokenizerModule *pNext; /* Next registered tokenizer module */
+};
+
+/*
+** Virtual-table object.
+*/
+struct Fts5Table {
+ sqlite3_vtab base; /* Base class used by SQLite core */
+ Fts5Config *pConfig; /* Virtual table configuration */
+ Fts5Index *pIndex; /* Full-text index */
+ Fts5Storage *pStorage; /* Document store */
+ Fts5Global *pGlobal; /* Global (connection wide) data */
+ Fts5Cursor *pSortCsr; /* Sort data from this cursor */
+#ifdef SQLITE_DEBUG
+ struct Fts5TransactionState ts;
+#endif
+};
+
+struct Fts5MatchPhrase {
+ Fts5Buffer *pPoslist; /* Pointer to current poslist */
+ int nTerm; /* Size of phrase in terms */
+};
+
+/*
+** pStmt:
+** SELECT rowid, <fts> FROM <fts> ORDER BY +rank;
+**
+** aIdx[]:
+** There is one entry in the aIdx[] array for each phrase in the query,
+** the value of which is the offset within aPoslist[] following the last
+** byte of the position list for the corresponding phrase.
+*/
+struct Fts5Sorter {
+ sqlite3_stmt *pStmt;
+ i64 iRowid; /* Current rowid */
+ const u8 *aPoslist; /* Position lists for current row */
+ int nIdx; /* Number of entries in aIdx[] */
+ int aIdx[0]; /* Offsets into aPoslist for current row */
+};
+
+
+/*
+** Virtual-table cursor object.
+**
+** zSpecial:
+** If this is a 'special' query (refer to function fts5SpecialMatch()),
+** then this variable points to a nul-terminated buffer containing the
+** result to return through the table-name column. It is nul-terminated
+** and should eventually be freed using sqlite3_free().
+*/
+struct Fts5Cursor {
+ sqlite3_vtab_cursor base; /* Base class used by SQLite core */
+ int idxNum; /* idxNum passed to xFilter() */
+ sqlite3_stmt *pStmt; /* Statement used to read %_content */
+ Fts5Expr *pExpr; /* Expression for MATCH queries */
+ Fts5Sorter *pSorter; /* Sorter for "ORDER BY rank" queries */
+ int csrflags; /* Mask of cursor flags (see below) */
+ Fts5Cursor *pNext; /* Next cursor in Fts5Cursor.pCsr list */
+ char *zSpecial; /* Result of special query */
+
+ /* "rank" function. Populated on demand from vtab.xColumn(). */
+ char *zRank; /* Custom rank function */
+ char *zRankArgs; /* Custom rank function args */
+ Fts5Auxiliary *pRank; /* Rank callback (or NULL) */
+ int nRankArg; /* Number of trailing arguments for rank() */
+ sqlite3_value **apRankArg; /* Array of trailing arguments */
+ sqlite3_stmt *pRankArgStmt; /* Origin of objects in apRankArg[] */
+
+ /* Variables used by auxiliary functions */
+ i64 iCsrId; /* Cursor id */
+ Fts5Auxiliary *pAux; /* Currently executing extension function */
+ Fts5Auxdata *pAuxdata; /* First in linked list of saved aux-data */
+ int *aColumnSize; /* Values for xColumnSize() */
+
+ int nInstCount; /* Number of phrase instances */
+ int *aInst; /* 3 integers per phrase instance */
+};
+
+/*
+** Values for Fts5Cursor.csrflags
+*/
+#define FTS5CSR_REQUIRE_CONTENT 0x01
+#define FTS5CSR_REQUIRE_DOCSIZE 0x02
+#define FTS5CSR_EOF 0x04
+#define FTS5CSR_FREE_ZRANK 0x08
+
+/*
+** Macros to Set(), Clear() and Test() cursor flags.
+*/
+#define CsrFlagSet(pCsr, flag) ((pCsr)->csrflags |= (flag))
+#define CsrFlagClear(pCsr, flag) ((pCsr)->csrflags &= ~(flag))
+#define CsrFlagTest(pCsr, flag) ((pCsr)->csrflags & (flag))
+
+struct Fts5Auxdata {
+ Fts5Auxiliary *pAux; /* Extension to which this belongs */
+ void *pPtr; /* Pointer value */
+ void(*xDelete)(void*); /* Destructor */
+ Fts5Auxdata *pNext; /* Next object in linked list */
+};
+
+#ifdef SQLITE_DEBUG
+#define FTS5_BEGIN 1
+#define FTS5_SYNC 2
+#define FTS5_COMMIT 3
+#define FTS5_ROLLBACK 4
+#define FTS5_SAVEPOINT 5
+#define FTS5_RELEASE 6
+#define FTS5_ROLLBACKTO 7
+static void fts5CheckTransactionState(Fts5Table *p, int op, int iSavepoint){
+ switch( op ){
+ case FTS5_BEGIN:
+ assert( p->ts.eState==0 );
+ p->ts.eState = 1;
+ p->ts.iSavepoint = -1;
+ break;
+
+ case FTS5_SYNC:
+ assert( p->ts.eState==1 );
+ p->ts.eState = 2;
+ break;
+
+ case FTS5_COMMIT:
+ assert( p->ts.eState==2 );
+ p->ts.eState = 0;
+ break;
+
+ case FTS5_ROLLBACK:
+ assert( p->ts.eState==1 || p->ts.eState==2 || p->ts.eState==0 );
+ p->ts.eState = 0;
+ break;
+
+ case FTS5_SAVEPOINT:
+ assert( p->ts.eState==1 );
+ assert( iSavepoint>=0 );
+ assert( iSavepoint>p->ts.iSavepoint );
+ p->ts.iSavepoint = iSavepoint;
+ break;
+
+ case FTS5_RELEASE:
+ assert( p->ts.eState==1 );
+ assert( iSavepoint>=0 );
+ assert( iSavepoint<=p->ts.iSavepoint );
+ p->ts.iSavepoint = iSavepoint-1;
+ break;
+
+ case FTS5_ROLLBACKTO:
+ assert( p->ts.eState==1 );
+ assert( iSavepoint>=0 );
+ assert( iSavepoint<=p->ts.iSavepoint );
+ p->ts.iSavepoint = iSavepoint;
+ break;
+ }
+}
+#else
+# define fts5CheckTransactionState(x,y,z)
+#endif
+
+/*
+** Return true if pTab is a contentless table.
+*/
+static int fts5IsContentless(Fts5Table *pTab){
+ return pTab->pConfig->eContent==FTS5_CONTENT_NONE;
+}
+
+/*
+** Close a virtual table handle opened by fts5InitVtab(). If the bDestroy
+** argument is non-zero, attempt delete the shadow tables from teh database
+*/
+static int fts5FreeVtab(Fts5Table *pTab, int bDestroy){
+ int rc = SQLITE_OK;
+ if( pTab ){
+ int rc2;
+ rc2 = sqlite3Fts5IndexClose(pTab->pIndex, bDestroy);
+ if( rc==SQLITE_OK ) rc = rc2;
+ rc2 = sqlite3Fts5StorageClose(pTab->pStorage, bDestroy);
+ if( rc==SQLITE_OK ) rc = rc2;
+ sqlite3Fts5ConfigFree(pTab->pConfig);
+ sqlite3_free(pTab);
+ }
+ return rc;
+}
+
+/*
+** The xDisconnect() virtual table method.
+*/
+static int fts5DisconnectMethod(sqlite3_vtab *pVtab){
+ return fts5FreeVtab((Fts5Table*)pVtab, 0);
+}
+
+/*
+** The xDestroy() virtual table method.
+*/
+static int fts5DestroyMethod(sqlite3_vtab *pVtab){
+ return fts5FreeVtab((Fts5Table*)pVtab, 1);
+}
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the FTS3 virtual table.
+**
+** The argv[] array contains the following:
+**
+** argv[0] -> module name ("fts5")
+** argv[1] -> database name
+** argv[2] -> table name
+** argv[...] -> "column name" and other module argument fields.
+*/
+static int fts5InitVtab(
+ int bCreate, /* True for xCreate, false for xConnect */
+ sqlite3 *db, /* The SQLite database connection */
+ void *pAux, /* Hash table containing tokenizers */
+ int argc, /* Number of elements in argv array */
+ const char * const *argv, /* xCreate/xConnect argument array */
+ sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
+ char **pzErr /* Write any error message here */
+){
+ Fts5Global *pGlobal = (Fts5Global*)pAux;
+ const char **azConfig = (const char**)argv;
+ int rc = SQLITE_OK; /* Return code */
+ Fts5Config *pConfig; /* Results of parsing argc/argv */
+ Fts5Table *pTab = 0; /* New virtual table object */
+
+ /* Allocate the new vtab object and parse the configuration */
+ pTab = (Fts5Table*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Table));
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5ConfigParse(pGlobal, db, argc, azConfig, &pConfig, pzErr);
+ assert( (rc==SQLITE_OK && *pzErr==0) || pConfig==0 );
+ }
+ if( rc==SQLITE_OK ){
+ pTab->pConfig = pConfig;
+ pTab->pGlobal = pGlobal;
+ }
+
+ /* Open the index sub-system */
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5IndexOpen(pConfig, bCreate, &pTab->pIndex, pzErr);
+ }
+
+ /* Open the storage sub-system */
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5StorageOpen(
+ pConfig, pTab->pIndex, bCreate, &pTab->pStorage, pzErr
+ );
+ }
+
+ /* Call sqlite3_declare_vtab() */
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5ConfigDeclareVtab(pConfig);
+ }
+
+ if( rc!=SQLITE_OK ){
+ fts5FreeVtab(pTab, 0);
+ pTab = 0;
+ }else if( bCreate ){
+ fts5CheckTransactionState(pTab, FTS5_BEGIN, 0);
+ }
+ *ppVTab = (sqlite3_vtab*)pTab;
+ return rc;
+}
+
+/*
+** The xConnect() and xCreate() methods for the virtual table. All the
+** work is done in function fts5InitVtab().
+*/
+static int fts5ConnectMethod(
+ sqlite3 *db, /* Database connection */
+ void *pAux, /* Pointer to tokenizer hash table */
+ int argc, /* Number of elements in argv array */
+ const char * const *argv, /* xCreate/xConnect argument array */
+ sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
+ char **pzErr /* OUT: sqlite3_malloc'd error message */
+){
+ return fts5InitVtab(0, db, pAux, argc, argv, ppVtab, pzErr);
+}
+static int fts5CreateMethod(
+ sqlite3 *db, /* Database connection */
+ void *pAux, /* Pointer to tokenizer hash table */
+ int argc, /* Number of elements in argv array */
+ const char * const *argv, /* xCreate/xConnect argument array */
+ sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
+ char **pzErr /* OUT: sqlite3_malloc'd error message */
+){
+ return fts5InitVtab(1, db, pAux, argc, argv, ppVtab, pzErr);
+}
+
+/*
+** The three query plans xBestIndex may choose between.
+*/
+#define FTS5_PLAN_SCAN 1 /* No usable constraint */
+#define FTS5_PLAN_MATCH 2 /* (<tbl> MATCH ?) */
+#define FTS5_PLAN_SORTED_MATCH 3 /* (<tbl> MATCH ? ORDER BY rank) */
+#define FTS5_PLAN_ROWID 4 /* (rowid = ?) */
+#define FTS5_PLAN_SOURCE 5 /* A source cursor for SORTED_MATCH */
+#define FTS5_PLAN_SPECIAL 6 /* An internal query */
+
+#define FTS5_PLAN(idxNum) ((idxNum) & 0x7)
+
+#define FTS5_ORDER_DESC 8 /* ORDER BY rowid DESC */
+#define FTS5_ORDER_ASC 16 /* ORDER BY rowid ASC */
+
+/*
+** Search the object passed as the first argument for a usable constraint
+** on column iCol using operator eOp. If one is found, return its index in
+** the pInfo->aConstraint[] array. If no such constraint is found, return
+** a negative value.
+*/
+static int fts5FindConstraint(sqlite3_index_info *pInfo, int eOp, int iCol){
+ int i;
+ for(i=0; i<pInfo->nConstraint; i++){
+ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
+ if( p->usable && p->iColumn==iCol && p->op==eOp ) return i;
+ }
+ return -1;
+}
+
+/*
+** Implementation of the xBestIndex method for FTS5 tables. There
+** are three possible strategies, in order of preference:
+**
+** 1. Full-text search using a MATCH operator.
+** 2. A by-rowid lookup.
+** 3. A full-table scan.
+*/
+static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
+ Fts5Table *pTab = (Fts5Table*)pVTab;
+ Fts5Config *pConfig = pTab->pConfig;
+ int iCons;
+ int ePlan = FTS5_PLAN_SCAN;
+ int iRankMatch;
+
+ iCons = fts5FindConstraint(pInfo,SQLITE_INDEX_CONSTRAINT_MATCH,pConfig->nCol);
+ if( iCons>=0 ){
+ ePlan = FTS5_PLAN_MATCH;
+ pInfo->estimatedCost = 1.0;
+ }else{
+ iCons = fts5FindConstraint(pInfo, SQLITE_INDEX_CONSTRAINT_EQ, -1);
+ if( iCons>=0 ){
+ ePlan = FTS5_PLAN_ROWID;
+ pInfo->estimatedCost = 2.0;
+ }
+ }
+
+ if( iCons>=0 ){
+ pInfo->aConstraintUsage[iCons].argvIndex = 1;
+ pInfo->aConstraintUsage[iCons].omit = 1;
+ }else{
+ pInfo->estimatedCost = 10000000.0;
+ }
+
+ if( pInfo->nOrderBy==1 ){
+ int iSort = pInfo->aOrderBy[0].iColumn;
+ if( iSort<0 ){
+ /* ORDER BY rowid [ASC|DESC] */
+ pInfo->orderByConsumed = 1;
+ }else if( iSort==(pConfig->nCol+1) && ePlan==FTS5_PLAN_MATCH ){
+ /* ORDER BY rank [ASC|DESC] */
+ pInfo->orderByConsumed = 1;
+ ePlan = FTS5_PLAN_SORTED_MATCH;
+ }
+
+ if( pInfo->orderByConsumed ){
+ ePlan |= pInfo->aOrderBy[0].desc ? FTS5_ORDER_DESC : FTS5_ORDER_ASC;
+ }
+ }
+
+ iRankMatch = fts5FindConstraint(
+ pInfo, SQLITE_INDEX_CONSTRAINT_MATCH, pConfig->nCol+1
+ );
+ if( iRankMatch>=0 ){
+ pInfo->aConstraintUsage[iRankMatch].argvIndex = 1 + (iCons>=0);
+ pInfo->aConstraintUsage[iRankMatch].omit = 1;
+ }
+
+ pInfo->idxNum = ePlan;
+ return SQLITE_OK;
+}
+
+/*
+** Implementation of xOpen method.
+*/
+static int fts5OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
+ Fts5Table *pTab = (Fts5Table*)pVTab;
+ Fts5Config *pConfig = pTab->pConfig;
+ Fts5Cursor *pCsr; /* New cursor object */
+ int nByte; /* Bytes of space to allocate */
+ int rc = SQLITE_OK; /* Return code */
+
+ nByte = sizeof(Fts5Cursor) + pConfig->nCol * sizeof(int);
+ pCsr = (Fts5Cursor*)sqlite3_malloc(nByte);
+ if( pCsr ){
+ Fts5Global *pGlobal = pTab->pGlobal;
+ memset(pCsr, 0, nByte);
+ pCsr->aColumnSize = (int*)&pCsr[1];
+ pCsr->pNext = pGlobal->pCsr;
+ pGlobal->pCsr = pCsr;
+ pCsr->iCsrId = ++pGlobal->iNextId;
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ *ppCsr = (sqlite3_vtab_cursor*)pCsr;
+ return rc;
+}
+
+static int fts5StmtType(int idxNum){
+ if( FTS5_PLAN(idxNum)==FTS5_PLAN_SCAN ){
+ return (idxNum&FTS5_ORDER_DESC) ? FTS5_STMT_SCAN_DESC : FTS5_STMT_SCAN_ASC;
+ }
+ return FTS5_STMT_LOOKUP;
+}
+
+/*
+** This function is called after the cursor passed as the only argument
+** is moved to point at a different row. It clears all cached data
+** specific to the previous row stored by the cursor object.
+*/
+static void fts5CsrNewrow(Fts5Cursor *pCsr){
+ CsrFlagSet(pCsr, FTS5CSR_REQUIRE_CONTENT | FTS5CSR_REQUIRE_DOCSIZE );
+ sqlite3_free(pCsr->aInst);
+ pCsr->aInst = 0;
+ pCsr->nInstCount = 0;
+}
+
+/*
+** Close the cursor. For additional information see the documentation
+** on the xClose method of the virtual table interface.
+*/
+static int fts5CloseMethod(sqlite3_vtab_cursor *pCursor){
+ Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
+ Fts5Cursor **pp;
+ Fts5Auxdata *pData;
+ Fts5Auxdata *pNext;
+
+ fts5CsrNewrow(pCsr);
+ if( pCsr->pStmt ){
+ int eStmt = fts5StmtType(pCsr->idxNum);
+ sqlite3Fts5StorageStmtRelease(pTab->pStorage, eStmt, pCsr->pStmt);
+ }
+ if( pCsr->pSorter ){
+ Fts5Sorter *pSorter = pCsr->pSorter;
+ sqlite3_finalize(pSorter->pStmt);
+ sqlite3_free(pSorter);
+ }
+
+ if( pCsr->idxNum!=FTS5_PLAN_SOURCE ){
+ sqlite3Fts5ExprFree(pCsr->pExpr);
+ }
+
+ for(pData=pCsr->pAuxdata; pData; pData=pNext){
+ pNext = pData->pNext;
+ if( pData->xDelete ) pData->xDelete(pData->pPtr);
+ sqlite3_free(pData);
+ }
+
+ /* Remove the cursor from the Fts5Global.pCsr list */
+ for(pp=&pTab->pGlobal->pCsr; (*pp)!=pCsr; pp=&(*pp)->pNext);
+ *pp = pCsr->pNext;
+
+ sqlite3_finalize(pCsr->pRankArgStmt);
+ sqlite3_free(pCsr->apRankArg);
+
+ sqlite3_free(pCsr->zSpecial);
+ if( CsrFlagTest(pCsr, FTS5CSR_FREE_ZRANK) ){
+ sqlite3_free(pCsr->zRank);
+ sqlite3_free(pCsr->zRankArgs);
+ }
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+static int fts5SorterNext(Fts5Cursor *pCsr){
+ Fts5Sorter *pSorter = pCsr->pSorter;
+ int rc;
+
+ rc = sqlite3_step(pSorter->pStmt);
+ if( rc==SQLITE_DONE ){
+ rc = SQLITE_OK;
+ CsrFlagSet(pCsr, FTS5CSR_EOF);
+ }else if( rc==SQLITE_ROW ){
+ const u8 *a;
+ const u8 *aBlob;
+ int nBlob;
+ int i;
+ int iOff = 0;
+ rc = SQLITE_OK;
+
+ pSorter->iRowid = sqlite3_column_int64(pSorter->pStmt, 0);
+ nBlob = sqlite3_column_bytes(pSorter->pStmt, 1);
+ aBlob = a = sqlite3_column_blob(pSorter->pStmt, 1);
+
+ for(i=0; i<(pSorter->nIdx-1); i++){
+ int iVal;
+ a += getVarint32(a, iVal);
+ iOff += iVal;
+ pSorter->aIdx[i] = iOff;
+ }
+ pSorter->aIdx[i] = &aBlob[nBlob] - a;
+
+ pSorter->aPoslist = a;
+ fts5CsrNewrow(pCsr);
+ }
+
+ return rc;
+}
+
+/*
+** Advance the cursor to the next row in the table that matches the
+** search criteria.
+**
+** Return SQLITE_OK if nothing goes wrong. SQLITE_OK is returned
+** even if we reach end-of-file. The fts5EofMethod() will be called
+** subsequently to determine whether or not an EOF was hit.
+*/
+static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
+ int ePlan = FTS5_PLAN(pCsr->idxNum);
+ int rc = SQLITE_OK;
+
+ switch( ePlan ){
+ case FTS5_PLAN_MATCH:
+ case FTS5_PLAN_SOURCE:
+ rc = sqlite3Fts5ExprNext(pCsr->pExpr);
+ if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
+ CsrFlagSet(pCsr, FTS5CSR_EOF);
+ }
+ fts5CsrNewrow(pCsr);
+ break;
+
+ case FTS5_PLAN_SPECIAL: {
+ CsrFlagSet(pCsr, FTS5CSR_EOF);
+ break;
+ }
+
+ case FTS5_PLAN_SORTED_MATCH: {
+ rc = fts5SorterNext(pCsr);
+ break;
+ }
+
+ default:
+ rc = sqlite3_step(pCsr->pStmt);
+ if( rc!=SQLITE_ROW ){
+ CsrFlagSet(pCsr, FTS5CSR_EOF);
+ rc = sqlite3_reset(pCsr->pStmt);
+ }else{
+ rc = SQLITE_OK;
+ }
+ break;
+ }
+
+ return rc;
+}
+
+static int fts5CursorFirstSorted(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
+ Fts5Config *pConfig = pTab->pConfig;
+ Fts5Sorter *pSorter;
+ int nPhrase;
+ int nByte;
+ int rc = SQLITE_OK;
+ char *zSql;
+ const char *zRank = pCsr->zRank;
+ const char *zRankArgs = pCsr->zRankArgs;
+
+ nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+ nByte = sizeof(Fts5Sorter) + sizeof(int) * nPhrase;
+ pSorter = (Fts5Sorter*)sqlite3_malloc(nByte);
+ if( pSorter==0 ) return SQLITE_NOMEM;
+ memset(pSorter, 0, nByte);
+ pSorter->nIdx = nPhrase;
+
+ /* TODO: It would be better to have some system for reusing statement
+ ** handles here, rather than preparing a new one for each query. But that
+ ** is not possible as SQLite reference counts the virtual table objects.
+ ** And since the statement required here reads from this very virtual
+ ** table, saving it creates a circular reference.
+ **
+ ** If SQLite a built-in statement cache, this wouldn't be a problem. */
+ zSql = sqlite3_mprintf("SELECT rowid, rank FROM %Q.%Q ORDER BY %s(%s%s%s) %s",
+ pConfig->zDb, pConfig->zName, zRank, pConfig->zName,
+ (zRankArgs ? ", " : ""),
+ (zRankArgs ? zRankArgs : ""),
+ bDesc ? "DESC" : "ASC"
+ );
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pSorter->pStmt, 0);
+ sqlite3_free(zSql);
+ }
+
+ pCsr->pSorter = pSorter;
+ if( rc==SQLITE_OK ){
+ assert( pTab->pSortCsr==0 );
+ pTab->pSortCsr = pCsr;
+ rc = fts5SorterNext(pCsr);
+ pTab->pSortCsr = 0;
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_finalize(pSorter->pStmt);
+ sqlite3_free(pSorter);
+ pCsr->pSorter = 0;
+ }
+
+ return rc;
+}
+
+static int fts5CursorFirst(Fts5Table *pTab, Fts5Cursor *pCsr, int bDesc){
+ int rc;
+ rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->pIndex, bDesc);
+ if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
+ CsrFlagSet(pCsr, FTS5CSR_EOF);
+ }
+ fts5CsrNewrow(pCsr);
+ return rc;
+}
+
+/*
+** Process a "special" query. A special query is identified as one with a
+** MATCH expression that begins with a '*' character. The remainder of
+** the text passed to the MATCH operator are used as the special query
+** parameters.
+*/
+static int fts5SpecialMatch(
+ Fts5Table *pTab,
+ Fts5Cursor *pCsr,
+ const char *zQuery
+){
+ int rc = SQLITE_OK; /* Return code */
+ const char *z = zQuery; /* Special query text */
+ int n; /* Number of bytes in text at z */
+
+ while( z[0]==' ' ) z++;
+ for(n=0; z[n] && z[n]!=' '; n++);
+
+ assert( pTab->base.zErrMsg==0 );
+ assert( pCsr->zSpecial==0 );
+
+ if( 0==sqlite3_strnicmp("reads", z, n) ){
+ pCsr->zSpecial = sqlite3_mprintf("%d", sqlite3Fts5IndexReads(pTab->pIndex));
+ pCsr->idxNum = FTS5_PLAN_SPECIAL;
+ if( pCsr->zSpecial==0 ) rc = SQLITE_NOMEM;
+ }
+ else{
+ /* An unrecognized directive. Return an error message. */
+ pTab->base.zErrMsg = sqlite3_mprintf("unknown special query: %.*s", n, z);
+ rc = SQLITE_ERROR;
+ }
+
+ return rc;
+}
+
+/*
+** Search for an auxiliary function named zName that can be used with table
+** pTab. If one is found, return a pointer to the corresponding Fts5Auxiliary
+** structure. Otherwise, if no such function exists, return NULL.
+*/
+static Fts5Auxiliary *fts5FindAuxiliary(Fts5Table *pTab, const char *zName){
+ Fts5Auxiliary *pAux;
+
+ for(pAux=pTab->pGlobal->pAux; pAux; pAux=pAux->pNext){
+ if( sqlite3_stricmp(zName, pAux->zFunc)==0 ) return pAux;
+ }
+
+ /* No function of the specified name was found. Return 0. */
+ return 0;
+}
+
+
+static int fts5FindRankFunction(Fts5Cursor *pCsr){
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ Fts5Config *pConfig = pTab->pConfig;
+ int rc = SQLITE_OK;
+ Fts5Auxiliary *pAux = 0;
+ const char *zRank = pCsr->zRank;
+ const char *zRankArgs = pCsr->zRankArgs;
+
+ if( zRankArgs ){
+ char *zSql = sqlite3_mprintf("SELECT %s", zRankArgs);
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ sqlite3_stmt *pStmt = 0;
+ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ assert( rc==SQLITE_OK || pCsr->pRankArgStmt==0 );
+ if( rc==SQLITE_OK ){
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ int nByte;
+ pCsr->nRankArg = sqlite3_column_count(pStmt);
+ nByte = sizeof(sqlite3_value*)*pCsr->nRankArg;
+ pCsr->apRankArg = (sqlite3_value**)sqlite3Fts5MallocZero(&rc, nByte);
+ if( rc==SQLITE_OK ){
+ int i;
+ for(i=0; i<pCsr->nRankArg; i++){
+ pCsr->apRankArg[i] = sqlite3_column_value(pStmt, i);
+ }
+ }
+ pCsr->pRankArgStmt = pStmt;
+ }else{
+ rc = sqlite3_finalize(pStmt);
+ assert( rc!=SQLITE_OK );
+ }
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ pAux = fts5FindAuxiliary(pTab, zRank);
+ if( pAux==0 ){
+ assert( pTab->base.zErrMsg==0 );
+ pTab->base.zErrMsg = sqlite3_mprintf("no such function: %s", zRank);
+ rc = SQLITE_ERROR;
+ }
+ }
+
+ pCsr->pRank = pAux;
+ return rc;
+}
+
+
+static int fts5CursorParseRank(
+ Fts5Config *pConfig,
+ Fts5Cursor *pCsr,
+ sqlite3_value *pRank
+){
+ int rc = SQLITE_OK;
+ if( pRank ){
+ const char *z = (const char*)sqlite3_value_text(pRank);
+ char *zRank = 0;
+ char *zRankArgs = 0;
+
+ rc = sqlite3Fts5ConfigParseRank(z, &zRank, &zRankArgs);
+ if( rc==SQLITE_OK ){
+ pCsr->zRank = zRank;
+ pCsr->zRankArgs = zRankArgs;
+ CsrFlagSet(pCsr, FTS5CSR_FREE_ZRANK);
+ }else if( rc==SQLITE_ERROR ){
+ pCsr->base.pVtab->zErrMsg = sqlite3_mprintf(
+ "parse error in rank function: %s", z
+ );
+ }
+ }else{
+ if( pConfig->zRank ){
+ pCsr->zRank = (char*)pConfig->zRank;
+ pCsr->zRankArgs = (char*)pConfig->zRankArgs;
+ }else{
+ pCsr->zRank = (char*)FTS5_DEFAULT_RANK;
+ pCsr->zRankArgs = 0;
+ }
+ }
+ return rc;
+}
+
+/*
+** This is the xFilter interface for the virtual table. See
+** the virtual table xFilter method documentation for additional
+** information.
+**
+** There are three possible query strategies:
+**
+** 1. Full-text search using a MATCH operator.
+** 2. A by-rowid lookup.
+** 3. A full-table scan.
+*/
+static int fts5FilterMethod(
+ sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
+ int idxNum, /* Strategy index */
+ const char *idxStr, /* Unused */
+ int nVal, /* Number of elements in apVal */
+ sqlite3_value **apVal /* Arguments for the indexing scheme */
+){
+ Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
+ int bDesc = ((idxNum & FTS5_ORDER_DESC) ? 1 : 0);
+ int rc = SQLITE_OK;
+
+ assert( nVal<=2 );
+ assert( pCsr->pStmt==0 );
+ assert( pCsr->pExpr==0 );
+ assert( pCsr->csrflags==0 );
+ assert( pCsr->pRank==0 );
+ assert( pCsr->zRank==0 );
+ assert( pCsr->zRankArgs==0 );
+
+ if( pTab->pSortCsr ){
+ /* If pSortCsr is non-NULL, then this call is being made as part of
+ ** processing for a "... MATCH <expr> ORDER BY rank" query (ePlan is
+ ** set to FTS5_PLAN_SORTED_MATCH). pSortCsr is the cursor that will
+ ** return results to the user for this query. The current cursor
+ ** (pCursor) is used to execute the query issued by function
+ ** fts5CursorFirstSorted() above. */
+ assert( FTS5_PLAN(idxNum)==FTS5_PLAN_SCAN );
+ pCsr->idxNum = FTS5_PLAN_SOURCE;
+ pCsr->pExpr = pTab->pSortCsr->pExpr;
+ rc = fts5CursorFirst(pTab, pCsr, bDesc);
+ }else{
+ int ePlan = FTS5_PLAN(idxNum);
+ pCsr->idxNum = idxNum;
+ if( ePlan==FTS5_PLAN_MATCH || ePlan==FTS5_PLAN_SORTED_MATCH ){
+ const char *zExpr = (const char*)sqlite3_value_text(apVal[0]);
+
+ rc = fts5CursorParseRank(pTab->pConfig, pCsr, (nVal==2 ? apVal[1] : 0));
+ if( rc==SQLITE_OK ){
+ if( zExpr[0]=='*' ){
+ /* The user has issued a query of the form "MATCH '*...'". This
+ ** indicates that the MATCH expression is not a full text query,
+ ** but a request for an internal parameter. */
+ rc = fts5SpecialMatch(pTab, pCsr, &zExpr[1]);
+ }else{
+ char **pzErr = &pTab->base.zErrMsg;
+ rc = sqlite3Fts5ExprNew(pTab->pConfig, zExpr, &pCsr->pExpr, pzErr);
+ if( rc==SQLITE_OK ){
+ if( ePlan==FTS5_PLAN_MATCH ){
+ rc = fts5CursorFirst(pTab, pCsr, bDesc);
+ }else{
+ rc = fts5CursorFirstSorted(pTab, pCsr, bDesc);
+ }
+ }
+ }
+ }
+ }else{
+ /* This is either a full-table scan (ePlan==FTS5_PLAN_SCAN) or a lookup
+ ** by rowid (ePlan==FTS5_PLAN_ROWID). */
+ int eStmt = fts5StmtType(idxNum);
+ rc = sqlite3Fts5StorageStmt(
+ pTab->pStorage, eStmt, &pCsr->pStmt, &pTab->base.zErrMsg
+ );
+ if( rc==SQLITE_OK ){
+ if( ePlan==FTS5_PLAN_ROWID ){
+ sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]);
+ }
+ rc = fts5NextMethod(pCursor);
+ }
+ }
+ }
+
+ return rc;
+}
+
+/*
+** This is the xEof method of the virtual table. SQLite calls this
+** routine to find out if it has reached the end of a result set.
+*/
+static int fts5EofMethod(sqlite3_vtab_cursor *pCursor){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
+ return (CsrFlagTest(pCsr, FTS5CSR_EOF) ? 1 : 0);
+}
+
+/*
+** Return the rowid that the cursor currently points to.
+*/
+static i64 fts5CursorRowid(Fts5Cursor *pCsr){
+ assert( FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_MATCH
+ || FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_SORTED_MATCH
+ || FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_SOURCE
+ );
+ if( pCsr->pSorter ){
+ return pCsr->pSorter->iRowid;
+ }else{
+ return sqlite3Fts5ExprRowid(pCsr->pExpr);
+ }
+}
+
+/*
+** This is the xRowid method. The SQLite core calls this routine to
+** retrieve the rowid for the current row of the result set. fts5
+** exposes %_content.docid as the rowid for the virtual table. The
+** rowid should be written to *pRowid.
+*/
+static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
+ int ePlan = FTS5_PLAN(pCsr->idxNum);
+
+ assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 );
+ switch( ePlan ){
+ case FTS5_PLAN_SPECIAL:
+ *pRowid = 0;
+ break;
+
+ case FTS5_PLAN_SOURCE:
+ case FTS5_PLAN_MATCH:
+ case FTS5_PLAN_SORTED_MATCH:
+ *pRowid = fts5CursorRowid(pCsr);
+ break;
+
+ default:
+ *pRowid = sqlite3_column_int64(pCsr->pStmt, 0);
+ break;
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** If the cursor requires seeking (bSeekRequired flag is set), seek it.
+** Return SQLITE_OK if no error occurs, or an SQLite error code otherwise.
+*/
+static int fts5SeekCursor(Fts5Cursor *pCsr){
+ int rc = SQLITE_OK;
+
+ /* If the cursor does not yet have a statement handle, obtain one now. */
+ if( pCsr->pStmt==0 ){
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ int eStmt = fts5StmtType(pCsr->idxNum);
+ rc = sqlite3Fts5StorageStmt(
+ pTab->pStorage, eStmt, &pCsr->pStmt, &pTab->base.zErrMsg
+ );
+ assert( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) );
+ }
+
+ if( rc==SQLITE_OK && CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) ){
+ assert( pCsr->pExpr );
+ sqlite3_reset(pCsr->pStmt);
+ sqlite3_bind_int64(pCsr->pStmt, 1, fts5CursorRowid(pCsr));
+ rc = sqlite3_step(pCsr->pStmt);
+ if( rc==SQLITE_ROW ){
+ rc = SQLITE_OK;
+ CsrFlagClear(pCsr, FTS5CSR_REQUIRE_CONTENT);
+ }else{
+ rc = sqlite3_reset(pCsr->pStmt);
+ if( rc==SQLITE_OK ){
+ rc = FTS5_CORRUPT;
+ }
+ }
+ }
+ return rc;
+}
+
+static void fts5SetVtabError(Fts5Table *p, const char *zFormat, ...){
+ va_list ap; /* ... printf arguments */
+ va_start(ap, zFormat);
+ assert( p->base.zErrMsg==0 );
+ p->base.zErrMsg = sqlite3_vmprintf(zFormat, ap);
+ va_end(ap);
+}
+
+/*
+** This function is called to handle an FTS INSERT command. In other words,
+** an INSERT statement of the form:
+**
+** INSERT INTO fts(fts) VALUES($pCmd)
+** INSERT INTO fts(fts, rank) VALUES($pCmd, $pVal)
+**
+** Argument pVal is the value assigned to column "fts" by the INSERT
+** statement. This function returns SQLITE_OK if successful, or an SQLite
+** error code if an error occurs.
+**
+** The commands implemented by this function are documented in the "Special
+** INSERT Directives" section of the documentation. It should be updated if
+** more commands are added to this function.
+*/
+static int fts5SpecialInsert(
+ Fts5Table *pTab, /* Fts5 table object */
+ sqlite3_value *pCmd, /* Value inserted into special column */
+ sqlite3_value *pVal /* Value inserted into rowid column */
+){
+ Fts5Config *pConfig = pTab->pConfig;
+ const char *z = (const char*)sqlite3_value_text(pCmd);
+ int rc = SQLITE_OK;
+ int bError = 0;
+
+ if( 0==sqlite3_stricmp("delete-all", z) ){
+ if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ fts5SetVtabError(pTab,
+ "'delete-all' may only be used with a "
+ "contentless or external content fts5 table"
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage);
+ }
+ }else if( 0==sqlite3_stricmp("rebuild", z) ){
+ if( pConfig->eContent==FTS5_CONTENT_NONE ){
+ fts5SetVtabError(pTab,
+ "'rebuild' may not be used with a contentless fts5 table"
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ rc = sqlite3Fts5StorageRebuild(pTab->pStorage);
+ }
+ }else if( 0==sqlite3_stricmp("optimize", z) ){
+ rc = sqlite3Fts5StorageOptimize(pTab->pStorage);
+ }else if( 0==sqlite3_stricmp("integrity-check", z) ){
+ rc = sqlite3Fts5StorageIntegrity(pTab->pStorage);
+ }else{
+ rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5ConfigSetValue(pTab->pConfig, z, pVal, &bError);
+ }
+ if( rc==SQLITE_OK ){
+ if( bError ){
+ rc = SQLITE_ERROR;
+ }else{
+ rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, z, pVal);
+ }
+ }
+ }
+ return rc;
+}
+
+static int fts5SpecialDelete(
+ Fts5Table *pTab,
+ sqlite3_value **apVal,
+ sqlite3_int64 *piRowid
+){
+ int rc = SQLITE_OK;
+ int eType1 = sqlite3_value_type(apVal[1]);
+ if( eType1==SQLITE_INTEGER ){
+ sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]);
+ rc = sqlite3Fts5StorageSpecialDelete(pTab->pStorage, iDel, &apVal[2]);
+ }
+ 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
+** inserted, updated or deleted.
+*/
+static int fts5UpdateMethod(
+ sqlite3_vtab *pVtab, /* Virtual table handle */
+ int nArg, /* Size of argument array */
+ sqlite3_value **apVal, /* Array of arguments */
+ sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */
+){
+ Fts5Table *pTab = (Fts5Table*)pVtab;
+ Fts5Config *pConfig = pTab->pConfig;
+ int eType0; /* value_type() of apVal[0] */
+ int eConflict; /* ON CONFLICT for this DML */
+ int rc = SQLITE_OK; /* Return code */
+
+ /* A transaction must be open when this is called. */
+ assert( pTab->ts.eState==1 );
+
+ /* A delete specifies a single argument - the rowid of the row to remove.
+ ** Update and insert operations pass:
+ **
+ ** 1. The "old" rowid, or NULL.
+ ** 2. The "new" rowid.
+ ** 3. Values for each of the nCol matchable columns.
+ ** 4. Values for the two hidden columns (<tablename> and "rank").
+ */
+ assert( nArg==1 || nArg==(2 + pConfig->nCol + 2) );
+
+ eType0 = sqlite3_value_type(apVal[0]);
+ eConflict = sqlite3_vtab_on_conflict(pConfig->db);
+
+ assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL );
+ assert( pVtab->zErrMsg==0 );
+
+ if( rc==SQLITE_OK && eType0==SQLITE_INTEGER ){
+ if( fts5IsContentless(pTab) ){
+ pTab->base.zErrMsg = sqlite3_mprintf(
+ "cannot %s contentless fts5 table: %s",
+ (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */
+ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel);
+ }
+ }else if( nArg>1 ){
+ sqlite3_value *pCmd = apVal[2 + pConfig->nCol];
+ if( SQLITE_NULL!=sqlite3_value_type(pCmd) ){
+ const char *z = (const char*)sqlite3_value_text(pCmd);
+ if( pConfig->eContent!=FTS5_CONTENT_NORMAL
+ && 0==sqlite3_stricmp("delete", z)
+ ){
+ return fts5SpecialDelete(pTab, apVal, pRowid);
+ }else{
+ return fts5SpecialInsert(pTab, pCmd, apVal[2 + pConfig->nCol + 1]);
+ }
+ }
+ }
+
+
+ if( rc==SQLITE_OK && nArg>1 ){
+ rc = sqlite3Fts5StorageInsert(pTab->pStorage, apVal, eConflict, pRowid);
+ }
+
+ return rc;
+}
+
+/*
+** Implementation of xSync() method.
+*/
+static int fts5SyncMethod(sqlite3_vtab *pVtab){
+ int rc;
+ Fts5Table *pTab = (Fts5Table*)pVtab;
+ fts5CheckTransactionState(pTab, FTS5_SYNC, 0);
+ rc = sqlite3Fts5StorageSync(pTab->pStorage, 1);
+ return rc;
+}
+
+/*
+** Implementation of xBegin() method.
+*/
+static int fts5BeginMethod(sqlite3_vtab *pVtab){
+ fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_BEGIN, 0);
+ return SQLITE_OK;
+}
+
+/*
+** 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 fts5SyncMethod().
+*/
+static int fts5CommitMethod(sqlite3_vtab *pVtab){
+ fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_COMMIT, 0);
+ return SQLITE_OK;
+}
+
+/*
+** Implementation of xRollback(). Discard the contents of the pending-terms
+** hash-table. Any changes made to the database are reverted by SQLite.
+*/
+static int fts5RollbackMethod(sqlite3_vtab *pVtab){
+ int rc;
+ Fts5Table *pTab = (Fts5Table*)pVtab;
+ fts5CheckTransactionState(pTab, FTS5_ROLLBACK, 0);
+ rc = sqlite3Fts5StorageRollback(pTab->pStorage);
+ return rc;
+}
+
+static void *fts5ApiUserData(Fts5Context *pCtx){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ return pCsr->pAux->pUserData;
+}
+
+static int fts5ApiColumnCount(Fts5Context *pCtx){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ return ((Fts5Table*)(pCsr->base.pVtab))->pConfig->nCol;
+}
+
+static int fts5ApiColumnTotalSize(
+ Fts5Context *pCtx,
+ int iCol,
+ sqlite3_int64 *pnToken
+){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ return sqlite3Fts5StorageSize(pTab->pStorage, iCol, pnToken);
+}
+
+static int fts5ApiRowCount(Fts5Context *pCtx, i64 *pnRow){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ return sqlite3Fts5StorageRowCount(pTab->pStorage, pnRow);
+}
+
+static int fts5ApiTokenize(
+ Fts5Context *pCtx,
+ const char *pText, int nText,
+ void *pUserData,
+ int (*xToken)(void*, const char*, int, int, int)
+){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ return sqlite3Fts5Tokenize(pTab->pConfig, pText, nText, pUserData, xToken);
+}
+
+static int fts5ApiPhraseCount(Fts5Context *pCtx){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ return sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+}
+
+static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase);
+}
+
+static int fts5CsrPoslist(Fts5Cursor *pCsr, int iPhrase, const u8 **pa){
+ int n;
+ if( pCsr->pSorter ){
+ Fts5Sorter *pSorter = pCsr->pSorter;
+ int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]);
+ n = pSorter->aIdx[iPhrase] - i1;
+ *pa = &pSorter->aPoslist[i1];
+ }else{
+ n = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa);
+ }
+ return n;
+}
+
+/*
+** Ensure that the Fts5Cursor.nInstCount and aInst[] variables are populated
+** correctly for the current view. Return SQLITE_OK if successful, or an
+** SQLite error code otherwise.
+*/
+static int fts5CacheInstArray(Fts5Cursor *pCsr){
+ int rc = SQLITE_OK;
+ if( pCsr->aInst==0 ){
+ Fts5PoslistReader *aIter; /* One iterator for each phrase */
+ int nIter; /* Number of iterators/phrases */
+ int nByte;
+
+ nIter = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+ nByte = sizeof(Fts5PoslistReader) * nIter;
+ aIter = (Fts5PoslistReader*)sqlite3Fts5MallocZero(&rc, nByte);
+ if( aIter ){
+ Fts5Buffer buf = {0, 0, 0}; /* Build up aInst[] here */
+ int nInst = 0; /* Number instances seen so far */
+ int i;
+
+ /* Initialize all iterators */
+ for(i=0; i<nIter; i++){
+ const u8 *a;
+ int n = fts5CsrPoslist(pCsr, i, &a);
+ sqlite3Fts5PoslistReaderInit(-1, a, n, &aIter[i]);
+ }
+
+ while( 1 ){
+ int *aInst;
+ int iBest = -1;
+ for(i=0; i<nIter; i++){
+ if( (aIter[i].bEof==0)
+ && (iBest<0 || aIter[i].iPos<aIter[iBest].iPos)
+ ){
+ iBest = i;
+ }
+ }
+
+ if( iBest<0 ) break;
+ nInst++;
+ if( sqlite3Fts5BufferGrow(&rc, &buf, nInst * sizeof(int) * 3) ) break;
+
+ aInst = &((int*)buf.p)[3 * (nInst-1)];
+ aInst[0] = iBest;
+ aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos);
+ aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos);
+ sqlite3Fts5PoslistReaderNext(&aIter[iBest]);
+ }
+
+ pCsr->aInst = (int*)buf.p;
+ pCsr->nInstCount = nInst;
+ sqlite3_free(aIter);
+ }
+ }
+ return rc;
+}
+
+static int fts5ApiInstCount(Fts5Context *pCtx, int *pnInst){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ int rc;
+ if( SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) ){
+ *pnInst = pCsr->nInstCount;
+ }
+ return rc;
+}
+
+static int fts5ApiInst(
+ Fts5Context *pCtx,
+ int iIdx,
+ int *piPhrase,
+ int *piCol,
+ int *piOff
+){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ int rc;
+ if( SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) ){
+ if( iIdx<0 || iIdx>=pCsr->nInstCount ){
+ rc = SQLITE_RANGE;
+ }else{
+ *piPhrase = pCsr->aInst[iIdx*3];
+ *piCol = pCsr->aInst[iIdx*3 + 1];
+ *piOff = pCsr->aInst[iIdx*3 + 2];
+ }
+ }
+ return rc;
+}
+
+static sqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){
+ return fts5CursorRowid((Fts5Cursor*)pCtx);
+}
+
+static int fts5ApiColumnText(
+ Fts5Context *pCtx,
+ int iCol,
+ const char **pz,
+ int *pn
+){
+ int rc = SQLITE_OK;
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ if( fts5IsContentless((Fts5Table*)(pCsr->base.pVtab)) ){
+ *pz = 0;
+ *pn = 0;
+ }else{
+ rc = fts5SeekCursor(pCsr);
+ if( rc==SQLITE_OK ){
+ *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol+1);
+ *pn = sqlite3_column_bytes(pCsr->pStmt, iCol+1);
+ }
+ }
+ return rc;
+}
+
+static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ int rc = SQLITE_OK;
+
+ if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_DOCSIZE) ){
+ i64 iRowid = fts5CursorRowid(pCsr);
+ rc = sqlite3Fts5StorageDocsize(pTab->pStorage, iRowid, pCsr->aColumnSize);
+ }
+ if( iCol<0 ){
+ int i;
+ *pnToken = 0;
+ for(i=0; i<pTab->pConfig->nCol; i++){
+ *pnToken += pCsr->aColumnSize[i];
+ }
+ }else if( iCol<pTab->pConfig->nCol ){
+ *pnToken = pCsr->aColumnSize[iCol];
+ }else{
+ *pnToken = 0;
+ rc = SQLITE_RANGE;
+ }
+ return rc;
+}
+
+static int fts5ApiSetAuxdata(
+ Fts5Context *pCtx, /* Fts5 context */
+ void *pPtr, /* Pointer to save as auxdata */
+ void(*xDelete)(void*) /* Destructor for pPtr (or NULL) */
+){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Auxdata *pData;
+
+ for(pData=pCsr->pAuxdata; pData; pData=pData->pNext){
+ if( pData->pAux==pCsr->pAux ) break;
+ }
+
+ if( pData ){
+ if( pData->xDelete ){
+ pData->xDelete(pData->pPtr);
+ }
+ }else{
+ pData = (Fts5Auxdata*)sqlite3_malloc(sizeof(Fts5Auxdata));
+ if( pData==0 ){
+ if( xDelete ) xDelete(pPtr);
+ return SQLITE_NOMEM;
+ }
+ memset(pData, 0, sizeof(Fts5Auxdata));
+ pData->pAux = pCsr->pAux;
+ pData->pNext = pCsr->pAuxdata;
+ pCsr->pAuxdata = pData;
+ }
+
+ pData->xDelete = xDelete;
+ pData->pPtr = pPtr;
+ return SQLITE_OK;
+}
+
+static void *fts5ApiGetAuxdata(Fts5Context *pCtx, int bClear){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Auxdata *pData;
+ void *pRet = 0;
+
+ for(pData=pCsr->pAuxdata; pData; pData=pData->pNext){
+ if( pData->pAux==pCsr->pAux ) break;
+ }
+
+ if( pData ){
+ pRet = pData->pPtr;
+ if( bClear ){
+ pData->pPtr = 0;
+ pData->xDelete = 0;
+ }
+ }
+
+ return pRet;
+}
+
+static int fts5ApiQueryPhrase(Fts5Context*, int, void*,
+ int(*)(const Fts5ExtensionApi*, Fts5Context*, void*)
+);
+
+static const Fts5ExtensionApi sFts5Api = {
+ 1, /* iVersion */
+ fts5ApiUserData,
+ fts5ApiColumnCount,
+ fts5ApiRowCount,
+ fts5ApiColumnTotalSize,
+ fts5ApiTokenize,
+ fts5ApiPhraseCount,
+ fts5ApiPhraseSize,
+ fts5ApiInstCount,
+ fts5ApiInst,
+ fts5ApiRowid,
+ fts5ApiColumnText,
+ fts5ApiColumnSize,
+ fts5ApiQueryPhrase,
+ fts5ApiSetAuxdata,
+ fts5ApiGetAuxdata,
+};
+
+
+/*
+** Implementation of API function xQueryPhrase().
+*/
+static int fts5ApiQueryPhrase(
+ Fts5Context *pCtx,
+ int iPhrase,
+ void *pUserData,
+ int(*xCallback)(const Fts5ExtensionApi*, Fts5Context*, void*)
+){
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx;
+ Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
+ int rc;
+ Fts5Cursor *pNew = 0;
+
+ rc = fts5OpenMethod(pCsr->base.pVtab, (sqlite3_vtab_cursor**)&pNew);
+ if( rc==SQLITE_OK ){
+ Fts5Config *pConf = pTab->pConfig;
+ pNew->idxNum = FTS5_PLAN_MATCH;
+ pNew->base.pVtab = (sqlite3_vtab*)pTab;
+ rc = sqlite3Fts5ExprPhraseExpr(pConf, pCsr->pExpr, iPhrase, &pNew->pExpr);
+ }
+
+ if( rc==SQLITE_OK ){
+ for(rc = fts5CursorFirst(pTab, pNew, 0);
+ rc==SQLITE_OK && CsrFlagTest(pNew, FTS5CSR_EOF)==0;
+ rc = fts5NextMethod((sqlite3_vtab_cursor*)pNew)
+ ){
+ rc = xCallback(&sFts5Api, (Fts5Context*)pNew, pUserData);
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ break;
+ }
+ }
+ }
+
+ fts5CloseMethod((sqlite3_vtab_cursor*)pNew);
+ return rc;
+}
+
+static void fts5ApiInvoke(
+ Fts5Auxiliary *pAux,
+ Fts5Cursor *pCsr,
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ assert( pCsr->pAux==0 );
+ pCsr->pAux = pAux;
+ pAux->xFunc(&sFts5Api, (Fts5Context*)pCsr, context, argc, argv);
+ pCsr->pAux = 0;
+}
+
+static void fts5ApiCallback(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+
+ Fts5Auxiliary *pAux;
+ Fts5Cursor *pCsr;
+ i64 iCsrId;
+
+ assert( argc>=1 );
+ pAux = (Fts5Auxiliary*)sqlite3_user_data(context);
+ iCsrId = sqlite3_value_int64(argv[0]);
+
+ for(pCsr=pAux->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){
+ if( pCsr->iCsrId==iCsrId ) break;
+ }
+ if( pCsr==0 ){
+ char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId);
+ sqlite3_result_error(context, zErr, -1);
+ }else{
+ fts5ApiInvoke(pAux, pCsr, context, argc-1, &argv[1]);
+ }
+}
+
+/*
+** Return a "position-list blob" corresponding to the current position of
+** cursor pCsr via sqlite3_result_blob(). A position-list blob contains
+** the current position-list for each phrase in the query associated with
+** cursor pCsr.
+**
+** A position-list blob begins with (nPhrase-1) varints, where nPhrase is
+** the number of phrases in the query. Following the varints are the
+** concatenated position lists for each phrase, in order.
+**
+** The first varint (if it exists) contains the size of the position list
+** for phrase 0. The second (same disclaimer) contains the size of position
+** list 1. And so on. There is no size field for the final position list,
+** as it can be derived from the total size of the blob.
+*/
+static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
+ int i;
+ int rc = SQLITE_OK;
+ int nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr);
+ Fts5Buffer val;
+
+ memset(&val, 0, sizeof(Fts5Buffer));
+
+ /* Append the varints */
+ for(i=0; i<(nPhrase-1); i++){
+ const u8 *dummy;
+ int nByte = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &dummy);
+ sqlite3Fts5BufferAppendVarint(&rc, &val, nByte);
+ }
+
+ /* Append the position lists */
+ for(i=0; i<nPhrase; i++){
+ const u8 *pPoslist;
+ int nPoslist;
+ nPoslist = sqlite3Fts5ExprPoslist(pCsr->pExpr, i, &pPoslist);
+ sqlite3Fts5BufferAppendBlob(&rc, &val, nPoslist, pPoslist);
+ }
+
+ sqlite3_result_blob(pCtx, val.p, val.n, sqlite3_free);
+ return rc;
+}
+
+/*
+** This is the xColumn method, called by SQLite to request a value from
+** the row that the supplied cursor currently points to.
+*/
+static int fts5ColumnMethod(
+ sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
+ sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
+ int iCol /* Index of column to read value from */
+){
+ Fts5Table *pTab = (Fts5Table*)(pCursor->pVtab);
+ Fts5Config *pConfig = pTab->pConfig;
+ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
+ int rc = SQLITE_OK;
+
+ assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 );
+
+ if( pCsr->idxNum==FTS5_PLAN_SPECIAL ){
+ if( iCol==pConfig->nCol ){
+ sqlite3_result_text(pCtx, pCsr->zSpecial, -1, SQLITE_TRANSIENT);
+ }
+ }else
+
+ if( iCol==pConfig->nCol ){
+ /* User is requesting the value of the special column with the same name
+ ** as the table. Return the cursor integer id number. This value is only
+ ** useful in that it may be passed as the first argument to an FTS5
+ ** auxiliary function. */
+ sqlite3_result_int64(pCtx, pCsr->iCsrId);
+ }else if( iCol==pConfig->nCol+1 ){
+
+ /* The value of the "rank" column. */
+ if( FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_SOURCE ){
+ fts5PoslistBlob(pCtx, pCsr);
+ }else if(
+ FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_MATCH
+ || FTS5_PLAN(pCsr->idxNum)==FTS5_PLAN_SORTED_MATCH
+ ){
+ if( pCsr->pRank || SQLITE_OK==(rc = fts5FindRankFunction(pCsr)) ){
+ fts5ApiInvoke(pCsr->pRank, pCsr, pCtx, pCsr->nRankArg, pCsr->apRankArg);
+ }
+ }
+ }else if( !fts5IsContentless(pTab) ){
+ rc = fts5SeekCursor(pCsr);
+ if( rc==SQLITE_OK ){
+ sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1));
+ }
+ }
+ return rc;
+}
+
+
+/*
+** This routine implements the xFindFunction method for the FTS3
+** virtual table.
+*/
+static int fts5FindFunctionMethod(
+ sqlite3_vtab *pVtab, /* Virtual table handle */
+ int nArg, /* Number of SQL function arguments */
+ const char *zName, /* Name of SQL function */
+ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */
+ void **ppArg /* OUT: User data for *pxFunc */
+){
+ Fts5Table *pTab = (Fts5Table*)pVtab;
+ Fts5Auxiliary *pAux;
+
+ pAux = fts5FindAuxiliary(pTab, zName);
+ if( pAux ){
+ *pxFunc = fts5ApiCallback;
+ *ppArg = (void*)pAux;
+ return 1;
+ }
+
+ /* No function of the specified name was found. Return 0. */
+ return 0;
+}
+
+/*
+** Implementation of FTS3 xRename method. Rename an fts5 table.
+*/
+static int fts5RenameMethod(
+ sqlite3_vtab *pVtab, /* Virtual table handle */
+ const char *zName /* New name of table */
+){
+ int rc = SQLITE_OK;
+ return rc;
+}
+
+/*
+** The xSavepoint() method.
+**
+** Flush the contents of the pending-terms table to disk.
+*/
+static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
+ Fts5Table *pTab = (Fts5Table*)pVtab;
+ fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint);
+ return sqlite3Fts5StorageSync(pTab->pStorage, 0);
+}
+
+/*
+** The xRelease() method.
+**
+** This is a no-op.
+*/
+static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
+ Fts5Table *pTab = (Fts5Table*)pVtab;
+ fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint);
+ return sqlite3Fts5StorageSync(pTab->pStorage, 0);
+}
+
+/*
+** The xRollbackTo() method.
+**
+** Discard the contents of the pending terms table.
+*/
+static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
+ Fts5Table *pTab = (Fts5Table*)pVtab;
+ fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
+ return sqlite3Fts5StorageRollback(pTab->pStorage);
+}
+
+/*
+** Register a new auxiliary function with global context pGlobal.
+*/
+static int fts5CreateAux(
+ fts5_api *pApi, /* Global context (one per db handle) */
+ const char *zName, /* Name of new function */
+ void *pUserData, /* User data for aux. function */
+ fts5_extension_function xFunc, /* Aux. function implementation */
+ void(*xDestroy)(void*) /* Destructor for pUserData */
+){
+ Fts5Global *pGlobal = (Fts5Global*)pApi;
+ int rc = sqlite3_overload_function(pGlobal->db, zName, -1);
+ if( rc==SQLITE_OK ){
+ Fts5Auxiliary *pAux;
+ int nByte; /* Bytes of space to allocate */
+
+ nByte = sizeof(Fts5Auxiliary) + strlen(zName) + 1;
+ pAux = (Fts5Auxiliary*)sqlite3_malloc(nByte);
+ if( pAux ){
+ memset(pAux, 0, nByte);
+ pAux->zFunc = (char*)&pAux[1];
+ strcpy(pAux->zFunc, zName);
+ pAux->pGlobal = pGlobal;
+ pAux->pUserData = pUserData;
+ pAux->xFunc = xFunc;
+ pAux->xDestroy = xDestroy;
+ pAux->pNext = pGlobal->pAux;
+ pGlobal->pAux = pAux;
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Register a new tokenizer. This is the implementation of the
+** fts5_api.xCreateTokenizer() method.
+*/
+static int fts5CreateTokenizer(
+ fts5_api *pApi, /* Global context (one per db handle) */
+ const char *zName, /* Name of new function */
+ void *pUserData, /* User data for aux. function */
+ fts5_tokenizer *pTokenizer, /* Tokenizer implementation */
+ void(*xDestroy)(void*) /* Destructor for pUserData */
+){
+ Fts5Global *pGlobal = (Fts5Global*)pApi;
+ Fts5TokenizerModule *pNew;
+ int nByte; /* Bytes of space to allocate */
+ int rc = SQLITE_OK;
+
+ nByte = sizeof(Fts5TokenizerModule) + strlen(zName) + 1;
+ pNew = (Fts5TokenizerModule*)sqlite3_malloc(nByte);
+ if( pNew ){
+ memset(pNew, 0, nByte);
+ pNew->zName = (char*)&pNew[1];
+ strcpy(pNew->zName, zName);
+ pNew->pUserData = pUserData;
+ pNew->x = *pTokenizer;
+ pNew->xDestroy = xDestroy;
+ pNew->pNext = pGlobal->pTok;
+ pGlobal->pTok = pNew;
+ if( pNew->pNext==0 ){
+ pGlobal->pDfltTok = pNew;
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+
+ return rc;
+}
+
+/*
+** Find a tokenizer. This is the implementation of the
+** fts5_api.xFindTokenizer() method.
+*/
+static int fts5FindTokenizer(
+ fts5_api *pApi, /* Global context (one per db handle) */
+ const char *zName, /* Name of new function */
+ void **ppUserData,
+ fts5_tokenizer *pTokenizer /* Populate this object */
+){
+ Fts5Global *pGlobal = (Fts5Global*)pApi;
+ int rc = SQLITE_OK;
+ Fts5TokenizerModule *pTok;
+
+ if( zName==0 ){
+ pTok = pGlobal->pDfltTok;
+ }else{
+ for(pTok=pGlobal->pTok; pTok; pTok=pTok->pNext){
+ if( sqlite3_stricmp(zName, pTok->zName)==0 ) break;
+ }
+ }
+
+ if( pTok ){
+ *pTokenizer = pTok->x;
+ *ppUserData = pTok->pUserData;
+ }else{
+ memset(pTokenizer, 0, sizeof(fts5_tokenizer));
+ rc = SQLITE_ERROR;
+ }
+
+ return rc;
+}
+
+int sqlite3Fts5GetTokenizer(
+ Fts5Global *pGlobal,
+ const char **azArg,
+ int nArg,
+ Fts5Tokenizer **ppTok,
+ fts5_tokenizer **ppTokApi
+){
+ Fts5TokenizerModule *pMod = 0;
+ int rc = SQLITE_OK;
+
+ if( nArg==0 ){
+ pMod = pGlobal->pDfltTok;
+ }else{
+ for(pMod=pGlobal->pTok; pMod; pMod=pMod->pNext){
+ if( sqlite3_stricmp(azArg[0], pMod->zName)==0 ) break;
+ }
+ }
+
+ if( pMod==0 ){
+ rc = SQLITE_ERROR;
+ }else{
+ rc = pMod->x.xCreate(pMod->pUserData, &azArg[1], (nArg?nArg-1:0), ppTok);
+ *ppTokApi = &pMod->x;
+ }
+
+ if( rc!=SQLITE_OK ){
+ *ppTokApi = 0;
+ *ppTok = 0;
+ }
+
+ return rc;
+}
+
+static void fts5ModuleDestroy(void *pCtx){
+ Fts5TokenizerModule *pTok, *pNextTok;
+ Fts5Auxiliary *pAux, *pNextAux;
+ Fts5Global *pGlobal = (Fts5Global*)pCtx;
+
+ for(pAux=pGlobal->pAux; pAux; pAux=pNextAux){
+ pNextAux = pAux->pNext;
+ if( pAux->xDestroy ) pAux->xDestroy(pAux->pUserData);
+ sqlite3_free(pAux);
+ }
+
+ for(pTok=pGlobal->pTok; pTok; pTok=pNextTok){
+ pNextTok = pTok->pNext;
+ if( pTok->xDestroy ) pTok->xDestroy(pTok->pUserData);
+ sqlite3_free(pTok);
+ }
+
+ sqlite3_free(pGlobal);
+}
+
+static void fts5Fts5Func(
+ sqlite3_context *pCtx, /* Function call context */
+ int nArg, /* Number of args */
+ sqlite3_value **apVal /* Function arguments */
+){
+ Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx);
+ char buf[8];
+ assert( nArg==0 );
+ assert( sizeof(buf)>=sizeof(pGlobal) );
+ memcpy(buf, (void*)&pGlobal, sizeof(pGlobal));
+ sqlite3_result_blob(pCtx, buf, sizeof(pGlobal), SQLITE_TRANSIENT);
+}
+
+int sqlite3Fts5Init(sqlite3 *db){
+ static const sqlite3_module fts5Mod = {
+ /* iVersion */ 2,
+ /* xCreate */ fts5CreateMethod,
+ /* xConnect */ fts5ConnectMethod,
+ /* xBestIndex */ fts5BestIndexMethod,
+ /* xDisconnect */ fts5DisconnectMethod,
+ /* xDestroy */ fts5DestroyMethod,
+ /* xOpen */ fts5OpenMethod,
+ /* xClose */ fts5CloseMethod,
+ /* xFilter */ fts5FilterMethod,
+ /* xNext */ fts5NextMethod,
+ /* xEof */ fts5EofMethod,
+ /* xColumn */ fts5ColumnMethod,
+ /* xRowid */ fts5RowidMethod,
+ /* xUpdate */ fts5UpdateMethod,
+ /* xBegin */ fts5BeginMethod,
+ /* xSync */ fts5SyncMethod,
+ /* xCommit */ fts5CommitMethod,
+ /* xRollback */ fts5RollbackMethod,
+ /* xFindFunction */ fts5FindFunctionMethod,
+ /* xRename */ fts5RenameMethod,
+ /* xSavepoint */ fts5SavepointMethod,
+ /* xRelease */ fts5ReleaseMethod,
+ /* xRollbackTo */ fts5RollbackToMethod,
+ };
+
+ int rc;
+ Fts5Global *pGlobal = 0;
+ pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global));
+
+ if( pGlobal==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ void *p = (void*)pGlobal;
+ memset(pGlobal, 0, sizeof(Fts5Global));
+ pGlobal->db = db;
+ pGlobal->api.iVersion = 1;
+ pGlobal->api.xCreateFunction = fts5CreateAux;
+ pGlobal->api.xCreateTokenizer = fts5CreateTokenizer;
+ pGlobal->api.xFindTokenizer = fts5FindTokenizer;
+ rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy);
+ if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db);
+ if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db);
+ if( rc==SQLITE_OK ) rc = sqlite3Fts5AuxInit(&pGlobal->api);
+ if( rc==SQLITE_OK ) rc = sqlite3Fts5TokenizerInit(&pGlobal->api);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(
+ db, "fts5", 0, SQLITE_UTF8, p, fts5Fts5Func, 0, 0
+ );
+ }
+ }
+ return rc;
+}
+#endif /* defined(SQLITE_ENABLE_FTS5) */
+
+
diff --git a/ext/fts5/fts5.h b/ext/fts5/fts5.h
new file mode 100644
index 000000000..28be0de67
--- /dev/null
+++ b/ext/fts5/fts5.h
@@ -0,0 +1,319 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+**
+** Interfaces to extend FTS5. Using the interfaces defined in this file,
+** FTS5 may be extended with:
+**
+** * custom tokenizers, and
+** * custom auxiliary functions.
+*/
+
+
+#ifndef _FTS5_H
+#define _FTS5_H
+
+#include "sqlite3.h"
+
+/*************************************************************************
+** CUSTOM AUXILIARY FUNCTIONS
+**
+** Virtual table implementations may overload SQL functions by implementing
+** the sqlite3_module.xFindFunction() method.
+*/
+
+typedef struct Fts5ExtensionApi Fts5ExtensionApi;
+typedef struct Fts5Context Fts5Context;
+
+typedef void (*fts5_extension_function)(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ sqlite3_context *pCtx, /* Context for returning result/error */
+ int nVal, /* Number of values in apVal[] array */
+ sqlite3_value **apVal /* Array of trailing arguments */
+);
+
+/*
+** EXTENSION API FUNCTIONS
+**
+** xUserData(pFts):
+** Return a copy of the context pointer the extension function was
+** registered with.
+**
+** xColumnTotalSize(pFts, iCol, pnToken):
+** If parameter iCol is less than zero, set output variable *pnToken
+** to the total number of tokens in the FTS5 table. Or, if iCol is
+** non-negative but less than the number of columns in the table, return
+** the total number of tokens in column iCol, considering all rows in
+** the FTS5 table.
+**
+** If parameter iCol is greater than or equal to the number of columns
+** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g.
+** an OOM condition or IO error), an appropriate SQLite error code is
+** returned.
+**
+** xColumnCount:
+** Returns the number of columns in the FTS5 table.
+**
+** xColumnSize:
+** Reports the size in tokens of a column value from the current row.
+**
+** xColumnText:
+** Reports the size in tokens of a column value from the current row.
+**
+** xPhraseCount:
+** Returns the number of phrases in the current query expression.
+**
+** xPhraseSize:
+** Returns the number of tokens in phrase iPhrase of the query. Phrases
+** are numbered starting from zero.
+**
+** xInstCount:
+** Set *pnInst to the total number of occurrences of all phrases within
+** the query within the current row. Return SQLITE_OK if successful, or
+** an error code (i.e. SQLITE_NOMEM) if an error occurs.
+**
+** xInst:
+** Query for the details of phrase match iIdx within the current row.
+** Phrase matches are numbered starting from zero, so the iIdx argument
+** should be greater than or equal to zero and smaller than the value
+** output by xInstCount().
+**
+** Returns SQLITE_OK if successful, or an error code (i.e. SQLITE_NOMEM)
+** if an error occurs.
+**
+** xRowid:
+** Returns the rowid of the current row.
+**
+** xTokenize:
+** Tokenize text using the tokenizer belonging to the FTS5 table.
+**
+** xQueryPhrase(pFts5, iPhrase, pUserData, xCallback):
+** This API function is used to query the FTS table for phrase iPhrase
+** of the current query. Specifically, a query equivalent to:
+**
+** ... FROM ftstable WHERE ftstable MATCH $p ORDER BY rowid
+**
+** with $p set to a phrase equivalent to the phrase iPhrase of the
+** current query is executed. For each row visited, the callback function
+** passed as the fourth argument is invoked. The context and API objects
+** passed to the callback function may be used to access the properties of
+** each matched row. Invoking Api.xUserData() returns a copy of the pointer
+** passed as the third argument to pUserData.
+**
+** If the callback function returns any value other than SQLITE_OK, the
+** query is abandoned and the xQueryPhrase function returns immediately.
+** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK.
+** Otherwise, the error code is propagated upwards.
+**
+** If the query runs to completion without incident, SQLITE_OK is returned.
+** Or, if some error occurs before the query completes or is aborted by
+** the callback, an SQLite error code is returned.
+**
+**
+** xSetAuxdata(pFts5, pAux, xDelete)
+**
+** Save the pointer passed as the second argument as the extension functions
+** "auxiliary data". The pointer may then be retrieved by the current or any
+** future invocation of the same fts5 extension function made as part of
+** of the same MATCH query using the xGetAuxdata() API.
+**
+** Each extension function is allocated a single auxiliary data slot for
+** each FTS query (MATCH expression). If the extension function is invoked
+** more than once for a single FTS query, then all invocations share a
+** single auxiliary data context.
+**
+** If there is already an auxiliary data pointer when this function is
+** invoked, then it is replaced by the new pointer. If an xDelete callback
+** was specified along with the original pointer, it is invoked at this
+** point.
+**
+** The xDelete callback, if one is specified, is also invoked on the
+** auxiliary data pointer after the FTS5 query has finished.
+**
+** If an error (e.g. an OOM condition) occurs within this function, an
+** the auxiliary data is set to NULL and an error code returned. If the
+** xDelete parameter was not NULL, it is invoked on the auxiliary data
+** pointer before returning.
+**
+**
+** xGetAuxdata(pFts5, bClear)
+**
+** Returns the current auxiliary data pointer for the fts5 extension
+** function. See the xSetAuxdata() method for details.
+**
+** If the bClear argument is non-zero, then the auxiliary data is cleared
+** (set to NULL) before this function returns. In this case the xDelete,
+** if any, is not invoked.
+**
+**
+** xRowCount(pFts5, pnRow)
+**
+** This function is used to retrieve the total number of rows in the table.
+** In other words, the same value that would be returned by:
+**
+** SELECT count(*) FROM ftstable;
+*/
+struct Fts5ExtensionApi {
+ int iVersion; /* Currently always set to 1 */
+
+ void *(*xUserData)(Fts5Context*);
+
+ int (*xColumnCount)(Fts5Context*);
+ int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow);
+ int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken);
+
+ int (*xTokenize)(Fts5Context*,
+ const char *pText, int nText, /* Text to tokenize */
+ void *pCtx, /* Context passed to xToken() */
+ int (*xToken)(void*, const char*, int, int, int) /* Callback */
+ );
+
+ int (*xPhraseCount)(Fts5Context*);
+ int (*xPhraseSize)(Fts5Context*, int iPhrase);
+
+ int (*xInstCount)(Fts5Context*, int *pnInst);
+ int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff);
+
+ sqlite3_int64 (*xRowid)(Fts5Context*);
+ int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn);
+ int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken);
+
+ int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData,
+ int(*)(const Fts5ExtensionApi*,Fts5Context*,void*)
+ );
+ int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*));
+ void *(*xGetAuxdata)(Fts5Context*, int bClear);
+};
+
+/*
+** CUSTOM AUXILIARY FUNCTIONS
+*************************************************************************/
+
+/*************************************************************************
+** CUSTOM TOKENIZERS
+**
+** Applications may also register custom tokenizer types. A tokenizer
+** is registered by providing fts5 with a populated instance of the
+** following structure. The structure methods are expected to function
+** as follows:
+**
+** xCreate:
+** This function is used to allocate and inititalize a tokenizer instance.
+** A tokenizer instance is required to actually tokenize text.
+**
+** The first argument passed to this function is a copy of the (void*)
+** pointer provided by the application when the fts5_tokenizer object
+** was registered with FTS5 (the third argument to xCreateTokenizer()).
+** The second and third arguments are an array of nul-terminated strings
+** containing the tokenizer arguments, if any, specified following the
+** tokenizer name as part of the CREATE VIRTUAL TABLE statement used
+** to create the FTS5 table.
+**
+** The final argument is an output variable. If successful, (*ppOut)
+** should be set to point to the new tokenizer handle and SQLITE_OK
+** returned. If an error occurs, some value other than SQLITE_OK should
+** be returned. In this case, fts5 assumes that the final value of *ppOut
+** is undefined.
+**
+** xDelete:
+** This function is invoked to delete a tokenizer handle previously
+** allocated using xCreate(). Fts5 guarantees that this function will
+** be invoked exactly once for each successful call to xCreate().
+**
+** xTokenize:
+** This function is expected to tokenize the nText byte string indicated
+** by argument pText. pText may not be nul-terminated. The first argument
+** passed to this function is a pointer to an Fts5Tokenizer object returned
+** by an earlier call to xCreate().
+**
+** For each token in the input string, the supplied callback xToken() must
+** be invoked. The first argument to it should be a copy of the pointer
+** passed as the second argument to xTokenize(). The next two arguments
+** are a pointer to a buffer containing the token text, and the size of
+** the token in bytes. The 4th and 5th arguments are the byte offsets of
+** the first byte of and first byte immediately following the text from
+** which the token is derived within the input.
+**
+** FTS5 assumes the xToken() callback is invoked for each token in the
+** order that they occur within the input text.
+**
+** If an xToken() callback returns any value other than SQLITE_OK, then
+** the tokenization should be abandoned and the xTokenize() method should
+** immediately return a copy of the xToken() return value. Or, if the
+** input buffer is exhausted, xTokenize() should return SQLITE_OK. Finally,
+** if an error occurs with the xTokenize() implementation itself, it
+** may abandon the tokenization and return any error code other than
+** SQLITE_OK or SQLITE_DONE.
+**
+*/
+typedef struct Fts5Tokenizer Fts5Tokenizer;
+typedef struct fts5_tokenizer fts5_tokenizer;
+struct fts5_tokenizer {
+ int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut);
+ void (*xDelete)(Fts5Tokenizer*);
+ int (*xTokenize)(Fts5Tokenizer*,
+ void *pCtx,
+ const char *pText, int nText,
+ int (*xToken)(
+ void *pCtx, /* Copy of 2nd argument to xTokenize() */
+ const char *pToken, /* Pointer to buffer containing token */
+ int nToken, /* Size of token in bytes */
+ int iStart, /* Byte offset of token within input text */
+ int iEnd /* Byte offset of end of token within input text */
+ )
+ );
+};
+
+/*
+** END OF CUSTOM TOKENIZERS
+*************************************************************************/
+
+/*************************************************************************
+** FTS5 EXTENSION REGISTRATION API
+*/
+typedef struct fts5_api fts5_api;
+struct fts5_api {
+ int iVersion; /* Currently always set to 1 */
+
+ /* Create a new tokenizer */
+ int (*xCreateTokenizer)(
+ fts5_api *pApi,
+ const char *zName,
+ void *pContext,
+ fts5_tokenizer *pTokenizer,
+ void (*xDestroy)(void*)
+ );
+
+ /* Find an existing tokenizer */
+ int (*xFindTokenizer)(
+ fts5_api *pApi,
+ const char *zName,
+ void **ppContext,
+ fts5_tokenizer *pTokenizer
+ );
+
+ /* Create a new auxiliary function */
+ int (*xCreateFunction)(
+ fts5_api *pApi,
+ const char *zName,
+ void *pContext,
+ fts5_extension_function xFunction,
+ void (*xDestroy)(void*)
+ );
+};
+
+/*
+** END OF REGISTRATION API
+*************************************************************************/
+
+#endif /* _FTS5_H */
+
diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h
new file mode 100644
index 000000000..59d327121
--- /dev/null
+++ b/ext/fts5/fts5Int.h
@@ -0,0 +1,575 @@
+/*
+** 2014 May 31
+**
+** 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 _FTS5INT_H
+#define _FTS5INT_H
+
+#include "fts5.h"
+#include "sqliteInt.h"
+
+
+/*
+** Maximum number of prefix indexes on single FTS5 table. This must be
+** less than 32. If it is set to anything large than that, an #error
+** directive in fts5_index.c will cause the build to fail.
+*/
+#define FTS5_MAX_PREFIX_INDEXES 31
+
+#define FTS5_DEFAULT_NEARDIST 10
+#define FTS5_DEFAULT_RANK "bm25"
+
+/* Name of rank and rowid columns */
+#define FTS5_RANK_NAME "rank"
+#define FTS5_ROWID_NAME "rowid"
+
+#ifdef SQLITE_DEBUG
+# define FTS5_CORRUPT sqlite3Fts5Corrupt()
+int sqlite3Fts5Corrupt(void);
+#else
+# define FTS5_CORRUPT SQLITE_CORRUPT_VTAB
+#endif
+
+/**************************************************************************
+** Interface to code in fts5.c.
+*/
+typedef struct Fts5Global Fts5Global;
+
+int sqlite3Fts5GetTokenizer(
+ Fts5Global*,
+ const char **azArg,
+ int nArg,
+ Fts5Tokenizer**,
+ fts5_tokenizer**
+);
+
+/*
+** End of interface to code in fts5.c.
+**************************************************************************/
+
+/**************************************************************************
+** Interface to code in fts5_config.c. fts5_config.c contains contains code
+** to parse the arguments passed to the CREATE VIRTUAL TABLE statement.
+*/
+
+typedef struct Fts5Config Fts5Config;
+
+/*
+** An instance of the following structure encodes all information that can
+** be gleaned from the CREATE VIRTUAL TABLE statement.
+**
+** And all information loaded from the %_config table.
+**
+** nAutomerge:
+** The minimum number of segments that an auto-merge operation should
+** attempt to merge together. A value of 1 sets the object to use the
+** compile time default. Zero disables auto-merge altogether.
+*/
+struct Fts5Config {
+ sqlite3 *db; /* Database handle */
+ char *zDb; /* Database holding FTS index (e.g. "main") */
+ char *zName; /* Name of FTS index */
+ int nCol; /* Number of columns */
+ char **azCol; /* Column names */
+ int nPrefix; /* Number of prefix indexes */
+ int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */
+ int eContent; /* An FTS5_CONTENT value */
+ char *zContent; /* content table */
+ char *zContentRowid; /* "content_rowid=" option value */
+ Fts5Tokenizer *pTok;
+ fts5_tokenizer *pTokApi;
+
+ /* Values loaded from the %_config table */
+ int iCookie; /* Incremented when %_config is modified */
+ int pgsz; /* Approximate page size used in %_data */
+ int nAutomerge; /* 'automerge' setting */
+ int nCrisisMerge; /* Maximum allowed segments per level */
+ char *zRank; /* Name of rank function */
+ char *zRankArgs; /* Arguments to rank function */
+};
+
+#define FTS5_CONTENT_NORMAL 0
+#define FTS5_CONTENT_NONE 1
+#define FTS5_CONTENT_EXTERNAL 2
+
+
+
+int sqlite3Fts5ConfigParse(
+ Fts5Global*, sqlite3*, int, const char **, Fts5Config**, char**
+);
+void sqlite3Fts5ConfigFree(Fts5Config*);
+
+int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig);
+
+int sqlite3Fts5Tokenize(
+ Fts5Config *pConfig, /* FTS5 Configuration object */
+ const char *pText, int nText, /* Text to tokenize */
+ void *pCtx, /* Context passed to xToken() */
+ int (*xToken)(void*, const char*, int, int, int) /* Callback */
+);
+
+void sqlite3Fts5Dequote(char *z);
+
+/* Load the contents of the %_config table */
+int sqlite3Fts5ConfigLoad(Fts5Config*, int);
+
+/* Set the value of a single config attribute */
+int sqlite3Fts5ConfigSetValue(Fts5Config*, const char*, sqlite3_value*, int*);
+
+int sqlite3Fts5ConfigParseRank(const char*, char**, char**);
+
+/*
+** End of interface to code in fts5_config.c.
+**************************************************************************/
+
+/**************************************************************************
+** Interface to code in fts5_buffer.c.
+*/
+
+/*
+** Buffer object for the incremental building of string data.
+*/
+typedef struct Fts5Buffer Fts5Buffer;
+struct Fts5Buffer {
+ u8 *p;
+ int n;
+ int nSpace;
+};
+
+int sqlite3Fts5BufferGrow(int*, Fts5Buffer*, int);
+void sqlite3Fts5BufferAppendVarint(int*, Fts5Buffer*, i64);
+void sqlite3Fts5BufferAppendBlob(int*, Fts5Buffer*, int, const u8*);
+void sqlite3Fts5BufferAppendString(int *, Fts5Buffer*, const char*);
+void sqlite3Fts5BufferFree(Fts5Buffer*);
+void sqlite3Fts5BufferZero(Fts5Buffer*);
+void sqlite3Fts5BufferSet(int*, Fts5Buffer*, int, const u8*);
+void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...);
+void sqlite3Fts5BufferAppendListElem(int*, Fts5Buffer*, const char*, int);
+void sqlite3Fts5BufferAppend32(int*, Fts5Buffer*, int);
+
+#define fts5BufferZero(x) sqlite3Fts5BufferZero(x)
+#define fts5BufferGrow(a,b,c) sqlite3Fts5BufferGrow(a,b,c)
+#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,c)
+#define fts5BufferFree(a) sqlite3Fts5BufferFree(a)
+#define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d)
+#define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d)
+#define fts5BufferAppend32(a,b,c) sqlite3Fts5BufferAppend32(a,b,c)
+
+/* Write and decode big-endian 32-bit integer values */
+void sqlite3Fts5Put32(u8*, int);
+int sqlite3Fts5Get32(const u8*);
+
+#define FTS5_POS2COLUMN(iPos) (int)(iPos >> 32)
+#define FTS5_POS2OFFSET(iPos) (int)(iPos & 0xFFFFFFFF)
+
+typedef struct Fts5PoslistReader Fts5PoslistReader;
+struct Fts5PoslistReader {
+ /* Variables used only by sqlite3Fts5PoslistIterXXX() functions. */
+ int iCol; /* If (iCol>=0), this column only */
+ const u8 *a; /* Position list to iterate through */
+ int n; /* Size of buffer at a[] in bytes */
+ int i; /* Current offset in a[] */
+
+ /* Output variables */
+ int bEof; /* Set to true at EOF */
+ i64 iPos; /* (iCol<<32) + iPos */
+};
+int sqlite3Fts5PoslistReaderInit(
+ int iCol, /* If (iCol>=0), this column only */
+ const u8 *a, int n, /* Poslist buffer to iterate through */
+ Fts5PoslistReader *pIter /* Iterator object to initialize */
+);
+int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader*);
+
+typedef struct Fts5PoslistWriter Fts5PoslistWriter;
+struct Fts5PoslistWriter {
+ i64 iPrev;
+};
+int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
+
+int sqlite3Fts5PoslistNext(
+ const u8 *a, int n, /* Buffer containing poslist */
+ int *pi, /* IN/OUT: Offset within a[] */
+ int *piCol, /* IN/OUT: Current column */
+ int *piOff /* IN/OUT: Current token offset */
+);
+
+int sqlite3Fts5PoslistNext64(
+ const u8 *a, int n, /* Buffer containing poslist */
+ int *pi, /* IN/OUT: Offset within a[] */
+ i64 *piOff /* IN/OUT: Current offset */
+);
+
+/* Malloc utility */
+void *sqlite3Fts5MallocZero(int *pRc, int nByte);
+
+/*
+** End of interface to code in fts5_buffer.c.
+**************************************************************************/
+
+/**************************************************************************
+** Interface to code in fts5_index.c. fts5_index.c contains contains code
+** to access the data stored in the %_data table.
+*/
+
+typedef struct Fts5Index Fts5Index;
+typedef struct Fts5IndexIter Fts5IndexIter;
+
+/*
+** Values used as part of the flags argument passed to IndexQuery().
+*/
+#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */
+#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */
+#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */
+
+/*
+** Create/destroy an Fts5Index object.
+*/
+int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
+int sqlite3Fts5IndexClose(Fts5Index *p, int bDestroy);
+
+/*
+** for(
+** pIter = sqlite3Fts5IndexQuery(p, "token", 5, 0);
+** 0==sqlite3Fts5IterEof(pIter);
+** sqlite3Fts5IterNext(pIter)
+** ){
+** i64 iRowid = sqlite3Fts5IterRowid(pIter);
+** }
+*/
+
+/*
+** Open a new iterator to iterate though all docids that match the
+** specified token or token prefix.
+*/
+int sqlite3Fts5IndexQuery(
+ Fts5Index *p, /* FTS index to query */
+ const char *pToken, int nToken, /* Token (or prefix) to query for */
+ int flags, /* Mask of FTS5INDEX_QUERY_X flags */
+ Fts5IndexIter **ppIter
+);
+
+/*
+** The various operations on open token or token prefix iterators opened
+** using sqlite3Fts5IndexQuery().
+*/
+int sqlite3Fts5IterEof(Fts5IndexIter*);
+int sqlite3Fts5IterNext(Fts5IndexIter*);
+int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch);
+i64 sqlite3Fts5IterRowid(Fts5IndexIter*);
+int sqlite3Fts5IterPoslist(Fts5IndexIter*, const u8 **pp, int *pn);
+
+/*
+** Close an iterator opened by sqlite3Fts5IndexQuery().
+*/
+void sqlite3Fts5IterClose(Fts5IndexIter*);
+
+/*
+** Insert or remove data to or from the index. Each time a document is
+** added to or removed from the index, this function is called one or more
+** times.
+**
+** For an insert, it must be called once for each token in the new document.
+** If the operation is a delete, it must be called (at least) once for each
+** unique token in the document with an iCol value less than zero. The iPos
+** argument is ignored for a delete.
+*/
+int sqlite3Fts5IndexWrite(
+ Fts5Index *p, /* Index to write to */
+ int iCol, /* Column token appears in (-ve -> delete) */
+ int iPos, /* Position of token within column */
+ const char *pToken, int nToken /* Token to add or remove to or from index */
+);
+
+/*
+** Indicate that subsequent calls to sqlite3Fts5IndexWrite() pertain to
+** document iDocid.
+*/
+int sqlite3Fts5IndexBeginWrite(
+ Fts5Index *p, /* Index to write to */
+ i64 iDocid /* Docid to add or remove data from */
+);
+
+/*
+** Flush any data stored in the in-memory hash tables to the database.
+** If the bCommit flag is true, also close any open blob handles.
+*/
+int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit);
+
+/*
+** Discard any data stored in the in-memory hash tables. Do not write it
+** to the database. Additionally, assume that the contents of the %_data
+** table may have changed on disk. So any in-memory caches of %_data
+** records must be invalidated.
+*/
+int sqlite3Fts5IndexRollback(Fts5Index *p);
+
+/*
+** Retrieve and clear the current error code, respectively.
+*/
+int sqlite3Fts5IndexErrcode(Fts5Index*);
+void sqlite3Fts5IndexReset(Fts5Index*);
+
+/*
+** Get or set the "averages" record.
+*/
+int sqlite3Fts5IndexGetAverages(Fts5Index *p, Fts5Buffer *pBuf);
+int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int);
+
+/*
+** Functions called by the storage module as part of integrity-check.
+*/
+u64 sqlite3Fts5IndexCksum(Fts5Config*,i64,int,int,const char*,int);
+int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum);
+
+/*
+** Called during virtual module initialization to register UDF
+** fts5_decode() with SQLite
+*/
+int sqlite3Fts5IndexInit(sqlite3*);
+
+int sqlite3Fts5IndexSetCookie(Fts5Index*, int);
+
+/*
+** Return the total number of entries read from the %_data table by
+** this connection since it was created.
+*/
+int sqlite3Fts5IndexReads(Fts5Index *p);
+
+int sqlite3Fts5IndexReinit(Fts5Index *p);
+int sqlite3Fts5IndexOptimize(Fts5Index *p);
+
+int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
+
+int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v);
+#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&b)
+
+int sqlite3Fts5GetVarintLen(u32 iVal);
+
+/*
+** End of interface to code in fts5_index.c.
+**************************************************************************/
+
+/**************************************************************************
+** Interface to code in fts5_hash.c.
+*/
+typedef struct Fts5Hash Fts5Hash;
+
+/*
+** Create a hash table, free a hash table.
+*/
+int sqlite3Fts5HashNew(Fts5Hash**, int *pnSize);
+void sqlite3Fts5HashFree(Fts5Hash*);
+
+int sqlite3Fts5HashWrite(
+ Fts5Hash*,
+ i64 iRowid, /* Rowid for this entry */
+ int iCol, /* Column token appears in (-ve -> delete) */
+ int iPos, /* Position of token within column */
+ const char *pToken, int nToken /* Token to add or remove to or from index */
+);
+
+/*
+** Empty (but do not delete) a hash table.
+*/
+void sqlite3Fts5HashClear(Fts5Hash*);
+
+int sqlite3Fts5HashQuery(
+ Fts5Hash*, /* Hash table to query */
+ const char *pTerm, int nTerm, /* Query term */
+ const u8 **ppDoclist, /* OUT: Pointer to doclist for pTerm */
+ int *pnDoclist /* OUT: Size of doclist in bytes */
+);
+
+int sqlite3Fts5HashScanInit(
+ Fts5Hash*, /* Hash table to query */
+ const char *pTerm, int nTerm /* Query prefix */
+);
+void sqlite3Fts5HashScanNext(Fts5Hash*);
+int sqlite3Fts5HashScanEof(Fts5Hash*);
+void sqlite3Fts5HashScanEntry(Fts5Hash *,
+ const char **pzTerm, /* OUT: term (nul-terminated) */
+ const u8 **ppDoclist, /* OUT: pointer to doclist */
+ int *pnDoclist /* OUT: size of doclist in bytes */
+);
+
+
+/*
+** End of interface to code in fts5_hash.c.
+**************************************************************************/
+
+/**************************************************************************
+** Interface to code in fts5_storage.c. fts5_storage.c contains contains
+** code to access the data stored in the %_content and %_docsize tables.
+*/
+
+#define FTS5_STMT_SCAN_ASC 0 /* SELECT rowid, * FROM ... ORDER BY 1 ASC */
+#define FTS5_STMT_SCAN_DESC 1 /* SELECT rowid, * FROM ... ORDER BY 1 DESC */
+#define FTS5_STMT_LOOKUP 2 /* SELECT rowid, * FROM ... WHERE rowid=? */
+
+typedef struct Fts5Storage Fts5Storage;
+
+int sqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int, Fts5Storage**, char**);
+int sqlite3Fts5StorageClose(Fts5Storage *p, int bDestroy);
+
+int sqlite3Fts5DropTable(Fts5Config*, const char *zPost);
+int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **);
+
+int sqlite3Fts5StorageDelete(Fts5Storage *p, i64);
+int sqlite3Fts5StorageInsert(Fts5Storage *p, sqlite3_value **apVal, int, i64*);
+
+int sqlite3Fts5StorageIntegrity(Fts5Storage *p);
+
+int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt**, char**);
+void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*);
+
+int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol);
+int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnAvg);
+int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow);
+
+int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit);
+int sqlite3Fts5StorageRollback(Fts5Storage *p);
+
+int sqlite3Fts5StorageConfigValue(Fts5Storage *p, const char*, sqlite3_value*);
+
+int sqlite3Fts5StorageSpecialDelete(Fts5Storage *p, i64 iDel, sqlite3_value**);
+
+int sqlite3Fts5StorageDeleteAll(Fts5Storage *p);
+int sqlite3Fts5StorageRebuild(Fts5Storage *p);
+int sqlite3Fts5StorageOptimize(Fts5Storage *p);
+
+/*
+** End of interface to code in fts5_storage.c.
+**************************************************************************/
+
+
+/**************************************************************************
+** Interface to code in fts5_expr.c.
+*/
+typedef struct Fts5Expr Fts5Expr;
+typedef struct Fts5ExprNode Fts5ExprNode;
+typedef struct Fts5Parse Fts5Parse;
+typedef struct Fts5Token Fts5Token;
+typedef struct Fts5ExprPhrase Fts5ExprPhrase;
+typedef struct Fts5ExprNearset Fts5ExprNearset;
+
+struct Fts5Token {
+ const char *p; /* Token text (not NULL terminated) */
+ int n; /* Size of buffer p in bytes */
+};
+
+/* Parse a MATCH expression. */
+int sqlite3Fts5ExprNew(
+ Fts5Config *pConfig,
+ const char *zExpr,
+ Fts5Expr **ppNew,
+ char **pzErr
+);
+
+/*
+** for(rc = sqlite3Fts5ExprFirst(pExpr, pIdx, bDesc);
+** rc==SQLITE_OK && 0==sqlite3Fts5ExprEof(pExpr);
+** rc = sqlite3Fts5ExprNext(pExpr)
+** ){
+** // The document with rowid iRowid matches the expression!
+** i64 iRowid = sqlite3Fts5ExprRowid(pExpr);
+** }
+*/
+int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, int bDesc);
+int sqlite3Fts5ExprNext(Fts5Expr*);
+int sqlite3Fts5ExprEof(Fts5Expr*);
+i64 sqlite3Fts5ExprRowid(Fts5Expr*);
+
+void sqlite3Fts5ExprFree(Fts5Expr*);
+
+/* Called during startup to register a UDF with SQLite */
+int sqlite3Fts5ExprInit(Fts5Global*, sqlite3*);
+
+int sqlite3Fts5ExprPhraseCount(Fts5Expr*);
+int sqlite3Fts5ExprPhraseSize(Fts5Expr*, int iPhrase);
+int sqlite3Fts5ExprPoslist(Fts5Expr*, int, const u8 **);
+
+int sqlite3Fts5ExprPhraseExpr(Fts5Config*, Fts5Expr*, int, Fts5Expr**);
+
+/*******************************************
+** The fts5_expr.c API above this point is used by the other hand-written
+** C code in this module. The interfaces below this point are called by
+** the parser code in fts5parse.y. */
+
+void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...);
+
+Fts5ExprNode *sqlite3Fts5ParseNode(
+ Fts5Parse *pParse,
+ int eType,
+ Fts5ExprNode *pLeft,
+ Fts5ExprNode *pRight,
+ Fts5ExprNearset *pNear
+);
+
+Fts5ExprPhrase *sqlite3Fts5ParseTerm(
+ Fts5Parse *pParse,
+ Fts5ExprPhrase *pPhrase,
+ Fts5Token *pToken,
+ int bPrefix
+);
+
+Fts5ExprNearset *sqlite3Fts5ParseNearset(
+ Fts5Parse*,
+ Fts5ExprNearset*,
+ Fts5ExprPhrase*
+);
+
+void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase*);
+void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset*);
+void sqlite3Fts5ParseNodeFree(Fts5ExprNode*);
+
+void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*);
+void sqlite3Fts5ParseSetColumn(Fts5Parse*, Fts5ExprNearset*, Fts5Token*);
+void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p);
+void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*);
+
+/*
+** End of interface to code in fts5_expr.c.
+**************************************************************************/
+
+
+
+/**************************************************************************
+** Interface to code in fts5_aux.c.
+*/
+
+int sqlite3Fts5AuxInit(fts5_api*);
+/*
+** End of interface to code in fts5_aux.c.
+**************************************************************************/
+
+/**************************************************************************
+** Interface to code in fts5_tokenizer.c.
+*/
+
+int sqlite3Fts5TokenizerInit(fts5_api*);
+/*
+** End of interface to code in fts5_tokenizer.c.
+**************************************************************************/
+
+/**************************************************************************
+** Interface to code in fts5_sorter.c.
+*/
+typedef struct Fts5Sorter Fts5Sorter;
+
+int sqlite3Fts5SorterNew(Fts5Expr *pExpr, Fts5Sorter **pp);
+
+/*
+** End of interface to code in fts5_sorter.c.
+**************************************************************************/
+
+#endif
diff --git a/ext/fts5/fts5_aux.c b/ext/fts5/fts5_aux.c
new file mode 100644
index 000000000..8e4beffe6
--- /dev/null
+++ b/ext/fts5/fts5_aux.c
@@ -0,0 +1,556 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+*/
+
+#ifdef SQLITE_ENABLE_FTS5
+
+#include "fts5Int.h"
+#include <math.h>
+
+/*
+** Object used to iterate through all "coalesced phrase instances" in
+** a single column of the current row. If the phrase instances in the
+** column being considered do not overlap, this object simply iterates
+** through them. Or, if they do overlap (share one or more tokens in
+** common), each set of overlapping instances is treated as a single
+** match. See documentation for the highlight() auxiliary function for
+** details.
+**
+** Usage is:
+**
+** for(rc = fts5CInstIterNext(pApi, pFts, iCol, &iter);
+** (rc==SQLITE_OK && 0==fts5CInstIterEof(&iter);
+** rc = fts5CInstIterNext(&iter)
+** ){
+** printf("instance starts at %d, ends at %d\n", iter.iStart, iter.iEnd);
+** }
+**
+*/
+typedef struct CInstIter CInstIter;
+struct CInstIter {
+ const Fts5ExtensionApi *pApi; /* API offered by current FTS version */
+ Fts5Context *pFts; /* First arg to pass to pApi functions */
+ int iCol; /* Column to search */
+ int iInst; /* Next phrase instance index */
+ int nInst; /* Total number of phrase instances */
+
+ /* Output variables */
+ int iStart; /* First token in coalesced phrase instance */
+ int iEnd; /* Last token in coalesced phrase instance */
+};
+
+/*
+** Advance the iterator to the next coalesced phrase instance. Return
+** an SQLite error code if an error occurs, or SQLITE_OK otherwise.
+*/
+static int fts5CInstIterNext(CInstIter *pIter){
+ int rc = SQLITE_OK;
+ pIter->iStart = -1;
+ pIter->iEnd = -1;
+
+ while( rc==SQLITE_OK && pIter->iInst<pIter->nInst ){
+ int ip; int ic; int io;
+ rc = pIter->pApi->xInst(pIter->pFts, pIter->iInst, &ip, &ic, &io);
+ if( rc==SQLITE_OK ){
+ if( ic==pIter->iCol ){
+ int iEnd = io - 1 + pIter->pApi->xPhraseSize(pIter->pFts, ip);
+ if( pIter->iStart<0 ){
+ pIter->iStart = io;
+ pIter->iEnd = iEnd;
+ }else if( io<=pIter->iEnd ){
+ if( iEnd>pIter->iEnd ) pIter->iEnd = iEnd;
+ }else{
+ break;
+ }
+ }
+ pIter->iInst++;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Initialize the iterator object indicated by the final parameter to
+** iterate through coalesced phrase instances in column iCol.
+*/
+static int fts5CInstIterInit(
+ const Fts5ExtensionApi *pApi,
+ Fts5Context *pFts,
+ int iCol,
+ CInstIter *pIter
+){
+ int rc;
+
+ memset(pIter, 0, sizeof(CInstIter));
+ pIter->pApi = pApi;
+ pIter->pFts = pFts;
+ pIter->iCol = iCol;
+ rc = pApi->xInstCount(pFts, &pIter->nInst);
+
+ if( rc==SQLITE_OK ){
+ rc = fts5CInstIterNext(pIter);
+ }
+
+ return rc;
+}
+
+
+
+/*************************************************************************
+** Start of highlight() implementation.
+*/
+typedef struct HighlightContext HighlightContext;
+struct HighlightContext {
+ CInstIter iter; /* Coalesced Instance Iterator */
+ int iPos; /* Current token offset in zIn[] */
+ int iRangeStart; /* First token to include */
+ int iRangeEnd; /* If non-zero, last token to include */
+ const char *zOpen; /* Opening highlight */
+ const char *zClose; /* Closing highlight */
+ const char *zIn; /* Input text */
+ int nIn; /* Size of input text in bytes */
+ int iOff; /* Current offset within zIn[] */
+ char *zOut; /* Output value */
+};
+
+/*
+** Append text to the HighlightContext output string - p->zOut. Argument
+** z points to a buffer containing n bytes of text to append. If n is
+** negative, everything up until the first '\0' is appended to the output.
+**
+** If *pRc is set to any value other than SQLITE_OK when this function is
+** called, it is a no-op. If an error (i.e. an OOM condition) is encountered,
+** *pRc is set to an error code before returning.
+*/
+static void fts5HighlightAppend(
+ int *pRc,
+ HighlightContext *p,
+ const char *z, int n
+){
+ if( *pRc==SQLITE_OK ){
+ if( n<0 ) n = strlen(z);
+ p->zOut = sqlite3_mprintf("%z%.*s", p->zOut, n, z);
+ if( p->zOut==0 ) *pRc = SQLITE_NOMEM;
+ }
+}
+
+/*
+** Tokenizer callback used by implementation of highlight() function.
+*/
+static int fts5HighlightCb(
+ void *pContext, /* Pointer to HighlightContext object */
+ const char *pToken, /* Buffer containing token */
+ int nToken, /* Size of token in bytes */
+ int iStartOff, /* Start offset of token */
+ int iEndOff /* End offset of token */
+){
+ HighlightContext *p = (HighlightContext*)pContext;
+ int rc = SQLITE_OK;
+ int iPos = p->iPos++;
+
+ if( p->iRangeEnd>0 ){
+ if( iPos<p->iRangeStart || iPos>p->iRangeEnd ) return SQLITE_OK;
+ if( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff;
+ }
+
+ if( iPos==p->iter.iStart ){
+ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iStartOff - p->iOff);
+ fts5HighlightAppend(&rc, p, p->zOpen, -1);
+ p->iOff = iStartOff;
+ }
+
+ if( iPos==p->iter.iEnd ){
+ if( p->iRangeEnd && p->iter.iStart<p->iRangeStart ){
+ fts5HighlightAppend(&rc, p, p->zOpen, -1);
+ }
+ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
+ fts5HighlightAppend(&rc, p, p->zClose, -1);
+ p->iOff = iEndOff;
+ if( rc==SQLITE_OK ){
+ rc = fts5CInstIterNext(&p->iter);
+ }
+ }
+
+ if( p->iRangeEnd>0 && iPos==p->iRangeEnd ){
+ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff);
+ p->iOff = iEndOff;
+ if( iPos<p->iter.iEnd ){
+ fts5HighlightAppend(&rc, p, p->zClose, -1);
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Implementation of highlight() function.
+*/
+static void fts5HighlightFunction(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ sqlite3_context *pCtx, /* Context for returning result/error */
+ int nVal, /* Number of values in apVal[] array */
+ sqlite3_value **apVal /* Array of trailing arguments */
+){
+ HighlightContext ctx;
+ int rc;
+ int iCol;
+
+ if( nVal!=3 ){
+ const char *zErr = "wrong number of arguments to function highlight()";
+ sqlite3_result_error(pCtx, zErr, -1);
+ return;
+ }
+
+ iCol = sqlite3_value_int(apVal[0]);
+ memset(&ctx, 0, sizeof(HighlightContext));
+ ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]);
+ ctx.zClose = (const char*)sqlite3_value_text(apVal[2]);
+ rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn);
+
+ if( ctx.zIn ){
+ if( rc==SQLITE_OK ){
+ rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter);
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
+ }
+ fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
+
+ if( rc==SQLITE_OK ){
+ sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_error_code(pCtx, rc);
+ }
+ sqlite3_free(ctx.zOut);
+ }
+}
+/*
+** End of highlight() implementation.
+**************************************************************************/
+
+/*
+** Implementation of snippet() function.
+*/
+static void fts5SnippetFunction(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ sqlite3_context *pCtx, /* Context for returning result/error */
+ int nVal, /* Number of values in apVal[] array */
+ sqlite3_value **apVal /* Array of trailing arguments */
+){
+ HighlightContext ctx;
+ int rc = SQLITE_OK; /* Return code */
+ int iCol; /* 1st argument to snippet() */
+ const char *zEllips; /* 4th argument to snippet() */
+ int nToken; /* 5th argument to snippet() */
+ int nInst; /* Number of instance matches this row */
+ int i; /* Used to iterate through instances */
+ int nPhrase; /* Number of phrases in query */
+ unsigned char *aSeen; /* Array of "seen instance" flags */
+ int iBestCol; /* Column containing best snippet */
+ int iBestStart = 0; /* First token of best snippet */
+ int iBestLast; /* Last token of best snippet */
+ int nBestScore = 0; /* Score of best snippet */
+ int nColSize; /* Total size of iBestCol in tokens */
+
+ if( nVal!=5 ){
+ const char *zErr = "wrong number of arguments to function snippet()";
+ sqlite3_result_error(pCtx, zErr, -1);
+ return;
+ }
+
+ memset(&ctx, 0, sizeof(HighlightContext));
+ iCol = sqlite3_value_int(apVal[0]);
+ ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]);
+ ctx.zClose = (const char*)sqlite3_value_text(apVal[2]);
+ zEllips = (const char*)sqlite3_value_text(apVal[3]);
+ nToken = sqlite3_value_int(apVal[4]);
+ iBestLast = nToken-1;
+
+ iBestCol = (iCol>=0 ? iCol : 0);
+ nPhrase = pApi->xPhraseCount(pFts);
+ aSeen = sqlite3_malloc(nPhrase);
+ if( aSeen==0 ){
+ rc = SQLITE_NOMEM;
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = pApi->xInstCount(pFts, &nInst);
+ }
+ for(i=0; rc==SQLITE_OK && i<nInst; i++){
+ int ip, iSnippetCol, iStart;
+ memset(aSeen, 0, nPhrase);
+ rc = pApi->xInst(pFts, i, &ip, &iSnippetCol, &iStart);
+ if( rc==SQLITE_OK && (iCol<0 || iSnippetCol==iCol) ){
+ int nScore = 1000;
+ int iLast = iStart - 1 + pApi->xPhraseSize(pFts, ip);
+ int j;
+ aSeen[ip] = 1;
+
+ for(j=i+1; rc==SQLITE_OK && j<nInst; j++){
+ int ic; int io; int iFinal;
+ rc = pApi->xInst(pFts, j, &ip, &ic, &io);
+ iFinal = io + pApi->xPhraseSize(pFts, ip) - 1;
+ if( rc==SQLITE_OK && ic==iSnippetCol && iLast<iStart+nToken ){
+ nScore += aSeen[ip] ? 1000 : 1;
+ aSeen[ip] = 1;
+ if( iFinal>iLast ) iLast = iFinal;
+ }
+ }
+
+ if( rc==SQLITE_OK && nScore>nBestScore ){
+ iBestCol = iSnippetCol;
+ iBestStart = iStart;
+ iBestLast = iLast;
+ nBestScore = nScore;
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = pApi->xColumnSize(pFts, iBestCol, &nColSize);
+ }
+ if( rc==SQLITE_OK ){
+ rc = pApi->xColumnText(pFts, iBestCol, &ctx.zIn, &ctx.nIn);
+ }
+ if( ctx.zIn ){
+ if( rc==SQLITE_OK ){
+ rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter);
+ }
+
+ if( (iBestStart+nToken-1)>iBestLast ){
+ iBestStart -= (iBestStart+nToken-1-iBestLast) / 2;
+ }
+ if( iBestStart+nToken>nColSize ){
+ iBestStart = nColSize - nToken;
+ }
+ if( iBestStart<0 ) iBestStart = 0;
+
+ ctx.iRangeStart = iBestStart;
+ ctx.iRangeEnd = iBestStart + nToken - 1;
+
+ if( iBestStart>0 ){
+ fts5HighlightAppend(&rc, &ctx, zEllips, -1);
+ }
+ if( rc==SQLITE_OK ){
+ rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb);
+ }
+ if( ctx.iRangeEnd>=(nColSize-1) ){
+ fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff);
+ }else{
+ fts5HighlightAppend(&rc, &ctx, zEllips, -1);
+ }
+
+ if( rc==SQLITE_OK ){
+ sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_error_code(pCtx, rc);
+ }
+ sqlite3_free(ctx.zOut);
+ }
+ sqlite3_free(aSeen);
+}
+
+/************************************************************************/
+
+/*
+** The first time the bm25() function is called for a query, an instance
+** of the following structure is allocated and populated.
+*/
+typedef struct Fts5Bm25Data Fts5Bm25Data;
+struct Fts5Bm25Data {
+ int nPhrase; /* Number of phrases in query */
+ double avgdl; /* Average number of tokens in each row */
+ double *aIDF; /* IDF for each phrase */
+ double *aFreq; /* Array used to calculate phrase freq. */
+};
+
+/*
+** Callback used by fts5Bm25GetData() to count the number of rows in the
+** table matched by each individual phrase within the query.
+*/
+static int fts5CountCb(
+ const Fts5ExtensionApi *pApi,
+ Fts5Context *pFts,
+ void *pUserData /* Pointer to sqlite3_int64 variable */
+){
+ sqlite3_int64 *pn = (sqlite3_int64*)pUserData;
+ (*pn)++;
+ return SQLITE_OK;
+}
+
+/*
+** Set *ppData to point to the Fts5Bm25Data object for the current query.
+** If the object has not already been allocated, allocate and populate it
+** now.
+*/
+static int fts5Bm25GetData(
+ const Fts5ExtensionApi *pApi,
+ Fts5Context *pFts,
+ Fts5Bm25Data **ppData /* OUT: bm25-data object for this query */
+){
+ int rc = SQLITE_OK; /* Return code */
+ Fts5Bm25Data *p; /* Object to return */
+
+ p = pApi->xGetAuxdata(pFts, 0);
+ if( p==0 ){
+ int nPhrase; /* Number of phrases in query */
+ sqlite3_int64 nRow; /* Number of rows in table */
+ sqlite3_int64 nToken; /* Number of tokens in table */
+ int nByte; /* Bytes of space to allocate */
+ int i;
+
+ /* Allocate the Fts5Bm25Data object */
+ nPhrase = pApi->xPhraseCount(pFts);
+ nByte = sizeof(Fts5Bm25Data) + nPhrase*2*sizeof(double);
+ p = (Fts5Bm25Data*)sqlite3_malloc(nByte);
+ if( p==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(p, 0, nByte);
+ p->nPhrase = nPhrase;
+ p->aIDF = (double*)&p[1];
+ p->aFreq = &p->aIDF[nPhrase];
+ }
+
+ /* Calculate the average document length for this FTS5 table */
+ if( rc==SQLITE_OK ) rc = pApi->xRowCount(pFts, &nRow);
+ if( rc==SQLITE_OK ) rc = pApi->xColumnTotalSize(pFts, -1, &nToken);
+ if( rc==SQLITE_OK ) p->avgdl = (double)nToken / (double)nRow;
+
+ /* Calculate an IDF for each phrase in the query */
+ for(i=0; rc==SQLITE_OK && i<nPhrase; i++){
+ sqlite3_int64 nHit = 0;
+ rc = pApi->xQueryPhrase(pFts, i, (void*)&nHit, fts5CountCb);
+ if( rc==SQLITE_OK ){
+ /* Calculate the IDF (Inverse Document Frequency) for phrase i.
+ ** This is done using the standard BM25 formula as found on wikipedia:
+ **
+ ** IDF = log( (N - nHit + 0.5) / (nHit + 0.5) )
+ **
+ ** where "N" is the total number of documents in the set and nHit
+ ** is the number that contain at least one instance of the phrase
+ ** under consideration.
+ **
+ ** The problem with this is that if (N < 2*nHit), the IDF is
+ ** negative. Which is undesirable. So the mimimum allowable IDF is
+ ** (1e-6) - roughly the same as a term that appears in just over
+ ** half of set of 5,000,000 documents. */
+ double idf = log( (nRow - nHit + 0.5) / (nHit + 0.5) );
+ if( idf<=0.0 ) idf = 1e-6;
+ p->aIDF[i] = idf;
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(p);
+ }else{
+ rc = pApi->xSetAuxdata(pFts, p, sqlite3_free);
+ }
+ if( rc!=SQLITE_OK ) p = 0;
+ }
+ *ppData = p;
+ return rc;
+}
+
+/*
+** Implementation of bm25() function.
+*/
+static void fts5Bm25Function(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ sqlite3_context *pCtx, /* Context for returning result/error */
+ int nVal, /* Number of values in apVal[] array */
+ sqlite3_value **apVal /* Array of trailing arguments */
+){
+ const double k1 = 1.2; /* Constant "k1" from BM25 formula */
+ const double b = 0.75; /* Constant "b" from BM25 formula */
+ int rc = SQLITE_OK; /* Error code */
+ double score = 0.0; /* SQL function return value */
+ Fts5Bm25Data *pData; /* Values allocated/calculated once only */
+ int i; /* Iterator variable */
+ int nInst; /* Value returned by xInstCount() */
+ double D; /* Total number of tokens in row */
+ double *aFreq; /* Array of phrase freq. for current row */
+
+ /* Calculate the phrase frequency (symbol "f(qi,D)" in the documentation)
+ ** for each phrase in the query for the current row. */
+ rc = fts5Bm25GetData(pApi, pFts, &pData);
+ if( rc==SQLITE_OK ){
+ aFreq = pData->aFreq;
+ memset(aFreq, 0, sizeof(double) * pData->nPhrase);
+ rc = pApi->xInstCount(pFts, &nInst);
+ }
+ for(i=0; rc==SQLITE_OK && i<nInst; i++){
+ int ip; int ic; int io;
+ rc = pApi->xInst(pFts, i, &ip, &ic, &io);
+ if( rc==SQLITE_OK ){
+ double w = (nVal > ic) ? sqlite3_value_double(apVal[ic]) : 1.0;
+ aFreq[ip] += w;
+ }
+ }
+
+ /* Figure out the total size of the current row in tokens. */
+ if( rc==SQLITE_OK ){
+ int nTok;
+ rc = pApi->xColumnSize(pFts, -1, &nTok);
+ D = (double)nTok;
+ }
+
+ /* Determine the BM25 score for the current row. */
+ for(i=0; rc==SQLITE_OK && i<pData->nPhrase; i++){
+ score += pData->aIDF[i] * (
+ ( aFreq[i] * (k1 + 1.0) ) /
+ ( aFreq[i] + k1 * (1 - b + b * D / pData->avgdl) )
+ );
+ }
+
+ /* If no error has occurred, return the calculated score. Otherwise,
+ ** throw an SQL exception. */
+ if( rc==SQLITE_OK ){
+ sqlite3_result_double(pCtx, -1.0 * score);
+ }else{
+ sqlite3_result_error_code(pCtx, rc);
+ }
+}
+
+int sqlite3Fts5AuxInit(fts5_api *pApi){
+ struct Builtin {
+ const char *zFunc; /* Function name (nul-terminated) */
+ void *pUserData; /* User-data pointer */
+ fts5_extension_function xFunc;/* Callback function */
+ void (*xDestroy)(void*); /* Destructor function */
+ } aBuiltin [] = {
+ { "snippet", 0, fts5SnippetFunction, 0 },
+ { "highlight", 0, fts5HighlightFunction, 0 },
+ { "bm25", 0, fts5Bm25Function, 0 },
+ };
+ int rc = SQLITE_OK; /* Return code */
+ int i; /* To iterate through builtin functions */
+
+ for(i=0; rc==SQLITE_OK && i<sizeof(aBuiltin)/sizeof(aBuiltin[0]); i++){
+ rc = pApi->xCreateFunction(pApi,
+ aBuiltin[i].zFunc,
+ aBuiltin[i].pUserData,
+ aBuiltin[i].xFunc,
+ aBuiltin[i].xDestroy
+ );
+ }
+
+ return rc;
+}
+#endif /* SQLITE_ENABLE_FTS5 */
+
+
diff --git a/ext/fts5/fts5_buffer.c b/ext/fts5/fts5_buffer.c
new file mode 100644
index 000000000..94fb4216d
--- /dev/null
+++ b/ext/fts5/fts5_buffer.c
@@ -0,0 +1,299 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+*/
+
+
+#ifdef SQLITE_ENABLE_FTS5
+
+#include "fts5Int.h"
+
+int sqlite3Fts5BufferGrow(int *pRc, Fts5Buffer *pBuf, int nByte){
+ /* A no-op if an error has already occurred */
+ if( *pRc ) return 1;
+
+ if( (pBuf->n + nByte) > pBuf->nSpace ){
+ u8 *pNew;
+ int nNew = pBuf->nSpace ? pBuf->nSpace*2 : 64;
+ while( nNew<(pBuf->n + nByte) ){
+ nNew = nNew * 2;
+ }
+ pNew = sqlite3_realloc(pBuf->p, nNew);
+ if( pNew==0 ){
+ *pRc = SQLITE_NOMEM;
+ return 1;
+ }else{
+ pBuf->nSpace = nNew;
+ pBuf->p = pNew;
+ }
+ }
+ return 0;
+}
+
+/*
+** Encode value iVal as an SQLite varint and append it to the buffer object
+** pBuf. If an OOM error occurs, set the error code in p.
+*/
+void sqlite3Fts5BufferAppendVarint(int *pRc, Fts5Buffer *pBuf, i64 iVal){
+ if( sqlite3Fts5BufferGrow(pRc, pBuf, 9) ) return;
+ pBuf->n += sqlite3PutVarint(&pBuf->p[pBuf->n], iVal);
+}
+
+void sqlite3Fts5Put32(u8 *aBuf, int iVal){
+ aBuf[0] = (iVal>>24) & 0x00FF;
+ aBuf[1] = (iVal>>16) & 0x00FF;
+ aBuf[2] = (iVal>> 8) & 0x00FF;
+ aBuf[3] = (iVal>> 0) & 0x00FF;
+}
+
+int sqlite3Fts5Get32(const u8 *aBuf){
+ return (aBuf[0] << 24) + (aBuf[1] << 16) + (aBuf[2] << 8) + aBuf[3];
+}
+
+void sqlite3Fts5BufferAppend32(int *pRc, Fts5Buffer *pBuf, int iVal){
+ if( sqlite3Fts5BufferGrow(pRc, pBuf, 4) ) return;
+ sqlite3Fts5Put32(&pBuf->p[pBuf->n], iVal);
+ pBuf->n += 4;
+}
+
+/*
+** Append buffer nData/pData to buffer pBuf. If an OOM error occurs, set
+** the error code in p. If an error has already occurred when this function
+** is called, it is a no-op.
+*/
+void sqlite3Fts5BufferAppendBlob(
+ int *pRc,
+ Fts5Buffer *pBuf,
+ int nData,
+ const u8 *pData
+){
+ assert( *pRc || nData>=0 );
+ if( sqlite3Fts5BufferGrow(pRc, pBuf, nData) ) return;
+ memcpy(&pBuf->p[pBuf->n], pData, nData);
+ pBuf->n += nData;
+}
+
+/*
+** Append the nul-terminated string zStr to the buffer pBuf. This function
+** ensures that the byte following the buffer data is set to 0x00, even
+** though this byte is not included in the pBuf->n count.
+*/
+void sqlite3Fts5BufferAppendString(
+ int *pRc,
+ Fts5Buffer *pBuf,
+ const char *zStr
+){
+ int nStr = strlen(zStr);
+ if( sqlite3Fts5BufferGrow(pRc, pBuf, nStr+1) ) return;
+ sqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr, (const u8*)zStr);
+ if( *pRc==SQLITE_OK ) pBuf->p[pBuf->n] = 0x00;
+}
+
+/*
+** Argument zFmt is a printf() style format string. This function performs
+** the printf() style processing, then appends the results to buffer pBuf.
+**
+** Like sqlite3Fts5BufferAppendString(), this function ensures that the byte
+** following the buffer data is set to 0x00, even though this byte is not
+** included in the pBuf->n count.
+*/
+void sqlite3Fts5BufferAppendPrintf(
+ int *pRc,
+ Fts5Buffer *pBuf,
+ char *zFmt, ...
+){
+ if( *pRc==SQLITE_OK ){
+ char *zTmp;
+ va_list ap;
+ va_start(ap, zFmt);
+ zTmp = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+
+ if( zTmp==0 ){
+ *pRc = SQLITE_NOMEM;
+ }else{
+ sqlite3Fts5BufferAppendString(pRc, pBuf, zTmp);
+ sqlite3_free(zTmp);
+ }
+ }
+}
+
+/*
+** Free any buffer allocated by pBuf. Zero the structure before returning.
+*/
+void sqlite3Fts5BufferFree(Fts5Buffer *pBuf){
+ sqlite3_free(pBuf->p);
+ memset(pBuf, 0, sizeof(Fts5Buffer));
+}
+
+/*
+** Zero the contents of the buffer object. But do not free the associated
+** memory allocation.
+*/
+void sqlite3Fts5BufferZero(Fts5Buffer *pBuf){
+ pBuf->n = 0;
+}
+
+/*
+** Set the buffer to contain nData/pData. If an OOM error occurs, leave an
+** the error code in p. If an error has already occurred when this function
+** is called, it is a no-op.
+*/
+void sqlite3Fts5BufferSet(
+ int *pRc,
+ Fts5Buffer *pBuf,
+ int nData,
+ const u8 *pData
+){
+ pBuf->n = 0;
+ sqlite3Fts5BufferAppendBlob(pRc, pBuf, nData, pData);
+}
+
+int sqlite3Fts5PoslistNext64(
+ const u8 *a, int n, /* Buffer containing poslist */
+ int *pi, /* IN/OUT: Offset within a[] */
+ i64 *piOff /* IN/OUT: Current offset */
+){
+ int i = *pi;
+ if( i>=n ){
+ /* EOF */
+ *piOff = -1;
+ return 1;
+ }else{
+ i64 iOff = *piOff;
+ int iVal;
+ i += getVarint32(&a[i], iVal);
+ if( iVal==1 ){
+ i += getVarint32(&a[i], iVal);
+ iOff = ((i64)iVal) << 32;
+ i += getVarint32(&a[i], iVal);
+ }
+ *piOff = iOff + (iVal-2);
+ *pi = i;
+ return 0;
+ }
+}
+
+
+/*
+** Advance the iterator object passed as the only argument. Return true
+** if the iterator reaches EOF, or false otherwise.
+*/
+int sqlite3Fts5PoslistReaderNext(Fts5PoslistReader *pIter){
+ if( sqlite3Fts5PoslistNext64(pIter->a, pIter->n, &pIter->i, &pIter->iPos)
+ || (pIter->iCol>=0 && (pIter->iPos >> 32) > pIter->iCol)
+ ){
+ pIter->bEof = 1;
+ }
+ return pIter->bEof;
+}
+
+int sqlite3Fts5PoslistReaderInit(
+ int iCol, /* If (iCol>=0), this column only */
+ const u8 *a, int n, /* Poslist buffer to iterate through */
+ Fts5PoslistReader *pIter /* Iterator object to initialize */
+){
+ memset(pIter, 0, sizeof(*pIter));
+ pIter->a = a;
+ pIter->n = n;
+ pIter->iCol = iCol;
+ do {
+ sqlite3Fts5PoslistReaderNext(pIter);
+ }while( pIter->bEof==0 && (pIter->iPos >> 32)<iCol );
+ return pIter->bEof;
+}
+
+int sqlite3Fts5PoslistWriterAppend(
+ Fts5Buffer *pBuf,
+ Fts5PoslistWriter *pWriter,
+ i64 iPos
+){
+ static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32;
+ int rc = SQLITE_OK;
+ if( (iPos & colmask) != (pWriter->iPrev & colmask) ){
+ fts5BufferAppendVarint(&rc, pBuf, 1);
+ fts5BufferAppendVarint(&rc, pBuf, (iPos >> 32));
+ pWriter->iPrev = (iPos & colmask);
+ }
+ fts5BufferAppendVarint(&rc, pBuf, (iPos - pWriter->iPrev) + 2);
+ pWriter->iPrev = iPos;
+ return rc;
+}
+
+int sqlite3Fts5PoslistNext(
+ const u8 *a, int n, /* Buffer containing poslist */
+ int *pi, /* IN/OUT: Offset within a[] */
+ int *piCol, /* IN/OUT: Current column */
+ int *piOff /* IN/OUT: Current token offset */
+){
+ int i = *pi;
+ int iVal;
+ if( i>=n ){
+ /* EOF */
+ return 1;
+ }
+ i += getVarint32(&a[i], iVal);
+ if( iVal==1 ){
+ i += getVarint32(&a[i], iVal);
+ *piCol = iVal;
+ *piOff = 0;
+ i += getVarint32(&a[i], iVal);
+ }
+ *piOff += (iVal-2);
+ *pi = i;
+ return 0;
+}
+
+void sqlite3Fts5BufferAppendListElem(
+ int *pRc, /* IN/OUT: Error code */
+ Fts5Buffer *pBuf, /* Buffer to append to */
+ const char *z, int n /* Value to append to buffer */
+){
+ int bParen = (n==0);
+ int nMax = n*2 + 2 + 1;
+ u8 *pOut;
+ int i;
+
+ /* Ensure the buffer has space for the new list element */
+ if( sqlite3Fts5BufferGrow(pRc, pBuf, nMax) ) return;
+ pOut = &pBuf->p[pBuf->n];
+
+ /* Figure out if we need the enclosing {} */
+ for(i=0; i<n && bParen==0; i++){
+ if( z[i]=='"' || z[i]==' ' ){
+ bParen = 1;
+ }
+ }
+
+ if( bParen ) *pOut++ = '{';
+ for(i=0; i<n; i++){
+ *pOut++ = z[i];
+ }
+ if( bParen ) *pOut++ = '}';
+
+ pBuf->n = pOut - pBuf->p;
+ *pOut = '\0';
+}
+
+void *sqlite3Fts5MallocZero(int *pRc, int nByte){
+ void *pRet = 0;
+ if( *pRc==SQLITE_OK ){
+ pRet = sqlite3_malloc(nByte);
+ if( pRet==0 && nByte>0 ){
+ *pRc = SQLITE_NOMEM;
+ }else{
+ memset(pRet, 0, nByte);
+ }
+ }
+ return pRet;
+}
+#endif /* SQLITE_ENABLE_FTS5 */
+
diff --git a/ext/fts5/fts5_config.c b/ext/fts5/fts5_config.c
new file mode 100644
index 000000000..0450db691
--- /dev/null
+++ b/ext/fts5/fts5_config.c
@@ -0,0 +1,795 @@
+/*
+** 2014 Jun 09
+**
+** 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 an SQLite module implementing full-text search.
+*/
+
+#ifdef SQLITE_ENABLE_FTS5
+
+
+#include "fts5Int.h"
+
+#define FTS5_DEFAULT_PAGE_SIZE 1000
+#define FTS5_DEFAULT_AUTOMERGE 4
+#define FTS5_DEFAULT_CRISISMERGE 16
+
+/* Maximum allowed page size */
+#define FTS5_MAX_PAGE_SIZE (128*1024)
+
+static int fts5_iswhitespace(char x){
+ return (x==' ');
+}
+
+static int fts5_isopenquote(char x){
+ return (x=='"' || x=='\'' || x=='[' || x=='`');
+}
+
+/*
+** Argument pIn points to a character that is part of a nul-terminated
+** string. Return a pointer to the first character following *pIn in
+** the string that is not a white-space character.
+*/
+static const char *fts5ConfigSkipWhitespace(const char *pIn){
+ const char *p = pIn;
+ if( p ){
+ while( fts5_iswhitespace(*p) ){ p++; }
+ }
+ return p;
+}
+
+/*
+** Argument pIn points to a character that is part of a nul-terminated
+** string. Return a pointer to the first character following *pIn in
+** the string that is not a "bareword" character.
+*/
+static const char *fts5ConfigSkipBareword(const char *pIn){
+ const char *p = pIn;
+ while( *p && *p!=' ' && *p!=':' && *p!='!' && *p!='@'
+ && *p!='#' && *p!='$' && *p!='%' && *p!='^' && *p!='&'
+ && *p!='*' && *p!='(' && *p!=')' && *p!='='
+ ){
+ p++;
+ }
+ if( p==pIn ) p = 0;
+ return p;
+}
+
+static int fts5_isdigit(char a){
+ return (a>='0' && a<='9');
+}
+
+
+
+static const char *fts5ConfigSkipLiteral(const char *pIn){
+ const char *p = pIn;
+ if( p ){
+ switch( *p ){
+ case 'n': case 'N':
+ if( sqlite3_strnicmp("null", p, 4)==0 ){
+ p = &p[4];
+ }else{
+ p = 0;
+ }
+ break;
+
+ case 'x': case 'X':
+ p++;
+ if( *p=='\'' ){
+ p++;
+ while( (*p>='a' && *p<='f')
+ || (*p>='A' && *p<='F')
+ || (*p>='0' && *p<='9')
+ ){
+ p++;
+ }
+ if( *p=='\'' && 0==((p-pIn)%2) ){
+ p++;
+ }else{
+ p = 0;
+ }
+ }else{
+ p = 0;
+ }
+ break;
+
+ case '\'':
+ p++;
+ while( p ){
+ if( *p=='\'' ){
+ p++;
+ if( *p!='\'' ) break;
+ }
+ p++;
+ if( *p==0 ) p = 0;
+ }
+ break;
+
+ default:
+ /* maybe a number */
+ if( *p=='+' || *p=='-' ) p++;
+ while( fts5_isdigit(*p) ) p++;
+
+ /* At this point, if the literal was an integer, the parse is
+ ** finished. Or, if it is a floating point value, it may continue
+ ** with either a decimal point or an 'E' character. */
+ if( *p=='.' && fts5_isdigit(p[1]) ){
+ p += 2;
+ while( fts5_isdigit(*p) ) p++;
+ }
+ if( p==pIn ) p = 0;
+
+ break;
+ }
+ }
+
+ return p;
+}
+
+static int fts5Dequote(char *z){
+ char q;
+ int iIn = 1;
+ int iOut = 0;
+ int bRet = 1;
+ q = z[0];
+
+ assert( q=='[' || q=='\'' || q=='"' || q=='`' );
+ if( q=='[' ) q = ']';
+
+ while( z[iIn] ){
+ if( z[iIn]==q ){
+ if( z[iIn+1]!=q ){
+ if( z[iIn+1]=='\0' ) bRet = 0;
+ break;
+ }
+ z[iOut++] = q;
+ iIn += 2;
+ }else{
+ z[iOut++] = z[iIn++];
+ }
+ }
+ z[iOut] = '\0';
+
+ return bRet;
+}
+
+/*
+** Convert an SQL-style quoted string into a normal string by removing
+** the quote characters. The conversion is done in-place. If the
+** input does not begin with a quote character, then this routine
+** is a no-op.
+**
+** Examples:
+**
+** "abc" becomes abc
+** 'xyz' becomes xyz
+** [pqr] becomes pqr
+** `mno` becomes mno
+*/
+void sqlite3Fts5Dequote(char *z){
+ char quote; /* Quote character (if any ) */
+
+ assert( 0==fts5_iswhitespace(z[0]) );
+ quote = z[0];
+ if( quote=='[' || quote=='\'' || quote=='"' || quote=='`' ){
+ fts5Dequote(z);
+ }
+}
+
+/*
+** Trim any white-space from the right of nul-terminated string z.
+*/
+static char *fts5TrimString(char *z){
+ int n = strlen(z);
+ while( n>0 && fts5_iswhitespace(z[n-1]) ){
+ z[--n] = '\0';
+ }
+ while( fts5_iswhitespace(*z) ) z++;
+ return z;
+}
+
+/*
+** Duplicate the string passed as the only argument into a buffer allocated
+** by sqlite3_malloc().
+**
+** Return 0 if an OOM error is encountered.
+*/
+static char *fts5Strdup(int *pRc, const char *z){
+ char *pRet = 0;
+ if( *pRc==SQLITE_OK ){
+ pRet = sqlite3_mprintf("%s", z);
+ if( pRet==0 ) *pRc = SQLITE_NOMEM;
+ }
+ return pRet;
+}
+
+/*
+** Argument z points to a nul-terminated string containing an SQL identifier.
+** This function returns a copy of the identifier enclosed in backtick
+** quotes.
+*/
+static char *fts5EscapeName(int *pRc, const char *z){
+ char *pRet = 0;
+ if( *pRc==SQLITE_OK ){
+ int n = strlen(z);
+ pRet = (char*)sqlite3_malloc(2 + 2*n + 1);
+ if( pRet==0 ){
+ *pRc = SQLITE_NOMEM;
+ }else{
+ int i;
+ char *p = pRet;
+ *p++ = '`';
+ for(i=0; i<n; i++){
+ if( z[i]=='`' ) *p++ = '`';
+ *p++ = z[i];
+ }
+ *p++ = '`';
+ *p++ = '\0';
+ }
+ }
+ return pRet;
+}
+
+/*
+** Parse the "special" CREATE VIRTUAL TABLE directive and update
+** configuration object pConfig as appropriate.
+**
+** If successful, object pConfig is updated and SQLITE_OK returned. If
+** an error occurs, an SQLite error code is returned and an error message
+** may be left in *pzErr. It is the responsibility of the caller to
+** eventually free any such error message using sqlite3_free().
+*/
+static int fts5ConfigParseSpecial(
+ Fts5Global *pGlobal,
+ Fts5Config *pConfig, /* Configuration object to update */
+ const char *zCmd, /* Special command to parse */
+ int nCmd, /* Size of zCmd in bytes */
+ const char *zArg, /* Argument to parse */
+ char **pzErr /* OUT: Error message */
+){
+ if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){
+ const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES;
+ int rc = SQLITE_OK;
+ const char *p;
+ if( pConfig->aPrefix ){
+ *pzErr = sqlite3_mprintf("multiple prefix=... directives");
+ rc = SQLITE_ERROR;
+ }else{
+ pConfig->aPrefix = sqlite3Fts5MallocZero(&rc, nByte);
+ }
+ p = zArg;
+ while( rc==SQLITE_OK && p[0] ){
+ int nPre = 0;
+ while( p[0]==' ' ) p++;
+ while( p[0]>='0' && p[0]<='9' && nPre<1000 ){
+ nPre = nPre*10 + (p[0] - '0');
+ p++;
+ }
+ while( p[0]==' ' ) p++;
+ if( p[0]==',' ){
+ p++;
+ }else if( p[0] ){
+ *pzErr = sqlite3_mprintf("malformed prefix=... directive");
+ rc = SQLITE_ERROR;
+ }
+ if( rc==SQLITE_OK && (nPre==0 || nPre>=1000) ){
+ *pzErr = sqlite3_mprintf("prefix length out of range: %d", nPre);
+ rc = SQLITE_ERROR;
+ }
+ pConfig->aPrefix[pConfig->nPrefix] = nPre;
+ pConfig->nPrefix++;
+ }
+ return rc;
+ }
+
+ if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){
+ int rc = SQLITE_OK;
+ const char *p = (const char*)zArg;
+ int nArg = strlen(zArg) + 1;
+ char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg);
+ char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2);
+ char *pSpace = pDel;
+
+ if( azArg && pSpace ){
+ if( pConfig->pTok ){
+ *pzErr = sqlite3_mprintf("multiple tokenize=... directives");
+ rc = SQLITE_ERROR;
+ }else{
+ for(nArg=0; p && *p; nArg++){
+ const char *p2 = fts5ConfigSkipWhitespace(p);
+ if( p2 && *p2=='\'' ){
+ p = fts5ConfigSkipLiteral(p2);
+ }else{
+ p = fts5ConfigSkipBareword(p2);
+ }
+ if( p ){
+ memcpy(pSpace, p2, p-p2);
+ azArg[nArg] = pSpace;
+ sqlite3Fts5Dequote(pSpace);
+ pSpace += (p - p2) + 1;
+ p = fts5ConfigSkipWhitespace(p);
+ }
+ }
+ if( p==0 ){
+ *pzErr = sqlite3_mprintf("parse error in tokenize directive");
+ rc = SQLITE_ERROR;
+ }else{
+ rc = sqlite3Fts5GetTokenizer(pGlobal,
+ (const char**)azArg, nArg, &pConfig->pTok, &pConfig->pTokApi
+ );
+ if( rc!=SQLITE_OK ){
+ *pzErr = sqlite3_mprintf("error in tokenizer constructor");
+ }
+ }
+ }
+ }
+
+ sqlite3_free(azArg);
+ sqlite3_free(pDel);
+ return rc;
+ }
+
+ if( sqlite3_strnicmp("content", zCmd, nCmd)==0 ){
+ int rc = SQLITE_OK;
+ if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){
+ *pzErr = sqlite3_mprintf("multiple content=... directives");
+ rc = SQLITE_ERROR;
+ }else{
+ if( zArg[0] ){
+ pConfig->eContent = FTS5_CONTENT_EXTERNAL;
+ pConfig->zContent = sqlite3_mprintf("%Q.%Q", pConfig->zDb, zArg);
+ }else{
+ pConfig->eContent = FTS5_CONTENT_NONE;
+ pConfig->zContent = sqlite3_mprintf(
+ "%Q.'%q_docsize'", pConfig->zDb, pConfig->zName
+ );
+ }
+ if( pConfig->zContent==0 ) rc = SQLITE_NOMEM;
+ }
+ return rc;
+ }
+
+ if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){
+ int rc = SQLITE_OK;
+ if( pConfig->zContentRowid ){
+ *pzErr = sqlite3_mprintf("multiple content_rowid=... directives");
+ rc = SQLITE_ERROR;
+ }else{
+ pConfig->zContentRowid = fts5EscapeName(&rc, zArg);
+ }
+ return rc;
+ }
+
+ *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd);
+ return SQLITE_ERROR;
+}
+
+/*
+** Allocate an instance of the default tokenizer ("simple") at
+** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error
+** code if an error occurs.
+*/
+static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){
+ assert( pConfig->pTok==0 && pConfig->pTokApi==0 );
+ return sqlite3Fts5GetTokenizer(
+ pGlobal, 0, 0, &pConfig->pTok, &pConfig->pTokApi
+ );
+}
+
+/*
+** Arguments nArg/azArg contain the string arguments passed to the xCreate
+** or xConnect method of the virtual table. This function attempts to
+** allocate an instance of Fts5Config containing the results of parsing
+** those arguments.
+**
+** If successful, SQLITE_OK is returned and *ppOut is set to point to the
+** new Fts5Config object. If an error occurs, an SQLite error code is
+** returned, *ppOut is set to NULL and an error message may be left in
+** *pzErr. It is the responsibility of the caller to eventually free any
+** such error message using sqlite3_free().
+*/
+int sqlite3Fts5ConfigParse(
+ Fts5Global *pGlobal,
+ sqlite3 *db,
+ int nArg, /* Number of arguments */
+ const char **azArg, /* Array of nArg CREATE VIRTUAL TABLE args */
+ Fts5Config **ppOut, /* OUT: Results of parse */
+ char **pzErr /* OUT: Error message */
+){
+ int rc = SQLITE_OK; /* Return code */
+ Fts5Config *pRet; /* New object to return */
+ int i;
+
+ *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config));
+ if( pRet==0 ) return SQLITE_NOMEM;
+ memset(pRet, 0, sizeof(Fts5Config));
+ pRet->db = db;
+ pRet->iCookie = -1;
+
+ pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg);
+ pRet->zDb = fts5Strdup(&rc, azArg[1]);
+ pRet->zName = fts5Strdup(&rc, azArg[2]);
+ if( rc==SQLITE_OK && sqlite3_stricmp(pRet->zName, FTS5_RANK_NAME)==0 ){
+ *pzErr = sqlite3_mprintf("reserved fts5 table name: %s", pRet->zName);
+ rc = SQLITE_ERROR;
+ }
+
+ for(i=3; rc==SQLITE_OK && i<nArg; i++){
+ char *zDup = fts5Strdup(&rc, azArg[i]);
+ if( zDup ){
+ char *zCol = 0;
+ int bParseError = 0;
+
+ /* Check if this is a quoted column name */
+ if( fts5_isopenquote(zDup[0]) ){
+ bParseError = fts5Dequote(zDup);
+ zCol = zDup;
+ }else{
+ char *z = (char*)fts5ConfigSkipBareword(zDup);
+ if( *z=='\0' ){
+ zCol = zDup;
+ }else{
+ int nCmd = z - zDup;
+ z = (char*)fts5ConfigSkipWhitespace(z);
+ if( *z!='=' ){
+ bParseError = 1;
+ }else{
+ z++;
+ z = fts5TrimString(z);
+ if( fts5_isopenquote(*z) ){
+ if( fts5Dequote(z) ) bParseError = 1;
+ }else{
+ char *z2 = (char*)fts5ConfigSkipBareword(z);
+ if( *z2 ) bParseError = 1;
+ }
+ if( bParseError==0 ){
+ rc = fts5ConfigParseSpecial(pGlobal, pRet, zDup, nCmd, z, pzErr);
+ }
+ }
+ }
+ }
+
+ if( bParseError ){
+ assert( *pzErr==0 );
+ *pzErr = sqlite3_mprintf("parse error in \"%s\"", zDup);
+ rc = SQLITE_ERROR;
+ }else if( zCol ){
+ if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME)
+ || 0==sqlite3_stricmp(zCol, FTS5_ROWID_NAME)
+ ){
+ *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zCol);
+ rc = SQLITE_ERROR;
+ }else{
+ pRet->azCol[pRet->nCol++] = zCol;
+ zDup = 0;
+ }
+ }
+
+ sqlite3_free(zDup);
+ }
+ }
+
+ /* If a tokenizer= option was successfully parsed, the tokenizer has
+ ** already been allocated. Otherwise, allocate an instance of the default
+ ** tokenizer (simple) now. */
+ if( rc==SQLITE_OK && pRet->pTok==0 ){
+ rc = fts5ConfigDefaultTokenizer(pGlobal, pRet);
+ }
+
+ /* If no zContent option was specified, fill in the default values. */
+ if( rc==SQLITE_OK && pRet->eContent==FTS5_CONTENT_NORMAL ){
+ pRet->zContent = sqlite3_mprintf("%Q.'%q_content'", pRet->zDb, pRet->zName);
+ if( pRet->zContent==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ sqlite3_free(pRet->zContentRowid);
+ pRet->zContentRowid = 0;
+ }
+ }
+ if( rc==SQLITE_OK && pRet->zContentRowid==0 ){
+ pRet->zContentRowid = fts5Strdup(&rc, "rowid");
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3Fts5ConfigFree(pRet);
+ *ppOut = 0;
+ }
+ return rc;
+}
+
+/*
+** Free the configuration object passed as the only argument.
+*/
+void sqlite3Fts5ConfigFree(Fts5Config *pConfig){
+ if( pConfig ){
+ int i;
+ if( pConfig->pTok && pConfig->pTokApi->xDelete ){
+ pConfig->pTokApi->xDelete(pConfig->pTok);
+ }
+ sqlite3_free(pConfig->zDb);
+ sqlite3_free(pConfig->zName);
+ for(i=0; i<pConfig->nCol; i++){
+ sqlite3_free(pConfig->azCol[i]);
+ }
+ sqlite3_free(pConfig->azCol);
+ sqlite3_free(pConfig->aPrefix);
+ sqlite3_free(pConfig->zRank);
+ sqlite3_free(pConfig->zRankArgs);
+ sqlite3_free(pConfig->zContent);
+ sqlite3_free(pConfig->zContentRowid);
+ sqlite3_free(pConfig);
+ }
+}
+
+/*
+** Call sqlite3_declare_vtab() based on the contents of the configuration
+** object passed as the only argument. Return SQLITE_OK if successful, or
+** an SQLite error code if an error occurs.
+*/
+int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){
+ int i;
+ int rc;
+ char *zSql;
+ char *zOld;
+
+ zSql = (char*)sqlite3_mprintf("CREATE TABLE x(");
+ for(i=0; zSql && i<pConfig->nCol; i++){
+ zOld = zSql;
+ zSql = sqlite3_mprintf("%s%s%Q", zOld, (i==0?"":", "), pConfig->azCol[i]);
+ sqlite3_free(zOld);
+ }
+
+ if( zSql ){
+ zOld = zSql;
+ zSql = sqlite3_mprintf("%s, %Q HIDDEN, %s HIDDEN)",
+ zOld, pConfig->zName, FTS5_RANK_NAME
+ );
+ sqlite3_free(zOld);
+ }
+
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_declare_vtab(pConfig->db, zSql);
+ sqlite3_free(zSql);
+ }
+
+ return rc;
+}
+
+/*
+** Tokenize the text passed via the second and third arguments.
+**
+** The callback is invoked once for each token in the input text. The
+** arguments passed to it are, in order:
+**
+** void *pCtx // Copy of 4th argument to sqlite3Fts5Tokenize()
+** const char *pToken // Pointer to buffer containing token
+** int nToken // Size of token in bytes
+** int iStart // Byte offset of start of token within input text
+** int iEnd // Byte offset of end of token within input text
+** int iPos // Position of token in input (first token is 0)
+**
+** If the callback returns a non-zero value the tokenization is abandoned
+** and no further callbacks are issued.
+**
+** This function returns SQLITE_OK if successful or an SQLite error code
+** if an error occurs. If the tokenization was abandoned early because
+** the callback returned SQLITE_DONE, this is not an error and this function
+** still returns SQLITE_OK. Or, if the tokenization was abandoned early
+** because the callback returned another non-zero value, it is assumed
+** to be an SQLite error code and returned to the caller.
+*/
+int sqlite3Fts5Tokenize(
+ Fts5Config *pConfig, /* FTS5 Configuration object */
+ const char *pText, int nText, /* Text to tokenize */
+ void *pCtx, /* Context passed to xToken() */
+ int (*xToken)(void*, const char*, int, int, int) /* Callback */
+){
+ return pConfig->pTokApi->xTokenize(pConfig->pTok, pCtx, pText, nText, xToken);
+}
+
+/*
+** Argument pIn points to the first character in what is expected to be
+** a comma-separated list of SQL literals followed by a ')' character.
+** If it actually is this, return a pointer to the ')'. Otherwise, return
+** NULL to indicate a parse error.
+*/
+static const char *fts5ConfigSkipArgs(const char *pIn){
+ const char *p = pIn;
+
+ while( 1 ){
+ p = fts5ConfigSkipWhitespace(p);
+ p = fts5ConfigSkipLiteral(p);
+ p = fts5ConfigSkipWhitespace(p);
+ if( p==0 || *p==')' ) break;
+ if( *p!=',' ){
+ p = 0;
+ break;
+ }
+ p++;
+ }
+
+ return p;
+}
+
+/*
+** Parameter zIn contains a rank() function specification. The format of
+** this is:
+**
+** + Bareword (function name)
+** + Open parenthesis - "("
+** + Zero or more SQL literals in a comma separated list
+** + Close parenthesis - ")"
+*/
+int sqlite3Fts5ConfigParseRank(
+ const char *zIn, /* Input string */
+ char **pzRank, /* OUT: Rank function name */
+ char **pzRankArgs /* OUT: Rank function arguments */
+){
+ const char *p = zIn;
+ const char *pRank;
+ char *zRank = 0;
+ char *zRankArgs = 0;
+ int rc = SQLITE_OK;
+
+ *pzRank = 0;
+ *pzRankArgs = 0;
+
+ p = fts5ConfigSkipWhitespace(p);
+ pRank = p;
+ p = fts5ConfigSkipBareword(p);
+
+ if( p ){
+ zRank = sqlite3Fts5MallocZero(&rc, 1 + p - pRank);
+ if( zRank ) memcpy(zRank, pRank, p-pRank);
+ }else{
+ rc = SQLITE_ERROR;
+ }
+
+ if( rc==SQLITE_OK ){
+ p = fts5ConfigSkipWhitespace(p);
+ if( *p!='(' ) rc = SQLITE_ERROR;
+ p++;
+ }
+ if( rc==SQLITE_OK ){
+ const char *pArgs;
+ p = fts5ConfigSkipWhitespace(p);
+ pArgs = p;
+ if( *p!=')' ){
+ p = fts5ConfigSkipArgs(p);
+ if( p==0 ){
+ rc = SQLITE_ERROR;
+ }else if( p!=pArgs ){
+ zRankArgs = sqlite3Fts5MallocZero(&rc, 1 + p - pArgs);
+ if( zRankArgs ) memcpy(zRankArgs, pArgs, p-pArgs);
+ }
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(zRank);
+ assert( zRankArgs==0 );
+ }else{
+ *pzRank = zRank;
+ *pzRankArgs = zRankArgs;
+ }
+ return rc;
+}
+
+int sqlite3Fts5ConfigSetValue(
+ Fts5Config *pConfig,
+ const char *zKey,
+ sqlite3_value *pVal,
+ int *pbBadkey
+){
+ int rc = SQLITE_OK;
+ if( 0==sqlite3_stricmp(zKey, "cookie") ){
+ pConfig->iCookie = sqlite3_value_int(pVal);
+ }
+
+ else if( 0==sqlite3_stricmp(zKey, "pgsz") ){
+ int pgsz = 0;
+ if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
+ pgsz = sqlite3_value_int(pVal);
+ }
+ if( pgsz<=0 || pgsz>FTS5_MAX_PAGE_SIZE ){
+ if( pbBadkey ) *pbBadkey = 1;
+ }else{
+ pConfig->pgsz = pgsz;
+ }
+ }
+
+ else if( 0==sqlite3_stricmp(zKey, "automerge") ){
+ int nAutomerge = -1;
+ if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
+ nAutomerge = sqlite3_value_int(pVal);
+ }
+ if( nAutomerge<0 || nAutomerge>64 ){
+ if( pbBadkey ) *pbBadkey = 1;
+ }else{
+ if( nAutomerge==1 ) nAutomerge = FTS5_DEFAULT_AUTOMERGE;
+ pConfig->nAutomerge = nAutomerge;
+ }
+ }
+
+ else if( 0==sqlite3_stricmp(zKey, "crisismerge") ){
+ int nCrisisMerge = -1;
+ if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
+ nCrisisMerge = sqlite3_value_int(pVal);
+ }
+ if( nCrisisMerge<0 ){
+ if( pbBadkey ) *pbBadkey = 1;
+ }else{
+ if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
+ pConfig->nCrisisMerge = nCrisisMerge;
+ }
+ }
+
+ else if( 0==sqlite3_stricmp(zKey, "rank") ){
+ const char *zIn = (const char*)sqlite3_value_text(pVal);
+ char *zRank;
+ char *zRankArgs;
+ rc = sqlite3Fts5ConfigParseRank(zIn, &zRank, &zRankArgs);
+ if( rc==SQLITE_OK ){
+ sqlite3_free(pConfig->zRank);
+ sqlite3_free(pConfig->zRankArgs);
+ pConfig->zRank = zRank;
+ pConfig->zRankArgs = zRankArgs;
+ }else if( rc==SQLITE_ERROR ){
+ rc = SQLITE_OK;
+ if( pbBadkey ) *pbBadkey = 1;
+ }
+ }else{
+ if( pbBadkey ) *pbBadkey = 1;
+ }
+ return rc;
+}
+
+/*
+** Load the contents of the %_config table into memory.
+*/
+int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
+ const char *zSelect = "SELECT k, v FROM %Q.'%q_config'";
+ char *zSql;
+ sqlite3_stmt *p = 0;
+ int rc;
+
+ /* Set default values */
+ pConfig->pgsz = FTS5_DEFAULT_PAGE_SIZE;
+ pConfig->nAutomerge = FTS5_DEFAULT_AUTOMERGE;
+ pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE;
+
+ zSql = sqlite3_mprintf(zSelect, pConfig->zDb, pConfig->zName);
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p, 0);
+ sqlite3_free(zSql);
+ }
+
+ assert( rc==SQLITE_OK || p==0 );
+ if( rc==SQLITE_OK ){
+ while( SQLITE_ROW==sqlite3_step(p) ){
+ const char *zK = (const char*)sqlite3_column_text(p, 0);
+ sqlite3_value *pVal = sqlite3_column_value(p, 1);
+ sqlite3Fts5ConfigSetValue(pConfig, zK, pVal, 0);
+ }
+ rc = sqlite3_finalize(p);
+ }
+
+ if( rc==SQLITE_OK ){
+ pConfig->iCookie = iCookie;
+ }
+ return rc;
+}
+
+#endif /* SQLITE_ENABLE_FTS5 */
diff --git a/ext/fts5/fts5_expr.c b/ext/fts5/fts5_expr.c
new file mode 100644
index 000000000..878b54f53
--- /dev/null
+++ b/ext/fts5/fts5_expr.c
@@ -0,0 +1,1701 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+**
+*/
+
+#ifdef SQLITE_ENABLE_FTS5
+
+
+#include "fts5Int.h"
+#include "fts5parse.h"
+
+/*
+** All token types in the generated fts5parse.h file are greater than 0.
+*/
+#define FTS5_EOF 0
+
+typedef struct Fts5ExprTerm Fts5ExprTerm;
+
+/*
+** Functions generated by lemon from fts5parse.y.
+*/
+void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(u64));
+void sqlite3Fts5ParserFree(void*, void (*freeProc)(void*));
+void sqlite3Fts5Parser(void*, int, Fts5Token, Fts5Parse*);
+
+struct Fts5Expr {
+ Fts5Index *pIndex;
+ Fts5ExprNode *pRoot;
+ int bDesc; /* Iterate in descending docid order */
+ int nPhrase; /* Number of phrases in expression */
+ Fts5ExprPhrase **apExprPhrase; /* Pointers to phrase objects */
+};
+
+/*
+** eType:
+** Expression node type. Always one of:
+**
+** FTS5_AND (pLeft, pRight valid)
+** FTS5_OR (pLeft, pRight valid)
+** FTS5_NOT (pLeft, pRight valid)
+** FTS5_STRING (pNear valid)
+*/
+struct Fts5ExprNode {
+ int eType; /* Node type */
+ Fts5ExprNode *pLeft; /* Left hand child node */
+ Fts5ExprNode *pRight; /* Right hand child node */
+ Fts5ExprNearset *pNear; /* For FTS5_STRING - cluster of phrases */
+ int bEof; /* True at EOF */
+ i64 iRowid; /* Current rowid */
+};
+
+/*
+** An instance of the following structure represents a single search term
+** or term prefix.
+*/
+struct Fts5ExprTerm {
+ int bPrefix; /* True for a prefix term */
+ char *zTerm; /* nul-terminated term */
+ Fts5IndexIter *pIter; /* Iterator for this term */
+};
+
+/*
+** A phrase. One or more terms that must appear in a contiguous sequence
+** within a document for it to match.
+*/
+struct Fts5ExprPhrase {
+ Fts5ExprNode *pNode; /* FTS5_STRING node this phrase is part of */
+ Fts5Buffer poslist; /* Current position list */
+ int nTerm; /* Number of entries in aTerm[] */
+ Fts5ExprTerm aTerm[0]; /* Terms that make up this phrase */
+};
+
+/*
+** One or more phrases that must appear within a certain token distance of
+** each other within each matching document.
+*/
+struct Fts5ExprNearset {
+ int nNear; /* NEAR parameter */
+ int iCol; /* Column to search (-1 -> all columns) */
+ int nPhrase; /* Number of entries in aPhrase[] array */
+ Fts5ExprPhrase *apPhrase[0]; /* Array of phrase pointers */
+};
+
+
+/*
+** Parse context.
+*/
+struct Fts5Parse {
+ Fts5Config *pConfig;
+ char *zErr;
+ int rc;
+ int nPhrase; /* Size of apPhrase array */
+ Fts5ExprPhrase **apPhrase; /* Array of all phrases */
+ Fts5ExprNode *pExpr; /* Result of a successful parse */
+};
+
+void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){
+ if( pParse->rc==SQLITE_OK ){
+ va_list ap;
+ va_start(ap, zFmt);
+ pParse->zErr = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+ pParse->rc = SQLITE_ERROR;
+ }
+}
+
+static int fts5ExprIsspace(char t){
+ return t==' ' || t=='\t' || t=='\n' || t=='\r';
+}
+
+static int fts5ExprIstoken(char t){
+ return fts5ExprIsspace(t)==0 && t!='\0'
+ && t!=':' && t!='(' && t!=')'
+ && t!=',' && t!='+' && t!='*';
+}
+
+/*
+** Read the first token from the nul-terminated string at *pz.
+*/
+static int fts5ExprGetToken(
+ Fts5Parse *pParse,
+ const char **pz, /* IN/OUT: Pointer into buffer */
+ Fts5Token *pToken
+){
+ const char *z = *pz;
+ int tok;
+
+ /* Skip past any whitespace */
+ while( fts5ExprIsspace(*z) ) z++;
+
+ pToken->p = z;
+ pToken->n = 1;
+ switch( *z ){
+ case '(': tok = FTS5_LP; break;
+ case ')': tok = FTS5_RP; break;
+ case ':': tok = FTS5_COLON; break;
+ case ',': tok = FTS5_COMMA; break;
+ case '+': tok = FTS5_PLUS; break;
+ case '*': tok = FTS5_STAR; break;
+ case '\0': tok = FTS5_EOF; break;
+
+ case '"': {
+ const char *z2;
+ tok = FTS5_STRING;
+
+ for(z2=&z[1]; 1; z2++){
+ if( z2[0]=='"' ){
+ z2++;
+ if( z2[0]!='"' ) break;
+ }
+ if( z2[0]=='\0' ){
+ sqlite3Fts5ParseError(pParse, "unterminated string");
+ return FTS5_EOF;
+ }
+ }
+ pToken->n = (z2 - z);
+ break;
+ }
+
+ default: {
+ const char *z2;
+ tok = FTS5_STRING;
+ for(z2=&z[1]; fts5ExprIstoken(*z2); z2++);
+ pToken->n = (z2 - z);
+ if( pToken->n==2 && memcmp(pToken->p, "OR", 2)==0 ) tok = FTS5_OR;
+ if( pToken->n==3 && memcmp(pToken->p, "NOT", 3)==0 ) tok = FTS5_NOT;
+ if( pToken->n==3 && memcmp(pToken->p, "AND", 3)==0 ) tok = FTS5_AND;
+ break;
+ }
+ }
+
+ *pz = &pToken->p[pToken->n];
+ return tok;
+}
+
+static void *fts5ParseAlloc(u64 t){ return sqlite3_malloc((int)t); }
+static void fts5ParseFree(void *p){ sqlite3_free(p); }
+
+int sqlite3Fts5ExprNew(
+ Fts5Config *pConfig, /* FTS5 Configuration */
+ const char *zExpr, /* Expression text */
+ Fts5Expr **ppNew,
+ char **pzErr
+){
+ Fts5Parse sParse;
+ Fts5Token token;
+ const char *z = zExpr;
+ int t; /* Next token type */
+ void *pEngine;
+ Fts5Expr *pNew;
+
+ *ppNew = 0;
+ *pzErr = 0;
+ memset(&sParse, 0, sizeof(sParse));
+ pEngine = sqlite3Fts5ParserAlloc(fts5ParseAlloc);
+ if( pEngine==0 ){ return SQLITE_NOMEM; }
+ sParse.pConfig = pConfig;
+
+ do {
+ t = fts5ExprGetToken(&sParse, &z, &token);
+ sqlite3Fts5Parser(pEngine, t, token, &sParse);
+ }while( sParse.rc==SQLITE_OK && t!=FTS5_EOF );
+ sqlite3Fts5ParserFree(pEngine, fts5ParseFree);
+
+ assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 );
+ if( sParse.rc==SQLITE_OK ){
+ *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr));
+ if( pNew==0 ){
+ sParse.rc = SQLITE_NOMEM;
+ sqlite3Fts5ParseNodeFree(sParse.pExpr);
+ }else{
+ pNew->pRoot = sParse.pExpr;
+ pNew->pIndex = 0;
+ pNew->apExprPhrase = sParse.apPhrase;
+ pNew->nPhrase = sParse.nPhrase;
+ sParse.apPhrase = 0;
+ }
+ }
+
+ sqlite3_free(sParse.apPhrase);
+ *pzErr = sParse.zErr;
+ return sParse.rc;
+}
+
+static char *fts5ExprStrdup(int *pRc, const char *zIn){
+ char *zRet = 0;
+ if( *pRc==SQLITE_OK ){
+ int nByte = strlen(zIn) + 1;
+ zRet = sqlite3_malloc(nByte);
+ if( zRet ){
+ memcpy(zRet, zIn, nByte);
+ }else{
+ *pRc = SQLITE_NOMEM;
+ }
+ }
+ return zRet;
+}
+
+static void *fts5ExprMalloc(int *pRc, int nByte){
+ void *pRet = 0;
+ if( *pRc==SQLITE_OK ){
+ pRet = sqlite3_malloc(nByte);
+ if( pRet ){
+ memset(pRet, 0, nByte);
+ }else{
+ *pRc = SQLITE_NOMEM;
+ }
+ }
+ return pRet;
+}
+
+/*
+** Create a new FTS5 expression by cloning phrase iPhrase of the
+** expression passed as the second argument.
+*/
+int sqlite3Fts5ExprPhraseExpr(
+ Fts5Config *pConfig,
+ Fts5Expr *pExpr,
+ int iPhrase,
+ Fts5Expr **ppNew
+){
+ int rc = SQLITE_OK; /* Return code */
+ Fts5ExprPhrase *pOrig = 0; /* The phrase extracted from pExpr */
+ int i; /* Used to iterate through phrase terms */
+
+ /* Components of the new expression object */
+ Fts5Expr *pNew;
+ Fts5ExprPhrase **apPhrase;
+ Fts5ExprNode *pNode;
+ Fts5ExprNearset *pNear;
+ Fts5ExprPhrase *pCopy;
+
+ pOrig = pExpr->apExprPhrase[iPhrase];
+ pNew = (Fts5Expr*)fts5ExprMalloc(&rc, sizeof(Fts5Expr));
+ apPhrase = (Fts5ExprPhrase**)fts5ExprMalloc(&rc, sizeof(Fts5ExprPhrase*));
+ pNode = (Fts5ExprNode*)fts5ExprMalloc(&rc, sizeof(Fts5ExprNode));
+ pNear = (Fts5ExprNearset*)fts5ExprMalloc(&rc,
+ sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*)
+ );
+ pCopy = (Fts5ExprPhrase*)fts5ExprMalloc(&rc,
+ sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * pOrig->nTerm
+ );
+
+ for(i=0; rc==SQLITE_OK && i<pOrig->nTerm; i++){
+ pCopy->aTerm[i].zTerm = fts5ExprStrdup(&rc, pOrig->aTerm[i].zTerm);
+ pCopy->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix;
+ }
+
+ if( rc==SQLITE_OK ){
+ /* All the allocations succeeded. Put the expression object together. */
+ pNew->pIndex = pExpr->pIndex;
+ pNew->pRoot = pNode;
+ pNew->nPhrase = 1;
+ pNew->apExprPhrase = apPhrase;
+ pNew->apExprPhrase[0] = pCopy;
+
+ pNode->eType = FTS5_STRING;
+ pNode->pNear = pNear;
+
+ pNear->iCol = -1;
+ pNear->nPhrase = 1;
+ pNear->apPhrase[0] = pCopy;
+
+ pCopy->nTerm = pOrig->nTerm;
+ pCopy->pNode = pNode;
+ }else{
+ /* At least one allocation failed. Free them all. */
+ if( pCopy ){
+ for(i=0; i<pOrig->nTerm; i++){
+ sqlite3_free(pCopy->aTerm[i].zTerm);
+ }
+ sqlite3_free(pCopy);
+ sqlite3_free(pNear);
+ sqlite3_free(pNode);
+ sqlite3_free(apPhrase);
+ sqlite3_free(pNew);
+ pNew = 0;
+ }
+ }
+
+ *ppNew = pNew;
+ return rc;
+}
+
+/*
+** Free the expression node object passed as the only argument.
+*/
+void sqlite3Fts5ParseNodeFree(Fts5ExprNode *p){
+ if( p ){
+ sqlite3Fts5ParseNodeFree(p->pLeft);
+ sqlite3Fts5ParseNodeFree(p->pRight);
+ sqlite3Fts5ParseNearsetFree(p->pNear);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Free the expression object passed as the only argument.
+*/
+void sqlite3Fts5ExprFree(Fts5Expr *p){
+ if( p ){
+ sqlite3Fts5ParseNodeFree(p->pRoot);
+ sqlite3_free(p->apExprPhrase);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** All individual term iterators in pPhrase are guaranteed to be valid and
+** pointing to the same rowid when this function is called. This function
+** checks if the current rowid really is a match, and if so populates
+** the pPhrase->poslist buffer accordingly. Output parameter *pbMatch
+** is set to true if this is really a match, or false otherwise.
+**
+** SQLITE_OK is returned if an error occurs, or an SQLite error code
+** otherwise. It is not considered an error code if the current rowid is
+** not a match.
+*/
+static int fts5ExprPhraseIsMatch(
+ Fts5Expr *pExpr, /* Expression pPhrase belongs to */
+ int iCol, /* If >=0, search for matches in iCol only */
+ Fts5ExprPhrase *pPhrase, /* Phrase object to initialize */
+ int *pbMatch /* OUT: Set to true if really a match */
+){
+ Fts5PoslistWriter writer = {0};
+ Fts5PoslistReader aStatic[4];
+ Fts5PoslistReader *aIter = aStatic;
+ int i;
+ int rc = SQLITE_OK;
+
+ fts5BufferZero(&pPhrase->poslist);
+
+ /* If the aStatic[] array is not large enough, allocate a large array
+ ** using sqlite3_malloc(). This approach could be improved upon. */
+ if( pPhrase->nTerm>(sizeof(aStatic) / sizeof(aStatic[0])) ){
+ int nByte = sizeof(Fts5PoslistReader) * pPhrase->nTerm;
+ aIter = (Fts5PoslistReader*)sqlite3_malloc(nByte);
+ if( !aIter ) return SQLITE_NOMEM;
+ }
+
+ /* Initialize a term iterator for each term in the phrase */
+ for(i=0; i<pPhrase->nTerm; i++){
+ int n;
+ const u8 *a;
+ rc = sqlite3Fts5IterPoslist(pPhrase->aTerm[i].pIter, &a, &n);
+ if( rc || sqlite3Fts5PoslistReaderInit(iCol, a, n, &aIter[i]) ){
+ goto ismatch_out;
+ }
+ }
+
+ while( 1 ){
+ int bMatch;
+ i64 iPos = aIter[0].iPos;
+ do {
+ bMatch = 1;
+ for(i=0; i<pPhrase->nTerm; i++){
+ Fts5PoslistReader *pPos = &aIter[i];
+ i64 iAdj = iPos + i;
+ if( pPos->iPos!=iAdj ){
+ bMatch = 0;
+ while( pPos->iPos<iAdj ){
+ if( sqlite3Fts5PoslistReaderNext(pPos) ) goto ismatch_out;
+ }
+ if( pPos->iPos>iAdj ) iPos = pPos->iPos-i;
+ }
+ }
+ }while( bMatch==0 );
+
+ /* Append position iPos to the output */
+ rc = sqlite3Fts5PoslistWriterAppend(&pPhrase->poslist, &writer, iPos);
+ if( rc!=SQLITE_OK ) goto ismatch_out;
+
+ for(i=0; i<pPhrase->nTerm; i++){
+ if( sqlite3Fts5PoslistReaderNext(&aIter[i]) ) goto ismatch_out;
+ }
+ }
+
+ ismatch_out:
+ *pbMatch = (pPhrase->poslist.n>0);
+ if( aIter!=aStatic ) sqlite3_free(aIter);
+ return rc;
+}
+
+typedef struct Fts5LookaheadReader Fts5LookaheadReader;
+struct Fts5LookaheadReader {
+ const u8 *a; /* Buffer containing position list */
+ int n; /* Size of buffer a[] in bytes */
+ int i; /* Current offset in position list */
+ i64 iPos; /* Current position */
+ i64 iLookahead; /* Next position */
+};
+
+#define FTS5_LOOKAHEAD_EOF (((i64)1) << 62)
+
+static int fts5LookaheadReaderNext(Fts5LookaheadReader *p){
+ p->iPos = p->iLookahead;
+ if( sqlite3Fts5PoslistNext64(p->a, p->n, &p->i, &p->iLookahead) ){
+ p->iLookahead = FTS5_LOOKAHEAD_EOF;
+ }
+ return (p->iPos==FTS5_LOOKAHEAD_EOF);
+}
+
+static int fts5LookaheadReaderInit(
+ const u8 *a, int n, /* Buffer to read position list from */
+ Fts5LookaheadReader *p /* Iterator object to initialize */
+){
+ memset(p, 0, sizeof(Fts5LookaheadReader));
+ p->a = a;
+ p->n = n;
+ fts5LookaheadReaderNext(p);
+ return fts5LookaheadReaderNext(p);
+}
+
+#if 0
+static int fts5LookaheadReaderEof(Fts5LookaheadReader *p){
+ return (p->iPos==FTS5_LOOKAHEAD_EOF);
+}
+#endif
+
+typedef struct Fts5NearTrimmer Fts5NearTrimmer;
+struct Fts5NearTrimmer {
+ Fts5LookaheadReader reader; /* Input iterator */
+ Fts5PoslistWriter writer; /* Writer context */
+ Fts5Buffer *pOut; /* Output poslist */
+};
+
+/*
+** The near-set object passed as the first argument contains more than
+** one phrase. All phrases currently point to the same row. The
+** Fts5ExprPhrase.poslist buffers are populated accordingly. This function
+** tests if the current row contains instances of each phrase sufficiently
+** close together to meet the NEAR constraint. Output variable *pbMatch
+** is set to true if it does, or false otherwise.
+**
+** If no error occurs, SQLITE_OK is returned. Or, if an error does occur,
+** an SQLite error code. If a value other than SQLITE_OK is returned, the
+** final value of *pbMatch is undefined.
+**
+** TODO: This function should also edit the position lists associated
+** with each phrase to remove any phrase instances that are not part of
+** a set of intances that collectively matches the NEAR constraint.
+*/
+static int fts5ExprNearIsMatch(Fts5ExprNearset *pNear, int *pbMatch){
+ Fts5NearTrimmer aStatic[4];
+ Fts5NearTrimmer *a = aStatic;
+
+ Fts5ExprPhrase **apPhrase = pNear->apPhrase;
+
+ int i;
+ int rc = SQLITE_OK;
+ int bMatch;
+
+ assert( pNear->nPhrase>1 );
+
+ /* If the aStatic[] array is not large enough, allocate a large array
+ ** using sqlite3_malloc(). This approach could be improved upon. */
+ if( pNear->nPhrase>(sizeof(aStatic) / sizeof(aStatic[0])) ){
+ int nByte = sizeof(Fts5LookaheadReader) * pNear->nPhrase;
+ a = (Fts5NearTrimmer*)sqlite3_malloc(nByte);
+ if( !a ) return SQLITE_NOMEM;
+ memset(a, 0, nByte);
+ }else{
+ memset(aStatic, 0, sizeof(aStatic));
+ }
+
+ /* Initialize a lookahead iterator for each phrase. After passing the
+ ** buffer and buffer size to the lookaside-reader init function, zero
+ ** the phrase poslist buffer. The new poslist for the phrase (containing
+ ** the same entries as the original with some entries removed on account
+ ** of the NEAR constraint) is written over the original even as it is
+ ** being read. This is safe as the entries for the new poslist are a
+ ** subset of the old, so it is not possible for data yet to be read to
+ ** be overwritten. */
+ for(i=0; i<pNear->nPhrase; i++){
+ Fts5Buffer *pPoslist = &apPhrase[i]->poslist;
+ fts5LookaheadReaderInit(pPoslist->p, pPoslist->n, &a[i].reader);
+ pPoslist->n = 0;
+ a[i].pOut = pPoslist;
+ }
+
+ while( 1 ){
+ int iAdv;
+ i64 iMin;
+ i64 iMax;
+
+ /* This block advances the phrase iterators until they point to a set of
+ ** entries that together comprise a match. */
+ iMax = a[0].reader.iPos;
+ do {
+ bMatch = 1;
+ for(i=0; i<pNear->nPhrase; i++){
+ Fts5LookaheadReader *pPos = &a[i].reader;
+ iMin = iMax - pNear->apPhrase[i]->nTerm - pNear->nNear;
+ if( pPos->iPos<iMin || pPos->iPos>iMax ){
+ bMatch = 0;
+ while( pPos->iPos<iMin ){
+ if( fts5LookaheadReaderNext(pPos) ) goto ismatch_out;
+ }
+ if( pPos->iPos>iMax ) iMax = pPos->iPos;
+ }
+ }
+ }while( bMatch==0 );
+
+ /* Add an entry to each output position list */
+ for(i=0; i<pNear->nPhrase; i++){
+ i64 iPos = a[i].reader.iPos;
+ Fts5PoslistWriter *pWriter = &a[i].writer;
+ if( a[i].pOut->n==0 || iPos!=pWriter->iPrev ){
+ sqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos);
+ }
+ }
+
+ iAdv = 0;
+ iMin = a[0].reader.iLookahead;
+ for(i=0; i<pNear->nPhrase; i++){
+ if( a[i].reader.iLookahead < iMin ){
+ iMin = a[i].reader.iLookahead;
+ iAdv = i;
+ }
+ }
+ if( fts5LookaheadReaderNext(&a[iAdv].reader) ) goto ismatch_out;
+ }
+
+ ismatch_out:
+ *pbMatch = (a[0].pOut->n>0);
+ if( a!=aStatic ) sqlite3_free(a);
+ return rc;
+}
+
+/*
+** Advance each term iterator in each phrase in pNear. If any reach EOF,
+** set output variable *pbEof to true before returning.
+*/
+static int fts5ExprNearAdvanceAll(
+ Fts5Expr *pExpr, /* Expression pPhrase belongs to */
+ Fts5ExprNearset *pNear, /* Near object to advance iterators of */
+ int *pbEof /* OUT: Set to true if phrase at EOF */
+){
+ int i, j; /* Phrase and token index, respectively */
+
+ for(i=0; i<pNear->nPhrase; i++){
+ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
+ for(j=0; j<pPhrase->nTerm; j++){
+ Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter;
+ int rc = sqlite3Fts5IterNext(pIter);
+ if( rc || sqlite3Fts5IterEof(pIter) ){
+ *pbEof = 1;
+ return rc;
+ }
+ }
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** Advance iterator pIter until it points to a value equal to or laster
+** than the initial value of *piLast. If this means the iterator points
+** to a value laster than *piLast, update *piLast to the new lastest value.
+**
+** If the iterator reaches EOF, set *pbEof to true before returning. If
+** an error occurs, set *pRc to an error code. If either *pbEof or *pRc
+** are set, return a non-zero value. Otherwise, return zero.
+*/
+static int fts5ExprAdvanceto(
+ Fts5IndexIter *pIter, /* Iterator to advance */
+ int bDesc, /* True if iterator is "rowid DESC" */
+ i64 *piLast, /* IN/OUT: Lastest rowid seen so far */
+ int *pRc, /* OUT: Error code */
+ int *pbEof /* OUT: Set to true if EOF */
+){
+ i64 iLast = *piLast;
+ i64 iRowid;
+
+ iRowid = sqlite3Fts5IterRowid(pIter);
+ if( (bDesc==0 && iLast>iRowid) || (bDesc && iLast<iRowid) ){
+ sqlite3Fts5IterNextFrom(pIter, iLast);
+ if( sqlite3Fts5IterEof(pIter) ){
+ *pbEof = 1;
+ return 1;
+ }
+ iRowid = sqlite3Fts5IterRowid(pIter);
+ assert( (bDesc==0 && iRowid>=iLast) || (bDesc==1 && iRowid<=iLast) );
+ }
+ *piLast = iRowid;
+
+ return 0;
+}
+
+/*
+** All individual term iterators in pNear are guaranteed to be valid when
+** this function is called. This function checks if all term iterators
+** point to the same rowid, and if not, advances them until they do.
+** If an EOF is reached before this happens, *pbEof is set to true before
+** returning.
+**
+** SQLITE_OK is returned if an error occurs, or an SQLite error code
+** otherwise. It is not considered an error code if an iterator reaches
+** EOF.
+*/
+static int fts5ExprNearNextRowidMatch(
+ Fts5Expr *pExpr, /* Expression pPhrase belongs to */
+ Fts5ExprNode *pNode,
+ int bFromValid,
+ i64 iFrom
+){
+ Fts5ExprNearset *pNear = pNode->pNear;
+ int rc = SQLITE_OK;
+ int i, j; /* Phrase and token index, respectively */
+ i64 iLast; /* Lastest rowid any iterator points to */
+ int bMatch; /* True if all terms are at the same rowid */
+
+ /* Initialize iLast, the "lastest" rowid any iterator points to. If the
+ ** iterator skips through rowids in the default ascending order, this means
+ ** the maximum rowid. Or, if the iterator is "ORDER BY rowid DESC", then it
+ ** means the minimum rowid. */
+ iLast = sqlite3Fts5IterRowid(pNear->apPhrase[0]->aTerm[0].pIter);
+ if( bFromValid && (iFrom>iLast)==(pExpr->bDesc==0) ){
+ assert( pExpr->bDesc || iFrom>=iLast );
+ iLast = iFrom;
+ }
+
+ do {
+ bMatch = 1;
+ for(i=0; i<pNear->nPhrase; i++){
+ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
+ for(j=0; j<pPhrase->nTerm; j++){
+ Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter;
+ i64 iRowid = sqlite3Fts5IterRowid(pIter);
+ if( iRowid!=iLast ) bMatch = 0;
+ if( fts5ExprAdvanceto(pIter, pExpr->bDesc, &iLast, &rc, &pNode->bEof) ){
+ return rc;
+ }
+ }
+ }
+ }while( bMatch==0 );
+
+ pNode->iRowid = iLast;
+ return rc;
+}
+
+/*
+** Argument pNode points to a NEAR node. All individual term iterators
+** point to valid entries (not EOF).
+*
+** This function tests if the term iterators currently all point to the
+** same rowid, and if so, if the row matches the phrase and NEAR constraints.
+** If so, the pPhrase->poslist buffers are populated and the pNode->iRowid
+** variable set before returning. Or, if the current combination of
+** iterators is not a match, they are advanced until they are. If one of
+** the iterators reaches EOF before a match is found, *pbEof is set to
+** true before returning. The final values of the pPhrase->poslist and
+** iRowid fields are undefined in this case.
+**
+** SQLITE_OK is returned if an error occurs, or an SQLite error code
+** otherwise. It is not considered an error code if an iterator reaches
+** EOF.
+*/
+static int fts5ExprNearNextMatch(
+ Fts5Expr *pExpr, /* Expression that pNear is a part of */
+ Fts5ExprNode *pNode, /* The "NEAR" node (FTS5_STRING) */
+ int bFromValid,
+ i64 iFrom
+){
+ int rc = SQLITE_OK;
+ Fts5ExprNearset *pNear = pNode->pNear;
+ while( 1 ){
+ int i;
+
+ /* Advance the iterators until they all point to the same rowid */
+ rc = fts5ExprNearNextRowidMatch(pExpr, pNode, bFromValid, iFrom);
+ if( pNode->bEof || rc!=SQLITE_OK ) break;
+
+ /* Check that each phrase in the nearset matches the current row.
+ ** Populate the pPhrase->poslist buffers at the same time. If any
+ ** phrase is not a match, break out of the loop early. */
+ for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
+ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
+ if( pPhrase->nTerm>1 || pNear->iCol>=0 ){
+ int bMatch = 0;
+ rc = fts5ExprPhraseIsMatch(pExpr, pNear->iCol, pPhrase, &bMatch);
+ if( bMatch==0 ) break;
+ }else{
+ int n;
+ const u8 *a;
+ rc = sqlite3Fts5IterPoslist(pPhrase->aTerm[0].pIter, &a, &n);
+ fts5BufferSet(&rc, &pPhrase->poslist, n, a);
+ }
+ }
+
+ if( rc==SQLITE_OK && i==pNear->nPhrase ){
+ int bMatch = 1;
+ if( pNear->nPhrase>1 ){
+ rc = fts5ExprNearIsMatch(pNear, &bMatch);
+ }
+ if( rc!=SQLITE_OK || bMatch ) break;
+ }
+
+ /* If control flows to here, then the current rowid is not a match.
+ ** Advance all term iterators in all phrases to the next rowid. */
+ if( rc==SQLITE_OK ){
+ rc = fts5ExprNearAdvanceAll(pExpr, pNear, &pNode->bEof);
+ }
+ if( pNode->bEof || rc!=SQLITE_OK ) break;
+ }
+
+ return rc;
+}
+
+/*
+** Initialize all term iterators in the pNear object. If any term is found
+** to match no documents at all, set *pbEof to true and return immediately,
+** without initializing any further iterators.
+*/
+static int fts5ExprNearInitAll(
+ Fts5Expr *pExpr,
+ Fts5ExprNode *pNode
+){
+ Fts5ExprNearset *pNear = pNode->pNear;
+ Fts5ExprTerm *pTerm;
+ Fts5ExprPhrase *pPhrase;
+ int i, j;
+ int rc = SQLITE_OK;
+
+ for(i=0; rc==SQLITE_OK && i<pNear->nPhrase; i++){
+ pPhrase = pNear->apPhrase[i];
+ for(j=0; j<pPhrase->nTerm; j++){
+ pTerm = &pPhrase->aTerm[j];
+ rc = sqlite3Fts5IndexQuery(
+ pExpr->pIndex, pTerm->zTerm, strlen(pTerm->zTerm),
+ (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) |
+ (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0),
+ &pTerm->pIter
+ );
+ assert( rc==SQLITE_OK || pTerm->pIter==0 );
+ if( pTerm->pIter==0 || sqlite3Fts5IterEof(pTerm->pIter) ){
+ pNode->bEof = 1;
+ break;
+ }
+ }
+ }
+
+ return rc;
+}
+
+/* fts5ExprNodeNext() calls fts5ExprNodeNextMatch(). And vice-versa. */
+static int fts5ExprNodeNextMatch(Fts5Expr*, Fts5ExprNode*, int, i64);
+
+/*
+** Compare the values currently indicated by the two nodes as follows:
+**
+** res = (*p1) - (*p2)
+**
+** Nodes that point to values that come later in the iteration order are
+** considered to be larger. Nodes at EOF are the largest of all.
+**
+** This means that if the iteration order is ASC, then numerically larger
+** rowids are considered larger. Or if it is the default DESC, numerically
+** smaller rowids are larger.
+*/
+static int fts5NodeCompare(
+ Fts5Expr *pExpr,
+ Fts5ExprNode *p1,
+ Fts5ExprNode *p2
+){
+ if( p2->bEof ) return -1;
+ if( p1->bEof ) return +1;
+ if( pExpr->bDesc==0 ){
+ if( p1->iRowid<p2->iRowid ) return -1;
+ return (p1->iRowid > p2->iRowid);
+ }else{
+ if( p1->iRowid>p2->iRowid ) return -1;
+ return (p1->iRowid < p2->iRowid);
+ }
+}
+
+/*
+** Advance node iterator pNode, part of expression pExpr. If argument
+** bFromValid is zero, then pNode is advanced exactly once. Or, if argument
+** bFromValid is non-zero, then pNode is advanced until it is at or past
+** rowid value iFrom. Whether "past" means "less than" or "greater than"
+** depends on whether this is an ASC or DESC iterator.
+*/
+static int fts5ExprNodeNext(
+ Fts5Expr *pExpr,
+ Fts5ExprNode *pNode,
+ int bFromValid,
+ i64 iFrom
+){
+ int rc = SQLITE_OK;
+
+ if( pNode->bEof==0 ){
+ switch( pNode->eType ){
+ case FTS5_STRING: {
+ rc = fts5ExprNearAdvanceAll(pExpr, pNode->pNear, &pNode->bEof);
+ break;
+ };
+
+ case FTS5_AND: {
+ rc = fts5ExprNodeNext(pExpr, pNode->pLeft, bFromValid, iFrom);
+ if( rc==SQLITE_OK ){
+ /* todo: update (iFrom/bFromValid) here */
+ rc = fts5ExprNodeNext(pExpr, pNode->pRight, bFromValid, iFrom);
+ }
+ break;
+ }
+
+ case FTS5_OR: {
+ Fts5ExprNode *p1 = pNode->pLeft;
+ Fts5ExprNode *p2 = pNode->pRight;
+ int cmp = fts5NodeCompare(pExpr, p1, p2);
+
+ if( cmp==0 ){
+ rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom);
+ if( rc==SQLITE_OK ){
+ rc = fts5ExprNodeNext(pExpr, p2, bFromValid, iFrom);
+ }
+ }else{
+ rc = fts5ExprNodeNext(pExpr, (cmp < 0) ? p1 : p2, bFromValid, iFrom);
+ }
+
+ break;
+ }
+
+ default: assert( pNode->eType==FTS5_NOT ); {
+ rc = fts5ExprNodeNext(pExpr, pNode->pLeft, bFromValid, iFrom);
+ break;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = fts5ExprNodeNextMatch(pExpr, pNode, bFromValid, iFrom);
+ }
+ }
+
+ return rc;
+}
+
+static void fts5ExprSetEof(Fts5ExprNode *pNode){
+ if( pNode ){
+ pNode->bEof = 1;
+ fts5ExprSetEof(pNode->pLeft);
+ fts5ExprSetEof(pNode->pRight);
+ }
+}
+
+/*
+**
+*/
+static int fts5ExprNodeNextMatch(
+ Fts5Expr *pExpr,
+ Fts5ExprNode *pNode,
+ int bFromValid,
+ i64 iFrom
+){
+ int rc = SQLITE_OK;
+ if( pNode->bEof==0 ){
+ switch( pNode->eType ){
+
+ case FTS5_STRING: {
+ rc = fts5ExprNearNextMatch(pExpr, pNode, bFromValid, iFrom);
+ break;
+ }
+
+ case FTS5_AND: {
+ Fts5ExprNode *p1 = pNode->pLeft;
+ Fts5ExprNode *p2 = pNode->pRight;
+
+ while( p1->bEof==0 && p2->bEof==0 && p2->iRowid!=p1->iRowid ){
+ Fts5ExprNode *pAdv;
+ assert( pExpr->bDesc==0 || pExpr->bDesc==1 );
+ if( pExpr->bDesc==(p1->iRowid > p2->iRowid) ){
+ pAdv = p1;
+ if( bFromValid==0 || pExpr->bDesc==(p2->iRowid < iFrom) ){
+ iFrom = p2->iRowid;
+ }
+ }else{
+ pAdv = p2;
+ if( bFromValid==0 || pExpr->bDesc==(p1->iRowid < iFrom) ){
+ iFrom = p1->iRowid;
+ }
+ }
+ rc = fts5ExprNodeNext(pExpr, pAdv, 1, iFrom);
+ if( rc!=SQLITE_OK ) break;
+ }
+ if( p1->bEof || p2->bEof ){
+ fts5ExprSetEof(pNode);
+ }
+ pNode->iRowid = p1->iRowid;
+ break;
+ }
+
+ case FTS5_OR: {
+ Fts5ExprNode *p1 = pNode->pLeft;
+ Fts5ExprNode *p2 = pNode->pRight;
+ Fts5ExprNode *pNext = (fts5NodeCompare(pExpr, p1, p2) > 0 ? p2 : p1);
+ pNode->bEof = pNext->bEof;
+ pNode->iRowid = pNext->iRowid;
+ break;
+ }
+
+ default: assert( pNode->eType==FTS5_NOT ); {
+ Fts5ExprNode *p1 = pNode->pLeft;
+ Fts5ExprNode *p2 = pNode->pRight;
+ while( rc==SQLITE_OK ){
+ int cmp;
+ while( rc==SQLITE_OK && (cmp = fts5NodeCompare(pExpr, p1, p2))>0 ){
+ rc = fts5ExprNodeNext(pExpr, p2, bFromValid, iFrom);
+ }
+ if( rc || cmp ) break;
+ rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom);
+ }
+ pNode->bEof = p1->bEof;
+ pNode->iRowid = p1->iRowid;
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/*
+** Set node pNode, which is part of expression pExpr, to point to the first
+** match. If there are no matches, set the Node.bEof flag to indicate EOF.
+**
+** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise.
+** It is not an error if there are no matches.
+*/
+static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){
+ int rc = SQLITE_OK;
+ pNode->bEof = 0;
+
+ if( pNode->eType==FTS5_STRING ){
+
+ /* Initialize all term iterators in the NEAR object. */
+ rc = fts5ExprNearInitAll(pExpr, pNode);
+
+ /* Attempt to advance to the first match */
+ if( rc==SQLITE_OK && pNode->bEof==0 ){
+ rc = fts5ExprNearNextMatch(pExpr, pNode, 0, 0);
+ }
+
+ }else{
+ rc = fts5ExprNodeFirst(pExpr, pNode->pLeft);
+ if( rc==SQLITE_OK ){
+ rc = fts5ExprNodeFirst(pExpr, pNode->pRight);
+ }
+ if( rc==SQLITE_OK ){
+ rc = fts5ExprNodeNextMatch(pExpr, pNode, 0, 0);
+ }
+ }
+ return rc;
+}
+
+
+
+/*
+** Begin iterating through the set of documents in index pIdx matched by
+** the MATCH expression passed as the first argument. If the "bDesc" parameter
+** is passed a non-zero value, iteration is in descending rowid order. Or,
+** if it is zero, in ascending order.
+**
+** Return SQLITE_OK if successful, or an SQLite error code otherwise. It
+** is not considered an error if the query does not match any documents.
+*/
+int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, int bDesc){
+ int rc = SQLITE_OK;
+ if( p->pRoot ){
+ p->pIndex = pIdx;
+ p->bDesc = bDesc;
+ rc = fts5ExprNodeFirst(p, p->pRoot);
+ }
+ return rc;
+}
+
+/*
+** Move to the next document
+**
+** Return SQLITE_OK if successful, or an SQLite error code otherwise. It
+** is not considered an error if the query does not match any documents.
+*/
+int sqlite3Fts5ExprNext(Fts5Expr *p){
+ int rc;
+ rc = fts5ExprNodeNext(p, p->pRoot, 0, 0);
+ return rc;
+}
+
+int sqlite3Fts5ExprEof(Fts5Expr *p){
+ return (p->pRoot==0 || p->pRoot->bEof);
+}
+
+i64 sqlite3Fts5ExprRowid(Fts5Expr *p){
+ return p->pRoot->iRowid;
+}
+
+/*
+** Argument pIn points to a buffer of nIn bytes. This function allocates
+** and returns a new buffer populated with a copy of (pIn/nIn) with a
+** nul-terminator byte appended to it.
+**
+** It is the responsibility of the caller to eventually free the returned
+** buffer using sqlite3_free(). If an OOM error occurs, NULL is returned.
+*/
+static char *fts5Strndup(int *pRc, const char *pIn, int nIn){
+ char *zRet = 0;
+ if( *pRc==SQLITE_OK ){
+ zRet = (char*)sqlite3_malloc(nIn+1);
+ if( zRet ){
+ memcpy(zRet, pIn, nIn);
+ zRet[nIn] = '\0';
+ }else{
+ *pRc = SQLITE_NOMEM;
+ }
+ }
+ return zRet;
+}
+
+static int fts5ParseStringFromToken(Fts5Token *pToken, char **pz){
+ int rc = SQLITE_OK;
+ *pz = fts5Strndup(&rc, pToken->p, pToken->n);
+ return rc;
+}
+
+/*
+** Free the phrase object passed as the only argument.
+*/
+static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){
+ if( pPhrase ){
+ int i;
+ for(i=0; i<pPhrase->nTerm; i++){
+ Fts5ExprTerm *pTerm = &pPhrase->aTerm[i];
+ sqlite3_free(pTerm->zTerm);
+ if( pTerm->pIter ){
+ sqlite3Fts5IterClose(pTerm->pIter);
+ }
+ }
+ fts5BufferFree(&pPhrase->poslist);
+ sqlite3_free(pPhrase);
+ }
+}
+
+/*
+** If argument pNear is NULL, then a new Fts5ExprNearset object is allocated
+** and populated with pPhrase. Or, if pNear is not NULL, phrase pPhrase is
+** appended to it and the results returned.
+**
+** If an OOM error occurs, both the pNear and pPhrase objects are freed and
+** NULL returned.
+*/
+Fts5ExprNearset *sqlite3Fts5ParseNearset(
+ Fts5Parse *pParse, /* Parse context */
+ Fts5ExprNearset *pNear, /* Existing nearset, or NULL */
+ Fts5ExprPhrase *pPhrase /* Recently parsed phrase */
+){
+ const int SZALLOC = 8;
+ Fts5ExprNearset *pRet = 0;
+
+ if( pParse->rc==SQLITE_OK ){
+ if( pPhrase==0 ){
+ return pNear;
+ }
+ if( pNear==0 ){
+ int nByte = sizeof(Fts5ExprNearset) + SZALLOC * sizeof(Fts5ExprPhrase*);
+ pRet = sqlite3_malloc(nByte);
+ if( pRet==0 ){
+ pParse->rc = SQLITE_NOMEM;
+ }else{
+ memset(pRet, 0, nByte);
+ pRet->iCol = -1;
+ }
+ }else if( (pNear->nPhrase % SZALLOC)==0 ){
+ int nNew = pRet->nPhrase + SZALLOC;
+ int nByte = sizeof(Fts5ExprNearset) + nNew * sizeof(Fts5ExprPhrase*);
+
+ pRet = (Fts5ExprNearset*)sqlite3_realloc(pNear, nByte);
+ if( pRet==0 ){
+ pParse->rc = SQLITE_NOMEM;
+ }
+ }else{
+ pRet = pNear;
+ }
+ }
+
+ if( pRet==0 ){
+ assert( pParse->rc!=SQLITE_OK );
+ sqlite3Fts5ParseNearsetFree(pNear);
+ sqlite3Fts5ParsePhraseFree(pPhrase);
+ }else{
+ pRet->apPhrase[pRet->nPhrase++] = pPhrase;
+ }
+ return pRet;
+}
+
+typedef struct TokenCtx TokenCtx;
+struct TokenCtx {
+ Fts5ExprPhrase *pPhrase;
+};
+
+/*
+** Callback for tokenizing terms used by ParseTerm().
+*/
+static int fts5ParseTokenize(
+ void *pContext, /* Pointer to Fts5InsertCtx object */
+ const char *pToken, /* Buffer containing token */
+ int nToken, /* Size of token in bytes */
+ int iStart, /* Start offset of token */
+ int iEnd /* End offset of token */
+){
+ int rc = SQLITE_OK;
+ const int SZALLOC = 8;
+ TokenCtx *pCtx = (TokenCtx*)pContext;
+ Fts5ExprPhrase *pPhrase = pCtx->pPhrase;
+ Fts5ExprTerm *pTerm;
+
+ if( pPhrase==0 || (pPhrase->nTerm % SZALLOC)==0 ){
+ Fts5ExprPhrase *pNew;
+ int nNew = SZALLOC + (pPhrase ? pPhrase->nTerm : 0);
+
+ pNew = (Fts5ExprPhrase*)sqlite3_realloc(pPhrase,
+ sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * nNew
+ );
+ if( pNew==0 ) return SQLITE_NOMEM;
+ if( pPhrase==0 ) memset(pNew, 0, sizeof(Fts5ExprPhrase));
+ pCtx->pPhrase = pPhrase = pNew;
+ pNew->nTerm = nNew - SZALLOC;
+ }
+
+ pTerm = &pPhrase->aTerm[pPhrase->nTerm++];
+ memset(pTerm, 0, sizeof(Fts5ExprTerm));
+ pTerm->zTerm = fts5Strndup(&rc, pToken, nToken);
+
+ return rc;
+}
+
+
+/*
+** Free the phrase object passed as the only argument.
+*/
+void sqlite3Fts5ParsePhraseFree(Fts5ExprPhrase *pPhrase){
+ fts5ExprPhraseFree(pPhrase);
+}
+
+/*
+** Free the phrase object passed as the second argument.
+*/
+void sqlite3Fts5ParseNearsetFree(Fts5ExprNearset *pNear){
+ if( pNear ){
+ int i;
+ for(i=0; i<pNear->nPhrase; i++){
+ fts5ExprPhraseFree(pNear->apPhrase[i]);
+ }
+ sqlite3_free(pNear);
+ }
+}
+
+void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){
+ assert( pParse->pExpr==0 );
+ pParse->pExpr = p;
+}
+
+/*
+** This function is called by the parser to process a string token. The
+** string may or may not be quoted. In any case it is tokenized and a
+** phrase object consisting of all tokens returned.
+*/
+Fts5ExprPhrase *sqlite3Fts5ParseTerm(
+ Fts5Parse *pParse, /* Parse context */
+ Fts5ExprPhrase *pAppend, /* Phrase to append to */
+ Fts5Token *pToken, /* String to tokenize */
+ int bPrefix /* True if there is a trailing "*" */
+){
+ Fts5Config *pConfig = pParse->pConfig;
+ TokenCtx sCtx; /* Context object passed to callback */
+ int rc; /* Tokenize return code */
+ char *z = 0;
+
+ memset(&sCtx, 0, sizeof(TokenCtx));
+ sCtx.pPhrase = pAppend;
+
+ rc = fts5ParseStringFromToken(pToken, &z);
+ if( rc==SQLITE_OK ){
+ sqlite3Fts5Dequote(z);
+ rc = sqlite3Fts5Tokenize(pConfig, z, strlen(z), &sCtx, fts5ParseTokenize);
+ }
+ sqlite3_free(z);
+ if( rc ){
+ pParse->rc = rc;
+ fts5ExprPhraseFree(sCtx.pPhrase);
+ sCtx.pPhrase = 0;
+ }else if( sCtx.pPhrase ){
+
+ if( pAppend==0 ){
+ if( (pParse->nPhrase % 8)==0 ){
+ int nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8);
+ Fts5ExprPhrase **apNew;
+ apNew = (Fts5ExprPhrase**)sqlite3_realloc(pParse->apPhrase, nByte);
+ if( apNew==0 ){
+ pParse->rc = SQLITE_NOMEM;
+ fts5ExprPhraseFree(sCtx.pPhrase);
+ return 0;
+ }
+ pParse->apPhrase = apNew;
+ }
+ pParse->nPhrase++;
+ }
+
+ pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase;
+ assert( sCtx.pPhrase->nTerm>0 );
+ sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = bPrefix;
+ }
+
+ return sCtx.pPhrase;
+}
+
+/*
+** Token pTok has appeared in a MATCH expression where the NEAR operator
+** is expected. If token pTok does not contain "NEAR", store an error
+** in the pParse object.
+*/
+void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token *pTok){
+ if( pParse->rc==SQLITE_OK ){
+ if( pTok->n!=4 || memcmp("NEAR", pTok->p, 4) ){
+ sqlite3Fts5ParseError(
+ pParse, "fts5: syntax error near \"%.*s\"", pTok->n, pTok->p
+ );
+ }
+ }
+}
+
+void sqlite3Fts5ParseSetDistance(
+ Fts5Parse *pParse,
+ Fts5ExprNearset *pNear,
+ Fts5Token *p
+){
+ int nNear = 0;
+ int i;
+ if( p->n ){
+ for(i=0; i<p->n; i++){
+ char c = (char)p->p[i];
+ if( c<'0' || c>'9' ){
+ sqlite3Fts5ParseError(
+ pParse, "expected integer, got \"%.*s\"", p->n, p->p
+ );
+ return;
+ }
+ nNear = nNear * 10 + (p->p[i] - '0');
+ }
+ }else{
+ nNear = FTS5_DEFAULT_NEARDIST;
+ }
+ pNear->nNear = nNear;
+}
+
+void sqlite3Fts5ParseSetColumn(
+ Fts5Parse *pParse,
+ Fts5ExprNearset *pNear,
+ Fts5Token *p
+){
+ char *z = 0;
+ int rc = fts5ParseStringFromToken(p, &z);
+ if( rc==SQLITE_OK ){
+ Fts5Config *pConfig = pParse->pConfig;
+ int i;
+ for(i=0; i<pConfig->nCol; i++){
+ if( 0==sqlite3_stricmp(pConfig->azCol[i], z) ){
+ pNear->iCol = i;
+ break;
+ }
+ }
+ if( i==pConfig->nCol ){
+ sqlite3Fts5ParseError(pParse, "no such column: %s", z);
+ }
+ sqlite3_free(z);
+ }else{
+ pParse->rc = rc;
+ }
+}
+
+/*
+** Allocate and return a new expression object. If anything goes wrong (i.e.
+** OOM error), leave an error code in pParse and return NULL.
+*/
+Fts5ExprNode *sqlite3Fts5ParseNode(
+ Fts5Parse *pParse, /* Parse context */
+ int eType, /* FTS5_STRING, AND, OR or NOT */
+ Fts5ExprNode *pLeft, /* Left hand child expression */
+ Fts5ExprNode *pRight, /* Right hand child expression */
+ Fts5ExprNearset *pNear /* For STRING expressions, the near cluster */
+){
+ Fts5ExprNode *pRet = 0;
+
+ if( pParse->rc==SQLITE_OK ){
+ assert( (eType!=FTS5_STRING && !pNear)
+ || (eType==FTS5_STRING && !pLeft && !pRight)
+ );
+ if( eType==FTS5_STRING && pNear==0 ) return 0;
+ if( eType!=FTS5_STRING && pLeft==0 ) return pRight;
+ if( eType!=FTS5_STRING && pRight==0 ) return pLeft;
+ pRet = (Fts5ExprNode*)sqlite3_malloc(sizeof(Fts5ExprNode));
+ if( pRet==0 ){
+ pParse->rc = SQLITE_NOMEM;
+ }else{
+ memset(pRet, 0, sizeof(*pRet));
+ pRet->eType = eType;
+ pRet->pLeft = pLeft;
+ pRet->pRight = pRight;
+ pRet->pNear = pNear;
+ if( eType==FTS5_STRING ){
+ int iPhrase;
+ for(iPhrase=0; iPhrase<pNear->nPhrase; iPhrase++){
+ pNear->apPhrase[iPhrase]->pNode = pRet;
+ }
+ }
+ }
+ }
+
+ if( pRet==0 ){
+ assert( pParse->rc!=SQLITE_OK );
+ sqlite3Fts5ParseNodeFree(pLeft);
+ sqlite3Fts5ParseNodeFree(pRight);
+ sqlite3Fts5ParseNearsetFree(pNear);
+ }
+ return pRet;
+}
+
+static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){
+ char *zQuoted = sqlite3_malloc(strlen(pTerm->zTerm) * 2 + 3 + 2);
+ if( zQuoted ){
+ int i = 0;
+ char *zIn = pTerm->zTerm;
+ zQuoted[i++] = '"';
+ while( *zIn ){
+ if( *zIn=='"' ) zQuoted[i++] = '"';
+ zQuoted[i++] = *zIn++;
+ }
+ zQuoted[i++] = '"';
+ if( pTerm->bPrefix ){
+ zQuoted[i++] = ' ';
+ zQuoted[i++] = '*';
+ }
+ zQuoted[i++] = '\0';
+ }
+ return zQuoted;
+}
+
+static char *fts5PrintfAppend(char *zApp, const char *zFmt, ...){
+ char *zNew;
+ va_list ap;
+ va_start(ap, zFmt);
+ zNew = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+ if( zApp ){
+ char *zNew2 = sqlite3_mprintf("%s%s", zApp, zNew);
+ sqlite3_free(zNew);
+ zNew = zNew2;
+ }
+ sqlite3_free(zApp);
+ return zNew;
+}
+
+/*
+** Compose a tcl-readable representation of expression pExpr. Return a
+** pointer to a buffer containing that representation. It is the
+** responsibility of the caller to at some point free the buffer using
+** sqlite3_free().
+*/
+static char *fts5ExprPrintTcl(
+ Fts5Config *pConfig,
+ const char *zNearsetCmd,
+ Fts5ExprNode *pExpr
+){
+ char *zRet = 0;
+ if( pExpr->eType==FTS5_STRING ){
+ Fts5ExprNearset *pNear = pExpr->pNear;
+ int i;
+ int iTerm;
+
+ zRet = fts5PrintfAppend(zRet, "[%s ", zNearsetCmd);
+ if( pNear->iCol>=0 ){
+ zRet = fts5PrintfAppend(zRet, "-col %d ", pNear->iCol);
+ if( zRet==0 ) return 0;
+ }
+
+ if( pNear->nPhrase>1 ){
+ zRet = fts5PrintfAppend(zRet, "-near %d ", pNear->nNear);
+ if( zRet==0 ) return 0;
+ }
+
+ zRet = fts5PrintfAppend(zRet, "--");
+ if( zRet==0 ) return 0;
+
+ for(i=0; i<pNear->nPhrase; i++){
+ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
+
+ zRet = fts5PrintfAppend(zRet, " {");
+ for(iTerm=0; zRet && iTerm<pPhrase->nTerm; iTerm++){
+ char *zTerm = pPhrase->aTerm[iTerm].zTerm;
+ zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" ", zTerm);
+ }
+
+ if( zRet ) zRet = fts5PrintfAppend(zRet, "}");
+ if( zRet==0 ) return 0;
+ }
+
+ if( zRet ) zRet = fts5PrintfAppend(zRet, "]");
+ if( zRet==0 ) return 0;
+
+ }else{
+ char *zOp = 0;
+ char *z1 = 0;
+ char *z2 = 0;
+ switch( pExpr->eType ){
+ case FTS5_AND: zOp = "&&"; break;
+ case FTS5_NOT: zOp = "&& !"; break;
+ case FTS5_OR: zOp = "||"; break;
+ default: assert( 0 );
+ }
+
+ z1 = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->pLeft);
+ z2 = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->pRight);
+ if( z1 && z2 ){
+ int b1 = pExpr->pLeft->eType!=FTS5_STRING;
+ int b2 = pExpr->pRight->eType!=FTS5_STRING;
+ zRet = sqlite3_mprintf("%s%s%s %s %s%s%s",
+ b1 ? "(" : "", z1, b1 ? ")" : "",
+ zOp,
+ b2 ? "(" : "", z2, b2 ? ")" : ""
+ );
+ }
+ sqlite3_free(z1);
+ sqlite3_free(z2);
+ }
+
+ return zRet;
+}
+
+static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){
+ char *zRet = 0;
+ if( pExpr->eType==FTS5_STRING ){
+ Fts5ExprNearset *pNear = pExpr->pNear;
+ int i;
+ int iTerm;
+
+ if( pNear->iCol>=0 ){
+ zRet = fts5PrintfAppend(zRet, "%s : ", pConfig->azCol[pNear->iCol]);
+ if( zRet==0 ) return 0;
+ }
+
+ if( pNear->nPhrase>1 ){
+ zRet = fts5PrintfAppend(zRet, "NEAR(");
+ if( zRet==0 ) return 0;
+ }
+
+ for(i=0; i<pNear->nPhrase; i++){
+ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i];
+ if( i!=0 ){
+ zRet = fts5PrintfAppend(zRet, " ");
+ if( zRet==0 ) return 0;
+ }
+ for(iTerm=0; iTerm<pPhrase->nTerm; iTerm++){
+ char *zTerm = fts5ExprTermPrint(&pPhrase->aTerm[iTerm]);
+ if( zTerm ){
+ zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" + ", zTerm);
+ sqlite3_free(zTerm);
+ }
+ if( zTerm==0 || zRet==0 ){
+ sqlite3_free(zRet);
+ return 0;
+ }
+ }
+ }
+
+ if( pNear->nPhrase>1 ){
+ zRet = fts5PrintfAppend(zRet, ", %d)", pNear->nNear);
+ if( zRet==0 ) return 0;
+ }
+
+ }else{
+ char *zOp = 0;
+ char *z1 = 0;
+ char *z2 = 0;
+ switch( pExpr->eType ){
+ case FTS5_AND: zOp = "AND"; break;
+ case FTS5_NOT: zOp = "NOT"; break;
+ case FTS5_OR: zOp = "OR"; break;
+ default: assert( 0 );
+ }
+
+ z1 = fts5ExprPrint(pConfig, pExpr->pLeft);
+ z2 = fts5ExprPrint(pConfig, pExpr->pRight);
+ if( z1 && z2 ){
+ int b1 = pExpr->pLeft->eType!=FTS5_STRING;
+ int b2 = pExpr->pRight->eType!=FTS5_STRING;
+ zRet = sqlite3_mprintf("%s%s%s %s %s%s%s",
+ b1 ? "(" : "", z1, b1 ? ")" : "",
+ zOp,
+ b2 ? "(" : "", z2, b2 ? ")" : ""
+ );
+ }
+ sqlite3_free(z1);
+ sqlite3_free(z2);
+ }
+
+ return zRet;
+}
+
+/*
+** The implementation of user-defined scalar functions fts5_expr() (bTcl==0)
+** and fts5_expr_tcl() (bTcl!=0).
+*/
+static void fts5ExprFunction(
+ sqlite3_context *pCtx, /* Function call context */
+ int nArg, /* Number of args */
+ sqlite3_value **apVal, /* Function arguments */
+ int bTcl
+){
+ Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx);
+ sqlite3 *db = sqlite3_context_db_handle(pCtx);
+ const char *zExpr = 0;
+ char *zErr = 0;
+ Fts5Expr *pExpr = 0;
+ int rc;
+ int i;
+
+ const char **azConfig; /* Array of arguments for Fts5Config */
+ const char *zNearsetCmd = "nearset";
+ int nConfig; /* Size of azConfig[] */
+ Fts5Config *pConfig = 0;
+
+ if( bTcl && nArg>1 ){
+ zNearsetCmd = (const char*)sqlite3_value_text(apVal[1]);
+ }
+
+ nConfig = nArg + 2 - bTcl;
+ azConfig = (const char**)sqlite3_malloc(sizeof(char*) * nConfig);
+ if( azConfig==0 ){
+ sqlite3_result_error_nomem(pCtx);
+ return;
+ }
+ azConfig[0] = 0;
+ azConfig[1] = "main";
+ azConfig[2] = "tbl";
+ for(i=1+bTcl; i<nArg; i++){
+ azConfig[i+2-bTcl] = (const char*)sqlite3_value_text(apVal[i]);
+ }
+ zExpr = (const char*)sqlite3_value_text(apVal[0]);
+
+ rc = sqlite3Fts5ConfigParse(pGlobal, db, nConfig, azConfig, &pConfig, &zErr);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5ExprNew(pConfig, zExpr, &pExpr, &zErr);
+ }
+ if( rc==SQLITE_OK ){
+ char *zText;
+ if( pExpr->pRoot==0 ){
+ zText = sqlite3_mprintf("");
+ }else if( bTcl ){
+ zText = fts5ExprPrintTcl(pConfig, zNearsetCmd, pExpr->pRoot);
+ }else{
+ zText = fts5ExprPrint(pConfig, pExpr->pRoot);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_result_text(pCtx, zText, -1, SQLITE_TRANSIENT);
+ sqlite3_free(zText);
+ }
+ }
+
+ if( rc!=SQLITE_OK ){
+ if( zErr ){
+ sqlite3_result_error(pCtx, zErr, -1);
+ sqlite3_free(zErr);
+ }else{
+ sqlite3_result_error_code(pCtx, rc);
+ }
+ }
+ sqlite3_free(azConfig);
+ sqlite3Fts5ConfigFree(pConfig);
+ sqlite3Fts5ExprFree(pExpr);
+}
+
+static void fts5ExprFunctionHr(
+ sqlite3_context *pCtx, /* Function call context */
+ int nArg, /* Number of args */
+ sqlite3_value **apVal /* Function arguments */
+){
+ fts5ExprFunction(pCtx, nArg, apVal, 0);
+}
+static void fts5ExprFunctionTcl(
+ sqlite3_context *pCtx, /* Function call context */
+ int nArg, /* Number of args */
+ sqlite3_value **apVal /* Function arguments */
+){
+ fts5ExprFunction(pCtx, nArg, apVal, 1);
+}
+
+/*
+** This is called during initialization to register the fts5_expr() scalar
+** UDF with the SQLite handle passed as the only argument.
+*/
+int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){
+ struct Fts5ExprFunc {
+ const char *z;
+ void (*x)(sqlite3_context*,int,sqlite3_value**);
+ } aFunc[] = {
+ { "fts5_expr", fts5ExprFunctionHr },
+ { "fts5_expr_tcl", fts5ExprFunctionTcl },
+ };
+ int i;
+ int rc = SQLITE_OK;
+ void *pCtx = (void*)pGlobal;
+
+ for(i=0; rc==SQLITE_OK && i<(sizeof(aFunc) / sizeof(aFunc[0])); i++){
+ struct Fts5ExprFunc *p = &aFunc[i];
+ rc = sqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0);
+ }
+
+ return rc;
+}
+
+/*
+** Return the number of phrases in expression pExpr.
+*/
+int sqlite3Fts5ExprPhraseCount(Fts5Expr *pExpr){
+ return pExpr->nPhrase;
+}
+
+/*
+** Return the number of terms in the iPhrase'th phrase in pExpr.
+*/
+int sqlite3Fts5ExprPhraseSize(Fts5Expr *pExpr, int iPhrase){
+ if( iPhrase<0 || iPhrase>=pExpr->nPhrase ) return 0;
+ return pExpr->apExprPhrase[iPhrase]->nTerm;
+}
+
+/*
+** This function is used to access the current position list for phrase
+** iPhrase.
+*/
+int sqlite3Fts5ExprPoslist(Fts5Expr *pExpr, int iPhrase, const u8 **pa){
+ if( iPhrase>=0 && iPhrase<pExpr->nPhrase ){
+ Fts5ExprPhrase *pPhrase = pExpr->apExprPhrase[iPhrase];
+ Fts5ExprNode *pNode = pPhrase->pNode;
+ if( pNode->bEof==0 && pNode->iRowid==pExpr->pRoot->iRowid ){
+ *pa = pPhrase->poslist.p;
+ return pPhrase->poslist.n;
+ }
+ }
+ *pa = 0;
+ return 0;
+}
+
+#endif /* SQLITE_ENABLE_FTS5 */
diff --git a/ext/fts5/fts5_hash.c b/ext/fts5/fts5_hash.c
new file mode 100644
index 000000000..fa7701a6d
--- /dev/null
+++ b/ext/fts5/fts5_hash.c
@@ -0,0 +1,448 @@
+/*
+** 2014 August 11
+**
+** 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.
+**
+******************************************************************************
+**
+*/
+
+#ifdef SQLITE_ENABLE_FTS5
+
+
+#include "fts5Int.h"
+
+typedef struct Fts5HashEntry Fts5HashEntry;
+
+/*
+** This file contains the implementation of an in-memory hash table used
+** to accumuluate "term -> doclist" content before it is flused to a level-0
+** segment.
+*/
+
+
+struct Fts5Hash {
+ int *pnByte; /* Pointer to bytes counter */
+ int nEntry; /* Number of entries currently in hash */
+ int nSlot; /* Size of aSlot[] array */
+ Fts5HashEntry *pScan; /* Current ordered scan item */
+ Fts5HashEntry **aSlot; /* Array of hash slots */
+};
+
+/*
+** Each entry in the hash table is represented by an object of the
+** following type. Each object, its key (zKey[]) and its current data
+** are stored in a single memory allocation. The position list data
+** immediately follows the key data in memory.
+**
+** The data that follows the key is in a similar, but not identical format
+** to the doclist data stored in the database. It is:
+**
+** * Rowid, as a varint
+** * Position list, without 0x00 terminator.
+** * Size of previous position list and rowid, as a 4 byte
+** big-endian integer.
+**
+** iRowidOff:
+** Offset of last rowid written to data area. Relative to first byte of
+** structure.
+**
+** nData:
+** Bytes of data written since iRowidOff.
+*/
+struct Fts5HashEntry {
+ Fts5HashEntry *pHashNext; /* Next hash entry with same hash-key */
+ Fts5HashEntry *pScanNext; /* Next entry in sorted order */
+
+ int nAlloc; /* Total size of allocation */
+ int iSzPoslist; /* Offset of space for 4-byte poslist size */
+ int nData; /* Total bytes of data (incl. structure) */
+ u8 bDel; /* Set delete-flag @ iSzPoslist */
+
+ int iCol; /* Column of last value written */
+ int iPos; /* Position of last value written */
+ i64 iRowid; /* Rowid of last value written */
+ char zKey[0]; /* Nul-terminated entry key */
+};
+
+/*
+** Allocate a new hash table.
+*/
+int sqlite3Fts5HashNew(Fts5Hash **ppNew, int *pnByte){
+ int rc = SQLITE_OK;
+ Fts5Hash *pNew;
+
+ *ppNew = pNew = (Fts5Hash*)sqlite3_malloc(sizeof(Fts5Hash));
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int nByte;
+ memset(pNew, 0, sizeof(Fts5Hash));
+ pNew->pnByte = pnByte;
+
+ pNew->nSlot = 1024;
+ nByte = sizeof(Fts5HashEntry*) * pNew->nSlot;
+ pNew->aSlot = (Fts5HashEntry**)sqlite3_malloc(nByte);
+ if( pNew->aSlot==0 ){
+ sqlite3_free(pNew);
+ *ppNew = 0;
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pNew->aSlot, 0, nByte);
+ }
+ }
+ return rc;
+}
+
+/*
+** Free a hash table object.
+*/
+void sqlite3Fts5HashFree(Fts5Hash *pHash){
+ if( pHash ){
+ sqlite3Fts5HashClear(pHash);
+ sqlite3_free(pHash->aSlot);
+ sqlite3_free(pHash);
+ }
+}
+
+/*
+** Empty (but do not delete) a hash table.
+*/
+void sqlite3Fts5HashClear(Fts5Hash *pHash){
+ int i;
+ for(i=0; i<pHash->nSlot; i++){
+ Fts5HashEntry *pNext;
+ Fts5HashEntry *pSlot;
+ for(pSlot=pHash->aSlot[i]; pSlot; pSlot=pNext){
+ pNext = pSlot->pHashNext;
+ sqlite3_free(pSlot);
+ }
+ }
+ memset(pHash->aSlot, 0, pHash->nSlot * sizeof(Fts5HashEntry*));
+ pHash->nEntry = 0;
+}
+
+static unsigned int fts5HashKey(int nSlot, const char *p, int n){
+ int i;
+ unsigned int h = 13;
+ for(i=n-1; i>=0; i--){
+ h = (h << 3) ^ h ^ p[i];
+ }
+ return (h % nSlot);
+}
+
+/*
+** Resize the hash table by doubling the number of slots.
+*/
+static int fts5HashResize(Fts5Hash *pHash){
+ int nNew = pHash->nSlot*2;
+ int i;
+ Fts5HashEntry **apNew;
+ Fts5HashEntry **apOld = pHash->aSlot;
+
+ apNew = (Fts5HashEntry**)sqlite3_malloc(nNew*sizeof(Fts5HashEntry*));
+ if( !apNew ) return SQLITE_NOMEM;
+ memset(apNew, 0, nNew*sizeof(Fts5HashEntry*));
+
+ for(i=0; i<pHash->nSlot; i++){
+ while( apOld[i] ){
+ int iHash;
+ Fts5HashEntry *p = apOld[i];
+ apOld[i] = p->pHashNext;
+ iHash = fts5HashKey(nNew, p->zKey, strlen(p->zKey));
+ p->pHashNext = apNew[iHash];
+ apNew[iHash] = p;
+ }
+ }
+
+ sqlite3_free(apOld);
+ pHash->nSlot = nNew;
+ pHash->aSlot = apNew;
+ return SQLITE_OK;
+}
+
+static void fts5HashAddPoslistSize(Fts5HashEntry *p){
+ if( p->iSzPoslist ){
+ u8 *pPtr = (u8*)p;
+ int nSz = (p->nData - p->iSzPoslist - 1); /* Size in bytes */
+ int nPos = nSz*2 + p->bDel; /* Value of nPos field */
+
+ assert( p->bDel==0 || p->bDel==1 );
+ if( nPos<=127 ){
+ pPtr[p->iSzPoslist] = nPos;
+ }else{
+ int nByte = sqlite3Fts5GetVarintLen((u32)nPos);
+ memmove(&pPtr[p->iSzPoslist + nByte], &pPtr[p->iSzPoslist + 1], nSz);
+ sqlite3PutVarint(&pPtr[p->iSzPoslist], nPos);
+ p->nData += (nByte-1);
+ }
+ p->bDel = 0;
+ p->iSzPoslist = 0;
+ }
+}
+
+int sqlite3Fts5HashWrite(
+ Fts5Hash *pHash,
+ i64 iRowid, /* Rowid for this entry */
+ int iCol, /* Column token appears in (-ve -> delete) */
+ int iPos, /* Position of token within column */
+ const char *pToken, int nToken /* Token to add or remove to or from index */
+){
+ unsigned int iHash = fts5HashKey(pHash->nSlot, pToken, nToken);
+ Fts5HashEntry *p;
+ u8 *pPtr;
+ int nIncr = 0; /* Amount to increment (*pHash->pnByte) by */
+
+ /* Attempt to locate an existing hash entry */
+ for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
+ if( memcmp(p->zKey, pToken, nToken)==0 && p->zKey[nToken]==0 ) break;
+ }
+
+ /* If an existing hash entry cannot be found, create a new one. */
+ if( p==0 ){
+ int nByte = sizeof(Fts5HashEntry) + nToken + 1 + 64;
+ if( nByte<128 ) nByte = 128;
+
+ if( (pHash->nEntry*2)>=pHash->nSlot ){
+ int rc = fts5HashResize(pHash);
+ if( rc!=SQLITE_OK ) return rc;
+ iHash = fts5HashKey(pHash->nSlot, pToken, nToken);
+ }
+
+ p = (Fts5HashEntry*)sqlite3_malloc(nByte);
+ if( !p ) return SQLITE_NOMEM;
+ memset(p, 0, sizeof(Fts5HashEntry));
+ p->nAlloc = nByte;
+ memcpy(p->zKey, pToken, nToken);
+ p->zKey[nToken] = '\0';
+ p->nData = nToken + 1 + sizeof(Fts5HashEntry);
+ p->nData += sqlite3PutVarint(&((u8*)p)[p->nData], iRowid);
+ p->iSzPoslist = p->nData;
+ p->nData += 1;
+ p->iRowid = iRowid;
+ p->pHashNext = pHash->aSlot[iHash];
+ pHash->aSlot[iHash] = p;
+ pHash->nEntry++;
+ nIncr += p->nData;
+ }
+
+ /* Check there is enough space to append a new entry. Worst case scenario
+ ** is:
+ **
+ ** + 9 bytes for a new rowid,
+ ** + 4 byte reserved for the "poslist size" varint.
+ ** + 1 byte for a "new column" byte,
+ ** + 3 bytes for a new column number (16-bit max) as a varint,
+ ** + 5 bytes for the new position offset (32-bit max).
+ */
+ if( (p->nAlloc - p->nData) < (9 + 4 + 1 + 3 + 5) ){
+ int nNew = p->nAlloc * 2;
+ Fts5HashEntry *pNew;
+ Fts5HashEntry **pp;
+ pNew = (Fts5HashEntry*)sqlite3_realloc(p, nNew);
+ if( pNew==0 ) return SQLITE_NOMEM;
+ pNew->nAlloc = nNew;
+ for(pp=&pHash->aSlot[iHash]; *pp!=p; pp=&(*pp)->pHashNext);
+ *pp = pNew;
+ p = pNew;
+ }
+ pPtr = (u8*)p;
+ nIncr -= p->nData;
+
+ /* If this is a new rowid, append the 4-byte size field for the previous
+ ** entry, and the new rowid for this entry. */
+ if( iRowid!=p->iRowid ){
+ fts5HashAddPoslistSize(p);
+ p->nData += sqlite3PutVarint(&pPtr[p->nData], iRowid - p->iRowid);
+ p->iSzPoslist = p->nData;
+ p->nData += 1;
+ p->iCol = 0;
+ p->iPos = 0;
+ p->iRowid = iRowid;
+ }
+
+ if( iCol>=0 ){
+ /* Append a new column value, if necessary */
+ assert( iCol>=p->iCol );
+ if( iCol!=p->iCol ){
+ pPtr[p->nData++] = 0x01;
+ p->nData += sqlite3PutVarint(&pPtr[p->nData], iCol);
+ p->iCol = iCol;
+ p->iPos = 0;
+ }
+
+ /* Append the new position offset */
+ p->nData += sqlite3PutVarint(&pPtr[p->nData], iPos - p->iPos + 2);
+ p->iPos = iPos;
+ }else{
+ /* This is a delete. Set the delete flag. */
+ p->bDel = 1;
+ }
+ nIncr += p->nData;
+
+ *pHash->pnByte += nIncr;
+ return SQLITE_OK;
+}
+
+
+/*
+** Arguments pLeft and pRight point to linked-lists of hash-entry objects,
+** each sorted in key order. This function merges the two lists into a
+** single list and returns a pointer to its first element.
+*/
+static Fts5HashEntry *fts5HashEntryMerge(
+ Fts5HashEntry *pLeft,
+ Fts5HashEntry *pRight
+){
+ Fts5HashEntry *p1 = pLeft;
+ Fts5HashEntry *p2 = pRight;
+ Fts5HashEntry *pRet = 0;
+ Fts5HashEntry **ppOut = &pRet;
+
+ while( p1 || p2 ){
+ if( p1==0 ){
+ *ppOut = p2;
+ p2 = 0;
+ }else if( p2==0 ){
+ *ppOut = p1;
+ p1 = 0;
+ }else{
+ int i = 0;
+ while( p1->zKey[i]==p2->zKey[i] ) i++;
+
+ if( ((u8)p1->zKey[i])>((u8)p2->zKey[i]) ){
+ /* p2 is smaller */
+ *ppOut = p2;
+ ppOut = &p2->pScanNext;
+ p2 = p2->pScanNext;
+ }else{
+ /* p1 is smaller */
+ *ppOut = p1;
+ ppOut = &p1->pScanNext;
+ p1 = p1->pScanNext;
+ }
+ *ppOut = 0;
+ }
+ }
+
+ return pRet;
+}
+
+/*
+** Extract all tokens from hash table iHash and link them into a list
+** in sorted order. The hash table is cleared before returning. It is
+** the responsibility of the caller to free the elements of the returned
+** list.
+*/
+static int fts5HashEntrySort(
+ Fts5Hash *pHash,
+ const char *pTerm, int nTerm, /* Query prefix, if any */
+ Fts5HashEntry **ppSorted
+){
+ const int nMergeSlot = 32;
+ Fts5HashEntry **ap;
+ Fts5HashEntry *pList;
+ int iSlot;
+ int i;
+
+ *ppSorted = 0;
+ ap = sqlite3_malloc(sizeof(Fts5HashEntry*) * nMergeSlot);
+ if( !ap ) return SQLITE_NOMEM;
+ memset(ap, 0, sizeof(Fts5HashEntry*) * nMergeSlot);
+
+ for(iSlot=0; iSlot<pHash->nSlot; iSlot++){
+ Fts5HashEntry *pIter;
+ for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){
+ if( pTerm==0 || 0==memcmp(pIter->zKey, pTerm, nTerm) ){
+ Fts5HashEntry *pEntry = pIter;
+ pEntry->pScanNext = 0;
+ for(i=0; ap[i]; i++){
+ pEntry = fts5HashEntryMerge(pEntry, ap[i]);
+ ap[i] = 0;
+ }
+ ap[i] = pEntry;
+ }
+ }
+ }
+
+ pList = 0;
+ for(i=0; i<nMergeSlot; i++){
+ pList = fts5HashEntryMerge(pList, ap[i]);
+ }
+
+ pHash->nEntry = 0;
+ sqlite3_free(ap);
+ *ppSorted = pList;
+ return SQLITE_OK;
+}
+
+/*
+** Query the hash table for a doclist associated with term pTerm/nTerm.
+*/
+int sqlite3Fts5HashQuery(
+ Fts5Hash *pHash, /* Hash table to query */
+ const char *pTerm, int nTerm, /* Query term */
+ const u8 **ppDoclist, /* OUT: Pointer to doclist for pTerm */
+ int *pnDoclist /* OUT: Size of doclist in bytes */
+){
+ unsigned int iHash = fts5HashKey(pHash->nSlot, pTerm, nTerm);
+ Fts5HashEntry *p;
+
+ for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
+ if( memcmp(p->zKey, pTerm, nTerm)==0 && p->zKey[nTerm]==0 ) break;
+ }
+
+ if( p ){
+ fts5HashAddPoslistSize(p);
+ *ppDoclist = (const u8*)&p->zKey[nTerm+1];
+ *pnDoclist = p->nData - (sizeof(*p) + nTerm + 1);
+ }else{
+ *ppDoclist = 0;
+ *pnDoclist = 0;
+ }
+
+ return SQLITE_OK;
+}
+
+int sqlite3Fts5HashScanInit(
+ Fts5Hash *p, /* Hash table to query */
+ const char *pTerm, int nTerm /* Query prefix */
+){
+ return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan);
+}
+
+void sqlite3Fts5HashScanNext(Fts5Hash *p){
+ Fts5HashEntry *pScan = p->pScan;
+ if( pScan ) p->pScan = pScan->pScanNext;
+}
+
+int sqlite3Fts5HashScanEof(Fts5Hash *p){
+ return (p->pScan==0);
+}
+
+void sqlite3Fts5HashScanEntry(
+ Fts5Hash *pHash,
+ const char **pzTerm, /* OUT: term (nul-terminated) */
+ const u8 **ppDoclist, /* OUT: pointer to doclist */
+ int *pnDoclist /* OUT: size of doclist in bytes */
+){
+ Fts5HashEntry *p;
+ if( (p = pHash->pScan) ){
+ int nTerm = strlen(p->zKey);
+ fts5HashAddPoslistSize(p);
+ *pzTerm = p->zKey;
+ *ppDoclist = (const u8*)&p->zKey[nTerm+1];
+ *pnDoclist = p->nData - (sizeof(*p) + nTerm + 1);
+ }else{
+ *pzTerm = 0;
+ *ppDoclist = 0;
+ *pnDoclist = 0;
+ }
+}
+
+#endif /* SQLITE_ENABLE_FTS5 */
diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c
new file mode 100644
index 000000000..05c8d6831
--- /dev/null
+++ b/ext/fts5/fts5_index.c
@@ -0,0 +1,5337 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+**
+** Low level access to the FTS index stored in the database file. The
+** routines in this file file implement all read and write access to the
+** %_data table. Other parts of the system access this functionality via
+** the interface defined in fts5Int.h.
+*/
+
+#ifdef SQLITE_ENABLE_FTS5
+
+#include "fts5Int.h"
+
+/*
+** Overview:
+**
+** The %_data table contains all the FTS indexes for an FTS5 virtual table.
+** As well as the main term index, there may be up to 31 prefix indexes.
+** The format is similar to FTS3/4, except that:
+**
+** * all segment b-tree leaf data is stored in fixed size page records
+** (e.g. 1000 bytes). A single doclist may span multiple pages. Care is
+** taken to ensure it is possible to iterate in either direction through
+** the entries in a doclist, or to seek to a specific entry within a
+** doclist, without loading it into memory.
+**
+** * large doclists that span many pages have associated "doclist index"
+** records that contain a copy of the first docid on each page spanned by
+** the doclist. This is used to speed up seek operations, and merges of
+** large doclists with very small doclists.
+**
+** * extra fields in the "structure record" record the state of ongoing
+** incremental merge operations.
+**
+*/
+
+
+#define FTS5_OPT_WORK_UNIT 1000 /* Number of leaf pages per optimize step */
+#define FTS5_WORK_UNIT 64 /* Number of leaf pages in unit of work */
+
+#define FTS5_MIN_DLIDX_SIZE 4 /* Add dlidx if this many empty pages */
+
+/*
+** Details:
+**
+** The %_data table managed by this module,
+**
+** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB);
+**
+** , contains the following 5 types of records. See the comments surrounding
+** the FTS5_*_ROWID macros below for a description of how %_data rowids are
+** assigned to each fo them.
+**
+** 1. Structure Records:
+**
+** The set of segments that make up an index - the index structure - are
+** recorded in a single record within the %_data table. The record consists
+** of a single 32-bit configuration cookie value followed by a list of
+** SQLite varints. If the FTS table features more than one index (because
+** there are one or more prefix indexes), it is guaranteed that all share
+** the same cookie value.
+**
+** Immediately following the configuration cookie, the record begins with
+** three varints:
+**
+** + number of levels,
+** + total number of segments on all levels,
+** + value of write counter.
+**
+** Then, for each level from 0 to nMax:
+**
+** + number of input segments in ongoing merge.
+** + total number of segments in level.
+** + for each segment from oldest to newest:
+** + segment id (always > 0)
+** + b-tree height (1 -> root is leaf, 2 -> root is parent of leaf etc.)
+** + first leaf page number (often 1, always greater than 0)
+** + final leaf page number
+**
+** 2. The Averages Record:
+**
+** A single record within the %_data table. The data is a list of varints.
+** The first value is the number of rows in the index. Then, for each column
+** from left to right, the total number of tokens in the column for all
+** rows of the table.
+**
+** 3. Segment leaves:
+**
+** TERM DOCLIST FORMAT:
+**
+** Most of each segment leaf is taken up by term/doclist data. The
+** general format of the term/doclist data is:
+**
+** varint : size of first term
+** blob: first term data
+** doclist: first doclist
+** zero-or-more {
+** varint: number of bytes in common with previous term
+** varint: number of bytes of new term data (nNew)
+** blob: nNew bytes of new term data
+** doclist: next doclist
+** }
+**
+** doclist format:
+**
+** varint: first rowid
+** poslist: first poslist
+** zero-or-more {
+** varint: rowid delta (always > 0)
+** poslist: next poslist
+** }
+** 0x00 byte
+**
+** poslist format:
+**
+** varint: size of poslist in bytes multiplied by 2, not including
+** this field. Plus 1 if this entry carries the "delete" flag.
+** collist: collist for column 0
+** zero-or-more {
+** 0x01 byte
+** varint: column number (I)
+** collist: collist for column I
+** }
+**
+** collist format:
+**
+** varint: first offset + 2
+** zero-or-more {
+** varint: offset delta + 2
+** }
+**
+** PAGINATION
+**
+** The format described above is only accurate if the entire term/doclist
+** data fits on a single leaf page. If this is not the case, the format
+** is changed in two ways:
+**
+** + if the first rowid on a page occurs before the first term, it
+** is stored as a literal value:
+**
+** varint: first rowid
+**
+** + the first term on each page is stored in the same way as the
+** very first term of the segment:
+**
+** varint : size of first term
+** blob: first term data
+**
+** Each leaf page begins with:
+**
+** + 2-byte unsigned containing offset to first rowid (or 0).
+** + 2-byte unsigned containing offset to first term (or 0).
+**
+** Followed by term/doclist data.
+**
+** 4. Segment interior nodes:
+**
+** The interior nodes turn the list of leaves into a b+tree.
+**
+** Each interior node begins with a varint - the page number of the left
+** most child node. Following this, for each leaf page except the first,
+** the interior nodes contain:
+**
+** a) If the leaf page contains at least one term, then a term-prefix that
+** is greater than all previous terms, and less than or equal to the
+** first term on the leaf page.
+**
+** b) If the leaf page no terms, a record indicating how many consecutive
+** leaves contain no terms, and whether or not there is an associated
+** by-rowid index record.
+**
+** By definition, there is never more than one type (b) record in a row.
+** Type (b) records only ever appear on height=1 pages - immediate parents
+** of leaves. Only type (a) records are pushed to higher levels.
+**
+** Term format:
+**
+** * Number of bytes in common with previous term plus 2, as a varint.
+** * Number of bytes of new term data, as a varint.
+** * new term data.
+**
+** No-term format:
+**
+** * either an 0x00 or 0x01 byte. If the value 0x01 is used, then there
+** is an associated index-by-rowid record.
+** * the number of zero-term leaves as a varint.
+**
+** 5. Segment doclist indexes:
+**
+** A list of varints. If the first termless page contains at least one
+** docid, the list begins with that docid as a varint followed by the
+** value 1 (0x01). Or, if the first termless page contains no docids,
+** a varint containing the last docid stored on the term page followed
+** by a 0 (0x00) value.
+**
+** For each subsequent page in the doclist, either a 0x00 byte if the
+** page contains no terms, or a delta-encoded docid (always +ve)
+** representing the first docid on the page otherwise.
+*/
+
+/*
+** Rowids for the averages and structure records in the %_data table.
+*/
+#define FTS5_AVERAGES_ROWID 1 /* Rowid used for the averages record */
+#define FTS5_STRUCTURE_ROWID(iIdx) (10 + (iIdx)) /* For structure records */
+
+/*
+** Macros determining the rowids used by segment nodes. All nodes in all
+** segments for all indexes (the regular FTS index and any prefix indexes)
+** are stored in the %_data table with large positive rowids.
+**
+** The %_data table may contain up to (1<<FTS5_SEGMENT_INDEX_BITS)
+** indexes - one regular term index and zero or more prefix indexes.
+**
+** Each segment in an index has a unique id greater than zero.
+**
+** Each node in a segment b-tree is assigned a "page number" that is unique
+** within nodes of its height within the segment (leaf nodes have a height
+** of 0, parents 1, etc.). Page numbers are allocated sequentially so that
+** a nodes page number is always one more than its left sibling.
+**
+** The rowid for a node is then found using the FTS5_SEGMENT_ROWID() macro
+** below. The FTS5_SEGMENT_*_BITS macros define the number of bits used
+** to encode the three FTS5_SEGMENT_ROWID() arguments. This module returns
+** SQLITE_FULL and fails the current operation if they ever prove too small.
+*/
+#define FTS5_DATA_IDX_B 5 /* Max of 31 prefix indexes */
+#define FTS5_DATA_ID_B 16 /* Max seg id number 65535 */
+#define FTS5_DATA_HEIGHT_B 5 /* Max b-tree height of 32 */
+#define FTS5_DATA_PAGE_B 31 /* Max page number of 2147483648 */
+
+#define FTS5_SEGMENT_ROWID(idx, segid, height, pgno) ( \
+ ((i64)(idx) << (FTS5_DATA_ID_B + FTS5_DATA_PAGE_B + FTS5_DATA_HEIGHT_B)) + \
+ ((i64)(segid) << (FTS5_DATA_PAGE_B + FTS5_DATA_HEIGHT_B)) + \
+ ((i64)(height) << (FTS5_DATA_PAGE_B)) + \
+ ((i64)(pgno)) \
+)
+
+#if FTS5_MAX_PREFIX_INDEXES > ((1<<FTS5_DATA_IDX_B)-1)
+# error "FTS5_MAX_PREFIX_INDEXES is too large"
+#endif
+
+/*
+** The height of segment b-trees is actually limited to one less than
+** (1<<HEIGHT_BITS). This is because the rowid address space for nodes
+** with such a height is used by doclist indexes.
+*/
+#define FTS5_SEGMENT_MAX_HEIGHT ((1 << FTS5_DATA_HEIGHT_B)-1)
+
+/*
+** The rowid for the doclist index associated with leaf page pgno of segment
+** segid in index idx.
+*/
+#define FTS5_DOCLIST_IDX_ROWID(idx, segid, pgno) \
+ FTS5_SEGMENT_ROWID(idx, segid, FTS5_SEGMENT_MAX_HEIGHT, pgno)
+
+#ifdef SQLITE_DEBUG
+int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; }
+#endif
+
+
+/*
+** Each time a blob is read from the %_data table, it is padded with this
+** many zero bytes. This makes it easier to decode the various record formats
+** without overreading if the records are corrupt.
+*/
+#define FTS5_DATA_ZERO_PADDING 8
+
+typedef struct Fts5BtreeIter Fts5BtreeIter;
+typedef struct Fts5BtreeIterLevel Fts5BtreeIterLevel;
+typedef struct Fts5ChunkIter Fts5ChunkIter;
+typedef struct Fts5Data Fts5Data;
+typedef struct Fts5DlidxIter Fts5DlidxIter;
+typedef struct Fts5MultiSegIter Fts5MultiSegIter;
+typedef struct Fts5NodeIter Fts5NodeIter;
+typedef struct Fts5PageWriter Fts5PageWriter;
+typedef struct Fts5PosIter Fts5PosIter;
+typedef struct Fts5SegIter Fts5SegIter;
+typedef struct Fts5DoclistIter Fts5DoclistIter;
+typedef struct Fts5SegWriter Fts5SegWriter;
+typedef struct Fts5Structure Fts5Structure;
+typedef struct Fts5StructureLevel Fts5StructureLevel;
+typedef struct Fts5StructureSegment Fts5StructureSegment;
+
+struct Fts5Data {
+ u8 *p; /* Pointer to buffer containing record */
+ int n; /* Size of record in bytes */
+ int nRef; /* Ref count */
+};
+
+/*
+** One object per %_data table.
+*/
+struct Fts5Index {
+ Fts5Config *pConfig; /* Virtual table configuration */
+ char *zDataTbl; /* Name of %_data table */
+ int nWorkUnit; /* Leaf pages in a "unit" of work */
+
+ /*
+ ** Variables related to the accumulation of tokens and doclists within the
+ ** in-memory hash tables before they are flushed to disk.
+ */
+ Fts5Hash **apHash; /* Array of hash tables */
+ int nMaxPendingData; /* Max pending data before flush to disk */
+ int nPendingData; /* Current bytes of pending data */
+ i64 iWriteRowid; /* Rowid for current doc being written */
+
+ /* Error state. */
+ int rc; /* Current error code */
+
+ /* State used by the fts5DataXXX() functions. */
+ sqlite3_blob *pReader; /* RO incr-blob open on %_data table */
+ sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */
+ sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */
+ int nRead; /* Total number of blocks read */
+};
+
+struct Fts5DoclistIter {
+ int bDesc; /* True for DESC order, false for ASC */
+ u8 *a;
+ int n;
+ int i;
+
+ /* Output variables. aPoslist==0 at EOF */
+ i64 iRowid;
+ u8 *aPoslist;
+ int nPoslist;
+};
+
+/*
+** Each iterator used by external modules is an instance of this type.
+*/
+struct Fts5IndexIter {
+ Fts5Index *pIndex;
+ Fts5Structure *pStruct;
+ Fts5MultiSegIter *pMulti;
+ Fts5DoclistIter *pDoclist;
+ Fts5Buffer poslist; /* Buffer containing current poslist */
+};
+
+/*
+** The contents of the "structure" record for each index are represented
+** using an Fts5Structure record in memory. Which uses instances of the
+** other Fts5StructureXXX types as components.
+*/
+struct Fts5StructureSegment {
+ int iSegid; /* Segment id */
+ int nHeight; /* Height of segment b-tree */
+ int pgnoFirst; /* First leaf page number in segment */
+ int pgnoLast; /* Last leaf page number in segment */
+};
+struct Fts5StructureLevel {
+ int nMerge; /* Number of segments in incr-merge */
+ int nSeg; /* Total number of segments on level */
+ Fts5StructureSegment *aSeg; /* Array of segments. aSeg[0] is oldest. */
+};
+struct Fts5Structure {
+ u64 nWriteCounter; /* Total leaves written to level 0 */
+ int nLevel; /* Number of levels in this index */
+ Fts5StructureLevel aLevel[0]; /* Array of nLevel level objects */
+};
+
+/*
+** An object of type Fts5SegWriter is used to write to segments.
+*/
+struct Fts5PageWriter {
+ int pgno; /* Page number for this page */
+ Fts5Buffer buf; /* Buffer containing page data */
+ Fts5Buffer term; /* Buffer containing previous term on page */
+};
+struct Fts5SegWriter {
+ int iIdx; /* Index to write to */
+ int iSegid; /* Segid to write to */
+ int nWriter; /* Number of entries in aWriter */
+ Fts5PageWriter *aWriter; /* Array of PageWriter objects */
+ i64 iPrevRowid; /* Previous docid written to current leaf */
+ u8 bFirstRowidInDoclist; /* True if next rowid is first in doclist */
+ u8 bFirstRowidInPage; /* True if next rowid is first in page */
+ u8 bFirstTermInPage; /* True if next term will be first in leaf */
+ int nLeafWritten; /* Number of leaf pages written */
+ int nEmpty; /* Number of contiguous term-less nodes */
+ Fts5Buffer cdlidx; /* Doclist index */
+ i64 iDlidxPrev; /* Previous rowid appended to dlidx */
+ int bDlidxPrevValid; /* True if iDlidxPrev is valid */
+};
+
+/*
+** Object for iterating through the merged results of one or more segments,
+** visiting each term/docid pair in the merged data.
+**
+** nSeg is always a power of two greater than or equal to the number of
+** segments that this object is merging data from. Both the aSeg[] and
+** aFirst[] arrays are sized at nSeg entries. The aSeg[] array is padded
+** with zeroed objects - these are handled as if they were iterators opened
+** on empty segments.
+**
+** The results of comparing segments aSeg[N] and aSeg[N+1], where N is an
+** even number, is stored in aFirst[(nSeg+N)/2]. The "result" of the
+** comparison in this context is the index of the iterator that currently
+** points to the smaller term/rowid combination. Iterators at EOF are
+** considered to be greater than all other iterators.
+**
+** aFirst[1] contains the index in aSeg[] of the iterator that points to
+** the smallest key overall. aFirst[0] is unused.
+*/
+
+typedef struct Fts5CResult Fts5CResult;
+struct Fts5CResult {
+ u16 iFirst; /* aSeg[] index of firstest iterator */
+ u8 bTermEq; /* True if the terms are equal */
+};
+
+struct Fts5MultiSegIter {
+ int nSeg; /* Size of aSeg[] array */
+ int bRev; /* True to iterate in reverse order */
+ int bSkipEmpty; /* True to skip deleted entries */
+ Fts5SegIter *aSeg; /* Array of segment iterators */
+ Fts5CResult *aFirst; /* Current merge state (see above) */
+};
+
+/*
+** Object for iterating through a single segment, visiting each term/docid
+** pair in the segment.
+**
+** pSeg:
+** The segment to iterate through.
+**
+** iLeafPgno:
+** Current leaf page number within segment.
+**
+** iLeafOffset:
+** Byte offset within the current leaf that is the first byte of the
+** position list data (one byte passed the position-list size field).
+** rowid field of the current entry. Usually this is the size field of the
+** position list data. The exception is if the rowid for the current entry
+** is the last thing on the leaf page.
+**
+** pLeaf:
+** Buffer containing current leaf page data. Set to NULL at EOF.
+**
+** iTermLeafPgno, iTermLeafOffset:
+** Leaf page number containing the last term read from the segment. And
+** the offset immediately following the term data.
+**
+** flags:
+** Mask of FTS5_SEGITER_XXX values. Interpreted as follows:
+**
+** FTS5_SEGITER_ONETERM:
+** If set, set the iterator to point to EOF after the current doclist
+** has been exhausted. Do not proceed to the next term in the segment.
+**
+** FTS5_SEGITER_REVERSE:
+** This flag is only ever set if FTS5_SEGITER_ONETERM is also set. If
+** it is set, iterate through docids in descending order instead of the
+** default ascending order.
+**
+** iRowidOffset/nRowidOffset/aRowidOffset:
+** These are used if the FTS5_SEGITER_REVERSE flag is set.
+**
+** For each rowid on the page corresponding to the current term, the
+** corresponding aRowidOffset[] entry is set to the byte offset of the
+** start of the "position-list-size" field within the page.
+*/
+struct Fts5SegIter {
+ Fts5StructureSegment *pSeg; /* Segment to iterate through */
+ int iIdx; /* Byte offset within current leaf */
+ int flags; /* Mask of configuration flags */
+ int iLeafPgno; /* Current leaf page number */
+ Fts5Data *pLeaf; /* Current leaf data */
+ int iLeafOffset; /* Byte offset within current leaf */
+
+ /* The page and offset from which the current term was read. The offset
+ ** is the offset of the first rowid in the current doclist. */
+ int iTermLeafPgno;
+ int iTermLeafOffset;
+
+ /* The following are only used if the FTS5_SEGITER_REVERSE flag is set. */
+ int iRowidOffset; /* Current entry in aRowidOffset[] */
+ int nRowidOffset; /* Allocated size of aRowidOffset[] array */
+ int *aRowidOffset; /* Array of offset to rowid fields */
+
+ Fts5DlidxIter *pDlidx; /* If there is a doclist-index */
+
+ /* Variables populated based on current entry. */
+ Fts5Buffer term; /* Current term */
+ i64 iRowid; /* Current rowid */
+ int nPos; /* Number of bytes in current position list */
+ int bDel; /* True if the delete flag is set */
+};
+
+#define FTS5_SEGITER_ONETERM 0x01
+#define FTS5_SEGITER_REVERSE 0x02
+
+
+/*
+** Object for iterating through paginated data.
+*/
+struct Fts5ChunkIter {
+ Fts5Data *pLeaf; /* Current leaf data. NULL -> EOF. */
+ i64 iLeafRowid; /* Absolute rowid of current leaf */
+ int nRem; /* Remaining bytes of data to read */
+
+ /* Output parameters */
+ u8 *p; /* Pointer to chunk of data */
+ int n; /* Size of buffer p in bytes */
+};
+
+/*
+** Object for iterating through a single position list on disk.
+*/
+struct Fts5PosIter {
+ Fts5ChunkIter chunk; /* Current chunk of data */
+ int iOff; /* Offset within chunk data */
+
+ int iCol;
+ int iPos;
+};
+
+/*
+** Object for iterating through the conents of a single internal node in
+** memory.
+*/
+struct Fts5NodeIter {
+ /* Internal. Set and managed by fts5NodeIterXXX() functions. Except,
+ ** the EOF test for the iterator is (Fts5NodeIter.aData==0). */
+ const u8 *aData;
+ int nData;
+ int iOff;
+
+ /* Output variables */
+ Fts5Buffer term;
+ int nEmpty;
+ int iChild;
+ int bDlidx;
+};
+
+/*
+** An instance of the following type is used to iterate through the contents
+** of a doclist-index record.
+**
+** pData:
+** Record containing the doclist-index data.
+**
+** bEof:
+** Set to true once iterator has reached EOF.
+**
+** iOff:
+** Set to the current offset within record pData.
+*/
+struct Fts5DlidxIter {
+ Fts5Data *pData; /* Data for doclist index, if any */
+ int iOff; /* Current offset into pDlidx */
+ int bEof; /* At EOF already */
+ int iFirstOff; /* Used by reverse iterators only */
+
+ /* Output variables */
+ int iLeafPgno; /* Page number of current leaf page */
+ i64 iRowid; /* First rowid on leaf iLeafPgno */
+};
+
+
+/*
+** An Fts5BtreeIter object is used to iterate through all entries in the
+** b-tree hierarchy belonging to a single fts5 segment. In this case the
+** "b-tree hierarchy" is all b-tree nodes except leaves. Each entry in the
+** b-tree hierarchy consists of the following:
+**
+** iLeaf: The page number of the leaf page the entry points to.
+**
+** term: A split-key that all terms on leaf page $iLeaf must be greater
+** than or equal to. The "term" associated with the first b-tree
+** hierarchy entry (the one that points to leaf page 1) is always
+** an empty string.
+**
+** nEmpty: The number of empty (termless) leaf pages that immediately
+** following iLeaf.
+**
+** The Fts5BtreeIter object is only used as part of the integrity-check code.
+*/
+struct Fts5BtreeIterLevel {
+ Fts5NodeIter s; /* Iterator for the current node */
+ Fts5Data *pData; /* Data for the current node */
+};
+struct Fts5BtreeIter {
+ Fts5Index *p; /* FTS5 backend object */
+ Fts5StructureSegment *pSeg; /* Iterate through this segment's b-tree */
+ int iIdx; /* Index pSeg belongs to */
+ int nLvl; /* Size of aLvl[] array */
+ Fts5BtreeIterLevel *aLvl; /* Level for each tier of b-tree */
+
+ /* Output variables */
+ Fts5Buffer term; /* Current term */
+ int iLeaf; /* Leaf containing terms >= current term */
+ int nEmpty; /* Number of "empty" leaves following iLeaf */
+ int bEof; /* Set to true at EOF */
+ int bDlidx; /* True if there exists a dlidx */
+};
+
+
+static void fts5PutU16(u8 *aOut, u16 iVal){
+ aOut[0] = (iVal>>8);
+ aOut[1] = (iVal&0xFF);
+}
+
+static u16 fts5GetU16(const u8 *aIn){
+ return ((u16)aIn[0] << 8) + aIn[1];
+}
+
+/*
+** This is a copy of the sqlite3GetVarint32() routine from the SQLite core.
+** Except, this version does handle the single byte case that the core
+** version depends on being handled before its function is called.
+*/
+int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v){
+ u32 a,b;
+
+ /* The 1-byte case. Overwhelmingly the most common. */
+ a = *p;
+ /* a: p0 (unmasked) */
+ if (!(a&0x80))
+ {
+ /* Values between 0 and 127 */
+ *v = a;
+ return 1;
+ }
+
+ /* The 2-byte case */
+ p++;
+ b = *p;
+ /* b: p1 (unmasked) */
+ if (!(b&0x80))
+ {
+ /* Values between 128 and 16383 */
+ a &= 0x7f;
+ a = a<<7;
+ *v = a | b;
+ return 2;
+ }
+
+ /* The 3-byte case */
+ p++;
+ a = a<<14;
+ a |= *p;
+ /* a: p0<<14 | p2 (unmasked) */
+ if (!(a&0x80))
+ {
+ /* Values between 16384 and 2097151 */
+ a &= (0x7f<<14)|(0x7f);
+ b &= 0x7f;
+ b = b<<7;
+ *v = a | b;
+ return 3;
+ }
+
+ /* A 32-bit varint is used to store size information in btrees.
+ ** Objects are rarely larger than 2MiB limit of a 3-byte varint.
+ ** A 3-byte varint is sufficient, for example, to record the size
+ ** of a 1048569-byte BLOB or string.
+ **
+ ** We only unroll the first 1-, 2-, and 3- byte cases. The very
+ ** rare larger cases can be handled by the slower 64-bit varint
+ ** routine.
+ */
+ {
+ u64 v64;
+ u8 n;
+ p -= 2;
+ n = sqlite3GetVarint(p, &v64);
+ *v = (u32)v64;
+ assert( n>3 && n<=9 );
+ return n;
+ }
+}
+
+int sqlite3Fts5GetVarintLen(u32 iVal){
+ if( iVal<(1 << 7 ) ) return 1;
+ if( iVal<(1 << 14) ) return 2;
+ if( iVal<(1 << 21) ) return 3;
+ if( iVal<(1 << 28) ) return 4;
+ return 5;
+}
+
+/*
+** Allocate and return a buffer at least nByte bytes in size.
+**
+** If an OOM error is encountered, return NULL and set the error code in
+** the Fts5Index handle passed as the first argument.
+*/
+static void *fts5IdxMalloc(Fts5Index *p, int nByte){
+ void *pRet = 0;
+ if( p->rc==SQLITE_OK ){
+ pRet = sqlite3_malloc(nByte);
+ if( pRet==0 ){
+ p->rc = SQLITE_NOMEM;
+ }else{
+ memset(pRet, 0, nByte);
+ }
+ }
+ return pRet;
+}
+
+/*
+** Compare the contents of the pLeft buffer with the pRight/nRight blob.
+**
+** Return -ve if pLeft is smaller than pRight, 0 if they are equal or
+** +ve if pRight is smaller than pLeft. In other words:
+**
+** res = *pLeft - *pRight
+*/
+static int fts5BufferCompareBlob(
+ Fts5Buffer *pLeft, /* Left hand side of comparison */
+ const u8 *pRight, int nRight /* Right hand side of comparison */
+){
+ int nCmp = MIN(pLeft->n, nRight);
+ int res = memcmp(pLeft->p, pRight, nCmp);
+ return (res==0 ? (pLeft->n - nRight) : res);
+}
+
+/*
+** Compare the contents of the two buffers using memcmp(). If one buffer
+** is a prefix of the other, it is considered the lesser.
+**
+** Return -ve if pLeft is smaller than pRight, 0 if they are equal or
+** +ve if pRight is smaller than pLeft. In other words:
+**
+** res = *pLeft - *pRight
+*/
+static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){
+ int nCmp = MIN(pLeft->n, pRight->n);
+ int res = memcmp(pLeft->p, pRight->p, nCmp);
+ return (res==0 ? (pLeft->n - pRight->n) : res);
+}
+
+
+/*
+** Close the read-only blob handle, if it is open.
+*/
+static void fts5CloseReader(Fts5Index *p){
+ if( p->pReader ){
+ sqlite3_blob *pReader = p->pReader;
+ p->pReader = 0;
+ sqlite3_blob_close(pReader);
+ }
+}
+
+static Fts5Data *fts5DataReadOrBuffer(
+ Fts5Index *p,
+ Fts5Buffer *pBuf,
+ i64 iRowid
+){
+ Fts5Data *pRet = 0;
+ if( p->rc==SQLITE_OK ){
+ int rc = SQLITE_OK;
+
+#if 0
+Fts5Buffer buf = {0,0,0};
+fts5DebugRowid(&rc, &buf, iRowid);
+fprintf(stdout, "read: %s\n", buf.p);
+fflush(stdout);
+sqlite3_free(buf.p);
+#endif
+ if( p->pReader ){
+ /* This call may return SQLITE_ABORT if there has been a savepoint
+ ** rollback since it was last used. In this case a new blob handle
+ ** is required. */
+ rc = sqlite3_blob_reopen(p->pReader, iRowid);
+ if( rc==SQLITE_ABORT ){
+ fts5CloseReader(p);
+ rc = SQLITE_OK;
+ }
+ }
+
+ /* If the blob handle is not yet open, open and seek it. Otherwise, use
+ ** the blob_reopen() API to reseek the existing blob handle. */
+ if( p->pReader==0 ){
+ Fts5Config *pConfig = p->pConfig;
+ rc = sqlite3_blob_open(pConfig->db,
+ pConfig->zDb, p->zDataTbl, "block", iRowid, 0, &p->pReader
+ );
+ }
+
+ if( rc==SQLITE_OK ){
+ u8 *aOut; /* Read blob data into this buffer */
+ int nByte = sqlite3_blob_bytes(p->pReader);
+ if( pBuf ){
+ fts5BufferZero(pBuf);
+ fts5BufferGrow(&rc, pBuf, nByte);
+ aOut = pBuf->p;
+ pBuf->n = nByte;
+ }else{
+ int nSpace = nByte + FTS5_DATA_ZERO_PADDING;
+ pRet = (Fts5Data*)sqlite3Fts5MallocZero(&rc, nSpace+sizeof(Fts5Data));
+ if( pRet ){
+ pRet->n = nByte;
+ aOut = pRet->p = (u8*)&pRet[1];
+ pRet->nRef = 1;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_blob_read(p->pReader, aOut, nByte, 0);
+ }
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(pRet);
+ pRet = 0;
+ }
+ }
+ p->rc = rc;
+ p->nRead++;
+ }
+
+ return pRet;
+}
+
+/*
+** Retrieve a record from the %_data table.
+**
+** If an error occurs, NULL is returned and an error left in the
+** Fts5Index object.
+*/
+static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){
+ Fts5Data *pRet = fts5DataReadOrBuffer(p, 0, iRowid);
+ assert( (pRet==0)==(p->rc!=SQLITE_OK) );
+ return pRet;
+}
+
+/*
+** Read a record from the %_data table into the buffer supplied as the
+** second argument.
+**
+** If an error occurs, an error is left in the Fts5Index object. If an
+** error has already occurred when this function is called, it is a
+** no-op.
+*/
+static void fts5DataBuffer(Fts5Index *p, Fts5Buffer *pBuf, i64 iRowid){
+ (void)fts5DataReadOrBuffer(p, pBuf, iRowid);
+}
+
+/*
+** Release a reference to data record returned by an earlier call to
+** fts5DataRead().
+*/
+static void fts5DataRelease(Fts5Data *pData){
+ if( pData ){
+ assert( pData->nRef>0 );
+ pData->nRef--;
+ if( pData->nRef==0 ) sqlite3_free(pData);
+ }
+}
+
+static void fts5DataReference(Fts5Data *pData){
+ pData->nRef++;
+}
+
+/*
+** INSERT OR REPLACE a record into the %_data table.
+*/
+static void fts5DataWrite(Fts5Index *p, i64 iRowid, const u8 *pData, int nData){
+ if( p->rc!=SQLITE_OK ) return;
+
+ if( p->pWriter==0 ){
+ int rc;
+ Fts5Config *pConfig = p->pConfig;
+ char *zSql = sqlite3_mprintf(
+ "REPLACE INTO '%q'.%Q(id, block) VALUES(?,?)", pConfig->zDb, p->zDataTbl
+ );
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p->pWriter, 0);
+ sqlite3_free(zSql);
+ }
+ if( rc!=SQLITE_OK ){
+ p->rc = rc;
+ return;
+ }
+ }
+
+ sqlite3_bind_int64(p->pWriter, 1, iRowid);
+ sqlite3_bind_blob(p->pWriter, 2, pData, nData, SQLITE_STATIC);
+ sqlite3_step(p->pWriter);
+ p->rc = sqlite3_reset(p->pWriter);
+}
+
+/*
+** Execute the following SQL:
+**
+** DELETE FROM %_data WHERE id BETWEEN $iFirst AND $iLast
+*/
+static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){
+ if( p->rc!=SQLITE_OK ) return;
+
+ if( p->pDeleter==0 ){
+ int rc;
+ Fts5Config *pConfig = p->pConfig;
+ char *zSql = sqlite3_mprintf(
+ "DELETE FROM '%q'.%Q WHERE id>=? AND id<=?", pConfig->zDb, p->zDataTbl
+ );
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &p->pDeleter, 0);
+ sqlite3_free(zSql);
+ }
+ if( rc!=SQLITE_OK ){
+ p->rc = rc;
+ return;
+ }
+ }
+
+ sqlite3_bind_int64(p->pDeleter, 1, iFirst);
+ sqlite3_bind_int64(p->pDeleter, 2, iLast);
+ sqlite3_step(p->pDeleter);
+ p->rc = sqlite3_reset(p->pDeleter);
+}
+
+/*
+** Close the sqlite3_blob handle used to read records from the %_data table.
+** And discard any cached reads. This function is called at the end of
+** a read transaction or when any sub-transaction is rolled back.
+*/
+#if 0
+static void fts5DataReset(Fts5Index *p){
+ if( p->pReader ){
+ sqlite3_blob_close(p->pReader);
+ p->pReader = 0;
+ }
+}
+#endif
+
+/*
+** Remove all records associated with segment iSegid in index iIdx.
+*/
+static void fts5DataRemoveSegment(Fts5Index *p, int iIdx, int iSegid){
+ i64 iFirst = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, 0);
+ i64 iLast = FTS5_SEGMENT_ROWID(iIdx, iSegid+1, 0, 0)-1;
+ fts5DataDelete(p, iFirst, iLast);
+}
+
+/*
+** Release a reference to an Fts5Structure object returned by an earlier
+** call to fts5StructureRead() or fts5StructureDecode().
+*/
+static void fts5StructureRelease(Fts5Structure *pStruct){
+ if( pStruct ){
+ int i;
+ for(i=0; i<pStruct->nLevel; i++){
+ sqlite3_free(pStruct->aLevel[i].aSeg);
+ }
+ sqlite3_free(pStruct);
+ }
+}
+
+/*
+** Deserialize and return the structure record currently stored in serialized
+** form within buffer pData/nData.
+**
+** The Fts5Structure.aLevel[] and each Fts5StructureLevel.aSeg[] array
+** are over-allocated by one slot. This allows the structure contents
+** to be more easily edited.
+**
+** If an error occurs, *ppOut is set to NULL and an SQLite error code
+** returned. Otherwise, *ppOut is set to point to the new object and
+** SQLITE_OK returned.
+*/
+static int fts5StructureDecode(
+ const u8 *pData, /* Buffer containing serialized structure */
+ int nData, /* Size of buffer pData in bytes */
+ int *piCookie, /* Configuration cookie value */
+ Fts5Structure **ppOut /* OUT: Deserialized object */
+){
+ int rc = SQLITE_OK;
+ int i = 0;
+ int iLvl;
+ int nLevel = 0;
+ int nSegment = 0;
+ int nByte; /* Bytes of space to allocate at pRet */
+ Fts5Structure *pRet = 0; /* Structure object to return */
+
+ /* Grab the cookie value */
+ if( piCookie ) *piCookie = sqlite3Fts5Get32(pData);
+ i = 4;
+
+ /* Read the total number of levels and segments from the start of the
+ ** structure record. */
+ i += fts5GetVarint32(&pData[i], nLevel);
+ i += fts5GetVarint32(&pData[i], nSegment);
+ nByte = (
+ sizeof(Fts5Structure) + /* Main structure */
+ sizeof(Fts5StructureLevel) * (nLevel) /* aLevel[] array */
+ );
+ pRet = (Fts5Structure*)sqlite3Fts5MallocZero(&rc, nByte);
+
+ if( pRet ){
+ pRet->nLevel = nLevel;
+ i += sqlite3GetVarint(&pData[i], &pRet->nWriteCounter);
+
+ for(iLvl=0; rc==SQLITE_OK && iLvl<nLevel; iLvl++){
+ Fts5StructureLevel *pLvl = &pRet->aLevel[iLvl];
+ int nTotal;
+ int iSeg;
+
+ i += fts5GetVarint32(&pData[i], pLvl->nMerge);
+ i += fts5GetVarint32(&pData[i], nTotal);
+ assert( nTotal>=pLvl->nMerge );
+ pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc,
+ nTotal * sizeof(Fts5StructureSegment)
+ );
+
+ if( rc==SQLITE_OK ){
+ pLvl->nSeg = nTotal;
+ for(iSeg=0; iSeg<nTotal; iSeg++){
+ i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].iSegid);
+ i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].nHeight);
+ i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].pgnoFirst);
+ i += fts5GetVarint32(&pData[i], pLvl->aSeg[iSeg].pgnoLast);
+ }
+ }else{
+ fts5StructureRelease(pRet);
+ pRet = 0;
+ }
+ }
+ }
+
+ *ppOut = pRet;
+ return rc;
+}
+
+/*
+**
+*/
+static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){
+ if( *pRc==SQLITE_OK ){
+ Fts5Structure *pStruct = *ppStruct;
+ int nLevel = pStruct->nLevel;
+ int nByte = (
+ sizeof(Fts5Structure) + /* Main structure */
+ sizeof(Fts5StructureLevel) * (nLevel+1) /* aLevel[] array */
+ );
+
+ pStruct = sqlite3_realloc(pStruct, nByte);
+ if( pStruct ){
+ memset(&pStruct->aLevel[nLevel], 0, sizeof(Fts5StructureLevel));
+ pStruct->nLevel++;
+ *ppStruct = pStruct;
+ }else{
+ *pRc = SQLITE_NOMEM;
+ }
+ }
+}
+
+/*
+** Extend level iLvl so that there is room for at least nExtra more
+** segments.
+*/
+static void fts5StructureExtendLevel(
+ int *pRc,
+ Fts5Structure *pStruct,
+ int iLvl,
+ int nExtra,
+ int bInsert
+){
+ if( *pRc==SQLITE_OK ){
+ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
+ Fts5StructureSegment *aNew;
+ int nByte;
+
+ nByte = (pLvl->nSeg + nExtra) * sizeof(Fts5StructureSegment);
+ aNew = sqlite3_realloc(pLvl->aSeg, nByte);
+ if( aNew ){
+ if( bInsert==0 ){
+ memset(&aNew[pLvl->nSeg], 0, sizeof(Fts5StructureSegment) * nExtra);
+ }else{
+ int nMove = pLvl->nSeg * sizeof(Fts5StructureSegment);
+ memmove(&aNew[nExtra], aNew, nMove);
+ memset(aNew, 0, sizeof(Fts5StructureSegment) * nExtra);
+ }
+ pLvl->aSeg = aNew;
+ }else{
+ *pRc = SQLITE_NOMEM;
+ }
+ }
+}
+
+/*
+** Read, deserialize and return the structure record for index iIdx.
+**
+** The Fts5Structure.aLevel[] and each Fts5StructureLevel.aSeg[] array
+** are over-allocated as described for function fts5StructureDecode()
+** above.
+**
+** If an error occurs, NULL is returned and an error code left in the
+** Fts5Index handle. If an error has already occurred when this function
+** is called, it is a no-op.
+*/
+static Fts5Structure *fts5StructureRead(Fts5Index *p, int iIdx){
+ Fts5Config *pConfig = p->pConfig;
+ Fts5Structure *pRet = 0; /* Object to return */
+ Fts5Data *pData; /* %_data entry containing structure record */
+ int iCookie; /* Configuration cookie */
+
+ assert( iIdx<=pConfig->nPrefix );
+ pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID(iIdx));
+ if( !pData ) return 0;
+ p->rc = fts5StructureDecode(pData->p, pData->n, &iCookie, &pRet);
+
+ if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){
+ p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
+ }
+
+ fts5DataRelease(pData);
+ if( p->rc!=SQLITE_OK ){
+ fts5StructureRelease(pRet);
+ pRet = 0;
+ }
+ return pRet;
+}
+
+/*
+** Return the total number of segments in index structure pStruct.
+*/
+static int fts5StructureCountSegments(Fts5Structure *pStruct){
+ int nSegment = 0; /* Total number of segments */
+ if( pStruct ){
+ int iLvl; /* Used to iterate through levels */
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+ nSegment += pStruct->aLevel[iLvl].nSeg;
+ }
+ }
+
+ return nSegment;
+}
+
+/*
+** Serialize and store the "structure" record for index iIdx.
+**
+** If an error occurs, leave an error code in the Fts5Index object. If an
+** error has already occurred, this function is a no-op.
+*/
+static void fts5StructureWrite(Fts5Index *p, int iIdx, Fts5Structure *pStruct){
+ if( p->rc==SQLITE_OK ){
+ int nSegment; /* Total number of segments */
+ Fts5Buffer buf; /* Buffer to serialize record into */
+ int iLvl; /* Used to iterate through levels */
+ int iCookie; /* Cookie value to store */
+
+ nSegment = fts5StructureCountSegments(pStruct);
+ memset(&buf, 0, sizeof(Fts5Buffer));
+
+ /* Append the current configuration cookie */
+ iCookie = p->pConfig->iCookie;
+ if( iCookie<0 ) iCookie = 0;
+ fts5BufferAppend32(&p->rc, &buf, iCookie);
+
+ fts5BufferAppendVarint(&p->rc, &buf, pStruct->nLevel);
+ fts5BufferAppendVarint(&p->rc, &buf, nSegment);
+ fts5BufferAppendVarint(&p->rc, &buf, (i64)pStruct->nWriteCounter);
+
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+ int iSeg; /* Used to iterate through segments */
+ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
+ fts5BufferAppendVarint(&p->rc, &buf, pLvl->nMerge);
+ fts5BufferAppendVarint(&p->rc, &buf, pLvl->nSeg);
+ assert( pLvl->nMerge<=pLvl->nSeg );
+
+ for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){
+ fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].iSegid);
+ fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].nHeight);
+ fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoFirst);
+ fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoLast);
+ }
+ }
+
+ fts5DataWrite(p, FTS5_STRUCTURE_ROWID(iIdx), buf.p, buf.n);
+ fts5BufferFree(&buf);
+ }
+}
+
+#if 0
+static void fts5DebugStructure(int*,Fts5Buffer*,Fts5Structure*);
+static void fts5PrintStructure(const char *zCaption, Fts5Structure *pStruct){
+ int rc = SQLITE_OK;
+ Fts5Buffer buf;
+ memset(&buf, 0, sizeof(buf));
+ fts5DebugStructure(&rc, &buf, pStruct);
+ fprintf(stdout, "%s: %s\n", zCaption, buf.p);
+ fflush(stdout);
+ fts5BufferFree(&buf);
+}
+#else
+# define fts5PrintStructure(x,y)
+#endif
+
+static int fts5SegmentSize(Fts5StructureSegment *pSeg){
+ return 1 + pSeg->pgnoLast - pSeg->pgnoFirst;
+}
+
+/*
+** Return a copy of index structure pStruct. Except, promote as many
+** segments as possible to level iPromote. If an OOM occurs, NULL is
+** returned.
+*/
+static void fts5StructurePromoteTo(
+ Fts5Index *p,
+ int iPromote,
+ int szPromote,
+ Fts5Structure *pStruct
+){
+ int il, is;
+ Fts5StructureLevel *pOut = &pStruct->aLevel[iPromote];
+
+ if( pOut->nMerge==0 ){
+ for(il=iPromote+1; il<pStruct->nLevel; il++){
+ Fts5StructureLevel *pLvl = &pStruct->aLevel[il];
+ if( pLvl->nMerge ) return;
+ for(is=pLvl->nSeg-1; is>=0; is--){
+ int sz = fts5SegmentSize(&pLvl->aSeg[is]);
+ if( sz>szPromote ) return;
+ fts5StructureExtendLevel(&p->rc, pStruct, iPromote, 1, 1);
+ if( p->rc ) return;
+ memcpy(pOut->aSeg, &pLvl->aSeg[is], sizeof(Fts5StructureSegment));
+ pOut->nSeg++;
+ pLvl->nSeg--;
+ }
+ }
+ }
+}
+
+/*
+** A new segment has just been written to level iLvl of index structure
+** pStruct. This function determines if any segments should be promoted
+** as a result. Segments are promoted in two scenarios:
+**
+** a) If the segment just written is smaller than one or more segments
+** within the previous populated level, it is promoted to the previous
+** populated level.
+**
+** b) If the segment just written is larger than the newest segment on
+** the next populated level, then that segment, and any other adjacent
+** segments that are also smaller than the one just written, are
+** promoted.
+**
+** If one or more segments are promoted, the structure object is updated
+** to reflect this.
+*/
+static void fts5StructurePromote(
+ Fts5Index *p, /* FTS5 backend object */
+ int iLvl, /* Index level just updated */
+ Fts5Structure *pStruct /* Index structure */
+){
+ if( p->rc==SQLITE_OK ){
+ int iTst;
+ int iPromote = -1;
+ int szPromote; /* Promote anything this size or smaller */
+ Fts5StructureSegment *pSeg; /* Segment just written */
+ int szSeg; /* Size of segment just written */
+
+
+ pSeg = &pStruct->aLevel[iLvl].aSeg[pStruct->aLevel[iLvl].nSeg-1];
+ szSeg = (1 + pSeg->pgnoLast - pSeg->pgnoFirst);
+
+ /* Check for condition (a) */
+ for(iTst=iLvl-1; iTst>=0 && pStruct->aLevel[iTst].nSeg==0; iTst--);
+ if( iTst>=0 ){
+ int i;
+ int szMax = 0;
+ Fts5StructureLevel *pTst = &pStruct->aLevel[iTst];
+ assert( pTst->nMerge==0 );
+ for(i=0; i<pTst->nSeg; i++){
+ int sz = pTst->aSeg[i].pgnoLast - pTst->aSeg[i].pgnoFirst + 1;
+ if( sz>szMax ) szMax = sz;
+ }
+ if( szMax>=szSeg ){
+ /* Condition (a) is true. Promote the newest segment on level
+ ** iLvl to level iTst. */
+ iPromote = iTst;
+ szPromote = szMax;
+ }
+ }
+
+ /* If condition (a) is not met, assume (b) is true. StructurePromoteTo()
+ ** is a no-op if it is not. */
+ if( iPromote<0 ){
+ iPromote = iLvl;
+ szPromote = szSeg;
+ }
+ fts5StructurePromoteTo(p, iPromote, szPromote, pStruct);
+ }
+}
+
+
+/*
+** If the pIter->iOff offset currently points to an entry indicating one
+** or more term-less nodes, advance past it and set pIter->nEmpty to
+** the number of empty child nodes.
+*/
+static void fts5NodeIterGobbleNEmpty(Fts5NodeIter *pIter){
+ if( pIter->iOff<pIter->nData && 0==(pIter->aData[pIter->iOff] & 0xfe) ){
+ pIter->bDlidx = pIter->aData[pIter->iOff] & 0x01;
+ pIter->iOff++;
+ pIter->iOff += fts5GetVarint32(&pIter->aData[pIter->iOff], pIter->nEmpty);
+ }else{
+ pIter->nEmpty = 0;
+ pIter->bDlidx = 0;
+ }
+}
+
+/*
+** Advance to the next entry within the node.
+*/
+static void fts5NodeIterNext(int *pRc, Fts5NodeIter *pIter){
+ if( pIter->iOff>=pIter->nData ){
+ pIter->aData = 0;
+ pIter->iChild += pIter->nEmpty;
+ }else{
+ int nPre, nNew;
+ pIter->iOff += fts5GetVarint32(&pIter->aData[pIter->iOff], nPre);
+ pIter->iOff += fts5GetVarint32(&pIter->aData[pIter->iOff], nNew);
+ pIter->term.n = nPre-2;
+ fts5BufferAppendBlob(pRc, &pIter->term, nNew, pIter->aData+pIter->iOff);
+ pIter->iOff += nNew;
+ pIter->iChild += (1 + pIter->nEmpty);
+ fts5NodeIterGobbleNEmpty(pIter);
+ if( *pRc ) pIter->aData = 0;
+ }
+}
+
+
+/*
+** Initialize the iterator object pIter to iterate through the internal
+** segment node in pData.
+*/
+static void fts5NodeIterInit(const u8 *aData, int nData, Fts5NodeIter *pIter){
+ memset(pIter, 0, sizeof(*pIter));
+ pIter->aData = aData;
+ pIter->nData = nData;
+ pIter->iOff = fts5GetVarint32(aData, pIter->iChild);
+ fts5NodeIterGobbleNEmpty(pIter);
+}
+
+/*
+** Free any memory allocated by the iterator object.
+*/
+static void fts5NodeIterFree(Fts5NodeIter *pIter){
+ fts5BufferFree(&pIter->term);
+}
+
+/*
+** The iterator passed as the first argument has the following fields set
+** as follows. This function sets up the rest of the iterator so that it
+** points to the first rowid in the doclist-index.
+**
+** pData: pointer to doclist-index record,
+** iLeafPgno: page number that this doclist-index is associated with.
+**
+** When this function is called pIter->iLeafPgno is the page number the
+** doclist is associated with (the one featuring the term).
+*/
+static int fts5DlidxIterFirst(Fts5DlidxIter *pIter){
+ Fts5Data *pData = pIter->pData;
+ int i;
+ int bPresent;
+
+ assert( pIter->pData );
+ assert( pIter->iLeafPgno>0 );
+
+ /* Read the first rowid value. And the "present" flag that follows it. */
+ pIter->iOff += getVarint(&pData->p[0], (u64*)&pIter->iRowid);
+ bPresent = pData->p[pIter->iOff++];
+ if( bPresent ){
+ i = 0;
+ }else{
+ /* Count the number of leading 0x00 bytes. */
+ for(i=1; pIter->iOff<pData->n; i++){
+ if( pData->p[pIter->iOff] ) break;
+ pIter->iOff++;
+ }
+
+ /* Unless we are already at the end of the doclist-index, load the first
+ ** rowid value. */
+ if( pIter->iOff<pData->n ){
+ i64 iVal;
+ pIter->iOff += getVarint(&pData->p[pIter->iOff], (u64*)&iVal);
+ pIter->iRowid += iVal;
+ }else{
+ pIter->bEof = 1;
+ }
+ }
+ pIter->iLeafPgno += (i+1);
+
+ pIter->iFirstOff = pIter->iOff;
+ return pIter->bEof;
+}
+
+/*
+** Advance the iterator passed as the only argument.
+*/
+static int fts5DlidxIterNext(Fts5DlidxIter *pIter){
+ Fts5Data *pData = pIter->pData;
+ int iOff;
+
+ for(iOff=pIter->iOff; iOff<pData->n; iOff++){
+ if( pData->p[iOff] ) break;
+ }
+
+ if( iOff<pData->n ){
+ i64 iVal;
+ pIter->iLeafPgno += (iOff - pIter->iOff) + 1;
+ iOff += getVarint(&pData->p[iOff], (u64*)&iVal);
+ pIter->iRowid += iVal;
+ pIter->iOff = iOff;
+ }else{
+ pIter->bEof = 1;
+ }
+
+ return pIter->bEof;
+}
+
+static int fts5DlidxIterEof(Fts5Index *p, Fts5DlidxIter *pIter){
+ return pIter->bEof;
+}
+
+static void fts5DlidxIterLast(Fts5DlidxIter *pIter){
+ if( fts5DlidxIterFirst(pIter)==0 ){
+ while( 0==fts5DlidxIterNext(pIter) );
+ pIter->bEof = 0;
+ }
+}
+
+static int fts5DlidxIterPrev(Fts5DlidxIter *pIter){
+ int iOff = pIter->iOff;
+
+ assert( pIter->bEof==0 );
+ if( iOff<=pIter->iFirstOff ){
+ pIter->bEof = 1;
+ }else{
+ u8 *a = pIter->pData->p;
+ i64 iVal;
+ int iLimit;
+
+ /* Currently iOff points to the first byte of a varint. This block
+ ** decrements iOff until it points to the first byte of the previous
+ ** varint. Taking care not to read any memory locations that occur
+ ** before the buffer in memory. */
+ iLimit = (iOff>9 ? iOff-9 : 0);
+ for(iOff--; iOff>iLimit; iOff--){
+ if( (a[iOff-1] & 0x80)==0 ) break;
+ }
+
+ getVarint(&a[iOff], (u64*)&iVal);
+ pIter->iRowid -= iVal;
+ pIter->iLeafPgno--;
+
+ /* Skip backwards passed any 0x00 bytes. */
+ while( iOff>pIter->iFirstOff
+ && a[iOff-1]==0x00 && (a[iOff-2] & 0x80)==0
+ ){
+ iOff--;
+ pIter->iLeafPgno--;
+ }
+ pIter->iOff = iOff;
+ }
+
+ return pIter->bEof;
+}
+
+static Fts5DlidxIter *fts5DlidxIterInit(
+ Fts5Index *p, /* Fts5 Backend to iterate within */
+ int bRev, /* True for ORDER BY ASC */
+ int iIdx, int iSegid, /* Segment iSegid within index iIdx */
+ int iLeafPg /* Leaf page number to load dlidx for */
+){
+ Fts5DlidxIter *pIter;
+
+ pIter = (Fts5DlidxIter*)fts5IdxMalloc(p, sizeof(Fts5DlidxIter));
+ if( pIter==0 ) return 0;
+
+ pIter->pData = fts5DataRead(p, FTS5_DOCLIST_IDX_ROWID(iIdx, iSegid, iLeafPg));
+ if( pIter->pData==0 ){
+ sqlite3_free(pIter);
+ pIter = 0;
+ }else{
+ pIter->iLeafPgno = iLeafPg;
+ if( bRev==0 ){
+ fts5DlidxIterFirst(pIter);
+ }else{
+ fts5DlidxIterLast(pIter);
+ }
+ }
+
+ return pIter;
+}
+
+/*
+** Free a doclist-index iterator object allocated by fts5DlidxIterInit().
+*/
+static void fts5DlidxIterFree(Fts5DlidxIter *pIter){
+ if( pIter ){
+ fts5DataRelease(pIter->pData);
+ sqlite3_free(pIter);
+ }
+}
+
+static void fts5LeafHeader(Fts5Data *pLeaf, int *piRowid, int *piTerm){
+ *piRowid = (int)fts5GetU16(&pLeaf->p[0]);
+ *piTerm = (int)fts5GetU16(&pLeaf->p[2]);
+}
+
+/*
+** Load the next leaf page into the segment iterator.
+*/
+static void fts5SegIterNextPage(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5SegIter *pIter /* Iterator to advance to next page */
+){
+ Fts5StructureSegment *pSeg = pIter->pSeg;
+ fts5DataRelease(pIter->pLeaf);
+ pIter->iLeafPgno++;
+ if( pIter->iLeafPgno<=pSeg->pgnoLast ){
+ pIter->pLeaf = fts5DataRead(p,
+ FTS5_SEGMENT_ROWID(pIter->iIdx, pSeg->iSegid, 0, pIter->iLeafPgno)
+ );
+ }else{
+ pIter->pLeaf = 0;
+ }
+}
+
+/*
+** Argument p points to a buffer containing a varint to be interpreted as a
+** position list size field. Read the varint and return the number of bytes
+** read. Before returning, set *pnSz to the number of bytes in the position
+** list, and *pbDel to true if the delete flag is set, or false otherwise.
+*/
+static int fts5GetPoslistSize(const u8 *p, int *pnSz, int *pbDel){
+ int nSz;
+ int n = fts5GetVarint32(p, nSz);
+ *pnSz = nSz/2;
+ *pbDel = nSz & 0x0001;
+ return n;
+}
+
+/*
+** Fts5SegIter.iLeafOffset currently points to the first byte of a
+** position-list size field. Read the value of the field and store it
+** in the following variables:
+**
+** Fts5SegIter.nPos
+** Fts5SegIter.bDel
+**
+** Leave Fts5SegIter.iLeafOffset pointing to the first byte of the
+** position list content (if any).
+*/
+static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){
+ if( p->rc==SQLITE_OK ){
+ int iOff = pIter->iLeafOffset; /* Offset to read at */
+ if( iOff>=pIter->pLeaf->n ){
+ assert( 0 );
+ fts5SegIterNextPage(p, pIter);
+ if( pIter->pLeaf==0 ){
+ if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
+ return;
+ }
+ iOff = 4;
+ }
+ iOff += fts5GetPoslistSize(pIter->pLeaf->p+iOff, &pIter->nPos,&pIter->bDel);
+ pIter->iLeafOffset = iOff;
+ }
+}
+
+/*
+** Fts5SegIter.iLeafOffset currently points to the first byte of the
+** "nSuffix" field of a term. Function parameter nKeep contains the value
+** of the "nPrefix" field (if there was one - it is passed 0 if this is
+** the first term in the segment).
+**
+** This function populates:
+**
+** Fts5SegIter.term
+** Fts5SegIter.rowid
+** Fts5SegIter.nPos
+** Fts5SegIter.bDel
+**
+** accordingly and leaves (Fts5SegIter.iLeafOffset) set to the content of
+** the first position list. The position list belonging to document
+** (Fts5SegIter.iRowid).
+*/
+static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){
+ u8 *a = pIter->pLeaf->p; /* Buffer to read data from */
+ int iOff = pIter->iLeafOffset; /* Offset to read at */
+ int nNew; /* Bytes of new data */
+
+ iOff += fts5GetVarint32(&a[iOff], nNew);
+ pIter->term.n = nKeep;
+ fts5BufferAppendBlob(&p->rc, &pIter->term, nNew, &a[iOff]);
+ iOff += nNew;
+ pIter->iTermLeafOffset = iOff;
+ pIter->iTermLeafPgno = pIter->iLeafPgno;
+ if( iOff>=pIter->pLeaf->n ){
+ fts5SegIterNextPage(p, pIter);
+ if( pIter->pLeaf==0 ){
+ if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT;
+ return;
+ }
+ iOff = 4;
+ a = pIter->pLeaf->p;
+ }
+ iOff += sqlite3GetVarint(&a[iOff], (u64*)&pIter->iRowid);
+ pIter->iLeafOffset = iOff;
+}
+
+/*
+** Initialize the iterator object pIter to iterate through the entries in
+** segment pSeg within index iIdx. The iterator is left pointing to the
+** first entry when this function returns.
+**
+** If an error occurs, Fts5Index.rc is set to an appropriate error code. If
+** an error has already occurred when this function is called, it is a no-op.
+*/
+static void fts5SegIterInit(
+ Fts5Index *p,
+ int iIdx, /* Config.aHash[] index of FTS index */
+ Fts5StructureSegment *pSeg, /* Description of segment */
+ Fts5SegIter *pIter /* Object to populate */
+){
+ if( pSeg->pgnoFirst==0 ){
+ /* This happens if the segment is being used as an input to an incremental
+ ** merge and all data has already been "trimmed". See function
+ ** fts5TrimSegments() for details. In this case leave the iterator empty.
+ ** The caller will see the (pIter->pLeaf==0) and assume the iterator is
+ ** at EOF already. */
+ assert( pIter->pLeaf==0 );
+ return;
+ }
+
+ if( p->rc==SQLITE_OK ){
+ memset(pIter, 0, sizeof(*pIter));
+ pIter->pSeg = pSeg;
+ pIter->iIdx = iIdx;
+ pIter->iLeafPgno = pSeg->pgnoFirst-1;
+ fts5SegIterNextPage(p, pIter);
+ }
+
+ if( p->rc==SQLITE_OK ){
+ u8 *a = pIter->pLeaf->p;
+ pIter->iLeafOffset = fts5GetU16(&a[2]);
+ fts5SegIterLoadTerm(p, pIter, 0);
+ fts5SegIterLoadNPos(p, pIter);
+ }
+}
+
+/*
+** This function is only ever called on iterators created by calls to
+** Fts5IndexQuery() with the FTS5INDEX_QUERY_DESC flag set.
+**
+** The iterator is in an unusual state when this function is called: the
+** Fts5SegIter.iLeafOffset variable is set to the offset of the start of
+** the position-list size field for the first relevant rowid on the page.
+** Fts5SegIter.rowid is set, but nPos and bDel are not.
+**
+** This function advances the iterator so that it points to the last
+** relevant rowid on the page and, if necessary, initializes the
+** aRowidOffset[] and iRowidOffset variables. At this point the iterator
+** is in its regular state - Fts5SegIter.iLeafOffset points to the first
+** byte of the position list content associated with said rowid.
+*/
+static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
+ int n = pIter->pLeaf->n;
+ int i = pIter->iLeafOffset;
+ u8 *a = pIter->pLeaf->p;
+ int iRowidOffset = 0;
+
+ while( p->rc==SQLITE_OK && i<n ){
+ i64 iDelta = 0;
+ int nPos;
+ int bDummy;
+
+ i += fts5GetPoslistSize(&a[i], &nPos, &bDummy);
+ i += nPos;
+ if( i>=n ) break;
+ i += getVarint(&a[i], (u64*)&iDelta);
+ if( iDelta==0 ) break;
+ pIter->iRowid += iDelta;
+
+ if( iRowidOffset>=pIter->nRowidOffset ){
+ int nNew = pIter->nRowidOffset + 8;
+ int *aNew = (int*)sqlite3_realloc(pIter->aRowidOffset, nNew*sizeof(int));
+ if( aNew==0 ){
+ p->rc = SQLITE_NOMEM;
+ break;
+ }
+ pIter->aRowidOffset = aNew;
+ pIter->nRowidOffset = nNew;
+ }
+
+ pIter->aRowidOffset[iRowidOffset++] = pIter->iLeafOffset;
+ pIter->iLeafOffset = i;
+ }
+ pIter->iRowidOffset = iRowidOffset;
+ fts5SegIterLoadNPos(p, pIter);
+}
+
+/*
+**
+*/
+static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){
+ assert( pIter->flags & FTS5_SEGITER_REVERSE );
+ assert( pIter->flags & FTS5_SEGITER_ONETERM );
+
+ fts5DataRelease(pIter->pLeaf);
+ pIter->pLeaf = 0;
+ while( p->rc==SQLITE_OK && pIter->iLeafPgno>pIter->iTermLeafPgno ){
+ Fts5Data *pNew;
+ pIter->iLeafPgno--;
+ pNew = fts5DataRead(p, FTS5_SEGMENT_ROWID(
+ pIter->iIdx, pIter->pSeg->iSegid, 0, pIter->iLeafPgno
+ ));
+ if( pNew ){
+ if( pIter->iLeafPgno==pIter->iTermLeafPgno ){
+ if( pIter->iTermLeafOffset<pNew->n ){
+ pIter->pLeaf = pNew;
+ pIter->iLeafOffset = pIter->iTermLeafOffset;
+ }
+ }else{
+ int iRowidOff, dummy;
+ fts5LeafHeader(pNew, &iRowidOff, &dummy);
+ if( iRowidOff ){
+ pIter->pLeaf = pNew;
+ pIter->iLeafOffset = iRowidOff;
+ }
+ }
+
+ if( pIter->pLeaf ){
+ u8 *a = &pIter->pLeaf->p[pIter->iLeafOffset];
+ pIter->iLeafOffset += getVarint(a, (u64*)&pIter->iRowid);
+ break;
+ }else{
+ fts5DataRelease(pNew);
+ }
+ }
+ }
+
+ if( pIter->pLeaf ){
+ fts5SegIterReverseInitPage(p, pIter);
+ }
+}
+
+/*
+** Return true if the iterator passed as the second argument currently
+** points to a delete marker. A delete marker is an entry with a 0 byte
+** position-list.
+*/
+static int fts5MultiIterIsEmpty(Fts5Index *p, Fts5MultiSegIter *pIter){
+ Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst];
+ return (p->rc==SQLITE_OK && pSeg->pLeaf && pSeg->nPos==0);
+}
+
+/*
+** Advance iterator pIter to the next entry.
+**
+** If an error occurs, Fts5Index.rc is set to an appropriate error code. It
+** is not considered an error if the iterator reaches EOF. If an error has
+** already occurred when this function is called, it is a no-op.
+*/
+static void fts5SegIterNext(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5SegIter *pIter, /* Iterator to advance */
+ int *pbNewTerm /* OUT: Set for new term */
+){
+ assert( pbNewTerm==0 || *pbNewTerm==0 );
+ if( p->rc==SQLITE_OK ){
+ if( pIter->flags & FTS5_SEGITER_REVERSE ){
+
+ if( pIter->iRowidOffset>0 ){
+ u8 *a = pIter->pLeaf->p;
+ int iOff;
+ int nPos;
+ int bDummy;
+ i64 iDelta;
+
+ if( p->rc==SQLITE_OK ){
+ pIter->iRowidOffset--;
+ pIter->iLeafOffset = iOff = pIter->aRowidOffset[pIter->iRowidOffset];
+ iOff += fts5GetPoslistSize(&a[iOff], &nPos, &bDummy);
+ iOff += nPos;
+ getVarint(&a[iOff], (u64*)&iDelta);
+ pIter->iRowid -= iDelta;
+ fts5SegIterLoadNPos(p, pIter);
+ }
+ }else{
+ fts5SegIterReverseNewPage(p, pIter);
+ }
+ }else{
+ Fts5Data *pLeaf = pIter->pLeaf;
+ int iOff;
+ int bNewTerm = 0;
+ int nKeep = 0;
+
+ /* Search for the end of the position list within the current page. */
+ u8 *a = pLeaf->p;
+ int n = pLeaf->n;
+
+ iOff = pIter->iLeafOffset + pIter->nPos;
+
+ if( iOff<n ){
+ /* The next entry is on the current page */
+ u64 iDelta;
+ iOff += sqlite3GetVarint(&a[iOff], &iDelta);
+ pIter->iLeafOffset = iOff;
+ if( iDelta==0 ){
+ bNewTerm = 1;
+ if( iOff>=n ){
+ fts5SegIterNextPage(p, pIter);
+ pIter->iLeafOffset = 4;
+ }else if( iOff!=fts5GetU16(&a[2]) ){
+ pIter->iLeafOffset += fts5GetVarint32(&a[iOff], nKeep);
+ }
+ }else{
+ pIter->iRowid += iDelta;
+ }
+ }else if( pIter->pSeg==0 ){
+ const u8 *pList = 0;
+ const char *zTerm;
+ int nList;
+ if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){
+ sqlite3Fts5HashScanNext(p->apHash[0]);
+ sqlite3Fts5HashScanEntry(p->apHash[0], &zTerm, &pList, &nList);
+ }
+ if( pList==0 ){
+ fts5DataRelease(pIter->pLeaf);
+ pIter->pLeaf = 0;
+ }else{
+ pIter->pLeaf->p = (u8*)pList;
+ pIter->pLeaf->n = nList;
+ sqlite3Fts5BufferSet(&p->rc, &pIter->term, strlen(zTerm), (u8*)zTerm);
+ pIter->iLeafOffset = getVarint(pList, (u64*)&pIter->iRowid);
+ }
+ }else{
+ iOff = 0;
+ /* Next entry is not on the current page */
+ while( iOff==0 ){
+ fts5SegIterNextPage(p, pIter);
+ pLeaf = pIter->pLeaf;
+ if( pLeaf==0 ) break;
+ if( (iOff = fts5GetU16(&pLeaf->p[0])) ){
+ iOff += sqlite3GetVarint(&pLeaf->p[iOff], (u64*)&pIter->iRowid);
+ pIter->iLeafOffset = iOff;
+ }
+ else if( (iOff = fts5GetU16(&pLeaf->p[2])) ){
+ pIter->iLeafOffset = iOff;
+ bNewTerm = 1;
+ }
+ }
+ }
+
+ /* Check if the iterator is now at EOF. If so, return early. */
+ if( pIter->pLeaf ){
+ if( bNewTerm ){
+ if( pIter->flags & FTS5_SEGITER_ONETERM ){
+ fts5DataRelease(pIter->pLeaf);
+ pIter->pLeaf = 0;
+ }else{
+ fts5SegIterLoadTerm(p, pIter, nKeep);
+ fts5SegIterLoadNPos(p, pIter);
+ if( pbNewTerm ) *pbNewTerm = 1;
+ }
+ }else{
+ fts5SegIterLoadNPos(p, pIter);
+ }
+ }
+ }
+ }
+}
+
+#define SWAPVAL(T, a, b) { T tmp; tmp=a; a=b; b=tmp; }
+
+/*
+** Iterator pIter currently points to the first rowid in a doclist. This
+** function sets the iterator up so that iterates in reverse order through
+** the doclist.
+*/
+static void fts5SegIterReverse(Fts5Index *p, int iIdx, Fts5SegIter *pIter){
+ Fts5DlidxIter *pDlidx = pIter->pDlidx;
+ Fts5Data *pLast = 0;
+ int pgnoLast = 0;
+
+ if( pDlidx ){
+ /* If the doclist-iterator is already at EOF, then the current doclist
+ ** contains no entries except those on the current page. */
+ if( fts5DlidxIterEof(p, pDlidx)==0 ){
+ int iSegid = pIter->pSeg->iSegid;
+ pgnoLast = pDlidx->iLeafPgno;
+ pLast = fts5DataRead(p, FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, pgnoLast));
+ }else{
+ pIter->iLeafOffset -= sqlite3Fts5GetVarintLen(pIter->nPos*2+pIter->bDel);
+ }
+ }else{
+ int iOff; /* Byte offset within pLeaf */
+ Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */
+
+ /* Currently, Fts5SegIter.iLeafOffset (and iOff) points to the first
+ ** byte of position-list content for the current rowid. Back it up
+ ** so that it points to the start of the position-list size field. */
+ pIter->iLeafOffset -= sqlite3Fts5GetVarintLen(pIter->nPos*2+pIter->bDel);
+ iOff = pIter->iLeafOffset;
+ assert( iOff>=4 );
+
+ /* Search for a new term within the current leaf. If one can be found,
+ ** then this page contains the largest rowid for the current term. */
+ while( iOff<pLeaf->n ){
+ int nPos;
+ i64 iDelta;
+ int bDummy;
+
+ /* Read the position-list size field */
+ iOff += fts5GetPoslistSize(&pLeaf->p[iOff], &nPos, &bDummy);
+ iOff += nPos;
+ if( iOff>=pLeaf->n ) break;
+
+ /* Rowid delta. Or, if 0x00, the end of doclist marker. */
+ nPos = getVarint(&pLeaf->p[iOff], (u64*)&iDelta);
+ if( iDelta==0 ) break;
+ iOff += nPos;
+ }
+
+ /* If this condition is true then the largest rowid for the current
+ ** term may not be stored on the current page. So search forward to
+ ** see where said rowid really is. */
+ if( iOff>=pLeaf->n ){
+ int pgno;
+ Fts5StructureSegment *pSeg = pIter->pSeg;
+
+ /* The last rowid in the doclist may not be on the current page. Search
+ ** forward to find the page containing the last rowid. */
+ for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){
+ i64 iAbs = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, 0, pgno);
+ Fts5Data *pNew = fts5DataRead(p, iAbs);
+ if( pNew ){
+ int iRowid, iTerm;
+ fts5LeafHeader(pNew, &iRowid, &iTerm);
+ if( iRowid ){
+ SWAPVAL(Fts5Data*, pNew, pLast);
+ pgnoLast = pgno;
+ }
+ fts5DataRelease(pNew);
+ if( iTerm ) break;
+ }
+ }
+ }
+ }
+
+ /* If pLast is NULL at this point, then the last rowid for this doclist
+ ** lies on the page currently indicated by the iterator. In this case
+ ** pIter->iLeafOffset is already set to point to the position-list size
+ ** field associated with the first relevant rowid on the page.
+ **
+ ** Or, if pLast is non-NULL, then it is the page that contains the last
+ ** rowid. In this case configure the iterator so that it points to the
+ ** first rowid on this page.
+ */
+ if( pLast ){
+ int dummy;
+ int iOff;
+ fts5DataRelease(pIter->pLeaf);
+ pIter->pLeaf = pLast;
+ pIter->iLeafPgno = pgnoLast;
+ fts5LeafHeader(pLast, &iOff, &dummy);
+ iOff += getVarint(&pLast->p[iOff], (u64*)&pIter->iRowid);
+ pIter->iLeafOffset = iOff;
+ }
+
+ fts5SegIterReverseInitPage(p, pIter);
+}
+
+/*
+** Iterator pIter currently points to the first rowid of a doclist within
+** index iIdx. There is a doclist-index associated with the final term on
+** the current page. If the current term is the last term on the page,
+** load the doclist-index from disk and initialize an iterator at
+** (pIter->pDlidx).
+*/
+static void fts5SegIterLoadDlidx(Fts5Index *p, int iIdx, Fts5SegIter *pIter){
+ int iSeg = pIter->pSeg->iSegid;
+ int bRev = (pIter->flags & FTS5_SEGITER_REVERSE);
+ Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */
+
+ assert( pIter->flags & FTS5_SEGITER_ONETERM );
+ assert( pIter->pDlidx==0 );
+
+ /* Check if the current doclist ends on this page. If it does, return
+ ** early without loading the doclist-index (as it belongs to a different
+ ** term. */
+ if( pIter->iTermLeafPgno==pIter->iLeafPgno ){
+ int iOff = pIter->iLeafOffset + pIter->nPos;
+ while( iOff<pLeaf->n ){
+ i64 iDelta;
+
+ /* iOff is currently the offset of the start of position list data */
+ iOff += getVarint(&pLeaf->p[iOff], (u64*)&iDelta);
+ if( iDelta==0 ) return;
+
+ if( iOff<pLeaf->n ){
+ int bDummy;
+ int nPos;
+ iOff += fts5GetPoslistSize(&pLeaf->p[iOff], &nPos, &bDummy);
+ iOff += nPos;
+ }
+ }
+ }
+
+ pIter->pDlidx = fts5DlidxIterInit(p, bRev, iIdx, iSeg, pIter->iTermLeafPgno);
+}
+
+/*
+** Initialize the object pIter to point to term pTerm/nTerm within segment
+** pSeg, index iIdx. If there is no such term in the index, the iterator
+** is set to EOF.
+**
+** If an error occurs, Fts5Index.rc is set to an appropriate error code. If
+** an error has already occurred when this function is called, it is a no-op.
+*/
+static void fts5SegIterSeekInit(
+ Fts5Index *p, /* FTS5 backend */
+ int iIdx, /* Config.aHash[] index of FTS index */
+ const u8 *pTerm, int nTerm, /* Term to seek to */
+ int flags, /* Mask of FTS5INDEX_XXX flags */
+ Fts5StructureSegment *pSeg, /* Description of segment */
+ Fts5SegIter *pIter /* Object to populate */
+){
+ int iPg = 1;
+ int h;
+ int bGe = ((flags & FTS5INDEX_QUERY_PREFIX) && iIdx==0);
+ int bDlidx = 0; /* True if there is a doclist-index */
+
+ assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 );
+ assert( pTerm && nTerm );
+ memset(pIter, 0, sizeof(*pIter));
+ pIter->pSeg = pSeg;
+ pIter->iIdx = iIdx;
+
+ /* This block sets stack variable iPg to the leaf page number that may
+ ** contain term (pTerm/nTerm), if it is present in the segment. */
+ for(h=pSeg->nHeight-1; h>0; h--){
+ Fts5NodeIter node; /* For iterating through internal nodes */
+ i64 iRowid = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, h, iPg);
+ Fts5Data *pNode = fts5DataRead(p, iRowid);
+ if( pNode==0 ) break;
+
+ fts5NodeIterInit(pNode->p, pNode->n, &node);
+ assert( node.term.n==0 );
+
+ iPg = node.iChild;
+ bDlidx = node.bDlidx;
+ for(fts5NodeIterNext(&p->rc, &node);
+ node.aData && fts5BufferCompareBlob(&node.term, pTerm, nTerm)<=0;
+ fts5NodeIterNext(&p->rc, &node)
+ ){
+ iPg = node.iChild;
+ bDlidx = node.bDlidx;
+ }
+ fts5NodeIterFree(&node);
+ fts5DataRelease(pNode);
+ }
+
+ if( iPg<pSeg->pgnoFirst ){
+ iPg = pSeg->pgnoFirst;
+ bDlidx = 0;
+ }
+
+ pIter->iLeafPgno = iPg - 1;
+ fts5SegIterNextPage(p, pIter);
+
+ if( pIter->pLeaf ){
+ int res;
+ pIter->iLeafOffset = fts5GetU16(&pIter->pLeaf->p[2]);
+ fts5SegIterLoadTerm(p, pIter, 0);
+ fts5SegIterLoadNPos(p, pIter);
+ do {
+ res = fts5BufferCompareBlob(&pIter->term, pTerm, nTerm);
+ if( res>=0 ) break;
+ fts5SegIterNext(p, pIter, 0);
+ }while( pIter->pLeaf && p->rc==SQLITE_OK );
+
+ if( bGe==0 && res ){
+ /* Set iterator to point to EOF */
+ fts5DataRelease(pIter->pLeaf);
+ pIter->pLeaf = 0;
+ }
+ }
+
+ if( p->rc==SQLITE_OK && bGe==0 ){
+ pIter->flags |= FTS5_SEGITER_ONETERM;
+ if( pIter->pLeaf ){
+ if( flags & FTS5INDEX_QUERY_DESC ){
+ pIter->flags |= FTS5_SEGITER_REVERSE;
+ }
+ if( bDlidx ){
+ fts5SegIterLoadDlidx(p, iIdx, pIter);
+ }
+ if( flags & FTS5INDEX_QUERY_DESC ){
+ fts5SegIterReverse(p, iIdx, pIter);
+ }
+ }
+ }
+}
+
+/*
+** Initialize the object pIter to point to term pTerm/nTerm within the
+** in-memory hash table iIdx. If there is no such term in the table, the
+** iterator is set to EOF.
+**
+** If an error occurs, Fts5Index.rc is set to an appropriate error code. If
+** an error has already occurred when this function is called, it is a no-op.
+*/
+static void fts5SegIterHashInit(
+ Fts5Index *p, /* FTS5 backend */
+ int iIdx, /* Config.aHash[] index of FTS index */
+ const u8 *pTerm, int nTerm, /* Term to seek to */
+ int flags, /* Mask of FTS5INDEX_XXX flags */
+ Fts5SegIter *pIter /* Object to populate */
+){
+ Fts5Hash *pHash = p->apHash[iIdx];
+ const u8 *pList = 0;
+ int nList = 0;
+ const u8 *z = 0;
+ int n = 0;
+
+ assert( pHash );
+ assert( p->rc==SQLITE_OK );
+
+ if( pTerm==0 || (iIdx==0 && (flags & FTS5INDEX_QUERY_PREFIX)) ){
+ p->rc = sqlite3Fts5HashScanInit(pHash, (const char*)pTerm, nTerm);
+ sqlite3Fts5HashScanEntry(pHash, (const char**)&z, &pList, &nList);
+ n = (z ? strlen((const char*)z) : 0);
+ }else{
+ pIter->flags |= FTS5_SEGITER_ONETERM;
+ sqlite3Fts5HashQuery(pHash, (const char*)pTerm, nTerm, &pList, &nList);
+ z = pTerm;
+ n = nTerm;
+ }
+
+ if( pList ){
+ Fts5Data *pLeaf;
+ sqlite3Fts5BufferSet(&p->rc, &pIter->term, n, z);
+ pLeaf = fts5IdxMalloc(p, sizeof(Fts5Data));
+ if( pLeaf==0 ) return;
+ pLeaf->nRef = 1;
+ pLeaf->p = (u8*)pList;
+ pLeaf->n = nList;
+ pIter->pLeaf = pLeaf;
+ pIter->iLeafOffset = getVarint(pLeaf->p, (u64*)&pIter->iRowid);
+
+ if( flags & FTS5INDEX_QUERY_DESC ){
+ pIter->flags |= FTS5_SEGITER_REVERSE;
+ fts5SegIterReverseInitPage(p, pIter);
+ }else{
+ fts5SegIterLoadNPos(p, pIter);
+ }
+ }
+}
+
+/*
+** Zero the iterator passed as the only argument.
+*/
+static void fts5SegIterClear(Fts5SegIter *pIter){
+ fts5BufferFree(&pIter->term);
+ fts5DataRelease(pIter->pLeaf);
+ fts5DlidxIterFree(pIter->pDlidx);
+ sqlite3_free(pIter->aRowidOffset);
+ memset(pIter, 0, sizeof(Fts5SegIter));
+}
+
+#ifdef SQLITE_DEBUG
+
+/*
+** This function is used as part of the big assert() procedure implemented by
+** fts5AssertMultiIterSetup(). It ensures that the result currently stored
+** in *pRes is the correct result of comparing the current positions of the
+** two iterators.
+*/
+static void fts5AssertComparisonResult(
+ Fts5MultiSegIter *pIter,
+ Fts5SegIter *p1,
+ Fts5SegIter *p2,
+ Fts5CResult *pRes
+){
+ int i1 = p1 - pIter->aSeg;
+ int i2 = p2 - pIter->aSeg;
+
+ if( p1->pLeaf || p2->pLeaf ){
+ if( p1->pLeaf==0 ){
+ assert( pRes->iFirst==i2 );
+ }else if( p2->pLeaf==0 ){
+ assert( pRes->iFirst==i1 );
+ }else{
+ int nMin = MIN(p1->term.n, p2->term.n);
+ int res = memcmp(p1->term.p, p2->term.p, nMin);
+ if( res==0 ) res = p1->term.n - p2->term.n;
+
+ if( res==0 ){
+ assert( pRes->bTermEq==1 );
+ assert( p1->iRowid!=p2->iRowid );
+ res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : 1;
+ }else{
+ assert( pRes->bTermEq==0 );
+ }
+
+ if( res<0 ){
+ assert( pRes->iFirst==i1 );
+ }else{
+ assert( pRes->iFirst==i2 );
+ }
+ }
+ }
+}
+
+/*
+** This function is a no-op unless SQLITE_DEBUG is defined when this module
+** is compiled. In that case, this function is essentially an assert()
+** statement used to verify that the contents of the pIter->aFirst[] array
+** are correct.
+*/
+static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5MultiSegIter *pIter){
+ if( p->rc==SQLITE_OK ){
+ int i;
+ for(i=0; i<pIter->nSeg; i+=2){
+ Fts5SegIter *p1 = &pIter->aSeg[i];
+ Fts5SegIter *p2 = &pIter->aSeg[i+1];
+ Fts5CResult *pRes = &pIter->aFirst[(pIter->nSeg + i) / 2];
+ fts5AssertComparisonResult(pIter, p1, p2, pRes);
+ }
+
+ for(i=1; i<(pIter->nSeg / 2); i+=2){
+ Fts5CResult *pRes = &pIter->aFirst[i];
+ Fts5SegIter *p1 = &pIter->aSeg[ pIter->aFirst[i*2].iFirst ];
+ Fts5SegIter *p2 = &pIter->aSeg[ pIter->aFirst[i*2+1].iFirst ];
+
+ fts5AssertComparisonResult(pIter, p1, p2, pRes);
+ }
+ }
+}
+#else
+# define fts5AssertMultiIterSetup(x,y)
+#endif
+
+/*
+** Do the comparison necessary to populate pIter->aFirst[iOut].
+**
+** If the returned value is non-zero, then it is the index of an entry
+** in the pIter->aSeg[] array that is (a) not at EOF, and (b) pointing
+** to a key that is a duplicate of another, higher priority,
+** segment-iterator in the pSeg->aSeg[] array.
+*/
+static int fts5MultiIterDoCompare(Fts5MultiSegIter *pIter, int iOut){
+ int i1; /* Index of left-hand Fts5SegIter */
+ int i2; /* Index of right-hand Fts5SegIter */
+ int iRes;
+ Fts5SegIter *p1; /* Left-hand Fts5SegIter */
+ Fts5SegIter *p2; /* Right-hand Fts5SegIter */
+ Fts5CResult *pRes = &pIter->aFirst[iOut];
+
+ assert( iOut<pIter->nSeg && iOut>0 );
+ assert( pIter->bRev==0 || pIter->bRev==1 );
+
+ if( iOut>=(pIter->nSeg/2) ){
+ i1 = (iOut - pIter->nSeg/2) * 2;
+ i2 = i1 + 1;
+ }else{
+ i1 = pIter->aFirst[iOut*2].iFirst;
+ i2 = pIter->aFirst[iOut*2+1].iFirst;
+ }
+ p1 = &pIter->aSeg[i1];
+ p2 = &pIter->aSeg[i2];
+
+ pRes->bTermEq = 0;
+ if( p1->pLeaf==0 ){ /* If p1 is at EOF */
+ iRes = i2;
+ }else if( p2->pLeaf==0 ){ /* If p2 is at EOF */
+ iRes = i1;
+ }else{
+ int res = fts5BufferCompare(&p1->term, &p2->term);
+ if( res==0 ){
+ assert( i2>i1 );
+ assert( i2!=0 );
+ pRes->bTermEq = 1;
+ if( p1->iRowid==p2->iRowid ){
+ p1->bDel = p2->bDel;
+ return i2;
+ }
+ res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1;
+ }
+ assert( res!=0 );
+ if( res<0 ){
+ iRes = i1;
+ }else{
+ iRes = i2;
+ }
+ }
+
+ pRes->iFirst = iRes;
+ return 0;
+}
+
+/*
+** Move the seg-iter so that it points to the first rowid on page iLeafPgno.
+** It is an error if leaf iLeafPgno contains no rowid.
+*/
+static void fts5SegIterGotoPage(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5SegIter *pIter, /* Iterator to advance */
+ int iLeafPgno
+){
+ assert( iLeafPgno>pIter->iLeafPgno );
+ if( p->rc==SQLITE_OK ){
+ pIter->iLeafPgno = iLeafPgno-1;
+ fts5SegIterNextPage(p, pIter);
+ assert( p->rc!=SQLITE_OK || pIter->iLeafPgno==iLeafPgno );
+ }
+
+ if( p->rc==SQLITE_OK ){
+ int iOff;
+ u8 *a = pIter->pLeaf->p;
+ int n = pIter->pLeaf->n;
+
+ iOff = fts5GetU16(&a[0]);
+ if( iOff<4 || iOff>=n ){
+ p->rc = FTS5_CORRUPT;
+ }else{
+ iOff += getVarint(&a[iOff], (u64*)&pIter->iRowid);
+ pIter->iLeafOffset = iOff;
+ fts5SegIterLoadNPos(p, pIter);
+ }
+ }
+}
+
+/*
+** Advance the iterator passed as the second argument until it is at or
+** past rowid iFrom. Regardless of the value of iFrom, the iterator is
+** always advanced at least once.
+*/
+static void fts5SegIterNextFrom(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5SegIter *pIter, /* Iterator to advance */
+ i64 iMatch /* Advance iterator at least this far */
+){
+ int bRev = (pIter->flags & FTS5_SEGITER_REVERSE);
+ Fts5DlidxIter *pDlidx = pIter->pDlidx;
+ int iLeafPgno = pIter->iLeafPgno;
+ int bMove = 1;
+
+ assert( pIter->flags & FTS5_SEGITER_ONETERM );
+ assert( pIter->pDlidx );
+ assert( pIter->pLeaf );
+
+ if( bRev==0 ){
+ while( fts5DlidxIterEof(p, pDlidx)==0 && iMatch>pDlidx->iRowid ){
+ iLeafPgno = pDlidx->iLeafPgno;
+ fts5DlidxIterNext(pDlidx);
+ }
+ assert( iLeafPgno>=pIter->iLeafPgno || p->rc );
+ if( iLeafPgno>pIter->iLeafPgno ){
+ fts5SegIterGotoPage(p, pIter, iLeafPgno);
+ bMove = 0;
+ }
+ }else{
+ assert( iMatch<pIter->iRowid );
+ while( fts5DlidxIterEof(p, pDlidx)==0 && iMatch<pDlidx->iRowid ){
+ fts5DlidxIterPrev(pDlidx);
+ }
+ iLeafPgno = pDlidx->iLeafPgno;
+
+ assert( fts5DlidxIterEof(p, pDlidx) || iLeafPgno<=pIter->iLeafPgno );
+
+ if( iLeafPgno<pIter->iLeafPgno ){
+ pIter->iLeafPgno = iLeafPgno+1;
+ fts5SegIterReverseNewPage(p, pIter);
+ bMove = 0;
+ }
+ }
+
+ while( 1 ){
+ if( bMove ) fts5SegIterNext(p, pIter, 0);
+ if( pIter->pLeaf==0 ) break;
+ if( bRev==0 && pIter->iRowid>=iMatch ) break;
+ if( bRev!=0 && pIter->iRowid<=iMatch ) break;
+ bMove = 1;
+ }
+}
+
+
+/*
+** Free the iterator object passed as the second argument.
+*/
+static void fts5MultiIterFree(Fts5Index *p, Fts5MultiSegIter *pIter){
+ if( pIter ){
+ int i;
+ for(i=0; i<pIter->nSeg; i++){
+ fts5SegIterClear(&pIter->aSeg[i]);
+ }
+ sqlite3_free(pIter);
+ }
+}
+
+static void fts5MultiIterAdvanced(
+ Fts5Index *p, /* FTS5 backend to iterate within */
+ Fts5MultiSegIter *pIter, /* Iterator to update aFirst[] array for */
+ int iChanged, /* Index of sub-iterator just advanced */
+ int iMinset /* Minimum entry in aFirst[] to set */
+){
+ int i;
+ for(i=(pIter->nSeg+iChanged)/2; i>=iMinset && p->rc==SQLITE_OK; i=i/2){
+ int iEq;
+ if( (iEq = fts5MultiIterDoCompare(pIter, i)) ){
+ fts5SegIterNext(p, &pIter->aSeg[iEq], 0);
+ i = pIter->nSeg + iEq;
+ }
+ }
+}
+
+static int fts5MultiIterAdvanceRowid(
+ Fts5Index *p, /* FTS5 backend to iterate within */
+ Fts5MultiSegIter *pIter, /* Iterator to update aFirst[] array for */
+ int iChanged /* Index of sub-iterator just advanced */
+){
+ int i;
+ Fts5SegIter *pNew = &pIter->aSeg[iChanged];
+ Fts5SegIter *pOther = &pIter->aSeg[iChanged ^ 0x0001];
+
+ for(i=(pIter->nSeg+iChanged)/2; p->rc==SQLITE_OK; i=i/2){
+ Fts5CResult *pRes = &pIter->aFirst[i];
+
+ assert( pNew->pLeaf );
+ assert( pRes->bTermEq==0 || pOther->pLeaf );
+
+ if( pRes->bTermEq ){
+ if( pNew->iRowid==pOther->iRowid ){
+ return 1;
+ }else if( (pOther->iRowid>pNew->iRowid)==pIter->bRev ){
+ pNew = pOther;
+ }
+ }
+ pRes->iFirst = (pNew - pIter->aSeg);
+ if( i==1 ) break;
+
+ pOther = &pIter->aSeg[ pIter->aFirst[i ^ 0x0001].iFirst ];
+ }
+
+ return 0;
+}
+
+/*
+** Move the iterator to the next entry.
+**
+** If an error occurs, an error code is left in Fts5Index.rc. It is not
+** considered an error if the iterator reaches EOF, or if it is already at
+** EOF when this function is called.
+*/
+static void fts5MultiIterNext(
+ Fts5Index *p,
+ Fts5MultiSegIter *pIter,
+ int bFrom, /* True if argument iFrom is valid */
+ i64 iFrom /* Advance at least as far as this */
+){
+ if( p->rc==SQLITE_OK ){
+ int bUseFrom = bFrom;
+ do {
+ int iFirst = pIter->aFirst[1].iFirst;
+ int bNewTerm = 0;
+ Fts5SegIter *pSeg = &pIter->aSeg[iFirst];
+ if( bUseFrom && pSeg->pDlidx ){
+ fts5SegIterNextFrom(p, pSeg, iFrom);
+ }else{
+ fts5SegIterNext(p, pSeg, &bNewTerm);
+ }
+
+ if( pSeg->pLeaf==0 || bNewTerm
+ || fts5MultiIterAdvanceRowid(p, pIter, iFirst)
+ ){
+ fts5MultiIterAdvanced(p, pIter, iFirst, 1);
+ }
+ fts5AssertMultiIterSetup(p, pIter);
+
+ bUseFrom = 0;
+ }while( pIter->bSkipEmpty && fts5MultiIterIsEmpty(p, pIter) );
+ }
+}
+
+/*
+** Allocate a new Fts5MultiSegIter object.
+**
+** The new object will be used to iterate through data in structure pStruct.
+** If iLevel is -ve, then all data in all segments is merged. Or, if iLevel
+** is zero or greater, data from the first nSegment segments on level iLevel
+** is merged.
+**
+** The iterator initially points to the first term/rowid entry in the
+** iterated data.
+*/
+static void fts5MultiIterNew(
+ Fts5Index *p, /* FTS5 backend to iterate within */
+ Fts5Structure *pStruct, /* Structure of specific index */
+ int iIdx, /* Config.aHash[] index of FTS index */
+ int bSkipEmpty, /* True to ignore delete-keys */
+ int flags, /* FTS5INDEX_QUERY_XXX flags */
+ const u8 *pTerm, int nTerm, /* Term to seek to (or NULL/0) */
+ int iLevel, /* Level to iterate (-1 for all) */
+ int nSegment, /* Number of segments to merge (iLevel>=0) */
+ Fts5MultiSegIter **ppOut /* New object */
+){
+ int nSeg; /* Number of segments merged */
+ int nSlot; /* Power of two >= nSeg */
+ int iIter = 0; /* */
+ int iSeg; /* Used to iterate through segments */
+ Fts5StructureLevel *pLvl;
+ Fts5MultiSegIter *pNew;
+
+ assert( (pTerm==0 && nTerm==0) || iLevel<0 );
+
+ /* Allocate space for the new multi-seg-iterator. */
+ if( iLevel<0 ){
+ nSeg = fts5StructureCountSegments(pStruct);
+ nSeg += (p->apHash ? 1 : 0);
+ }else{
+ nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment);
+ }
+ for(nSlot=2; nSlot<nSeg; nSlot=nSlot*2);
+ *ppOut = pNew = fts5IdxMalloc(p,
+ sizeof(Fts5MultiSegIter) + /* pNew */
+ sizeof(Fts5SegIter) * nSlot + /* pNew->aSeg[] */
+ sizeof(Fts5CResult) * nSlot /* pNew->aFirst[] */
+ );
+ if( pNew==0 ) return;
+ pNew->nSeg = nSlot;
+ pNew->aSeg = (Fts5SegIter*)&pNew[1];
+ pNew->aFirst = (Fts5CResult*)&pNew->aSeg[nSlot];
+ pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC));
+ pNew->bSkipEmpty = bSkipEmpty;
+
+ /* Initialize each of the component segment iterators. */
+ if( iLevel<0 ){
+ Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel];
+ if( p->apHash ){
+ /* Add a segment iterator for the current contents of the hash table. */
+ Fts5SegIter *pIter = &pNew->aSeg[iIter++];
+ fts5SegIterHashInit(p, iIdx, pTerm, nTerm, flags, pIter);
+ }
+ for(pLvl=&pStruct->aLevel[0]; pLvl<pEnd; pLvl++){
+ for(iSeg=pLvl->nSeg-1; iSeg>=0; iSeg--){
+ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
+ Fts5SegIter *pIter = &pNew->aSeg[iIter++];
+ if( pTerm==0 ){
+ fts5SegIterInit(p, iIdx, pSeg, pIter);
+ }else{
+ fts5SegIterSeekInit(p, iIdx, pTerm, nTerm, flags, pSeg, pIter);
+ }
+ }
+ }
+ }else{
+ pLvl = &pStruct->aLevel[iLevel];
+ for(iSeg=nSeg-1; iSeg>=0; iSeg--){
+ fts5SegIterInit(p, iIdx, &pLvl->aSeg[iSeg], &pNew->aSeg[iIter++]);
+ }
+ }
+ assert( iIter==nSeg );
+
+ /* If the above was successful, each component iterators now points
+ ** to the first entry in its segment. In this case initialize the
+ ** aFirst[] array. Or, if an error has occurred, free the iterator
+ ** object and set the output variable to NULL. */
+ if( p->rc==SQLITE_OK ){
+ for(iIter=nSlot-1; iIter>0; iIter--){
+ int iEq;
+ if( (iEq = fts5MultiIterDoCompare(pNew, iIter)) ){
+ fts5SegIterNext(p, &pNew->aSeg[iEq], 0);
+ fts5MultiIterAdvanced(p, pNew, iEq, iIter);
+ }
+ }
+ fts5AssertMultiIterSetup(p, pNew);
+
+ if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){
+ fts5MultiIterNext(p, pNew, 0, 0);
+ }
+ }else{
+ fts5MultiIterFree(p, pNew);
+ *ppOut = 0;
+ }
+}
+
+/*
+** Return true if the iterator is at EOF or if an error has occurred.
+** False otherwise.
+*/
+static int fts5MultiIterEof(Fts5Index *p, Fts5MultiSegIter *pIter){
+ return (p->rc || pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0);
+}
+
+/*
+** Return the rowid of the entry that the iterator currently points
+** to. If the iterator points to EOF when this function is called the
+** results are undefined.
+*/
+static i64 fts5MultiIterRowid(Fts5MultiSegIter *pIter){
+ assert( pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf );
+ return pIter->aSeg[ pIter->aFirst[1].iFirst ].iRowid;
+}
+
+/*
+** Move the iterator to the next entry at or following iMatch.
+*/
+static void fts5MultiIterNextFrom(
+ Fts5Index *p,
+ Fts5MultiSegIter *pIter,
+ i64 iMatch
+){
+ while( 1 ){
+ i64 iRowid;
+ fts5MultiIterNext(p, pIter, 1, iMatch);
+ if( fts5MultiIterEof(p, pIter) ) break;
+ iRowid = fts5MultiIterRowid(pIter);
+ if( pIter->bRev==0 && iRowid>=iMatch ) break;
+ if( pIter->bRev!=0 && iRowid<=iMatch ) break;
+ }
+}
+
+/*
+** Return a pointer to a buffer containing the term associated with the
+** entry that the iterator currently points to.
+*/
+static const u8 *fts5MultiIterTerm(Fts5MultiSegIter *pIter, int *pn){
+ Fts5SegIter *p = &pIter->aSeg[ pIter->aFirst[1].iFirst ];
+ *pn = p->term.n;
+ return p->term.p;
+}
+
+/*
+** Return true if the chunk iterator passed as the second argument is
+** at EOF. Or if an error has already occurred. Otherwise, return false.
+*/
+static int fts5ChunkIterEof(Fts5Index *p, Fts5ChunkIter *pIter){
+ return (p->rc || pIter->pLeaf==0);
+}
+
+/*
+** Advance the chunk-iterator to the next chunk of data to read.
+*/
+static void fts5ChunkIterNext(Fts5Index *p, Fts5ChunkIter *pIter){
+ assert( pIter->nRem>=pIter->n );
+ pIter->nRem -= pIter->n;
+ fts5DataRelease(pIter->pLeaf);
+ pIter->pLeaf = 0;
+ pIter->p = 0;
+ if( pIter->nRem>0 ){
+ Fts5Data *pLeaf;
+ pIter->iLeafRowid++;
+ pLeaf = pIter->pLeaf = fts5DataRead(p, pIter->iLeafRowid);
+ if( pLeaf ){
+ pIter->n = MIN(pIter->nRem, pLeaf->n-4);
+ pIter->p = pLeaf->p+4;
+ }
+ }
+}
+
+/*
+** Intialize the chunk iterator to read the position list data for which
+** the size field is at offset iOff of leaf pLeaf.
+*/
+static void fts5ChunkIterInit(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5SegIter *pSeg, /* Segment iterator to read poslist from */
+ Fts5ChunkIter *pIter /* Initialize this object */
+){
+ Fts5Data *pLeaf = pSeg->pLeaf;
+ int iOff = pSeg->iLeafOffset;
+
+ memset(pIter, 0, sizeof(*pIter));
+ /* If Fts5SegIter.pSeg is NULL, then this iterator iterates through data
+ ** currently stored in a hash table. In this case there is no leaf-rowid
+ ** to calculate. */
+ if( pSeg->pSeg ){
+ int iId = pSeg->pSeg->iSegid;
+ i64 rowid = FTS5_SEGMENT_ROWID(pSeg->iIdx, iId, 0, pSeg->iLeafPgno);
+ pIter->iLeafRowid = rowid;
+ }
+
+ fts5DataReference(pLeaf);
+ pIter->pLeaf = pLeaf;
+ pIter->nRem = pSeg->nPos;
+ pIter->n = MIN(pLeaf->n - iOff, pIter->nRem);
+ pIter->p = pLeaf->p + iOff;
+ if( pIter->n==0 ){
+ fts5ChunkIterNext(p, pIter);
+ }
+}
+
+static void fts5ChunkIterRelease(Fts5ChunkIter *pIter){
+ fts5DataRelease(pIter->pLeaf);
+ pIter->pLeaf = 0;
+}
+
+/*
+** Read and return the next 32-bit varint from the position-list iterator
+** passed as the second argument.
+**
+** If an error occurs, zero is returned an an error code left in
+** Fts5Index.rc. If an error has already occurred when this function is
+** called, it is a no-op.
+*/
+static int fts5PosIterReadVarint(Fts5Index *p, Fts5PosIter *pIter){
+ int iVal = 0;
+ if( p->rc==SQLITE_OK ){
+ if( pIter->iOff>=pIter->chunk.n ){
+ fts5ChunkIterNext(p, &pIter->chunk);
+ if( fts5ChunkIterEof(p, &pIter->chunk) ) return 0;
+ pIter->iOff = 0;
+ }
+ pIter->iOff += fts5GetVarint32(&pIter->chunk.p[pIter->iOff], iVal);
+ }
+ return iVal;
+}
+
+/*
+** Advance the position list iterator to the next entry.
+*/
+static void fts5PosIterNext(Fts5Index *p, Fts5PosIter *pIter){
+ int iVal;
+ assert( fts5ChunkIterEof(p, &pIter->chunk)==0 );
+ iVal = fts5PosIterReadVarint(p, pIter);
+ if( fts5ChunkIterEof(p, &pIter->chunk)==0 ){
+ if( iVal==1 ){
+ pIter->iCol = fts5PosIterReadVarint(p, pIter);
+ pIter->iPos = fts5PosIterReadVarint(p, pIter) - 2;
+ }else{
+ pIter->iPos += (iVal - 2);
+ }
+ }
+}
+
+/*
+** Initialize the Fts5PosIter object passed as the final argument to iterate
+** through the position-list associated with the index entry that iterator
+** pMulti currently points to.
+*/
+static void fts5PosIterInit(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5MultiSegIter *pMulti, /* Multi-seg iterator to read pos-list from */
+ Fts5PosIter *pIter /* Initialize this object */
+){
+ if( p->rc==SQLITE_OK ){
+ Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ];
+ memset(pIter, 0, sizeof(*pIter));
+ fts5ChunkIterInit(p, pSeg, &pIter->chunk);
+ if( fts5ChunkIterEof(p, &pIter->chunk)==0 ){
+ fts5PosIterNext(p, pIter);
+ }
+ }
+}
+
+/*
+** Return true if the position iterator passed as the second argument is
+** at EOF. Or if an error has already occurred. Otherwise, return false.
+*/
+static int fts5PosIterEof(Fts5Index *p, Fts5PosIter *pIter){
+ return (p->rc || pIter->chunk.pLeaf==0);
+}
+
+/*
+** Allocate a new segment-id for the structure pStruct.
+**
+** If an error has already occurred, this function is a no-op. 0 is
+** returned in this case.
+*/
+static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
+ int i;
+ if( p->rc!=SQLITE_OK ) return 0;
+
+ for(i=0; i<100; i++){
+ int iSegid;
+ sqlite3_randomness(sizeof(int), (void*)&iSegid);
+ iSegid = iSegid & ((1 << FTS5_DATA_ID_B)-1);
+ if( iSegid ){
+ int iLvl, iSeg;
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+ for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
+ if( iSegid==pStruct->aLevel[iLvl].aSeg[iSeg].iSegid ){
+ iSegid = 0;
+ }
+ }
+ }
+ }
+ if( iSegid ) return iSegid;
+ }
+
+ p->rc = SQLITE_ERROR;
+ return 0;
+}
+
+/*
+** Discard all data currently cached in the hash-tables.
+*/
+static void fts5IndexDiscardData(Fts5Index *p){
+ assert( p->apHash || p->nPendingData==0 );
+ if( p->apHash ){
+ Fts5Config *pConfig = p->pConfig;
+ int i;
+ for(i=0; i<=pConfig->nPrefix; i++){
+ if( p->apHash[i] ) sqlite3Fts5HashClear(p->apHash[i]);
+ }
+ p->nPendingData = 0;
+ }
+}
+
+/*
+** Return the size of the prefix, in bytes, that buffer (nNew/pNew) shares
+** with buffer (nOld/pOld).
+*/
+static int fts5PrefixCompress(
+ int nOld, const u8 *pOld,
+ int nNew, const u8 *pNew
+){
+ int i;
+ for(i=0; i<nNew && i<nOld; i++){
+ if( pOld[i]!=pNew[i] ) break;
+ }
+ return i;
+}
+
+/*
+** If an "nEmpty" record must be written to the b-tree before the next
+** term, write it now.
+*/
+static void fts5WriteBtreeNEmpty(Fts5Index *p, Fts5SegWriter *pWriter){
+ if( pWriter->nEmpty ){
+ int bFlag = 0;
+ Fts5PageWriter *pPg;
+ pPg = &pWriter->aWriter[1];
+ if( pWriter->nEmpty>=FTS5_MIN_DLIDX_SIZE && pWriter->cdlidx.n ){
+ i64 iKey = FTS5_DOCLIST_IDX_ROWID(
+ pWriter->iIdx, pWriter->iSegid,
+ pWriter->aWriter[0].pgno - 1 - pWriter->nEmpty
+ );
+ assert( pWriter->cdlidx.n>0 );
+ fts5DataWrite(p, iKey, pWriter->cdlidx.p, pWriter->cdlidx.n);
+ bFlag = 1;
+ }
+ fts5BufferAppendVarint(&p->rc, &pPg->buf, bFlag);
+ fts5BufferAppendVarint(&p->rc, &pPg->buf, pWriter->nEmpty);
+ pWriter->nEmpty = 0;
+ }
+
+ /* Whether or not it was written to disk, zero the doclist index at this
+ ** point */
+ sqlite3Fts5BufferZero(&pWriter->cdlidx);
+ pWriter->bDlidxPrevValid = 0;
+}
+
+static void fts5WriteBtreeGrow(Fts5Index *p, Fts5SegWriter *pWriter){
+ if( p->rc==SQLITE_OK ){
+ Fts5PageWriter *aNew;
+ Fts5PageWriter *pNew;
+ int nNew = sizeof(Fts5PageWriter) * (pWriter->nWriter+1);
+
+ aNew = (Fts5PageWriter*)sqlite3_realloc(pWriter->aWriter, nNew);
+ if( aNew==0 ){
+ p->rc = SQLITE_NOMEM;
+ return;
+ }
+
+ pNew = &aNew[pWriter->nWriter];
+ memset(pNew, 0, sizeof(Fts5PageWriter));
+ pNew->pgno = 1;
+ fts5BufferAppendVarint(&p->rc, &pNew->buf, 1);
+
+ pWriter->nWriter++;
+ pWriter->aWriter = aNew;
+ }
+}
+
+/*
+** This is called once for each leaf page except the first that contains
+** at least one term. Argument (nTerm/pTerm) is the split-key - a term that
+** is larger than all terms written to earlier leaves, and equal to or
+** smaller than the first term on the new leaf.
+**
+** If an error occurs, an error code is left in Fts5Index.rc. If an error
+** has already occurred when this function is called, it is a no-op.
+*/
+static void fts5WriteBtreeTerm(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5SegWriter *pWriter, /* Writer object */
+ int nTerm, const u8 *pTerm /* First term on new page */
+){
+ int iHeight;
+ for(iHeight=1; 1; iHeight++){
+ Fts5PageWriter *pPage;
+
+ if( iHeight>=pWriter->nWriter ){
+ fts5WriteBtreeGrow(p, pWriter);
+ if( p->rc ) return;
+ }
+ pPage = &pWriter->aWriter[iHeight];
+
+ fts5WriteBtreeNEmpty(p, pWriter);
+
+ if( pPage->buf.n>=p->pConfig->pgsz ){
+ /* pPage will be written to disk. The term will be written into the
+ ** parent of pPage. */
+ i64 iRowid = FTS5_SEGMENT_ROWID(
+ pWriter->iIdx, pWriter->iSegid, iHeight, pPage->pgno
+ );
+ fts5DataWrite(p, iRowid, pPage->buf.p, pPage->buf.n);
+ fts5BufferZero(&pPage->buf);
+ fts5BufferZero(&pPage->term);
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, pPage[-1].pgno);
+ pPage->pgno++;
+ }else{
+ int nPre = fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm);
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, nPre+2);
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, nTerm-nPre);
+ fts5BufferAppendBlob(&p->rc, &pPage->buf, nTerm-nPre, pTerm+nPre);
+ fts5BufferSet(&p->rc, &pPage->term, nTerm, pTerm);
+ break;
+ }
+ }
+}
+
+static void fts5WriteBtreeNoTerm(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5SegWriter *pWriter /* Writer object */
+){
+ if( pWriter->bFirstRowidInPage ){
+ /* No rowids on this page. Append an 0x00 byte to the current
+ ** doclist-index */
+ if( pWriter->bDlidxPrevValid==0 ){
+ i64 iRowid = pWriter->iPrevRowid;
+ sqlite3Fts5BufferAppendVarint(&p->rc, &pWriter->cdlidx, iRowid);
+ pWriter->bDlidxPrevValid = 1;
+ pWriter->iDlidxPrev = iRowid;
+ }
+ sqlite3Fts5BufferAppendVarint(&p->rc, &pWriter->cdlidx, 0);
+ }
+ pWriter->nEmpty++;
+}
+
+/*
+** Rowid iRowid has just been appended to the current leaf page. As it is
+** the first on its page, append an entry to the current doclist-index.
+*/
+static void fts5WriteDlidxAppend(
+ Fts5Index *p,
+ Fts5SegWriter *pWriter,
+ i64 iRowid
+){
+ i64 iVal;
+ if( pWriter->bDlidxPrevValid ){
+ iVal = iRowid - pWriter->iDlidxPrev;
+ }else{
+ sqlite3Fts5BufferAppendVarint(&p->rc, &pWriter->cdlidx, iRowid);
+ iVal = 1;
+ }
+ sqlite3Fts5BufferAppendVarint(&p->rc, &pWriter->cdlidx, iVal);
+ pWriter->bDlidxPrevValid = 1;
+ pWriter->iDlidxPrev = iRowid;
+}
+
+static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){
+ static const u8 zero[] = { 0x00, 0x00, 0x00, 0x00 };
+ Fts5PageWriter *pPage = &pWriter->aWriter[0];
+ i64 iRowid;
+
+ if( pWriter->bFirstTermInPage ){
+ /* No term was written to this page. */
+ assert( 0==fts5GetU16(&pPage->buf.p[2]) );
+ fts5WriteBtreeNoTerm(p, pWriter);
+ }
+
+ /* Write the current page to the db. */
+ iRowid = FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, 0, pPage->pgno);
+ fts5DataWrite(p, iRowid, pPage->buf.p, pPage->buf.n);
+
+ /* Initialize the next page. */
+ fts5BufferZero(&pPage->buf);
+ fts5BufferAppendBlob(&p->rc, &pPage->buf, 4, zero);
+ pPage->pgno++;
+
+ /* Increase the leaves written counter */
+ pWriter->nLeafWritten++;
+
+ /* The new leaf holds no terms */
+ pWriter->bFirstTermInPage = 1;
+}
+
+/*
+** Append term pTerm/nTerm to the segment being written by the writer passed
+** as the second argument.
+**
+** If an error occurs, set the Fts5Index.rc error code. If an error has
+** already occurred, this function is a no-op.
+*/
+static void fts5WriteAppendTerm(
+ Fts5Index *p,
+ Fts5SegWriter *pWriter,
+ int nTerm, const u8 *pTerm
+){
+ int nPrefix; /* Bytes of prefix compression for term */
+ Fts5PageWriter *pPage = &pWriter->aWriter[0];
+
+ assert( pPage==0 || pPage->buf.n==0 || pPage->buf.n>4 );
+ if( pPage && pPage->buf.n==0 ){
+ /* Zero the first term and first docid fields */
+ static const u8 zero[] = { 0x00, 0x00, 0x00, 0x00 };
+ fts5BufferAppendBlob(&p->rc, &pPage->buf, 4, zero);
+ assert( pWriter->bFirstTermInPage );
+ }
+ if( p->rc ) return;
+
+ if( pWriter->bFirstTermInPage ){
+ /* Update the "first term" field of the page header. */
+ assert( pPage->buf.p[2]==0 && pPage->buf.p[3]==0 );
+ fts5PutU16(&pPage->buf.p[2], pPage->buf.n);
+ nPrefix = 0;
+ if( pPage->pgno!=1 ){
+ /* This is the first term on a leaf that is not the leftmost leaf in
+ ** the segment b-tree. In this case it is necessary to add a term to
+ ** the b-tree hierarchy that is (a) larger than the largest term
+ ** already written to the segment and (b) smaller than or equal to
+ ** this term. In other words, a prefix of (pTerm/nTerm) that is one
+ ** byte longer than the longest prefix (pTerm/nTerm) shares with the
+ ** previous term.
+ **
+ ** Usually, the previous term is available in pPage->term. The exception
+ ** is if this is the first term written in an incremental-merge step.
+ ** In this case the previous term is not available, so just write a
+ ** copy of (pTerm/nTerm) into the parent node. This is slightly
+ ** inefficient, but still correct. */
+ int n = nTerm;
+ if( pPage->term.n ){
+ n = 1 + fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm);
+ }
+ fts5WriteBtreeTerm(p, pWriter, n, pTerm);
+ pPage = &pWriter->aWriter[0];
+ }
+ }else{
+ nPrefix = fts5PrefixCompress(pPage->term.n, pPage->term.p, nTerm, pTerm);
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, nPrefix);
+ }
+
+ /* Append the number of bytes of new data, then the term data itself
+ ** to the page. */
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, nTerm - nPrefix);
+ fts5BufferAppendBlob(&p->rc, &pPage->buf, nTerm - nPrefix, &pTerm[nPrefix]);
+
+ /* Update the Fts5PageWriter.term field. */
+ fts5BufferSet(&p->rc, &pPage->term, nTerm, pTerm);
+ pWriter->bFirstTermInPage = 0;
+
+ pWriter->bFirstRowidInPage = 0;
+ pWriter->bFirstRowidInDoclist = 1;
+
+ /* If the current leaf page is full, flush it to disk. */
+ if( pPage->buf.n>=p->pConfig->pgsz ){
+ fts5WriteFlushLeaf(p, pWriter);
+ pWriter->bFirstRowidInPage = 1;
+ }
+}
+
+/*
+** Append a docid and position-list size field to the writers output.
+*/
+static void fts5WriteAppendRowid(
+ Fts5Index *p,
+ Fts5SegWriter *pWriter,
+ i64 iRowid,
+ int nPos
+){
+ if( p->rc==SQLITE_OK ){
+ Fts5PageWriter *pPage = &pWriter->aWriter[0];
+
+ /* If this is to be the first docid written to the page, set the
+ ** docid-pointer in the page-header. Also append a value to the dlidx
+ ** buffer, in case a doclist-index is required. */
+ if( pWriter->bFirstRowidInPage ){
+ fts5PutU16(pPage->buf.p, pPage->buf.n);
+ fts5WriteDlidxAppend(p, pWriter, iRowid);
+ }
+
+ /* Write the docid. */
+ if( pWriter->bFirstRowidInDoclist || pWriter->bFirstRowidInPage ){
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid);
+ }else{
+ assert( p->rc || iRowid>pWriter->iPrevRowid );
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid - pWriter->iPrevRowid);
+ }
+ pWriter->iPrevRowid = iRowid;
+ pWriter->bFirstRowidInDoclist = 0;
+ pWriter->bFirstRowidInPage = 0;
+
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, nPos);
+
+ if( pPage->buf.n>=p->pConfig->pgsz ){
+ fts5WriteFlushLeaf(p, pWriter);
+ pWriter->bFirstRowidInPage = 1;
+ }
+ }
+}
+
+static void fts5WriteAppendPoslistInt(
+ Fts5Index *p,
+ Fts5SegWriter *pWriter,
+ int iVal
+){
+ if( p->rc==SQLITE_OK ){
+ Fts5PageWriter *pPage = &pWriter->aWriter[0];
+ fts5BufferAppendVarint(&p->rc, &pPage->buf, iVal);
+ if( pPage->buf.n>=p->pConfig->pgsz ){
+ fts5WriteFlushLeaf(p, pWriter);
+ pWriter->bFirstRowidInPage = 1;
+ }
+ }
+}
+
+static void fts5WriteAppendPoslistData(
+ Fts5Index *p,
+ Fts5SegWriter *pWriter,
+ const u8 *aData,
+ int nData
+){
+ Fts5PageWriter *pPage = &pWriter->aWriter[0];
+ const u8 *a = aData;
+ int n = nData;
+
+ assert( p->pConfig->pgsz>0 );
+ while( p->rc==SQLITE_OK && (pPage->buf.n + n)>=p->pConfig->pgsz ){
+ int nReq = p->pConfig->pgsz - pPage->buf.n;
+ int nCopy = 0;
+ while( nCopy<nReq ){
+ i64 dummy;
+ nCopy += getVarint(&a[nCopy], (u64*)&dummy);
+ }
+ fts5BufferAppendBlob(&p->rc, &pPage->buf, nCopy, a);
+ a += nCopy;
+ n -= nCopy;
+ fts5WriteFlushLeaf(p, pWriter);
+ pWriter->bFirstRowidInPage = 1;
+ }
+ if( n>0 ){
+ fts5BufferAppendBlob(&p->rc, &pPage->buf, n, a);
+ }
+}
+
+static void fts5WriteAppendZerobyte(Fts5Index *p, Fts5SegWriter *pWriter){
+ fts5BufferAppendVarint(&p->rc, &pWriter->aWriter[0].buf, 0);
+}
+
+/*
+** Flush any data cached by the writer object to the database. Free any
+** allocations associated with the writer.
+*/
+static void fts5WriteFinish(
+ Fts5Index *p,
+ Fts5SegWriter *pWriter, /* Writer object */
+ int *pnHeight, /* OUT: Height of the b-tree */
+ int *pnLeaf /* OUT: Number of leaf pages in b-tree */
+){
+ int i;
+ if( p->rc==SQLITE_OK ){
+ Fts5PageWriter *pLeaf = &pWriter->aWriter[0];
+ if( pLeaf->pgno==1 && pLeaf->buf.n==0 ){
+ *pnLeaf = 0;
+ *pnHeight = 0;
+ }else{
+ if( pLeaf->buf.n>4 ){
+ fts5WriteFlushLeaf(p, pWriter);
+ }
+ *pnLeaf = pLeaf->pgno-1;
+ if( pWriter->nWriter==1 && pWriter->nEmpty>=FTS5_MIN_DLIDX_SIZE ){
+ fts5WriteBtreeGrow(p, pWriter);
+ }
+ if( pWriter->nWriter>1 ){
+ fts5WriteBtreeNEmpty(p, pWriter);
+ }
+ *pnHeight = pWriter->nWriter;
+
+ for(i=1; i<pWriter->nWriter; i++){
+ Fts5PageWriter *pPg = &pWriter->aWriter[i];
+ fts5DataWrite(p,
+ FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, i, pPg->pgno),
+ pPg->buf.p, pPg->buf.n
+ );
+ }
+ }
+ }
+ for(i=0; i<pWriter->nWriter; i++){
+ Fts5PageWriter *pPg = &pWriter->aWriter[i];
+ assert( pPg || p->rc!=SQLITE_OK );
+ if( pPg ){
+ fts5BufferFree(&pPg->term);
+ fts5BufferFree(&pPg->buf);
+ }
+ }
+ sqlite3_free(pWriter->aWriter);
+ sqlite3Fts5BufferFree(&pWriter->cdlidx);
+}
+
+static void fts5WriteInit(
+ Fts5Index *p,
+ Fts5SegWriter *pWriter,
+ int iIdx, int iSegid
+){
+ memset(pWriter, 0, sizeof(Fts5SegWriter));
+ pWriter->iIdx = iIdx;
+ pWriter->iSegid = iSegid;
+
+ pWriter->aWriter = (Fts5PageWriter*)fts5IdxMalloc(p,sizeof(Fts5PageWriter));
+ if( pWriter->aWriter==0 ) return;
+ pWriter->nWriter = 1;
+ pWriter->aWriter[0].pgno = 1;
+ pWriter->bFirstTermInPage = 1;
+}
+
+static void fts5WriteInitForAppend(
+ Fts5Index *p, /* FTS5 backend object */
+ Fts5SegWriter *pWriter, /* Writer to initialize */
+ int iIdx, /* Index segment is a part of */
+ Fts5StructureSegment *pSeg /* Segment object to append to */
+){
+ int nByte = pSeg->nHeight * sizeof(Fts5PageWriter);
+ memset(pWriter, 0, sizeof(Fts5SegWriter));
+ pWriter->iIdx = iIdx;
+ pWriter->iSegid = pSeg->iSegid;
+ pWriter->aWriter = (Fts5PageWriter*)fts5IdxMalloc(p, nByte);
+ pWriter->nWriter = pSeg->nHeight;
+
+ if( p->rc==SQLITE_OK ){
+ int pgno = 1;
+ int i;
+ pWriter->aWriter[0].pgno = pSeg->pgnoLast+1;
+ for(i=pSeg->nHeight-1; i>0; i--){
+ i64 iRowid = FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, i, pgno);
+ Fts5PageWriter *pPg = &pWriter->aWriter[i];
+ pPg->pgno = pgno;
+ fts5DataBuffer(p, &pPg->buf, iRowid);
+ if( p->rc==SQLITE_OK ){
+ Fts5NodeIter ss;
+ fts5NodeIterInit(pPg->buf.p, pPg->buf.n, &ss);
+ while( ss.aData ) fts5NodeIterNext(&p->rc, &ss);
+ fts5BufferSet(&p->rc, &pPg->term, ss.term.n, ss.term.p);
+ pgno = ss.iChild;
+ fts5NodeIterFree(&ss);
+ }
+ }
+ if( pSeg->nHeight==1 ){
+ pWriter->nEmpty = pSeg->pgnoLast-1;
+ }
+ assert( (pgno+pWriter->nEmpty)==pSeg->pgnoLast );
+ pWriter->bFirstTermInPage = 1;
+ assert( pWriter->aWriter[0].term.n==0 );
+ }
+}
+
+/*
+** Iterator pIter was used to iterate through the input segments of on an
+** incremental merge operation. This function is called if the incremental
+** merge step has finished but the input has not been completely exhausted.
+*/
+static void fts5TrimSegments(Fts5Index *p, Fts5MultiSegIter *pIter){
+ int i;
+ Fts5Buffer buf;
+ memset(&buf, 0, sizeof(Fts5Buffer));
+ for(i=0; i<pIter->nSeg; i++){
+ Fts5SegIter *pSeg = &pIter->aSeg[i];
+ if( pSeg->pSeg==0 ){
+ /* no-op */
+ }else if( pSeg->pLeaf==0 ){
+ /* All keys from this input segment have been transfered to the output.
+ ** Set both the first and last page-numbers to 0 to indicate that the
+ ** segment is now empty. */
+ pSeg->pSeg->pgnoLast = 0;
+ pSeg->pSeg->pgnoFirst = 0;
+ }else{
+ int iOff = pSeg->iTermLeafOffset; /* Offset on new first leaf page */
+ i64 iLeafRowid;
+ Fts5Data *pData;
+ int iId = pSeg->pSeg->iSegid;
+ u8 aHdr[4] = {0x00, 0x00, 0x00, 0x04};
+
+ iLeafRowid = FTS5_SEGMENT_ROWID(pSeg->iIdx, iId, 0, pSeg->iTermLeafPgno);
+ pData = fts5DataRead(p, iLeafRowid);
+ if( pData ){
+ fts5BufferZero(&buf);
+ fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr);
+ fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n);
+ fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p);
+ fts5BufferAppendBlob(&p->rc, &buf, pData->n - iOff, &pData->p[iOff]);
+ fts5DataRelease(pData);
+ pSeg->pSeg->pgnoFirst = pSeg->iTermLeafPgno;
+ fts5DataDelete(p, FTS5_SEGMENT_ROWID(pSeg->iIdx, iId, 0, 1),iLeafRowid);
+ fts5DataWrite(p, iLeafRowid, buf.p, buf.n);
+ }
+ }
+ }
+ fts5BufferFree(&buf);
+}
+
+/*
+**
+*/
+static void fts5IndexMergeLevel(
+ Fts5Index *p, /* FTS5 backend object */
+ int iIdx, /* Index to work on */
+ Fts5Structure **ppStruct, /* IN/OUT: Stucture of index iIdx */
+ int iLvl, /* Level to read input from */
+ int *pnRem /* Write up to this many output leaves */
+){
+ Fts5Structure *pStruct = *ppStruct;
+ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
+ Fts5StructureLevel *pLvlOut;
+ Fts5MultiSegIter *pIter = 0; /* Iterator to read input data */
+ int nRem = pnRem ? *pnRem : 0; /* Output leaf pages left to write */
+ int nInput; /* Number of input segments */
+ Fts5SegWriter writer; /* Writer object */
+ Fts5StructureSegment *pSeg; /* Output segment */
+ Fts5Buffer term;
+ int bRequireDoclistTerm = 0; /* Doclist terminator (0x00) required */
+ int bOldest; /* True if the output segment is the oldest */
+
+ assert( iLvl<pStruct->nLevel );
+ assert( pLvl->nMerge<=pLvl->nSeg );
+
+ memset(&writer, 0, sizeof(Fts5SegWriter));
+ memset(&term, 0, sizeof(Fts5Buffer));
+ writer.iIdx = iIdx;
+ if( pLvl->nMerge ){
+ pLvlOut = &pStruct->aLevel[iLvl+1];
+ assert( pLvlOut->nSeg>0 );
+ nInput = pLvl->nMerge;
+ fts5WriteInitForAppend(p, &writer, iIdx, &pLvlOut->aSeg[pLvlOut->nSeg-1]);
+ pSeg = &pLvlOut->aSeg[pLvlOut->nSeg-1];
+ }else{
+ int iSegid = fts5AllocateSegid(p, pStruct);
+
+ /* Extend the Fts5Structure object as required to ensure the output
+ ** segment exists. */
+ if( iLvl==pStruct->nLevel-1 ){
+ fts5StructureAddLevel(&p->rc, ppStruct);
+ pStruct = *ppStruct;
+ }
+ fts5StructureExtendLevel(&p->rc, pStruct, iLvl+1, 1, 0);
+ if( p->rc ) return;
+ pLvl = &pStruct->aLevel[iLvl];
+ pLvlOut = &pStruct->aLevel[iLvl+1];
+
+ fts5WriteInit(p, &writer, iIdx, iSegid);
+
+ /* Add the new segment to the output level */
+ if( iLvl+1==pStruct->nLevel ) pStruct->nLevel++;
+ pSeg = &pLvlOut->aSeg[pLvlOut->nSeg];
+ pLvlOut->nSeg++;
+ pSeg->pgnoFirst = 1;
+ pSeg->iSegid = iSegid;
+
+ /* Read input from all segments in the input level */
+ nInput = pLvl->nSeg;
+ }
+ bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2);
+
+#if 0
+fprintf(stdout, "merging %d segments from level %d!", nInput, iLvl);
+fflush(stdout);
+#endif
+
+ assert( iLvl>=0 );
+ for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, 0, iLvl, nInput, &pIter);
+ fts5MultiIterEof(p, pIter)==0;
+ fts5MultiIterNext(p, pIter, 0, 0)
+ ){
+ Fts5SegIter *pSeg = &pIter->aSeg[ pIter->aFirst[1].iFirst ];
+ Fts5ChunkIter sPos; /* Used to iterate through position list */
+ int nPos; /* position-list size field value */
+ int nTerm;
+ const u8 *pTerm;
+
+ /* Check for key annihilation. */
+ if( pSeg->nPos==0 && (bOldest || pSeg->bDel==0) ) continue;
+
+ fts5ChunkIterInit(p, pSeg, &sPos);
+
+ pTerm = fts5MultiIterTerm(pIter, &nTerm);
+ if( nTerm!=term.n || memcmp(pTerm, term.p, nTerm) ){
+ if( pnRem && writer.nLeafWritten>nRem ){
+ fts5ChunkIterRelease(&sPos);
+ break;
+ }
+
+ /* This is a new term. Append a term to the output segment. */
+ if( bRequireDoclistTerm ){
+ fts5WriteAppendZerobyte(p, &writer);
+ }
+ fts5WriteAppendTerm(p, &writer, nTerm, pTerm);
+ fts5BufferSet(&p->rc, &term, nTerm, pTerm);
+ bRequireDoclistTerm = 1;
+ }
+
+ /* Append the rowid to the output */
+ /* WRITEPOSLISTSIZE */
+ nPos = pSeg->nPos*2 + pSeg->bDel;
+ fts5WriteAppendRowid(p, &writer, fts5MultiIterRowid(pIter), nPos);
+
+ for(/* noop */; !fts5ChunkIterEof(p, &sPos); fts5ChunkIterNext(p, &sPos)){
+ fts5WriteAppendPoslistData(p, &writer, sPos.p, sPos.n);
+ }
+
+ fts5ChunkIterRelease(&sPos);
+ }
+
+ /* Flush the last leaf page to disk. Set the output segment b-tree height
+ ** and last leaf page number at the same time. */
+ fts5WriteFinish(p, &writer, &pSeg->nHeight, &pSeg->pgnoLast);
+
+ if( fts5MultiIterEof(p, pIter) ){
+ int i;
+
+ /* Remove the redundant segments from the %_data table */
+ for(i=0; i<nInput; i++){
+ fts5DataRemoveSegment(p, iIdx, pLvl->aSeg[i].iSegid);
+ }
+
+ /* Remove the redundant segments from the input level */
+ if( pLvl->nSeg!=nInput ){
+ int nMove = (pLvl->nSeg - nInput) * sizeof(Fts5StructureSegment);
+ memmove(pLvl->aSeg, &pLvl->aSeg[nInput], nMove);
+ }
+ pLvl->nSeg -= nInput;
+ pLvl->nMerge = 0;
+ if( pSeg->pgnoLast==0 ){
+ pLvlOut->nSeg--;
+ }
+ }else{
+ assert( pSeg->nHeight>0 && pSeg->pgnoLast>0 );
+ fts5TrimSegments(p, pIter);
+ pLvl->nMerge = nInput;
+ }
+
+ fts5MultiIterFree(p, pIter);
+ fts5BufferFree(&term);
+ if( pnRem ) *pnRem -= writer.nLeafWritten;
+}
+
+/*
+** A total of nLeaf leaf pages of data has just been flushed to a level-0
+** segments in index iIdx with structure pStruct. This function updates the
+** write-counter accordingly and, if necessary, performs incremental merge
+** work.
+**
+** If an error occurs, set the Fts5Index.rc error code. If an error has
+** already occurred, this function is a no-op.
+*/
+static void fts5IndexWork(
+ Fts5Index *p, /* FTS5 backend object */
+ int iIdx, /* Index to work on */
+ Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */
+ int nLeaf /* Number of output leaves just written */
+){
+ if( p->rc==SQLITE_OK ){
+ Fts5Structure *pStruct = *ppStruct;
+ i64 nWrite; /* Initial value of write-counter */
+ int nWork; /* Number of work-quanta to perform */
+ int nRem; /* Number of leaf pages left to write */
+
+ /* Update the write-counter. While doing so, set nWork. */
+ nWrite = pStruct->nWriteCounter;
+ nWork = ((nWrite + nLeaf) / p->nWorkUnit) - (nWrite / p->nWorkUnit);
+ pStruct->nWriteCounter += nLeaf;
+ nRem = p->nWorkUnit * nWork * pStruct->nLevel;
+
+ while( nRem>0 ){
+ int iLvl; /* To iterate through levels */
+ int iBestLvl = 0; /* Level offering the most input segments */
+ int nBest = 0; /* Number of input segments on best level */
+
+ /* Set iBestLvl to the level to read input segments from. */
+ assert( pStruct->nLevel>0 );
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
+ if( pLvl->nMerge ){
+ if( pLvl->nMerge>nBest ){
+ iBestLvl = iLvl;
+ nBest = pLvl->nMerge;
+ }
+ break;
+ }
+ if( pLvl->nSeg>nBest ){
+ nBest = pLvl->nSeg;
+ iBestLvl = iLvl;
+ }
+ }
+
+ /* If nBest is still 0, then the index must be empty. */
+#ifdef SQLITE_DEBUG
+ for(iLvl=0; nBest==0 && iLvl<pStruct->nLevel; iLvl++){
+ assert( pStruct->aLevel[iLvl].nSeg==0 );
+ }
+#endif
+
+ if( nBest<p->pConfig->nAutomerge
+ && pStruct->aLevel[iBestLvl].nMerge==0
+ ){
+ break;
+ }
+ fts5IndexMergeLevel(p, iIdx, &pStruct, iBestLvl, &nRem);
+ assert( nRem==0 || p->rc==SQLITE_OK );
+ if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
+ fts5StructurePromote(p, iBestLvl+1, pStruct);
+ }
+ *ppStruct = pStruct;
+ }
+
+ }
+}
+
+static void fts5IndexCrisisMerge(
+ Fts5Index *p, /* FTS5 backend object */
+ int iIdx, /* Index to work on */
+ Fts5Structure **ppStruct /* IN/OUT: Current structure of index */
+){
+ Fts5Structure *pStruct = *ppStruct;
+ int iLvl = 0;
+ while( p->rc==SQLITE_OK
+ && iLvl<pStruct->nLevel
+ && pStruct->aLevel[iLvl].nSeg>=p->pConfig->nCrisisMerge
+ ){
+ fts5IndexMergeLevel(p, iIdx, &pStruct, iLvl, 0);
+ fts5StructurePromote(p, iLvl+1, pStruct);
+ iLvl++;
+ }
+ *ppStruct = pStruct;
+}
+
+static int fts5IndexReturn(Fts5Index *p){
+ int rc = p->rc;
+ p->rc = SQLITE_OK;
+ return rc;
+}
+
+typedef struct Fts5FlushCtx Fts5FlushCtx;
+struct Fts5FlushCtx {
+ Fts5Index *pIdx;
+ Fts5SegWriter writer;
+};
+
+/*
+** Buffer aBuf[] contains a list of varints, all small enough to fit
+** in a 32-bit integer. Return the size of the largest prefix of this
+** list nMax bytes or less in size.
+*/
+static int fts5PoslistPrefix(const u8 *aBuf, int nMax){
+ int ret;
+ u32 dummy;
+ ret = fts5GetVarint32(aBuf, dummy);
+ while( 1 ){
+ int i = fts5GetVarint32(&aBuf[ret], dummy);
+ if( (ret + i) > nMax ) break;
+ ret += i;
+ }
+ return ret;
+}
+
+#define fts5BufferSafeAppendBlob(pBuf, pBlob, nBlob) { \
+ assert( pBuf->nSpace>=(pBuf->n+nBlob) ); \
+ memcpy(&pBuf->p[pBuf->n], pBlob, nBlob); \
+ pBuf->n += nBlob; \
+}
+
+/*
+** Flush the contents of in-memory hash table iHash to a new level-0
+** segment on disk. Also update the corresponding structure record.
+**
+** If an error occurs, set the Fts5Index.rc error code. If an error has
+** already occurred, this function is a no-op.
+*/
+static void fts5FlushOneHash(Fts5Index *p, int iHash, int *pnLeaf){
+ Fts5Hash *pHash = p->apHash[iHash];
+ Fts5Structure *pStruct;
+ int iSegid;
+ int pgnoLast = 0; /* Last leaf page number in segment */
+
+ /* Obtain a reference to the index structure and allocate a new segment-id
+ ** for the new level-0 segment. */
+ pStruct = fts5StructureRead(p, iHash);
+ iSegid = fts5AllocateSegid(p, pStruct);
+
+ if( iSegid ){
+ const int pgsz = p->pConfig->pgsz;
+
+ Fts5StructureSegment *pSeg; /* New segment within pStruct */
+ int nHeight; /* Height of new segment b-tree */
+ Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */
+ const u8 *zPrev = 0;
+
+ Fts5SegWriter writer;
+ fts5WriteInit(p, &writer, iHash, iSegid);
+
+ /* Pre-allocate the buffer used to assemble leaf pages to the target
+ ** page size. */
+ assert( pgsz>0 );
+ pBuf = &writer.aWriter[0].buf;
+ fts5BufferGrow(&p->rc, pBuf, pgsz + 20);
+
+ /* Begin scanning through hash table entries. */
+ if( p->rc==SQLITE_OK ){
+ memset(pBuf->p, 0, 4);
+ pBuf->n = 4;
+ p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0);
+ }
+
+ while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){
+ const char *zTerm;
+ int nTerm;
+ const u8 *pDoclist;
+ int nDoclist;
+ int nSuffix; /* Size of term suffix */
+
+ sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist);
+ nTerm = strlen(zTerm);
+
+ /* Decide if the term will fit on the current leaf. If it will not,
+ ** flush the leaf to disk here. */
+ if( (pBuf->n + nTerm + 2) > pgsz ){
+ fts5WriteFlushLeaf(p, &writer);
+ pBuf = &writer.aWriter[0].buf;
+ if( (nTerm + 32) > pBuf->nSpace ){
+ fts5BufferGrow(&p->rc, pBuf, nTerm + 32 - pBuf->n);
+ if( p->rc ) break;
+ }
+ }
+
+ /* Write the term to the leaf. And push it up into the b-tree hierarchy */
+ if( writer.bFirstTermInPage==0 ){
+ int nPre = fts5PrefixCompress(nTerm, zPrev, nTerm, (const u8*)zTerm);
+ pBuf->n += sqlite3PutVarint(&pBuf->p[pBuf->n], nPre);
+ nSuffix = nTerm - nPre;
+ }else{
+ fts5PutU16(&pBuf->p[2], pBuf->n);
+ writer.bFirstTermInPage = 0;
+ if( writer.aWriter[0].pgno!=1 ){
+ int nPre = fts5PrefixCompress(nTerm, zPrev, nTerm, (const u8*)zTerm);
+ fts5WriteBtreeTerm(p, &writer, nPre+1, (const u8*)zTerm);
+ pBuf = &writer.aWriter[0].buf;
+ assert( nPre<nTerm );
+ }
+ nSuffix = nTerm;
+ }
+ pBuf->n += sqlite3PutVarint(&pBuf->p[pBuf->n], nSuffix);
+ fts5BufferSafeAppendBlob(pBuf, (const u8*)&zTerm[nTerm-nSuffix], nSuffix);
+
+ if( pgsz>=(pBuf->n + nDoclist + 1) ){
+ /* The entire doclist will fit on the current leaf. */
+ fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist);
+ }else{
+ i64 iRowid = 0;
+ i64 iDelta = 0;
+ int iOff = 0;
+ int bFirstDocid = 0;
+
+ /* The entire doclist will not fit on this leaf. The following
+ ** loop iterates through the poslists that make up the current
+ ** doclist. */
+ while( iOff<nDoclist ){
+ int nPos;
+ int nCopy;
+ int bDummy;
+ iOff += getVarint(&pDoclist[iOff], (u64*)&iDelta);
+ nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy);
+ nCopy += nPos;
+ iRowid += iDelta;
+
+ if( bFirstDocid ){
+ fts5PutU16(&pBuf->p[0], pBuf->n); /* first docid on page */
+ pBuf->n += sqlite3PutVarint(&pBuf->p[pBuf->n], iRowid);
+ bFirstDocid = 0;
+ fts5WriteDlidxAppend(p, &writer, iRowid);
+ }else{
+ pBuf->n += sqlite3PutVarint(&pBuf->p[pBuf->n], iDelta);
+ }
+ assert( pBuf->n<=pBuf->nSpace );
+
+ if( (pBuf->n + nCopy) <= pgsz ){
+ /* The entire poslist will fit on the current leaf. So copy
+ ** it in one go. */
+ fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy);
+ }else{
+ /* The entire poslist will not fit on this leaf. So it needs
+ ** to be broken into sections. The only qualification being
+ ** that each varint must be stored contiguously. */
+ const u8 *pPoslist = &pDoclist[iOff];
+ int iPos = 0;
+ while( 1 ){
+ int nSpace = pgsz - pBuf->n;
+ int n = 0;
+ if( (nCopy - iPos)<=nSpace ){
+ n = nCopy - iPos;
+ }else{
+ n = fts5PoslistPrefix(&pPoslist[iPos], nSpace);
+ assert( n>=nSpace );
+ }
+ assert( n>0 );
+ fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n);
+ iPos += n;
+ if( pBuf->n>=pgsz ){
+ fts5WriteFlushLeaf(p, &writer);
+ pBuf = &writer.aWriter[0].buf;
+ }
+ if( iPos>=nCopy ) break;
+ }
+ bFirstDocid = 1;
+ }
+ iOff += nCopy;
+ }
+ }
+
+ pBuf->p[pBuf->n++] = '\0';
+ assert( pBuf->n<=pBuf->nSpace );
+ zPrev = (const u8*)zTerm;
+ sqlite3Fts5HashScanNext(pHash);
+ }
+ sqlite3Fts5HashClear(pHash);
+ fts5WriteFinish(p, &writer, &nHeight, &pgnoLast);
+
+ /* Update the Fts5Structure. It is written back to the database by the
+ ** fts5StructureRelease() call below. */
+ if( pStruct->nLevel==0 ){
+ fts5StructureAddLevel(&p->rc, &pStruct);
+ }
+ fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0);
+ if( p->rc==SQLITE_OK ){
+ pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ];
+ pSeg->iSegid = iSegid;
+ pSeg->nHeight = nHeight;
+ pSeg->pgnoFirst = 1;
+ pSeg->pgnoLast = pgnoLast;
+ }
+ fts5StructurePromote(p, 0, pStruct);
+ }
+
+
+ if( p->pConfig->nAutomerge>0 ) fts5IndexWork(p, iHash, &pStruct, pgnoLast);
+ fts5IndexCrisisMerge(p, iHash, &pStruct);
+ fts5StructureWrite(p, iHash, pStruct);
+ fts5StructureRelease(pStruct);
+}
+
+/*
+** Flush any data stored in the in-memory hash tables to the database.
+*/
+static void fts5IndexFlush(Fts5Index *p){
+ Fts5Config *pConfig = p->pConfig;
+ int i; /* Used to iterate through indexes */
+ int nLeaf = 0; /* Number of leaves written */
+
+ /* If an error has already occured this call is a no-op. */
+ if( p->rc!=SQLITE_OK || p->nPendingData==0 ) return;
+ assert( p->apHash );
+
+ /* Flush the terms and each prefix index to disk */
+ for(i=0; i<=pConfig->nPrefix; i++){
+ fts5FlushOneHash(p, i, &nLeaf);
+ }
+ p->nPendingData = 0;
+}
+
+
+int sqlite3Fts5IndexOptimize(Fts5Index *p){
+ Fts5Config *pConfig = p->pConfig;
+ int i;
+
+ fts5IndexFlush(p);
+ for(i=0; i<=pConfig->nPrefix; i++){
+ Fts5Structure *pStruct = fts5StructureRead(p, i);
+ Fts5Structure *pNew = 0;
+ int nSeg = 0;
+ if( pStruct ){
+ nSeg = fts5StructureCountSegments(pStruct);
+ if( nSeg>1 ){
+ int nByte = sizeof(Fts5Structure);
+ nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel);
+ pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte);
+ }
+ }
+ if( pNew ){
+ Fts5StructureLevel *pLvl;
+ int nByte = nSeg * sizeof(Fts5StructureSegment);
+ pNew->nLevel = pStruct->nLevel+1;
+ pNew->nWriteCounter = pStruct->nWriteCounter;
+ pLvl = &pNew->aLevel[pStruct->nLevel];
+ pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte);
+ if( pLvl->aSeg ){
+ int iLvl, iSeg;
+ int iSegOut = 0;
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+ for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
+ pLvl->aSeg[iSegOut] = pStruct->aLevel[iLvl].aSeg[iSeg];
+ iSegOut++;
+ }
+ }
+ pLvl->nSeg = nSeg;
+ }else{
+ sqlite3_free(pNew);
+ pNew = 0;
+ }
+ }
+
+ if( pNew ){
+ int iLvl = pNew->nLevel-1;
+ while( p->rc==SQLITE_OK && pNew->aLevel[iLvl].nSeg>0 ){
+ int nRem = FTS5_OPT_WORK_UNIT;
+ fts5IndexMergeLevel(p, i, &pNew, iLvl, &nRem);
+ }
+
+ fts5StructureWrite(p, i, pNew);
+ fts5StructureRelease(pNew);
+ }
+
+ fts5StructureRelease(pStruct);
+ }
+
+ return fts5IndexReturn(p);
+}
+
+
+
+/*
+** Return a simple checksum value based on the arguments.
+*/
+static u64 fts5IndexEntryCksum(
+ i64 iRowid,
+ int iCol,
+ int iPos,
+ const char *pTerm,
+ int nTerm
+){
+ int i;
+ u64 ret = iRowid;
+ ret += (ret<<3) + iCol;
+ ret += (ret<<3) + iPos;
+ for(i=0; i<nTerm; i++) ret += (ret<<3) + pTerm[i];
+ return ret;
+}
+
+static void fts5BtreeIterInit(
+ Fts5Index *p,
+ int iIdx,
+ Fts5StructureSegment *pSeg,
+ Fts5BtreeIter *pIter
+){
+ int nByte;
+ int i;
+ nByte = sizeof(pIter->aLvl[0]) * (pSeg->nHeight-1);
+ memset(pIter, 0, sizeof(*pIter));
+ if( nByte ){
+ pIter->aLvl = (Fts5BtreeIterLevel*)fts5IdxMalloc(p, nByte);
+ }
+ if( p->rc==SQLITE_OK ){
+ pIter->nLvl = pSeg->nHeight-1;
+ pIter->iIdx = iIdx;
+ pIter->p = p;
+ pIter->pSeg = pSeg;
+ }
+ for(i=0; p->rc==SQLITE_OK && i<pIter->nLvl; i++){
+ i64 iRowid = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, i+1, 1);
+ Fts5Data *pData;
+ pIter->aLvl[i].pData = pData = fts5DataRead(p, iRowid);
+ if( pData ){
+ fts5NodeIterInit(pData->p, pData->n, &pIter->aLvl[i].s);
+ }
+ }
+
+ if( pIter->nLvl==0 || p->rc ){
+ pIter->bEof = 1;
+ pIter->iLeaf = pSeg->pgnoLast;
+ }else{
+ pIter->nEmpty = pIter->aLvl[0].s.nEmpty;
+ pIter->iLeaf = pIter->aLvl[0].s.iChild;
+ pIter->bDlidx = pIter->aLvl[0].s.bDlidx;
+ }
+}
+
+static void fts5BtreeIterNext(Fts5BtreeIter *pIter){
+ Fts5Index *p = pIter->p;
+ int i;
+
+ assert( pIter->bEof==0 && pIter->aLvl[0].s.aData );
+ for(i=0; i<pIter->nLvl && p->rc==SQLITE_OK; i++){
+ Fts5BtreeIterLevel *pLvl = &pIter->aLvl[i];
+ fts5NodeIterNext(&p->rc, &pLvl->s);
+ if( pLvl->s.aData ){
+ fts5BufferSet(&p->rc, &pIter->term, pLvl->s.term.n, pLvl->s.term.p);
+ break;
+ }else{
+ fts5NodeIterFree(&pLvl->s);
+ fts5DataRelease(pLvl->pData);
+ pLvl->pData = 0;
+ }
+ }
+ if( i==pIter->nLvl || p->rc ){
+ pIter->bEof = 1;
+ }else{
+ int iSegid = pIter->pSeg->iSegid;
+ for(i--; i>=0; i--){
+ Fts5BtreeIterLevel *pLvl = &pIter->aLvl[i];
+ i64 iRowid = FTS5_SEGMENT_ROWID(pIter->iIdx,iSegid,i+1,pLvl[1].s.iChild);
+ pLvl->pData = fts5DataRead(p, iRowid);
+ if( pLvl->pData ){
+ fts5NodeIterInit(pLvl->pData->p, pLvl->pData->n, &pLvl->s);
+ }
+ }
+ }
+
+ pIter->nEmpty = pIter->aLvl[0].s.nEmpty;
+ pIter->bDlidx = pIter->aLvl[0].s.bDlidx;
+ pIter->iLeaf = pIter->aLvl[0].s.iChild;
+ assert( p->rc==SQLITE_OK || pIter->bEof );
+}
+
+static void fts5BtreeIterFree(Fts5BtreeIter *pIter){
+ int i;
+ for(i=0; i<pIter->nLvl; i++){
+ Fts5BtreeIterLevel *pLvl = &pIter->aLvl[i];
+ fts5NodeIterFree(&pLvl->s);
+ if( pLvl->pData ){
+ fts5DataRelease(pLvl->pData);
+ pLvl->pData = 0;
+ }
+ }
+ sqlite3_free(pIter->aLvl);
+ fts5BufferFree(&pIter->term);
+}
+
+/*
+** This function is purely an internal test. It does not contribute to
+** FTS functionality, or even the integrity-check, in any way.
+**
+** Instead, it tests that the same set of pgno/rowid combinations are
+** visited regardless of whether the doclist-index identified by parameters
+** iIdx/iSegid/iLeaf is iterated in forwards or reverse order.
+*/
+#ifdef SQLITE_DEBUG
+static void fts5DlidxIterTestReverse(
+ Fts5Index *p,
+ int iIdx, /* Index to load doclist-index from */
+ int iSegid, /* Segment id to load from */
+ int iLeaf /* Load doclist-index for this leaf */
+){
+ Fts5DlidxIter *pDlidx = 0;
+ i64 cksum1 = 13;
+ i64 cksum2 = 13;
+
+ for(pDlidx=fts5DlidxIterInit(p, 0, iIdx, iSegid, iLeaf);
+ fts5DlidxIterEof(p, pDlidx)==0;
+ fts5DlidxIterNext(pDlidx)
+ ){
+ assert( pDlidx->iLeafPgno>iLeaf );
+ cksum1 = (cksum1 ^ ( (i64)(pDlidx->iLeafPgno) << 32 ));
+ cksum1 = (cksum1 ^ pDlidx->iRowid);
+ }
+ fts5DlidxIterFree(pDlidx);
+ pDlidx = 0;
+
+ for(pDlidx=fts5DlidxIterInit(p, 1, iIdx, iSegid, iLeaf);
+ fts5DlidxIterEof(p, pDlidx)==0;
+ fts5DlidxIterPrev(pDlidx)
+ ){
+ assert( pDlidx->iLeafPgno>iLeaf );
+ cksum2 = (cksum2 ^ ( (i64)(pDlidx->iLeafPgno) << 32 ));
+ cksum2 = (cksum2 ^ pDlidx->iRowid);
+ }
+ fts5DlidxIterFree(pDlidx);
+ pDlidx = 0;
+
+ if( p->rc==SQLITE_OK && cksum1!=cksum2 ) p->rc = FTS5_CORRUPT;
+}
+#else
+# define fts5DlidxIterTestReverse(w,x,y,z)
+#endif
+
+static void fts5IndexIntegrityCheckSegment(
+ Fts5Index *p, /* FTS5 backend object */
+ int iIdx, /* Index that pSeg is a part of */
+ Fts5StructureSegment *pSeg /* Segment to check internal consistency */
+){
+ Fts5BtreeIter iter; /* Used to iterate through b-tree hierarchy */
+
+ /* Iterate through the b-tree hierarchy. */
+ for(fts5BtreeIterInit(p, iIdx, pSeg, &iter);
+ iter.bEof==0;
+ fts5BtreeIterNext(&iter)
+ ){
+ i64 iRow; /* Rowid for this leaf */
+ Fts5Data *pLeaf; /* Data for this leaf */
+ int iOff; /* Offset of first term on leaf */
+ int i; /* Used to iterate through empty leaves */
+
+ /* If the leaf in question has already been trimmed from the segment,
+ ** ignore this b-tree entry. Otherwise, load it into memory. */
+ if( iter.iLeaf<pSeg->pgnoFirst ) continue;
+ iRow = FTS5_SEGMENT_ROWID(iIdx, pSeg->iSegid, 0, iter.iLeaf);
+ pLeaf = fts5DataRead(p, iRow);
+ if( pLeaf==0 ) break;
+
+ /* Check that the leaf contains at least one term, and that it is equal
+ ** to or larger than the split-key in iter.term. */
+ iOff = fts5GetU16(&pLeaf->p[2]);
+ if( iOff==0 ){
+ p->rc = FTS5_CORRUPT;
+ }else{
+ int nTerm; /* Size of term on leaf in bytes */
+ int res; /* Comparison of term and split-key */
+ iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm);
+ res = memcmp(&pLeaf->p[iOff], iter.term.p, MIN(nTerm, iter.term.n));
+ if( res==0 ) res = nTerm - iter.term.n;
+ if( res<0 ){
+ p->rc = FTS5_CORRUPT;
+ }
+ }
+ fts5DataRelease(pLeaf);
+ if( p->rc ) break;
+
+ /* Now check that the iter.nEmpty leaves following the current leaf
+ ** (a) exist and (b) contain no terms. */
+ for(i=1; p->rc==SQLITE_OK && i<=iter.nEmpty; i++){
+ pLeaf = fts5DataRead(p, iRow+i);
+ if( pLeaf && 0!=fts5GetU16(&pLeaf->p[2]) ){
+ p->rc = FTS5_CORRUPT;
+ }
+ fts5DataRelease(pLeaf);
+ }
+
+ /* If there is a doclist-index, check that it looks right. */
+ if( iter.bDlidx ){
+ Fts5DlidxIter *pDlidx = 0; /* For iterating through doclist index */
+ int iPrevLeaf = iter.iLeaf;
+ int iSegid = pSeg->iSegid;
+ int iPg;
+ i64 iKey;
+
+ for(pDlidx=fts5DlidxIterInit(p, 0, iIdx, iSegid, iter.iLeaf);
+ fts5DlidxIterEof(p, pDlidx)==0;
+ fts5DlidxIterNext(pDlidx)
+ ){
+
+ /* Check any rowid-less pages that occur before the current leaf. */
+ for(iPg=iPrevLeaf+1; iPg<pDlidx->iLeafPgno; iPg++){
+ iKey = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, iPg);
+ pLeaf = fts5DataRead(p, iKey);
+ if( pLeaf ){
+ if( fts5GetU16(&pLeaf->p[0])!=0 ) p->rc = FTS5_CORRUPT;
+ fts5DataRelease(pLeaf);
+ }
+ }
+ iPrevLeaf = pDlidx->iLeafPgno;
+
+ /* Check that the leaf page indicated by the iterator really does
+ ** contain the rowid suggested by the same. */
+ iKey = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, pDlidx->iLeafPgno);
+ pLeaf = fts5DataRead(p, iKey);
+ if( pLeaf ){
+ i64 iRowid;
+ int iRowidOff = fts5GetU16(&pLeaf->p[0]);
+ getVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid);
+ if( iRowid!=pDlidx->iRowid ) p->rc = FTS5_CORRUPT;
+ fts5DataRelease(pLeaf);
+ }
+
+ }
+
+ for(iPg=iPrevLeaf+1; iPg<=(iter.iLeaf + iter.nEmpty); iPg++){
+ iKey = FTS5_SEGMENT_ROWID(iIdx, iSegid, 0, iPg);
+ pLeaf = fts5DataRead(p, iKey);
+ if( pLeaf ){
+ if( fts5GetU16(&pLeaf->p[0])!=0 ) p->rc = FTS5_CORRUPT;
+ fts5DataRelease(pLeaf);
+ }
+ }
+
+ fts5DlidxIterFree(pDlidx);
+ fts5DlidxIterTestReverse(p, iIdx, iSegid, iter.iLeaf);
+ }
+ }
+
+ /* Either iter.iLeaf must be the rightmost leaf-page in the segment, or
+ ** else the segment has been completely emptied by an ongoing merge
+ ** operation. */
+ if( p->rc==SQLITE_OK
+ && iter.iLeaf!=pSeg->pgnoLast
+ && (pSeg->pgnoFirst || pSeg->pgnoLast)
+ ){
+ p->rc = FTS5_CORRUPT;
+ }
+
+ fts5BtreeIterFree(&iter);
+}
+
+/*
+** Iterator pMulti currently points to a valid entry (not EOF). This
+** function appends a copy of the position-list of the entry pMulti
+** currently points to to buffer pBuf.
+**
+** If an error occurs, an error code is left in p->rc. It is assumed
+** no error has already occurred when this function is called.
+*/
+static void fts5MultiIterPoslist(
+ Fts5Index *p,
+ Fts5MultiSegIter *pMulti,
+ int bSz,
+ Fts5Buffer *pBuf
+){
+ if( p->rc==SQLITE_OK ){
+ Fts5ChunkIter iter;
+ Fts5SegIter *pSeg = &pMulti->aSeg[ pMulti->aFirst[1].iFirst ];
+ assert( fts5MultiIterEof(p, pMulti)==0 );
+ fts5ChunkIterInit(p, pSeg, &iter);
+ if( fts5ChunkIterEof(p, &iter)==0 ){
+ if( bSz ){
+ /* WRITEPOSLISTSIZE */
+ fts5BufferAppendVarint(&p->rc, pBuf, iter.nRem * 2);
+ }
+ while( fts5ChunkIterEof(p, &iter)==0 ){
+ fts5BufferAppendBlob(&p->rc, pBuf, iter.n, iter.p);
+ fts5ChunkIterNext(p, &iter);
+ }
+ }
+ fts5ChunkIterRelease(&iter);
+ }
+}
+
+static void fts5DoclistIterNext(Fts5DoclistIter *pIter){
+ if( pIter->i<pIter->n ){
+ int bDummy;
+ if( pIter->i ){
+ i64 iDelta;
+ pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&iDelta);
+ if( pIter->bDesc ){
+ pIter->iRowid -= iDelta;
+ }else{
+ pIter->iRowid += iDelta;
+ }
+ }else{
+ pIter->i += getVarint(&pIter->a[pIter->i], (u64*)&pIter->iRowid);
+ }
+ pIter->i += fts5GetPoslistSize(
+ &pIter->a[pIter->i], &pIter->nPoslist, &bDummy
+ );
+ pIter->aPoslist = &pIter->a[pIter->i];
+ pIter->i += pIter->nPoslist;
+ }else{
+ pIter->aPoslist = 0;
+ }
+}
+
+static void fts5DoclistIterInit(
+ Fts5Buffer *pBuf,
+ int bDesc,
+ Fts5DoclistIter *pIter
+){
+ memset(pIter, 0, sizeof(*pIter));
+ pIter->a = pBuf->p;
+ pIter->n = pBuf->n;
+ pIter->bDesc = bDesc;
+ fts5DoclistIterNext(pIter);
+}
+
+/*
+** Append a doclist to buffer pBuf.
+*/
+static void fts5MergeAppendDocid(
+ int *pRc, /* IN/OUT: Error code */
+ int bDesc,
+ Fts5Buffer *pBuf, /* Buffer to write to */
+ i64 *piLastRowid, /* IN/OUT: Previous rowid written (if any) */
+ i64 iRowid /* Rowid to append */
+){
+ if( pBuf->n==0 ){
+ fts5BufferAppendVarint(pRc, pBuf, iRowid);
+ }else if( bDesc ){
+ fts5BufferAppendVarint(pRc, pBuf, *piLastRowid - iRowid);
+ }else{
+ fts5BufferAppendVarint(pRc, pBuf, iRowid - *piLastRowid);
+ }
+ *piLastRowid = iRowid;
+}
+
+/*
+** Buffers p1 and p2 contain doclists. This function merges the content
+** of the two doclists together and sets buffer p1 to the result before
+** returning.
+**
+** If an error occurs, an error code is left in p->rc. If an error has
+** already occurred, this function is a no-op.
+*/
+static void fts5MergePrefixLists(
+ Fts5Index *p, /* FTS5 backend object */
+ int bDesc,
+ Fts5Buffer *p1, /* First list to merge */
+ Fts5Buffer *p2 /* Second list to merge */
+){
+ if( p2->n ){
+ i64 iLastRowid = 0;
+ Fts5DoclistIter i1;
+ Fts5DoclistIter i2;
+ Fts5Buffer out;
+ Fts5Buffer tmp;
+ memset(&out, 0, sizeof(out));
+ memset(&tmp, 0, sizeof(tmp));
+
+ fts5DoclistIterInit(p1, bDesc, &i1);
+ fts5DoclistIterInit(p2, bDesc, &i2);
+ while( p->rc==SQLITE_OK && (i1.aPoslist!=0 || i2.aPoslist!=0) ){
+ if( i2.aPoslist==0 || (i1.aPoslist &&
+ ( (bDesc && i1.iRowid>i2.iRowid) || (!bDesc && i1.iRowid<i2.iRowid) )
+ )){
+ /* Copy entry from i1 */
+ fts5MergeAppendDocid(&p->rc, bDesc, &out, &iLastRowid, i1.iRowid);
+ /* WRITEPOSLISTSIZE */
+ fts5BufferAppendVarint(&p->rc, &out, i1.nPoslist * 2);
+ fts5BufferAppendBlob(&p->rc, &out, i1.nPoslist, i1.aPoslist);
+ fts5DoclistIterNext(&i1);
+ }
+ else if( i1.aPoslist==0 || i2.iRowid!=i1.iRowid ){
+ /* Copy entry from i2 */
+ fts5MergeAppendDocid(&p->rc, bDesc, &out, &iLastRowid, i2.iRowid);
+ /* WRITEPOSLISTSIZE */
+ fts5BufferAppendVarint(&p->rc, &out, i2.nPoslist * 2);
+ fts5BufferAppendBlob(&p->rc, &out, i2.nPoslist, i2.aPoslist);
+ fts5DoclistIterNext(&i2);
+ }
+ else{
+ Fts5PoslistReader r1;
+ Fts5PoslistReader r2;
+ Fts5PoslistWriter writer;
+
+ memset(&writer, 0, sizeof(writer));
+
+ /* Merge the two position lists. */
+ fts5MergeAppendDocid(&p->rc, bDesc, &out, &iLastRowid, i2.iRowid);
+ fts5BufferZero(&tmp);
+ sqlite3Fts5PoslistReaderInit(-1, i1.aPoslist, i1.nPoslist, &r1);
+ sqlite3Fts5PoslistReaderInit(-1, i2.aPoslist, i2.nPoslist, &r2);
+ while( p->rc==SQLITE_OK && (r1.bEof==0 || r2.bEof==0) ){
+ i64 iNew;
+ if( r2.bEof || (r1.bEof==0 && r1.iPos<r2.iPos) ){
+ iNew = r1.iPos;
+ sqlite3Fts5PoslistReaderNext(&r1);
+ }else{
+ iNew = r2.iPos;
+ sqlite3Fts5PoslistReaderNext(&r2);
+ if( r1.iPos==r2.iPos ) sqlite3Fts5PoslistReaderNext(&r1);
+ }
+ p->rc = sqlite3Fts5PoslistWriterAppend(&tmp, &writer, iNew);
+ }
+
+ /* WRITEPOSLISTSIZE */
+ fts5BufferAppendVarint(&p->rc, &out, tmp.n * 2);
+ fts5BufferAppendBlob(&p->rc, &out, tmp.n, tmp.p);
+ fts5DoclistIterNext(&i1);
+ fts5DoclistIterNext(&i2);
+ }
+ }
+
+ fts5BufferSet(&p->rc, p1, out.n, out.p);
+ fts5BufferFree(&tmp);
+ fts5BufferFree(&out);
+ }
+}
+
+static void fts5BufferSwap(Fts5Buffer *p1, Fts5Buffer *p2){
+ Fts5Buffer tmp = *p1;
+ *p1 = *p2;
+ *p2 = tmp;
+}
+
+static void fts5SetupPrefixIter(
+ Fts5Index *p, /* Index to read from */
+ int bDesc, /* True for "ORDER BY rowid DESC" */
+ const u8 *pToken, /* Buffer containing prefix to match */
+ int nToken, /* Size of buffer pToken in bytes */
+ Fts5IndexIter *pIter /* Populate this object */
+){
+ Fts5Structure *pStruct;
+ Fts5Buffer *aBuf;
+ const int nBuf = 32;
+
+ aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf);
+ pStruct = fts5StructureRead(p, 0);
+
+ if( aBuf && pStruct ){
+ Fts5DoclistIter *pDoclist;
+ int i;
+ i64 iLastRowid = 0;
+ Fts5MultiSegIter *p1 = 0; /* Iterator used to gather data from index */
+ Fts5Buffer doclist;
+
+ memset(&doclist, 0, sizeof(doclist));
+ for(fts5MultiIterNew(p, pStruct, 0, 1, 1, pToken, nToken, -1, 0, &p1);
+ fts5MultiIterEof(p, p1)==0;
+ fts5MultiIterNext(p, p1, 0, 0)
+ ){
+ i64 iRowid = fts5MultiIterRowid(p1);
+ int nTerm;
+ const u8 *pTerm = fts5MultiIterTerm(p1, &nTerm);
+ assert( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 );
+ if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break;
+
+ if( doclist.n>0
+ && ((!bDesc && iRowid<=iLastRowid) || (bDesc && iRowid>=iLastRowid))
+ ){
+
+ for(i=0; p->rc==SQLITE_OK && doclist.n; i++){
+ assert( i<nBuf );
+ if( aBuf[i].n==0 ){
+ fts5BufferSwap(&doclist, &aBuf[i]);
+ fts5BufferZero(&doclist);
+ }else{
+ fts5MergePrefixLists(p, bDesc, &doclist, &aBuf[i]);
+ fts5BufferZero(&aBuf[i]);
+ }
+ }
+ }
+ if( doclist.n==0 ){
+ fts5BufferAppendVarint(&p->rc, &doclist, iRowid);
+ }else if( bDesc ){
+ fts5BufferAppendVarint(&p->rc, &doclist, iLastRowid - iRowid);
+ }else{
+ fts5BufferAppendVarint(&p->rc, &doclist, iRowid - iLastRowid);
+ }
+ iLastRowid = iRowid;
+ fts5MultiIterPoslist(p, p1, 1, &doclist);
+ }
+
+ for(i=0; i<nBuf; i++){
+ fts5MergePrefixLists(p, bDesc, &doclist, &aBuf[i]);
+ fts5BufferFree(&aBuf[i]);
+ }
+ fts5MultiIterFree(p, p1);
+
+ pDoclist = (Fts5DoclistIter*)fts5IdxMalloc(p, sizeof(Fts5DoclistIter));
+ if( !pDoclist ){
+ fts5BufferFree(&doclist);
+ }else{
+ pIter->pDoclist = pDoclist;
+ fts5DoclistIterInit(&doclist, bDesc, pIter->pDoclist);
+ }
+ }
+
+ fts5StructureRelease(pStruct);
+ sqlite3_free(aBuf);
+}
+
+static int fts5QueryCksum(
+ Fts5Index *p,
+ const char *z,
+ int n,
+ int flags,
+ u64 *pCksum
+){
+ u64 cksum = *pCksum;
+ Fts5IndexIter *pIdxIter = 0;
+ int rc = sqlite3Fts5IndexQuery(p, z, n, flags, &pIdxIter);
+
+ while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIdxIter) ){
+ const u8 *pPos;
+ int nPos;
+ i64 rowid = sqlite3Fts5IterRowid(pIdxIter);
+ rc = sqlite3Fts5IterPoslist(pIdxIter, &pPos, &nPos);
+ if( rc==SQLITE_OK ){
+ Fts5PoslistReader sReader;
+ for(sqlite3Fts5PoslistReaderInit(-1, pPos, nPos, &sReader);
+ sReader.bEof==0;
+ sqlite3Fts5PoslistReaderNext(&sReader)
+ ){
+ int iCol = FTS5_POS2COLUMN(sReader.iPos);
+ int iOff = FTS5_POS2OFFSET(sReader.iPos);
+ cksum ^= fts5IndexEntryCksum(rowid, iCol, iOff, z, n);
+ }
+ rc = sqlite3Fts5IterNext(pIdxIter);
+ }
+ }
+ sqlite3Fts5IterClose(pIdxIter);
+
+ *pCksum = cksum;
+ return rc;
+}
+
+/*
+** Run internal checks to ensure that the FTS index (a) is internally
+** consistent and (b) contains entries for which the XOR of the checksums
+** as calculated by fts5IndexEntryCksum() is cksum.
+**
+** Return SQLITE_CORRUPT if any of the internal checks fail, or if the
+** checksum does not match. Return SQLITE_OK if all checks pass without
+** error, or some other SQLite error code if another error (e.g. OOM)
+** occurs.
+*/
+int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){
+ Fts5Config *pConfig = p->pConfig;
+ int iIdx; /* Used to iterate through indexes */
+ u64 cksum2 = 0; /* Checksum based on contents of indexes */
+ u64 cksum3 = 0; /* Checksum based on contents of indexes */
+ Fts5Buffer term = {0,0,0}; /* Buffer used to hold most recent term */
+
+ /* Check that the internal nodes of each segment match the leaves */
+ for(iIdx=0; p->rc==SQLITE_OK && iIdx<=pConfig->nPrefix; iIdx++){
+ Fts5Structure *pStruct = fts5StructureRead(p, iIdx);
+ if( pStruct ){
+ int iLvl, iSeg;
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+ for(iSeg=0; iSeg<pStruct->aLevel[iLvl].nSeg; iSeg++){
+ Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg];
+ fts5IndexIntegrityCheckSegment(p, iIdx, pSeg);
+ }
+ }
+ }
+ fts5StructureRelease(pStruct);
+ }
+
+ /* The cksum argument passed to this function is a checksum calculated
+ ** based on all expected entries in the FTS index (including prefix index
+ ** entries). This block checks that a checksum calculated based on the
+ ** actual contents of FTS index is identical.
+ **
+ ** Two versions of the same checksum are calculated. The first (stack
+ ** variable cksum2) based on entries extracted from the full-text index
+ ** while doing a linear scan of each individual index in turn.
+ **
+ ** As each term visited by the linear scans, a separate query for the
+ ** same term is performed. cksum3 is calculated based on the entries
+ ** extracted by these queries.
+ */
+ for(iIdx=0; iIdx<=pConfig->nPrefix; iIdx++){
+ Fts5MultiSegIter *pIter;
+ Fts5Structure *pStruct = fts5StructureRead(p, iIdx);
+ for(fts5MultiIterNew(p, pStruct, iIdx, 0, 0, 0, 0, -1, 0, &pIter);
+ fts5MultiIterEof(p, pIter)==0;
+ fts5MultiIterNext(p, pIter, 0, 0)
+ ){
+ Fts5PosIter sPos; /* Used to iterate through position list */
+ int n; /* Size of term in bytes */
+ i64 iRowid = fts5MultiIterRowid(pIter);
+ char *z = (char*)fts5MultiIterTerm(pIter, &n);
+
+ /* Update cksum2 with the entries associated with the current term
+ ** and rowid. */
+ for(fts5PosIterInit(p, pIter, &sPos);
+ fts5PosIterEof(p, &sPos)==0;
+ fts5PosIterNext(p, &sPos)
+ ){
+ cksum2 ^= fts5IndexEntryCksum(iRowid, sPos.iCol, sPos.iPos, z, n);
+ }
+
+ /* If this is a new term, query for it. Update cksum3 with the results. */
+ if( p->rc==SQLITE_OK && (term.n!=n || memcmp(term.p, z, n)) ){
+ int rc;
+ int flags = (iIdx==0 ? 0 : FTS5INDEX_QUERY_PREFIX);
+ u64 ck1 = 0;
+ u64 ck2 = 0;
+
+ /* Check that the results returned for ASC and DESC queries are
+ ** the same. If not, call this corruption. */
+ rc = fts5QueryCksum(p, z, n, flags, &ck1);
+ if( rc==SQLITE_OK ){
+ rc = fts5QueryCksum(p, z, n, flags|FTS5INDEX_QUERY_DESC, &ck2);
+ }
+ if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT;
+
+ /* If this is a prefix query, check that the results returned if the
+ ** the index is disabled are the same. In both ASC and DESC order. */
+ if( iIdx>0 && rc==SQLITE_OK ){
+ int f = flags|FTS5INDEX_QUERY_TEST_NOIDX;
+ ck2 = 0;
+ rc = fts5QueryCksum(p, z, n, f, &ck2);
+ if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT;
+ }
+ if( iIdx>0 && rc==SQLITE_OK ){
+ int f = flags|FTS5INDEX_QUERY_TEST_NOIDX|FTS5INDEX_QUERY_DESC;
+ ck2 = 0;
+ rc = fts5QueryCksum(p, z, n, f, &ck2);
+ if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT;
+ }
+
+ cksum3 ^= ck1;
+ fts5BufferSet(&rc, &term, n, (const u8*)z);
+ p->rc = rc;
+ }
+ }
+ fts5MultiIterFree(p, pIter);
+ fts5StructureRelease(pStruct);
+ }
+ if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT;
+ if( p->rc==SQLITE_OK && cksum!=cksum3 ) p->rc = FTS5_CORRUPT;
+
+ fts5BufferFree(&term);
+ return fts5IndexReturn(p);
+}
+
+
+/*
+** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain
+** to the document with rowid iRowid.
+*/
+int sqlite3Fts5IndexBeginWrite(Fts5Index *p, i64 iRowid){
+ assert( p->rc==SQLITE_OK );
+
+ /* Allocate hash tables if they have not already been allocated */
+ if( p->apHash==0 ){
+ int i;
+ int rc = SQLITE_OK;
+ int nHash = p->pConfig->nPrefix + 1;
+ Fts5Hash **apNew;
+
+ apNew = (Fts5Hash**)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Hash*)*nHash);
+ for(i=0; rc==SQLITE_OK && i<nHash; i++){
+ rc = sqlite3Fts5HashNew(&apNew[i], &p->nPendingData);
+ }
+ if( rc==SQLITE_OK ){
+ p->apHash = apNew;
+ }else{
+ if( apNew ){
+ for(i=0; i<nHash; i++){
+ sqlite3Fts5HashFree(apNew[i]);
+ }
+ sqlite3_free(apNew);
+ }
+ return rc;
+ }
+ }
+
+ if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){
+ fts5IndexFlush(p);
+ }
+ p->iWriteRowid = iRowid;
+ return fts5IndexReturn(p);
+}
+
+/*
+** Commit data to disk.
+*/
+int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){
+ assert( p->rc==SQLITE_OK );
+ fts5IndexFlush(p);
+ if( bCommit ) fts5CloseReader(p);
+ return fts5IndexReturn(p);
+}
+
+/*
+** Discard any data stored in the in-memory hash tables. Do not write it
+** to the database. Additionally, assume that the contents of the %_data
+** table may have changed on disk. So any in-memory caches of %_data
+** records must be invalidated.
+*/
+int sqlite3Fts5IndexRollback(Fts5Index *p){
+ fts5CloseReader(p);
+ fts5IndexDiscardData(p);
+ assert( p->rc==SQLITE_OK );
+ return SQLITE_OK;
+}
+
+/*
+** The %_data table is completely empty when this function is called. This
+** function populates it with the initial structure objects for each index,
+** and the initial version of the "averages" record (a zero-byte blob).
+*/
+int sqlite3Fts5IndexReinit(Fts5Index *p){
+ int i;
+ Fts5Structure s;
+
+ memset(&s, 0, sizeof(Fts5Structure));
+ for(i=0; i<p->pConfig->nPrefix+1; i++){
+ fts5StructureWrite(p, i, &s);
+ }
+ if( p->rc==SQLITE_OK ){
+ p->rc = sqlite3Fts5IndexSetAverages(p, (const u8*)"", 0);
+ }
+
+ return fts5IndexReturn(p);
+}
+
+/*
+** Open a new Fts5Index handle. If the bCreate argument is true, create
+** and initialize the underlying %_data table.
+**
+** If successful, set *pp to point to the new object and return SQLITE_OK.
+** Otherwise, set *pp to NULL and return an SQLite error code.
+*/
+int sqlite3Fts5IndexOpen(
+ Fts5Config *pConfig,
+ int bCreate,
+ Fts5Index **pp,
+ char **pzErr
+){
+ int rc = SQLITE_OK;
+ Fts5Index *p; /* New object */
+
+ *pp = p = (Fts5Index*)sqlite3_malloc(sizeof(Fts5Index));
+ if( !p ) return SQLITE_NOMEM;
+
+ memset(p, 0, sizeof(Fts5Index));
+ p->pConfig = pConfig;
+ p->nWorkUnit = FTS5_WORK_UNIT;
+ p->nMaxPendingData = 1024*1024;
+ p->zDataTbl = sqlite3_mprintf("%s_data", pConfig->zName);
+ if( p->zDataTbl==0 ){
+ rc = SQLITE_NOMEM;
+ }else if( bCreate ){
+ rc = sqlite3Fts5CreateTable(
+ pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr
+ );
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5IndexReinit(p);
+ }
+ }
+
+ assert( p->rc==SQLITE_OK || rc!=SQLITE_OK );
+ if( rc ){
+ sqlite3Fts5IndexClose(p, 0);
+ *pp = 0;
+ }
+ return rc;
+}
+
+/*
+** Close a handle opened by an earlier call to sqlite3Fts5IndexOpen().
+*/
+int sqlite3Fts5IndexClose(Fts5Index *p, int bDestroy){
+ int rc = SQLITE_OK;
+ if( p ){
+ if( bDestroy ){
+ rc = sqlite3Fts5DropTable(p->pConfig, "data");
+ }
+ assert( p->pReader==0 );
+ sqlite3_finalize(p->pWriter);
+ sqlite3_finalize(p->pDeleter);
+ if( p->apHash ){
+ int i;
+ for(i=0; i<=p->pConfig->nPrefix; i++){
+ sqlite3Fts5HashFree(p->apHash[i]);
+ }
+ sqlite3_free(p->apHash);
+ }
+ sqlite3_free(p->zDataTbl);
+ sqlite3_free(p);
+ }
+ return rc;
+}
+
+/*
+** Argument p points to a buffer containing utf-8 text that is n bytes in
+** size. Return the number of bytes in the nChar character prefix of the
+** buffer, or 0 if there are less than nChar characters in total.
+*/
+static int fts5IndexCharlenToBytelen(const char *p, int nByte, int nChar){
+ int n = 0;
+ int i;
+ for(i=0; i<nChar; i++){
+ if( n>=nByte ) return 0; /* Input contains fewer than nChar chars */
+ if( (unsigned char)p[n++]>=0xc0 ){
+ while( (p[n] & 0xc0)==0x80 ) n++;
+ }
+ }
+ return n;
+}
+
+/*
+** pIn is a UTF-8 encoded string, nIn bytes in size. Return the number of
+** unicode characters in the string.
+*/
+int fts5IndexCharlen(const char *pIn, int nIn){
+ int nChar = 0;
+ int i = 0;
+ while( i<nIn ){
+ if( (unsigned char)pIn[i++]>=0xc0 ){
+ while( i<nIn && (pIn[i] & 0xc0)==0x80 ) i++;
+ }
+ nChar++;
+ }
+ return nChar;
+}
+
+/*
+** Calculate and return a checksum that is the XOR of the index entry
+** checksum of all entries that would be generated by the token specified
+** by the final 5 arguments.
+*/
+u64 sqlite3Fts5IndexCksum(
+ Fts5Config *pConfig, /* Configuration object */
+ i64 iRowid, /* Document term appears in */
+ int iCol, /* Column term appears in */
+ int iPos, /* Position term appears in */
+ const char *pTerm, int nTerm /* Term at iPos */
+){
+ u64 ret = 0; /* Return value */
+ int iIdx; /* For iterating through indexes */
+
+ ret = fts5IndexEntryCksum(iRowid, iCol, iPos, pTerm, nTerm);
+
+ for(iIdx=0; iIdx<pConfig->nPrefix; iIdx++){
+ int nByte = fts5IndexCharlenToBytelen(pTerm, nTerm, pConfig->aPrefix[iIdx]);
+ if( nByte ){
+ ret ^= fts5IndexEntryCksum(iRowid, iCol, iPos, pTerm, nByte);
+ }
+ }
+
+ return ret;
+}
+
+/*
+** Insert or remove data to or from the index. Each time a document is
+** added to or removed from the index, this function is called one or more
+** times.
+**
+** For an insert, it must be called once for each token in the new document.
+** If the operation is a delete, it must be called (at least) once for each
+** unique token in the document with an iCol value less than zero. The iPos
+** argument is ignored for a delete.
+*/
+int sqlite3Fts5IndexWrite(
+ Fts5Index *p, /* Index to write to */
+ int iCol, /* Column token appears in (-ve -> delete) */
+ int iPos, /* Position of token within column */
+ const char *pToken, int nToken /* Token to add or remove to or from index */
+){
+ int i; /* Used to iterate through indexes */
+ int rc; /* Return code */
+ Fts5Config *pConfig = p->pConfig;
+
+ assert( p->rc==SQLITE_OK );
+
+ /* Add the new token to the main terms hash table. And to each of the
+ ** prefix hash tables that it is large enough for. */
+ rc = sqlite3Fts5HashWrite(
+ p->apHash[0], p->iWriteRowid, iCol, iPos, pToken, nToken
+ );
+ for(i=0; i<pConfig->nPrefix && rc==SQLITE_OK; i++){
+ int nByte = fts5IndexCharlenToBytelen(pToken, nToken, pConfig->aPrefix[i]);
+ if( nByte ){
+ rc = sqlite3Fts5HashWrite(
+ p->apHash[i+1], p->iWriteRowid, iCol, iPos, pToken, nByte
+ );
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Open a new iterator to iterate though all docids that match the
+** specified token or token prefix.
+*/
+int sqlite3Fts5IndexQuery(
+ Fts5Index *p, /* FTS index to query */
+ const char *pToken, int nToken, /* Token (or prefix) to query for */
+ int flags, /* Mask of FTS5INDEX_QUERY_X flags */
+ Fts5IndexIter **ppIter /* OUT: New iterator object */
+){
+ Fts5Config *pConfig = p->pConfig;
+ Fts5IndexIter *pRet;
+ int iIdx = 0;
+
+ if( flags & FTS5INDEX_QUERY_PREFIX ){
+ if( flags & FTS5INDEX_QUERY_TEST_NOIDX ){
+ iIdx = 1+pConfig->nPrefix;
+ }else{
+ int nChar = fts5IndexCharlen(pToken, nToken);
+ for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){
+ if( pConfig->aPrefix[iIdx-1]==nChar ) break;
+ }
+ }
+ }
+
+ pRet = (Fts5IndexIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5IndexIter));
+ if( pRet ){
+ memset(pRet, 0, sizeof(Fts5IndexIter));
+
+ pRet->pIndex = p;
+ if( iIdx<=pConfig->nPrefix ){
+ pRet->pStruct = fts5StructureRead(p, iIdx);
+ if( pRet->pStruct ){
+ fts5MultiIterNew(p, pRet->pStruct,
+ iIdx, 1, flags, (const u8*)pToken, nToken, -1, 0, &pRet->pMulti
+ );
+ }
+ }else{
+ int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0;
+ fts5SetupPrefixIter(p, bDesc, (const u8*)pToken, nToken, pRet);
+ }
+ }
+
+ if( p->rc ){
+ sqlite3Fts5IterClose(pRet);
+ pRet = 0;
+ }
+ *ppIter = pRet;
+ return fts5IndexReturn(p);
+}
+
+/*
+** Return true if the iterator passed as the only argument is at EOF.
+*/
+int sqlite3Fts5IterEof(Fts5IndexIter *pIter){
+ assert( pIter->pIndex->rc==SQLITE_OK );
+ if( pIter->pDoclist ){
+ return pIter->pDoclist->aPoslist==0;
+ }else{
+ return fts5MultiIterEof(pIter->pIndex, pIter->pMulti);
+ }
+}
+
+/*
+** Move to the next matching rowid.
+*/
+int sqlite3Fts5IterNext(Fts5IndexIter *pIter){
+ assert( pIter->pIndex->rc==SQLITE_OK );
+ if( pIter->pDoclist ){
+ fts5DoclistIterNext(pIter->pDoclist);
+ }else{
+ fts5BufferZero(&pIter->poslist);
+ fts5MultiIterNext(pIter->pIndex, pIter->pMulti, 0, 0);
+ }
+ return fts5IndexReturn(pIter->pIndex);
+}
+
+/*
+** Move the doclist-iter passed as the first argument to the next
+** matching rowid that occurs at or after iMatch. The definition of "at
+** or after" depends on whether this iterator iterates in ascending or
+** descending rowid order.
+*/
+static void fts5DoclistIterNextFrom(Fts5DoclistIter *p, i64 iMatch){
+ do{
+ i64 iRowid = p->iRowid;
+ if( p->bDesc==0 && iRowid>=iMatch ) break;
+ if( p->bDesc!=0 && iRowid<=iMatch ) break;
+ fts5DoclistIterNext(p);
+ }while( p->aPoslist );
+}
+
+/*
+** Move to the next matching rowid that occurs at or after iMatch. The
+** definition of "at or after" depends on whether this iterator iterates
+** in ascending or descending rowid order.
+*/
+int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIter, i64 iMatch){
+ if( pIter->pDoclist ){
+ fts5DoclistIterNextFrom(pIter->pDoclist, iMatch);
+ }else{
+ fts5MultiIterNextFrom(pIter->pIndex, pIter->pMulti, iMatch);
+ }
+ return fts5IndexReturn(pIter->pIndex);
+}
+
+/*
+** Return the current rowid.
+*/
+i64 sqlite3Fts5IterRowid(Fts5IndexIter *pIter){
+ if( pIter->pDoclist ){
+ return pIter->pDoclist->iRowid;
+ }else{
+ return fts5MultiIterRowid(pIter->pMulti);
+ }
+}
+
+
+/*
+** Return a pointer to a buffer containing a copy of the position list for
+** the current entry. Output variable *pn is set to the size of the buffer
+** in bytes before returning.
+**
+** The returned position list does not include the "number of bytes" varint
+** field that starts the position list on disk.
+*/
+int sqlite3Fts5IterPoslist(Fts5IndexIter *pIter, const u8 **pp, int *pn){
+ assert( pIter->pIndex->rc==SQLITE_OK );
+ if( pIter->pDoclist ){
+ *pn = pIter->pDoclist->nPoslist;
+ *pp = pIter->pDoclist->aPoslist;
+ }else{
+ Fts5Index *p = pIter->pIndex;
+ fts5BufferZero(&pIter->poslist);
+ fts5MultiIterPoslist(p, pIter->pMulti, 0, &pIter->poslist);
+ *pn = pIter->poslist.n;
+ *pp = pIter->poslist.p;
+ }
+ return fts5IndexReturn(pIter->pIndex);
+}
+
+/*
+** Close an iterator opened by an earlier call to sqlite3Fts5IndexQuery().
+*/
+void sqlite3Fts5IterClose(Fts5IndexIter *pIter){
+ if( pIter ){
+ if( pIter->pDoclist ){
+ sqlite3_free(pIter->pDoclist->a);
+ sqlite3_free(pIter->pDoclist);
+ }else{
+ fts5MultiIterFree(pIter->pIndex, pIter->pMulti);
+ fts5StructureRelease(pIter->pStruct);
+ fts5BufferFree(&pIter->poslist);
+ }
+ fts5CloseReader(pIter->pIndex);
+ sqlite3_free(pIter);
+ }
+}
+
+/*
+** Read the "averages" record into the buffer supplied as the second
+** argument. Return SQLITE_OK if successful, or an SQLite error code
+** if an error occurs.
+*/
+int sqlite3Fts5IndexGetAverages(Fts5Index *p, Fts5Buffer *pBuf){
+ assert( p->rc==SQLITE_OK );
+ fts5DataReadOrBuffer(p, pBuf, FTS5_AVERAGES_ROWID);
+ return fts5IndexReturn(p);
+}
+
+/*
+** Replace the current "averages" record with the contents of the buffer
+** supplied as the second argument.
+*/
+int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8 *pData, int nData){
+ assert( p->rc==SQLITE_OK );
+ fts5DataWrite(p, FTS5_AVERAGES_ROWID, pData, nData);
+ return fts5IndexReturn(p);
+}
+
+/*
+** Return the total number of blocks this module has read from the %_data
+** table since it was created.
+*/
+int sqlite3Fts5IndexReads(Fts5Index *p){
+ return p->nRead;
+}
+
+/*
+** Set the 32-bit cookie value stored at the start of all structure
+** records to the value passed as the second argument.
+**
+** Return SQLITE_OK if successful, or an SQLite error code if an error
+** occurs.
+*/
+int sqlite3Fts5IndexSetCookie(Fts5Index *p, int iNew){
+ int rc = SQLITE_OK;
+ Fts5Config *pConfig = p->pConfig;
+ u8 aCookie[4];
+ int i;
+
+ assert( p->rc==SQLITE_OK );
+ sqlite3Fts5Put32(aCookie, iNew);
+ for(i=0; rc==SQLITE_OK && i<=pConfig->nPrefix; i++){
+ sqlite3_blob *pBlob = 0;
+ i64 iRowid = FTS5_STRUCTURE_ROWID(i);
+ rc = sqlite3_blob_open(
+ pConfig->db, pConfig->zDb, p->zDataTbl, "block", iRowid, 1, &pBlob
+ );
+ if( rc==SQLITE_OK ){
+ sqlite3_blob_write(pBlob, aCookie, 4, 0);
+ rc = sqlite3_blob_close(pBlob);
+ }
+ }
+
+ return rc;
+}
+
+int sqlite3Fts5IndexLoadConfig(Fts5Index *p){
+ Fts5Structure *pStruct;
+ pStruct = fts5StructureRead(p, 0);
+ fts5StructureRelease(pStruct);
+ return fts5IndexReturn(p);
+}
+
+/*************************************************************************
+**************************************************************************
+** Below this point is the implementation of the fts5_decode() scalar
+** function only.
+*/
+
+/*
+** Decode a segment-data rowid from the %_data table. This function is
+** the opposite of macro FTS5_SEGMENT_ROWID().
+*/
+static void fts5DecodeRowid(
+ i64 iRowid, /* Rowid from %_data table */
+ int *piIdx, /* OUT: Index */
+ int *piSegid, /* OUT: Segment id */
+ int *piHeight, /* OUT: Height */
+ int *piPgno /* OUT: Page number */
+){
+ *piPgno = (int)(iRowid & (((i64)1 << FTS5_DATA_PAGE_B) - 1));
+ iRowid >>= FTS5_DATA_PAGE_B;
+
+ *piHeight = (int)(iRowid & (((i64)1 << FTS5_DATA_HEIGHT_B) - 1));
+ iRowid >>= FTS5_DATA_HEIGHT_B;
+
+ *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1));
+ iRowid >>= FTS5_DATA_ID_B;
+
+ *piIdx = (int)(iRowid & (((i64)1 << FTS5_DATA_IDX_B) - 1));
+}
+
+static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){
+ int iIdx,iSegid,iHeight,iPgno; /* Rowid compenents */
+ fts5DecodeRowid(iKey, &iIdx, &iSegid, &iHeight, &iPgno);
+
+ if( iSegid==0 ){
+ if( iKey==FTS5_AVERAGES_ROWID ){
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(averages) ");
+ }else{
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf,
+ "{structure idx=%d}", (int)(iKey-10)
+ );
+ }
+ }
+ else if( iHeight==FTS5_SEGMENT_MAX_HEIGHT ){
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(dlidx idx=%d segid=%d pgno=%d)",
+ iIdx, iSegid, iPgno
+ );
+ }else{
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "(idx=%d segid=%d h=%d pgno=%d)",
+ iIdx, iSegid, iHeight, iPgno
+ );
+ }
+}
+
+static void fts5DebugStructure(
+ int *pRc, /* IN/OUT: error code */
+ Fts5Buffer *pBuf,
+ Fts5Structure *p
+){
+ int iLvl, iSeg; /* Iterate through levels, segments */
+
+ for(iLvl=0; iLvl<p->nLevel; iLvl++){
+ Fts5StructureLevel *pLvl = &p->aLevel[iLvl];
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf,
+ " {lvl=%d nMerge=%d", iLvl, pLvl->nMerge
+ );
+ for(iSeg=0; iSeg<pLvl->nSeg; iSeg++){
+ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg];
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf,
+ " {id=%d h=%d leaves=%d..%d}", pSeg->iSegid, pSeg->nHeight,
+ pSeg->pgnoFirst, pSeg->pgnoLast
+ );
+ }
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}");
+ }
+}
+
+/*
+** This is part of the fts5_decode() debugging aid.
+**
+** Arguments pBlob/nBlob contain a serialized Fts5Structure object. This
+** function appends a human-readable representation of the same object
+** to the buffer passed as the second argument.
+*/
+static void fts5DecodeStructure(
+ int *pRc, /* IN/OUT: error code */
+ Fts5Buffer *pBuf,
+ const u8 *pBlob, int nBlob
+){
+ int rc; /* Return code */
+ Fts5Structure *p = 0; /* Decoded structure object */
+
+ rc = fts5StructureDecode(pBlob, nBlob, 0, &p);
+ if( rc!=SQLITE_OK ){
+ *pRc = rc;
+ return;
+ }
+
+ fts5DebugStructure(pRc, pBuf, p);
+ fts5StructureRelease(p);
+}
+
+/*
+** Buffer (a/n) is assumed to contain a list of serialized varints. Read
+** each varint and append its string representation to buffer pBuf. Return
+** after either the input buffer is exhausted or a 0 value is read.
+**
+** The return value is the number of bytes read from the input buffer.
+*/
+static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){
+ int iOff = 0;
+ while( iOff<n ){
+ int iVal;
+ iOff += fts5GetVarint32(&a[iOff], iVal);
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %d", iVal);
+ }
+ return iOff;
+}
+
+/*
+** The start of buffer (a/n) contains the start of a doclist. The doclist
+** may or may not finish within the buffer. This function appends a text
+** representation of the part of the doclist that is present to buffer
+** pBuf.
+**
+** The return value is the number of bytes read from the input buffer.
+*/
+static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){
+ i64 iDocid;
+ int iOff = 0;
+
+ if( iOff<n ){
+ iOff += sqlite3GetVarint(&a[iOff], (u64*)&iDocid);
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " rowid=%lld", iDocid);
+ }
+ while( iOff<n ){
+ int nPos;
+ int bDummy;
+ iOff += fts5GetPoslistSize(&a[iOff], &nPos, &bDummy);
+ iOff += fts5DecodePoslist(pRc, pBuf, &a[iOff], MIN(n-iOff, nPos));
+ if( iOff<n ){
+ i64 iDelta;
+ iOff += sqlite3GetVarint(&a[iOff], (u64*)&iDelta);
+ if( iDelta==0 ) return iOff;
+ iDocid += iDelta;
+ sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " rowid=%lld", iDocid);
+ }
+ }
+
+ return iOff;
+}
+
+/*
+** The implementation of user-defined scalar function fts5_decode().
+*/
+static void fts5DecodeFunction(
+ sqlite3_context *pCtx, /* Function call context */
+ int nArg, /* Number of args (always 2) */
+ sqlite3_value **apVal /* Function arguments */
+){
+ i64 iRowid; /* Rowid for record being decoded */
+ int iIdx,iSegid,iHeight,iPgno; /* Rowid components */
+ const u8 *aBlob; int n; /* Record to decode */
+ u8 *a = 0;
+ Fts5Buffer s; /* Build up text to return here */
+ int rc = SQLITE_OK; /* Return code */
+ int nSpace = 0;
+
+ assert( nArg==2 );
+ memset(&s, 0, sizeof(Fts5Buffer));
+ iRowid = sqlite3_value_int64(apVal[0]);
+ n = sqlite3_value_bytes(apVal[1]);
+ aBlob = sqlite3_value_blob(apVal[1]);
+
+ nSpace = n + FTS5_DATA_ZERO_PADDING;
+ a = (u8*)sqlite3Fts5MallocZero(&rc, nSpace);
+ if( a==0 ) goto decode_out;
+ memcpy(a, aBlob, n);
+ fts5DecodeRowid(iRowid, &iIdx, &iSegid, &iHeight, &iPgno);
+
+ fts5DebugRowid(&rc, &s, iRowid);
+ if( iHeight==FTS5_SEGMENT_MAX_HEIGHT ){
+ Fts5Data dlidx;
+ Fts5DlidxIter iter;
+
+ dlidx.p = a;
+ dlidx.n = n;
+ dlidx.nRef = 2;
+
+ memset(&iter, 0, sizeof(Fts5DlidxIter));
+ iter.pData = &dlidx;
+ iter.iLeafPgno = iPgno;
+
+ for(fts5DlidxIterFirst(&iter); iter.bEof==0; fts5DlidxIterNext(&iter)){
+ sqlite3Fts5BufferAppendPrintf(&rc, &s,
+ " %d(%lld)", iter.iLeafPgno, iter.iRowid
+ );
+ }
+ }else if( iSegid==0 ){
+ if( iRowid==FTS5_AVERAGES_ROWID ){
+ /* todo */
+ }else{
+ fts5DecodeStructure(&rc, &s, a, n);
+ }
+ }else{
+
+ Fts5Buffer term;
+ memset(&term, 0, sizeof(Fts5Buffer));
+
+ if( iHeight==0 ){
+ int iTermOff = 0;
+ int iRowidOff = 0;
+ int iOff;
+ int nKeep = 0;
+
+ if( n>=4 ){
+ iRowidOff = fts5GetU16(&a[0]);
+ iTermOff = fts5GetU16(&a[2]);
+ }else{
+ sqlite3Fts5BufferSet(&rc, &s, 8, (const u8*)"corrupt");
+ goto decode_out;
+ }
+
+ if( iRowidOff ){
+ iOff = iRowidOff;
+ }else if( iTermOff ){
+ iOff = iTermOff;
+ }else{
+ iOff = n;
+ }
+ fts5DecodePoslist(&rc, &s, &a[4], iOff-4);
+
+ assert( iRowidOff==0 || iOff==iRowidOff );
+ if( iRowidOff ){
+ iOff += fts5DecodeDoclist(&rc, &s, &a[iOff], n-iOff);
+ }
+
+ assert( iTermOff==0 || iOff==iTermOff );
+ while( iOff<n ){
+ int nByte;
+ iOff += fts5GetVarint32(&a[iOff], nByte);
+ term.n= nKeep;
+ fts5BufferAppendBlob(&rc, &term, nByte, &a[iOff]);
+ iOff += nByte;
+
+ sqlite3Fts5BufferAppendPrintf(
+ &rc, &s, " term=%.*s", term.n, (const char*)term.p
+ );
+ iOff += fts5DecodeDoclist(&rc, &s, &a[iOff], n-iOff);
+ if( iOff<n ){
+ iOff += fts5GetVarint32(&a[iOff], nKeep);
+ }
+ }
+ fts5BufferFree(&term);
+ }else{
+ Fts5NodeIter ss;
+ for(fts5NodeIterInit(a, n, &ss); ss.aData; fts5NodeIterNext(&rc, &ss)){
+ if( ss.term.n==0 ){
+ sqlite3Fts5BufferAppendPrintf(&rc, &s, " left=%d", ss.iChild);
+ }else{
+ sqlite3Fts5BufferAppendPrintf(&rc,&s, " \"%.*s\"",
+ ss.term.n, ss.term.p
+ );
+ }
+ if( ss.nEmpty ){
+ sqlite3Fts5BufferAppendPrintf(&rc, &s, " empty=%d%s", ss.nEmpty,
+ ss.bDlidx ? "*" : ""
+ );
+ }
+ }
+ fts5NodeIterFree(&ss);
+ }
+ }
+
+ decode_out:
+ sqlite3_free(a);
+ if( rc==SQLITE_OK ){
+ sqlite3_result_text(pCtx, (const char*)s.p, s.n, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_error_code(pCtx, rc);
+ }
+ fts5BufferFree(&s);
+}
+
+/*
+** The implementation of user-defined scalar function fts5_rowid().
+*/
+static void fts5RowidFunction(
+ sqlite3_context *pCtx, /* Function call context */
+ int nArg, /* Number of args (always 2) */
+ sqlite3_value **apVal /* Function arguments */
+){
+ const char *zArg;
+ if( nArg==0 ){
+ sqlite3_result_error(pCtx, "should be: fts5_rowid(subject, ....)", -1);
+ }else{
+ zArg = (const char*)sqlite3_value_text(apVal[0]);
+ if( 0==sqlite3_stricmp(zArg, "segment") ){
+ i64 iRowid;
+ int idx, segid, height, pgno;
+ if( nArg!=5 ){
+ sqlite3_result_error(pCtx,
+ "should be: fts5_rowid('segment', idx, segid, height, pgno))", -1
+ );
+ }else{
+ idx = sqlite3_value_int(apVal[1]);
+ segid = sqlite3_value_int(apVal[2]);
+ height = sqlite3_value_int(apVal[3]);
+ pgno = sqlite3_value_int(apVal[4]);
+ iRowid = FTS5_SEGMENT_ROWID(idx, segid, height, pgno);
+ sqlite3_result_int64(pCtx, iRowid);
+ }
+ }else if( 0==sqlite3_stricmp(zArg, "start-of-index") ){
+ i64 iRowid;
+ int idx;
+ if( nArg!=2 ){
+ sqlite3_result_error(pCtx,
+ "should be: fts5_rowid('start-of-index', idx)", -1
+ );
+ }else{
+ idx = sqlite3_value_int(apVal[1]);
+ iRowid = FTS5_SEGMENT_ROWID(idx, 1, 0, 0);
+ sqlite3_result_int64(pCtx, iRowid);
+ }
+ }else {
+ sqlite3_result_error(pCtx,
+ "first arg to fts5_rowid() must be 'segment' "
+ "or 'start-of-index'"
+ , -1
+ );
+ }
+ }
+}
+
+/*
+** This is called as part of registering the FTS5 module with database
+** connection db. It registers several user-defined scalar functions useful
+** with FTS5.
+**
+** If successful, SQLITE_OK is returned. If an error occurs, some other
+** SQLite error code is returned instead.
+*/
+int sqlite3Fts5IndexInit(sqlite3 *db){
+ int rc = sqlite3_create_function(
+ db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0
+ );
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(
+ db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0
+ );
+ }
+ return rc;
+}
+
+#endif /* SQLITE_ENABLE_FTS5 */
diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c
new file mode 100644
index 000000000..075b2eb66
--- /dev/null
+++ b/ext/fts5/fts5_storage.c
@@ -0,0 +1,992 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+**
+*/
+
+#ifdef SQLITE_ENABLE_FTS5
+
+
+#include "fts5Int.h"
+
+struct Fts5Storage {
+ Fts5Config *pConfig;
+ Fts5Index *pIndex;
+ int bTotalsValid; /* True if nTotalRow/aTotalSize[] are valid */
+ i64 nTotalRow; /* Total number of rows in FTS table */
+ i64 *aTotalSize; /* Total sizes of each column */
+ sqlite3_stmt *aStmt[10];
+};
+
+
+#if FTS5_STMT_SCAN_ASC!=0
+# error "FTS5_STMT_SCAN_ASC mismatch"
+#endif
+#if FTS5_STMT_SCAN_DESC!=1
+# error "FTS5_STMT_SCAN_DESC mismatch"
+#endif
+#if FTS5_STMT_LOOKUP!=2
+# error "FTS5_STMT_LOOKUP mismatch"
+#endif
+
+#define FTS5_STMT_INSERT_CONTENT 3
+#define FTS5_STMT_REPLACE_CONTENT 4
+
+#define FTS5_STMT_DELETE_CONTENT 5
+#define FTS5_STMT_REPLACE_DOCSIZE 6
+#define FTS5_STMT_DELETE_DOCSIZE 7
+
+#define FTS5_STMT_LOOKUP_DOCSIZE 8
+
+#define FTS5_STMT_REPLACE_CONFIG 9
+
+/*
+** Prepare the two insert statements - Fts5Storage.pInsertContent and
+** Fts5Storage.pInsertDocsize - if they have not already been prepared.
+** Return SQLITE_OK if successful, or an SQLite error code if an error
+** occurs.
+*/
+static int fts5StorageGetStmt(
+ Fts5Storage *p, /* Storage handle */
+ int eStmt, /* FTS5_STMT_XXX constant */
+ sqlite3_stmt **ppStmt, /* OUT: Prepared statement handle */
+ char **pzErrMsg /* OUT: Error message (if any) */
+){
+ int rc = SQLITE_OK;
+
+ assert( eStmt>=0 && eStmt<ArraySize(p->aStmt) );
+ if( p->aStmt[eStmt]==0 ){
+ const char *azStmt[] = {
+ "SELECT * FROM %s ORDER BY %s ASC", /* SCAN_ASC */
+ "SELECT * FROM %s ORDER BY %s DESC", /* SCAN_DESC */
+ "SELECT * FROM %s WHERE %s=?", /* LOOKUP */
+
+ "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */
+ "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */
+ "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */
+ "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */
+ "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */
+
+ "SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */
+
+ "REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */
+ };
+ Fts5Config *pC = p->pConfig;
+ char *zSql = 0;
+
+ switch( eStmt ){
+ case FTS5_STMT_SCAN_ASC:
+ case FTS5_STMT_SCAN_DESC:
+ case FTS5_STMT_LOOKUP:
+ zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContent, pC->zContentRowid);
+ break;
+
+ case FTS5_STMT_INSERT_CONTENT:
+ case FTS5_STMT_REPLACE_CONTENT: {
+ int nCol = pC->nCol + 1;
+ char *zBind;
+ int i;
+
+ zBind = sqlite3_malloc(1 + nCol*2);
+ if( zBind ){
+ for(i=0; i<nCol; i++){
+ zBind[i*2] = '?';
+ zBind[i*2 + 1] = ',';
+ }
+ zBind[i*2-1] = '\0';
+ zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind);
+ sqlite3_free(zBind);
+ }
+ break;
+ }
+
+ default:
+ zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName);
+ break;
+ }
+
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v2(pC->db, zSql, -1, &p->aStmt[eStmt], 0);
+ sqlite3_free(zSql);
+ if( rc!=SQLITE_OK && pzErrMsg ){
+ *pzErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pC->db));
+ }
+ }
+ }
+
+ *ppStmt = p->aStmt[eStmt];
+ return rc;
+}
+
+
+static int fts5ExecPrintf(
+ sqlite3 *db,
+ char **pzErr,
+ const char *zFormat,
+ ...
+){
+ int rc;
+ va_list ap; /* ... printf arguments */
+ va_start(ap, zFormat);
+ char *zSql = sqlite3_vmprintf(zFormat, ap);
+
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_exec(db, zSql, 0, 0, pzErr);
+ sqlite3_free(zSql);
+ }
+
+ va_end(ap);
+ return rc;
+}
+
+/*
+** Drop the shadow table with the postfix zPost (e.g. "content"). Return
+** SQLITE_OK if successful or an SQLite error code otherwise.
+*/
+int sqlite3Fts5DropTable(Fts5Config *pConfig, const char *zPost){
+ return fts5ExecPrintf(pConfig->db, 0, "DROP TABLE IF EXISTS %Q.'%q_%q'",
+ pConfig->zDb, pConfig->zName, zPost
+ );
+}
+
+/*
+** Create the shadow table named zPost, with definition zDefn. Return
+** SQLITE_OK if successful, or an SQLite error code otherwise.
+*/
+int sqlite3Fts5CreateTable(
+ Fts5Config *pConfig, /* FTS5 configuration */
+ const char *zPost, /* Shadow table to create (e.g. "content") */
+ const char *zDefn, /* Columns etc. for shadow table */
+ int bWithout, /* True for without rowid */
+ char **pzErr /* OUT: Error message */
+){
+ int rc;
+ char *zErr = 0;
+
+ rc = fts5ExecPrintf(pConfig->db, &zErr, "CREATE TABLE %Q.'%q_%q'(%s)%s",
+ pConfig->zDb, pConfig->zName, zPost, zDefn, bWithout?" WITHOUT ROWID":""
+ );
+ if( zErr ){
+ *pzErr = sqlite3_mprintf(
+ "fts5: error creating shadow table %q_%s: %s",
+ pConfig->zName, zPost, zErr
+ );
+ sqlite3_free(zErr);
+ }
+
+ return rc;
+}
+
+/*
+** Open a new Fts5Index handle. If the bCreate argument is true, create
+** and initialize the underlying tables
+**
+** If successful, set *pp to point to the new object and return SQLITE_OK.
+** Otherwise, set *pp to NULL and return an SQLite error code.
+*/
+int sqlite3Fts5StorageOpen(
+ Fts5Config *pConfig,
+ Fts5Index *pIndex,
+ int bCreate,
+ Fts5Storage **pp,
+ char **pzErr /* OUT: Error message */
+){
+ int rc = SQLITE_OK;
+ Fts5Storage *p; /* New object */
+ int nByte; /* Bytes of space to allocate */
+
+ nByte = sizeof(Fts5Storage) /* Fts5Storage object */
+ + pConfig->nCol * sizeof(i64); /* Fts5Storage.aTotalSize[] */
+ *pp = p = (Fts5Storage*)sqlite3_malloc(nByte);
+ if( !p ) return SQLITE_NOMEM;
+
+ memset(p, 0, nByte);
+ p->aTotalSize = (i64*)&p[1];
+ p->pConfig = pConfig;
+ p->pIndex = pIndex;
+
+ if( bCreate ){
+ if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ char *zDefn = sqlite3_malloc(32 + pConfig->nCol * 10);
+ if( zDefn==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int i;
+ int iOff = sprintf(zDefn, "id INTEGER PRIMARY KEY");
+ for(i=0; i<pConfig->nCol; i++){
+ iOff += sprintf(&zDefn[iOff], ", c%d", i);
+ }
+ rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr);
+ }
+ sqlite3_free(zDefn);
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5CreateTable(
+ pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr
+ );
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5CreateTable(
+ pConfig, "config", "k PRIMARY KEY, v", 1, pzErr
+ );
+ }
+ }
+
+ if( rc ){
+ sqlite3Fts5StorageClose(p, 0);
+ *pp = 0;
+ }
+ return rc;
+}
+
+/*
+** Close a handle opened by an earlier call to sqlite3Fts5StorageOpen().
+*/
+int sqlite3Fts5StorageClose(Fts5Storage *p, int bDestroy){
+ int rc = SQLITE_OK;
+ if( p ){
+ int i;
+
+ /* Finalize all SQL statements */
+ for(i=0; i<ArraySize(p->aStmt); i++){
+ sqlite3_finalize(p->aStmt[i]);
+ }
+
+ /* If required, remove the shadow tables from the database */
+ if( bDestroy ){
+ if( p->pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ rc = sqlite3Fts5DropTable(p->pConfig, "content");
+ }
+ if( rc==SQLITE_OK ) rc = sqlite3Fts5DropTable(p->pConfig, "docsize");
+ if( rc==SQLITE_OK ) rc = sqlite3Fts5DropTable(p->pConfig, "config");
+ }
+
+ sqlite3_free(p);
+ }
+ return rc;
+}
+
+typedef struct Fts5InsertCtx Fts5InsertCtx;
+struct Fts5InsertCtx {
+ Fts5Storage *pStorage;
+ int iCol;
+ int szCol; /* Size of column value in tokens */
+};
+
+/*
+** Tokenization callback used when inserting tokens into the FTS index.
+*/
+static int fts5StorageInsertCallback(
+ void *pContext, /* Pointer to Fts5InsertCtx object */
+ const char *pToken, /* Buffer containing token */
+ int nToken, /* Size of token in bytes */
+ int iStart, /* Start offset of token */
+ int iEnd /* End offset of token */
+){
+ Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext;
+ Fts5Index *pIdx = pCtx->pStorage->pIndex;
+ int iPos = pCtx->szCol++;
+ return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, iPos, pToken, nToken);
+}
+
+/*
+** If a row with rowid iDel is present in the %_content table, add the
+** delete-markers to the FTS index necessary to delete it. Do not actually
+** remove the %_content row at this time though.
+*/
+static int fts5StorageDeleteFromIndex(Fts5Storage *p, i64 iDel){
+ Fts5Config *pConfig = p->pConfig;
+ sqlite3_stmt *pSeek; /* SELECT to read row iDel from %_data */
+ int rc; /* Return code */
+
+ rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0);
+ if( rc==SQLITE_OK ){
+ int rc2;
+ sqlite3_bind_int64(pSeek, 1, iDel);
+ if( sqlite3_step(pSeek)==SQLITE_ROW ){
+ int iCol;
+ Fts5InsertCtx ctx;
+ ctx.pStorage = p;
+ ctx.iCol = -1;
+ rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iDel);
+ for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
+ ctx.szCol = 0;
+ rc = sqlite3Fts5Tokenize(pConfig,
+ (const char*)sqlite3_column_text(pSeek, iCol),
+ sqlite3_column_bytes(pSeek, iCol),
+ (void*)&ctx,
+ fts5StorageInsertCallback
+ );
+ p->aTotalSize[iCol-1] -= (i64)ctx.szCol;
+ }
+ p->nTotalRow--;
+ }
+ rc2 = sqlite3_reset(pSeek);
+ if( rc==SQLITE_OK ) rc = rc2;
+ }
+
+ return rc;
+}
+
+
+/*
+** Insert a record into the %_docsize table. Specifically, do:
+**
+** INSERT OR REPLACE INTO %_docsize(id, sz) VALUES(iRowid, pBuf);
+*/
+static int fts5StorageInsertDocsize(
+ Fts5Storage *p, /* Storage module to write to */
+ i64 iRowid, /* id value */
+ Fts5Buffer *pBuf /* sz value */
+){
+ sqlite3_stmt *pReplace = 0;
+ int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pReplace, 1, iRowid);
+ sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
+ sqlite3_step(pReplace);
+ rc = sqlite3_reset(pReplace);
+ }
+ return rc;
+}
+
+/*
+** Load the contents of the "averages" record from disk into the
+** p->nTotalRow and p->aTotalSize[] variables. If successful, and if
+** argument bCache is true, set the p->bTotalsValid flag to indicate
+** that the contents of aTotalSize[] and nTotalRow are valid until
+** further notice.
+**
+** Return SQLITE_OK if successful, or an SQLite error code if an error
+** occurs.
+*/
+static int fts5StorageLoadTotals(Fts5Storage *p, int bCache){
+ int rc = SQLITE_OK;
+ if( p->bTotalsValid==0 ){
+ int nCol = p->pConfig->nCol;
+ Fts5Buffer buf;
+ memset(&buf, 0, sizeof(buf));
+
+ memset(p->aTotalSize, 0, sizeof(i64) * nCol);
+ p->nTotalRow = 0;
+ rc = sqlite3Fts5IndexGetAverages(p->pIndex, &buf);
+ if( rc==SQLITE_OK && buf.n ){
+ int i = 0;
+ int iCol;
+ i += getVarint(&buf.p[i], (u64*)&p->nTotalRow);
+ for(iCol=0; i<buf.n && iCol<nCol; iCol++){
+ i += getVarint(&buf.p[i], (u64*)&p->aTotalSize[iCol]);
+ }
+ }
+ sqlite3_free(buf.p);
+ p->bTotalsValid = bCache;
+ }
+ return rc;
+}
+
+/*
+** Store the current contents of the p->nTotalRow and p->aTotalSize[]
+** variables in the "averages" record on disk.
+**
+** Return SQLITE_OK if successful, or an SQLite error code if an error
+** occurs.
+*/
+static int fts5StorageSaveTotals(Fts5Storage *p){
+ int nCol = p->pConfig->nCol;
+ int i;
+ Fts5Buffer buf;
+ int rc = SQLITE_OK;
+ memset(&buf, 0, sizeof(buf));
+
+ sqlite3Fts5BufferAppendVarint(&rc, &buf, p->nTotalRow);
+ for(i=0; i<nCol; i++){
+ sqlite3Fts5BufferAppendVarint(&rc, &buf, p->aTotalSize[i]);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5IndexSetAverages(p->pIndex, buf.p, buf.n);
+ }
+ sqlite3_free(buf.p);
+
+ return rc;
+}
+
+/*
+** Remove a row from the FTS table.
+*/
+int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel){
+ int rc;
+ sqlite3_stmt *pDel;
+
+ rc = fts5StorageLoadTotals(p, 1);
+
+ /* Delete the index records */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageDeleteFromIndex(p, iDel);
+ }
+
+ /* Delete the %_docsize record */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pDel, 1, iDel);
+ sqlite3_step(pDel);
+ rc = sqlite3_reset(pDel);
+ }
+
+ /* Delete the %_content record */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pDel, 1, iDel);
+ sqlite3_step(pDel);
+ rc = sqlite3_reset(pDel);
+ }
+
+ /* Write the averages record */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageSaveTotals(p);
+ }
+
+ return rc;
+}
+
+int sqlite3Fts5StorageSpecialDelete(
+ Fts5Storage *p,
+ i64 iDel,
+ sqlite3_value **apVal
+){
+ Fts5Config *pConfig = p->pConfig;
+ int rc;
+ sqlite3_stmt *pDel;
+
+ assert( pConfig->eContent!=FTS5_CONTENT_NORMAL );
+ rc = fts5StorageLoadTotals(p, 1);
+
+ /* Delete the index records */
+ if( rc==SQLITE_OK ){
+ int iCol;
+ Fts5InsertCtx ctx;
+ ctx.pStorage = p;
+ ctx.iCol = -1;
+
+ rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iDel);
+ for(iCol=0; rc==SQLITE_OK && iCol<pConfig->nCol; iCol++){
+ ctx.szCol = 0;
+ rc = sqlite3Fts5Tokenize(pConfig,
+ (const char*)sqlite3_value_text(apVal[iCol]),
+ sqlite3_value_bytes(apVal[iCol]),
+ (void*)&ctx,
+ fts5StorageInsertCallback
+ );
+ p->aTotalSize[iCol] -= (i64)ctx.szCol;
+ }
+ p->nTotalRow--;
+ }
+
+ /* Delete the %_docsize record */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pDel, 1, iDel);
+ sqlite3_step(pDel);
+ rc = sqlite3_reset(pDel);
+ }
+
+ /* Write the averages record */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageSaveTotals(p);
+ }
+
+ return rc;
+}
+
+/*
+** Delete all entries in the FTS5 index.
+*/
+int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){
+ Fts5Config *pConfig = p->pConfig;
+ int rc;
+
+ /* Delete the contents of the %_data and %_docsize tables. */
+ rc = fts5ExecPrintf(pConfig->db, 0,
+ "DELETE FROM %Q.'%q_data';"
+ "DELETE FROM %Q.'%q_docsize';",
+ pConfig->zDb, pConfig->zName,
+ pConfig->zDb, pConfig->zName
+ );
+
+ /* Reinitialize the %_data table. This call creates the initial structure
+ ** and averages records. */
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5IndexReinit(p->pIndex);
+ }
+ return rc;
+}
+
+int sqlite3Fts5StorageRebuild(Fts5Storage *p){
+ Fts5Buffer buf = {0,0,0};
+ Fts5Config *pConfig = p->pConfig;
+ sqlite3_stmt *pScan = 0;
+ Fts5InsertCtx ctx;
+ int rc;
+
+ memset(&ctx, 0, sizeof(Fts5InsertCtx));
+ ctx.pStorage = p;
+ rc = sqlite3Fts5StorageDeleteAll(p);
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageLoadTotals(p, 1);
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN_ASC, &pScan, 0);
+ }
+
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){
+ i64 iRowid = sqlite3_column_int64(pScan, 0);
+
+ sqlite3Fts5BufferZero(&buf);
+ rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iRowid);
+ for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
+ ctx.szCol = 0;
+ rc = sqlite3Fts5Tokenize(pConfig,
+ (const char*)sqlite3_column_text(pScan, ctx.iCol+1),
+ sqlite3_column_bytes(pScan, ctx.iCol+1),
+ (void*)&ctx,
+ fts5StorageInsertCallback
+ );
+ sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
+ p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
+ }
+ p->nTotalRow++;
+
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageInsertDocsize(p, iRowid, &buf);
+ }
+ }
+ sqlite3_free(buf.p);
+
+ /* Write the averages record */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageSaveTotals(p);
+ }
+ return rc;
+}
+
+int sqlite3Fts5StorageOptimize(Fts5Storage *p){
+ return sqlite3Fts5IndexOptimize(p->pIndex);
+}
+
+/*
+** Allocate a new rowid. This is used for "external content" tables when
+** a NULL value is inserted into the rowid column. The new rowid is allocated
+** by inserting a dummy row into the %_docsize table. The dummy will be
+** overwritten later.
+*/
+static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){
+ sqlite3_stmt *pReplace = 0;
+ int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_null(pReplace, 1);
+ sqlite3_bind_null(pReplace, 2);
+ sqlite3_step(pReplace);
+ rc = sqlite3_reset(pReplace);
+ }
+ if( rc==SQLITE_OK ){
+ *piRowid = sqlite3_last_insert_rowid(p->pConfig->db);
+ }
+ return rc;
+}
+
+/*
+** Insert a new row into the FTS table.
+*/
+int sqlite3Fts5StorageInsert(
+ Fts5Storage *p, /* Storage module to write to */
+ sqlite3_value **apVal, /* Array of values passed to xUpdate() */
+ int eConflict, /* on conflict clause */
+ i64 *piRowid /* OUT: rowid of new record */
+){
+ Fts5Config *pConfig = p->pConfig;
+ int rc = SQLITE_OK; /* Return code */
+ sqlite3_stmt *pInsert; /* Statement used to write %_content table */
+ int eStmt = 0; /* Type of statement used on %_content */
+ int i; /* Counter variable */
+ Fts5InsertCtx ctx; /* Tokenization callback context object */
+ Fts5Buffer buf; /* Buffer used to build up %_docsize blob */
+
+ memset(&buf, 0, sizeof(Fts5Buffer));
+ rc = fts5StorageLoadTotals(p, 1);
+
+ /* Insert the new row into the %_content table. */
+ if( rc==SQLITE_OK ){
+ if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){
+ if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){
+ *piRowid = sqlite3_value_int64(apVal[1]);
+ }else{
+ rc = fts5StorageNewRowid(p, piRowid);
+ }
+ }else{
+ if( eConflict==SQLITE_REPLACE ){
+ eStmt = FTS5_STMT_REPLACE_CONTENT;
+ if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){
+ rc = fts5StorageDeleteFromIndex(p, sqlite3_value_int64(apVal[1]));
+ }
+ }else{
+ eStmt = FTS5_STMT_INSERT_CONTENT;
+ }
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageGetStmt(p, eStmt, &pInsert, 0);
+ }
+ for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
+ rc = sqlite3_bind_value(pInsert, i, apVal[i]);
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_step(pInsert);
+ rc = sqlite3_reset(pInsert);
+ }
+ *piRowid = sqlite3_last_insert_rowid(pConfig->db);
+ }
+ }
+
+ /* Add new entries to the FTS index */
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5IndexBeginWrite(p->pIndex, *piRowid);
+ ctx.pStorage = p;
+ }
+ for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
+ ctx.szCol = 0;
+ rc = sqlite3Fts5Tokenize(pConfig,
+ (const char*)sqlite3_value_text(apVal[ctx.iCol+2]),
+ sqlite3_value_bytes(apVal[ctx.iCol+2]),
+ (void*)&ctx,
+ fts5StorageInsertCallback
+ );
+ sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
+ p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
+ }
+ p->nTotalRow++;
+
+ /* Write the %_docsize record */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageInsertDocsize(p, *piRowid, &buf);
+ }
+ sqlite3_free(buf.p);
+
+ /* Write the averages record */
+ if( rc==SQLITE_OK ){
+ rc = fts5StorageSaveTotals(p);
+ }
+
+ return rc;
+}
+
+static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){
+ Fts5Config *pConfig = p->pConfig;
+ char *zSql;
+ int rc;
+
+ zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'",
+ pConfig->zDb, pConfig->zName, zSuffix
+ );
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ sqlite3_stmt *pCnt = 0;
+ rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0);
+ if( rc==SQLITE_OK ){
+ if( SQLITE_ROW==sqlite3_step(pCnt) ){
+ *pnRow = sqlite3_column_int64(pCnt, 0);
+ }
+ rc = sqlite3_finalize(pCnt);
+ }
+ }
+
+ sqlite3_free(zSql);
+ return rc;
+}
+
+/*
+** Context object used by sqlite3Fts5StorageIntegrity().
+*/
+typedef struct Fts5IntegrityCtx Fts5IntegrityCtx;
+struct Fts5IntegrityCtx {
+ i64 iRowid;
+ int iCol;
+ int szCol;
+ u64 cksum;
+ Fts5Config *pConfig;
+};
+
+/*
+** Tokenization callback used by integrity check.
+*/
+static int fts5StorageIntegrityCallback(
+ void *pContext, /* Pointer to Fts5InsertCtx object */
+ const char *pToken, /* Buffer containing token */
+ int nToken, /* Size of token in bytes */
+ int iStart, /* Start offset of token */
+ int iEnd /* End offset of token */
+){
+ Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext;
+ int iPos = pCtx->szCol++;
+ pCtx->cksum ^= sqlite3Fts5IndexCksum(
+ pCtx->pConfig, pCtx->iRowid, pCtx->iCol, iPos, pToken, nToken
+ );
+ return SQLITE_OK;
+}
+
+/*
+** Check that the contents of the FTS index match that of the %_content
+** table. Return SQLITE_OK if they do, or SQLITE_CORRUPT if not. Return
+** some other SQLite error code if an error occurs while attempting to
+** determine this.
+*/
+int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
+ Fts5Config *pConfig = p->pConfig;
+ int rc; /* Return code */
+ int *aColSize; /* Array of size pConfig->nCol */
+ i64 *aTotalSize; /* Array of size pConfig->nCol */
+ Fts5IntegrityCtx ctx;
+ sqlite3_stmt *pScan;
+
+ memset(&ctx, 0, sizeof(Fts5IntegrityCtx));
+ ctx.pConfig = p->pConfig;
+ aTotalSize = (i64*)sqlite3_malloc(pConfig->nCol * (sizeof(int)+sizeof(i64)));
+ if( !aTotalSize ) return SQLITE_NOMEM;
+ aColSize = (int*)&aTotalSize[pConfig->nCol];
+ memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol);
+
+ /* Generate the expected index checksum based on the contents of the
+ ** %_content table. This block stores the checksum in ctx.cksum. */
+ rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN_ASC, &pScan, 0);
+ if( rc==SQLITE_OK ){
+ int rc2;
+ while( SQLITE_ROW==sqlite3_step(pScan) ){
+ int i;
+ ctx.iRowid = sqlite3_column_int64(pScan, 0);
+ ctx.szCol = 0;
+ rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
+ for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
+ ctx.iCol = i;
+ ctx.szCol = 0;
+ rc = sqlite3Fts5Tokenize(
+ pConfig,
+ (const char*)sqlite3_column_text(pScan, i+1),
+ sqlite3_column_bytes(pScan, i+1),
+ (void*)&ctx,
+ fts5StorageIntegrityCallback
+ );
+ if( ctx.szCol!=aColSize[i] ) rc = FTS5_CORRUPT;
+ aTotalSize[i] += ctx.szCol;
+ }
+ if( rc!=SQLITE_OK ) break;
+ }
+ rc2 = sqlite3_reset(pScan);
+ if( rc==SQLITE_OK ) rc = rc2;
+ }
+
+ /* Test that the "totals" (sometimes called "averages") record looks Ok */
+ if( rc==SQLITE_OK ){
+ int i;
+ rc = fts5StorageLoadTotals(p, 0);
+ for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
+ if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT;
+ }
+ }
+
+ /* Check that the %_docsize and %_content tables contain the expected
+ ** number of rows. */
+ if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ i64 nRow;
+ rc = fts5StorageCount(p, "content", &nRow);
+ if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
+ }
+ if( rc==SQLITE_OK ){
+ i64 nRow;
+ rc = fts5StorageCount(p, "docsize", &nRow);
+ if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
+ }
+
+ /* Pass the expected checksum down to the FTS index module. It will
+ ** verify, amongst other things, that it matches the checksum generated by
+ ** inspecting the index itself. */
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum);
+ }
+
+ sqlite3_free(aTotalSize);
+ return rc;
+}
+
+/*
+** Obtain an SQLite statement handle that may be used to read data from the
+** %_content table.
+*/
+int sqlite3Fts5StorageStmt(
+ Fts5Storage *p,
+ int eStmt,
+ sqlite3_stmt **pp,
+ char **pzErrMsg
+){
+ int rc;
+ assert( eStmt==FTS5_STMT_SCAN_ASC
+ || eStmt==FTS5_STMT_SCAN_DESC
+ || eStmt==FTS5_STMT_LOOKUP
+ );
+ rc = fts5StorageGetStmt(p, eStmt, pp, pzErrMsg);
+ if( rc==SQLITE_OK ){
+ assert( p->aStmt[eStmt]==*pp );
+ p->aStmt[eStmt] = 0;
+ }
+ return rc;
+}
+
+/*
+** Release an SQLite statement handle obtained via an earlier call to
+** sqlite3Fts5StorageStmt(). The eStmt parameter passed to this function
+** must match that passed to the sqlite3Fts5StorageStmt() call.
+*/
+void sqlite3Fts5StorageStmtRelease(
+ Fts5Storage *p,
+ int eStmt,
+ sqlite3_stmt *pStmt
+){
+ assert( eStmt==FTS5_STMT_SCAN_ASC
+ || eStmt==FTS5_STMT_SCAN_DESC
+ || eStmt==FTS5_STMT_LOOKUP
+ );
+ if( p->aStmt[eStmt]==0 ){
+ sqlite3_reset(pStmt);
+ p->aStmt[eStmt] = pStmt;
+ }else{
+ sqlite3_finalize(pStmt);
+ }
+}
+
+static int fts5StorageDecodeSizeArray(
+ int *aCol, int nCol, /* Array to populate */
+ const u8 *aBlob, int nBlob /* Record to read varints from */
+){
+ int i;
+ int iOff = 0;
+ for(i=0; i<nCol; i++){
+ if( iOff>=nBlob ) return 1;
+ iOff += getVarint32(&aBlob[iOff], aCol[i]);
+ }
+ return (iOff!=nBlob);
+}
+
+/*
+** Argument aCol points to an array of integers containing one entry for
+** each table column. This function reads the %_docsize record for the
+** specified rowid and populates aCol[] with the results.
+**
+** An SQLite error code is returned if an error occurs, or SQLITE_OK
+** otherwise.
+*/
+int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){
+ int nCol = p->pConfig->nCol;
+ sqlite3_stmt *pLookup = 0;
+ int rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0);
+ if( rc==SQLITE_OK ){
+ int bCorrupt = 1;
+ sqlite3_bind_int64(pLookup, 1, iRowid);
+ if( SQLITE_ROW==sqlite3_step(pLookup) ){
+ const u8 *aBlob = sqlite3_column_blob(pLookup, 0);
+ int nBlob = sqlite3_column_bytes(pLookup, 0);
+ if( 0==fts5StorageDecodeSizeArray(aCol, nCol, aBlob, nBlob) ){
+ bCorrupt = 0;
+ }
+ }
+ rc = sqlite3_reset(pLookup);
+ if( bCorrupt && rc==SQLITE_OK ){
+ rc = FTS5_CORRUPT;
+ }
+ }
+ return rc;
+}
+
+int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){
+ int rc = fts5StorageLoadTotals(p, 0);
+ if( rc==SQLITE_OK ){
+ *pnToken = 0;
+ if( iCol<0 ){
+ int i;
+ for(i=0; i<p->pConfig->nCol; i++){
+ *pnToken += p->aTotalSize[i];
+ }
+ }else if( iCol<p->pConfig->nCol ){
+ *pnToken = p->aTotalSize[iCol];
+ }else{
+ rc = SQLITE_RANGE;
+ }
+ }
+ return rc;
+}
+
+int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){
+ int rc = fts5StorageLoadTotals(p, 0);
+ if( rc==SQLITE_OK ){
+ *pnRow = p->nTotalRow;
+ }
+ return rc;
+}
+
+/*
+** Flush any data currently held in-memory to disk.
+*/
+int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit){
+ if( bCommit && p->bTotalsValid ){
+ int rc = fts5StorageSaveTotals(p);
+ p->bTotalsValid = 0;
+ if( rc!=SQLITE_OK ) return rc;
+ }
+ return sqlite3Fts5IndexSync(p->pIndex, bCommit);
+}
+
+int sqlite3Fts5StorageRollback(Fts5Storage *p){
+ p->bTotalsValid = 0;
+ return sqlite3Fts5IndexRollback(p->pIndex);
+}
+
+int sqlite3Fts5StorageConfigValue(
+ Fts5Storage *p,
+ const char *z,
+ sqlite3_value *pVal
+){
+ sqlite3_stmt *pReplace = 0;
+ int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_CONFIG, &pReplace, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_text(pReplace, 1, z, -1, SQLITE_STATIC);
+ sqlite3_bind_value(pReplace, 2, pVal);
+ sqlite3_step(pReplace);
+ rc = sqlite3_reset(pReplace);
+ }
+ if( rc==SQLITE_OK ){
+ int iNew = p->pConfig->iCookie + 1;
+ rc = sqlite3Fts5IndexSetCookie(p->pIndex, iNew);
+ if( rc==SQLITE_OK ){
+ p->pConfig->iCookie = iNew;
+ }
+ }
+ return rc;
+}
+
+
+#endif /* SQLITE_ENABLE_FTS5 */
diff --git a/ext/fts5/fts5_tcl.c b/ext/fts5/fts5_tcl.c
new file mode 100644
index 000000000..f1c228427
--- /dev/null
+++ b/ext/fts5/fts5_tcl.c
@@ -0,0 +1,867 @@
+/*
+** 2014 Dec 01
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+*/
+
+
+#ifdef SQLITE_TEST
+#include <tcl.h>
+
+#ifdef SQLITE_ENABLE_FTS5
+
+#include "fts5.h"
+#include <string.h>
+#include <assert.h>
+
+/*************************************************************************
+** This is a copy of the first part of the SqliteDb structure in
+** tclsqlite.c. We need it here so that the get_sqlite_pointer routine
+** can extract the sqlite3* pointer from an existing Tcl SQLite
+** connection.
+*/
+struct SqliteDb {
+ sqlite3 *db;
+};
+
+/*
+** Decode a pointer to an sqlite3 object.
+*/
+static int f5tDbPointer(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **ppDb){
+ struct SqliteDb *p;
+ Tcl_CmdInfo cmdInfo;
+ char *z = Tcl_GetString(pObj);
+ if( Tcl_GetCommandInfo(interp, z, &cmdInfo) ){
+ p = (struct SqliteDb*)cmdInfo.objClientData;
+ *ppDb = p->db;
+ return TCL_OK;
+ }
+ return TCL_ERROR;
+}
+
+/* End of code that accesses the SqliteDb struct.
+**************************************************************************/
+
+static int f5tDbAndApi(
+ Tcl_Interp *interp,
+ Tcl_Obj *pObj,
+ sqlite3 **ppDb,
+ fts5_api **ppApi
+){
+ sqlite3 *db = 0;
+ int rc = f5tDbPointer(interp, pObj, &db);
+ if( rc!=TCL_OK ){
+ return TCL_ERROR;
+ }else{
+ sqlite3_stmt *pStmt = 0;
+ fts5_api *pApi = 0;
+
+ rc = sqlite3_prepare_v2(db, "SELECT fts5()", -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0);
+ return TCL_ERROR;
+ }
+
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ const void *pPtr = sqlite3_column_blob(pStmt, 0);
+ memcpy((void*)&pApi, pPtr, sizeof(pApi));
+ }
+
+ if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0);
+ return TCL_ERROR;
+ }
+
+ *ppDb = db;
+ *ppApi = pApi;
+ }
+
+ return TCL_OK;
+}
+
+typedef struct F5tFunction F5tFunction;
+struct F5tFunction {
+ Tcl_Interp *interp;
+ Tcl_Obj *pScript;
+};
+
+typedef struct F5tApi F5tApi;
+struct F5tApi {
+ const Fts5ExtensionApi *pApi;
+ Fts5Context *pFts;
+};
+
+/*
+** An object of this type is used with the xSetAuxdata() and xGetAuxdata()
+** API test wrappers. The tcl interface allows a single tcl value to be
+** saved using xSetAuxdata(). Instead of simply storing a pointer to the
+** tcl object, the code in this file wraps it in an sqlite3_malloc'd
+** instance of the following struct so that if the destructor is not
+** correctly invoked it will be reported as an SQLite memory leak.
+*/
+typedef struct F5tAuxData F5tAuxData;
+struct F5tAuxData {
+ Tcl_Obj *pObj;
+};
+
+static int xTokenizeCb(
+ void *pCtx,
+ const char *zToken, int nToken,
+ int iStart, int iEnd
+){
+ F5tFunction *p = (F5tFunction*)pCtx;
+ Tcl_Obj *pEval = Tcl_DuplicateObj(p->pScript);
+ int rc;
+
+ Tcl_IncrRefCount(pEval);
+ Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zToken, nToken));
+ Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(iStart));
+ Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(iEnd));
+
+ rc = Tcl_EvalObjEx(p->interp, pEval, 0);
+ Tcl_DecrRefCount(pEval);
+
+ return rc;
+}
+
+static int xF5tApi(void*, Tcl_Interp*, int, Tcl_Obj *CONST []);
+
+static int xQueryPhraseCb(
+ const Fts5ExtensionApi *pApi,
+ Fts5Context *pFts,
+ void *pCtx
+){
+ F5tFunction *p = (F5tFunction*)pCtx;
+ static sqlite3_int64 iCmd = 0;
+ Tcl_Obj *pEval;
+ int rc;
+
+ char zCmd[64];
+ F5tApi sApi;
+
+ sApi.pApi = pApi;
+ sApi.pFts = pFts;
+ sprintf(zCmd, "f5t_2_%lld", iCmd++);
+ Tcl_CreateObjCommand(p->interp, zCmd, xF5tApi, &sApi, 0);
+
+ pEval = Tcl_DuplicateObj(p->pScript);
+ Tcl_IncrRefCount(pEval);
+ Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zCmd, -1));
+ rc = Tcl_EvalObjEx(p->interp, pEval, 0);
+ Tcl_DecrRefCount(pEval);
+ Tcl_DeleteCommand(p->interp, zCmd);
+
+ return rc;
+}
+
+static void xSetAuxdataDestructor(void *p){
+ F5tAuxData *pData = (F5tAuxData*)p;
+ Tcl_DecrRefCount(pData->pObj);
+ sqlite3_free(pData);
+}
+
+/*
+** api sub-command...
+**
+** Description...
+*/
+static int xF5tApi(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ struct Sub {
+ const char *zName;
+ int nArg;
+ const char *zMsg;
+ } aSub[] = {
+ { "xColumnCount", 0, "" },
+ { "xRowCount", 0, "" },
+ { "xColumnTotalSize", 1, "COL" },
+ { "xTokenize", 2, "TEXT SCRIPT" },
+ { "xPhraseCount", 0, "" },
+ { "xPhraseSize", 1, "PHRASE" },
+ { "xInstCount", 0, "" },
+ { "xInst", 1, "IDX" },
+ { "xRowid", 0, "" },
+ { "xColumnText", 1, "COL" },
+ { "xColumnSize", 1, "COL" },
+ { "xQueryPhrase", 2, "PHRASE SCRIPT" },
+ { "xSetAuxdata", 1, "VALUE" },
+ { "xGetAuxdata", 1, "CLEAR" },
+ { 0, 0, 0}
+ };
+
+ int rc;
+ int iSub = 0;
+ F5tApi *p = (F5tApi*)clientData;
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND");
+ return TCL_ERROR;
+ }
+
+ rc = Tcl_GetIndexFromObjStruct(
+ interp, objv[1], aSub, sizeof(aSub[0]), "SUB-COMMAND", 0, &iSub
+ );
+ if( rc!=TCL_OK ) return rc;
+ if( aSub[iSub].nArg!=objc-2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, aSub[iSub].zMsg);
+ return TCL_ERROR;
+ }
+
+#define CASE(i,str) case i: assert( strcmp(aSub[i].zName, str)==0 );
+ switch( iSub ){
+ CASE(0, "xColumnCount") {
+ int nCol;
+ nCol = p->pApi->xColumnCount(p->pFts);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(nCol));
+ }
+ break;
+ }
+ CASE(1, "xRowCount") {
+ sqlite3_int64 nRow;
+ rc = p->pApi->xRowCount(p->pFts, &nRow);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nRow));
+ }
+ break;
+ }
+ CASE(2, "xColumnTotalSize") {
+ int iCol;
+ sqlite3_int64 nSize;
+ if( Tcl_GetIntFromObj(interp, objv[2], &iCol) ) return TCL_ERROR;
+ rc = p->pApi->xColumnTotalSize(p->pFts, iCol, &nSize);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize));
+ }
+ break;
+ }
+ CASE(3, "xTokenize") {
+ int nText;
+ char *zText = Tcl_GetStringFromObj(objv[2], &nText);
+ F5tFunction ctx;
+ ctx.interp = interp;
+ ctx.pScript = objv[3];
+ rc = p->pApi->xTokenize(p->pFts, zText, nText, &ctx, xTokenizeCb);
+ if( rc==SQLITE_OK ){
+ Tcl_ResetResult(interp);
+ }
+ return rc;
+ }
+ CASE(4, "xPhraseCount") {
+ int nPhrase;
+ nPhrase = p->pApi->xPhraseCount(p->pFts);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(nPhrase));
+ }
+ break;
+ }
+ CASE(5, "xPhraseSize") {
+ int iPhrase;
+ int sz;
+ if( Tcl_GetIntFromObj(interp, objv[2], &iPhrase) ){
+ return TCL_ERROR;
+ }
+ sz = p->pApi->xPhraseSize(p->pFts, iPhrase);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(sz));
+ }
+ break;
+ }
+ CASE(6, "xInstCount") {
+ int nInst;
+ rc = p->pApi->xInstCount(p->pFts, &nInst);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(nInst));
+ }
+ break;
+ }
+ CASE(7, "xInst") {
+ int iIdx, ip, ic, io;
+ if( Tcl_GetIntFromObj(interp, objv[2], &iIdx) ){
+ return TCL_ERROR;
+ }
+ rc = p->pApi->xInst(p->pFts, iIdx, &ip, &ic, &io);
+ if( rc==SQLITE_OK ){
+ Tcl_Obj *pList = Tcl_NewObj();
+ Tcl_ListObjAppendElement(interp, pList, Tcl_NewIntObj(ip));
+ Tcl_ListObjAppendElement(interp, pList, Tcl_NewIntObj(ic));
+ Tcl_ListObjAppendElement(interp, pList, Tcl_NewIntObj(io));
+ Tcl_SetObjResult(interp, pList);
+ }
+ break;
+ }
+ CASE(8, "xRowid") {
+ sqlite3_int64 iRowid = p->pApi->xRowid(p->pFts);
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(iRowid));
+ break;
+ }
+ CASE(9, "xColumnText") {
+ const char *z = 0;
+ int n = 0;
+ int iCol;
+ if( Tcl_GetIntFromObj(interp, objv[2], &iCol) ){
+ return TCL_ERROR;
+ }
+ rc = p->pApi->xColumnText(p->pFts, iCol, &z, &n);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(z, n));
+ }
+ break;
+ }
+ CASE(10, "xColumnSize") {
+ int n = 0;
+ int iCol;
+ if( Tcl_GetIntFromObj(interp, objv[2], &iCol) ){
+ return TCL_ERROR;
+ }
+ rc = p->pApi->xColumnSize(p->pFts, iCol, &n);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(n));
+ }
+ break;
+ }
+ CASE(11, "xQueryPhrase") {
+ int iPhrase;
+ F5tFunction ctx;
+ if( Tcl_GetIntFromObj(interp, objv[2], &iPhrase) ){
+ return TCL_ERROR;
+ }
+ ctx.interp = interp;
+ ctx.pScript = objv[3];
+ rc = p->pApi->xQueryPhrase(p->pFts, iPhrase, &ctx, xQueryPhraseCb);
+ if( rc==SQLITE_OK ){
+ Tcl_ResetResult(interp);
+ }
+ break;
+ }
+ CASE(12, "xSetAuxdata") {
+ F5tAuxData *pData = (F5tAuxData*)sqlite3_malloc(sizeof(F5tAuxData));
+ if( pData==0 ){
+ Tcl_AppendResult(interp, "out of memory", 0);
+ return TCL_ERROR;
+ }
+ pData->pObj = objv[2];
+ Tcl_IncrRefCount(pData->pObj);
+ rc = p->pApi->xSetAuxdata(p->pFts, pData, xSetAuxdataDestructor);
+ break;
+ }
+ CASE(13, "xGetAuxdata") {
+ F5tAuxData *pData;
+ int bClear;
+ if( Tcl_GetBooleanFromObj(interp, objv[2], &bClear) ){
+ return TCL_ERROR;
+ }
+ pData = (F5tAuxData*)p->pApi->xGetAuxdata(p->pFts, bClear);
+ if( pData==0 ){
+ Tcl_ResetResult(interp);
+ }else{
+ Tcl_SetObjResult(interp, pData->pObj);
+ if( bClear ){
+ xSetAuxdataDestructor((void*)pData);
+ }
+ }
+ break;
+ }
+
+ default:
+ assert( 0 );
+ break;
+ }
+#undef CASE
+
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "error in api call", 0);
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+static void xF5tFunction(
+ const Fts5ExtensionApi *pApi, /* API offered by current FTS version */
+ Fts5Context *pFts, /* First arg to pass to pApi functions */
+ sqlite3_context *pCtx, /* Context for returning result/error */
+ int nVal, /* Number of values in apVal[] array */
+ sqlite3_value **apVal /* Array of trailing arguments */
+){
+ F5tFunction *p = (F5tFunction*)pApi->xUserData(pFts);
+ Tcl_Obj *pEval; /* Script to evaluate */
+ int i;
+ int rc;
+
+ static sqlite3_int64 iCmd = 0;
+ char zCmd[64];
+ F5tApi sApi;
+ sApi.pApi = pApi;
+ sApi.pFts = pFts;
+
+ sprintf(zCmd, "f5t_%lld", iCmd++);
+ Tcl_CreateObjCommand(p->interp, zCmd, xF5tApi, &sApi, 0);
+ pEval = Tcl_DuplicateObj(p->pScript);
+ Tcl_IncrRefCount(pEval);
+ Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zCmd, -1));
+
+ for(i=0; i<nVal; i++){
+ Tcl_Obj *pObj = 0;
+ switch( sqlite3_value_type(apVal[i]) ){
+ case SQLITE_TEXT:
+ pObj = Tcl_NewStringObj((const char*)sqlite3_value_text(apVal[i]), -1);
+ break;
+ case SQLITE_BLOB:
+ pObj = Tcl_NewByteArrayObj(
+ sqlite3_value_blob(apVal[i]), sqlite3_value_bytes(apVal[i])
+ );
+ break;
+ case SQLITE_INTEGER:
+ pObj = Tcl_NewWideIntObj(sqlite3_value_int64(apVal[i]));
+ break;
+ case SQLITE_FLOAT:
+ pObj = Tcl_NewDoubleObj(sqlite3_value_double(apVal[i]));
+ break;
+ default:
+ pObj = Tcl_NewObj();
+ break;
+ }
+ Tcl_ListObjAppendElement(p->interp, pEval, pObj);
+ }
+
+ rc = Tcl_EvalObjEx(p->interp, pEval, TCL_GLOBAL_ONLY);
+ Tcl_DecrRefCount(pEval);
+ Tcl_DeleteCommand(p->interp, zCmd);
+
+ if( rc!=TCL_OK ){
+ sqlite3_result_error(pCtx, Tcl_GetStringResult(p->interp), -1);
+ }else{
+ Tcl_Obj *pVar = Tcl_GetObjResult(p->interp);
+ int n;
+ const char *zType = (pVar->typePtr ? pVar->typePtr->name : "");
+ char c = zType[0];
+ if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){
+ /* Only return a BLOB type if the Tcl variable is a bytearray and
+ ** has no string representation. */
+ unsigned char *data = Tcl_GetByteArrayFromObj(pVar, &n);
+ sqlite3_result_blob(pCtx, data, n, SQLITE_TRANSIENT);
+ }else if( c=='b' && strcmp(zType,"boolean")==0 ){
+ Tcl_GetIntFromObj(0, pVar, &n);
+ sqlite3_result_int(pCtx, n);
+ }else if( c=='d' && strcmp(zType,"double")==0 ){
+ double r;
+ Tcl_GetDoubleFromObj(0, pVar, &r);
+ sqlite3_result_double(pCtx, r);
+ }else if( (c=='w' && strcmp(zType,"wideInt")==0) ||
+ (c=='i' && strcmp(zType,"int")==0) ){
+ Tcl_WideInt v;
+ Tcl_GetWideIntFromObj(0, pVar, &v);
+ sqlite3_result_int64(pCtx, v);
+ }else{
+ unsigned char *data = (unsigned char *)Tcl_GetStringFromObj(pVar, &n);
+ sqlite3_result_text(pCtx, (char *)data, n, SQLITE_TRANSIENT);
+ }
+ }
+}
+
+static void xF5tDestroy(void *pCtx){
+ F5tFunction *p = (F5tFunction*)pCtx;
+ Tcl_DecrRefCount(p->pScript);
+ ckfree(p);
+}
+
+/*
+** sqlite3_fts5_create_function DB NAME SCRIPT
+**
+** Description...
+*/
+static int f5tCreateFunction(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ char *zName;
+ Tcl_Obj *pScript;
+ sqlite3 *db = 0;
+ fts5_api *pApi = 0;
+ F5tFunction *pCtx = 0;
+ int rc;
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB NAME SCRIPT");
+ return TCL_ERROR;
+ }
+ if( f5tDbAndApi(interp, objv[1], &db, &pApi) ) return TCL_ERROR;
+
+ zName = Tcl_GetString(objv[2]);
+ pScript = objv[3];
+ pCtx = (F5tFunction*)ckalloc(sizeof(F5tFunction));
+ pCtx->interp = interp;
+ pCtx->pScript = pScript;
+ Tcl_IncrRefCount(pScript);
+
+ rc = pApi->xCreateFunction(
+ pApi, zName, (void*)pCtx, xF5tFunction, xF5tDestroy
+ );
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "error: ", sqlite3_errmsg(db), 0);
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+typedef struct F5tTokenizeCtx F5tTokenizeCtx;
+struct F5tTokenizeCtx {
+ Tcl_Obj *pRet;
+ int bSubst;
+ const char *zInput;
+};
+
+static int xTokenizeCb2(
+ void *pCtx,
+ const char *zToken, int nToken,
+ int iStart, int iEnd
+){
+ F5tTokenizeCtx *p = (F5tTokenizeCtx*)pCtx;
+ if( p->bSubst ){
+ Tcl_ListObjAppendElement(0, p->pRet, Tcl_NewStringObj(zToken, nToken));
+ Tcl_ListObjAppendElement(
+ 0, p->pRet, Tcl_NewStringObj(&p->zInput[iStart], iEnd-iStart)
+ );
+ }else{
+ Tcl_ListObjAppendElement(0, p->pRet, Tcl_NewStringObj(zToken, nToken));
+ Tcl_ListObjAppendElement(0, p->pRet, Tcl_NewIntObj(iStart));
+ Tcl_ListObjAppendElement(0, p->pRet, Tcl_NewIntObj(iEnd));
+ }
+ return SQLITE_OK;
+}
+
+
+/*
+** sqlite3_fts5_tokenize DB TOKENIZER TEXT
+**
+** Description...
+*/
+static int f5tTokenize(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ char *zText;
+ int nText;
+ sqlite3 *db = 0;
+ fts5_api *pApi = 0;
+ Fts5Tokenizer *pTok = 0;
+ fts5_tokenizer tokenizer;
+ Tcl_Obj *pRet = 0;
+ void *pUserdata;
+ int rc;
+
+ int nArg;
+ const char **azArg;
+ F5tTokenizeCtx ctx;
+
+ if( objc!=4 && objc!=5 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "?-subst? DB NAME TEXT");
+ return TCL_ERROR;
+ }
+ if( objc==5 ){
+ char *zOpt = Tcl_GetString(objv[1]);
+ if( strcmp("-subst", zOpt) ){
+ Tcl_AppendResult(interp, "unrecognized option: ", zOpt, 0);
+ return TCL_ERROR;
+ }
+ }
+ if( f5tDbAndApi(interp, objv[objc-3], &db, &pApi) ) return TCL_ERROR;
+ if( Tcl_SplitList(interp, Tcl_GetString(objv[objc-2]), &nArg, &azArg) ){
+ return TCL_ERROR;
+ }
+ if( nArg==0 ){
+ Tcl_AppendResult(interp, "no such tokenizer: ", 0);
+ Tcl_Free((void*)azArg);
+ return TCL_ERROR;
+ }
+ zText = Tcl_GetStringFromObj(objv[objc-1], &nText);
+
+ rc = pApi->xFindTokenizer(pApi, azArg[0], &pUserdata, &tokenizer);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "no such tokenizer: ", azArg[0], 0);
+ return TCL_ERROR;
+ }
+
+ rc = tokenizer.xCreate(pUserdata, &azArg[1], nArg-1, &pTok);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "error in tokenizer.xCreate()", 0);
+ return TCL_ERROR;
+ }
+
+ pRet = Tcl_NewObj();
+ Tcl_IncrRefCount(pRet);
+ ctx.bSubst = (objc==5);
+ ctx.pRet = pRet;
+ ctx.zInput = zText;
+ rc = tokenizer.xTokenize(pTok, (void*)&ctx, zText, nText, xTokenizeCb2);
+ tokenizer.xDelete(pTok);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "error in tokenizer.xTokenize()", 0);
+ Tcl_DecrRefCount(pRet);
+ return TCL_ERROR;
+ }
+
+
+ Tcl_Free((void*)azArg);
+ Tcl_SetObjResult(interp, pRet);
+ Tcl_DecrRefCount(pRet);
+ return TCL_OK;
+}
+
+/*************************************************************************
+** Start of tokenizer wrapper.
+*/
+
+typedef struct F5tTokenizerContext F5tTokenizerContext;
+typedef struct F5tTokenizerCb F5tTokenizerCb;
+typedef struct F5tTokenizerModule F5tTokenizerModule;
+typedef struct F5tTokenizerModule F5tTokenizerInstance;
+
+struct F5tTokenizerContext {
+ void *pCtx;
+ int (*xToken)(void*, const char*, int, int, int);
+};
+
+struct F5tTokenizerModule {
+ Tcl_Interp *interp;
+ Tcl_Obj *pScript;
+ F5tTokenizerContext *pContext;
+};
+
+static int f5tTokenizerCreate(
+ void *pCtx,
+ const char **azArg,
+ int nArg,
+ Fts5Tokenizer **ppOut
+){
+ F5tTokenizerModule *pMod = (F5tTokenizerModule*)pCtx;
+ Tcl_Obj *pEval;
+ int rc = TCL_OK;
+ int i;
+
+ pEval = Tcl_DuplicateObj(pMod->pScript);
+ Tcl_IncrRefCount(pEval);
+ for(i=0; rc==TCL_OK && i<nArg; i++){
+ Tcl_Obj *pObj = Tcl_NewStringObj(azArg[i], -1);
+ rc = Tcl_ListObjAppendElement(pMod->interp, pEval, pObj);
+ }
+
+ if( rc==TCL_OK ){
+ rc = Tcl_EvalObjEx(pMod->interp, pEval, TCL_GLOBAL_ONLY);
+ }
+ Tcl_DecrRefCount(pEval);
+
+ if( rc==TCL_OK ){
+ F5tTokenizerInstance *pInst = ckalloc(sizeof(F5tTokenizerInstance));
+ memset(pInst, 0, sizeof(F5tTokenizerInstance));
+ pInst->interp = pMod->interp;
+ pInst->pScript = Tcl_GetObjResult(pMod->interp);
+ pInst->pContext = pMod->pContext;
+ Tcl_IncrRefCount(pInst->pScript);
+ *ppOut = (Fts5Tokenizer*)pInst;
+ }
+
+ return rc;
+}
+
+
+static void f5tTokenizerDelete(Fts5Tokenizer *p){
+ F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p;
+ Tcl_DecrRefCount(pInst->pScript);
+ ckfree(pInst);
+}
+
+static int f5tTokenizerTokenize(
+ Fts5Tokenizer *p,
+ void *pCtx,
+ const char *pText, int nText,
+ int (*xToken)(void*, const char*, int, int, int)
+){
+ F5tTokenizerInstance *pInst = (F5tTokenizerInstance*)p;
+ void *pOldCtx;
+ int (*xOldToken)(void*, const char*, int, int, int);
+ Tcl_Obj *pEval;
+ int rc;
+
+ pOldCtx = pInst->pContext->pCtx;
+ xOldToken = pInst->pContext->xToken;
+
+ pEval = Tcl_DuplicateObj(pInst->pScript);
+ Tcl_IncrRefCount(pEval);
+ rc = Tcl_ListObjAppendElement(
+ pInst->interp, pEval, Tcl_NewStringObj(pText, nText)
+ );
+ if( rc==TCL_OK ){
+ rc = Tcl_EvalObjEx(pInst->interp, pEval, TCL_GLOBAL_ONLY);
+ }
+ Tcl_DecrRefCount(pEval);
+
+ pInst->pContext->pCtx = pOldCtx;
+ pInst->pContext->xToken = xOldToken;
+ return rc;
+}
+
+extern const char *sqlite3ErrName(int);
+
+/*
+** sqlite3_fts5_token TEXT START END POS
+*/
+static int f5tTokenizerReturn(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ F5tTokenizerContext *p = (F5tTokenizerContext*)clientData;
+ int iStart;
+ int iEnd;
+ int nToken;
+ char *zToken;
+ int rc;
+
+ assert( p );
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "TEXT START END");
+ return TCL_ERROR;
+ }
+ if( p->xToken==0 ){
+ Tcl_AppendResult(interp,
+ "sqlite3_fts5_token may only be used by tokenizer callback", 0
+ );
+ return TCL_ERROR;
+ }
+
+ zToken = Tcl_GetStringFromObj(objv[1], &nToken);
+ if( Tcl_GetIntFromObj(interp, objv[2], &iStart)
+ || Tcl_GetIntFromObj(interp, objv[3], &iEnd)
+ ){
+ return TCL_ERROR;
+ }
+
+ rc = p->xToken(p->pCtx, zToken, nToken, iStart, iEnd);
+ Tcl_SetResult(interp, (char*)sqlite3ErrName(rc), TCL_VOLATILE);
+ return TCL_OK;
+}
+
+static void f5tDelTokenizer(void *pCtx){
+ F5tTokenizerModule *pMod = (F5tTokenizerModule*)pCtx;
+ Tcl_DecrRefCount(pMod->pScript);
+ ckfree(pMod);
+}
+
+/*
+** sqlite3_fts5_create_tokenizer DB NAME SCRIPT
+**
+** Register a tokenizer named NAME implemented by script SCRIPT. When
+** a tokenizer instance is created (fts5_tokenizer.xCreate), any tokenizer
+** arguments are appended to SCRIPT and the result executed.
+**
+** The value returned by (SCRIPT + args) is itself a tcl script. This
+** script - call it SCRIPT2 - is executed to tokenize text using the
+** tokenizer instance "returned" by SCRIPT. Specifically, to tokenize
+** text SCRIPT2 is invoked with a single argument appended to it - the
+** text to tokenize.
+**
+** SCRIPT2 should invoke the [sqlite3_fts5_token] command once for each
+** token within the tokenized text.
+*/
+static int f5tCreateTokenizer(
+ ClientData clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ F5tTokenizerContext *pContext = (F5tTokenizerContext*)clientData;
+ sqlite3 *db;
+ fts5_api *pApi;
+ char *zName;
+ Tcl_Obj *pScript;
+ fts5_tokenizer t;
+ F5tTokenizerModule *pMod;
+ int rc;
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB NAME SCRIPT");
+ return TCL_ERROR;
+ }
+ if( f5tDbAndApi(interp, objv[1], &db, &pApi) ){
+ return TCL_ERROR;
+ }
+ zName = Tcl_GetString(objv[2]);
+ pScript = objv[3];
+
+ t.xCreate = f5tTokenizerCreate;
+ t.xTokenize = f5tTokenizerTokenize;
+ t.xDelete = f5tTokenizerDelete;
+
+ pMod = (F5tTokenizerModule*)ckalloc(sizeof(F5tTokenizerModule));
+ pMod->interp = interp;
+ pMod->pScript = pScript;
+ pMod->pContext = pContext;
+ Tcl_IncrRefCount(pScript);
+ rc = pApi->xCreateTokenizer(pApi, zName, (void*)pMod, &t, f5tDelTokenizer);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "error in fts5_api.xCreateTokenizer()", 0);
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+static void xF5tFree(ClientData clientData){
+ ckfree(clientData);
+}
+
+/*
+** Entry point.
+*/
+int Fts5tcl_Init(Tcl_Interp *interp){
+ static struct Cmd {
+ char *zName;
+ Tcl_ObjCmdProc *xProc;
+ int bTokenizeCtx;
+ } aCmd[] = {
+ { "sqlite3_fts5_create_tokenizer", f5tCreateTokenizer, 1 },
+ { "sqlite3_fts5_token", f5tTokenizerReturn, 1 },
+ { "sqlite3_fts5_tokenize", f5tTokenize, 0 },
+ { "sqlite3_fts5_create_function", f5tCreateFunction, 0 }
+ };
+ int i;
+ F5tTokenizerContext *pContext;
+
+ pContext = ckalloc(sizeof(F5tTokenizerContext));
+ memset(pContext, 0, sizeof(*pContext));
+
+ for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
+ struct Cmd *p = &aCmd[i];
+ void *pCtx = 0;
+ if( p->bTokenizeCtx ) pCtx = (void*)pContext;
+ Tcl_CreateObjCommand(interp, p->zName, p->xProc, pCtx, (i ? 0 : xF5tFree));
+ }
+
+ return TCL_OK;
+}
+#else /* SQLITE_ENABLE_FTS5 */
+int Fts5tcl_Init(Tcl_Interp *interp){
+ return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_FTS5 */
+#endif /* SQLITE_TEST */
diff --git a/ext/fts5/fts5_tokenize.c b/ext/fts5/fts5_tokenize.c
new file mode 100644
index 000000000..3f4261c69
--- /dev/null
+++ b/ext/fts5/fts5_tokenize.c
@@ -0,0 +1,1230 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+*/
+
+#if defined(SQLITE_ENABLE_FTS5)
+
+#include "fts5.h"
+#include <string.h>
+#include <assert.h>
+
+/**************************************************************************
+** Start of ascii tokenizer implementation.
+*/
+
+/*
+** For tokenizers with no "unicode" modifier, the set of token characters
+** is the same as the set of ASCII range alphanumeric characters.
+*/
+static unsigned char aAsciiTokenChar[128] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00..0x0F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10..0x1F */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20..0x2F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 0x30..0x3F */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40..0x4F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 0x50..0x5F */
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60..0x6F */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 0x70..0x7F */
+};
+
+typedef struct AsciiTokenizer AsciiTokenizer;
+struct AsciiTokenizer {
+ unsigned char aTokenChar[128];
+};
+
+static void fts5AsciiAddExceptions(
+ AsciiTokenizer *p,
+ const char *zArg,
+ int bTokenChars
+){
+ int i;
+ for(i=0; zArg[i]; i++){
+ if( (zArg[i] & 0x80)==0 ){
+ p->aTokenChar[(int)zArg[i]] = (unsigned char)bTokenChars;
+ }
+ }
+}
+
+/*
+** Create a "ascii" tokenizer.
+*/
+static int fts5AsciiCreate(
+ void *pCtx,
+ const char **azArg, int nArg,
+ Fts5Tokenizer **ppOut
+){
+ int rc = SQLITE_OK;
+ AsciiTokenizer *p = 0;
+ if( nArg%2 ){
+ rc = SQLITE_ERROR;
+ }else{
+ p = sqlite3_malloc(sizeof(AsciiTokenizer));
+ if( p==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int i;
+ memset(p, 0, sizeof(AsciiTokenizer));
+ memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar));
+ for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
+ const char *zArg = azArg[i+1];
+ if( 0==sqlite3_stricmp(azArg[i], "tokenchars") ){
+ fts5AsciiAddExceptions(p, zArg, 1);
+ }else
+ if( 0==sqlite3_stricmp(azArg[i], "separators") ){
+ fts5AsciiAddExceptions(p, zArg, 0);
+ }else{
+ rc = SQLITE_ERROR;
+ }
+ }
+ }
+ }
+
+ *ppOut = (Fts5Tokenizer*)p;
+ return rc;
+}
+
+/*
+** Delete a "ascii" tokenizer.
+*/
+static void fts5AsciiDelete(Fts5Tokenizer *p){
+ sqlite3_free(p);
+}
+
+
+static void asciiFold(char *aOut, const char *aIn, int nByte){
+ int i;
+ for(i=0; i<nByte; i++){
+ char c = aIn[i];
+ if( c>='A' && c<='Z' ) c += 32;
+ aOut[i] = c;
+ }
+}
+
+/*
+** Tokenize some text using the ascii tokenizer.
+*/
+static int fts5AsciiTokenize(
+ Fts5Tokenizer *pTokenizer,
+ void *pCtx,
+ const char *pText, int nText,
+ int (*xToken)(void*, const char*, int nToken, int iStart, int iEnd)
+){
+ AsciiTokenizer *p = (AsciiTokenizer*)pTokenizer;
+ int rc = SQLITE_OK;
+ int ie;
+ int is = 0;
+
+ char aFold[64];
+ int nFold = sizeof(aFold);
+ char *pFold = aFold;
+ unsigned char *a = p->aTokenChar;
+
+ while( is<nText && rc==SQLITE_OK ){
+ int nByte;
+
+ /* Skip any leading divider characters. */
+ while( is<nText && ((pText[is]&0x80)==0 && a[(int)pText[is]]==0) ){
+ is++;
+ }
+ if( is==nText ) break;
+
+ /* Count the token characters */
+ ie = is+1;
+ while( ie<nText && ((pText[ie]&0x80) || a[(int)pText[ie]] ) ){
+ ie++;
+ }
+
+ /* Fold to lower case */
+ nByte = ie-is;
+ if( nByte>nFold ){
+ if( pFold!=aFold ) sqlite3_free(pFold);
+ pFold = sqlite3_malloc(nByte*2);
+ if( pFold==0 ){
+ rc = SQLITE_NOMEM;
+ break;
+ }
+ nFold = nByte*2;
+ }
+ asciiFold(pFold, &pText[is], nByte);
+
+ /* Invoke the token callback */
+ rc = xToken(pCtx, pFold, nByte, is, ie);
+ is = ie+1;
+ }
+
+ if( pFold!=aFold ) sqlite3_free(pFold);
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ return rc;
+}
+
+/**************************************************************************
+** Start of unicode61 tokenizer implementation.
+*/
+
+/*
+** Functions in fts5_unicode2.c.
+*/
+int sqlite3Fts5UnicodeIsalnum(int c);
+int sqlite3Fts5UnicodeIsdiacritic(int c);
+int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic);
+
+
+/*
+** The following two macros - READ_UTF8 and WRITE_UTF8 - have been copied
+** from the sqlite3 source file utf.c. If this file is compiled as part
+** of the amalgamation, they are not required.
+*/
+#ifndef SQLITE_AMALGAMATION
+
+static const unsigned char sqlite3Utf8Trans1[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
+};
+
+#define READ_UTF8(zIn, zTerm, c) \
+ c = *(zIn++); \
+ if( c>=0xc0 ){ \
+ c = sqlite3Utf8Trans1[c-0xc0]; \
+ while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
+ c = (c<<6) + (0x3f & *(zIn++)); \
+ } \
+ if( c<0x80 \
+ || (c&0xFFFFF800)==0xD800 \
+ || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \
+ }
+
+
+#define WRITE_UTF8(zOut, c) { \
+ if( c<0x00080 ){ \
+ *zOut++ = (unsigned char)(c&0xFF); \
+ } \
+ else if( c<0x00800 ){ \
+ *zOut++ = 0xC0 + (unsigned char)((c>>6)&0x1F); \
+ *zOut++ = 0x80 + (unsigned char)(c & 0x3F); \
+ } \
+ else if( c<0x10000 ){ \
+ *zOut++ = 0xE0 + (unsigned char)((c>>12)&0x0F); \
+ *zOut++ = 0x80 + (unsigned char)((c>>6) & 0x3F); \
+ *zOut++ = 0x80 + (unsigned char)(c & 0x3F); \
+ }else{ \
+ *zOut++ = 0xF0 + (unsigned char)((c>>18) & 0x07); \
+ *zOut++ = 0x80 + (unsigned char)((c>>12) & 0x3F); \
+ *zOut++ = 0x80 + (unsigned char)((c>>6) & 0x3F); \
+ *zOut++ = 0x80 + (unsigned char)(c & 0x3F); \
+ } \
+}
+
+#endif /* ifndef SQLITE_AMALGAMATION */
+
+typedef struct Unicode61Tokenizer Unicode61Tokenizer;
+struct Unicode61Tokenizer {
+ unsigned char aTokenChar[128]; /* ASCII range token characters */
+ char *aFold; /* Buffer to fold text into */
+ int nFold; /* Size of aFold[] in bytes */
+ int bRemoveDiacritic; /* True if remove_diacritics=1 is set */
+ int nException;
+ int *aiException;
+};
+
+static int fts5UnicodeAddExceptions(
+ Unicode61Tokenizer *p, /* Tokenizer object */
+ const char *z, /* Characters to treat as exceptions */
+ int bTokenChars /* 1 for 'tokenchars', 0 for 'separators' */
+){
+ int rc = SQLITE_OK;
+ int n = strlen(z);
+ int *aNew;
+
+ if( n>0 ){
+ aNew = (int*)sqlite3_realloc(p->aiException, (n+p->nException)*sizeof(int));
+ if( aNew ){
+ int nNew = p->nException;
+ const unsigned char *zCsr = (const unsigned char*)z;
+ const unsigned char *zTerm = (const unsigned char*)&z[n];
+ while( zCsr<zTerm ){
+ int iCode;
+ int bToken;
+ READ_UTF8(zCsr, zTerm, iCode);
+ if( iCode<128 ){
+ p->aTokenChar[iCode] = bTokenChars;
+ }else{
+ bToken = sqlite3Fts5UnicodeIsalnum(iCode);
+ assert( (bToken==0 || bToken==1) );
+ assert( (bTokenChars==0 || bTokenChars==1) );
+ if( bToken!=bTokenChars && sqlite3Fts5UnicodeIsdiacritic(iCode)==0 ){
+ int i;
+ for(i=0; i<nNew; i++){
+ if( aNew[i]>iCode ) break;
+ }
+ memmove(&aNew[i+1], &aNew[i], (nNew-i)*sizeof(int));
+ aNew[i] = iCode;
+ nNew++;
+ }
+ }
+ }
+ p->aiException = aNew;
+ p->nException = nNew;
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ }
+
+ return rc;
+}
+
+/*
+** Return true if the p->aiException[] array contains the value iCode.
+*/
+static int fts5UnicodeIsException(Unicode61Tokenizer *p, int iCode){
+ if( p->nException>0 ){
+ int *a = p->aiException;
+ int iLo = 0;
+ int iHi = p->nException-1;
+
+ while( iHi>=iLo ){
+ int iTest = (iHi + iLo) / 2;
+ if( iCode==a[iTest] ){
+ return 1;
+ }else if( iCode>a[iTest] ){
+ iLo = iTest+1;
+ }else{
+ iHi = iTest-1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+** Delete a "unicode61" tokenizer.
+*/
+static void fts5UnicodeDelete(Fts5Tokenizer *pTok){
+ if( pTok ){
+ Unicode61Tokenizer *p = (Unicode61Tokenizer*)pTok;
+ sqlite3_free(p->aiException);
+ sqlite3_free(p->aFold);
+ sqlite3_free(p);
+ }
+ return;
+}
+
+/*
+** Create a "unicode61" tokenizer.
+*/
+static int fts5UnicodeCreate(
+ void *pCtx,
+ const char **azArg, int nArg,
+ Fts5Tokenizer **ppOut
+){
+ int rc = SQLITE_OK; /* Return code */
+ Unicode61Tokenizer *p = 0; /* New tokenizer object */
+
+ if( nArg%2 ){
+ rc = SQLITE_ERROR;
+ }else{
+ p = (Unicode61Tokenizer*)sqlite3_malloc(sizeof(Unicode61Tokenizer));
+ if( p ){
+ int i;
+ memset(p, 0, sizeof(Unicode61Tokenizer));
+ memcpy(p->aTokenChar, aAsciiTokenChar, sizeof(aAsciiTokenChar));
+ p->bRemoveDiacritic = 1;
+ p->nFold = 64;
+ p->aFold = sqlite3_malloc(p->nFold * sizeof(char));
+ if( p->aFold==0 ){
+ rc = SQLITE_NOMEM;
+ }
+ for(i=0; rc==SQLITE_OK && i<nArg; i+=2){
+ const char *zArg = azArg[i+1];
+ if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){
+ if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1] ){
+ rc = SQLITE_ERROR;
+ }
+ p->bRemoveDiacritic = (zArg[0]=='1');
+ }else
+ if( 0==sqlite3_stricmp(azArg[i], "tokenchars") ){
+ rc = fts5UnicodeAddExceptions(p, zArg, 1);
+ }else
+ if( 0==sqlite3_stricmp(azArg[i], "separators") ){
+ rc = fts5UnicodeAddExceptions(p, zArg, 0);
+ }else{
+ rc = SQLITE_ERROR;
+ }
+ }
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ if( rc!=SQLITE_OK ){
+ fts5UnicodeDelete((Fts5Tokenizer*)p);
+ p = 0;
+ }
+ *ppOut = (Fts5Tokenizer*)p;
+ }
+ return rc;
+}
+
+/*
+** Return true if, for the purposes of tokenizing with the tokenizer
+** passed as the first argument, codepoint iCode is considered a token
+** character (not a separator).
+*/
+static int fts5UnicodeIsAlnum(Unicode61Tokenizer *p, int iCode){
+ assert( (sqlite3Fts5UnicodeIsalnum(iCode) & 0xFFFFFFFE)==0 );
+ return sqlite3Fts5UnicodeIsalnum(iCode) ^ fts5UnicodeIsException(p, iCode);
+}
+
+static int fts5UnicodeTokenize(
+ Fts5Tokenizer *pTokenizer,
+ void *pCtx,
+ const char *pText, int nText,
+ int (*xToken)(void*, const char*, int nToken, int iStart, int iEnd)
+){
+ Unicode61Tokenizer *p = (Unicode61Tokenizer*)pTokenizer;
+ int rc = SQLITE_OK;
+ unsigned char *a = p->aTokenChar;
+
+ unsigned char *zTerm = (unsigned char*)&pText[nText];
+ unsigned char *zCsr = (unsigned char *)pText;
+
+ /* Output buffer */
+ char *aFold = p->aFold;
+ int nFold = p->nFold;
+ const char *pEnd = &aFold[nFold-6];
+
+ /* Each iteration of this loop gobbles up a contiguous run of separators,
+ ** then the next token. */
+ while( rc==SQLITE_OK ){
+ int iCode; /* non-ASCII codepoint read from input */
+ char *zOut = aFold;
+ int is;
+ int ie;
+
+ /* Skip any separator characters. */
+ while( 1 ){
+ if( zCsr>=zTerm ) goto tokenize_done;
+ if( *zCsr & 0x80 ) {
+ /* A character outside of the ascii range. Skip past it if it is
+ ** a separator character. Or break out of the loop if it is not. */
+ is = zCsr - (unsigned char*)pText;
+ READ_UTF8(zCsr, zTerm, iCode);
+ if( fts5UnicodeIsAlnum(p, iCode) ){
+ goto non_ascii_tokenchar;
+ }
+ }else{
+ if( a[*zCsr] ){
+ is = zCsr - (unsigned char*)pText;
+ goto ascii_tokenchar;
+ }
+ zCsr++;
+ }
+ }
+
+ /* Run through the tokenchars. Fold them into the output buffer along
+ ** the way. */
+ while( zCsr<zTerm ){
+
+ /* Grow the output buffer so that there is sufficient space to fit the
+ ** largest possible utf-8 character. */
+ if( zOut>pEnd ){
+ aFold = sqlite3_malloc(nFold*2);
+ if( aFold==0 ){
+ rc = SQLITE_NOMEM;
+ goto tokenize_done;
+ }
+ zOut = &aFold[zOut - p->aFold];
+ memcpy(aFold, p->aFold, nFold);
+ sqlite3_free(p->aFold);
+ p->aFold = aFold;
+ p->nFold = nFold = nFold*2;
+ pEnd = &aFold[nFold-6];
+ }
+
+ if( *zCsr & 0x80 ){
+ /* An non-ascii-range character. Fold it into the output buffer if
+ ** it is a token character, or break out of the loop if it is not. */
+ READ_UTF8(zCsr, zTerm, iCode);
+ if( fts5UnicodeIsAlnum(p,iCode)||sqlite3Fts5UnicodeIsdiacritic(iCode) ){
+ non_ascii_tokenchar:
+ iCode = sqlite3Fts5UnicodeFold(iCode, p->bRemoveDiacritic);
+ if( iCode ) WRITE_UTF8(zOut, iCode);
+ }else{
+ break;
+ }
+ }else if( a[*zCsr]==0 ){
+ /* An ascii-range separator character. End of token. */
+ break;
+ }else{
+ ascii_tokenchar:
+ if( *zCsr>='A' && *zCsr<='Z' ){
+ *zOut++ = *zCsr + 32;
+ }else{
+ *zOut++ = *zCsr;
+ }
+ zCsr++;
+ }
+ ie = zCsr - (unsigned char*)pText;
+ }
+
+ /* Invoke the token callback */
+ rc = xToken(pCtx, aFold, zOut-aFold, is, ie);
+ }
+
+ tokenize_done:
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ return rc;
+}
+
+/**************************************************************************
+** Start of porter stemmer implementation.
+*/
+
+/* Any tokens larger than this (in bytes) are passed through without
+** stemming. */
+#define FTS5_PORTER_MAX_TOKEN 64
+
+typedef struct PorterTokenizer PorterTokenizer;
+struct PorterTokenizer {
+ fts5_tokenizer tokenizer; /* Parent tokenizer module */
+ Fts5Tokenizer *pTokenizer; /* Parent tokenizer instance */
+ char aBuf[FTS5_PORTER_MAX_TOKEN + 64];
+};
+
+/*
+** Delete a "porter" tokenizer.
+*/
+static void fts5PorterDelete(Fts5Tokenizer *pTok){
+ if( pTok ){
+ PorterTokenizer *p = (PorterTokenizer*)pTok;
+ if( p->pTokenizer ){
+ p->tokenizer.xDelete(p->pTokenizer);
+ }
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Create a "porter" tokenizer.
+*/
+static int fts5PorterCreate(
+ void *pCtx,
+ const char **azArg, int nArg,
+ Fts5Tokenizer **ppOut
+){
+ fts5_api *pApi = (fts5_api*)pCtx;
+ int rc = SQLITE_OK;
+ PorterTokenizer *pRet;
+ void *pUserdata = 0;
+
+ pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer));
+ if( pRet ){
+ memset(pRet, 0, sizeof(PorterTokenizer));
+ rc = pApi->xFindTokenizer(pApi, "unicode61", &pUserdata, &pRet->tokenizer);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ if( rc==SQLITE_OK ){
+ rc = pRet->tokenizer.xCreate(pUserdata, 0, 0, &pRet->pTokenizer);
+ }
+
+ if( rc!=SQLITE_OK ){
+ fts5PorterDelete((Fts5Tokenizer*)pRet);
+ pRet = 0;
+ }
+ *ppOut = (Fts5Tokenizer*)pRet;
+ return rc;
+}
+
+typedef struct PorterContext PorterContext;
+struct PorterContext {
+ void *pCtx;
+ int (*xToken)(void*, const char*, int, int, int);
+ char *aBuf;
+};
+
+typedef struct PorterRule PorterRule;
+struct PorterRule {
+ const char *zSuffix;
+ int nSuffix;
+ int (*xCond)(char *zStem, int nStem);
+ const char *zOutput;
+ int nOutput;
+};
+
+#if 0
+static int fts5PorterApply(char *aBuf, int *pnBuf, PorterRule *aRule){
+ int ret = -1;
+ int nBuf = *pnBuf;
+ PorterRule *p;
+
+ for(p=aRule; p->zSuffix; p++){
+ assert( strlen(p->zSuffix)==p->nSuffix );
+ assert( strlen(p->zOutput)==p->nOutput );
+ if( nBuf<p->nSuffix ) continue;
+ if( 0==memcmp(&aBuf[nBuf - p->nSuffix], p->zSuffix, p->nSuffix) ) break;
+ }
+
+ if( p->zSuffix ){
+ int nStem = nBuf - p->nSuffix;
+ if( p->xCond==0 || p->xCond(aBuf, nStem) ){
+ memcpy(&aBuf[nStem], p->zOutput, p->nOutput);
+ *pnBuf = nStem + p->nOutput;
+ ret = p - aRule;
+ }
+ }
+
+ return ret;
+}
+#endif
+
+static int fts5PorterIsVowel(char c, int bYIsVowel){
+ return (
+ c=='a' || c=='e' || c=='i' || c=='o' || c=='u' || (bYIsVowel && c=='y')
+ );
+}
+
+static int fts5PorterGobbleVC(char *zStem, int nStem, int bPrevCons){
+ int i;
+ int bCons = bPrevCons;
+
+ /* Scan for a vowel */
+ for(i=0; i<nStem; i++){
+ if( 0==(bCons = !fts5PorterIsVowel(zStem[i], bCons)) ) break;
+ }
+
+ /* Scan for a consonent */
+ for(i++; i<nStem; i++){
+ if( (bCons = !fts5PorterIsVowel(zStem[i], bCons)) ) return i+1;
+ }
+ return 0;
+}
+
+/* porter rule condition: (m > 0) */
+static int fts5Porter_MGt0(char *zStem, int nStem){
+ return !!fts5PorterGobbleVC(zStem, nStem, 0);
+}
+
+/* porter rule condition: (m > 1) */
+static int fts5Porter_MGt1(char *zStem, int nStem){
+ int n;
+ n = fts5PorterGobbleVC(zStem, nStem, 0);
+ if( n && fts5PorterGobbleVC(&zStem[n], nStem-n, 1) ){
+ return 1;
+ }
+ return 0;
+}
+
+/* porter rule condition: (m = 1) */
+static int fts5Porter_MEq1(char *zStem, int nStem){
+ int n;
+ n = fts5PorterGobbleVC(zStem, nStem, 0);
+ if( n && 0==fts5PorterGobbleVC(&zStem[n], nStem-n, 1) ){
+ return 1;
+ }
+ return 0;
+}
+
+/* porter rule condition: (*o) */
+static int fts5Porter_Ostar(char *zStem, int nStem){
+ if( zStem[nStem-1]=='w' || zStem[nStem-1]=='x' || zStem[nStem-1]=='y' ){
+ return 0;
+ }else{
+ int i;
+ int mask = 0;
+ int bCons = 0;
+ for(i=0; i<nStem; i++){
+ bCons = !fts5PorterIsVowel(zStem[i], bCons);
+ assert( bCons==0 || bCons==1 );
+ mask = (mask << 1) + bCons;
+ }
+ return ((mask & 0x0007)==0x0005);
+ }
+}
+
+/* porter rule condition: (m > 1 and (*S or *T)) */
+static int fts5Porter_MGt1_and_S_or_T(char *zStem, int nStem){
+ return nStem>0
+ && (zStem[nStem-1]=='s' || zStem[nStem-1]=='t')
+ && fts5Porter_MGt1(zStem, nStem);
+}
+
+/* porter rule condition: (*v*) */
+static int fts5Porter_Vowel(char *zStem, int nStem){
+ int i;
+ for(i=0; i<nStem; i++){
+ if( fts5PorterIsVowel(zStem[i], i>0) ){
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**************************************************************************
+***************************************************************************
+** GENERATED CODE STARTS HERE (mkportersteps.tcl)
+*/
+
+static int fts5PorterStep4(char *aBuf, int *pnBuf){
+ int ret = 0;
+ int nBuf = *pnBuf;
+ switch( aBuf[nBuf-2] ){
+
+ case 'a':
+ if( nBuf>2 && 0==memcmp("al", &aBuf[nBuf-2], 2) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-2) ){
+ *pnBuf = nBuf - 2;
+ }
+ }
+ break;
+
+ case 'c':
+ if( nBuf>4 && 0==memcmp("ance", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-4) ){
+ *pnBuf = nBuf - 4;
+ }
+ }else if( nBuf>4 && 0==memcmp("ence", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-4) ){
+ *pnBuf = nBuf - 4;
+ }
+ }
+ break;
+
+ case 'e':
+ if( nBuf>2 && 0==memcmp("er", &aBuf[nBuf-2], 2) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-2) ){
+ *pnBuf = nBuf - 2;
+ }
+ }
+ break;
+
+ case 'i':
+ if( nBuf>2 && 0==memcmp("ic", &aBuf[nBuf-2], 2) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-2) ){
+ *pnBuf = nBuf - 2;
+ }
+ }
+ break;
+
+ case 'l':
+ if( nBuf>4 && 0==memcmp("able", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-4) ){
+ *pnBuf = nBuf - 4;
+ }
+ }else if( nBuf>4 && 0==memcmp("ible", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-4) ){
+ *pnBuf = nBuf - 4;
+ }
+ }
+ break;
+
+ case 'n':
+ if( nBuf>3 && 0==memcmp("ant", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }else if( nBuf>5 && 0==memcmp("ement", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-5) ){
+ *pnBuf = nBuf - 5;
+ }
+ }else if( nBuf>4 && 0==memcmp("ment", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-4) ){
+ *pnBuf = nBuf - 4;
+ }
+ }else if( nBuf>3 && 0==memcmp("ent", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }
+ break;
+
+ case 'o':
+ if( nBuf>3 && 0==memcmp("ion", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1_and_S_or_T(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }else if( nBuf>2 && 0==memcmp("ou", &aBuf[nBuf-2], 2) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-2) ){
+ *pnBuf = nBuf - 2;
+ }
+ }
+ break;
+
+ case 's':
+ if( nBuf>3 && 0==memcmp("ism", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }
+ break;
+
+ case 't':
+ if( nBuf>3 && 0==memcmp("ate", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }else if( nBuf>3 && 0==memcmp("iti", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }
+ break;
+
+ case 'u':
+ if( nBuf>3 && 0==memcmp("ous", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }
+ break;
+
+ case 'v':
+ if( nBuf>3 && 0==memcmp("ive", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }
+ break;
+
+ case 'z':
+ if( nBuf>3 && 0==memcmp("ize", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt1(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }
+ break;
+
+ }
+ return ret;
+}
+
+
+static int fts5PorterStep1B2(char *aBuf, int *pnBuf){
+ int ret = 0;
+ int nBuf = *pnBuf;
+ switch( aBuf[nBuf-2] ){
+
+ case 'a':
+ if( nBuf>2 && 0==memcmp("at", &aBuf[nBuf-2], 2) ){
+ memcpy(&aBuf[nBuf-2], "ate", 3);
+ *pnBuf = nBuf - 2 + 3;
+ ret = 1;
+ }
+ break;
+
+ case 'b':
+ if( nBuf>2 && 0==memcmp("bl", &aBuf[nBuf-2], 2) ){
+ memcpy(&aBuf[nBuf-2], "ble", 3);
+ *pnBuf = nBuf - 2 + 3;
+ ret = 1;
+ }
+ break;
+
+ case 'i':
+ if( nBuf>2 && 0==memcmp("iz", &aBuf[nBuf-2], 2) ){
+ memcpy(&aBuf[nBuf-2], "ize", 3);
+ *pnBuf = nBuf - 2 + 3;
+ ret = 1;
+ }
+ break;
+
+ }
+ return ret;
+}
+
+
+static int fts5PorterStep2(char *aBuf, int *pnBuf){
+ int ret = 0;
+ int nBuf = *pnBuf;
+ switch( aBuf[nBuf-2] ){
+
+ case 'a':
+ if( nBuf>7 && 0==memcmp("ational", &aBuf[nBuf-7], 7) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-7) ){
+ memcpy(&aBuf[nBuf-7], "ate", 3);
+ *pnBuf = nBuf - 7 + 3;
+ }
+ }else if( nBuf>6 && 0==memcmp("tional", &aBuf[nBuf-6], 6) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-6) ){
+ memcpy(&aBuf[nBuf-6], "tion", 4);
+ *pnBuf = nBuf - 6 + 4;
+ }
+ }
+ break;
+
+ case 'c':
+ if( nBuf>4 && 0==memcmp("enci", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-4) ){
+ memcpy(&aBuf[nBuf-4], "ence", 4);
+ *pnBuf = nBuf - 4 + 4;
+ }
+ }else if( nBuf>4 && 0==memcmp("anci", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-4) ){
+ memcpy(&aBuf[nBuf-4], "ance", 4);
+ *pnBuf = nBuf - 4 + 4;
+ }
+ }
+ break;
+
+ case 'e':
+ if( nBuf>4 && 0==memcmp("izer", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-4) ){
+ memcpy(&aBuf[nBuf-4], "ize", 3);
+ *pnBuf = nBuf - 4 + 3;
+ }
+ }
+ break;
+
+ case 'g':
+ if( nBuf>4 && 0==memcmp("logi", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-4) ){
+ memcpy(&aBuf[nBuf-4], "log", 3);
+ *pnBuf = nBuf - 4 + 3;
+ }
+ }
+ break;
+
+ case 'l':
+ if( nBuf>3 && 0==memcmp("bli", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-3) ){
+ memcpy(&aBuf[nBuf-3], "ble", 3);
+ *pnBuf = nBuf - 3 + 3;
+ }
+ }else if( nBuf>4 && 0==memcmp("alli", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-4) ){
+ memcpy(&aBuf[nBuf-4], "al", 2);
+ *pnBuf = nBuf - 4 + 2;
+ }
+ }else if( nBuf>5 && 0==memcmp("entli", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "ent", 3);
+ *pnBuf = nBuf - 5 + 3;
+ }
+ }else if( nBuf>3 && 0==memcmp("eli", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-3) ){
+ memcpy(&aBuf[nBuf-3], "e", 1);
+ *pnBuf = nBuf - 3 + 1;
+ }
+ }else if( nBuf>5 && 0==memcmp("ousli", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "ous", 3);
+ *pnBuf = nBuf - 5 + 3;
+ }
+ }
+ break;
+
+ case 'o':
+ if( nBuf>7 && 0==memcmp("ization", &aBuf[nBuf-7], 7) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-7) ){
+ memcpy(&aBuf[nBuf-7], "ize", 3);
+ *pnBuf = nBuf - 7 + 3;
+ }
+ }else if( nBuf>5 && 0==memcmp("ation", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "ate", 3);
+ *pnBuf = nBuf - 5 + 3;
+ }
+ }else if( nBuf>4 && 0==memcmp("ator", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-4) ){
+ memcpy(&aBuf[nBuf-4], "ate", 3);
+ *pnBuf = nBuf - 4 + 3;
+ }
+ }
+ break;
+
+ case 's':
+ if( nBuf>5 && 0==memcmp("alism", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "al", 2);
+ *pnBuf = nBuf - 5 + 2;
+ }
+ }else if( nBuf>7 && 0==memcmp("iveness", &aBuf[nBuf-7], 7) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-7) ){
+ memcpy(&aBuf[nBuf-7], "ive", 3);
+ *pnBuf = nBuf - 7 + 3;
+ }
+ }else if( nBuf>7 && 0==memcmp("fulness", &aBuf[nBuf-7], 7) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-7) ){
+ memcpy(&aBuf[nBuf-7], "ful", 3);
+ *pnBuf = nBuf - 7 + 3;
+ }
+ }else if( nBuf>7 && 0==memcmp("ousness", &aBuf[nBuf-7], 7) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-7) ){
+ memcpy(&aBuf[nBuf-7], "ous", 3);
+ *pnBuf = nBuf - 7 + 3;
+ }
+ }
+ break;
+
+ case 't':
+ if( nBuf>5 && 0==memcmp("aliti", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "al", 2);
+ *pnBuf = nBuf - 5 + 2;
+ }
+ }else if( nBuf>5 && 0==memcmp("iviti", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "ive", 3);
+ *pnBuf = nBuf - 5 + 3;
+ }
+ }else if( nBuf>6 && 0==memcmp("biliti", &aBuf[nBuf-6], 6) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-6) ){
+ memcpy(&aBuf[nBuf-6], "ble", 3);
+ *pnBuf = nBuf - 6 + 3;
+ }
+ }
+ break;
+
+ }
+ return ret;
+}
+
+
+static int fts5PorterStep3(char *aBuf, int *pnBuf){
+ int ret = 0;
+ int nBuf = *pnBuf;
+ switch( aBuf[nBuf-2] ){
+
+ case 'a':
+ if( nBuf>4 && 0==memcmp("ical", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-4) ){
+ memcpy(&aBuf[nBuf-4], "ic", 2);
+ *pnBuf = nBuf - 4 + 2;
+ }
+ }
+ break;
+
+ case 's':
+ if( nBuf>4 && 0==memcmp("ness", &aBuf[nBuf-4], 4) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-4) ){
+ *pnBuf = nBuf - 4;
+ }
+ }
+ break;
+
+ case 't':
+ if( nBuf>5 && 0==memcmp("icate", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "ic", 2);
+ *pnBuf = nBuf - 5 + 2;
+ }
+ }else if( nBuf>5 && 0==memcmp("iciti", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "ic", 2);
+ *pnBuf = nBuf - 5 + 2;
+ }
+ }
+ break;
+
+ case 'u':
+ if( nBuf>3 && 0==memcmp("ful", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ }
+ }
+ break;
+
+ case 'v':
+ if( nBuf>5 && 0==memcmp("ative", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ *pnBuf = nBuf - 5;
+ }
+ }
+ break;
+
+ case 'z':
+ if( nBuf>5 && 0==memcmp("alize", &aBuf[nBuf-5], 5) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-5) ){
+ memcpy(&aBuf[nBuf-5], "al", 2);
+ *pnBuf = nBuf - 5 + 2;
+ }
+ }
+ break;
+
+ }
+ return ret;
+}
+
+
+static int fts5PorterStep1B(char *aBuf, int *pnBuf){
+ int ret = 0;
+ int nBuf = *pnBuf;
+ switch( aBuf[nBuf-2] ){
+
+ case 'e':
+ if( nBuf>3 && 0==memcmp("eed", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_MGt0(aBuf, nBuf-3) ){
+ memcpy(&aBuf[nBuf-3], "ee", 2);
+ *pnBuf = nBuf - 3 + 2;
+ }
+ }else if( nBuf>2 && 0==memcmp("ed", &aBuf[nBuf-2], 2) ){
+ if( fts5Porter_Vowel(aBuf, nBuf-2) ){
+ *pnBuf = nBuf - 2;
+ ret = 1;
+ }
+ }
+ break;
+
+ case 'n':
+ if( nBuf>3 && 0==memcmp("ing", &aBuf[nBuf-3], 3) ){
+ if( fts5Porter_Vowel(aBuf, nBuf-3) ){
+ *pnBuf = nBuf - 3;
+ ret = 1;
+ }
+ }
+ break;
+
+ }
+ return ret;
+}
+
+/*
+** GENERATED CODE ENDS HERE (mkportersteps.tcl)
+***************************************************************************
+**************************************************************************/
+
+static void fts5PorterStep1A(char *aBuf, int *pnBuf){
+ int nBuf = *pnBuf;
+ if( aBuf[nBuf-1]=='s' ){
+ if( aBuf[nBuf-2]=='e' ){
+ if( (nBuf>4 && aBuf[nBuf-4]=='s' && aBuf[nBuf-3]=='s')
+ || (nBuf>3 && aBuf[nBuf-3]=='i' )
+ ){
+ *pnBuf = nBuf-2;
+ }else{
+ *pnBuf = nBuf-1;
+ }
+ }
+ else if( aBuf[nBuf-2]!='s' ){
+ *pnBuf = nBuf-1;
+ }
+ }
+}
+
+static int fts5PorterCb(
+ void *pCtx,
+ const char *pToken,
+ int nToken,
+ int iStart,
+ int iEnd
+){
+ PorterContext *p = (PorterContext*)pCtx;
+
+ char *aBuf;
+ int nBuf;
+
+ if( nToken>FTS5_PORTER_MAX_TOKEN || nToken<3 ) goto pass_through;
+ aBuf = p->aBuf;
+ nBuf = nToken;
+ memcpy(aBuf, pToken, nBuf);
+
+ /* Step 1. */
+ fts5PorterStep1A(aBuf, &nBuf);
+ if( fts5PorterStep1B(aBuf, &nBuf) ){
+ if( fts5PorterStep1B2(aBuf, &nBuf)==0 ){
+ char c = aBuf[nBuf-1];
+ if( fts5PorterIsVowel(c, 0)==0
+ && c!='l' && c!='s' && c!='z' && c==aBuf[nBuf-2]
+ ){
+ nBuf--;
+ }else if( fts5Porter_MEq1(aBuf, nBuf) && fts5Porter_Ostar(aBuf, nBuf) ){
+ aBuf[nBuf++] = 'e';
+ }
+ }
+ }
+
+ /* Step 1C. */
+ if( aBuf[nBuf-1]=='y' && fts5Porter_Vowel(aBuf, nBuf-1) ){
+ aBuf[nBuf-1] = 'i';
+ }
+
+ /* Steps 2 through 4. */
+ fts5PorterStep2(aBuf, &nBuf);
+ fts5PorterStep3(aBuf, &nBuf);
+ fts5PorterStep4(aBuf, &nBuf);
+
+ /* Step 5a. */
+ if( nBuf>0 && aBuf[nBuf-1]=='e' ){
+ if( fts5Porter_MGt1(aBuf, nBuf-1)
+ || (fts5Porter_MEq1(aBuf, nBuf-1) && !fts5Porter_Ostar(aBuf, nBuf-1))
+ ){
+ nBuf--;
+ }
+ }
+
+ /* Step 5b. */
+ if( nBuf>1 && aBuf[nBuf-1]=='l'
+ && aBuf[nBuf-2]=='l' && fts5Porter_MGt1(aBuf, nBuf-1)
+ ){
+ nBuf--;
+ }
+
+ return p->xToken(p->pCtx, aBuf, nBuf, iStart, iEnd);
+
+ pass_through:
+ return p->xToken(p->pCtx, pToken, nToken, iStart, iEnd);
+}
+
+/*
+** Tokenize using the porter tokenizer.
+*/
+static int fts5PorterTokenize(
+ Fts5Tokenizer *pTokenizer,
+ void *pCtx,
+ const char *pText, int nText,
+ int (*xToken)(void*, const char*, int nToken, int iStart, int iEnd)
+){
+ PorterTokenizer *p = (PorterTokenizer*)pTokenizer;
+ PorterContext sCtx;
+ sCtx.xToken = xToken;
+ sCtx.pCtx = pCtx;
+ sCtx.aBuf = p->aBuf;
+ return p->tokenizer.xTokenize(
+ p->pTokenizer, (void*)&sCtx, pText, nText, fts5PorterCb
+ );
+}
+
+/*
+** Register all built-in tokenizers with FTS5.
+*/
+int sqlite3Fts5TokenizerInit(fts5_api *pApi){
+ struct BuiltinTokenizer {
+ const char *zName;
+ fts5_tokenizer x;
+ } aBuiltin[] = {
+ { "unicode61", {fts5UnicodeCreate, fts5UnicodeDelete, fts5UnicodeTokenize}},
+ { "ascii", {fts5AsciiCreate, fts5AsciiDelete, fts5AsciiTokenize }},
+ { "porter", {fts5PorterCreate, fts5PorterDelete, fts5PorterTokenize }},
+ };
+
+ int rc = SQLITE_OK; /* Return code */
+ int i; /* To iterate through builtin functions */
+
+ for(i=0; rc==SQLITE_OK && i<sizeof(aBuiltin)/sizeof(aBuiltin[0]); i++){
+ rc = pApi->xCreateTokenizer(pApi,
+ aBuiltin[i].zName,
+ (void*)pApi,
+ &aBuiltin[i].x,
+ 0
+ );
+ }
+
+ return SQLITE_OK;
+}
+#endif /* defined(SQLITE_ENABLE_FTS5) */
+
+
diff --git a/ext/fts5/fts5_unicode2.c b/ext/fts5/fts5_unicode2.c
new file mode 100644
index 000000000..972e7ed97
--- /dev/null
+++ b/ext/fts5/fts5_unicode2.c
@@ -0,0 +1,363 @@
+/*
+** 2012 May 25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+*/
+
+/*
+** DO NOT EDIT THIS MACHINE GENERATED FILE.
+*/
+
+#if defined(SQLITE_ENABLE_FTS5)
+
+#include <assert.h>
+
+/*
+** Return true if the argument corresponds to a unicode codepoint
+** classified as either a letter or a number. Otherwise false.
+**
+** The results are undefined if the value passed to this function
+** is less than zero.
+*/
+int sqlite3Fts5UnicodeIsalnum(int c){
+ /* Each unsigned integer in the following array corresponds to a contiguous
+ ** range of unicode codepoints that are not either letters or numbers (i.e.
+ ** codepoints for which this function should return 0).
+ **
+ ** The most significant 22 bits in each 32-bit value contain the first
+ ** codepoint in the range. The least significant 10 bits are used to store
+ ** the size of the range (always at least 1). In other words, the value
+ ** ((C<<22) + N) represents a range of N codepoints starting with codepoint
+ ** C. It is not possible to represent a range larger than 1023 codepoints
+ ** using this format.
+ */
+ static const unsigned int aEntry[] = {
+ 0x00000030, 0x0000E807, 0x00016C06, 0x0001EC2F, 0x0002AC07,
+ 0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01,
+ 0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401,
+ 0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01,
+ 0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163C01,
+ 0x00164437, 0x0017CC02, 0x00180005, 0x00181816, 0x00187802,
+ 0x00192C15, 0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F,
+ 0x001B9C07, 0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401,
+ 0x001CC01B, 0x001E980B, 0x001FAC09, 0x001FD804, 0x00205804,
+ 0x00206C09, 0x00209403, 0x0020A405, 0x0020C00F, 0x00216403,
+ 0x00217801, 0x0023901B, 0x00240004, 0x0024E803, 0x0024F812,
+ 0x00254407, 0x00258804, 0x0025C001, 0x00260403, 0x0026F001,
+ 0x0026F807, 0x00271C02, 0x00272C03, 0x00275C01, 0x00278802,
+ 0x0027C802, 0x0027E802, 0x00280403, 0x0028F001, 0x0028F805,
+ 0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D401,
+ 0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03,
+ 0x002B8802, 0x002BC002, 0x002C0403, 0x002CF001, 0x002CF807,
+ 0x002D1C02, 0x002D2C03, 0x002D5802, 0x002D8802, 0x002DC001,
+ 0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804, 0x002F5C01,
+ 0x002FCC08, 0x00300403, 0x0030F807, 0x00311803, 0x00312804,
+ 0x00315402, 0x00318802, 0x0031FC01, 0x00320802, 0x0032F001,
+ 0x0032F807, 0x00331803, 0x00332804, 0x00335402, 0x00338802,
+ 0x00340802, 0x0034F807, 0x00351803, 0x00352804, 0x00355C01,
+ 0x00358802, 0x0035E401, 0x00360802, 0x00372801, 0x00373C06,
+ 0x00375801, 0x00376008, 0x0037C803, 0x0038C401, 0x0038D007,
+ 0x0038FC01, 0x00391C09, 0x00396802, 0x003AC401, 0x003AD006,
+ 0x003AEC02, 0x003B2006, 0x003C041F, 0x003CD00C, 0x003DC417,
+ 0x003E340B, 0x003E6424, 0x003EF80F, 0x003F380D, 0x0040AC14,
+ 0x00412806, 0x00415804, 0x00417803, 0x00418803, 0x00419C07,
+ 0x0041C404, 0x0042080C, 0x00423C01, 0x00426806, 0x0043EC01,
+ 0x004D740C, 0x004E400A, 0x00500001, 0x0059B402, 0x005A0001,
+ 0x005A6C02, 0x005BAC03, 0x005C4803, 0x005CC805, 0x005D4802,
+ 0x005DC802, 0x005ED023, 0x005F6004, 0x005F7401, 0x0060000F,
+ 0x0062A401, 0x0064800C, 0x0064C00C, 0x00650001, 0x00651002,
+ 0x0066C011, 0x00672002, 0x00677822, 0x00685C05, 0x00687802,
+ 0x0069540A, 0x0069801D, 0x0069FC01, 0x006A8007, 0x006AA006,
+ 0x006C0005, 0x006CD011, 0x006D6823, 0x006E0003, 0x006E840D,
+ 0x006F980E, 0x006FF004, 0x00709014, 0x0070EC05, 0x0071F802,
+ 0x00730008, 0x00734019, 0x0073B401, 0x0073C803, 0x00770027,
+ 0x0077F004, 0x007EF401, 0x007EFC03, 0x007F3403, 0x007F7403,
+ 0x007FB403, 0x007FF402, 0x00800065, 0x0081A806, 0x0081E805,
+ 0x00822805, 0x0082801A, 0x00834021, 0x00840002, 0x00840C04,
+ 0x00842002, 0x00845001, 0x00845803, 0x00847806, 0x00849401,
+ 0x00849C01, 0x0084A401, 0x0084B801, 0x0084E802, 0x00850005,
+ 0x00852804, 0x00853C01, 0x00864264, 0x00900027, 0x0091000B,
+ 0x0092704E, 0x00940200, 0x009C0475, 0x009E53B9, 0x00AD400A,
+ 0x00B39406, 0x00B3BC03, 0x00B3E404, 0x00B3F802, 0x00B5C001,
+ 0x00B5FC01, 0x00B7804F, 0x00B8C00C, 0x00BA001A, 0x00BA6C59,
+ 0x00BC00D6, 0x00BFC00C, 0x00C00005, 0x00C02019, 0x00C0A807,
+ 0x00C0D802, 0x00C0F403, 0x00C26404, 0x00C28001, 0x00C3EC01,
+ 0x00C64002, 0x00C6580A, 0x00C70024, 0x00C8001F, 0x00C8A81E,
+ 0x00C94001, 0x00C98020, 0x00CA2827, 0x00CB003F, 0x00CC0100,
+ 0x01370040, 0x02924037, 0x0293F802, 0x02983403, 0x0299BC10,
+ 0x029A7C01, 0x029BC008, 0x029C0017, 0x029C8002, 0x029E2402,
+ 0x02A00801, 0x02A01801, 0x02A02C01, 0x02A08C09, 0x02A0D804,
+ 0x02A1D004, 0x02A20002, 0x02A2D011, 0x02A33802, 0x02A38012,
+ 0x02A3E003, 0x02A4980A, 0x02A51C0D, 0x02A57C01, 0x02A60004,
+ 0x02A6CC1B, 0x02A77802, 0x02A8A40E, 0x02A90C01, 0x02A93002,
+ 0x02A97004, 0x02A9DC03, 0x02A9EC01, 0x02AAC001, 0x02AAC803,
+ 0x02AADC02, 0x02AAF802, 0x02AB0401, 0x02AB7802, 0x02ABAC07,
+ 0x02ABD402, 0x02AF8C0B, 0x03600001, 0x036DFC02, 0x036FFC02,
+ 0x037FFC01, 0x03EC7801, 0x03ECA401, 0x03EEC810, 0x03F4F802,
+ 0x03F7F002, 0x03F8001A, 0x03F88007, 0x03F8C023, 0x03F95013,
+ 0x03F9A004, 0x03FBFC01, 0x03FC040F, 0x03FC6807, 0x03FCEC06,
+ 0x03FD6C0B, 0x03FF8007, 0x03FFA007, 0x03FFE405, 0x04040003,
+ 0x0404DC09, 0x0405E411, 0x0406400C, 0x0407402E, 0x040E7C01,
+ 0x040F4001, 0x04215C01, 0x04247C01, 0x0424FC01, 0x04280403,
+ 0x04281402, 0x04283004, 0x0428E003, 0x0428FC01, 0x04294009,
+ 0x0429FC01, 0x042CE407, 0x04400003, 0x0440E016, 0x04420003,
+ 0x0442C012, 0x04440003, 0x04449C0E, 0x04450004, 0x04460003,
+ 0x0446CC0E, 0x04471404, 0x045AAC0D, 0x0491C004, 0x05BD442E,
+ 0x05BE3C04, 0x074000F6, 0x07440027, 0x0744A4B5, 0x07480046,
+ 0x074C0057, 0x075B0401, 0x075B6C01, 0x075BEC01, 0x075C5401,
+ 0x075CD401, 0x075D3C01, 0x075DBC01, 0x075E2401, 0x075EA401,
+ 0x075F0C01, 0x07BBC002, 0x07C0002C, 0x07C0C064, 0x07C2800F,
+ 0x07C2C40E, 0x07C3040F, 0x07C3440F, 0x07C4401F, 0x07C4C03C,
+ 0x07C5C02B, 0x07C7981D, 0x07C8402B, 0x07C90009, 0x07C94002,
+ 0x07CC0021, 0x07CCC006, 0x07CCDC46, 0x07CE0014, 0x07CE8025,
+ 0x07CF1805, 0x07CF8011, 0x07D0003F, 0x07D10001, 0x07D108B6,
+ 0x07D3E404, 0x07D4003E, 0x07D50004, 0x07D54018, 0x07D7EC46,
+ 0x07D9140B, 0x07DA0046, 0x07DC0074, 0x38000401, 0x38008060,
+ 0x380400F0,
+ };
+ static const unsigned int aAscii[4] = {
+ 0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001,
+ };
+
+ if( c<128 ){
+ return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 );
+ }else if( c<(1<<22) ){
+ unsigned int key = (((unsigned int)c)<<10) | 0x000003FF;
+ int iRes = 0;
+ int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;
+ int iLo = 0;
+ while( iHi>=iLo ){
+ int iTest = (iHi + iLo) / 2;
+ if( key >= aEntry[iTest] ){
+ iRes = iTest;
+ iLo = iTest+1;
+ }else{
+ iHi = iTest-1;
+ }
+ }
+ assert( aEntry[0]<key );
+ assert( key>=aEntry[iRes] );
+ return (((unsigned int)c) >= ((aEntry[iRes]>>10) + (aEntry[iRes]&0x3FF)));
+ }
+ return 1;
+}
+
+
+/*
+** If the argument is a codepoint corresponding to a lowercase letter
+** in the ASCII range with a diacritic added, return the codepoint
+** of the ASCII letter only. For example, if passed 235 - "LATIN
+** SMALL LETTER E WITH DIAERESIS" - return 65 ("LATIN SMALL LETTER
+** E"). The resuls of passing a codepoint that corresponds to an
+** uppercase letter are undefined.
+*/
+static int fts5_remove_diacritic(int c){
+ unsigned short aDia[] = {
+ 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995,
+ 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286,
+ 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732,
+ 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336,
+ 3456, 3696, 3712, 3728, 3744, 3896, 3912, 3928,
+ 3968, 4008, 4040, 4106, 4138, 4170, 4202, 4234,
+ 4266, 4296, 4312, 4344, 4408, 4424, 4472, 4504,
+ 6148, 6198, 6264, 6280, 6360, 6429, 6505, 6529,
+ 61448, 61468, 61534, 61592, 61642, 61688, 61704, 61726,
+ 61784, 61800, 61836, 61880, 61914, 61948, 61998, 62122,
+ 62154, 62200, 62218, 62302, 62364, 62442, 62478, 62536,
+ 62554, 62584, 62604, 62640, 62648, 62656, 62664, 62730,
+ 62924, 63050, 63082, 63274, 63390,
+ };
+ char aChar[] = {
+ '\0', 'a', 'c', 'e', 'i', 'n', 'o', 'u', 'y', 'y', 'a', 'c',
+ 'd', 'e', 'e', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'o', 'r',
+ 's', 't', 'u', 'u', 'w', 'y', 'z', 'o', 'u', 'a', 'i', 'o',
+ 'u', 'g', 'k', 'o', 'j', 'g', 'n', 'a', 'e', 'i', 'o', 'r',
+ 'u', 's', 't', 'h', 'a', 'e', 'o', 'y', '\0', '\0', '\0', '\0',
+ '\0', '\0', '\0', '\0', 'a', 'b', 'd', 'd', 'e', 'f', 'g', 'h',
+ 'h', 'i', 'k', 'l', 'l', 'm', 'n', 'p', 'r', 'r', 's', 't',
+ 'u', 'v', 'w', 'w', 'x', 'y', 'z', 'h', 't', 'w', 'y', 'a',
+ 'e', 'i', 'o', 'u', 'y',
+ };
+
+ unsigned int key = (((unsigned int)c)<<3) | 0x00000007;
+ int iRes = 0;
+ int iHi = sizeof(aDia)/sizeof(aDia[0]) - 1;
+ int iLo = 0;
+ while( iHi>=iLo ){
+ int iTest = (iHi + iLo) / 2;
+ if( key >= aDia[iTest] ){
+ iRes = iTest;
+ iLo = iTest+1;
+ }else{
+ iHi = iTest-1;
+ }
+ }
+ assert( key>=aDia[iRes] );
+ return ((c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : (int)aChar[iRes]);
+}
+
+
+/*
+** Return true if the argument interpreted as a unicode codepoint
+** is a diacritical modifier character.
+*/
+int sqlite3Fts5UnicodeIsdiacritic(int c){
+ unsigned int mask0 = 0x08029FDF;
+ unsigned int mask1 = 0x000361F8;
+ if( c<768 || c>817 ) return 0;
+ return (c < 768+32) ?
+ (mask0 & (1 << (c-768))) :
+ (mask1 & (1 << (c-768-32)));
+}
+
+
+/*
+** Interpret the argument as a unicode codepoint. If the codepoint
+** is an upper case character that has a lower case equivalent,
+** return the codepoint corresponding to the lower case version.
+** Otherwise, return a copy of the argument.
+**
+** The results are undefined if the value passed to this function
+** is less than zero.
+*/
+int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic){
+ /* Each entry in the following array defines a rule for folding a range
+ ** of codepoints to lower case. The rule applies to a range of nRange
+ ** codepoints starting at codepoint iCode.
+ **
+ ** If the least significant bit in flags is clear, then the rule applies
+ ** to all nRange codepoints (i.e. all nRange codepoints are upper case and
+ ** need to be folded). Or, if it is set, then the rule only applies to
+ ** every second codepoint in the range, starting with codepoint C.
+ **
+ ** The 7 most significant bits in flags are an index into the aiOff[]
+ ** array. If a specific codepoint C does require folding, then its lower
+ ** case equivalent is ((C + aiOff[flags>>1]) & 0xFFFF).
+ **
+ ** The contents of this array are generated by parsing the CaseFolding.txt
+ ** file distributed as part of the "Unicode Character Database". See
+ ** http://www.unicode.org for details.
+ */
+ static const struct TableEntry {
+ unsigned short iCode;
+ unsigned char flags;
+ unsigned char nRange;
+ } aEntry[] = {
+ {65, 14, 26}, {181, 64, 1}, {192, 14, 23},
+ {216, 14, 7}, {256, 1, 48}, {306, 1, 6},
+ {313, 1, 16}, {330, 1, 46}, {376, 116, 1},
+ {377, 1, 6}, {383, 104, 1}, {385, 50, 1},
+ {386, 1, 4}, {390, 44, 1}, {391, 0, 1},
+ {393, 42, 2}, {395, 0, 1}, {398, 32, 1},
+ {399, 38, 1}, {400, 40, 1}, {401, 0, 1},
+ {403, 42, 1}, {404, 46, 1}, {406, 52, 1},
+ {407, 48, 1}, {408, 0, 1}, {412, 52, 1},
+ {413, 54, 1}, {415, 56, 1}, {416, 1, 6},
+ {422, 60, 1}, {423, 0, 1}, {425, 60, 1},
+ {428, 0, 1}, {430, 60, 1}, {431, 0, 1},
+ {433, 58, 2}, {435, 1, 4}, {439, 62, 1},
+ {440, 0, 1}, {444, 0, 1}, {452, 2, 1},
+ {453, 0, 1}, {455, 2, 1}, {456, 0, 1},
+ {458, 2, 1}, {459, 1, 18}, {478, 1, 18},
+ {497, 2, 1}, {498, 1, 4}, {502, 122, 1},
+ {503, 134, 1}, {504, 1, 40}, {544, 110, 1},
+ {546, 1, 18}, {570, 70, 1}, {571, 0, 1},
+ {573, 108, 1}, {574, 68, 1}, {577, 0, 1},
+ {579, 106, 1}, {580, 28, 1}, {581, 30, 1},
+ {582, 1, 10}, {837, 36, 1}, {880, 1, 4},
+ {886, 0, 1}, {902, 18, 1}, {904, 16, 3},
+ {908, 26, 1}, {910, 24, 2}, {913, 14, 17},
+ {931, 14, 9}, {962, 0, 1}, {975, 4, 1},
+ {976, 140, 1}, {977, 142, 1}, {981, 146, 1},
+ {982, 144, 1}, {984, 1, 24}, {1008, 136, 1},
+ {1009, 138, 1}, {1012, 130, 1}, {1013, 128, 1},
+ {1015, 0, 1}, {1017, 152, 1}, {1018, 0, 1},
+ {1021, 110, 3}, {1024, 34, 16}, {1040, 14, 32},
+ {1120, 1, 34}, {1162, 1, 54}, {1216, 6, 1},
+ {1217, 1, 14}, {1232, 1, 88}, {1329, 22, 38},
+ {4256, 66, 38}, {4295, 66, 1}, {4301, 66, 1},
+ {7680, 1, 150}, {7835, 132, 1}, {7838, 96, 1},
+ {7840, 1, 96}, {7944, 150, 8}, {7960, 150, 6},
+ {7976, 150, 8}, {7992, 150, 8}, {8008, 150, 6},
+ {8025, 151, 8}, {8040, 150, 8}, {8072, 150, 8},
+ {8088, 150, 8}, {8104, 150, 8}, {8120, 150, 2},
+ {8122, 126, 2}, {8124, 148, 1}, {8126, 100, 1},
+ {8136, 124, 4}, {8140, 148, 1}, {8152, 150, 2},
+ {8154, 120, 2}, {8168, 150, 2}, {8170, 118, 2},
+ {8172, 152, 1}, {8184, 112, 2}, {8186, 114, 2},
+ {8188, 148, 1}, {8486, 98, 1}, {8490, 92, 1},
+ {8491, 94, 1}, {8498, 12, 1}, {8544, 8, 16},
+ {8579, 0, 1}, {9398, 10, 26}, {11264, 22, 47},
+ {11360, 0, 1}, {11362, 88, 1}, {11363, 102, 1},
+ {11364, 90, 1}, {11367, 1, 6}, {11373, 84, 1},
+ {11374, 86, 1}, {11375, 80, 1}, {11376, 82, 1},
+ {11378, 0, 1}, {11381, 0, 1}, {11390, 78, 2},
+ {11392, 1, 100}, {11499, 1, 4}, {11506, 0, 1},
+ {42560, 1, 46}, {42624, 1, 24}, {42786, 1, 14},
+ {42802, 1, 62}, {42873, 1, 4}, {42877, 76, 1},
+ {42878, 1, 10}, {42891, 0, 1}, {42893, 74, 1},
+ {42896, 1, 4}, {42912, 1, 10}, {42922, 72, 1},
+ {65313, 14, 26},
+ };
+ static const unsigned short aiOff[] = {
+ 1, 2, 8, 15, 16, 26, 28, 32,
+ 37, 38, 40, 48, 63, 64, 69, 71,
+ 79, 80, 116, 202, 203, 205, 206, 207,
+ 209, 210, 211, 213, 214, 217, 218, 219,
+ 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721,
+ 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274,
+ 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406,
+ 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462,
+ 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511,
+ 65514, 65521, 65527, 65528, 65529,
+ };
+
+ int ret = c;
+
+ assert( c>=0 );
+ assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 );
+
+ if( c<128 ){
+ if( c>='A' && c<='Z' ) ret = c + ('a' - 'A');
+ }else if( c<65536 ){
+ int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;
+ int iLo = 0;
+ int iRes = -1;
+
+ while( iHi>=iLo ){
+ int iTest = (iHi + iLo) / 2;
+ int cmp = (c - aEntry[iTest].iCode);
+ if( cmp>=0 ){
+ iRes = iTest;
+ iLo = iTest+1;
+ }else{
+ iHi = iTest-1;
+ }
+ }
+ assert( iRes<0 || c>=aEntry[iRes].iCode );
+
+ if( iRes>=0 ){
+ const struct TableEntry *p = &aEntry[iRes];
+ if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){
+ ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF;
+ assert( ret>0 );
+ }
+ }
+
+ if( bRemoveDiacritic ) ret = fts5_remove_diacritic(ret);
+ }
+
+ else if( c>=66560 && c<66600 ){
+ ret = c + 40;
+ }
+
+ return ret;
+}
+#endif /* defined(SQLITE_ENABLE_FTS5) */
diff --git a/ext/fts5/fts5parse.y b/ext/fts5/fts5parse.y
new file mode 100644
index 000000000..ec52bdbee
--- /dev/null
+++ b/ext/fts5/fts5parse.y
@@ -0,0 +1,155 @@
+/*
+** 2014 May 31
+**
+** 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.
+**
+******************************************************************************
+**
+*/
+
+
+// All token codes are small integers with #defines that begin with "TK_"
+%token_prefix FTS5_
+
+// The type of the data attached to each token is Token. This is also the
+// default type for non-terminals.
+//
+%token_type {Fts5Token}
+%default_type {Fts5Token}
+
+// The generated parser function takes a 4th argument as follows:
+%extra_argument {Fts5Parse *pParse}
+
+// This code runs whenever there is a syntax error
+//
+%syntax_error {
+ sqlite3Fts5ParseError(
+ pParse, "fts5: syntax error near \"%.*s\"",TOKEN.n,TOKEN.p
+ );
+}
+%stack_overflow {
+ assert( 0 );
+}
+
+// The name of the generated procedure that implements the parser
+// is as follows:
+%name sqlite3Fts5Parser
+
+// The following text is included near the beginning of the C source
+// code file that implements the parser.
+//
+%include {
+#include "fts5Int.h"
+#include "fts5parse.h"
+
+/*
+** Disable all error recovery processing in the parser push-down
+** automaton.
+*/
+#define YYNOERRORRECOVERY 1
+
+/*
+** Make yytestcase() the same as testcase()
+*/
+#define yytestcase(X) testcase(X)
+
+} // end %include
+
+%left OR.
+%left AND.
+%left NOT.
+%left COLON.
+
+input ::= expr(X). { sqlite3Fts5ParseFinished(pParse, X); }
+
+%type cnearset {Fts5ExprNode*}
+%type expr {Fts5ExprNode*}
+%type exprlist {Fts5ExprNode*}
+%destructor cnearset { sqlite3Fts5ParseNodeFree($$); }
+%destructor expr { sqlite3Fts5ParseNodeFree($$); }
+%destructor exprlist { sqlite3Fts5ParseNodeFree($$); }
+
+expr(A) ::= expr(X) AND expr(Y). {
+ A = sqlite3Fts5ParseNode(pParse, FTS5_AND, X, Y, 0);
+}
+expr(A) ::= expr(X) OR expr(Y). {
+ A = sqlite3Fts5ParseNode(pParse, FTS5_OR, X, Y, 0);
+}
+expr(A) ::= expr(X) NOT expr(Y). {
+ A = sqlite3Fts5ParseNode(pParse, FTS5_NOT, X, Y, 0);
+}
+
+expr(A) ::= LP expr(X) RP. {A = X;}
+expr(A) ::= exprlist(X). {A = X;}
+
+exprlist(A) ::= cnearset(X). {A = X;}
+exprlist(A) ::= exprlist(X) cnearset(Y). {
+ A = sqlite3Fts5ParseNode(pParse, FTS5_AND, X, Y, 0);
+}
+
+cnearset(A) ::= nearset(X). {
+ A = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, X);
+}
+cnearset(A) ::= STRING(X) COLON nearset(Y). {
+ sqlite3Fts5ParseSetColumn(pParse, Y, &X);
+ A = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, Y);
+}
+
+%type nearset {Fts5ExprNearset*}
+%type nearphrases {Fts5ExprNearset*}
+%destructor nearset { sqlite3Fts5ParseNearsetFree($$); }
+%destructor nearphrases { sqlite3Fts5ParseNearsetFree($$); }
+
+nearset(A) ::= phrase(X). { A = sqlite3Fts5ParseNearset(pParse, 0, X); }
+nearset(A) ::= STRING(X) LP nearphrases(Y) neardist_opt(Z) RP. {
+ sqlite3Fts5ParseNear(pParse, &X);
+ sqlite3Fts5ParseSetDistance(pParse, Y, &Z);
+ A = Y;
+}
+
+nearphrases(A) ::= phrase(X). {
+ A = sqlite3Fts5ParseNearset(pParse, 0, X);
+}
+nearphrases(A) ::= nearphrases(X) phrase(Y). {
+ A = sqlite3Fts5ParseNearset(pParse, X, Y);
+}
+
+/*
+** The optional ", <integer>" at the end of the NEAR() arguments.
+*/
+neardist_opt(A) ::= . { A.p = 0; A.n = 0; }
+neardist_opt(A) ::= COMMA STRING(X). { A = X; }
+
+/*
+** A phrase. A set of primitives connected by "+" operators. Examples:
+**
+** "the" + "quick brown" + fo *
+** "the quick brown fo" *
+** the+quick+brown+fo*
+*/
+%type phrase {Fts5ExprPhrase*}
+%destructor phrase { sqlite3Fts5ParsePhraseFree($$); }
+
+phrase(A) ::= phrase(X) PLUS STRING(Y) star_opt(Z). {
+ A = sqlite3Fts5ParseTerm(pParse, X, &Y, Z);
+}
+phrase(A) ::= STRING(Y) star_opt(Z). {
+ A = sqlite3Fts5ParseTerm(pParse, 0, &Y, Z);
+}
+
+/*
+** Optional "*" character.
+*/
+%type star_opt {int}
+
+star_opt(A) ::= STAR. { A = 1; }
+star_opt(A) ::= . { A = 0; }
+
+
+
+
diff --git a/ext/fts5/mkportersteps.tcl b/ext/fts5/mkportersteps.tcl
new file mode 100644
index 000000000..b6214c6bf
--- /dev/null
+++ b/ext/fts5/mkportersteps.tcl
@@ -0,0 +1,222 @@
+#
+# 2014 Jun 09
+#
+# 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 script generates the implementations of the following C functions,
+# which are part of the porter tokenizer implementation:
+#
+# static int fts5PorterStep1B(char *aBuf, int *pnBuf);
+# static int fts5PorterStep1B2(char *aBuf, int *pnBuf);
+# static int fts5PorterStep2(char *aBuf, int *pnBuf);
+# static int fts5PorterStep3(char *aBuf, int *pnBuf);
+# static int fts5PorterStep4(char *aBuf, int *pnBuf);
+#
+
+set O(Step1B2) {
+ { at {} ate 1 }
+ { bl {} ble 1 }
+ { iz {} ize 1 }
+}
+
+set O(Step1B) {
+ { "eed" fts5Porter_MGt0 "ee" 0 }
+ { "ed" fts5Porter_Vowel "" 1 }
+ { "ing" fts5Porter_Vowel "" 1 }
+}
+
+set O(Step2) {
+ { "ational" fts5Porter_MGt0 "ate" }
+ { "tional" fts5Porter_MGt0 "tion" }
+ { "enci" fts5Porter_MGt0 "ence" }
+ { "anci" fts5Porter_MGt0 "ance" }
+ { "izer" fts5Porter_MGt0 "ize" }
+ { "logi" fts5Porter_MGt0 "log" }
+ { "bli" fts5Porter_MGt0 "ble" }
+ { "alli" fts5Porter_MGt0 "al" }
+ { "entli" fts5Porter_MGt0 "ent" }
+ { "eli" fts5Porter_MGt0 "e" }
+ { "ousli" fts5Porter_MGt0 "ous" }
+ { "ization" fts5Porter_MGt0 "ize" }
+ { "ation" fts5Porter_MGt0 "ate" }
+ { "ator" fts5Porter_MGt0 "ate" }
+ { "alism" fts5Porter_MGt0 "al" }
+ { "iveness" fts5Porter_MGt0 "ive" }
+ { "fulness" fts5Porter_MGt0 "ful" }
+ { "ousness" fts5Porter_MGt0 "ous" }
+ { "aliti" fts5Porter_MGt0 "al" }
+ { "iviti" fts5Porter_MGt0 "ive" }
+ { "biliti" fts5Porter_MGt0 "ble" }
+}
+
+set O(Step3) {
+ { "icate" fts5Porter_MGt0 "ic" }
+ { "ative" fts5Porter_MGt0 "" }
+ { "alize" fts5Porter_MGt0 "al" }
+ { "iciti" fts5Porter_MGt0 "ic" }
+ { "ical" fts5Porter_MGt0 "ic" }
+ { "ful" fts5Porter_MGt0 "" }
+ { "ness" fts5Porter_MGt0 "" }
+}
+
+set O(Step4) {
+ { "al" fts5Porter_MGt1 "" }
+ { "ance" fts5Porter_MGt1 "" }
+ { "ence" fts5Porter_MGt1 "" }
+ { "er" fts5Porter_MGt1 "" }
+ { "ic" fts5Porter_MGt1 "" }
+ { "able" fts5Porter_MGt1 "" }
+ { "ible" fts5Porter_MGt1 "" }
+ { "ant" fts5Porter_MGt1 "" }
+ { "ement" fts5Porter_MGt1 "" }
+ { "ment" fts5Porter_MGt1 "" }
+ { "ent" fts5Porter_MGt1 "" }
+ { "ion" fts5Porter_MGt1_and_S_or_T "" }
+ { "ou" fts5Porter_MGt1 "" }
+ { "ism" fts5Porter_MGt1 "" }
+ { "ate" fts5Porter_MGt1 "" }
+ { "iti" fts5Porter_MGt1 "" }
+ { "ous" fts5Porter_MGt1 "" }
+ { "ive" fts5Porter_MGt1 "" }
+ { "ize" fts5Porter_MGt1 "" }
+}
+
+proc sort_cb {lhs rhs} {
+ set L [string range [lindex $lhs 0] end-1 end-1]
+ set R [string range [lindex $rhs 0] end-1 end-1]
+ string compare $L $R
+}
+
+proc create_step_function {name data} {
+
+ set T(function) {
+static int fts5Porter${name}(char *aBuf, int *pnBuf){
+ int ret = 0;
+ int nBuf = *pnBuf;
+ switch( aBuf[nBuf-2] ){
+ ${switchbody}
+ }
+ return ret;
+}
+ }
+
+ set T(case) {
+ case '${k}':
+ ${ifstmts}
+ break;
+ }
+
+ set T(if_0_0_0) {
+ if( ${match} ){
+ *pnBuf = nBuf - $n;
+ }
+ }
+ set T(if_1_0_0) {
+ if( ${match} ){
+ if( ${cond} ){
+ *pnBuf = nBuf - $n;
+ }
+ }
+ }
+ set T(if_0_1_0) {
+ if( ${match} ){
+ ${memcpy}
+ *pnBuf = nBuf - $n + $nRep;
+ }
+ }
+ set T(if_1_1_0) {
+ if( ${match} ){
+ if( ${cond} ){
+ ${memcpy}
+ *pnBuf = nBuf - $n + $nRep;
+ }
+ }
+ }
+ set T(if_1_0_1) {
+ if( ${match} ){
+ if( ${cond} ){
+ *pnBuf = nBuf - $n;
+ ret = 1;
+ }
+ }
+ }
+ set T(if_0_1_1) {
+ if( ${match} ){
+ ${memcpy}
+ *pnBuf = nBuf - $n + $nRep;
+ ret = 1;
+ }
+ }
+ set T(if_1_1_1) {
+ if( ${match} ){
+ if( ${cond} ){
+ ${memcpy}
+ *pnBuf = nBuf - $n + $nRep;
+ ret = 1;
+ }
+ }
+ }
+
+ set switchbody ""
+
+ foreach I $data {
+ set k [string range [lindex $I 0] end-1 end-1]
+ lappend aCase($k) $I
+ }
+ foreach k [lsort [array names aCase]] {
+ set ifstmts ""
+ foreach I $aCase($k) {
+ set zSuffix [lindex $I 0] ;# Suffix text for this rule
+ set zRep [lindex $I 2] ;# Replacement text for rule
+ set xCond [lindex $I 1] ;# Condition callback (or "")
+
+ set n [string length $zSuffix]
+ set nRep [string length $zRep]
+
+ set match "nBuf>$n && 0==memcmp(\"$zSuffix\", &aBuf\[nBuf-$n\], $n)"
+ set memcpy "memcpy(&aBuf\[nBuf-$n\], \"$zRep\", $nRep);"
+ set cond "${xCond}(aBuf, nBuf-$n)"
+
+ set bMemcpy [expr {$nRep>0}]
+ set bCond [expr {$xCond!=""}]
+ set bRet [expr {[llength $I]>3 && [lindex $I 3]}]
+
+ set t $T(if_${bCond}_${bMemcpy}_${bRet})
+ lappend ifstmts [string trim [subst -nocommands $t]]
+ }
+
+ set ifstmts [join $ifstmts "else "]
+
+ append switchbody [subst -nocommands $T(case)]
+ }
+
+
+ puts [subst -nocommands $T(function)]
+}
+
+
+puts [string trim {
+/**************************************************************************
+***************************************************************************
+** GENERATED CODE STARTS HERE (mkportersteps.tcl)
+*/
+}]
+foreach step [array names O] {
+ create_step_function $step $O($step)
+}
+puts [string trim {
+/*
+** GENERATED CODE ENDS HERE (mkportersteps.tcl)
+***************************************************************************
+**************************************************************************/
+}]
+
+
+
diff --git a/ext/fts5/test/fts5_common.tcl b/ext/fts5/test/fts5_common.tcl
new file mode 100644
index 000000000..9c612d202
--- /dev/null
+++ b/ext/fts5/test/fts5_common.tcl
@@ -0,0 +1,148 @@
+# 2014 Dec 19
+#
+# 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.
+#
+#***********************************************************************
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. .. test]
+}
+source $testdir/tester.tcl
+
+
+proc fts5_test_poslist {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xInstCount]} {incr i} {
+ lappend res [string map {{ } .} [$cmd xInst $i]]
+ }
+ set res
+}
+
+proc fts5_test_columnsize {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
+ lappend res [$cmd xColumnSize $i]
+ }
+ set res
+}
+
+proc fts5_test_columntext {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
+ lappend res [$cmd xColumnText $i]
+ }
+ set res
+}
+
+proc fts5_test_columntotalsize {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
+ lappend res [$cmd xColumnTotalSize $i]
+ }
+ set res
+}
+
+proc test_append_token {varname token iStart iEnd} {
+ upvar $varname var
+ lappend var $token
+}
+proc fts5_test_tokenize {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xColumnCount]} {incr i} {
+ set tokens [list]
+ $cmd xTokenize [$cmd xColumnText $i] [list test_append_token tokens]
+ lappend res $tokens
+ }
+ set res
+}
+
+proc fts5_test_rowcount {cmd} {
+ $cmd xRowCount
+}
+
+proc test_queryphrase_cb {cnt cmd} {
+ upvar $cnt L
+ for {set i 0} {$i < [$cmd xInstCount]} {incr i} {
+ foreach {ip ic io} [$cmd xInst $i] break
+ set A($ic) 1
+ }
+ foreach ic [array names A] {
+ lset L $ic [expr {[lindex $L $ic] + 1}]
+ }
+}
+proc fts5_test_queryphrase {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xPhraseCount]} {incr i} {
+ set cnt [list]
+ for {set j 0} {$j < [$cmd xColumnCount]} {incr j} { lappend cnt 0 }
+ $cmd xQueryPhrase $i [list test_queryphrase_cb cnt]
+ lappend res $cnt
+ }
+ set res
+}
+
+proc fts5_test_all {cmd} {
+ set res [list]
+ lappend res columnsize [fts5_test_columnsize $cmd]
+ lappend res columntext [fts5_test_columntext $cmd]
+ lappend res columntotalsize [fts5_test_columntotalsize $cmd]
+ lappend res poslist [fts5_test_poslist $cmd]
+ lappend res tokenize [fts5_test_tokenize $cmd]
+ lappend res rowcount [fts5_test_rowcount $cmd]
+ set res
+}
+
+proc fts5_aux_test_functions {db} {
+ foreach f {
+ fts5_test_columnsize
+ fts5_test_columntext
+ fts5_test_columntotalsize
+ fts5_test_poslist
+ fts5_test_tokenize
+ fts5_test_rowcount
+ fts5_test_all
+
+ fts5_test_queryphrase
+ } {
+ sqlite3_fts5_create_function $db $f $f
+ }
+}
+
+proc fts5_level_segs {tbl} {
+ set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
+ set ret [list]
+ foreach L [lrange [db one $sql] 1 end] {
+ lappend ret [expr [llength $L] - 2]
+ }
+ set ret
+}
+
+proc fts5_level_segids {tbl} {
+ set sql "SELECT fts5_decode(rowid,block) aS r FROM ${tbl}_data WHERE rowid=10"
+ set ret [list]
+ foreach L [lrange [db one $sql] 1 end] {
+ set lvl [list]
+ foreach S [lrange $L 2 end] {
+ regexp {id=([1234567890]*)} $S -> segid
+ lappend lvl $segid
+ }
+ lappend ret $lvl
+ }
+ set ret
+}
+
+proc fts5_rnddoc {n} {
+ set map [list 0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j]
+ set doc [list]
+ for {set i 0} {$i < $n} {incr i} {
+ lappend doc "x[string map $map [format %.3d [expr int(rand()*1000)]]]"
+ }
+ set doc
+}
+
diff --git a/ext/fts5/test/fts5aa.test b/ext/fts5/test/fts5aa.test
new file mode 100644
index 000000000..24a352115
--- /dev/null
+++ b/ext/fts5/test/fts5aa.test
@@ -0,0 +1,384 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5aa
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
+ SELECT name, sql FROM sqlite_master;
+} {
+ t1 {CREATE VIRTUAL TABLE t1 USING fts5(a, b, c)}
+ t1_data {CREATE TABLE 't1_data'(id INTEGER PRIMARY KEY, block BLOB)}
+ t1_content {CREATE TABLE 't1_content'(id INTEGER PRIMARY KEY, c0, c1, c2)}
+ t1_docsize {CREATE TABLE 't1_docsize'(id INTEGER PRIMARY KEY, sz BLOB)}
+ t1_config {CREATE TABLE 't1_config'(k PRIMARY KEY, v) WITHOUT ROWID}
+}
+
+do_execsql_test 1.1 {
+ DROP TABLE t1;
+ SELECT name, sql FROM sqlite_master;
+} {
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x,y);
+}
+do_execsql_test 2.1 {
+ INSERT INTO t1 VALUES('a b c', 'd e f');
+}
+do_execsql_test 2.2 {
+ SELECT fts5_decode(id, block) FROM t1_data WHERE id==10
+} {
+ {{structure idx=0} {lvl=0 nMerge=0 {id=27723 h=1 leaves=1..1}}}
+}
+do_execsql_test 2.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x,y);
+}
+foreach {i x y} {
+ 1 {g f d b f} {h h e i a}
+ 2 {f i g j e} {i j c f f}
+ 3 {e e i f a} {e h f d f}
+ 4 {h j f j i} {h a c f j}
+ 5 {d b j c g} {f e i b e}
+ 6 {a j a e e} {j d f d e}
+ 7 {g i j c h} {j d h c a}
+ 8 {j j i d d} {e e d f b}
+ 9 {c j j d c} {h j i f g}
+ 10 {b f h i a} {c f b b j}
+} {
+ do_execsql_test 3.$i.1 { INSERT INTO t1 VALUES($x, $y) }
+ do_execsql_test 3.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+ if {[set_test_counter errors]} break
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x,y);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+foreach {i x y} {
+ 1 {g f d b f} {h h e i a}
+ 2 {f i g j e} {i j c f f}
+ 3 {e e i f a} {e h f d f}
+ 4 {h j f j i} {h a c f j}
+ 5 {d b j c g} {f e i b e}
+ 6 {a j a e e} {j d f d e}
+ 7 {g i j c h} {j d h c a}
+ 8 {j j i d d} {e e d f b}
+ 9 {c j j d c} {h j i f g}
+ 10 {b f h i a} {c f b b j}
+} {
+ do_execsql_test 4.$i.1 { INSERT INTO t1 VALUES($x, $y) }
+ do_execsql_test 4.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+ if {[set_test_counter errors]} break
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x,y);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+foreach {i x y} {
+ 1 {dd abc abc abc abcde} {aaa dd ddd ddd aab}
+ 2 {dd aab d aaa b} {abcde c aaa aaa aaa}
+ 3 {abcde dd b b dd} {abc abc d abc ddddd}
+ 4 {aaa abcde dddd dddd abcde} {abc b b abcde abc}
+ 5 {aab dddd d dddd c} {ddd abcde dddd abcde c}
+ 6 {ddd dd b aab abcde} {d ddddd dddd c abc}
+ 7 {d ddddd ddd c abcde} {c aab d abcde ddd}
+ 8 {abcde aaa aab c c} {ddd c dddd b aaa}
+ 9 {abcde aab ddddd c aab} {dddd dddd b c dd}
+ 10 {ddd abcde dddd dd c} {dddd c c d abcde}
+} {
+ do_execsql_test 5.$i.1 { INSERT INTO t1 VALUES($x, $y) }
+ do_execsql_test 5.$i.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+ if {[set_test_counter errors]} break
+}
+
+#-------------------------------------------------------------------------
+#
+breakpoint
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x,y);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+do_execsql_test 6.1 {
+ INSERT INTO t1(rowid, x, y) VALUES(22, 'a b c', 'c b a');
+ REPLACE INTO t1(rowid, x, y) VALUES(22, 'd e f', 'f e d');
+}
+
+do_execsql_test 6.2 {
+ INSERT INTO t1(t1) VALUES('integrity-check')
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+expr srand(0)
+do_execsql_test 7.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x,y,z);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+proc doc {} {
+ set v [list aaa aab abc abcde b c d dd ddd dddd ddddd]
+ set ret [list]
+ for {set j 0} {$j < 20} {incr j} {
+ lappend ret [lindex $v [expr int(rand()*[llength $v])]]
+ }
+ return $ret
+}
+
+proc dump_structure {} {
+ db eval {SELECT fts5_decode(id, block) AS t FROM t1_data WHERE id=10} {
+ foreach lvl [lrange $t 1 end] {
+ set seg [string repeat . [expr [llength $lvl]-2]]
+ puts "[lrange $lvl 0 1] $seg"
+ }
+ }
+}
+
+for {set i 1} {$i <= 10} {incr i} {
+ do_test 7.$i {
+ for {set j 0} {$j < 10} {incr j} {
+ set x [doc]
+ set y [doc]
+ set z [doc]
+ set rowid [expr int(rand() * 100)]
+ execsql { REPLACE INTO t1(rowid,x,y,z) VALUES($rowid, $x, $y, $z) }
+ }
+ execsql { INSERT INTO t1(t1) VALUES('integrity-check'); }
+ } {}
+# if {$i==1} break
+}
+#db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t1_data} {puts $r}
+#exit
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 8.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, prefix="1,2,3");
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+do_execsql_test 8.1 {
+ INSERT INTO t1 VALUES('the quick brown fox');
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+
+#-------------------------------------------------------------------------
+#
+reset_db
+
+expr srand(0)
+
+do_execsql_test 9.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x,y,z, prefix="1,2,3");
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+proc doc {} {
+ set v [list aaa aab abc abcde b c d dd ddd dddd ddddd]
+ set ret [list]
+ for {set j 0} {$j < 20} {incr j} {
+ lappend ret [lindex $v [expr int(rand()*[llength $v])]]
+ }
+ return $ret
+}
+
+proc dump_structure {} {
+ db eval {SELECT fts5_decode(id, block) AS t FROM t1_data WHERE id=10} {
+ foreach lvl [lrange $t 1 end] {
+ set seg [string repeat . [expr [llength $lvl]-2]]
+ puts "[lrange $lvl 0 1] $seg"
+ }
+ }
+}
+
+for {set i 1} {$i <= 10} {incr i} {
+ do_test 9.$i {
+ for {set j 0} {$j < 100} {incr j} {
+ set x [doc]
+ set y [doc]
+ set z [doc]
+ set rowid [expr int(rand() * 100)]
+ execsql { REPLACE INTO t1(rowid,x,y,z) VALUES($rowid, $x, $y, $z) }
+ }
+ execsql { INSERT INTO t1(t1) VALUES('integrity-check'); }
+ } {}
+ if {[set_test_counter errors]} break
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 10.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x,y);
+}
+set d10 {
+ 1 {g f d b f} {h h e i a}
+ 2 {f i g j e} {i j c f f}
+ 3 {e e i f a} {e h f d f}
+ 4 {h j f j i} {h a c f j}
+ 5 {d b j c g} {f e i b e}
+ 6 {a j a e e} {j d f d e}
+ 7 {g i j c h} {j d h c a}
+ 8 {j j i d d} {e e d f b}
+ 9 {c j j d c} {h j i f g}
+ 10 {b f h i a} {c f b b j}
+}
+foreach {rowid x y} $d10 {
+ do_execsql_test 10.1.$rowid.1 { INSERT INTO t1 VALUES($x, $y) }
+ do_execsql_test 10.1.$rowid.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+}
+foreach rowid {5 9 8 1 2 4 10 7 3 5 6} {
+ do_execsql_test 10.2.$rowid.1 { DELETE FROM t1 WHERE rowid = $rowid }
+ do_execsql_test 10.2.$rowid.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+}
+foreach {rowid x y} $d10 {
+ do_execsql_test 10.3.$rowid.1 { INSERT INTO t1 VALUES($x, $y) }
+ do_execsql_test 10.3.$rowid.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+}
+
+do_execsql_test 10.4.1 { DELETE FROM t1 }
+do_execsql_test 10.4.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+
+#-------------------------------------------------------------------------
+#
+do_catchsql_test 11.1 {
+ CREATE VIRTUAL TABLE t2 USING fts5(a, b, c, rank);
+} {1 {reserved fts5 column name: rank}}
+do_catchsql_test 11.2 {
+ CREATE VIRTUAL TABLE rank USING fts5(a, b, c);
+} {1 {reserved fts5 table name: rank}}
+
+#-------------------------------------------------------------------------
+#
+do_execsql_test 12.1 {
+ CREATE VIRTUAL TABLE t2 USING fts5(x,y);
+} {}
+
+do_catchsql_test 12.2 {
+ SELECT t2 FROM t2 WHERE t2 MATCH '*stuff'
+} {1 {unknown special query: stuff}}
+
+do_test 12.3 {
+ set res [db one { SELECT t2 FROM t2 WHERE t2 MATCH '* reads ' }]
+ string is integer $res
+} {1}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 13.1 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(rowid, x) VALUES(1, 'o n e'), (2, 't w o');
+} {}
+
+do_execsql_test 13.2 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'o';
+} {1 2}
+
+do_execsql_test 13.4 {
+ DELETE FROM t1 WHERE rowid=2;
+} {}
+
+do_execsql_test 13.5 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'o';
+} {1}
+
+do_execsql_test 13.6 {
+ SELECT rowid FROM t1 WHERE t1 MATCH '.';
+} {}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 14.1 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ WITH d(x,y) AS (
+ SELECT NULL, 'xyz xyz xyz xyz xyz xyz'
+ UNION ALL
+ SELECT NULL, 'xyz xyz xyz xyz xyz xyz' FROM d
+ )
+ INSERT INTO t1 SELECT * FROM d LIMIT 200;
+}
+
+do_test 14.2 {
+ set nRow 0
+ db eval { SELECT * FROM t1 WHERE t1 MATCH 'xyz' } {
+ db eval {
+ BEGIN;
+ CREATE TABLE t2(a, b);
+ ROLLBACK;
+ }
+ incr nRow
+ }
+ set nRow
+} {200}
+
+do_test 14.3 {
+ set nRow 0
+ db eval { BEGIN; }
+ db eval { SELECT * FROM t1 WHERE t1 MATCH 'xyz' } {
+ db eval {
+ SAVEPOINT aaa;
+ CREATE TABLE t2(a, b);
+ ROLLBACK TO aaa;
+ RELEASE aaa;
+ }
+ incr nRow
+ }
+ set nRow
+} {200}
+
+do_execsql_test 15.0 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+do_execsql_test 15.1 {
+ UPDATE t1_content SET c1 = 'xyz xyz xyz xyz xyz abc' WHERE rowid = 1;
+}
+do_catchsql_test 15.2 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+} {1 {database disk image is malformed}}
+
+finish_test
+
+
diff --git a/ext/fts5/test/fts5ab.test b/ext/fts5/test/fts5ab.test
new file mode 100644
index 000000000..23fdec0df
--- /dev/null
+++ b/ext/fts5/test/fts5ab.test
@@ -0,0 +1,267 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ab
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b);
+ INSERT INTO t1 VALUES('hello', 'world');
+ INSERT INTO t1 VALUES('one two', 'three four');
+ INSERT INTO t1(rowid, a, b) VALUES(45, 'forty', 'five');
+}
+
+do_execsql_test 1.1 {
+ SELECT * FROM t1 ORDER BY rowid DESC;
+} { forty five {one two} {three four} hello world }
+
+do_execsql_test 1.2 {
+ SELECT rowid FROM t1 ORDER BY rowid DESC;
+} {45 2 1}
+
+do_execsql_test 1.3 {
+ SELECT rowid FROM t1 ORDER BY rowid ASC;
+} {1 2 45}
+
+do_execsql_test 1.4 {
+ SELECT * FROM t1 WHERE rowid=2;
+} {{one two} {three four}}
+
+do_execsql_test 1.5 {
+ SELECT * FROM t1 WHERE rowid=2.01;
+} {}
+
+do_execsql_test 1.6 {
+ SELECT * FROM t1 WHERE rowid=1.99;
+} {}
+
+#-------------------------------------------------------------------------
+
+reset_db
+do_execsql_test 2.1 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ INSERT INTO t1 VALUES('one');
+ INSERT INTO t1 VALUES('two');
+ INSERT INTO t1 VALUES('three');
+}
+
+do_catchsql_test 2.2 {
+ SELECT rowid, * FROM t1 WHERE t1 MATCH 'AND AND'
+} {1 {fts5: syntax error near "AND"}}
+
+do_execsql_test 2.3 { SELECT rowid, * FROM t1 WHERE t1 MATCH 'two' } {2 two}
+do_execsql_test 2.4 { SELECT rowid, * FROM t1 WHERE t1 MATCH 'three' } {3 three}
+do_execsql_test 2.5 { SELECT rowid, * FROM t1 WHERE t1 MATCH 'one' } {1 one}
+
+do_execsql_test 2.6 {
+ INSERT INTO t1 VALUES('a b c d e f g');
+ INSERT INTO t1 VALUES('b d e a a a i');
+ INSERT INTO t1 VALUES('x y z b c c c');
+}
+
+foreach {tn expr res} {
+ 1 a {5 4}
+ 2 b {6 5 4}
+ 3 c {6 4}
+ 4 d {5 4}
+ 5 e {5 4}
+ 6 f {4}
+ 7 g {4}
+ 8 x {6}
+ 9 y {6}
+ 10 z {6}
+} {
+ do_execsql_test 2.7.$tn.1 {
+ SELECT rowid FROM t1 WHERE t1 MATCH $expr ORDER BY rowid DESC
+ } $res
+ do_execsql_test 2.7.$tn.2 {
+ SELECT rowid FROM t1 WHERE t1 MATCH $expr ORDER BY rowid ASC
+ } [lsort -integer $res]
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a,b);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+foreach {tn a b} {
+ 1 {abashed abandons abase abash abaft} {abases abased}
+ 2 {abasing abases abaft abated abandons} {abases abandoned}
+ 3 {abatement abash abash abated abase} {abasements abashing}
+ 4 {abaft abasements abase abasement abasing} {abasement abases}
+ 5 {abaft abashing abatement abash abasements} {abandons abandoning}
+ 6 {aback abate abasements abashes abandoned} {abasement abased}
+ 7 {abandons abated abased aback abandoning} {abases abandoned}
+ 8 {abashing abases abasement abaft abashing} {abashed abate}
+ 9 {abash abase abate abashing abashed} {abandon abandoned}
+ 10 {abate abandoning abandons abasement aback} {abandon abandoning}
+} {
+ do_execsql_test 3.1.$tn.1 { INSERT INTO t1 VALUES($a, $b) }
+ do_execsql_test 3.1.$tn.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+}
+
+foreach {tn expr res} {
+ 1 {abash} {9 5 3 1}
+ 2 {abase} {9 4 3 1}
+ 3 {abase + abash} {1}
+ 4 {abash + abase} {9}
+ 5 {abaft + abashing} {8 5}
+ 6 {abandon + abandoning} {10}
+ 7 {"abashing abases abasement abaft abashing"} {8}
+} {
+ do_execsql_test 3.2.$tn {
+ SELECT rowid FROM t1 WHERE t1 MATCH $expr ORDER BY rowid DESC
+ } $res
+}
+
+do_execsql_test 3.3 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'NEAR(aback abate, 2)'
+} {6}
+
+foreach {tn expr res} {
+ 1 {abash} {1 3 5 9}
+ 2 {abase} {1 3 4 9}
+ 3 {abase + abash} {1}
+ 4 {abash + abase} {9}
+ 5 {abaft + abashing} {5 8}
+ 6 {abandon + abandoning} {10}
+ 7 {"abashing abases abasement abaft abashing"} {8}
+} {
+ do_execsql_test 3.4.$tn {
+ SELECT rowid FROM t1 WHERE t1 MATCH $expr
+ } $res
+}
+
+#-------------------------------------------------------------------------
+# Documents with more than 2M tokens.
+#
+
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE s1 USING fts5(x);
+}
+foreach {tn doc} [list \
+ 1 [string repeat {a x } 1500000] \
+ 2 "[string repeat {a a } 1500000] x" \
+] {
+ do_execsql_test 4.$tn { INSERT INTO s1 VALUES($doc) }
+}
+
+do_execsql_test 4.3 {
+ SELECT rowid FROM s1 WHERE s1 MATCH 'x'
+} {1 2}
+
+do_execsql_test 4.4 {
+ SELECT rowid FROM s1 WHERE s1 MATCH '"a x"'
+} {1 2}
+
+#-------------------------------------------------------------------------
+# Check that a special case of segment promotion works. The case is where
+# a new segment is written to level L, but the oldest segment within level
+# (L-2) is larger than it.
+#
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE s2 USING fts5(x);
+ INSERT INTO s2(s2, rank) VALUES('pgsz', 32);
+ INSERT INTO s2(s2, rank) VALUES('automerge', 0);
+}
+
+proc rnddoc {n} {
+ set map [list 0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j]
+ set doc [list]
+ for {set i 0} {$i < $n} {incr i} {
+ lappend doc [string map $map [format %.3d [expr int(rand()*1000)]]]
+ }
+ set doc
+}
+db func rnddoc rnddoc
+
+do_test 5.1 {
+ for {set i 1} {$i <= 65} {incr i} {
+ execsql { INSERT INTO s2 VALUES(rnddoc(10)) }
+ }
+ for {set i 1} {$i <= 63} {incr i} {
+ execsql { DELETE FROM s2 WHERE rowid = $i }
+ }
+ fts5_level_segs s2
+} {0 8}
+
+do_test 5.2 {
+ execsql {
+ INSERT INTO s2(s2, rank) VALUES('automerge', 8);
+ }
+ for {set i 0} {$i < 7} {incr i} {
+ execsql { INSERT INTO s2 VALUES(rnddoc(50)) }
+ }
+ fts5_level_segs s2
+} {8 0 0}
+
+# Test also the other type of segment promotion - when a new segment is written
+# that is larger than segments immediately following it.
+do_test 5.3 {
+ execsql {
+ DROP TABLE s2;
+ CREATE VIRTUAL TABLE s2 USING fts5(x);
+ INSERT INTO s2(s2, rank) VALUES('pgsz', 32);
+ INSERT INTO s2(s2, rank) VALUES('automerge', 0);
+ }
+
+ for {set i 1} {$i <= 16} {incr i} {
+ execsql { INSERT INTO s2 VALUES(rnddoc(5)) }
+ }
+ fts5_level_segs s2
+} {0 1}
+
+do_test 5.4 {
+ execsql { INSERT INTO s2 VALUES(rnddoc(160)) }
+ fts5_level_segs s2
+} {2 0}
+
+#-------------------------------------------------------------------------
+#
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE s3 USING fts5(x);
+ BEGIN;
+ INSERT INTO s3 VALUES('a b c');
+ INSERT INTO s3 VALUES('A B C');
+}
+
+do_execsql_test 6.1.1 {
+ SELECT rowid FROM s3 WHERE s3 MATCH 'a'
+} {1 2}
+
+do_execsql_test 6.1.2 {
+ SELECT rowid FROM s3 WHERE s3 MATCH 'a' ORDER BY rowid DESC
+} {2 1}
+
+do_execsql_test 6.2 {
+ COMMIT;
+}
+
+do_execsql_test 6.3 {
+ SELECT rowid FROM s3 WHERE s3 MATCH 'a'
+} {1 2}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5ac.test b/ext/fts5/test/fts5ac.test
new file mode 100644
index 000000000..b061d0bf6
--- /dev/null
+++ b/ext/fts5/test/fts5ac.test
@@ -0,0 +1,447 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ac
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+set data {
+ 0 {p o q e z k z p n f y u z y n y} {l o o l v v k}
+ 1 {p k h h p y l l h i p v n} {p p l u r i f a j g e r r x w}
+ 2 {l s z j k i m p s} {l w e j t j e e i t w r o p o}
+ 3 {x g y m y m h p} {k j j b r e y y a k y}
+ 4 {q m a i y i z} {o w a g k x g j m w e u k}
+ 5 {k o a w y b s z} {s g l m m l m g p}
+ 6 {d a q i z h b l c p k j g k} {p x u j x t v c z}
+ 7 {f d a g o c t i} {w f c x l d r k i j}
+ 8 {y g w u b q p o m j y b p a e k} {r i d k y w o z q m a t p}
+ 9 {r k o m c c j s x m x m x m q r} {y r c a q d z k n x n}
+ 10 {k j q m g q a j d} {d d e z g w h c d o o g x d}
+ 11 {j z u m o y q j f w e e w t r j w} {g m o r x n t n w i f g l z f}
+ 12 {s y w a w d o h x m k} {c w k z b p o r a}
+ 13 {u t h x e g s k n g i} {f j w g c s r}
+ 14 {b f i c s u z t k} {c k q s j u i z o}
+ 15 {n a f n u s w h y n s i q e w} {x g e g a s s h n}
+ 16 {k s q e j n p} {t r j f t o e k k l m i}
+ 17 {g d t u w r o p m n m n p h b o u} {h s w o s l j e}
+ 18 {f l q y q q g e e x j r} {n b r r g e i r t x q k}
+ 19 {f i r g o a w e p i l o a w} {e k r z t d g h g i b d i e m}
+ 20 {l d u u f p y} {g o m m u x m g l j t t x x u}
+ 21 {m c d k x i c z l} {m i a i e u h}
+ 22 {w b f o c g x y j} {z d w x d f h i p}
+ 23 {w u i u x t c h k i b} {b y k h b v r t g j}
+ 24 {h f d j s w s b a p k} {a q y u z e y m m j q r}
+ 25 {d i x y x x k i y f s d j h z p n} {l l q m e t c w g y h t s v g}
+ 26 {g s q w t d k x g f m j p k y} {r m b x e l t d}
+ 27 {j l s q u g y v e c l o} {m f l m m m h g x x l n c}
+ 28 {c t j g v r s b z j} {l c f y d t q n}
+ 29 {e x z y w i h l} {b n b x e y q e n u m}
+ 30 {g y y h j b w r} {q b q f u s k c k g r}
+ 31 {g u l x l b r c m z b u c} {k g t b x k x n t e z d h o}
+ 32 {w g v l z f b z h p s c v h} {g e w v m h k r g w a r f q}
+ 33 {c g n f u d o y o b} {e y o h x x y y i z s b h a j}
+ 34 {v y h c q u u s q y x x k s q} {d n r m y k n t i r n w e}
+ 35 {o u c x l e b t a} {y b a x y f z x r}
+ 36 {x p h l j a a u u j h} {x o f s z m b c q p}
+ 37 {k q t i c a q n m v v} {v r z e f m y o}
+ 38 {r w t t t t r v v o e p g h} {l w x a g a u h y}
+ 39 {o p v g v b a g o} {j t q c r b b g y z}
+ 40 {f s o r o d t h q f x l} {r d b m k i f s t d l m y x j w}
+ 41 {t m o t m f m f} {i p i q j v n v m b q}
+ 42 {t x w a r l w d t b c o d o} {a h f h w z d n s}
+ 43 {t u q c d g p q x j o l c x c} {m n t o z z j a y}
+ 44 {v d i i k b f s z r v r z y} {g n q y s x x m b x c l w}
+ 45 {p v v a c s z y e o l} {m v t u d k m k q b d c v z r}
+ 46 {f y k l d r q w r s t r e} {h m v r r l r r t f q e x y}
+ 47 {w l n l t y x} {n h s l a f c h u f l x x m v n o}
+ 48 {t n v i k e b p z p d j j l i o} {i v z p g u e j s i k n h w d c}
+ 49 {z v x p n l t a j c} {e j l e n c e t a d}
+ 50 {w u b x u i v h a i y m m r p m s} {s r h d o g z y f f x e}
+ 51 {d c c x b c a x g} {p r a j v u y}
+ 52 {f w g r c o d l t u e z h i} {j l l s s b j m}
+ 53 {p m t f k i x} {u v y a z g w v v m x h i}
+ 54 {l c z g l o j i c d e b} {b f v y w u i b e i y}
+ 55 {r h c x f x a d s} {z x y k f l r b q c v}
+ 56 {v x x c y h z x b g m o q n c} {h n b i t g h a q b c o r u}
+ 57 {d g l o h t b s b r} {n u e p t i m u}
+ 58 {t d y e t d c w u o s w x f c h} {i o s v y b r d r}
+ 59 {l b a p q n d r} {k d c c d n y q h g a o p e x}
+ 60 {f r z v m p k r} {x x r i s b a g f c}
+ 61 {s a z i e r f i w c n y v z t k s} {y y i r y n l s b w i e k n}
+ 62 {n x p r e x q r m v i b y} {f o o z n b s r q j}
+ 63 {y j s u j x o n r q t f} {f v k n v x u s o a d e f e}
+ 64 {u s i l y c x q} {r k c h p c h b o s s u s p b}
+ 65 {m p i o s h o} {s w h u n d m n q t y k b w c}
+ 66 {l d f g m x x x o} {s w d d f b y j j h h t i y p j o}
+ 67 {c b m h f n v w n h} {i r w i e x r w l z p x u g u l s}
+ 68 {y a h u h i m a y q} {d d r x h e v q n z y c j}
+ 69 {c x f d x o n p o b r t b l p l} {m i t k b x v f p t m l l y r o}
+ 70 {u t l w w m s} {m f m o l t k o p e}
+ 71 {f g q e l n d m z x q} {z s i i i m f w w f n g p e q}
+ 72 {n l h a v u o d f j d e x} {v v s l f g d g r a j x i f z x}
+ 73 {x v m v f i g q e w} {r y s j i k m j j e d g r n o i f}
+ 74 {g d y n o h p s y q z j d w n h w} {x o d l t j i b r d o r y}
+ 75 {p g b i u r b e q d v o a g w m k} {q y z s f q o h}
+ 76 {u z a q u f i f f b} {b s p b a a d x r r i q f}
+ 77 {w h h z t h p o a h h e e} {h w r p h k z v y f r x}
+ 78 {c a r k i a p u x} {f w l p t e m l}
+ 79 {q q u k o t r k z} {f b m c w p s s o z}
+ 80 {t i g v y q s r x m r x z e f} {x o j w a u e y s j c b u p p r o}
+ 81 {n j n h r l a r e o z w e} {v o r r j a v b}
+ 82 {i f i d k w d n h} {o i d z i z l m w s b q v u}
+ 83 {m d g q q b k b w f q q p p} {j m q f b y c i z k y q p l e a}
+ 84 {m x o n y f g} {y c n x n q j i y c l h b r q z}
+ 85 {v o z l n p c} {g n j n t b b x n c l d a g j v}
+ 86 {z n a y f b t k k t d b z a v} {r p c n r u k u}
+ 87 {b q t x z e c w} {q a o a l o a h i m j r}
+ 88 {j f h o x x a z g b a f a m i b} {j z c z y x e x w t}
+ 89 {t c t p r s u c q n} {z x l i k n f q l n t}
+ 90 {w t d q j g m r f k n} {l e w f w w a l y q k i q t p c t}
+ 91 {c b o k l i c b s j n m b l} {y f p q o w g}
+ 92 {f y d j o q t c c q m f j s t} {f h e d y m o k}
+ 93 {k x j r m a d o i z j} {r t t t f e b r x i v j v g o}
+ 94 {s f e a e t i h h d q p z t q} {b k m k w h c}
+ 95 {h b n j t k i h o q u} {w n g i t o k c a m y p f l x c p}
+ 96 {f c x p y r b m o l m o a} {p c a q s u n n x d c f a o}
+ 97 {u h h k m n k} {u b v n u a o c}
+ 98 {s p e t c z d f n w f} {l s f j b l c e s h}
+ 99 {r c v w i v h a t a c v c r e} {h h u m g o f b a e o}
+}
+
+# Usage:
+#
+# poslist aCol ?-pc VARNAME? ?-near N? ?-col C? -- phrase1 phrase2...
+#
+proc poslist {aCol args} {
+ set O(-near) 10
+ set O(-col) -1
+ set O(-pc) ""
+
+ set nOpt [lsearch -exact $args --]
+ if {$nOpt<0} { error "no -- option" }
+
+ foreach {k v} [lrange $args 0 [expr $nOpt-1]] {
+ if {[info exists O($k)]==0} { error "unrecognized option $k" }
+ set O($k) $v
+ }
+
+ if {$O(-pc) == ""} {
+ set counter 0
+ } else {
+ upvar $O(-pc) counter
+ }
+
+ # Set $phraselist to be a list of phrases. $nPhrase its length.
+ set phraselist [lrange $args [expr $nOpt+1] end]
+ set nPhrase [llength $phraselist]
+
+ for {set j 0} {$j < [llength $aCol]} {incr j} {
+ for {set i 0} {$i < $nPhrase} {incr i} {
+ set A($j,$i) [list]
+ }
+ }
+
+ set iCol -1
+ foreach col $aCol {
+ incr iCol
+ if {$O(-col)>=0 && $O(-col)!=$iCol} continue
+
+ set nToken [llength $col]
+
+ set iFL [expr $O(-near) >= $nToken ? $nToken - 1 : $O(-near)]
+ for { } {$iFL < $nToken} {incr iFL} {
+ for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
+ set B($iPhrase) [list]
+ }
+
+ for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
+ set p [lindex $phraselist $iPhrase]
+ set nPm1 [expr {[llength $p] - 1}]
+ set iFirst [expr $iFL - $O(-near) - [llength $p]]
+
+ for {set i $iFirst} {$i <= $iFL} {incr i} {
+ if {[lrange $col $i [expr $i+$nPm1]] == $p} { lappend B($iPhrase) $i }
+ }
+ if {[llength $B($iPhrase)] == 0} break
+ }
+
+ if {$iPhrase==$nPhrase} {
+ for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
+ set A($iCol,$iPhrase) [concat $A($iCol,$iPhrase) $B($iPhrase)]
+ set A($iCol,$iPhrase) [lsort -integer -uniq $A($iCol,$iPhrase)]
+ }
+ }
+ }
+ }
+
+ set res [list]
+#puts [array names A]
+
+ for {set iPhrase 0} {$iPhrase<$nPhrase} {incr iPhrase} {
+ for {set iCol 0} {$iCol < [llength $aCol]} {incr iCol} {
+ foreach a $A($iCol,$iPhrase) {
+ lappend res "$counter.$iCol.$a"
+ }
+ }
+ incr counter
+ }
+
+ #puts $res
+ return $res
+}
+
+# Usage:
+#
+# nearset aCol ?-near N? ?-col C? -- phrase1 phrase2...
+#
+proc nearset {args} {
+ set plist [poslist {*}$args]
+ return [expr [llength [lindex $plist 0]]>0]
+}
+
+proc instcompare {lhs rhs} {
+ foreach {p1 c1 o1} [split $lhs .] {}
+ foreach {p2 c2 o2} [split $rhs .] {}
+
+ set res [expr $c1 - $c2]
+ if {$res==0} { set res [expr $o1 - $o2] }
+ if {$res==0} { set res [expr $p1 - $p2] }
+
+ return $res
+}
+
+# Argument $expr is an FTS5 match expression designed to be executed against
+# an FTS5 table with the following schema:
+#
+# CREATE VIRTUAL TABLE xy USING fts5(x, y);
+#
+# Assuming the table contains the same records as stored int the global
+# $::data array (see above), this function returns a list containing one
+# element for each match in the dataset. The elements are themselves lists
+# formatted as follows:
+#
+# <rowid> {<phrase 0 matches> <phrase 1 matches>...}
+#
+# where each <phrase X matches> element is a list of phrase matches in the
+# same form as returned by auxiliary scalar function fts5_test().
+#
+proc matchdata {bPos expr {bAsc 1}} {
+
+ set tclexpr [db one {SELECT fts5_expr_tcl($expr, 'nearset $cols', 'x', 'y')}]
+ set res [list]
+
+ #puts $tclexpr
+ foreach {id x y} $::data {
+ set cols [list $x $y]
+ if $tclexpr {
+ if {$bPos} {
+ set N [regexp -all -inline {\[nearset [^\]]*\]} $tclexpr]
+ set rowres [list]
+ set cnt 0
+ foreach phrase $N {
+ set arglist [string range $phrase 9 end-1]
+ set cmd "poslist [lindex $arglist 0] -pc cnt [lrange $arglist 1 end]"
+ set pos [eval $cmd]
+ set rowres [concat $rowres $pos]
+ }
+ set rowres [lsort -command instcompare $rowres]
+ lappend res [list $id $rowres]
+ } else {
+ lappend res $id
+ }
+ }
+ }
+
+ if {$bAsc} {
+ set res [lsort -integer -increasing -index 0 $res]
+ } else {
+ set res [lsort -integer -decreasing -index 0 $res]
+ }
+
+ return [concat {*}$res]
+}
+
+#
+# End of test code
+#-------------------------------------------------------------------------
+
+proc fts5_test_poslist {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xInstCount]} {incr i} {
+ lappend res [string map {{ } .} [$cmd xInst $i]]
+ }
+ set res
+}
+
+
+foreach {tn2 sql} {
+ 1 {}
+ 2 {BEGIN}
+} {
+ reset_db
+ sqlite3_fts5_create_function db fts5_test_poslist fts5_test_poslist
+
+ do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE xx USING fts5(x,y);
+ INSERT INTO xx(xx, rank) VALUES('pgsz', 32);
+ }
+
+ execsql $sql
+
+ do_test $tn2.1.1 {
+ foreach {id x y} $data {
+ execsql { INSERT INTO xx(rowid, x, y) VALUES($id, $x, $y) }
+ }
+ execsql { INSERT INTO xx(xx) VALUES('integrity-check') }
+ } {}
+
+ #-------------------------------------------------------------------------
+ # Test phrase queries.
+ #
+ foreach {tn phrase} {
+ 1 "o"
+ 2 "b q"
+ 3 "e a e"
+ 4 "m d g q q b k b w f q q p p"
+ 5 "l o o l v v k"
+ 6 "a"
+ 7 "b"
+ 8 "c"
+ 9 "no"
+ 10 "L O O L V V K"
+ } {
+ set expr "\"$phrase\""
+ set res [matchdata 1 $expr]
+
+ do_execsql_test $tn2.1.2.$tn.[llength $res] {
+ SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
+ } $res
+ }
+
+ #-------------------------------------------------------------------------
+ # Test some AND and OR queries.
+ #
+ foreach {tn expr} {
+ 1.1 "a AND b"
+ 1.2 "a+b AND c"
+ 1.3 "d+c AND u"
+ 1.4 "d+c AND u+d"
+
+ 2.1 "a OR b"
+ 2.2 "a+b OR c"
+ 2.3 "d+c OR u"
+ 2.4 "d+c OR u+d"
+
+ 3.1 { a AND b AND c }
+ } {
+ set res [matchdata 1 $expr]
+ do_execsql_test $tn2.2.$tn.[llength $res] {
+ SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
+ } $res
+ }
+
+ #-------------------------------------------------------------------------
+ # Queries on a specific column.
+ #
+ foreach {tn expr} {
+ 1 "x:a"
+ 2 "y:a"
+ 3 "x:b"
+ 4 "y:b"
+ } {
+ set res [matchdata 1 $expr]
+ do_execsql_test $tn2.3.$tn.[llength $res] {
+ SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
+ } $res
+ }
+
+ #-------------------------------------------------------------------------
+ # Some NEAR queries.
+ #
+ foreach {tn expr} {
+ 1 "NEAR(a b)"
+ 2 "NEAR(r c)"
+ 2 { NEAR(r c, 5) }
+ 3 { NEAR(r c, 3) }
+ 4 { NEAR(r c, 2) }
+ 5 { NEAR(r c, 0) }
+ 6 { NEAR(a b c) }
+ 7 { NEAR(a b c, 8) }
+ 8 { x : NEAR(r c) }
+ 9 { y : NEAR(r c) }
+ } {
+ set res [matchdata 1 $expr]
+ do_execsql_test $tn2.4.1.$tn.[llength $res] {
+ SELECT rowid, fts5_test_poslist(xx) FROM xx WHERE xx match $expr
+ } $res
+ }
+
+ do_test $tn2.4.1 { poslist {{a b c}} -- a } {0.0.0}
+ do_test $tn2.4.2 { poslist {{a b c}} -- c } {0.0.2}
+
+ foreach {tn expr tclexpr} {
+ 1 {a b} {[N $x -- {a}] && [N $x -- {b}]}
+ } {
+ do_execsql_test $tn2.5.$tn {
+ SELECT fts5_expr_tcl($expr, 'N $x')
+ } [list $tclexpr]
+ }
+
+ #-------------------------------------------------------------------------
+ #
+ do_execsql_test $tn2.6.integrity {
+ INSERT INTO xx(xx) VALUES('integrity-check');
+ }
+ #db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM xx_data} {puts $r}
+ foreach {bAsc sql} {
+ 1 {SELECT rowid FROM xx WHERE xx MATCH $expr}
+ 0 {SELECT rowid FROM xx WHERE xx MATCH $expr ORDER BY rowid DESC}
+ } {
+ foreach {tn expr} {
+ 0.1 x
+ 1 { NEAR(r c) }
+ 2 { NEAR(r c, 5) }
+ 3 { NEAR(r c, 3) }
+ 4 { NEAR(r c, 2) }
+ 5 { NEAR(r c, 0) }
+ 6 { NEAR(a b c) }
+ 7 { NEAR(a b c, 8) }
+ 8 { x : NEAR(r c) }
+ 9 { y : NEAR(r c) }
+ 10 { x : "r c" }
+ 11 { y : "r c" }
+ 12 { a AND b }
+ 13 { a AND b AND c }
+ 14a { a }
+ 14b { a OR b }
+ 15 { a OR b AND c }
+ 16 { c AND b OR a }
+ 17 { c AND (b OR a) }
+ 18 { c NOT (b OR a) }
+ 19 { c NOT b OR a AND d }
+ } {
+ set res [matchdata 0 $expr $bAsc]
+ do_execsql_test $tn2.6.$bAsc.$tn.[llength $res] $sql $res
+ }
+ }
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5ad.test b/ext/fts5/test/fts5ad.test
new file mode 100644
index 000000000..461fe41e5
--- /dev/null
+++ b/ext/fts5/test/fts5ad.test
@@ -0,0 +1,235 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ad
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE yy USING fts5(x, y);
+ INSERT INTO yy VALUES('Changes the result to be', 'the list of all matching');
+ INSERT INTO yy VALUES('indices (or all matching', 'values if -inline is');
+ INSERT INTO yy VALUES('specified as well.) If', 'indices are returned, the');
+} {}
+
+foreach {tn match res} {
+ 1 {c*} {1}
+ 2 {i*} {3 2}
+ 3 {t*} {3 1}
+ 4 {r*} {3 1}
+} {
+ do_execsql_test 1.$tn {
+ SELECT rowid FROM yy WHERE yy MATCH $match ORDER BY rowid DESC
+ } $res
+}
+
+foreach {tn match res} {
+ 5 {c*} {1}
+ 6 {i*} {2 3}
+ 7 {t*} {1 3}
+ 8 {r*} {1 3}
+} {
+ do_execsql_test 1.$tn {
+ SELECT rowid FROM yy WHERE yy MATCH $match
+ } $res
+}
+
+foreach {T create} {
+ 2 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ }
+
+ 3 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix=1,2,3,4,5);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ }
+
+ 4 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ BEGIN;
+ }
+
+ 5 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix=1,2,3,4,5);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+ BEGIN;
+ }
+
+} {
+
+ do_test $T.1 {
+ execsql { DROP TABLE IF EXISTS t1 }
+ execsql $create
+ } {}
+
+ do_test $T.1 {
+ foreach {rowid a b} {
+ 0 {fghij uvwxyz klmn pq uvwx} {klmn f fgh uv fghij klmno}
+ 1 {uv f abcd abcd fghi} {pq klm uv uv fgh uv a}
+ 2 {klmn klm pqrs fghij uv} {f k uvw ab abcd pqr uv}
+ 3 {ab pqrst a fghi ab pqr fg} {k klmno a fg abcd}
+ 4 {abcd pqrst uvwx a fgh} {f klmno fghij kl pqrst}
+ 5 {uvwxyz k abcde u a} {uv k k kl klmn}
+ 6 {uvwxyz k klmn pqrst uv} {fghi pqrs abcde u k}
+ 7 {uvwxy klmn u p pqrst fgh} {p f fghi abcd uvw kl uv}
+ 8 {f klmno pqrst uvwxy pqrst} {uv abcde klm pq pqr}
+ 9 {f abcde a uvwxyz pqrst} {fghij abc k uvwx pqr fghij uvwxy}
+ 10 {ab uv f fg pqrst uvwxy} {fgh p uv k abc klm uvw}
+ 11 {pq klmno a uvw abcde uvwxyz} {fghij pq uvwxyz pqr fghi}
+ 12 {fgh u pq fgh uvw} {uvw pqr f uvwxy uvwx}
+ 13 {uvwx klmn f fgh abcd pqr} {uvw k fg uv klm abcd}
+ 14 {ab uvwx pqrst pqr uvwxyz pqrs} {uvwxyz abcde ab ab uvw abcde}
+ 15 {abc abcde uvwxyz abc kl k pqr} {klm k k klmno u fgh}
+ 16 {fghi abcd fghij uv uvwxyz ab uv} {klmn pqr a uvw fghi}
+ 17 {abc pqrst fghi uvwx uvw klmn fghi} {ab fg pqr pqrs p}
+ 18 {pqr kl a fghij fgh fg kl} {pqr uvwxyz uvw abcd uvwxyz}
+ 19 {fghi fghi pqr kl fghi f} {klmn u u klmno klmno}
+ 20 {abc pqrst klmno kl pq uvwxy} {abc k fghi pqrs klm}
+ 21 {a pqr uvwxyz uv fghi a fgh} {abc pqrs pqrst pq klm}
+ 22 {klm abc uvwxyz klm pqrst} {fghij k pq pqr u klm fghij}
+ 23 {p klm uv p a a} {uvwxy klmn uvw abcde pq}
+ 24 {uv fgh fg pq uvwxy u uvwxy} {pqrs a uvw p uvwx uvwxyz fg}
+ 25 {fghij fghi klmn abcd pq kl} {fghi abcde pqrs abcd fgh uvwxy}
+ 26 {pq fgh a abc klmno klmn} {fgh p k p fg fghij}
+ 27 {fg pq kl uvwx fghij pqrst klmn} {abcd uvw abcd fghij f fghij}
+ 28 {uvw fghi p fghij pq fgh uvwx} {k fghij abcd uvwx pqr fghi}
+ 29 {klm pq abcd pq f uvwxy} {pqrst p fghij pqr p}
+ 30 {ab uvwx fg uvwx klmn klm} {klmn klmno fghij klmn klm}
+ 31 {pq k pqr abcd a pqrs} {abcd abcd uvw a abcd klmno ab}
+ 32 {pqrst u abc pq klm} {abc kl uvwxyz fghij u fghi p}
+ 33 {f uvwxy u k f uvw uvwx} {pqrs uvw fghi fg pqrst klm}
+ 34 {pqrs pq fghij uvwxyz pqr} {ab abc abc uvw f pq f}
+ 35 {uvwxy ab uvwxy klmno kl pqrs} {abcde uvw pqrs uvwx k k}
+ 36 {uvwxyz k ab abcde abc uvw} {uvw abcde uvw klmn uv klmn}
+ 37 {k kl uv abcde uvwx fg u} {u abc uvwxy k fg abcd}
+ 38 {fghi pqrst fghi pqr pqrst uvwx} {u uv uvwx fghi abcde}
+ 39 {k pqrst k uvw fg pqrst fghij} {uvwxy ab kl klmn uvwxyz abcde}
+ 40 {fg uvwxy pqrs klmn uvwxyz klm p} {k uv ab fghij fgh k pqrs}
+ 41 {uvwx abc f pq uvwxy k} {ab uvwxyz abc f fghij}
+ 42 {uvwxy klmno uvwxyz uvwxyz pqrst} {uv kl kl klmno k f abcde}
+ 43 {abcde ab pqrs fg f fgh} {abc fghij fghi k k}
+ 44 {uvw abcd a ab pqrst klmn fg} {pqrst u uvwx pqrst fghij f pqrst}
+ 45 {uvwxy p kl uvwxyz ab pqrst fghi} {abc f pqr fg a k}
+ 46 {u p f a fgh} {a kl pq uv f}
+ 47 {pqrs abc fghij fg abcde ab a} {p ab uv pqrs kl fghi abcd}
+ 48 {abcde uvwxy pqrst uv abc pqr uvwx} {uvwxy klm uvwxy uvwx k}
+ 49 {fgh klm abcde klmno u} {a f fghij f uvwxyz abc u}
+ 50 {uv uvw uvwxyz uvwxyz uv ab} {uvwx pq fg u k uvwxy}
+ 51 {uvwxy pq p kl fghi} {pqrs fghi pqrs abcde uvwxyz ab}
+ 52 {pqr p uvwxy kl pqrs klmno fghij} {ab abcde abc pqrst pqrs uv}
+ 53 {fgh pqrst p a klmno} {ab ab pqrst pqr kl pqrst}
+ 54 {abcd klm ab uvw a fg u} {f pqr f abcd uv}
+ 55 {u fg uvwxyz k uvw} {abc pqrs f fghij fg pqrs uvwxy}
+ 56 {klm fg p fghi fg a} {uv a fghi uvwxyz a fghi}
+ 57 {uvwxy k abcde fgh f fghi} {f kl klmn f fghi klm}
+ 58 {klm k fgh uvw fgh fghi} {klmno uvwx u pqrst u}
+ 59 {fghi pqr pqrst p uvw fghij} {uv pqrst pqrs pq fghij klm}
+ 60 {uvwx klm uvwxy uv klmn} {p a a abc klmn ab k}
+ 61 {uvwxy uvwx klm uvwx klm} {pqrs ab ab uvwxyz fg}
+ 62 {kl uv uv uvw fg kl k} {abcde uvw fgh uvwxy klm}
+ 63 {a abc fgh u klm abcd} {fgh pqr uv klmn fghij}
+ 64 {klmn k klmn klmno pqrs pqr} {fg kl abcde klmno uvwxy kl pq}
+ 65 {uvwxyz klm fghi abc abcde kl} {uvwxy uvw uvwxyz uvwxyz pq pqrst}
+ 66 {pq klm abc pqrst fgh f} {u abcde pqrst abcde fg}
+ 67 {u pqrst kl u uvw klmno} {u pqr pqrs fgh u p}
+ 68 {abc fghi uvwxy fgh k pq} {uv p uvwx uvwxyz ab}
+ 69 {klmno f uvwxyz uvwxy klmn fg ab} {fgh kl a pqr abcd pqr}
+ 70 {fghi pqrst pqrst uv a} {uvwxy k p uvw uvwx a}
+ 71 {a fghij f p uvw} {klm fg abcd abcde klmno pqrs}
+ 72 {uv uvwx uvwx uvw klm} {uv fghi klmno uvwxy uvw}
+ 73 {kl uvwxy ab f pq klm u} {uvwxy klmn klm abcd pq fg k}
+ 74 {uvw pqrst abcd uvwxyz ab} {fgh fgh klmn abc pq}
+ 75 {uvwxyz klm pq abcd klmno pqr uvwxyz} {kl f a fg pqr klmn}
+ 76 {uvw uvwxy pqr k pqrst kl} {uvwxy abc uvw uvw u}
+ 77 {fgh klm u uvwxyz f uvwxy abcde} {uv abcde klmno u u ab}
+ 78 {klmno abc pq pqr fgh} {p uv abcd fgh abc u k}
+ 79 {fg pqr uvw pq uvwx} {uv uvw fghij pqrs fg p}
+ 80 {abcd pqrs uvwx uvwxy uvwx} {u uvw pqrst pqr abcde pqrs kl}
+ 81 {uvwxyz klm pq uvwxy fghij} {p pq klm fghij u a a}
+ 82 {uvwx k uvwxyz klmno pqrst kl} {abcde p f pqrst abcd uvwxyz p}
+ 83 {abcd abcde klm pqrst uvwxyz} {uvw pqrst u p uvwxyz a pqrs}
+ 84 {k klm abc uv uvwxy klm klmn} {k abc pqr a abc p kl}
+ 85 {klmn abcd pqrs p pq klm a} {klmn kl ab uvw pq}
+ 86 {klmn a pqrs abc uvw pqrst} {a pqr kl klm a k f}
+ 87 {pqrs ab uvwx uvwxy a pqr f} {fg klm uvwx pqr pqr}
+ 88 {klmno ab k kl u uvwxyz} {uv kl uvw fghi uv uvw}
+ 89 {pq fghi pqrst klmn uvwxy abc pqrs} {fg f f fg abc abcde klm}
+ 90 {kl a k fghi uvwx fghi u} {ab uvw pqr fg a p abc}
+ 91 {uvwx pqrs klmno ab fgh uvwx} {pqr uvwx abc kl f klmno kl}
+ 92 {fghij pq pqrs fghij f pqrst} {u abcde fg pq pqr fgh k}
+ 93 {fgh u pqrs abcde klmno abc} {abc fg pqrst pqr abcde}
+ 94 {uvwx p abc f pqr p} {k pqrs kl klm abc fghi klm}
+ 95 {kl p klmno uvwxyz klmn} {fghi ab a fghi pqrs kl}
+ 96 {pqr fgh pq uvwx a} {uvw klm klmno fg uvwxy uvwx}
+ 97 {fg abc uvwxyz fghi pqrst pq} {abc k a ab abcde f}
+ 98 {uvwxy fghi uvwxy u abcde abcde uvw} {klmn uvwx pqrs uvw uvwxy abcde}
+ 99 {pq fg fghi uvwx uvwx fghij uvwxy} {klmn klmn f abc fg a}
+ } {
+ execsql {
+ INSERT INTO t1(rowid, a, b) VALUES($rowid, $a, $b);
+ }
+ }
+ } {}
+
+ proc prefix_query {prefixlist} {
+ set ret [list]
+ db eval {SELECT rowid, a, b FROM t1 ORDER BY rowid DESC} {
+ set bMatch 1
+ foreach pref $prefixlist {
+ if { [lsearch -glob $a $pref]<0 && [lsearch -glob $b $pref]<0 } {
+ set bMatch 0
+ break
+ }
+ }
+ if {$bMatch} { lappend ret $rowid }
+ }
+ return $ret
+ }
+
+ foreach {bAsc sql} {
+ 0 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix ORDER BY rowid DESC}
+ 1 {SELECT rowid FROM t1 WHERE t1 MATCH $prefix}
+ } {
+ foreach {tn prefix} {
+ 1 {a*} 2 {ab*} 3 {abc*} 4 {abcd*} 5 {abcde*}
+ 6 {f*} 7 {fg*} 8 {fgh*} 9 {fghi*} 10 {fghij*}
+ 11 {k*} 12 {kl*} 13 {klm*} 14 {klmn*} 15 {klmno*}
+ 16 {p*} 17 {pq*} 18 {pqr*} 19 {pqrs*} 20 {pqrst*}
+ 21 {u*} 22 {uv*} 23 {uvw*} 24 {uvwx*} 25 {uvwxy*} 26 {uvwxyz*}
+ 27 {x*}
+ 28 {a f*} 29 {a* f*} 30 {a* fghij*}
+ } {
+ set res [prefix_query $prefix]
+ if {$bAsc} {
+ set res [lsort -integer -increasing $res]
+ }
+ set n [llength $res]
+ if {$T==5} breakpoint
+ do_execsql_test $T.$bAsc.$tn.$n $sql $res
+ }
+ }
+
+ catchsql COMMIT
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5ae.test b/ext/fts5/test/fts5ae.test
new file mode 100644
index 000000000..d310e723b
--- /dev/null
+++ b/ext/fts5/test/fts5ae.test
@@ -0,0 +1,281 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ae
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+do_execsql_test 1.1 {
+ INSERT INTO t1 VALUES('hello', 'world');
+ SELECT rowid FROM t1 WHERE t1 MATCH 'hello' ORDER BY rowid ASC;
+} {1}
+
+do_execsql_test 1.2 {
+ INSERT INTO t1 VALUES('world', 'hello');
+ SELECT rowid FROM t1 WHERE t1 MATCH 'hello' ORDER BY rowid ASC;
+} {1 2}
+
+do_execsql_test 1.3 {
+ INSERT INTO t1 VALUES('world', 'world');
+ SELECT rowid FROM t1 WHERE t1 MATCH 'hello' ORDER BY rowid ASC;
+} {1 2}
+
+do_execsql_test 1.4.1 {
+ INSERT INTO t1 VALUES('hello', 'hello');
+}
+
+do_execsql_test 1.4.2 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'hello' ORDER BY rowid ASC;
+} {1 2 4}
+
+fts5_aux_test_functions db
+
+#-------------------------------------------------------------------------
+#
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(x, y);
+ INSERT INTO t2 VALUES('u t l w w m s', 'm f m o l t k o p e');
+ INSERT INTO t2 VALUES('f g q e l n d m z x q', 'z s i i i m f w w f n g p');
+}
+
+do_execsql_test 2.1 {
+ SELECT rowid, fts5_test_poslist(t2) FROM t2
+ WHERE t2 MATCH 'm' ORDER BY rowid;
+} {
+ 1 {0.0.5 0.1.0 0.1.2}
+ 2 {0.0.7 0.1.5}
+}
+
+do_execsql_test 2.2 {
+ SELECT rowid, fts5_test_poslist(t2) FROM t2
+ WHERE t2 MATCH 'u OR q' ORDER BY rowid;
+} {
+ 1 {0.0.0}
+ 2 {1.0.2 1.0.10}
+}
+
+do_execsql_test 2.3 {
+ SELECT rowid, fts5_test_poslist(t2) FROM t2
+ WHERE t2 MATCH 'y:o' ORDER BY rowid;
+} {
+ 1 {0.1.3 0.1.7}
+}
+
+#-------------------------------------------------------------------------
+#
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t3 USING fts5(x, y);
+ INSERT INTO t3 VALUES( 'j f h o x x a z g b a f a m i b', 'j z c z y x w t');
+ INSERT INTO t3 VALUES( 'r c', '');
+}
+
+do_execsql_test 3.1 {
+ SELECT rowid, fts5_test_poslist(t3) FROM t3 WHERE t3 MATCH 'NEAR(a b)';
+} {
+ 1 {0.0.6 1.0.9 0.0.10 0.0.12 1.0.15}
+}
+
+do_execsql_test 3.2 {
+ SELECT rowid, fts5_test_poslist(t3) FROM t3 WHERE t3 MATCH 'NEAR(r c)';
+} {
+ 2 {0.0.0 1.0.1}
+}
+
+do_execsql_test 3.3 {
+ INSERT INTO t3
+ VALUES('k x j r m a d o i z j', 'r t t t f e b r x i v j v g o');
+ SELECT rowid, fts5_test_poslist(t3)
+ FROM t3 WHERE t3 MATCH 'a OR b AND c';
+} {
+ 1 {0.0.6 1.0.9 0.0.10 0.0.12 1.0.15 2.1.2}
+ 3 0.0.5
+}
+
+#-------------------------------------------------------------------------
+#
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t4 USING fts5(x, y);
+ INSERT INTO t4
+ VALUES('k x j r m a d o i z j', 'r t t t f e b r x i v j v g o');
+}
+
+do_execsql_test 4.1 {
+ SELECT rowid, fts5_test_poslist(t4) FROM t4 WHERE t4 MATCH 'a OR b AND c';
+} {
+ 1 0.0.5
+}
+
+#-------------------------------------------------------------------------
+# Test that the xColumnSize() and xColumnAvgsize() APIs work.
+#
+reset_db
+fts5_aux_test_functions db
+
+do_execsql_test 5.1 {
+ CREATE VIRTUAL TABLE t5 USING fts5(x, y);
+ INSERT INTO t5 VALUES('a b c d', 'e f g h i j');
+ INSERT INTO t5 VALUES('', 'a');
+ INSERT INTO t5 VALUES('a', '');
+}
+do_execsql_test 5.2 {
+ SELECT rowid, fts5_test_columnsize(t5) FROM t5 WHERE t5 MATCH 'a'
+ ORDER BY rowid DESC;
+} {
+ 3 {1 0}
+ 2 {0 1}
+ 1 {4 6}
+}
+
+do_execsql_test 5.2 {
+ SELECT rowid, fts5_test_columntext(t5) FROM t5 WHERE t5 MATCH 'a'
+ ORDER BY rowid DESC;
+} {
+ 3 {a {}}
+ 2 {{} a}
+ 1 {{a b c d} {e f g h i j}}
+}
+
+do_execsql_test 5.3 {
+ SELECT rowid, fts5_test_columntotalsize(t5) FROM t5 WHERE t5 MATCH 'a'
+ ORDER BY rowid DESC;
+} {
+ 3 {5 7}
+ 2 {5 7}
+ 1 {5 7}
+}
+
+do_execsql_test 5.4 {
+ INSERT INTO t5 VALUES('x y z', 'v w x y z');
+ SELECT rowid, fts5_test_columntotalsize(t5) FROM t5 WHERE t5 MATCH 'a'
+ ORDER BY rowid DESC;
+} {
+ 3 {8 12}
+ 2 {8 12}
+ 1 {8 12}
+}
+
+#-------------------------------------------------------------------------
+# Test the xTokenize() API
+#
+reset_db
+fts5_aux_test_functions db
+do_execsql_test 6.1 {
+ CREATE VIRTUAL TABLE t6 USING fts5(x, y);
+ INSERT INTO t6 VALUES('There are more', 'things in heaven and earth');
+ INSERT INTO t6 VALUES(', Horatio, Than are', 'dreamt of in your philosophy.');
+}
+
+do_execsql_test 6.2 {
+ SELECT rowid, fts5_test_tokenize(t6) FROM t6 WHERE t6 MATCH 't*'
+} {
+ 1 {{there are more} {things in heaven and earth}}
+ 2 {{horatio than are} {dreamt of in your philosophy}}
+}
+
+#-------------------------------------------------------------------------
+# Test the xQueryPhrase() API
+#
+reset_db
+fts5_aux_test_functions db
+do_execsql_test 7.1 {
+ CREATE VIRTUAL TABLE t7 USING fts5(x, y);
+}
+do_test 7.2 {
+ foreach {x y} {
+ {q i b w s a a e l o} {i b z a l f p t e u}
+ {b a z t a l o x d i} {b p a d b f h d w y}
+ {z m h n p p u i e g} {v h d v b x j j c z}
+ {a g i m v a u c b i} {p k s o t l r t b m}
+ {v v c j o d a s c p} {f f v o k p o f o g}
+ } {
+ execsql {INSERT INTO t7 VALUES($x, $y)}
+ }
+ execsql { SELECT count(*) FROM t7 }
+} {5}
+
+foreach {tn q res} {
+ 1 a {{4 2}}
+ 2 b {{3 4}}
+ 3 c {{2 1}}
+ 4 d {{2 2}}
+ 5 {a AND b} {{4 2} {3 4}}
+ 6 {a OR b OR c OR d} {{4 2} {3 4} {2 1} {2 2}}
+} {
+ do_execsql_test 7.3.$tn {
+ SELECT fts5_test_queryphrase(t7) FROM t7 WHERE t7 MATCH $q LIMIT 1
+ } [list $res]
+}
+
+do_execsql_test 7.4 {
+ SELECT fts5_test_rowcount(t7) FROM t7 WHERE t7 MATCH 'a';
+} {5 5 5 5}
+
+#do_execsql_test 7.4 {
+# SELECT rowid, bm25debug(t7) FROM t7 WHERE t7 MATCH 'a';
+#} {5 5 5 5}
+#
+
+#-------------------------------------------------------------------------
+#
+do_test 8.1 {
+ execsql { CREATE VIRTUAL TABLE t8 USING fts5(x, y) }
+ foreach {rowid x y} {
+ 0 {A o} {o o o C o o o o o o o o}
+ 1 {o o B} {o o o C C o o o o o o o}
+ 2 {A o o} {o o o o D D o o o o o o}
+ 3 {o B} {o o o o o D o o o o o o}
+ 4 {E o G} {H o o o o o o o o o o o}
+ 5 {F o G} {I o J o o o o o o o o o}
+ 6 {E o o} {H o J o o o o o o o o o}
+ 7 {o o o} {o o o o o o o o o o o o}
+ 9 {o o o} {o o o o o o o o o o o o}
+ } {
+ execsql { INSERT INTO t8(rowid, x, y) VALUES($rowid, $x, $y) }
+ }
+} {}
+
+foreach {tn q res} {
+ 1 {a} {0 2}
+ 2 {b} {3 1}
+ 3 {c} {1 0}
+ 4 {d} {2 3}
+ 5 {g AND (e OR f)} {5 4}
+ 6 {j AND (h OR i)} {5 6}
+} {
+ do_execsql_test 8.2.$tn.1 {
+ SELECT rowid FROM t8 WHERE t8 MATCH $q ORDER BY bm25(t8);
+ } $res
+
+ do_execsql_test 8.2.$tn.2 {
+ SELECT rowid FROM t8 WHERE t8 MATCH $q ORDER BY +rank;
+ } $res
+
+ do_execsql_test 8.2.$tn.3 {
+ SELECT rowid FROM t8 WHERE t8 MATCH $q ORDER BY rank;
+ } $res
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5af.test b/ext/fts5/test/fts5af.test
new file mode 100644
index 000000000..8c50f8486
--- /dev/null
+++ b/ext/fts5/test/fts5af.test
@@ -0,0 +1,144 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+# More specifically, the tests in this file focus on the built-in
+# snippet() function.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5af
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y);
+}
+
+proc do_snippet_test {tn doc match res} {
+
+ uplevel #0 [list set v1 $doc]
+ uplevel #0 [list set v2 $match]
+
+ do_execsql_test $tn.1 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES($v1, NULL);
+ SELECT snippet(t1, -1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH $v2;
+ } [list $res]
+
+ do_execsql_test $tn.2 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES(NULL, $v1);
+ SELECT snippet(t1, -1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH $v2;
+ } [list $res]
+
+ do_execsql_test $tn.3 {
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES($v1, NULL);
+ SELECT snippet(t1, -1, '[', ']', '...', 7) FROM t1 WHERE t1 MATCH $v2
+ ORDER BY rank DESC;
+ } [list $res]
+
+
+}
+
+
+foreach {tn doc res} {
+
+ 1.1 {X o o o o o o} {[X] o o o o o o}
+ 1.2 {o X o o o o o} {o [X] o o o o o}
+ 1.3 {o o X o o o o} {o o [X] o o o o}
+ 1.4 {o o o X o o o} {o o o [X] o o o}
+ 1.5 {o o o o X o o} {o o o o [X] o o}
+ 1.6 {o o o o o X o} {o o o o o [X] o}
+ 1.7 {o o o o o o X} {o o o o o o [X]}
+
+ 2.1 {X o o o o o o o} {[X] o o o o o o...}
+ 2.2 {o X o o o o o o} {o [X] o o o o o...}
+ 2.3 {o o X o o o o o} {o o [X] o o o o...}
+ 2.4 {o o o X o o o o} {o o o [X] o o o...}
+ 2.5 {o o o o X o o o} {...o o o [X] o o o}
+ 2.6 {o o o o o X o o} {...o o o o [X] o o}
+ 2.7 {o o o o o o X o} {...o o o o o [X] o}
+ 2.8 {o o o o o o o X} {...o o o o o o [X]}
+
+ 3.1 {X o o o o o o o o} {[X] o o o o o o...}
+ 3.2 {o X o o o o o o o} {o [X] o o o o o...}
+ 3.3 {o o X o o o o o o} {o o [X] o o o o...}
+ 3.4 {o o o X o o o o o} {o o o [X] o o o...}
+ 3.5 {o o o o X o o o o} {...o o o [X] o o o...}
+ 3.6 {o o o o o X o o o} {...o o o [X] o o o}
+ 3.7 {o o o o o o X o o} {...o o o o [X] o o}
+ 3.8 {o o o o o o o X o} {...o o o o o [X] o}
+ 3.9 {o o o o o o o o X} {...o o o o o o [X]}
+
+ 4.1 {X o o o o o X o o} {[X] o o o o o [X]...}
+ 4.2 {o X o o o o o X o} {...[X] o o o o o [X]...}
+ 4.3 {o o X o o o o o X} {...[X] o o o o o [X]}
+
+ 5.1 {X o o o o X o o o} {[X] o o o o [X] o...}
+ 5.2 {o X o o o o X o o} {...[X] o o o o [X] o...}
+ 5.3 {o o X o o o o X o} {...[X] o o o o [X] o}
+ 5.4 {o o o X o o o o X} {...o [X] o o o o [X]}
+
+ 6.1 {X o o o X o o o} {[X] o o o [X] o o...}
+ 6.2 {o X o o o X o o o} {o [X] o o o [X] o...}
+ 6.3 {o o X o o o X o o} {...o [X] o o o [X] o...}
+ 6.4 {o o o X o o o X o} {...o [X] o o o [X] o}
+ 6.5 {o o o o X o o o X} {...o o [X] o o o [X]}
+
+ 7.1 {X o o X o o o o o} {[X] o o [X] o o o...}
+ 7.2 {o X o o X o o o o} {o [X] o o [X] o o...}
+ 7.3 {o o X o o X o o o} {...o [X] o o [X] o o...}
+ 7.4 {o o o X o o X o o} {...o [X] o o [X] o o}
+ 7.5 {o o o o X o o X o} {...o o [X] o o [X] o}
+ 7.6 {o o o o o X o o X} {...o o o [X] o o [X]}
+} {
+ do_snippet_test 1.$tn $doc X $res
+}
+
+foreach {tn doc res} {
+ 1.1 {X Y o o o o o} {[X Y] o o o o o}
+ 1.2 {o X Y o o o o} {o [X Y] o o o o}
+ 1.3 {o o X Y o o o} {o o [X Y] o o o}
+ 1.4 {o o o X Y o o} {o o o [X Y] o o}
+ 1.5 {o o o o X Y o} {o o o o [X Y] o}
+ 1.6 {o o o o o X Y} {o o o o o [X Y]}
+
+ 2.1 {X Y o o o o o o} {[X Y] o o o o o...}
+ 2.2 {o X Y o o o o o} {o [X Y] o o o o...}
+ 2.3 {o o X Y o o o o} {o o [X Y] o o o...}
+ 2.4 {o o o X Y o o o} {...o o [X Y] o o o}
+ 2.5 {o o o o X Y o o} {...o o o [X Y] o o}
+ 2.6 {o o o o o X Y o} {...o o o o [X Y] o}
+ 2.7 {o o o o o o X Y} {...o o o o o [X Y]}
+
+ 3.1 {X Y o o o o o o o} {[X Y] o o o o o...}
+ 3.2 {o X Y o o o o o o} {o [X Y] o o o o...}
+ 3.3 {o o X Y o o o o o} {o o [X Y] o o o...}
+ 3.4 {o o o X Y o o o o} {...o o [X Y] o o o...}
+ 3.5 {o o o o X Y o o o} {...o o [X Y] o o o}
+ 3.6 {o o o o o X Y o o} {...o o o [X Y] o o}
+ 3.7 {o o o o o o X Y o} {...o o o o [X Y] o}
+ 3.8 {o o o o o o o X Y} {...o o o o o [X Y]}
+
+} {
+ do_snippet_test 2.$tn $doc "X + Y" $res
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5ag.test b/ext/fts5/test/fts5ag.test
new file mode 100644
index 000000000..42a588f56
--- /dev/null
+++ b/ext/fts5/test/fts5ag.test
@@ -0,0 +1,138 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ag
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+#-------------------------------------------------------------------------
+# This file attempts to verify that the extension APIs work with
+# "ORDER BY rank" queries. This is done by comparing the results of
+# the fts5_test() function when run with queries of the form:
+#
+# ... WHERE fts MATCH ? ORDER BY bm25(fts) [ASC|DESC]
+#
+# and
+#
+# ... WHERE fts MATCH ? ORDER BY rank [ASC|DESC]
+#
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y, z);
+}
+
+do_test 1.1 {
+ foreach {x y z} {
+ {j s m y m r n l u k} {z k f u z g h s w g} {r n o s s b v n w w}
+ {m v g n d x q r r s} {q t d a q a v l h j} {s k l f s i n v q v}
+ {m f f d h h s o h a} {y e v r q i u m h d} {b c k q m z l z h n}
+ {j e m v k p e c j m} {m p v z d x l n i a} {v p u p m t p q i f}
+ {v r w l e e t d z p} {c s b w k m n k o u} {w g y f v w v w v p}
+ {k d g o u j p z n o} {t g e q l z i g b j} {f i q q j y h b g h}
+ {j s w x o t j b t m} {v a v v r t x c q a} {r t k x w u l h a g}
+ {j y b i u d e m d w} {y s o j h i n a u p} {n a g b u c w e b m}
+ {b c k s c w j p w b} {m o c o w o b d q q} {n t y o y z y r z e}
+ {p n q l e l h z q c} {n s e i h c v b b u} {m p d i t a o o f f}
+ {k c o n v e z l b m} {s m n i n s d e s u} {t a u e q d a o u c}
+ {h d t o i a g b b p} {k x c i g f g b b k} {x f i v n a n n j i}
+ {f z k r b u s k z e} {n z v z w l e r h t} {t i s v v a v p n s}
+ {k f e c t z r e f d} {f m g r c w q k b v} {v y s y f r b f e f}
+ {z r c t d q q h x b} {u c g z n z u v s s} {y t n f f x b f d x}
+ {u n p n u t i m e j} {p j j d m f k p m z} {d o l v c o e a h w}
+ {h o q w t f v i c y} {c q u n r z s l l q} {z x a q w s b w s y}
+ {y m s x k i m n x c} {b i a n v h z n k a} {w l q p b h h g d y}
+ {z v s j f p v l f w} {c s b i z e k i g c} {x b v d w j f e d z}
+ {r k k j e o m k g b} {h b d c h m y b t u} {u j s h k z c u d y}
+ {v h i v s y z i k l} {d t m w q w c a z p} {r s e s x v d w k b}
+ {u r e q j y h o o s} {x x z r x y t f j s} {k n h x i i u e c v}
+ {q l f d a p w l q o} {y z q w j o p b o v} {s u h z h f d f n l}
+ {q o e o x x l g q i} {j g m h q q w c d b} {o m d h w a g b f n}
+ {m x k t s s y l v a} {j x t c a u w b w g} {n f j b v x y p u t}
+ {u w k a q b u w k w} {a h j u o w f s k p} {j o f s h y t j h g}
+ {x v b l m t l m h l} {t p y i y i q b q a} {k o o z w a c h c f}
+ {j g c d k w b d t v} {a k v c m a v h v p} {i c a i j g h l j h}
+ {l m v l c z j b p b} {z p z f l n k i b a} {j v q k g i x g i b}
+ {m c i w u z m i s z} {i z r f n l q z k w} {x n b p b q r g i z}
+ {d g i o o x l f x d} {r t m f b n q y c b} {i u g k w x n m p o}
+ {t o s i q d z x d t} {v a k s q z j c o o} {z f n n r l y w v v}
+ {w k h d t l j g n n} {r z m v y b l n c u} {v b v s c l n k g v}
+ {m a g r a b u u n z} {u y l h v w v k b f} {x l p g i s j f x v}
+ {v s g x k z a k a r} {l t g v j q l k p l} {f h n a x t v s t y}
+ {z u v u x p s j y t} {g b q e e g l n w g} {e n p j i g j f u r}
+ {q z l t w o l m p e} {t s g h r p r o t z} {y b f a o n u m z g}
+ {d t w n y b o g f o} {d a j e r l g g s h} {d z e l w q l t h f}
+ {f l u w q v x j a h} {f n u l l d m h h w} {d x c c e r o d q j}
+ {b y f q s q f u l g} {u z w l f d b i a g} {m v q b g u o z e z}
+ {h z p t s e x i v m} {l h q m e o x x x j} {e e d n p r m g j f}
+ {k h s g o n s d a x} {u d t t s j o v h a} {z r b a e u v o e s}
+ {m b b g a f c p a t} {w c m j o d b l g e} {f p j p m o s y v j}
+ {c r n h d w c a b l} {s g e u s d n j b g} {b o n a x a b x y l}
+ {r h u x f c d z n o} {x y l g u m i i w d} {t f h b z v r s r g}
+ {t i o r b v g g p a} {d x l u q k m o s u} {j f h t u n z u k m}
+ {g j t y d c n j y g} {w e s k v c w i g t} {g a h r g v g h r o}
+ {e j l a q j g i n h} {d z k c u p n u p p} {t u e e v z v r r g}
+ {l j s g k j k h z l} {p v d a t x d e q u} {r l u z b m g k s j}
+ {i e y d u x d i n l} {p f z k m m w p u l} {z l p m r q w n d a}
+ } {
+ execsql { INSERT INTO t1 VALUES($x, $y, $z) }
+ }
+ set {} {}
+} {}
+
+fts5_aux_test_functions db
+
+proc do_fts5ag_test {tn E} {
+ set q1 {SELECT fts5_test_all(t1) FROM t1 WHERE t1 MATCH $E ORDER BY rank}
+ set q2 {SELECT fts5_test_all(t1) FROM t1 WHERE t1 MATCH $E ORDER BY bm25(t1)}
+
+ set res [execsql $q1]
+ set expected [execsql $q2]
+ uplevel [list do_test $tn.1 [list set {} $res] $expected]
+
+ append q1 " DESC"
+ append q2 " DESC"
+
+ set res [execsql $q1]
+ set expected [execsql $q2]
+ uplevel [list do_test $tn.2 [list set {} $res] $expected]
+}
+
+foreach {tn expr} {
+ 2.1 a
+ 2.2 b
+ 2.3 c
+ 2.4 d
+
+ 2.5 {"m m"}
+ 2.6 {e + s}
+
+ 3.0 {a AND b}
+ 3.1 {a OR b}
+ 3.2 {b OR c AND d}
+ 3.3 {NEAR(c d)}
+} {
+ do_fts5ag_test $tn $expr
+
+ if {[set_test_counter errors]} break
+}
+
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5ah.test b/ext/fts5/test/fts5ah.test
new file mode 100644
index 000000000..1ee4ab123
--- /dev/null
+++ b/ext/fts5/test/fts5ah.test
@@ -0,0 +1,108 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ah
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+#-------------------------------------------------------------------------
+# This file contains tests for very large doclists.
+#
+
+do_test 1.0 {
+ execsql { CREATE VIRTUAL TABLE t1 USING fts5(a) }
+ execsql { INSERT INTO t1(t1, rank) VALUES('pgsz', 128) }
+ set v {w w w w w w w w w w w w w w w w w w w w}
+ execsql { INSERT INTO t1(rowid, a) VALUES(0, $v) }
+ for {set i 1} {$i <= 10000} {incr i} {
+ set v {x x x x x x x x x x x x x x x x x x x x}
+ if {($i % 2139)==0} {lset v 3 Y ; lappend Y $i}
+ if {($i % 1577)==0} {lset v 5 W ; lappend W $i}
+ execsql { INSERT INTO t1 VALUES($v) }
+ }
+ set v {w w w w w w w w w w w w w w w w w w w w}
+ execsql { INSERT INTO t1 VALUES($v) }
+} {}
+
+do_execsql_test 1.1.1 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'x AND w'
+} [lsort -integer -incr $W]
+
+do_execsql_test 1.1.2 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'x* AND w*'
+} [lsort -integer -incr $W]
+
+do_execsql_test 1.2 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'y AND x'
+} [lsort -integer -incr $Y]
+
+do_execsql_test 1.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+proc reads {} {
+ db one {SELECT t1 FROM t1 WHERE t1 MATCH '*reads'}
+}
+
+proc execsql_reads {sql} {
+ set nRead [reads]
+ execsql $sql
+ expr [reads] - $nRead
+}
+
+do_test 1.4 {
+ set nRead [reads]
+ execsql { SELECT rowid FROM t1 WHERE t1 MATCH 'x' }
+ set nReadX [expr [reads] - $nRead]
+ expr $nReadX>1000
+} {1}
+
+do_test 1.5 {
+ set fwd [execsql_reads {SELECT rowid FROM t1 WHERE t1 MATCH 'x' }]
+ set bwd [execsql_reads {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'x' ORDER BY 1 ASC
+ }]
+ expr {$bwd < $fwd + 12}
+} {1}
+
+foreach {tn q res} "
+ 1 { SELECT rowid FROM t1 WHERE t1 MATCH 'w + x' } [list $W]
+ 2 { SELECT rowid FROM t1 WHERE t1 MATCH 'x + w' } [list $W]
+ 3 { SELECT rowid FROM t1 WHERE t1 MATCH 'x AND w' } [list $W]
+ 4 { SELECT rowid FROM t1 WHERE t1 MATCH 'y AND x' } [list $Y]
+" {
+
+ do_test 1.6.$tn.1 {
+ set n [execsql_reads $q]
+ expr {$n < ($nReadX / 10)}
+ } {1}
+
+ do_test 1.6.$tn.2 {
+ set n [execsql_reads "$q ORDER BY rowid DESC"]
+ expr {$n < ($nReadX / 10)}
+ } {1}
+
+ do_execsql_test 1.6.$tn.3 $q [lsort -int -incr $res]
+ do_execsql_test 1.6.$tn.4 "$q ORDER BY rowid DESC" [lsort -int -decr $res]
+}
+
+#db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t1_data} {puts $r}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5ai.test b/ext/fts5/test/fts5ai.test
new file mode 100644
index 000000000..63c46fd04
--- /dev/null
+++ b/ext/fts5/test/fts5ai.test
@@ -0,0 +1,55 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+# Specifically, it tests transactions and savepoints
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ai
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a);
+} {}
+
+do_execsql_test 1.1 {
+ BEGIN;
+ INSERT INTO t1 VALUES('a b c');
+ INSERT INTO t1 VALUES('d e f');
+ SAVEPOINT one;
+ INSERT INTO t1 VALUES('g h i');
+ SAVEPOINT two;
+ INSERT INTO t1 VALUES('j k l');
+ ROLLBACK TO one;
+ INSERT INTO t1 VALUES('m n o');
+ SAVEPOINT two;
+ INSERT INTO t1 VALUES('p q r');
+ RELEASE one;
+ SAVEPOINT one;
+ INSERT INTO t1 VALUES('s t u');
+ ROLLBACK TO one;
+ COMMIT;
+}
+
+do_execsql_test 1.2 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5aj.test b/ext/fts5/test/fts5aj.test
new file mode 100644
index 000000000..6b9dddd8b
--- /dev/null
+++ b/ext/fts5/test/fts5aj.test
@@ -0,0 +1,69 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+# Specifically, this tests that, provided the amount of data remains
+# constant, the FTS index does not grow indefinitely as rows are inserted
+# and deleted,
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5aj
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+proc doc {} {
+ set dict [list a b c d e f g h i j k l m n o p q r s t u v w x y z]
+ set res [list]
+ for {set i 0} {$i < 20} {incr i} {
+ lappend res [lindex $dict [expr int(rand() * 26)]]
+ }
+ set res
+}
+
+proc structure {} {
+ set val [db one {SELECT fts5_decode(rowid,block) FROM t1_data WHERE rowid=10}]
+ foreach lvl [lrange $val 1 end] {
+ lappend res [expr [llength $lvl]-2]
+ }
+ set res
+}
+
+expr srand(0)
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 64);
+}
+
+for {set iTest 0} {$iTest < 50000} {incr iTest} {
+ if {$iTest > 1000} { execsql { DELETE FROM t1 WHERE rowid=($iTest-1000) } }
+ set new [doc]
+ execsql { INSERT INTO t1 VALUES($new) }
+ if {$iTest==10000} { set sz1 [db one {SELECT count(*) FROM t1_data}] }
+ if {0==($iTest % 1000)} {
+ set sz [db one {SELECT count(*) FROM t1_data}]
+ set s [structure]
+ do_execsql_test 1.$iTest.$sz.{$s} {
+ INSERT INTO t1(t1) VALUES('integrity-check')
+ }
+ }
+}
+
+do_execsql_test 2.0 { INSERT INTO t1(t1) VALUES('integrity-check') }
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5ak.test b/ext/fts5/test/fts5ak.test
new file mode 100644
index 000000000..4eb28324c
--- /dev/null
+++ b/ext/fts5/test/fts5ak.test
@@ -0,0 +1,143 @@
+# 2014 November 24
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+# Specifically, the auxiliary function "highlight".
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ak
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.1 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(x);
+ INSERT INTO ft1 VALUES('i d d a g i b g d d');
+ INSERT INTO ft1 VALUES('h d b j c c g a c a');
+ INSERT INTO ft1 VALUES('e j a e f h b f h h');
+ INSERT INTO ft1 VALUES('j f h d g h i b d f');
+ INSERT INTO ft1 VALUES('d c j d c j b c g e');
+ INSERT INTO ft1 VALUES('i a d e g j g d a a');
+ INSERT INTO ft1 VALUES('j f c e d a h j d b');
+ INSERT INTO ft1 VALUES('i c c f a d g h j e');
+ INSERT INTO ft1 VALUES('i d i g c d c h b f');
+ INSERT INTO ft1 VALUES('g d a e h a b c f j');
+}
+
+do_execsql_test 1.2 {
+ SELECT highlight(ft1, 0, '[', ']') FROM ft1 WHERE ft1 MATCH 'e';
+} {
+ {[e] j a [e] f h b f h h}
+ {d c j d c j b c g [e]}
+ {i a d [e] g j g d a a}
+ {j f c [e] d a h j d b}
+ {i c c f a d g h j [e]}
+ {g d a [e] h a b c f j}
+}
+
+do_execsql_test 1.3 {
+ SELECT highlight(ft1, 0, '[', ']') FROM ft1 WHERE ft1 MATCH 'h + d';
+} {
+ {[h d] b j c c g a c a}
+ {j f [h d] g h i b d f}
+}
+
+do_execsql_test 1.4 {
+ SELECT highlight(ft1, 0, '[', ']') FROM ft1 WHERE ft1 MATCH 'd + d';
+} {
+ {i [d d] a g i b g [d d]}
+}
+
+do_execsql_test 1.5 {
+ SELECT highlight(ft1, 0, '[', ']') FROM ft1 WHERE ft1 MATCH 'e e e'
+} {
+ {[e] j a [e] f h b f h h}
+ {d c j d c j b c g [e]}
+ {i a d [e] g j g d a a}
+ {j f c [e] d a h j d b}
+ {i c c f a d g h j [e]}
+ {g d a [e] h a b c f j}
+}
+
+do_execsql_test 1.6 {
+ SELECT highlight(ft1, 0, '[', ']') FROM ft1 WHERE ft1 MATCH 'd + d d + d';
+} {
+ {i [d d] a g i b g [d d]}
+}
+
+do_execsql_test 2.1 {
+ CREATE VIRTUAL TABLE ft2 USING fts5(x);
+ INSERT INTO ft2 VALUES('a b c d e f g h i j');
+}
+
+do_execsql_test 2.2 {
+ SELECT highlight(ft2, 0, '[', ']') FROM ft2 WHERE ft2 MATCH 'b+c+d c+d+e'
+} {{a [b c d e] f g h i j}}
+
+do_execsql_test 2.3 {
+ SELECT highlight(ft2, 0, '[', ']') FROM ft2 WHERE ft2 MATCH 'b+c+d e+f+g'
+} {
+ {a [b c d] [e f g] h i j}
+}
+
+do_execsql_test 2.4 {
+ SELECT highlight(ft2, 0, '[', ']') FROM ft2 WHERE ft2 MATCH 'b+c+d c'
+} {
+ {a [b c d] e f g h i j}
+}
+
+do_execsql_test 2.5 {
+ SELECT highlight(ft2, 0, '[', ']') FROM ft2 WHERE ft2 MATCH 'b+c c+d+e'
+} {
+ {a [b c d e] f g h i j}
+}
+
+do_execsql_test 2.6.1 {
+ SELECT highlight(ft2, 0, '[', ']') FROM ft2 WHERE ft2 MATCH 'f d'
+} {
+ {a b c [d] e [f] g h i j}
+}
+
+do_execsql_test 2.6.2 {
+ SELECT highlight(ft2, 0, '[', ']') FROM ft2 WHERE ft2 MATCH 'd f'
+} {
+ {a b c [d] e [f] g h i j}
+}
+
+#-------------------------------------------------------------------------
+# The example from the docs.
+#
+do_execsql_test 3.1 {
+ -- Assuming this:
+ CREATE VIRTUAL TABLE ft USING fts5(a);
+ INSERT INTO ft VALUES('a b c x c d e');
+ INSERT INTO ft VALUES('a b c c d e');
+ INSERT INTO ft VALUES('a b c d e');
+
+ -- The following SELECT statement returns these three rows:
+ -- '[a b c] x [c d e]'
+ -- '[a b c] [c d e]'
+ -- '[a b c d e]'
+ SELECT highlight(ft, 0, '[', ']') FROM ft WHERE ft MATCH 'a+b+c AND c+d+e';
+} {
+ {[a b c] x [c d e]}
+ {[a b c] [c d e]}
+ {[a b c d e]}
+}
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5al.test b/ext/fts5/test/fts5al.test
new file mode 100644
index 000000000..36402d6f6
--- /dev/null
+++ b/ext/fts5/test/fts5al.test
@@ -0,0 +1,273 @@
+# 2014 November 24
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+# Specifically, this function tests the %_config table.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5al
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+do_execsql_test 1.1 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(x);
+ SELECT * FROM ft1_config;
+} {}
+
+do_execsql_test 1.2 {
+ INSERT INTO ft1(ft1, rank) VALUES('pgsz', 32);
+ SELECT * FROM ft1_config;
+} {pgsz 32}
+
+do_execsql_test 1.3 {
+ INSERT INTO ft1(ft1, rank) VALUES('pgsz', 64);
+ SELECT * FROM ft1_config;
+} {pgsz 64}
+
+#--------------------------------------------------------------------------
+# Test the logic for parsing the rank() function definition.
+#
+foreach {tn defn} {
+ 1 "fname()"
+ 2 "fname(1)"
+ 3 "fname(1,2)"
+ 4 "fname(null,NULL,nUlL)"
+ 5 " fname ( null , NULL , nUlL ) "
+ 6 "fname('abc')"
+ 7 "fname('a''bc')"
+ 8 "fname('''abc')"
+ 9 "fname('abc''')"
+
+ 7 "fname( 'a''bc' )"
+ 8 "fname('''abc' )"
+ 9 "fname( 'abc''' )"
+
+ 10 "fname(X'1234ab')"
+
+ 11 "myfunc(1.2)"
+ 12 "myfunc(-1.0)"
+ 13 "myfunc(.01,'abc')"
+} {
+ do_execsql_test 2.1.$tn {
+ INSERT INTO ft1(ft1, rank) VALUES('rank', $defn);
+ }
+}
+
+foreach {tn defn} {
+ 1 ""
+ 2 "fname"
+ 3 "fname(X'234ab')"
+ 4 "myfunc(-1.,'abc')"
+} {
+ do_test 2.2.$tn {
+ catchsql { INSERT INTO ft1(ft1, rank) VALUES('rank', $defn) }
+ } {1 {SQL logic error or missing database}}
+}
+
+#-------------------------------------------------------------------------
+# Assorted tests of the tcl interface for creating extension functions.
+#
+
+do_execsql_test 3.1 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1 VALUES('q w e r t y');
+ INSERT INTO t1 VALUES('y t r e w q');
+}
+
+proc argtest {cmd args} { return $args }
+sqlite3_fts5_create_function db argtest argtest
+
+do_execsql_test 3.2.1 {
+ SELECT argtest(t1, 123) FROM t1 WHERE t1 MATCH 'q'
+} {123 123}
+
+do_execsql_test 3.2.2 {
+ SELECT argtest(t1, 123, 456) FROM t1 WHERE t1 MATCH 'q'
+} {{123 456} {123 456}}
+
+proc rowidtest {cmd} { $cmd xRowid }
+sqlite3_fts5_create_function db rowidtest rowidtest
+
+do_execsql_test 3.3.1 {
+ SELECT rowidtest(t1) FROM t1 WHERE t1 MATCH 'q'
+} {1 2}
+
+proc insttest {cmd} {
+ set res [list]
+ for {set i 0} {$i < [$cmd xInstCount]} {incr i} {
+ lappend res [$cmd xInst $i]
+ }
+ set res
+}
+sqlite3_fts5_create_function db insttest insttest
+
+do_execsql_test 3.4.1 {
+ SELECT insttest(t1) FROM t1 WHERE t1 MATCH 'q'
+} {
+ {{0 0 0}}
+ {{0 0 5}}
+}
+
+do_execsql_test 3.4.2 {
+ SELECT insttest(t1) FROM t1 WHERE t1 MATCH 'r+e OR w'
+} {
+ {{1 0 1}}
+ {{0 0 2} {1 0 4}}
+}
+
+proc coltest {cmd} {
+ list [$cmd xColumnSize 0] [$cmd xColumnText 0]
+}
+sqlite3_fts5_create_function db coltest coltest
+
+do_execsql_test 3.5.1 {
+ SELECT coltest(t1) FROM t1 WHERE t1 MATCH 'q'
+} {
+ {6 {q w e r t y}}
+ {6 {y t r e w q}}
+}
+
+#-------------------------------------------------------------------------
+# Tests for remapping the "rank" column.
+#
+# 4.1.*: Mapped to a function with no arguments.
+# 4.2.*: Mapped to a function with one or more arguments.
+#
+
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(a, b);
+ INSERT INTO t2 VALUES('a s h g s b j m r h', 's b p a d b b a o e');
+ INSERT INTO t2 VALUES('r h n t a g r d d i', 'l d n j r c f t o q');
+ INSERT INTO t2 VALUES('q k n i k c a a e m', 'c h n j p g s c i t');
+ INSERT INTO t2 VALUES('h j g t r e l s g s', 'k q k c i i c k n s');
+ INSERT INTO t2 VALUES('b l k h d n n n m i', 'p t i a r b t q o l');
+ INSERT INTO t2 VALUES('k r i l j b g i p a', 't q c h a i m g n l');
+ INSERT INTO t2 VALUES('a e c q n m o m d g', 'l c t g i s q g q e');
+ INSERT INTO t2 VALUES('b o j h f o g b p e', 'r t l h s b g i c p');
+ INSERT INTO t2 VALUES('s q k f q b j g h f', 'n m a o p e i e k t');
+ INSERT INTO t2 VALUES('o q g g q c o k a b', 'r t k p t f t h p c');
+}
+
+proc firstinst {cmd} {
+ foreach {p c o} [$cmd xInst 0] {}
+ expr $c*100 + $o
+}
+sqlite3_fts5_create_function db firstinst firstinst
+
+do_execsql_test 4.1.1 {
+ SELECT rowid, firstinst(t2) FROM t2 WHERE t2 MATCH 'a' ORDER BY rowid ASC
+} {
+ 1 0 2 4 3 6 5 103
+ 6 9 7 0 9 102 10 8
+}
+
+do_execsql_test 4.1.2 {
+ SELECT rowid, rank FROM t2
+ WHERE t2 MATCH 'a' AND rank MATCH 'firstinst()'
+ ORDER BY rowid ASC
+} {
+ 1 0 2 4 3 6 5 103
+ 6 9 7 0 9 102 10 8
+}
+
+do_execsql_test 4.1.3 {
+ SELECT rowid, rank FROM t2
+ WHERE t2 MATCH 'a' AND rank MATCH 'firstinst()'
+ ORDER BY rank DESC
+} {
+ 5 103 9 102 6 9 10 8 3 6 2 4 1 0 7 0
+}
+
+do_execsql_test 4.1.4 {
+ INSERT INTO t2(t2, rank) VALUES('rank', 'firstinst()');
+ SELECT rowid, rank FROM t2 WHERE t2 MATCH 'a' ORDER BY rowid ASC
+} {
+ 1 0 2 4 3 6 5 103
+ 6 9 7 0 9 102 10 8
+}
+
+do_execsql_test 4.1.5 {
+ SELECT rowid, rank FROM t2 WHERE t2 MATCH 'a' ORDER BY rank DESC
+} {
+ 5 103 9 102 6 9 10 8 3 6 2 4 1 0 7 0
+}
+
+do_execsql_test 4.1.6 {
+ INSERT INTO t2(t2, rank) VALUES('rank', 'firstinst ( ) ');
+ SELECT rowid, rank FROM t2 WHERE t2 MATCH 'a' ORDER BY rank DESC
+} {
+ 5 103 9 102 6 9 10 8 3 6 2 4 1 0 7 0
+}
+
+proc rowidplus {cmd ival} {
+ expr [$cmd xRowid] + $ival
+}
+sqlite3_fts5_create_function db rowidplus rowidplus
+
+do_execsql_test 4.2.1 {
+ INSERT INTO t2(t2, rank) VALUES('rank', 'rowidplus(100) ');
+ SELECT rowid, rank FROM t2 WHERE t2 MATCH 'o + q + g'
+} {
+ 10 110
+}
+do_execsql_test 4.2.2 {
+ INSERT INTO t2(t2, rank) VALUES('rank', 'rowidplus(111) ');
+ SELECT rowid, rank FROM t2 WHERE t2 MATCH 'o + q + g'
+} {
+ 10 121
+}
+
+do_execsql_test 4.2.3 {
+ SELECT rowid, rank FROM t2
+ WHERE t2 MATCH 'o + q + g' AND rank MATCH 'rowidplus(112)'
+} {
+ 10 122
+}
+
+proc rowidmod {cmd imod} {
+ expr [$cmd xRowid] % $imod
+}
+sqlite3_fts5_create_function db rowidmod rowidmod
+do_execsql_test 4.3.1 {
+ CREATE VIRTUAL TABLE t3 USING fts5(x);
+ INSERT INTO t3 VALUES('a one');
+ INSERT INTO t3 VALUES('a two');
+ INSERT INTO t3 VALUES('a three');
+ INSERT INTO t3 VALUES('a four');
+ INSERT INTO t3 VALUES('a five');
+ INSERT INTO t3(t3, rank) VALUES('rank', 'bm25()');
+}
+breakpoint
+
+do_execsql_test 4.3.2 {
+ SELECT * FROM t3
+ WHERE t3 MATCH 'a' AND rank MATCH 'rowidmod(4)'
+ ORDER BY rank ASC
+} {
+ {a four} {a one} {a five} {a two} {a three}
+}
+do_execsql_test 4.3.3 {
+ SELECT *, rank FROM t3
+ WHERE t3 MATCH 'a' AND rank MATCH 'rowidmod(3)'
+ ORDER BY rank ASC
+} {
+ {a three} 0 {a one} 1 {a four} 1 {a two} 2 {a five} 2
+}
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5auxdata.test b/ext/fts5/test/fts5auxdata.test
new file mode 100644
index 000000000..ee408a064
--- /dev/null
+++ b/ext/fts5/test/fts5auxdata.test
@@ -0,0 +1,109 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Tests focusing on the fts5 xSetAuxdata() and xGetAuxdata() APIs.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5auxdata
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE f1 USING fts5(a, b);
+ INSERT INTO f1(rowid, a, b) VALUES(1, 'a', 'b1');
+ INSERT INTO f1(rowid, a, b) VALUES(2, 'a', 'b2');
+ INSERT INTO f1(rowid, a, b) VALUES(3, 'a', 'b3');
+ INSERT INTO f1(rowid, a, b) VALUES(4, 'a', 'b4');
+ INSERT INTO f1(rowid, a, b) VALUES(5, 'a', 'b5');
+}
+
+proc aux_function_1 {cmd tn} {
+ switch [$cmd xRowid] {
+ 1 {
+ do_test $tn.1 [list $cmd xGetAuxdata 0 ] {}
+ $cmd xSetAuxdata "one"
+ }
+
+ 2 {
+ do_test $tn.2 [list $cmd xGetAuxdata 0 ] {one}
+ $cmd xSetAuxdata "two"
+ }
+
+ 3 {
+ do_test $tn.3 [list $cmd xGetAuxdata 0 ] {two}
+ }
+
+ 4 {
+ do_test $tn.4 [list $cmd xGetAuxdata 1 ] {two}
+ }
+
+ 5 {
+ do_test $tn.5 [list $cmd xGetAuxdata 0 ] {}
+ }
+ }
+}
+
+sqlite3_fts5_create_function db aux_function_1 aux_function_1
+db eval {
+ SELECT aux_function_1(f1, 1) FROM f1 WHERE f1 MATCH 'a'
+ ORDER BY rowid ASC
+}
+
+proc aux_function_2 {cmd tn inst} {
+ if {$inst == "A"} {
+ switch [$cmd xRowid] {
+ 1 {
+ do_test $tn.1.$inst [list $cmd xGetAuxdata 0 ] {}
+ $cmd xSetAuxdata "one $inst"
+ }
+ 2 {
+ do_test $tn.2.$inst [list $cmd xGetAuxdata 0 ] "one $inst"
+ $cmd xSetAuxdata "two $inst"
+ }
+ 3 {
+ do_test $tn.3.$inst [list $cmd xGetAuxdata 0 ] "two $inst"
+ }
+ 4 {
+ do_test $tn.4.$inst [list $cmd xGetAuxdata 1 ] "two $inst"
+ }
+ 5 {
+ do_test $tn.5.$inst [list $cmd xGetAuxdata 0 ] {}
+ }
+ }
+ } else {
+ switch [$cmd xRowid] {
+ 1 {
+ do_test $tn.1.$inst [list $cmd xGetAuxdata 0 ] "one A"
+ }
+ 2 {
+ do_test $tn.2.$inst [list $cmd xGetAuxdata 0 ] "two A"
+ }
+ 3 {
+ do_test $tn.3.$inst [list $cmd xGetAuxdata 0 ] "two A"
+ }
+ 4 {
+ do_test $tn.4.$inst [list $cmd xGetAuxdata 0 ] {}
+ }
+ 5 {
+ do_test $tn.5.$inst [list $cmd xGetAuxdata 0 ] {}
+ }
+ }
+ }
+}
+
+sqlite3_fts5_create_function db aux_function_2 aux_function_2
+db eval {
+ SELECT aux_function_2(f1, 2, 'A'), aux_function_2(f1, 2, 'B')
+ FROM f1 WHERE f1 MATCH 'a'
+ ORDER BY rowid ASC
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5bigpl.test b/ext/fts5/test/fts5bigpl.test
new file mode 100644
index 000000000..172c0396b
--- /dev/null
+++ b/ext/fts5/test/fts5bigpl.test
@@ -0,0 +1,58 @@
+# 2015 April 21
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This test is focused on really large position lists. Those that require
+# 4 or 5 byte position-list size varints. Because of the amount of memory
+# required, these tests only run on 64-bit platforms.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5bigpl
+
+if { $tcl_platform(wordSize)<8 } {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 { CREATE VIRTUAL TABLE t1 USING fts5(x) }
+
+do_test 1.1 {
+ foreach t {a b c d e f g h i j} {
+ set doc [string repeat "$t " 1200000]
+ execsql { INSERT INTO t1 VALUES($doc) }
+ }
+ execsql { INSERT INTO t1(t1) VALUES('integrity-check') }
+} {}
+
+do_test 1.2 {
+ execsql { DELETE FROM t1 }
+ foreach t {"a b" "b a" "c d" "d c"} {
+ set doc [string repeat "$t " 600000]
+ execsql { INSERT INTO t1 VALUES($doc) }
+ }
+ execsql { INSERT INTO t1(t1) VALUES('integrity-check') }
+} {}
+
+
+# 5-byte varint. This test takes 30 seconds or so on a 2014 workstation.
+# The generated database is roughly 635MiB.
+#
+do_test 2.1...slow {
+ execsql { DELETE FROM t1 }
+ foreach t {a} {
+ set doc [string repeat "$t " 150000000]
+ execsql { INSERT INTO t1 VALUES($doc) }
+ }
+ execsql { INSERT INTO t1(t1) VALUES('integrity-check') }
+} {}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5content.test b/ext/fts5/test/fts5content.test
new file mode 100644
index 000000000..145fa4b6a
--- /dev/null
+++ b/ext/fts5/test/fts5content.test
@@ -0,0 +1,190 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5content
+
+#-------------------------------------------------------------------------
+# Contentless tables
+#
+do_execsql_test 1.1 {
+ CREATE VIRTUAL TABLE f1 USING fts5(a, b, content='');
+ INSERT INTO f1(rowid, a, b) VALUES(1, 'one', 'o n e');
+ INSERT INTO f1(rowid, a, b) VALUES(2, 'two', 't w o');
+ INSERT INTO f1(rowid, a, b) VALUES(3, 'three', 't h r e e');
+}
+
+do_execsql_test 1.2 {
+ SELECT rowid FROM f1 WHERE f1 MATCH 'o';
+} {1 2}
+
+do_execsql_test 1.3 {
+ INSERT INTO f1(a, b) VALUES('four', 'f o u r');
+ SELECT rowid FROM f1 WHERE f1 MATCH 'o';
+} {1 2 4}
+
+do_execsql_test 1.4 {
+ SELECT rowid, a, b FROM f1 WHERE f1 MATCH 'o';
+} {1 {} {} 2 {} {} 4 {} {}}
+
+do_execsql_test 1.5 {
+ SELECT rowid, highlight(f1, 0, '[', ']') FROM f1 WHERE f1 MATCH 'o';
+} {1 {} 2 {} 4 {}}
+
+do_execsql_test 1.6 {
+ SELECT rowid, highlight(f1, 0, '[', ']') IS NULL FROM f1 WHERE f1 MATCH 'o';
+} {1 1 2 1 4 1}
+
+do_execsql_test 1.7 {
+ SELECT rowid, snippet(f1, -1, '[', ']', '...', 5) IS NULL
+ FROM f1 WHERE f1 MATCH 'o';
+} {1 1 2 1 4 1}
+
+do_execsql_test 1.8 {
+ SELECT rowid, snippet(f1, 1, '[', ']', '...', 5) IS NULL
+ FROM f1 WHERE f1 MATCH 'o';
+} {1 1 2 1 4 1}
+
+do_execsql_test 1.9 {
+ SELECT rowid FROM f1;
+} {1 2 3 4}
+
+do_execsql_test 1.10 {
+ SELECT * FROM f1;
+} {{} {} {} {} {} {} {} {}}
+
+do_execsql_test 1.11 {
+ SELECT rowid, a, b FROM f1 ORDER BY rowid ASC;
+} {1 {} {} 2 {} {} 3 {} {} 4 {} {}}
+
+do_execsql_test 1.12 {
+ SELECT a IS NULL FROM f1;
+} {1 1 1 1}
+
+do_catchsql_test 1.13 {
+ DELETE FROM f1 WHERE rowid = 2;
+} {1 {cannot DELETE from contentless fts5 table: f1}}
+
+do_catchsql_test 1.14 {
+ UPDATE f1 SET a = 'a b c' WHERE rowid = 2;
+} {1 {cannot UPDATE contentless fts5 table: f1}}
+
+do_execsql_test 1.15 {
+ INSERT INTO f1(f1, rowid, a, b) VALUES('delete', 2, 'two', 't w o');
+} {}
+
+do_execsql_test 1.16 {
+ SELECT rowid FROM f1 WHERE f1 MATCH 'o';
+} {1 4}
+
+do_execsql_test 1.17 {
+ SELECT rowid FROM f1;
+} {1 3 4}
+
+#-------------------------------------------------------------------------
+# External content tables
+#
+reset_db
+do_execsql_test 2.1 {
+ -- Create a table. And an external content fts5 table to index it.
+ CREATE TABLE tbl(a INTEGER PRIMARY KEY, b, c);
+ CREATE VIRTUAL TABLE fts_idx USING fts5(b, c, content='tbl', content_rowid='a');
+
+ -- Triggers to keep the FTS index up to date.
+ CREATE TRIGGER tbl_ai AFTER INSERT ON tbl BEGIN
+ INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c);
+ END;
+ CREATE TRIGGER tbl_ad AFTER DELETE ON tbl BEGIN
+ INSERT INTO fts_idx(fts_idx, rowid, b, c)
+ VALUES('delete', old.a, old.b, old.c);
+ END;
+ CREATE TRIGGER tbl_au AFTER UPDATE ON tbl BEGIN
+ INSERT INTO fts_idx(fts_idx, rowid, b, c)
+ VALUES('delete', old.a, old.b, old.c);
+ INSERT INTO fts_idx(rowid, b, c) VALUES (new.a, new.b, new.c);
+ END;
+}
+
+do_execsql_test 2.2 {
+ INSERT INTO tbl VALUES(1, 'one', 'o n e');
+ INSERT INTO tbl VALUES(NULL, 'two', 't w o');
+ INSERT INTO tbl VALUES(3, 'three', 't h r e e');
+}
+
+do_execsql_test 2.3 {
+ INSERT INTO fts_idx(fts_idx) VALUES('integrity-check');
+}
+
+do_execsql_test 2.4 {
+ DELETE FROM tbl WHERE rowid=2;
+ INSERT INTO fts_idx(fts_idx) VALUES('integrity-check');
+}
+
+do_execsql_test 2.5 {
+ UPDATE tbl SET c = c || ' x y z';
+ INSERT INTO fts_idx(fts_idx) VALUES('integrity-check');
+}
+
+do_execsql_test 2.6 {
+ SELECT * FROM fts_idx WHERE fts_idx MATCH 't AND x';
+} {three {t h r e e x y z}}
+
+do_execsql_test 2.7 {
+ SELECT highlight(fts_idx, 1, '[', ']') FROM fts_idx
+ WHERE fts_idx MATCH 't AND x';
+} {{[t] h r e e [x] y z}}
+
+#-------------------------------------------------------------------------
+# Quick tests of the 'delete-all' command.
+#
+do_execsql_test 3.1 {
+ CREATE VIRTUAL TABLE t3 USING fts5(x, content='');
+ INSERT INTO t3 VALUES('a b c');
+ INSERT INTO t3 VALUES('d e f');
+}
+
+do_execsql_test 3.2 {
+ SELECT count(*) FROM t3_docsize;
+ SELECT count(*) FROM t3_data;
+} {2 4}
+
+do_execsql_test 3.3 {
+ INSERT INTO t3(t3) VALUES('delete-all');
+ SELECT count(*) FROM t3_docsize;
+ SELECT count(*) FROM t3_data;
+} {0 2}
+
+do_execsql_test 3.4 {
+ INSERT INTO t3 VALUES('a b c');
+ INSERT INTO t3 VALUES('d e f');
+ SELECT rowid FROM t3 WHERE t3 MATCH 'e';
+} {2}
+
+do_execsql_test 3.5 {
+ SELECT rowid FROM t3 WHERE t3 MATCH 'c';
+} {1}
+
+do_execsql_test 3.6 {
+ SELECT count(*) FROM t3_docsize;
+ SELECT count(*) FROM t3_data;
+} {2 4}
+
+do_execsql_test 3.7 {
+ CREATE VIRTUAL TABLE t4 USING fts5(x);
+} {}
+do_catchsql_test 3.8 {
+ INSERT INTO t4(t4) VALUES('delete-all');
+} {1 {'delete-all' may only be used with a contentless or external content fts5 table}}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5corrupt.test b/ext/fts5/test/fts5corrupt.test
new file mode 100644
index 000000000..a9393de43
--- /dev/null
+++ b/ext/fts5/test/fts5corrupt.test
@@ -0,0 +1,74 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5corrupt
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+do_test 1.1 {
+ db transaction {
+ for {set i 1} {$i < 200} {incr i} {
+ set doc [list [string repeat x $i] [string repeat y $i]]
+ execsql { INSERT INTO t1(rowid, x) VALUES($i, $doc) }
+ }
+ }
+ fts5_level_segs t1
+} {1}
+db_save
+
+do_execsql_test 1.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+set segid [lindex [fts5_level_segids t1] 0]
+
+do_test 1.3 {
+ execsql {
+ DELETE FROM t1_data WHERE rowid = fts5_rowid('segment', 0, $segid, 0, 4);
+ }
+ catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
+} {1 {SQL logic error or missing database}}
+
+do_test 1.4 {
+ db_restore_and_reopen
+ execsql {
+ UPDATE t1_data set block = X'00000000' || substr(block, 5) WHERE
+ rowid = fts5_rowid('segment', 0, $segid, 0, 4);
+ }
+ catchsql { INSERT INTO t1(t1) VALUES('integrity-check') }
+} {1 {database disk image is malformed}}
+
+db_restore_and_reopen
+#db eval {SELECT rowid, fts5_decode(rowid, block) aS r FROM t1_data} {puts $r}
+
+
+#--------------------------------------------------------------------
+#
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(x);
+ INSERT INTO t2(t2, rank) VALUES('pgsz', 64);
+}
+db func rnddoc fts5_rnddoc
+do_test 2.1 {
+ for {set i 0} {$i < 500} {incr i} {
+ execsql { INSERT INTO t2 VALUES(rnddoc(50)) }
+ }
+ execsql { INSERT INTO t2(t2) VALUES('integrity-check') }
+} {}
+
+#--------------------------------------------------------------------
+#
+
+finish_test
+
diff --git a/ext/fts5/test/fts5dlidx.test b/ext/fts5/test/fts5dlidx.test
new file mode 100644
index 000000000..0bfc3f331
--- /dev/null
+++ b/ext/fts5/test/fts5dlidx.test
@@ -0,0 +1,80 @@
+# 2015 April 21
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This test is focused on uses of doclist-index records.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5dlidx
+
+if { $tcl_platform(wordSize)<8 } {
+ finish_test
+ return
+}
+
+proc do_fb_test {tn sql res} {
+ set res2 [lsort -integer -decr $res]
+ uplevel [list do_execsql_test $tn.1 $sql $res]
+ uplevel [list do_execsql_test $tn.2 "$sql ORDER BY rowid DESC" $res2]
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ INSERT INTO t1(t1, rank) VALUES('pgsz', 32);
+}
+
+foreach {tn spc1 spc2 mul} {
+ 1 10 100 1000
+ 2 1 1 128
+} {
+ set xdoc [list]
+ set ydoc [list]
+
+ execsql { DELETE FROM t1 }
+
+ do_test 1.$tn.1 {
+
+ execsql BEGIN
+ for {set i 0} {$i < 10000} {incr i} {
+ set rowid [expr $i * $mul]
+ set doc "a b c a b c a b c a b c a b c"
+ if {($i % $spc1)==0} {
+ lappend xdoc $rowid
+ append doc " x"
+ if {($i % $spc2)==0} {
+ lappend ydoc $rowid
+ append doc " y"
+ }
+ }
+ execsql { INSERT INTO t1(rowid, x) VALUES($rowid, $doc) }
+ }
+ execsql COMMIT
+ execsql { INSERT INTO t1(t1) VALUES('integrity-check') }
+ } {}
+
+ do_execsql_test 1.$tn.2 { INSERT INTO t1(t1) VALUES('integrity-check') }
+
+ do_fb_test 1.$tn.3.1 { SELECT rowid FROM t1 WHERE t1 MATCH 'a AND x' } $xdoc
+ do_fb_test 1.$tn.3.2 { SELECT rowid FROM t1 WHERE t1 MATCH 'x AND a' } $xdoc
+
+ do_fb_test 1.$tn.4.1 { SELECT rowid FROM t1 WHERE t1 MATCH 'a AND y' } $ydoc
+ do_fb_test 1.$tn.4.2 { SELECT rowid FROM t1 WHERE t1 MATCH 'y AND a' } $ydoc
+
+ do_fb_test 1.$tn.5.1 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'a + b + c + x' } $xdoc
+ do_fb_test 1.$tn.5.2 {
+ SELECT rowid FROM t1 WHERE t1 MATCH 'b + c + x + y' } $ydoc
+
+}
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5ea.test b/ext/fts5/test/fts5ea.test
new file mode 100644
index 000000000..f91300653
--- /dev/null
+++ b/ext/fts5/test/fts5ea.test
@@ -0,0 +1,83 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5ea
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+proc do_syntax_error_test {tn expr err} {
+ set ::se_expr $expr
+ do_catchsql_test $tn {SELECT fts5_expr($se_expr)} [list 1 $err]
+}
+
+proc do_syntax_test {tn expr res} {
+ set ::se_expr $expr
+ do_execsql_test $tn {SELECT fts5_expr($se_expr)} [list $res]
+}
+
+foreach {tn expr res} {
+ 1 {abc} {"abc"}
+ 2 {abc def} {"abc" AND "def"}
+ 3 {abc*} {"abc" *}
+ 4 {"abc def ghi" *} {"abc" + "def" + "ghi" *}
+ 5 {one AND two} {"one" AND "two"}
+ 6 {one+two} {"one" + "two"}
+ 7 {one AND two OR three} {("one" AND "two") OR "three"}
+ 8 {one OR two AND three} {"one" OR ("two" AND "three")}
+ 9 {NEAR(one two)} {NEAR("one" "two", 10)}
+ 10 {NEAR("one three"* two, 5)} {NEAR("one" + "three" * "two", 5)}
+} {
+ do_execsql_test 1.$tn {SELECT fts5_expr($expr)} [list $res]
+}
+
+foreach {tn expr res} {
+ 1 {c1:abc}
+ {c1 : "abc"}
+ 2 {c2 : NEAR(one two) c1:"hello world"}
+ {c2 : NEAR("one" "two", 10) AND c1 : "hello" + "world"}
+} {
+ do_execsql_test 2.$tn {SELECT fts5_expr($expr, 'c1', 'c2')} [list $res]
+}
+
+breakpoint
+foreach {tn expr err} {
+ 1 {AND} {fts5: syntax error near "AND"}
+ 2 {abc def AND} {fts5: syntax error near ""}
+ 3 {abc OR AND} {fts5: syntax error near "AND"}
+ 4 {(a OR b) abc} {fts5: syntax error near "abc"}
+ 5 {NEaR (a b)} {fts5: syntax error near "NEaR"}
+ 6 {(a OR b) NOT c)} {fts5: syntax error near ")"}
+ 7 {nosuch: a nosuch2: b} {no such column: nosuch}
+ 8 {addr: a nosuch2: b} {no such column: nosuch2}
+} {
+ do_catchsql_test 3.$tn {SELECT fts5_expr($expr, 'name', 'addr')} [list 1 $err]
+}
+
+
+
+# do_syntax_error_test 1.0 {NOT} {syntax error near "NOT"}
+
+
+
+# do_catchsql_test 1.1 {
+ # SELECT fts5_expr('a OR b NOT c')
+#} {0 {"a" OR "b" NOT "c"}}
+
+
+#do_execsql_test 1.0 { SELECT fts5_expr('a') } {{"a"}}
+
+finish_test
diff --git a/ext/fts5/test/fts5eb.test b/ext/fts5/test/fts5eb.test
new file mode 100644
index 000000000..987cb5ef1
--- /dev/null
+++ b/ext/fts5/test/fts5eb.test
@@ -0,0 +1,53 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5eb
+
+# If SQLITE_ENABLE_FTS5 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+proc do_syntax_error_test {tn expr err} {
+ set ::se_expr $expr
+ do_catchsql_test $tn {SELECT fts5_expr($se_expr)} [list 1 $err]
+}
+
+proc do_syntax_test {tn expr res} {
+ set ::se_expr $expr
+ do_execsql_test $tn {SELECT fts5_expr($se_expr)} [list $res]
+}
+
+foreach {tn expr res} {
+ 1 {abc} {"abc"}
+ 2 {abc .} {"abc"}
+ 3 {.} {}
+ 4 {abc OR .} {"abc"}
+ 5 {abc NOT .} {"abc"}
+ 6 {abc AND .} {"abc"}
+ 7 {. OR abc} {"abc"}
+ 8 {. NOT abc} {"abc"}
+ 9 {. AND abc} {"abc"}
+ 10 {abc + . + def} {"abc" + "def"}
+ 11 {abc . def} {"abc" AND "def"}
+ 12 {r+e OR w} {"r" + "e" OR "w"}
+} {
+ do_execsql_test 1.$tn {SELECT fts5_expr($expr)} [list $res]
+}
+
+
+finish_test
+
+
+
diff --git a/ext/fts5/test/fts5fault1.test b/ext/fts5/test/fts5fault1.test
new file mode 100644
index 000000000..ff6e2483e
--- /dev/null
+++ b/ext/fts5/test/fts5fault1.test
@@ -0,0 +1,353 @@
+# 2014 June 17
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+source $testdir/malloc_common.tcl
+set testprefix fts5fault1
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+# Simple tests:
+#
+# 1: CREATE VIRTUAL TABLE
+# 2: INSERT statement
+# 3: DELETE statement
+# 4: MATCH expressions
+#
+#
+
+faultsim_save_and_close
+do_faultsim_test 1 -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix='1, 2, 3') }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix='1, 2, 3');
+}
+faultsim_save_and_close
+do_faultsim_test 2 -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql {
+ INSERT INTO t1 VALUES('a b c', 'a bc def ghij klmno');
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix='1, 2, 3');
+ INSERT INTO t1 VALUES('a b c', 'a bc def ghij klmno');
+}
+faultsim_save_and_close
+do_faultsim_test 3 -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { DELETE FROM t1 }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t2 USING fts5(a, b);
+ INSERT INTO t2 VALUES('m f a jj th q jr ar', 'hj n h h sg j i m');
+ INSERT INTO t2 VALUES('nr s t g od j kf h', 'sb h aq rg op rb n nl');
+ INSERT INTO t2 VALUES('do h h pb p p q fr', 'c rj qs or cr a l i');
+ INSERT INTO t2 VALUES('lk gp t i lq mq qm p', 'h mr g f op ld aj h');
+ INSERT INTO t2 VALUES('ct d sq kc qi k f j', 'sn gh c of g s qt q');
+ INSERT INTO t2 VALUES('d ea d d om mp s ab', 'dm hg l df cm ft pa c');
+ INSERT INTO t2 VALUES('tc dk c jn n t sr ge', 'a a kn bc n i af h');
+ INSERT INTO t2 VALUES('ie ii d i b sa qo rf', 'a h m aq i b m fn');
+ INSERT INTO t2 VALUES('gs r fo a er m h li', 'tm c p gl eb ml q r');
+ INSERT INTO t2 VALUES('k fe fd rd a gi ho kk', 'ng m c r d ml rm r');
+}
+faultsim_save_and_close
+
+foreach {tn expr res} {
+ 1 { dk } 7
+ 2 { m f } 1
+ 3 { f* } {1 3 4 5 6 8 9 10}
+ 4 { m OR f } {1 4 5 8 9 10}
+ 5 { sn + gh } {5}
+ 6 { "sn gh" } {5}
+ 7 { NEAR(r a, 5) } {9}
+ 8 { m* f* } {1 4 6 8 9 10}
+ 9 { m* + f* } {1 8}
+} {
+ do_faultsim_test 4.$tn -prep {
+ faultsim_restore_and_reopen
+ } -body "
+ execsql { SELECT rowid FROM t2 WHERE t2 MATCH '$expr' }
+ " -test "
+ faultsim_test_result {[list 0 $res]}
+ "
+}
+
+
+#-------------------------------------------------------------------------
+# The following tests use a larger database populated with random data.
+#
+# The database page size is set to 512 bytes and the FTS5 page size left
+# at the default 1000 bytes. This means that reading a node may require
+# pulling an overflow page from disk, which is an extra opportunity for
+# an error to occur.
+#
+reset_db
+do_execsql_test 5.0.1 {
+ PRAGMA main.page_size = 512;
+ CREATE VIRTUAL TABLE x1 USING fts5(a, b);
+ PRAGMA main.page_size;
+} {512}
+
+proc rnddoc {n} {
+ set map [list 0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j]
+ set doc [list]
+ for {set i 0} {$i < $n} {incr i} {
+ lappend doc [string map $map [format %.3d [expr int(rand()*1000)]]]
+ }
+ set doc
+}
+db func rnddoc rnddoc
+
+do_execsql_test 5.0.2 {
+ WITH r(a, b) AS (
+ SELECT rnddoc(6), rnddoc(6) UNION ALL
+ SELECT rnddoc(6), rnddoc(6) FROM r
+ )
+ INSERT INTO x1 SELECT * FROM r LIMIT 10000;
+}
+
+set res [db one {
+ SELECT count(*) FROM x1 WHERE x1.a LIKE '%abc%' OR x1.b LIKE '%abc%'}
+]
+
+do_faultsim_test 5.1 -faults oom* -body {
+ execsql { SELECT count(*) FROM x1 WHERE x1 MATCH 'abc' }
+} -test {
+ faultsim_test_result [list 0 $::res]
+}
+do_faultsim_test 5.2 -faults oom* -body {
+ execsql { SELECT count(*) FROM x1 WHERE x1 MATCH 'abcd' }
+} -test {
+ faultsim_test_result [list 0 0]
+}
+
+proc test_astar {a b} {
+ return [expr { [regexp {a[^ ][^ ]} $a] || [regexp {a[^ ][^ ]} $b] }]
+}
+db func test_astar test_astar
+
+set res [db one { SELECT count(*) FROM x1 WHERE test_astar(a, b) } ]
+do_faultsim_test 5.3 -faults oom* -body {
+ execsql { SELECT count(*) FROM x1 WHERE x1 MATCH 'a*' }
+} -test {
+ faultsim_test_result [list 0 $::res]
+}
+
+do_faultsim_test 5.4 -faults oom* -prep {
+ db close
+ sqlite3 db test.db
+} -body {
+ execsql { INSERT INTO x1 VALUES('a b c d', 'e f g h') }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
+
+do_faultsim_test 5.5.1 -faults oom* -body {
+ execsql {
+ SELECT count(fts5_decode(rowid, block)) FROM x1_data WHERE rowid=1
+ }
+} -test {
+ faultsim_test_result [list 0 1]
+}
+do_faultsim_test 5.5.2 -faults oom* -body {
+ execsql {
+ SELECT count(fts5_decode(rowid, block)) FROM x1_data WHERE rowid=10
+ }
+} -test {
+ faultsim_test_result [list 0 1]
+}
+do_faultsim_test 5.5.3 -faults oom* -body {
+ execsql {
+ SELECT count(fts5_decode(rowid, block)) FROM x1_data WHERE rowid = (
+ SELECT min(rowid) FROM x1_data WHERE rowid>20
+ )
+ }
+} -test {
+ faultsim_test_result [list 0 1]
+}
+do_faultsim_test 5.5.4 -faults oom* -body {
+ execsql {
+ SELECT count(fts5_decode(rowid, block)) FROM x1_data WHERE rowid = (
+ SELECT max(rowid) FROM x1_data
+ )
+ }
+} -test {
+ faultsim_test_result [list 0 1]
+}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE x1 USING fts5(x);
+ INSERT INTO x1(x1, rank) VALUES('automerge', 0);
+
+ INSERT INTO x1 VALUES('a b c'); -- 1
+ INSERT INTO x1 VALUES('a b c'); -- 2
+ INSERT INTO x1 VALUES('a b c'); -- 3
+ INSERT INTO x1 VALUES('a b c'); -- 4
+ INSERT INTO x1 VALUES('a b c'); -- 5
+ INSERT INTO x1 VALUES('a b c'); -- 6
+ INSERT INTO x1 VALUES('a b c'); -- 7
+ INSERT INTO x1 VALUES('a b c'); -- 8
+ INSERT INTO x1 VALUES('a b c'); -- 9
+ INSERT INTO x1 VALUES('a b c'); -- 10
+ INSERT INTO x1 VALUES('a b c'); -- 11
+ INSERT INTO x1 VALUES('a b c'); -- 12
+ INSERT INTO x1 VALUES('a b c'); -- 13
+ INSERT INTO x1 VALUES('a b c'); -- 14
+ INSERT INTO x1 VALUES('a b c'); -- 15
+
+ SELECT count(*) FROM x1_data;
+} {17}
+
+faultsim_save_and_close
+
+do_faultsim_test 6.1 -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO x1 VALUES('d e f') }
+} -test {
+ faultsim_test_result [list 0 {}]
+ if {$testrc==0} {
+ set nCnt [db one {SELECT count(*) FROM x1_data}]
+ if {$nCnt!=3} { error "expected 3 entries but there are $nCnt" }
+ }
+}
+
+do_faultsim_test 6.2 -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO x1(x1, rank) VALUES('pgsz', 32) }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
+
+do_faultsim_test 6.3 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO x1(x1) VALUES('integrity-check') }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
+
+do_faultsim_test 6.4 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO x1(x1) VALUES('optimize') }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
+
+#-------------------------------------------------------------------------
+#
+do_faultsim_test 7.0 -faults oom* -prep {
+ catch { db close }
+} -body {
+ sqlite3 db test.db
+} -test {
+ faultsim_test_result [list 0 {}] [list 1 {}]
+}
+
+#-------------------------------------------------------------------------
+# A prefix query against a large document set.
+#
+proc rnddoc {n} {
+ set map [list 0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j]
+ set doc [list]
+ for {set i 0} {$i < $n} {incr i} {
+ lappend doc "x[string map $map [format %.3d [expr int(rand()*1000)]]]"
+ }
+ set doc
+}
+
+reset_db
+db func rnddoc rnddoc
+
+do_test 8.0 {
+ execsql { CREATE VIRTUAL TABLE x1 USING fts5(a) }
+ set ::res [list]
+ for {set i 1} {$i<100} {incr i 1} {
+ execsql { INSERT INTO x1 VALUES( rnddoc(50) ) }
+ lappend ::res $i
+ }
+} {}
+
+do_faultsim_test 8.1 -faults oom* -prep {
+} -body {
+ execsql {
+ SELECT rowid FROM x1 WHERE x1 MATCH 'x*'
+ }
+} -test {
+ faultsim_test_result [list 0 $::res]
+}
+
+#-------------------------------------------------------------------------
+# Segment promotion.
+#
+do_test 9.0 {
+ reset_db
+ db func rnddoc fts5_rnddoc
+ execsql {
+ CREATE VIRTUAL TABLE s2 USING fts5(x);
+ INSERT INTO s2(s2, rank) VALUES('pgsz', 32);
+ INSERT INTO s2(s2, rank) VALUES('automerge', 0);
+ }
+
+ for {set i 1} {$i <= 16} {incr i} {
+ execsql { INSERT INTO s2 VALUES(rnddoc(5)) }
+ }
+ fts5_level_segs s2
+} {0 1}
+set insert_doc [db one {SELECT rnddoc(160)}]
+faultsim_save_and_close
+
+do_faultsim_test 9.1 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO s2 VALUES($::insert_doc) }
+} -test {
+ faultsim_test_result {0 {}}
+ if {$testrc==0} {
+ set ls [fts5_level_segs s2]
+ if {$ls != "2 0"} { error "fts5_level_segs says {$ls}" }
+ }
+}
+
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5near.test b/ext/fts5/test/fts5near.test
new file mode 100644
index 000000000..f545447e6
--- /dev/null
+++ b/ext/fts5/test/fts5near.test
@@ -0,0 +1,65 @@
+# 2014 Jan 08
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Tests focused on the NEAR operator.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5near
+
+proc do_near_test {tn doc near res} {
+ uplevel [list do_execsql_test $tn "
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES('$doc');
+ SELECT count(*) FROM t1 WHERE t1 MATCH '$near';
+ " $res]
+}
+
+execsql {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = 'ascii tokenchars .')
+}
+
+do_near_test 1.1 ". . a . . . b . ." { NEAR(a b, 5) } 1
+do_near_test 1.2 ". . a . . . b . ." { NEAR(a b, 4) } 1
+do_near_test 1.3 ". . a . . . b . ." { NEAR(a b, 3) } 1
+do_near_test 1.4 ". . a . . . b . ." { NEAR(a b, 2) } 0
+
+do_near_test 1.5 ". . a . . . b . ." { NEAR(b a, 5) } 1
+do_near_test 1.6 ". . a . . . b . ." { NEAR(b a, 4) } 1
+do_near_test 1.7 ". . a . . . b . ." { NEAR(b a, 3) } 1
+do_near_test 1.8 ". . a . . . b . ." { NEAR(b a, 2) } 0
+
+do_near_test 1.9 ". a b . . . c . ." { NEAR("a b" c, 3) } 1
+do_near_test 1.10 ". a b . . . c . ." { NEAR("a b" c, 2) } 0
+do_near_test 1.11 ". a b . . . c . ." { NEAR(c "a b", 3) } 1
+do_near_test 1.12 ". a b . . . c . ." { NEAR(c "a b", 2) } 0
+
+do_near_test 1.13 ". a b . . . c d ." { NEAR(a+b c+d, 3) } 1
+do_near_test 1.14 ". a b . . . c d ." { NEAR(a+b c+d, 2) } 0
+do_near_test 1.15 ". a b . . . c d ." { NEAR(c+d a+b, 3) } 1
+do_near_test 1.16 ". a b . . . c d ." { NEAR(c+d a+b, 2) } 0
+
+do_near_test 1.17 ". a b . . . c d ." { NEAR(a b c d, 5) } 1
+do_near_test 1.18 ". a b . . . c d ." { NEAR(a b c d, 4) } 0
+do_near_test 1.19 ". a b . . . c d ." { NEAR(a+b c d, 4) } 1
+
+do_near_test 1.20 "a b c d e f g h i" { NEAR(b+c a+b+c+d i, 5) } 1
+do_near_test 1.21 "a b c d e f g h i" { NEAR(b+c a+b+c+d i, 4) } 0
+
+do_near_test 1.22 "a b c d e f g h i" { NEAR(a+b+c+d i b+c, 5) } 1
+do_near_test 1.23 "a b c d e f g h i" { NEAR(a+b+c+d i b+c, 4) } 0
+
+do_near_test 1.24 "a b c d e f g h i" { NEAR(i a+b+c+d b+c, 5) } 1
+do_near_test 1.25 "a b c d e f g h i" { NEAR(i a+b+c+d b+c, 4) } 0
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5optimize.test b/ext/fts5/test/fts5optimize.test
new file mode 100644
index 000000000..068cf4c22
--- /dev/null
+++ b/ext/fts5/test/fts5optimize.test
@@ -0,0 +1,60 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5optimize
+
+proc rnddoc {nWord} {
+ set vocab {a b c d e f g h i j k l m n o p q r s t u v w x y z}
+ set nVocab [llength $vocab]
+ set ret [list]
+ for {set i 0} {$i < $nWord} {incr i} {
+ lappend ret [lindex $vocab [expr {int(rand() * $nVocab)}]]
+ }
+ return $ret
+}
+
+
+foreach {tn nStep} {
+ 1 2
+ 2 10
+ 3 50
+ 4 500
+} {
+if {$tn!=4} continue
+ reset_db
+ db func rnddoc rnddoc
+ do_execsql_test 1.$tn.1 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, y);
+ }
+ do_test 1.$tn.2 {
+ for {set i 0} {$i < $nStep} {incr i} {
+ execsql { INSERT INTO t1 VALUES( rnddoc(5), rnddoc(5) ) }
+ }
+ } {}
+
+ do_execsql_test 1.$tn.3 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+
+ do_execsql_test 1.$tn.4 {
+ INSERT INTO t1(t1) VALUES('optimize');
+ }
+
+ do_execsql_test 1.$tn.5 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+ }
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5porter.test b/ext/fts5/test/fts5porter.test
new file mode 100644
index 000000000..83ca85230
--- /dev/null
+++ b/ext/fts5/test/fts5porter.test
@@ -0,0 +1,11800 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Tests focusing on the fts5 porter stemmer implementation.
+#
+# http://tartarus.org/martin/PorterStemmer/
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5porter
+
+set test_vocab {
+ a a aaron aaron
+ abaissiez abaissiez abandon abandon
+ abandoned abandon abase abas
+ abash abash abate abat
+ abated abat abatement abat
+ abatements abat abates abat
+ abbess abbess abbey abbei
+ abbeys abbei abbominable abbomin
+ abbot abbot abbots abbot
+ abbreviated abbrevi abed ab
+ abel abel aberga aberga
+ abergavenny abergavenni abet abet
+ abetting abet abhominable abhomin
+ abhor abhor abhorr abhorr
+ abhorred abhor abhorring abhor
+ abhors abhor abhorson abhorson
+ abide abid abides abid
+ abilities abil ability abil
+ abject abject abjectly abjectli
+ abjects abject abjur abjur
+ abjure abjur able abl
+ abler abler aboard aboard
+ abode abod aboded abod
+ abodements abod aboding abod
+ abominable abomin abominably abomin
+ abominations abomin abortive abort
+ abortives abort abound abound
+ abounding abound about about
+ above abov abr abr
+ abraham abraham abram abram
+ abreast abreast abridg abridg
+ abridge abridg abridged abridg
+ abridgment abridg abroach abroach
+ abroad abroad abrogate abrog
+ abrook abrook abrupt abrupt
+ abruption abrupt abruptly abruptli
+ absence absenc absent absent
+ absey absei absolute absolut
+ absolutely absolut absolv absolv
+ absolver absolv abstains abstain
+ abstemious abstemi abstinence abstin
+ abstract abstract absurd absurd
+ absyrtus absyrtu abundance abund
+ abundant abund abundantly abundantli
+ abus abu abuse abus
+ abused abus abuser abus
+ abuses abus abusing abus
+ abutting abut aby abi
+ abysm abysm ac ac
+ academe academ academes academ
+ accent accent accents accent
+ accept accept acceptable accept
+ acceptance accept accepted accept
+ accepts accept access access
+ accessary accessari accessible access
+ accidence accid accident accid
+ accidental accident accidentally accident
+ accidents accid accite accit
+ accited accit accites accit
+ acclamations acclam accommodate accommod
+ accommodated accommod accommodation accommod
+ accommodations accommod accommodo accommodo
+ accompanied accompani accompany accompani
+ accompanying accompani accomplices accomplic
+ accomplish accomplish accomplished accomplish
+ accomplishing accomplish accomplishment accomplish
+ accompt accompt accord accord
+ accordant accord accorded accord
+ accordeth accordeth according accord
+ accordingly accordingli accords accord
+ accost accost accosted accost
+ account account accountant account
+ accounted account accounts account
+ accoutred accoutr accoutrement accoutr
+ accoutrements accoutr accrue accru
+ accumulate accumul accumulated accumul
+ accumulation accumul accurs accur
+ accursed accurs accurst accurst
+ accus accu accusation accus
+ accusations accus accusative accus
+ accusativo accusativo accuse accus
+ accused accus accuser accus
+ accusers accus accuses accus
+ accuseth accuseth accusing accus
+ accustom accustom accustomed accustom
+ ace ac acerb acerb
+ ache ach acheron acheron
+ aches ach achiev achiev
+ achieve achiev achieved achiev
+ achievement achiev achievements achiev
+ achiever achiev achieves achiev
+ achieving achiev achilles achil
+ aching ach achitophel achitophel
+ acknowledg acknowledg acknowledge acknowledg
+ acknowledged acknowledg acknowledgment acknowledg
+ acknown acknown acold acold
+ aconitum aconitum acordo acordo
+ acorn acorn acquaint acquaint
+ acquaintance acquaint acquainted acquaint
+ acquaints acquaint acquir acquir
+ acquire acquir acquisition acquisit
+ acquit acquit acquittance acquitt
+ acquittances acquitt acquitted acquit
+ acre acr acres acr
+ across across act act
+ actaeon actaeon acted act
+ acting act action action
+ actions action actium actium
+ active activ actively activ
+ activity activ actor actor
+ actors actor acts act
+ actual actual acture actur
+ acute acut acutely acut
+ ad ad adage adag
+ adallas adalla adam adam
+ adamant adam add add
+ added ad adder adder
+ adders adder addeth addeth
+ addict addict addicted addict
+ addiction addict adding ad
+ addition addit additions addit
+ addle addl address address
+ addressing address addrest addrest
+ adds add adhere adher
+ adheres adher adieu adieu
+ adieus adieu adjacent adjac
+ adjoin adjoin adjoining adjoin
+ adjourn adjourn adjudg adjudg
+ adjudged adjudg adjunct adjunct
+ administer administ administration administr
+ admir admir admirable admir
+ admiral admir admiration admir
+ admire admir admired admir
+ admirer admir admiring admir
+ admiringly admiringli admission admiss
+ admit admit admits admit
+ admittance admitt admitted admit
+ admitting admit admonish admonish
+ admonishing admonish admonishment admonish
+ admonishments admonish admonition admonit
+ ado ado adonis adoni
+ adopt adopt adopted adopt
+ adoptedly adoptedli adoption adopt
+ adoptious adopti adopts adopt
+ ador ador adoration ador
+ adorations ador adore ador
+ adorer ador adores ador
+ adorest adorest adoreth adoreth
+ adoring ador adorn adorn
+ adorned adorn adornings adorn
+ adornment adorn adorns adorn
+ adown adown adramadio adramadio
+ adrian adrian adriana adriana
+ adriano adriano adriatic adriat
+ adsum adsum adulation adul
+ adulterate adulter adulterates adulter
+ adulterers adulter adulteress adulteress
+ adulteries adulteri adulterous adulter
+ adultery adulteri adultress adultress
+ advanc advanc advance advanc
+ advanced advanc advancement advanc
+ advancements advanc advances advanc
+ advancing advanc advantage advantag
+ advantageable advantag advantaged advantag
+ advantageous advantag advantages advantag
+ advantaging advantag advent advent
+ adventur adventur adventure adventur
+ adventures adventur adventuring adventur
+ adventurous adventur adventurously adventur
+ adversaries adversari adversary adversari
+ adverse advers adversely advers
+ adversities advers adversity advers
+ advertis adverti advertise advertis
+ advertised advertis advertisement advertis
+ advertising advertis advice advic
+ advis advi advise advis
+ advised advis advisedly advisedli
+ advises advis advisings advis
+ advocate advoc advocation advoc
+ aeacida aeacida aeacides aeacid
+ aedile aedil aediles aedil
+ aegeon aegeon aegion aegion
+ aegles aegl aemelia aemelia
+ aemilia aemilia aemilius aemiliu
+ aeneas aenea aeolus aeolu
+ aer aer aerial aerial
+ aery aeri aesculapius aesculapiu
+ aeson aeson aesop aesop
+ aetna aetna afar afar
+ afear afear afeard afeard
+ affability affabl affable affabl
+ affair affair affaire affair
+ affairs affair affect affect
+ affectation affect affectations affect
+ affected affect affectedly affectedli
+ affecteth affecteth affecting affect
+ affection affect affectionate affection
+ affectionately affection affections affect
+ affects affect affeer affeer
+ affianc affianc affiance affianc
+ affianced affianc affied affi
+ affin affin affined affin
+ affinity affin affirm affirm
+ affirmation affirm affirmatives affirm
+ afflict afflict afflicted afflict
+ affliction afflict afflictions afflict
+ afflicts afflict afford afford
+ affordeth affordeth affords afford
+ affray affrai affright affright
+ affrighted affright affrights affright
+ affront affront affronted affront
+ affy affi afield afield
+ afire afir afloat afloat
+ afoot afoot afore afor
+ aforehand aforehand aforesaid aforesaid
+ afraid afraid afresh afresh
+ afric afric africa africa
+ african african afront afront
+ after after afternoon afternoon
+ afterward afterward afterwards afterward
+ ag ag again again
+ against against agamemmon agamemmon
+ agamemnon agamemnon agate agat
+ agaz agaz age ag
+ aged ag agenor agenor
+ agent agent agents agent
+ ages ag aggravate aggrav
+ aggrief aggrief agile agil
+ agincourt agincourt agitation agit
+ aglet aglet agnize agniz
+ ago ago agone agon
+ agony agoni agree agre
+ agreed agre agreeing agre
+ agreement agreement agrees agre
+ agrippa agrippa aground aground
+ ague agu aguecheek aguecheek
+ agued agu agueface aguefac
+ agues agu ah ah
+ aha aha ahungry ahungri
+ ai ai aialvolio aialvolio
+ aiaria aiaria aid aid
+ aidance aidanc aidant aidant
+ aided aid aiding aid
+ aidless aidless aids aid
+ ail ail aim aim
+ aimed aim aimest aimest
+ aiming aim aims aim
+ ainsi ainsi aio aio
+ air air aired air
+ airless airless airs air
+ airy airi ajax ajax
+ akilling akil al al
+ alabaster alabast alack alack
+ alacrity alacr alarbus alarbu
+ alarm alarm alarms alarm
+ alarum alarum alarums alarum
+ alas ala alb alb
+ alban alban albans alban
+ albany albani albeit albeit
+ albion albion alchemist alchemist
+ alchemy alchemi alcibiades alcibiad
+ alcides alcid alder alder
+ alderman alderman aldermen aldermen
+ ale al alecto alecto
+ alehouse alehous alehouses alehous
+ alencon alencon alengon alengon
+ aleppo aleppo ales al
+ alewife alewif alexander alexand
+ alexanders alexand alexandria alexandria
+ alexandrian alexandrian alexas alexa
+ alias alia alice alic
+ alien alien aliena aliena
+ alight alight alighted alight
+ alights alight aliis alii
+ alike alik alisander alisand
+ alive aliv all all
+ alla alla allay allai
+ allayed allai allaying allai
+ allayment allay allayments allay
+ allays allai allegation alleg
+ allegations alleg allege alleg
+ alleged alleg allegiance allegi
+ allegiant allegi alley allei
+ alleys allei allhallowmas allhallowma
+ alliance allianc allicholy allicholi
+ allied alli allies alli
+ alligant allig alligator allig
+ allons allon allot allot
+ allots allot allotted allot
+ allottery allotteri allow allow
+ allowance allow allowed allow
+ allowing allow allows allow
+ allur allur allure allur
+ allurement allur alluring allur
+ allusion allus ally alli
+ allycholly allycholli almain almain
+ almanac almanac almanack almanack
+ almanacs almanac almighty almighti
+ almond almond almost almost
+ alms alm almsman almsman
+ aloes alo aloft aloft
+ alone alon along along
+ alonso alonso aloof aloof
+ aloud aloud alphabet alphabet
+ alphabetical alphabet alphonso alphonso
+ alps alp already alreadi
+ also also alt alt
+ altar altar altars altar
+ alter alter alteration alter
+ altered alter alters alter
+ althaea althaea although although
+ altitude altitud altogether altogeth
+ alton alton alway alwai
+ always alwai am am
+ amaimon amaimon amain amain
+ amaking amak amamon amamon
+ amaz amaz amaze amaz
+ amazed amaz amazedly amazedli
+ amazedness amazed amazement amaz
+ amazes amaz amazeth amazeth
+ amazing amaz amazon amazon
+ amazonian amazonian amazons amazon
+ ambassador ambassador ambassadors ambassador
+ amber amber ambiguides ambiguid
+ ambiguities ambigu ambiguous ambigu
+ ambition ambit ambitions ambit
+ ambitious ambiti ambitiously ambiti
+ amble ambl ambled ambl
+ ambles ambl ambling ambl
+ ambo ambo ambuscadoes ambuscado
+ ambush ambush amen amen
+ amend amend amended amend
+ amendment amend amends amend
+ amerce amerc america america
+ ames am amiable amiabl
+ amid amid amidst amidst
+ amiens amien amis ami
+ amiss amiss amities amiti
+ amity amiti amnipotent amnipot
+ among among amongst amongst
+ amorous amor amorously amor
+ amort amort amount amount
+ amounts amount amour amour
+ amphimacus amphimacu ample ampl
+ ampler ampler amplest amplest
+ amplified amplifi amplify amplifi
+ amply ampli ampthill ampthil
+ amurath amurath amyntas amynta
+ an an anatomiz anatomiz
+ anatomize anatom anatomy anatomi
+ ancestor ancestor ancestors ancestor
+ ancestry ancestri anchises anchis
+ anchor anchor anchorage anchorag
+ anchored anchor anchoring anchor
+ anchors anchor anchovies anchovi
+ ancient ancient ancientry ancientri
+ ancients ancient ancus ancu
+ and and andirons andiron
+ andpholus andpholu andren andren
+ andrew andrew andromache andromach
+ andronici andronici andronicus andronicu
+ anew anew ang ang
+ angel angel angelica angelica
+ angelical angel angelo angelo
+ angels angel anger anger
+ angerly angerli angers anger
+ anges ang angiers angier
+ angl angl anglais anglai
+ angle angl angler angler
+ angleterre angleterr angliae anglia
+ angling angl anglish anglish
+ angrily angrili angry angri
+ anguish anguish angus angu
+ animal anim animals anim
+ animis animi anjou anjou
+ ankle ankl anna anna
+ annals annal anne ann
+ annex annex annexed annex
+ annexions annexion annexment annex
+ annothanize annothan announces announc
+ annoy annoi annoyance annoy
+ annoying annoi annual annual
+ anoint anoint anointed anoint
+ anon anon another anoth
+ anselmo anselmo answer answer
+ answerable answer answered answer
+ answerest answerest answering answer
+ answers answer ant ant
+ ante ant antenor antenor
+ antenorides antenorid anteroom anteroom
+ anthem anthem anthems anthem
+ anthony anthoni anthropophagi anthropophagi
+ anthropophaginian anthropophaginian antiates antiat
+ antic antic anticipate anticip
+ anticipates anticip anticipatest anticipatest
+ anticipating anticip anticipation anticip
+ antick antick anticly anticli
+ antics antic antidote antidot
+ antidotes antidot antigonus antigonu
+ antiopa antiopa antipathy antipathi
+ antipholus antipholu antipholuses antipholus
+ antipodes antipod antiquary antiquari
+ antique antiqu antiquity antiqu
+ antium antium antoniad antoniad
+ antonio antonio antonius antoniu
+ antony antoni antres antr
+ anvil anvil any ani
+ anybody anybodi anyone anyon
+ anything anyth anywhere anywher
+ ap ap apace apac
+ apart apart apartment apart
+ apartments apart ape ap
+ apemantus apemantu apennines apennin
+ apes ap apiece apiec
+ apish apish apollinem apollinem
+ apollo apollo apollodorus apollodoru
+ apology apolog apoplex apoplex
+ apoplexy apoplexi apostle apostl
+ apostles apostl apostrophas apostropha
+ apoth apoth apothecary apothecari
+ appal appal appall appal
+ appalled appal appals appal
+ apparel apparel apparell apparel
+ apparelled apparel apparent appar
+ apparently appar apparition apparit
+ apparitions apparit appeach appeach
+ appeal appeal appeals appeal
+ appear appear appearance appear
+ appeared appear appeareth appeareth
+ appearing appear appears appear
+ appeas appea appease appeas
+ appeased appeas appelant appel
+ appele appel appelee appele
+ appeles appel appelez appelez
+ appellant appel appellants appel
+ appelons appelon appendix appendix
+ apperil apperil appertain appertain
+ appertaining appertain appertainings appertain
+ appertains appertain appertinent appertin
+ appertinents appertin appetite appetit
+ appetites appetit applaud applaud
+ applauded applaud applauding applaud
+ applause applaus applauses applaus
+ apple appl apples appl
+ appletart appletart appliance applianc
+ appliances applianc applications applic
+ applied appli applies appli
+ apply appli applying appli
+ appoint appoint appointed appoint
+ appointment appoint appointments appoint
+ appoints appoint apprehend apprehend
+ apprehended apprehend apprehends apprehend
+ apprehension apprehens apprehensions apprehens
+ apprehensive apprehens apprendre apprendr
+ apprenne apprenn apprenticehood apprenticehood
+ appris appri approach approach
+ approachers approach approaches approach
+ approacheth approacheth approaching approach
+ approbation approb approof approof
+ appropriation appropri approv approv
+ approve approv approved approv
+ approvers approv approves approv
+ appurtenance appurten appurtenances appurten
+ apricocks apricock april april
+ apron apron aprons apron
+ apt apt apter apter
+ aptest aptest aptly aptli
+ aptness apt aqua aqua
+ aquilon aquilon aquitaine aquitain
+ arabia arabia arabian arabian
+ araise arais arbitrate arbitr
+ arbitrating arbitr arbitrator arbitr
+ arbitrement arbitr arbors arbor
+ arbour arbour arc arc
+ arch arch archbishop archbishop
+ archbishopric archbishopr archdeacon archdeacon
+ arched arch archelaus archelau
+ archer archer archers archer
+ archery archeri archibald archibald
+ archidamus archidamu architect architect
+ arcu arcu arde ard
+ arden arden ardent ardent
+ ardour ardour are ar
+ argal argal argier argier
+ argo argo argosies argosi
+ argosy argosi argu argu
+ argue argu argued argu
+ argues argu arguing argu
+ argument argument arguments argument
+ argus argu ariachne ariachn
+ ariadne ariadn ariel ariel
+ aries ari aright aright
+ arinado arinado arinies arini
+ arion arion arise aris
+ arises aris ariseth ariseth
+ arising aris aristode aristod
+ aristotle aristotl arithmetic arithmet
+ arithmetician arithmetician ark ark
+ arm arm arma arma
+ armado armado armadoes armado
+ armagnac armagnac arme arm
+ armed arm armenia armenia
+ armies armi armigero armigero
+ arming arm armipotent armipot
+ armor armor armour armour
+ armourer armour armourers armour
+ armours armour armoury armouri
+ arms arm army armi
+ arn arn aroint aroint
+ arose aros arouse arous
+ aroused arous arragon arragon
+ arraign arraign arraigned arraign
+ arraigning arraign arraignment arraign
+ arrant arrant arras arra
+ array arrai arrearages arrearag
+ arrest arrest arrested arrest
+ arrests arrest arriv arriv
+ arrival arriv arrivance arriv
+ arrive arriv arrived arriv
+ arrives arriv arriving arriv
+ arrogance arrog arrogancy arrog
+ arrogant arrog arrow arrow
+ arrows arrow art art
+ artemidorus artemidoru arteries arteri
+ arthur arthur article articl
+ articles articl articulate articul
+ artificer artific artificial artifici
+ artillery artilleri artire artir
+ artist artist artists artist
+ artless artless artois artoi
+ arts art artus artu
+ arviragus arviragu as as
+ asaph asaph ascanius ascaniu
+ ascend ascend ascended ascend
+ ascendeth ascendeth ascends ascend
+ ascension ascens ascent ascent
+ ascribe ascrib ascribes ascrib
+ ash ash asham asham
+ ashamed asham asher asher
+ ashes ash ashford ashford
+ ashore ashor ashouting ashout
+ ashy ashi asia asia
+ aside asid ask ask
+ askance askanc asked ask
+ asker asker asketh asketh
+ asking ask asks ask
+ aslant aslant asleep asleep
+ asmath asmath asp asp
+ aspect aspect aspects aspect
+ aspen aspen aspersion aspers
+ aspic aspic aspicious aspici
+ aspics aspic aspir aspir
+ aspiration aspir aspire aspir
+ aspiring aspir asquint asquint
+ ass ass assail assail
+ assailable assail assailant assail
+ assailants assail assailed assail
+ assaileth assaileth assailing assail
+ assails assail assassination assassin
+ assault assault assaulted assault
+ assaults assault assay assai
+ assaying assai assays assai
+ assemblance assembl assemble assembl
+ assembled assembl assemblies assembl
+ assembly assembl assent assent
+ asses ass assez assez
+ assign assign assigned assign
+ assigns assign assinico assinico
+ assist assist assistance assist
+ assistances assist assistant assist
+ assistants assist assisted assist
+ assisting assist associate associ
+ associated associ associates associ
+ assuage assuag assubjugate assubjug
+ assum assum assume assum
+ assumes assum assumption assumpt
+ assur assur assurance assur
+ assure assur assured assur
+ assuredly assuredli assures assur
+ assyrian assyrian astonish astonish
+ astonished astonish astraea astraea
+ astray astrai astrea astrea
+ astronomer astronom astronomers astronom
+ astronomical astronom astronomy astronomi
+ asunder asund at at
+ atalanta atalanta ate at
+ ates at athenian athenian
+ athenians athenian athens athen
+ athol athol athversary athversari
+ athwart athwart atlas atla
+ atomies atomi atomy atomi
+ atone aton atonement aton
+ atonements aton atropos atropo
+ attach attach attached attach
+ attachment attach attain attain
+ attainder attaind attains attain
+ attaint attaint attainted attaint
+ attainture attaintur attempt attempt
+ attemptable attempt attempted attempt
+ attempting attempt attempts attempt
+ attend attend attendance attend
+ attendant attend attendants attend
+ attended attend attendents attend
+ attendeth attendeth attending attend
+ attends attend attent attent
+ attention attent attentive attent
+ attentivenes attentiven attest attest
+ attested attest attir attir
+ attire attir attired attir
+ attires attir attorney attornei
+ attorneyed attornei attorneys attornei
+ attorneyship attorneyship attract attract
+ attraction attract attractive attract
+ attracts attract attribute attribut
+ attributed attribut attributes attribut
+ attribution attribut attributive attribut
+ atwain atwain au au
+ aubrey aubrei auburn auburn
+ aucun aucun audacious audaci
+ audaciously audaci audacity audac
+ audible audibl audience audienc
+ audis audi audit audit
+ auditor auditor auditors auditor
+ auditory auditori audre audr
+ audrey audrei aufidius aufidiu
+ aufidiuses aufidius auger auger
+ aught aught augment augment
+ augmentation augment augmented augment
+ augmenting augment augurer augur
+ augurers augur augures augur
+ auguring augur augurs augur
+ augury auguri august august
+ augustus augustu auld auld
+ aumerle aumerl aunchient aunchient
+ aunt aunt aunts aunt
+ auricular auricular aurora aurora
+ auspicious auspici aussi aussi
+ austere auster austerely auster
+ austereness auster austerity auster
+ austria austria aut aut
+ authentic authent author author
+ authorities author authority author
+ authorized author authorizing author
+ authors author autolycus autolycu
+ autre autr autumn autumn
+ auvergne auvergn avail avail
+ avails avail avarice avaric
+ avaricious avarici avaunt avaunt
+ ave av aveng aveng
+ avenge aveng avenged aveng
+ averring aver avert avert
+ aves av avez avez
+ avis avi avoid avoid
+ avoided avoid avoiding avoid
+ avoids avoid avoirdupois avoirdupoi
+ avouch avouch avouched avouch
+ avouches avouch avouchment avouch
+ avow avow aw aw
+ await await awaits await
+ awak awak awake awak
+ awaked awak awaken awaken
+ awakened awaken awakens awaken
+ awakes awak awaking awak
+ award award awards award
+ awasy awasi away awai
+ awe aw aweary aweari
+ aweless aweless awful aw
+ awhile awhil awkward awkward
+ awl awl awooing awoo
+ awork awork awry awri
+ axe ax axle axl
+ axletree axletre ay ay
+ aye ay ayez ayez
+ ayli ayli azur azur
+ azure azur b b
+ ba ba baa baa
+ babbl babbl babble babbl
+ babbling babbl babe babe
+ babes babe babies babi
+ baboon baboon baboons baboon
+ baby babi babylon babylon
+ bacare bacar bacchanals bacchan
+ bacchus bacchu bach bach
+ bachelor bachelor bachelors bachelor
+ back back backbite backbit
+ backbitten backbitten backing back
+ backs back backward backward
+ backwardly backwardli backwards backward
+ bacon bacon bacons bacon
+ bad bad bade bade
+ badge badg badged badg
+ badges badg badly badli
+ badness bad baes bae
+ baffl baffl baffle baffl
+ baffled baffl bag bag
+ baggage baggag bagot bagot
+ bagpipe bagpip bags bag
+ bail bail bailiff bailiff
+ baillez baillez baily baili
+ baisant baisant baisees baise
+ baiser baiser bait bait
+ baited bait baiting bait
+ baitings bait baits bait
+ bajazet bajazet bak bak
+ bake bake baked bake
+ baker baker bakers baker
+ bakes bake baking bake
+ bal bal balanc balanc
+ balance balanc balcony balconi
+ bald bald baldrick baldrick
+ bale bale baleful bale
+ balk balk ball ball
+ ballad ballad ballads ballad
+ ballast ballast ballasting ballast
+ ballet ballet ballow ballow
+ balls ball balm balm
+ balms balm balmy balmi
+ balsam balsam balsamum balsamum
+ balth balth balthasar balthasar
+ balthazar balthazar bames bame
+ ban ban banbury banburi
+ band band bandied bandi
+ banding band bandit bandit
+ banditti banditti banditto banditto
+ bands band bandy bandi
+ bandying bandi bane bane
+ banes bane bang bang
+ bangor bangor banish banish
+ banished banish banishers banish
+ banishment banish banister banist
+ bank bank bankrout bankrout
+ bankrupt bankrupt bankrupts bankrupt
+ banks bank banner banner
+ bannerets banneret banners banner
+ banning ban banns bann
+ banquet banquet banqueted banquet
+ banqueting banquet banquets banquet
+ banquo banquo bans ban
+ baptism baptism baptista baptista
+ baptiz baptiz bar bar
+ barbarian barbarian barbarians barbarian
+ barbarism barbar barbarous barbar
+ barbary barbari barbason barbason
+ barbed barb barber barber
+ barbermonger barbermong bard bard
+ bardolph bardolph bards bard
+ bare bare bared bare
+ barefac barefac barefaced barefac
+ barefoot barefoot bareheaded barehead
+ barely bare bareness bare
+ barful bar bargain bargain
+ bargains bargain barge barg
+ bargulus bargulu baring bare
+ bark bark barking bark
+ barkloughly barkloughli barks bark
+ barky barki barley barlei
+ barm barm barn barn
+ barnacles barnacl barnardine barnardin
+ barne barn barnes barn
+ barnet barnet barns barn
+ baron baron barons baron
+ barony baroni barr barr
+ barrabas barraba barrel barrel
+ barrels barrel barren barren
+ barrenly barrenli barrenness barren
+ barricado barricado barricadoes barricado
+ barrow barrow bars bar
+ barson barson barter barter
+ bartholomew bartholomew bas ba
+ basan basan base base
+ baseless baseless basely base
+ baseness base baser baser
+ bases base basest basest
+ bashful bash bashfulness bash
+ basilisco basilisco basilisk basilisk
+ basilisks basilisk basimecu basimecu
+ basin basin basingstoke basingstok
+ basins basin basis basi
+ bask bask basket basket
+ baskets basket bass bass
+ bassanio bassanio basset basset
+ bassianus bassianu basta basta
+ bastard bastard bastardizing bastard
+ bastardly bastardli bastards bastard
+ bastardy bastardi basted bast
+ bastes bast bastinado bastinado
+ basting bast bat bat
+ batailles batail batch batch
+ bate bate bated bate
+ bates bate bath bath
+ bathe bath bathed bath
+ bathing bath baths bath
+ bating bate batler batler
+ bats bat batt batt
+ battalia battalia battalions battalion
+ batten batten batter batter
+ battering batter batters batter
+ battery batteri battle battl
+ battled battl battlefield battlefield
+ battlements battlement battles battl
+ batty batti bauble baubl
+ baubles baubl baubling baubl
+ baulk baulk bavin bavin
+ bawcock bawcock bawd bawd
+ bawdry bawdri bawds bawd
+ bawdy bawdi bawl bawl
+ bawling bawl bay bai
+ baying bai baynard baynard
+ bayonne bayonn bays bai
+ be be beach beach
+ beached beach beachy beachi
+ beacon beacon bead bead
+ beaded bead beadle beadl
+ beadles beadl beads bead
+ beadsmen beadsmen beagle beagl
+ beagles beagl beak beak
+ beaks beak beam beam
+ beamed beam beams beam
+ bean bean beans bean
+ bear bear beard beard
+ bearded beard beardless beardless
+ beards beard bearer bearer
+ bearers bearer bearest bearest
+ beareth beareth bearing bear
+ bears bear beast beast
+ beastliest beastliest beastliness beastli
+ beastly beastli beasts beast
+ beat beat beated beat
+ beaten beaten beating beat
+ beatrice beatric beats beat
+ beau beau beaufort beaufort
+ beaumond beaumond beaumont beaumont
+ beauteous beauteou beautied beauti
+ beauties beauti beautified beautifi
+ beautiful beauti beautify beautifi
+ beauty beauti beaver beaver
+ beavers beaver became becam
+ because becaus bechanc bechanc
+ bechance bechanc bechanced bechanc
+ beck beck beckon beckon
+ beckons beckon becks beck
+ becom becom become becom
+ becomed becom becomes becom
+ becoming becom becomings becom
+ bed bed bedabbled bedabbl
+ bedash bedash bedaub bedaub
+ bedazzled bedazzl bedchamber bedchamb
+ bedclothes bedcloth bedded bed
+ bedeck bedeck bedecking bedeck
+ bedew bedew bedfellow bedfellow
+ bedfellows bedfellow bedford bedford
+ bedlam bedlam bedrench bedrench
+ bedrid bedrid beds bed
+ bedtime bedtim bedward bedward
+ bee bee beef beef
+ beefs beef beehives beehiv
+ been been beer beer
+ bees bee beest beest
+ beetle beetl beetles beetl
+ beeves beev befall befal
+ befallen befallen befalls befal
+ befell befel befits befit
+ befitted befit befitting befit
+ befor befor before befor
+ beforehand beforehand befortune befortun
+ befriend befriend befriended befriend
+ befriends befriend beg beg
+ began began beget beget
+ begets beget begetting beget
+ begg begg beggar beggar
+ beggared beggar beggarly beggarli
+ beggarman beggarman beggars beggar
+ beggary beggari begging beg
+ begin begin beginners beginn
+ beginning begin beginnings begin
+ begins begin begnawn begnawn
+ begone begon begot begot
+ begotten begotten begrimed begrim
+ begs beg beguil beguil
+ beguile beguil beguiled beguil
+ beguiles beguil beguiling beguil
+ begun begun behalf behalf
+ behalfs behalf behav behav
+ behaved behav behavedst behavedst
+ behavior behavior behaviors behavior
+ behaviour behaviour behaviours behaviour
+ behead behead beheaded behead
+ beheld beheld behest behest
+ behests behest behind behind
+ behold behold beholder behold
+ beholders behold beholdest beholdest
+ beholding behold beholds behold
+ behoof behoof behooffull behoofful
+ behooves behoov behove behov
+ behoves behov behowls behowl
+ being be bel bel
+ belarius belariu belch belch
+ belching belch beldam beldam
+ beldame beldam beldams beldam
+ belee bele belgia belgia
+ belie beli belied beli
+ belief belief beliest beliest
+ believ believ believe believ
+ believed believ believes believ
+ believest believest believing believ
+ belike belik bell bell
+ bellario bellario belle bell
+ bellied belli bellies belli
+ bellman bellman bellona bellona
+ bellow bellow bellowed bellow
+ bellowing bellow bellows bellow
+ bells bell belly belli
+ bellyful belly belman belman
+ belmont belmont belock belock
+ belong belong belonging belong
+ belongings belong belongs belong
+ belov belov beloved belov
+ beloving belov below below
+ belt belt belzebub belzebub
+ bemadding bemad bemet bemet
+ bemete bemet bemoan bemoan
+ bemoaned bemoan bemock bemock
+ bemoil bemoil bemonster bemonst
+ ben ben bench bench
+ bencher bencher benches bench
+ bend bend bended bend
+ bending bend bends bend
+ bene bene beneath beneath
+ benedicite benedicit benedick benedick
+ benediction benedict benedictus benedictu
+ benefactors benefactor benefice benefic
+ beneficial benefici benefit benefit
+ benefited benefit benefits benefit
+ benetted benet benevolence benevol
+ benevolences benevol benied beni
+ benison benison bennet bennet
+ bent bent bentii bentii
+ bentivolii bentivolii bents bent
+ benumbed benumb benvolio benvolio
+ bepaint bepaint bepray beprai
+ bequeath bequeath bequeathed bequeath
+ bequeathing bequeath bequest bequest
+ ber ber berard berard
+ berattle berattl beray berai
+ bere bere bereave bereav
+ bereaved bereav bereaves bereav
+ bereft bereft bergamo bergamo
+ bergomask bergomask berhym berhym
+ berhyme berhym berkeley berkelei
+ bermoothes bermooth bernardo bernardo
+ berod berod berowne berown
+ berri berri berries berri
+ berrord berrord berry berri
+ bertram bertram berwick berwick
+ bescreen bescreen beseech beseech
+ beseeched beseech beseechers beseech
+ beseeching beseech beseek beseek
+ beseem beseem beseemeth beseemeth
+ beseeming beseem beseems beseem
+ beset beset beshrew beshrew
+ beside besid besides besid
+ besieg besieg besiege besieg
+ besieged besieg beslubber beslubb
+ besmear besmear besmeared besmear
+ besmirch besmirch besom besom
+ besort besort besotted besot
+ bespake bespak bespeak bespeak
+ bespice bespic bespoke bespok
+ bespotted bespot bess bess
+ bessy bessi best best
+ bestained bestain bested best
+ bestial bestial bestir bestir
+ bestirr bestirr bestow bestow
+ bestowed bestow bestowing bestow
+ bestows bestow bestraught bestraught
+ bestrew bestrew bestrid bestrid
+ bestride bestrid bestrides bestrid
+ bet bet betake betak
+ beteem beteem bethink bethink
+ bethought bethought bethrothed bethroth
+ bethump bethump betid betid
+ betide betid betideth betideth
+ betime betim betimes betim
+ betoken betoken betook betook
+ betossed betoss betray betrai
+ betrayed betrai betraying betrai
+ betrays betrai betrims betrim
+ betroth betroth betrothed betroth
+ betroths betroth bett bett
+ betted bet better better
+ bettered better bettering better
+ betters better betting bet
+ bettre bettr between between
+ betwixt betwixt bevel bevel
+ beverage beverag bevis bevi
+ bevy bevi bewail bewail
+ bewailed bewail bewailing bewail
+ bewails bewail beware bewar
+ bewasted bewast beweep beweep
+ bewept bewept bewet bewet
+ bewhored bewhor bewitch bewitch
+ bewitched bewitch bewitchment bewitch
+ bewray bewrai beyond beyond
+ bezonian bezonian bezonians bezonian
+ bianca bianca bianco bianco
+ bias bia bibble bibbl
+ bickerings bicker bid bid
+ bidden bidden bidding bid
+ biddings bid biddy biddi
+ bide bide bides bide
+ biding bide bids bid
+ bien bien bier bier
+ bifold bifold big big
+ bigamy bigami biggen biggen
+ bigger bigger bigness big
+ bigot bigot bilberry bilberri
+ bilbo bilbo bilboes bilbo
+ bilbow bilbow bill bill
+ billeted billet billets billet
+ billiards billiard billing bill
+ billow billow billows billow
+ bills bill bin bin
+ bind bind bindeth bindeth
+ binding bind binds bind
+ biondello biondello birch birch
+ bird bird birding bird
+ birdlime birdlim birds bird
+ birnam birnam birth birth
+ birthday birthdai birthdom birthdom
+ birthplace birthplac birthright birthright
+ birthrights birthright births birth
+ bis bi biscuit biscuit
+ bishop bishop bishops bishop
+ bisson bisson bit bit
+ bitch bitch bite bite
+ biter biter bites bite
+ biting bite bits bit
+ bitt bitt bitten bitten
+ bitter bitter bitterest bitterest
+ bitterly bitterli bitterness bitter
+ blab blab blabb blabb
+ blabbing blab blabs blab
+ black black blackamoor blackamoor
+ blackamoors blackamoor blackberries blackberri
+ blackberry blackberri blacker blacker
+ blackest blackest blackfriars blackfriar
+ blackheath blackheath blackmere blackmer
+ blackness black blacks black
+ bladder bladder bladders bladder
+ blade blade bladed blade
+ blades blade blains blain
+ blam blam blame blame
+ blamed blame blameful blame
+ blameless blameless blames blame
+ blanc blanc blanca blanca
+ blanch blanch blank blank
+ blanket blanket blanks blank
+ blaspheme blasphem blaspheming blasphem
+ blasphemous blasphem blasphemy blasphemi
+ blast blast blasted blast
+ blasting blast blastments blastment
+ blasts blast blaz blaz
+ blaze blaze blazes blaze
+ blazing blaze blazon blazon
+ blazoned blazon blazoning blazon
+ bleach bleach bleaching bleach
+ bleak bleak blear blear
+ bleared blear bleat bleat
+ bleated bleat bleats bleat
+ bled bled bleed bleed
+ bleedest bleedest bleedeth bleedeth
+ bleeding bleed bleeds bleed
+ blemish blemish blemishes blemish
+ blench blench blenches blench
+ blend blend blended blend
+ blent blent bless bless
+ blessed bless blessedly blessedli
+ blessedness blessed blesses bless
+ blesseth blesseth blessing bless
+ blessings bless blest blest
+ blew blew blind blind
+ blinded blind blindfold blindfold
+ blinding blind blindly blindli
+ blindness blind blinds blind
+ blink blink blinking blink
+ bliss bliss blist blist
+ blister blister blisters blister
+ blithe blith blithild blithild
+ bloat bloat block block
+ blockish blockish blocks block
+ blois bloi blood blood
+ blooded blood bloodhound bloodhound
+ bloodied bloodi bloodier bloodier
+ bloodiest bloodiest bloodily bloodili
+ bloodless bloodless bloods blood
+ bloodshed bloodsh bloodshedding bloodshed
+ bloodstained bloodstain bloody bloodi
+ bloom bloom blooms bloom
+ blossom blossom blossoming blossom
+ blossoms blossom blot blot
+ blots blot blotted blot
+ blotting blot blount blount
+ blow blow blowed blow
+ blowers blower blowest blowest
+ blowing blow blown blown
+ blows blow blowse blows
+ blubb blubb blubber blubber
+ blubbering blubber blue blue
+ bluecaps bluecap bluest bluest
+ blunt blunt blunted blunt
+ blunter blunter bluntest bluntest
+ blunting blunt bluntly bluntli
+ bluntness blunt blunts blunt
+ blur blur blurr blurr
+ blurs blur blush blush
+ blushes blush blushest blushest
+ blushing blush blust blust
+ bluster bluster blusterer bluster
+ blusters bluster bo bo
+ boar boar board board
+ boarded board boarding board
+ boards board boarish boarish
+ boars boar boast boast
+ boasted boast boastful boast
+ boasting boast boasts boast
+ boat boat boats boat
+ boatswain boatswain bob bob
+ bobb bobb boblibindo boblibindo
+ bobtail bobtail bocchus bocchu
+ bode bode boded bode
+ bodements bodement bodes bode
+ bodg bodg bodied bodi
+ bodies bodi bodiless bodiless
+ bodily bodili boding bode
+ bodkin bodkin body bodi
+ bodykins bodykin bog bog
+ boggle boggl boggler boggler
+ bogs bog bohemia bohemia
+ bohemian bohemian bohun bohun
+ boil boil boiling boil
+ boils boil boist boist
+ boisterous boister boisterously boister
+ boitier boitier bold bold
+ bolden bolden bolder bolder
+ boldest boldest boldly boldli
+ boldness bold bolds bold
+ bolingbroke bolingbrok bolster bolster
+ bolt bolt bolted bolt
+ bolter bolter bolters bolter
+ bolting bolt bolts bolt
+ bombard bombard bombards bombard
+ bombast bombast bon bon
+ bona bona bond bond
+ bondage bondag bonded bond
+ bondmaid bondmaid bondman bondman
+ bondmen bondmen bonds bond
+ bondslave bondslav bone bone
+ boneless boneless bones bone
+ bonfire bonfir bonfires bonfir
+ bonjour bonjour bonne bonn
+ bonnet bonnet bonneted bonnet
+ bonny bonni bonos bono
+ bonto bonto bonville bonvil
+ bood bood book book
+ bookish bookish books book
+ boon boon boor boor
+ boorish boorish boors boor
+ boot boot booted boot
+ booties booti bootless bootless
+ boots boot booty booti
+ bor bor bora bora
+ borachio borachio bordeaux bordeaux
+ border border bordered border
+ borderers border borders border
+ bore bore boreas borea
+ bores bore boring bore
+ born born borne born
+ borough borough boroughs borough
+ borrow borrow borrowed borrow
+ borrower borrow borrowing borrow
+ borrows borrow bosko bosko
+ boskos bosko bosky boski
+ bosom bosom bosoms bosom
+ boson boson boss boss
+ bosworth bosworth botch botch
+ botcher botcher botches botch
+ botchy botchi both both
+ bots bot bottle bottl
+ bottled bottl bottles bottl
+ bottom bottom bottomless bottomless
+ bottoms bottom bouciqualt bouciqualt
+ bouge boug bough bough
+ boughs bough bought bought
+ bounce bounc bouncing bounc
+ bound bound bounded bound
+ bounden bounden boundeth boundeth
+ bounding bound boundless boundless
+ bounds bound bounteous bounteou
+ bounteously bounteous bounties bounti
+ bountiful bounti bountifully bountifulli
+ bounty bounti bourbier bourbier
+ bourbon bourbon bourchier bourchier
+ bourdeaux bourdeaux bourn bourn
+ bout bout bouts bout
+ bove bove bow bow
+ bowcase bowcas bowed bow
+ bowels bowel bower bower
+ bowing bow bowl bowl
+ bowler bowler bowling bowl
+ bowls bowl bows bow
+ bowsprit bowsprit bowstring bowstr
+ box box boxes box
+ boy boi boyet boyet
+ boyish boyish boys boi
+ brabant brabant brabantio brabantio
+ brabble brabbl brabbler brabbler
+ brac brac brace brace
+ bracelet bracelet bracelets bracelet
+ brach brach bracy braci
+ brag brag bragg bragg
+ braggardism braggard braggards braggard
+ braggart braggart braggarts braggart
+ bragged brag bragging brag
+ bragless bragless brags brag
+ braid braid braided braid
+ brain brain brained brain
+ brainford brainford brainish brainish
+ brainless brainless brains brain
+ brainsick brainsick brainsickly brainsickli
+ brake brake brakenbury brakenburi
+ brakes brake brambles brambl
+ bran bran branch branch
+ branches branch branchless branchless
+ brand brand branded brand
+ brandish brandish brandon brandon
+ brands brand bras bra
+ brass brass brassy brassi
+ brat brat brats brat
+ brav brav brave brave
+ braved brave bravely brave
+ braver braver bravery braveri
+ braves brave bravest bravest
+ braving brave brawl brawl
+ brawler brawler brawling brawl
+ brawls brawl brawn brawn
+ brawns brawn bray brai
+ braying brai braz braz
+ brazen brazen brazier brazier
+ breach breach breaches breach
+ bread bread breadth breadth
+ break break breaker breaker
+ breakfast breakfast breaking break
+ breaks break breast breast
+ breasted breast breasting breast
+ breastplate breastplat breasts breast
+ breath breath breathe breath
+ breathed breath breather breather
+ breathers breather breathes breath
+ breathest breathest breathing breath
+ breathless breathless breaths breath
+ brecknock brecknock bred bred
+ breech breech breeches breech
+ breeching breech breed breed
+ breeder breeder breeders breeder
+ breeding breed breeds breed
+ breese brees breeze breez
+ breff breff bretagne bretagn
+ brethen brethen bretheren bretheren
+ brethren brethren brevis brevi
+ brevity breviti brew brew
+ brewage brewag brewer brewer
+ brewers brewer brewing brew
+ brews brew briareus briareu
+ briars briar brib brib
+ bribe bribe briber briber
+ bribes bribe brick brick
+ bricklayer bricklay bricks brick
+ bridal bridal bride bride
+ bridegroom bridegroom bridegrooms bridegroom
+ brides bride bridge bridg
+ bridgenorth bridgenorth bridges bridg
+ bridget bridget bridle bridl
+ bridled bridl brief brief
+ briefer briefer briefest briefest
+ briefly briefli briefness brief
+ brier brier briers brier
+ brigandine brigandin bright bright
+ brighten brighten brightest brightest
+ brightly brightli brightness bright
+ brim brim brimful brim
+ brims brim brimstone brimston
+ brinded brind brine brine
+ bring bring bringer bringer
+ bringeth bringeth bringing bring
+ bringings bring brings bring
+ brinish brinish brink brink
+ brisk brisk brisky briski
+ bristle bristl bristled bristl
+ bristly bristli bristol bristol
+ bristow bristow britain britain
+ britaine britain britaines britain
+ british british briton briton
+ britons briton brittany brittani
+ brittle brittl broach broach
+ broached broach broad broad
+ broader broader broadsides broadsid
+ brocas broca brock brock
+ brogues brogu broil broil
+ broiling broil broils broil
+ broke broke broken broken
+ brokenly brokenli broker broker
+ brokers broker brokes broke
+ broking broke brooch brooch
+ brooches brooch brood brood
+ brooded brood brooding brood
+ brook brook brooks brook
+ broom broom broomstaff broomstaff
+ broth broth brothel brothel
+ brother brother brotherhood brotherhood
+ brotherhoods brotherhood brotherly brotherli
+ brothers brother broths broth
+ brought brought brow brow
+ brown brown browner browner
+ brownist brownist browny browni
+ brows brow browse brows
+ browsing brows bruis brui
+ bruise bruis bruised bruis
+ bruises bruis bruising bruis
+ bruit bruit bruited bruit
+ brundusium brundusium brunt brunt
+ brush brush brushes brush
+ brute brute brutish brutish
+ brutus brutu bubble bubbl
+ bubbles bubbl bubbling bubbl
+ bubukles bubukl buck buck
+ bucket bucket buckets bucket
+ bucking buck buckingham buckingham
+ buckle buckl buckled buckl
+ buckler buckler bucklers buckler
+ bucklersbury bucklersburi buckles buckl
+ buckram buckram bucks buck
+ bud bud budded bud
+ budding bud budge budg
+ budger budger budget budget
+ buds bud buff buff
+ buffet buffet buffeting buffet
+ buffets buffet bug bug
+ bugbear bugbear bugle bugl
+ bugs bug build build
+ builded build buildeth buildeth
+ building build buildings build
+ builds build built built
+ bulk bulk bulks bulk
+ bull bull bullcalf bullcalf
+ bullen bullen bullens bullen
+ bullet bullet bullets bullet
+ bullocks bullock bulls bull
+ bully bulli bulmer bulmer
+ bulwark bulwark bulwarks bulwark
+ bum bum bumbast bumbast
+ bump bump bumper bumper
+ bums bum bunch bunch
+ bunches bunch bundle bundl
+ bung bung bunghole bunghol
+ bungle bungl bunting bunt
+ buoy buoi bur bur
+ burbolt burbolt burd burd
+ burden burden burdened burden
+ burdening burden burdenous burden
+ burdens burden burgh burgh
+ burgher burgher burghers burgher
+ burglary burglari burgomasters burgomast
+ burgonet burgonet burgundy burgundi
+ burial burial buried buri
+ burier burier buriest buriest
+ burly burli burn burn
+ burned burn burnet burnet
+ burneth burneth burning burn
+ burnish burnish burns burn
+ burnt burnt burr burr
+ burrows burrow burs bur
+ burst burst bursting burst
+ bursts burst burthen burthen
+ burthens burthen burton burton
+ bury buri burying buri
+ bush bush bushels bushel
+ bushes bush bushy bushi
+ busied busi busily busili
+ busines busin business busi
+ businesses busi buskin buskin
+ busky buski buss buss
+ busses buss bussing buss
+ bustle bustl bustling bustl
+ busy busi but but
+ butcheed butche butcher butcher
+ butchered butcher butcheries butcheri
+ butcherly butcherli butchers butcher
+ butchery butcheri butler butler
+ butt butt butter butter
+ buttered butter butterflies butterfli
+ butterfly butterfli butterwoman butterwoman
+ buttery butteri buttock buttock
+ buttocks buttock button button
+ buttonhole buttonhol buttons button
+ buttress buttress buttry buttri
+ butts butt buxom buxom
+ buy bui buyer buyer
+ buying bui buys bui
+ buzz buzz buzzard buzzard
+ buzzards buzzard buzzers buzzer
+ buzzing buzz by by
+ bye bye byzantium byzantium
+ c c ca ca
+ cabbage cabbag cabileros cabilero
+ cabin cabin cabins cabin
+ cable cabl cables cabl
+ cackling cackl cacodemon cacodemon
+ caddis caddi caddisses caddiss
+ cade cade cadence cadenc
+ cadent cadent cades cade
+ cadmus cadmu caduceus caduceu
+ cadwal cadwal cadwallader cadwallad
+ caelius caeliu caelo caelo
+ caesar caesar caesarion caesarion
+ caesars caesar cage cage
+ caged cage cagion cagion
+ cain cain caithness caith
+ caitiff caitiff caitiffs caitiff
+ caius caiu cak cak
+ cake cake cakes cake
+ calaber calab calais calai
+ calamities calam calamity calam
+ calchas calcha calculate calcul
+ calen calen calendar calendar
+ calendars calendar calf calf
+ caliban caliban calibans caliban
+ calipolis calipoli cality caliti
+ caliver caliv call call
+ callat callat called call
+ callet callet calling call
+ calls call calm calm
+ calmest calmest calmly calmli
+ calmness calm calms calm
+ calpurnia calpurnia calumniate calumni
+ calumniating calumni calumnious calumni
+ calumny calumni calve calv
+ calved calv calves calv
+ calveskins calveskin calydon calydon
+ cam cam cambio cambio
+ cambria cambria cambric cambric
+ cambrics cambric cambridge cambridg
+ cambyses cambys came came
+ camel camel camelot camelot
+ camels camel camest camest
+ camillo camillo camlet camlet
+ camomile camomil camp camp
+ campeius campeiu camping camp
+ camps camp can can
+ canakin canakin canaries canari
+ canary canari cancel cancel
+ cancell cancel cancelled cancel
+ cancelling cancel cancels cancel
+ cancer cancer candidatus candidatu
+ candied candi candle candl
+ candles candl candlesticks candlestick
+ candy candi canidius canidiu
+ cank cank canker canker
+ cankerblossom cankerblossom cankers canker
+ cannibally cannib cannibals cannib
+ cannon cannon cannoneer cannon
+ cannons cannon cannot cannot
+ canon canon canoniz canoniz
+ canonize canon canonized canon
+ canons canon canopied canopi
+ canopies canopi canopy canopi
+ canst canst canstick canstick
+ canterbury canterburi cantle cantl
+ cantons canton canus canu
+ canvas canva canvass canvass
+ canzonet canzonet cap cap
+ capability capabl capable capabl
+ capacities capac capacity capac
+ caparison caparison capdv capdv
+ cape cape capel capel
+ capels capel caper caper
+ capers caper capet capet
+ caphis caphi capilet capilet
+ capitaine capitain capital capit
+ capite capit capitol capitol
+ capitulate capitul capocchia capocchia
+ capon capon capons capon
+ capp capp cappadocia cappadocia
+ capriccio capriccio capricious caprici
+ caps cap capt capt
+ captain captain captains captain
+ captainship captainship captious captiou
+ captivate captiv captivated captiv
+ captivates captiv captive captiv
+ captives captiv captivity captiv
+ captum captum capucius capuciu
+ capulet capulet capulets capulet
+ car car carack carack
+ caracks carack carat carat
+ caraways carawai carbonado carbonado
+ carbuncle carbuncl carbuncled carbuncl
+ carbuncles carbuncl carcanet carcanet
+ carcase carcas carcases carcas
+ carcass carcass carcasses carcass
+ card card cardecue cardecu
+ carded card carders carder
+ cardinal cardin cardinally cardin
+ cardinals cardin cardmaker cardmak
+ cards card carduus carduu
+ care care cared care
+ career career careers career
+ careful care carefully carefulli
+ careless careless carelessly carelessli
+ carelessness careless cares care
+ caret caret cargo cargo
+ carl carl carlisle carlisl
+ carlot carlot carman carman
+ carmen carmen carnal carnal
+ carnally carnal carnarvonshire carnarvonshir
+ carnation carnat carnations carnat
+ carol carol carous carou
+ carouse carous caroused carous
+ carouses carous carousing carous
+ carp carp carpenter carpent
+ carper carper carpet carpet
+ carpets carpet carping carp
+ carriage carriag carriages carriag
+ carried carri carrier carrier
+ carriers carrier carries carri
+ carrion carrion carrions carrion
+ carry carri carrying carri
+ cars car cart cart
+ carters carter carthage carthag
+ carts cart carv carv
+ carve carv carved carv
+ carver carver carves carv
+ carving carv cas ca
+ casa casa casaer casaer
+ casca casca case case
+ casement casement casements casement
+ cases case cash cash
+ cashier cashier casing case
+ cask cask casket casket
+ casketed casket caskets casket
+ casque casqu casques casqu
+ cassado cassado cassandra cassandra
+ cassibelan cassibelan cassio cassio
+ cassius cassiu cassocks cassock
+ cast cast castalion castalion
+ castaway castawai castaways castawai
+ casted cast caster caster
+ castigate castig castigation castig
+ castile castil castiliano castiliano
+ casting cast castle castl
+ castles castl casts cast
+ casual casual casually casual
+ casualties casualti casualty casualti
+ cat cat cataian cataian
+ catalogue catalogu cataplasm cataplasm
+ cataracts cataract catarrhs catarrh
+ catastrophe catastroph catch catch
+ catcher catcher catches catch
+ catching catch cate cate
+ catechising catechis catechism catech
+ catechize catech cater cater
+ caterpillars caterpillar caters cater
+ caterwauling caterwaul cates cate
+ catesby catesbi cathedral cathedr
+ catlike catlik catling catl
+ catlings catl cato cato
+ cats cat cattle cattl
+ caucasus caucasu caudle caudl
+ cauf cauf caught caught
+ cauldron cauldron caus cau
+ cause caus caused caus
+ causeless causeless causer causer
+ causes caus causest causest
+ causeth causeth cautel cautel
+ cautelous cautel cautels cautel
+ cauterizing cauter caution caution
+ cautions caution cavaleiro cavaleiro
+ cavalery cavaleri cavaliers cavali
+ cave cave cavern cavern
+ caverns cavern caves cave
+ caveto caveto caviary caviari
+ cavil cavil cavilling cavil
+ cawdor cawdor cawdron cawdron
+ cawing caw ce ce
+ ceas cea cease ceas
+ ceases ceas ceaseth ceaseth
+ cedar cedar cedars cedar
+ cedius cediu celebrate celebr
+ celebrated celebr celebrates celebr
+ celebration celebr celerity celer
+ celestial celesti celia celia
+ cell cell cellar cellar
+ cellarage cellarag celsa celsa
+ cement cement censer censer
+ censor censor censorinus censorinu
+ censur censur censure censur
+ censured censur censurers censur
+ censures censur censuring censur
+ centaur centaur centaurs centaur
+ centre centr cents cent
+ centuries centuri centurion centurion
+ centurions centurion century centuri
+ cerberus cerberu cerecloth cerecloth
+ cerements cerement ceremonial ceremoni
+ ceremonies ceremoni ceremonious ceremoni
+ ceremoniously ceremoni ceremony ceremoni
+ ceres cere cerns cern
+ certain certain certainer certain
+ certainly certainli certainties certainti
+ certainty certainti certes cert
+ certificate certif certified certifi
+ certifies certifi certify certifi
+ ces ce cesario cesario
+ cess cess cesse cess
+ cestern cestern cetera cetera
+ cette cett chaces chace
+ chaf chaf chafe chafe
+ chafed chafe chafes chafe
+ chaff chaff chaffless chaffless
+ chafing chafe chain chain
+ chains chain chair chair
+ chairs chair chalic chalic
+ chalice chalic chalices chalic
+ chalk chalk chalks chalk
+ chalky chalki challeng challeng
+ challenge challeng challenged challeng
+ challenger challeng challengers challeng
+ challenges challeng cham cham
+ chamber chamber chamberers chamber
+ chamberlain chamberlain chamberlains chamberlain
+ chambermaid chambermaid chambermaids chambermaid
+ chambers chamber chameleon chameleon
+ champ champ champagne champagn
+ champain champain champains champain
+ champion champion champions champion
+ chanc chanc chance chanc
+ chanced chanc chancellor chancellor
+ chances chanc chandler chandler
+ chang chang change chang
+ changeable changeabl changed chang
+ changeful chang changeling changel
+ changelings changel changer changer
+ changes chang changest changest
+ changing chang channel channel
+ channels channel chanson chanson
+ chant chant chanticleer chanticl
+ chanting chant chantries chantri
+ chantry chantri chants chant
+ chaos chao chap chap
+ chape chape chapel chapel
+ chapeless chapeless chapels chapel
+ chaplain chaplain chaplains chaplain
+ chapless chapless chaplet chaplet
+ chapmen chapmen chaps chap
+ chapter chapter character charact
+ charactered charact characterless characterless
+ characters charact charactery characteri
+ characts charact charbon charbon
+ chare chare chares chare
+ charg charg charge charg
+ charged charg chargeful charg
+ charges charg chargeth chargeth
+ charging charg chariest chariest
+ chariness chari charing chare
+ chariot chariot chariots chariot
+ charitable charit charitably charit
+ charities chariti charity chariti
+ charlemain charlemain charles charl
+ charm charm charmed charm
+ charmer charmer charmeth charmeth
+ charmian charmian charming charm
+ charmingly charmingli charms charm
+ charneco charneco charnel charnel
+ charolois charoloi charon charon
+ charter charter charters charter
+ chartreux chartreux chary chari
+ charybdis charybdi chas cha
+ chase chase chased chase
+ chaser chaser chaseth chaseth
+ chasing chase chaste chast
+ chastely chast chastis chasti
+ chastise chastis chastised chastis
+ chastisement chastis chastity chastiti
+ chat chat chatham chatham
+ chatillon chatillon chats chat
+ chatt chatt chattels chattel
+ chatter chatter chattering chatter
+ chattles chattl chaud chaud
+ chaunted chaunt chaw chaw
+ chawdron chawdron che che
+ cheap cheap cheapen cheapen
+ cheaper cheaper cheapest cheapest
+ cheaply cheapli cheapside cheapsid
+ cheat cheat cheated cheat
+ cheater cheater cheaters cheater
+ cheating cheat cheats cheat
+ check check checked check
+ checker checker checking check
+ checks check cheek cheek
+ cheeks cheek cheer cheer
+ cheered cheer cheerer cheerer
+ cheerful cheer cheerfully cheerfulli
+ cheering cheer cheerless cheerless
+ cheerly cheerli cheers cheer
+ cheese chees chequer chequer
+ cher cher cherish cherish
+ cherished cherish cherisher cherish
+ cherishes cherish cherishing cherish
+ cherries cherri cherry cherri
+ cherrypit cherrypit chertsey chertsei
+ cherub cherub cherubims cherubim
+ cherubin cherubin cherubins cherubin
+ cheshu cheshu chess chess
+ chest chest chester chester
+ chestnut chestnut chestnuts chestnut
+ chests chest chetas cheta
+ chev chev cheval cheval
+ chevalier chevali chevaliers chevali
+ cheveril cheveril chew chew
+ chewed chew chewet chewet
+ chewing chew chez chez
+ chi chi chick chick
+ chicken chicken chickens chicken
+ chicurmurco chicurmurco chid chid
+ chidden chidden chide chide
+ chiders chider chides chide
+ chiding chide chief chief
+ chiefest chiefest chiefly chiefli
+ chien chien child child
+ childed child childeric childer
+ childhood childhood childhoods childhood
+ childing child childish childish
+ childishness childish childlike childlik
+ childness child children children
+ chill chill chilling chill
+ chime chime chimes chime
+ chimney chimnei chimneypiece chimneypiec
+ chimneys chimnei chimurcho chimurcho
+ chin chin china china
+ chine chine chines chine
+ chink chink chinks chink
+ chins chin chipp chipp
+ chipper chipper chips chip
+ chiron chiron chirping chirp
+ chirrah chirrah chirurgeonly chirurgeonli
+ chisel chisel chitopher chitoph
+ chivalrous chivalr chivalry chivalri
+ choice choic choicely choic
+ choicest choicest choir choir
+ choirs choir chok chok
+ choke choke choked choke
+ chokes choke choking choke
+ choler choler choleric choler
+ cholers choler chollors chollor
+ choose choos chooser chooser
+ chooses choos chooseth chooseth
+ choosing choos chop chop
+ chopine chopin choplogic choplog
+ chopp chopp chopped chop
+ chopping chop choppy choppi
+ chops chop chopt chopt
+ chor chor choristers chorist
+ chorus choru chose chose
+ chosen chosen chough chough
+ choughs chough chrish chrish
+ christ christ christen christen
+ christendom christendom christendoms christendom
+ christening christen christenings christen
+ christian christian christianlike christianlik
+ christians christian christmas christma
+ christom christom christopher christoph
+ christophero christophero chronicle chronicl
+ chronicled chronicl chronicler chronicl
+ chroniclers chronicl chronicles chronicl
+ chrysolite chrysolit chuck chuck
+ chucks chuck chud chud
+ chuffs chuff church church
+ churches church churchman churchman
+ churchmen churchmen churchyard churchyard
+ churchyards churchyard churl churl
+ churlish churlish churlishly churlishli
+ churls churl churn churn
+ chus chu cicatrice cicatric
+ cicatrices cicatric cicely cice
+ cicero cicero ciceter cicet
+ ciel ciel ciitzens ciitzen
+ cilicia cilicia cimber cimber
+ cimmerian cimmerian cinable cinabl
+ cincture cinctur cinders cinder
+ cine cine cinna cinna
+ cinque cinqu cipher cipher
+ ciphers cipher circa circa
+ circe circ circle circl
+ circled circl circlets circlet
+ circling circl circuit circuit
+ circum circum circumcised circumcis
+ circumference circumfer circummur circummur
+ circumscrib circumscrib circumscribed circumscrib
+ circumscription circumscript circumspect circumspect
+ circumstance circumst circumstanced circumstanc
+ circumstances circumst circumstantial circumstanti
+ circumvent circumv circumvention circumvent
+ cistern cistern citadel citadel
+ cital cital cite cite
+ cited cite cites cite
+ cities citi citing cite
+ citizen citizen citizens citizen
+ cittern cittern city citi
+ civet civet civil civil
+ civility civil civilly civilli
+ clack clack clad clad
+ claim claim claiming claim
+ claims claim clamb clamb
+ clamber clamber clammer clammer
+ clamor clamor clamorous clamor
+ clamors clamor clamour clamour
+ clamours clamour clang clang
+ clangor clangor clap clap
+ clapp clapp clapped clap
+ clapper clapper clapping clap
+ claps clap clare clare
+ clarence clarenc claret claret
+ claribel claribel clasp clasp
+ clasps clasp clatter clatter
+ claud claud claudio claudio
+ claudius claudiu clause claus
+ claw claw clawed claw
+ clawing claw claws claw
+ clay clai clays clai
+ clean clean cleanliest cleanliest
+ cleanly cleanli cleans clean
+ cleanse cleans cleansing cleans
+ clear clear clearer clearer
+ clearest clearest clearly clearli
+ clearness clear clears clear
+ cleave cleav cleaving cleav
+ clef clef cleft cleft
+ cleitus cleitu clemency clemenc
+ clement clement cleomenes cleomen
+ cleopatpa cleopatpa cleopatra cleopatra
+ clepeth clepeth clept clept
+ clerestories clerestori clergy clergi
+ clergyman clergyman clergymen clergymen
+ clerk clerk clerkly clerkli
+ clerks clerk clew clew
+ client client clients client
+ cliff cliff clifford clifford
+ cliffords clifford cliffs cliff
+ clifton clifton climate climat
+ climature climatur climb climb
+ climbed climb climber climber
+ climbeth climbeth climbing climb
+ climbs climb clime clime
+ cling cling clink clink
+ clinking clink clinquant clinquant
+ clip clip clipp clipp
+ clipper clipper clippeth clippeth
+ clipping clip clipt clipt
+ clitus clitu clo clo
+ cloak cloak cloakbag cloakbag
+ cloaks cloak clock clock
+ clocks clock clod clod
+ cloddy cloddi clodpole clodpol
+ clog clog clogging clog
+ clogs clog cloister cloister
+ cloistress cloistress cloquence cloquenc
+ clos clo close close
+ closed close closely close
+ closeness close closer closer
+ closes close closest closest
+ closet closet closing close
+ closure closur cloten cloten
+ clotens cloten cloth cloth
+ clothair clothair clotharius clothariu
+ clothe cloth clothes cloth
+ clothier clothier clothiers clothier
+ clothing cloth cloths cloth
+ clotpoles clotpol clotpoll clotpol
+ cloud cloud clouded cloud
+ cloudiness cloudi clouds cloud
+ cloudy cloudi clout clout
+ clouted clout clouts clout
+ cloven cloven clover clover
+ cloves clove clovest clovest
+ clowder clowder clown clown
+ clownish clownish clowns clown
+ cloy cloi cloyed cloi
+ cloying cloi cloyless cloyless
+ cloyment cloyment cloys cloi
+ club club clubs club
+ cluck cluck clung clung
+ clust clust clusters cluster
+ clutch clutch clyster clyster
+ cneius cneiu cnemies cnemi
+ co co coach coach
+ coaches coach coachmakers coachmak
+ coact coact coactive coactiv
+ coagulate coagul coal coal
+ coals coal coarse coars
+ coarsely coars coast coast
+ coasting coast coasts coast
+ coat coat coated coat
+ coats coat cobble cobbl
+ cobbled cobbl cobbler cobbler
+ cobham cobham cobloaf cobloaf
+ cobweb cobweb cobwebs cobweb
+ cock cock cockatrice cockatric
+ cockatrices cockatric cockle cockl
+ cockled cockl cockney cocknei
+ cockpit cockpit cocks cock
+ cocksure cocksur coctus coctu
+ cocytus cocytu cod cod
+ codding cod codling codl
+ codpiece codpiec codpieces codpiec
+ cods cod coelestibus coelestibu
+ coesar coesar coeur coeur
+ coffer coffer coffers coffer
+ coffin coffin coffins coffin
+ cog cog cogging cog
+ cogitation cogit cogitations cogit
+ cognition cognit cognizance cogniz
+ cogscomb cogscomb cohabitants cohabit
+ coher coher cohere coher
+ coherence coher coherent coher
+ cohorts cohort coif coif
+ coign coign coil coil
+ coin coin coinage coinag
+ coiner coiner coining coin
+ coins coin col col
+ colbrand colbrand colchos colcho
+ cold cold colder colder
+ coldest coldest coldly coldli
+ coldness cold coldspur coldspur
+ colebrook colebrook colic colic
+ collar collar collars collar
+ collateral collater colleagued colleagu
+ collect collect collected collect
+ collection collect college colleg
+ colleges colleg collied colli
+ collier collier colliers collier
+ collop collop collusion collus
+ colme colm colmekill colmekil
+ coloquintida coloquintida color color
+ colors color colossus colossu
+ colour colour colourable colour
+ coloured colour colouring colour
+ colours colour colt colt
+ colted colt colts colt
+ columbine columbin columbines columbin
+ colville colvil com com
+ comagene comagen comart comart
+ comb comb combat combat
+ combatant combat combatants combat
+ combated combat combating combat
+ combin combin combinate combin
+ combination combin combine combin
+ combined combin combless combless
+ combustion combust come come
+ comedian comedian comedians comedian
+ comedy comedi comeliness comeli
+ comely come comer comer
+ comers comer comes come
+ comest comest comet comet
+ cometh cometh comets comet
+ comfect comfect comfit comfit
+ comfits comfit comfort comfort
+ comfortable comfort comforted comfort
+ comforter comfort comforting comfort
+ comfortless comfortless comforts comfort
+ comic comic comical comic
+ coming come comings come
+ cominius cominiu comma comma
+ command command commande command
+ commanded command commander command
+ commanders command commanding command
+ commandment command commandments command
+ commands command comme comm
+ commenc commenc commence commenc
+ commenced commenc commencement commenc
+ commences commenc commencing commenc
+ commend commend commendable commend
+ commendation commend commendations commend
+ commended commend commending commend
+ commends commend comment comment
+ commentaries commentari commenting comment
+ comments comment commerce commerc
+ commingled commingl commiseration commiser
+ commission commiss commissioners commission
+ commissions commiss commit commit
+ commits commit committ committ
+ committed commit committing commit
+ commix commix commixed commix
+ commixtion commixt commixture commixtur
+ commodious commodi commodities commod
+ commodity commod common common
+ commonalty commonalti commoner common
+ commoners common commonly commonli
+ commons common commonweal commonw
+ commonwealth commonwealth commotion commot
+ commotions commot commune commun
+ communicat communicat communicate commun
+ communication commun communities commun
+ community commun comonty comonti
+ compact compact companies compani
+ companion companion companions companion
+ companionship companionship company compani
+ compar compar comparative compar
+ compare compar compared compar
+ comparing compar comparison comparison
+ comparisons comparison compartner compartn
+ compass compass compasses compass
+ compassing compass compassion compass
+ compassionate compassion compeers compeer
+ compel compel compell compel
+ compelled compel compelling compel
+ compels compel compensation compens
+ competence compet competency compet
+ competent compet competitor competitor
+ competitors competitor compil compil
+ compile compil compiled compil
+ complain complain complainer complain
+ complainest complainest complaining complain
+ complainings complain complains complain
+ complaint complaint complaints complaint
+ complement complement complements complement
+ complete complet complexion complexion
+ complexioned complexion complexions complexion
+ complices complic complies compli
+ compliment compliment complimental compliment
+ compliments compliment complot complot
+ complots complot complotted complot
+ comply compli compos compo
+ compose compos composed compos
+ composition composit compost compost
+ composture compostur composure composur
+ compound compound compounded compound
+ compounds compound comprehend comprehend
+ comprehended comprehend comprehends comprehend
+ compremises compremis compris compri
+ comprising compris compromis compromi
+ compromise compromis compt compt
+ comptible comptibl comptrollers comptrol
+ compulsatory compulsatori compulsion compuls
+ compulsive compuls compunctious compuncti
+ computation comput comrade comrad
+ comrades comrad comutual comutu
+ con con concave concav
+ concavities concav conceal conceal
+ concealed conceal concealing conceal
+ concealment conceal concealments conceal
+ conceals conceal conceit conceit
+ conceited conceit conceitless conceitless
+ conceits conceit conceiv conceiv
+ conceive conceiv conceived conceiv
+ conceives conceiv conceiving conceiv
+ conception concept conceptions concept
+ conceptious concepti concern concern
+ concernancy concern concerneth concerneth
+ concerning concern concernings concern
+ concerns concern conclave conclav
+ conclud conclud conclude conclud
+ concluded conclud concludes conclud
+ concluding conclud conclusion conclus
+ conclusions conclus concolinel concolinel
+ concord concord concubine concubin
+ concupiscible concupisc concupy concupi
+ concur concur concurring concur
+ concurs concur condemn condemn
+ condemnation condemn condemned condemn
+ condemning condemn condemns condemn
+ condescend condescend condign condign
+ condition condit conditionally condition
+ conditions condit condole condol
+ condolement condol condoling condol
+ conduce conduc conduct conduct
+ conducted conduct conducting conduct
+ conductor conductor conduit conduit
+ conduits conduit conected conect
+ coney conei confection confect
+ confectionary confectionari confections confect
+ confederacy confederaci confederate confeder
+ confederates confeder confer confer
+ conference confer conferr conferr
+ conferring confer confess confess
+ confessed confess confesses confess
+ confesseth confesseth confessing confess
+ confession confess confessions confess
+ confessor confessor confidence confid
+ confident confid confidently confid
+ confin confin confine confin
+ confined confin confineless confineless
+ confiners confin confines confin
+ confining confin confirm confirm
+ confirmation confirm confirmations confirm
+ confirmed confirm confirmer confirm
+ confirmers confirm confirming confirm
+ confirmities confirm confirms confirm
+ confiscate confisc confiscated confisc
+ confiscation confisc confixed confix
+ conflict conflict conflicting conflict
+ conflicts conflict confluence confluenc
+ conflux conflux conform conform
+ conformable conform confound confound
+ confounded confound confounding confound
+ confounds confound confront confront
+ confronted confront confus confu
+ confused confus confusedly confusedli
+ confusion confus confusions confus
+ confutation confut confutes confut
+ congeal congeal congealed congeal
+ congealment congeal congee conge
+ conger conger congest congest
+ congied congi congratulate congratul
+ congreeing congre congreeted congreet
+ congregate congreg congregated congreg
+ congregation congreg congregations congreg
+ congruent congruent congruing congru
+ conies coni conjectural conjectur
+ conjecture conjectur conjectures conjectur
+ conjoin conjoin conjoined conjoin
+ conjoins conjoin conjointly conjointli
+ conjunct conjunct conjunction conjunct
+ conjunctive conjunct conjur conjur
+ conjuration conjur conjurations conjur
+ conjure conjur conjured conjur
+ conjurer conjur conjurers conjur
+ conjures conjur conjuring conjur
+ conjuro conjuro conn conn
+ connected connect connive conniv
+ conqu conqu conquer conquer
+ conquered conquer conquering conquer
+ conqueror conqueror conquerors conqueror
+ conquers conquer conquest conquest
+ conquests conquest conquring conqur
+ conrade conrad cons con
+ consanguineous consanguin consanguinity consanguin
+ conscienc conscienc conscience conscienc
+ consciences conscienc conscionable conscion
+ consecrate consecr consecrated consecr
+ consecrations consecr consent consent
+ consented consent consenting consent
+ consents consent consequence consequ
+ consequences consequ consequently consequ
+ conserve conserv conserved conserv
+ conserves conserv consider consid
+ considerance consider considerate consider
+ consideration consider considerations consider
+ considered consid considering consid
+ considerings consid considers consid
+ consign consign consigning consign
+ consist consist consisteth consisteth
+ consisting consist consistory consistori
+ consists consist consolate consol
+ consolation consol consonancy conson
+ consonant conson consort consort
+ consorted consort consortest consortest
+ conspectuities conspectu conspir conspir
+ conspiracy conspiraci conspirant conspir
+ conspirator conspir conspirators conspir
+ conspire conspir conspired conspir
+ conspirers conspir conspires conspir
+ conspiring conspir constable constabl
+ constables constabl constance constanc
+ constancies constanc constancy constanc
+ constant constant constantine constantin
+ constantinople constantinopl constantly constantli
+ constellation constel constitution constitut
+ constrain constrain constrained constrain
+ constraineth constraineth constrains constrain
+ constraint constraint constring constr
+ construction construct construe constru
+ consul consul consuls consul
+ consulship consulship consulships consulship
+ consult consult consulting consult
+ consults consult consum consum
+ consume consum consumed consum
+ consumes consum consuming consum
+ consummate consumm consummation consumm
+ consumption consumpt consumptions consumpt
+ contagion contagion contagious contagi
+ contain contain containing contain
+ contains contain contaminate contamin
+ contaminated contamin contemn contemn
+ contemned contemn contemning contemn
+ contemns contemn contemplate contempl
+ contemplation contempl contemplative contempl
+ contempt contempt contemptible contempt
+ contempts contempt contemptuous contemptu
+ contemptuously contemptu contend contend
+ contended contend contending contend
+ contendon contendon content content
+ contenta contenta contented content
+ contenteth contenteth contention content
+ contentious contenti contentless contentless
+ contento contento contents content
+ contest contest contestation contest
+ continence contin continency contin
+ continent contin continents contin
+ continu continu continual continu
+ continually continu continuance continu
+ continuantly continuantli continuate continu
+ continue continu continued continu
+ continuer continu continues continu
+ continuing continu contract contract
+ contracted contract contracting contract
+ contraction contract contradict contradict
+ contradicted contradict contradiction contradict
+ contradicts contradict contraries contrari
+ contrarieties contrarieti contrariety contrarieti
+ contrarious contrari contrariously contrari
+ contrary contrari contre contr
+ contribution contribut contributors contributor
+ contrite contrit contriv contriv
+ contrive contriv contrived contriv
+ contriver contriv contrives contriv
+ contriving contriv control control
+ controll control controller control
+ controlling control controlment control
+ controls control controversy controversi
+ contumelious contumeli contumeliously contumeli
+ contumely contum contusions contus
+ convenience conveni conveniences conveni
+ conveniency conveni convenient conveni
+ conveniently conveni convented convent
+ conventicles conventicl convents convent
+ convers conver conversant convers
+ conversation convers conversations convers
+ converse convers conversed convers
+ converses convers conversing convers
+ conversion convers convert convert
+ converted convert convertest convertest
+ converting convert convertite convertit
+ convertites convertit converts convert
+ convey convei conveyance convey
+ conveyances convey conveyers convey
+ conveying convei convict convict
+ convicted convict convince convinc
+ convinced convinc convinces convinc
+ convive conviv convocation convoc
+ convoy convoi convulsions convuls
+ cony coni cook cook
+ cookery cookeri cooks cook
+ cool cool cooled cool
+ cooling cool cools cool
+ coop coop coops coop
+ cop cop copatain copatain
+ cope cope cophetua cophetua
+ copied copi copies copi
+ copious copiou copper copper
+ copperspur copperspur coppice coppic
+ copulation copul copulatives copul
+ copy copi cor cor
+ coragio coragio coral coral
+ coram coram corambus corambu
+ coranto coranto corantos coranto
+ corbo corbo cord cord
+ corded cord cordelia cordelia
+ cordial cordial cordis cordi
+ cords cord core core
+ corin corin corinth corinth
+ corinthian corinthian coriolanus coriolanu
+ corioli corioli cork cork
+ corky corki cormorant cormor
+ corn corn cornelia cornelia
+ cornelius corneliu corner corner
+ corners corner cornerstone cornerston
+ cornets cornet cornish cornish
+ corns corn cornuto cornuto
+ cornwall cornwal corollary corollari
+ coronal coron coronation coron
+ coronet coronet coronets coronet
+ corporal corpor corporals corpor
+ corporate corpor corpse corps
+ corpulent corpul correct correct
+ corrected correct correcting correct
+ correction correct correctioner correction
+ corrects correct correspondence correspond
+ correspondent correspond corresponding correspond
+ corresponsive correspons corrigible corrig
+ corrival corriv corrivals corriv
+ corroborate corrobor corrosive corros
+ corrupt corrupt corrupted corrupt
+ corrupter corrupt corrupters corrupt
+ corruptible corrupt corruptibly corrupt
+ corrupting corrupt corruption corrupt
+ corruptly corruptli corrupts corrupt
+ corse cors corses cors
+ corslet corslet cosmo cosmo
+ cost cost costard costard
+ costermongers costermong costlier costlier
+ costly costli costs cost
+ cot cot cote cote
+ coted cote cotsall cotsal
+ cotsole cotsol cotswold cotswold
+ cottage cottag cottages cottag
+ cotus cotu couch couch
+ couched couch couching couch
+ couchings couch coude coud
+ cough cough coughing cough
+ could could couldst couldst
+ coulter coulter council council
+ councillor councillor councils council
+ counsel counsel counsell counsel
+ counsellor counsellor counsellors counsellor
+ counselor counselor counselors counselor
+ counsels counsel count count
+ counted count countenanc countenanc
+ countenance counten countenances counten
+ counter counter counterchange counterchang
+ countercheck countercheck counterfeit counterfeit
+ counterfeited counterfeit counterfeiting counterfeit
+ counterfeitly counterfeitli counterfeits counterfeit
+ countermand countermand countermands countermand
+ countermines countermin counterpart counterpart
+ counterpoints counterpoint counterpois counterpoi
+ counterpoise counterpois counters counter
+ countervail countervail countess countess
+ countesses countess counties counti
+ counting count countless countless
+ countries countri countrv countrv
+ country countri countryman countryman
+ countrymen countrymen counts count
+ county counti couper couper
+ couple coupl coupled coupl
+ couplement couplement couples coupl
+ couplet couplet couplets couplet
+ cour cour courage courag
+ courageous courag courageously courag
+ courages courag courier courier
+ couriers courier couronne couronn
+ cours cour course cours
+ coursed cours courser courser
+ coursers courser courses cours
+ coursing cours court court
+ courted court courteous courteou
+ courteously courteous courtesan courtesan
+ courtesies courtesi courtesy courtesi
+ courtezan courtezan courtezans courtezan
+ courtier courtier courtiers courtier
+ courtlike courtlik courtly courtli
+ courtney courtnei courts court
+ courtship courtship cousin cousin
+ cousins cousin couterfeit couterfeit
+ coutume coutum covenant coven
+ covenants coven covent covent
+ coventry coventri cover cover
+ covered cover covering cover
+ coverlet coverlet covers cover
+ covert covert covertly covertli
+ coverture covertur covet covet
+ coveted covet coveting covet
+ covetings covet covetous covet
+ covetously covet covetousness covet
+ covets covet cow cow
+ coward coward cowarded coward
+ cowardice cowardic cowardly cowardli
+ cowards coward cowardship cowardship
+ cowish cowish cowl cowl
+ cowslip cowslip cowslips cowslip
+ cox cox coxcomb coxcomb
+ coxcombs coxcomb coy coi
+ coystrill coystril coz coz
+ cozen cozen cozenage cozenag
+ cozened cozen cozener cozen
+ cozeners cozen cozening cozen
+ coziers cozier crab crab
+ crabbed crab crabs crab
+ crack crack cracked crack
+ cracker cracker crackers cracker
+ cracking crack cracks crack
+ cradle cradl cradled cradl
+ cradles cradl craft craft
+ crafted craft craftied crafti
+ craftier craftier craftily craftili
+ crafts craft craftsmen craftsmen
+ crafty crafti cram cram
+ cramm cramm cramp cramp
+ cramps cramp crams cram
+ cranking crank cranks crank
+ cranmer cranmer crannied cranni
+ crannies cranni cranny cranni
+ crants crant crare crare
+ crash crash crassus crassu
+ crav crav crave crave
+ craved crave craven craven
+ cravens craven craves crave
+ craveth craveth craving crave
+ crawl crawl crawling crawl
+ crawls crawl craz craz
+ crazed craze crazy crazi
+ creaking creak cream cream
+ create creat created creat
+ creates creat creating creat
+ creation creation creator creator
+ creature creatur creatures creatur
+ credence credenc credent credent
+ credible credibl credit credit
+ creditor creditor creditors creditor
+ credo credo credulity credul
+ credulous credul creed creed
+ creek creek creeks creek
+ creep creep creeping creep
+ creeps creep crept crept
+ crescent crescent crescive cresciv
+ cressets cresset cressid cressid
+ cressida cressida cressids cressid
+ cressy cressi crest crest
+ crested crest crestfall crestfal
+ crestless crestless crests crest
+ cretan cretan crete crete
+ crevice crevic crew crew
+ crews crew crib crib
+ cribb cribb cribs crib
+ cricket cricket crickets cricket
+ cried cri criedst criedst
+ crier crier cries cri
+ criest criest crieth crieth
+ crime crime crimeful crime
+ crimeless crimeless crimes crime
+ criminal crimin crimson crimson
+ cringe cring cripple crippl
+ crisp crisp crisped crisp
+ crispian crispian crispianus crispianu
+ crispin crispin critic critic
+ critical critic critics critic
+ croak croak croaking croak
+ croaks croak crocodile crocodil
+ cromer cromer cromwell cromwel
+ crone crone crook crook
+ crookback crookback crooked crook
+ crooking crook crop crop
+ cropp cropp crosby crosbi
+ cross cross crossed cross
+ crosses cross crossest crossest
+ crossing cross crossings cross
+ crossly crossli crossness cross
+ crost crost crotchets crotchet
+ crouch crouch crouching crouch
+ crow crow crowd crowd
+ crowded crowd crowding crowd
+ crowds crowd crowflowers crowflow
+ crowing crow crowkeeper crowkeep
+ crown crown crowned crown
+ crowner crowner crownet crownet
+ crownets crownet crowning crown
+ crowns crown crows crow
+ crudy crudi cruel cruel
+ cruell cruell crueller crueller
+ cruelly cruelli cruels cruel
+ cruelty cruelti crum crum
+ crumble crumbl crumbs crumb
+ crupper crupper crusadoes crusado
+ crush crush crushed crush
+ crushest crushest crushing crush
+ crust crust crusts crust
+ crusty crusti crutch crutch
+ crutches crutch cry cry
+ crying cry crystal crystal
+ crystalline crystallin crystals crystal
+ cub cub cubbert cubbert
+ cubiculo cubiculo cubit cubit
+ cubs cub cuckold cuckold
+ cuckoldly cuckoldli cuckolds cuckold
+ cuckoo cuckoo cucullus cucullu
+ cudgel cudgel cudgeled cudgel
+ cudgell cudgel cudgelling cudgel
+ cudgels cudgel cue cue
+ cues cue cuff cuff
+ cuffs cuff cuique cuiqu
+ cull cull culling cull
+ cullion cullion cullionly cullionli
+ cullions cullion culpable culpabl
+ culverin culverin cum cum
+ cumber cumber cumberland cumberland
+ cunning cun cunningly cunningli
+ cunnings cun cuore cuor
+ cup cup cupbearer cupbear
+ cupboarding cupboard cupid cupid
+ cupids cupid cuppele cuppel
+ cups cup cur cur
+ curan curan curate curat
+ curb curb curbed curb
+ curbing curb curbs curb
+ curd curd curdied curdi
+ curds curd cure cure
+ cured cure cureless cureless
+ curer curer cures cure
+ curfew curfew curing cure
+ curio curio curiosity curios
+ curious curiou curiously curious
+ curl curl curled curl
+ curling curl curls curl
+ currance curranc currants currant
+ current current currents current
+ currish currish curry curri
+ curs cur curse curs
+ cursed curs curses curs
+ cursies cursi cursing curs
+ cursorary cursorari curst curst
+ curster curster curstest curstest
+ curstness curst cursy cursi
+ curtail curtail curtain curtain
+ curtains curtain curtal curtal
+ curtis curti curtle curtl
+ curtsied curtsi curtsies curtsi
+ curtsy curtsi curvet curvet
+ curvets curvet cushes cush
+ cushion cushion cushions cushion
+ custalorum custalorum custard custard
+ custody custodi custom custom
+ customary customari customed custom
+ customer custom customers custom
+ customs custom custure custur
+ cut cut cutler cutler
+ cutpurse cutpurs cutpurses cutpurs
+ cuts cut cutter cutter
+ cutting cut cuttle cuttl
+ cxsar cxsar cyclops cyclop
+ cydnus cydnu cygnet cygnet
+ cygnets cygnet cym cym
+ cymbals cymbal cymbeline cymbelin
+ cyme cyme cynic cynic
+ cynthia cynthia cypress cypress
+ cypriot cypriot cyprus cypru
+ cyrus cyru cytherea cytherea
+ d d dabbled dabbl
+ dace dace dad dad
+ daedalus daedalu daemon daemon
+ daff daff daffed daf
+ daffest daffest daffodils daffodil
+ dagger dagger daggers dagger
+ dagonet dagonet daily daili
+ daintier daintier dainties dainti
+ daintiest daintiest daintily daintili
+ daintiness dainti daintry daintri
+ dainty dainti daisied daisi
+ daisies daisi daisy daisi
+ dale dale dalliance dallianc
+ dallied dalli dallies dalli
+ dally dalli dallying dalli
+ dalmatians dalmatian dam dam
+ damage damag damascus damascu
+ damask damask damasked damask
+ dame dame dames dame
+ damm damm damn damn
+ damnable damnabl damnably damnabl
+ damnation damnat damned damn
+ damns damn damoiselle damoisel
+ damon damon damosella damosella
+ damp damp dams dam
+ damsel damsel damsons damson
+ dan dan danc danc
+ dance danc dancer dancer
+ dances danc dancing danc
+ dandle dandl dandy dandi
+ dane dane dang dang
+ danger danger dangerous danger
+ dangerously danger dangers danger
+ dangling dangl daniel daniel
+ danish danish dank dank
+ dankish dankish danskers dansker
+ daphne daphn dappled dappl
+ dapples dappl dar dar
+ dardan dardan dardanian dardanian
+ dardanius dardaniu dare dare
+ dared dare dareful dare
+ dares dare darest darest
+ daring dare darius dariu
+ dark dark darken darken
+ darkening darken darkens darken
+ darker darker darkest darkest
+ darkling darkl darkly darkli
+ darkness dark darling darl
+ darlings darl darnel darnel
+ darraign darraign dart dart
+ darted dart darter darter
+ dartford dartford darting dart
+ darts dart dash dash
+ dashes dash dashing dash
+ dastard dastard dastards dastard
+ dat dat datchet datchet
+ date date dated date
+ dateless dateless dates date
+ daub daub daughter daughter
+ daughters daughter daunt daunt
+ daunted daunt dauntless dauntless
+ dauphin dauphin daventry daventri
+ davy davi daw daw
+ dawn dawn dawning dawn
+ daws daw day dai
+ daylight daylight days dai
+ dazzle dazzl dazzled dazzl
+ dazzling dazzl de de
+ dead dead deadly deadli
+ deaf deaf deafing deaf
+ deafness deaf deafs deaf
+ deal deal dealer dealer
+ dealers dealer dealest dealest
+ dealing deal dealings deal
+ deals deal dealt dealt
+ dean dean deanery deaneri
+ dear dear dearer dearer
+ dearest dearest dearly dearli
+ dearness dear dears dear
+ dearth dearth dearths dearth
+ death death deathbed deathb
+ deathful death deaths death
+ deathsman deathsman deathsmen deathsmen
+ debarred debar debase debas
+ debate debat debated debat
+ debatement debat debateth debateth
+ debating debat debauch debauch
+ debile debil debility debil
+ debitor debitor debonair debonair
+ deborah deborah debosh debosh
+ debt debt debted debt
+ debtor debtor debtors debtor
+ debts debt debuty debuti
+ decay decai decayed decai
+ decayer decay decaying decai
+ decays decai deceas decea
+ decease deceas deceased deceas
+ deceit deceit deceitful deceit
+ deceits deceit deceiv deceiv
+ deceivable deceiv deceive deceiv
+ deceived deceiv deceiver deceiv
+ deceivers deceiv deceives deceiv
+ deceivest deceivest deceiveth deceiveth
+ deceiving deceiv december decemb
+ decent decent deceptious decepti
+ decerns decern decide decid
+ decides decid decimation decim
+ decipher deciph deciphers deciph
+ decision decis decius deciu
+ deck deck decking deck
+ decks deck deckt deckt
+ declare declar declares declar
+ declension declens declensions declens
+ declin declin decline declin
+ declined declin declines declin
+ declining declin decoct decoct
+ decorum decorum decreas decrea
+ decrease decreas decreasing decreas
+ decree decre decreed decre
+ decrees decre decrepit decrepit
+ dedicate dedic dedicated dedic
+ dedicates dedic dedication dedic
+ deed deed deedless deedless
+ deeds deed deem deem
+ deemed deem deep deep
+ deeper deeper deepest deepest
+ deeply deepli deeps deep
+ deepvow deepvow deer deer
+ deesse deess defac defac
+ deface defac defaced defac
+ defacer defac defacers defac
+ defacing defac defam defam
+ default default defeat defeat
+ defeated defeat defeats defeat
+ defeatures defeatur defect defect
+ defective defect defects defect
+ defence defenc defences defenc
+ defend defend defendant defend
+ defended defend defender defend
+ defenders defend defending defend
+ defends defend defense defens
+ defensible defens defensive defens
+ defer defer deferr deferr
+ defiance defianc deficient defici
+ defied defi defies defi
+ defil defil defile defil
+ defiler defil defiles defil
+ defiling defil define defin
+ definement defin definite definit
+ definitive definit definitively definit
+ deflow deflow deflower deflow
+ deflowered deflow deform deform
+ deformed deform deformities deform
+ deformity deform deftly deftli
+ defunct defunct defunction defunct
+ defuse defus defy defi
+ defying defi degenerate degener
+ degraded degrad degree degre
+ degrees degre deified deifi
+ deifying deifi deign deign
+ deigned deign deiphobus deiphobu
+ deities deiti deity deiti
+ deja deja deject deject
+ dejected deject delabreth delabreth
+ delay delai delayed delai
+ delaying delai delays delai
+ delectable delect deliberate deliber
+ delicate delic delicates delic
+ delicious delici deliciousness delici
+ delight delight delighted delight
+ delightful delight delights delight
+ delinquents delinqu deliv deliv
+ deliver deliv deliverance deliver
+ delivered deliv delivering deliv
+ delivers deliv delivery deliveri
+ delphos delpho deluded delud
+ deluding delud deluge delug
+ delve delv delver delver
+ delves delv demand demand
+ demanded demand demanding demand
+ demands demand demean demean
+ demeanor demeanor demeanour demeanour
+ demerits demerit demesnes demesn
+ demetrius demetriu demi demi
+ demigod demigod demise demis
+ demoiselles demoisel demon demon
+ demonstrable demonstr demonstrate demonstr
+ demonstrated demonstr demonstrating demonstr
+ demonstration demonstr demonstrative demonstr
+ demure demur demurely demur
+ demuring demur den den
+ denay denai deni deni
+ denial denial denials denial
+ denied deni denier denier
+ denies deni deniest deniest
+ denis deni denmark denmark
+ dennis denni denny denni
+ denote denot denoted denot
+ denotement denot denounc denounc
+ denounce denounc denouncing denounc
+ dens den denunciation denunci
+ deny deni denying deni
+ deo deo depart depart
+ departed depart departest departest
+ departing depart departure departur
+ depeche depech depend depend
+ dependant depend dependants depend
+ depended depend dependence depend
+ dependences depend dependency depend
+ dependent depend dependents depend
+ depender depend depending depend
+ depends depend deplore deplor
+ deploring deplor depopulate depopul
+ depos depo depose depos
+ deposed depos deposing depos
+ depositaries depositari deprav deprav
+ depravation deprav deprave deprav
+ depraved deprav depraves deprav
+ depress depress depriv depriv
+ deprive depriv depth depth
+ depths depth deputation deput
+ depute deput deputed deput
+ deputies deputi deputing deput
+ deputy deputi deracinate deracin
+ derby derbi dercetas derceta
+ dere dere derides derid
+ derision deris deriv deriv
+ derivation deriv derivative deriv
+ derive deriv derived deriv
+ derives deriv derogate derog
+ derogately derog derogation derog
+ des de desartless desartless
+ descant descant descend descend
+ descended descend descending descend
+ descends descend descension descens
+ descent descent descents descent
+ describe describ described describ
+ describes describ descried descri
+ description descript descriptions descript
+ descry descri desdemon desdemon
+ desdemona desdemona desert desert
+ deserts desert deserv deserv
+ deserve deserv deserved deserv
+ deservedly deservedli deserver deserv
+ deservers deserv deserves deserv
+ deservest deservest deserving deserv
+ deservings deserv design design
+ designment design designments design
+ designs design desir desir
+ desire desir desired desir
+ desirers desir desires desir
+ desirest desirest desiring desir
+ desirous desir desist desist
+ desk desk desolate desol
+ desolation desol desp desp
+ despair despair despairing despair
+ despairs despair despatch despatch
+ desperate desper desperately desper
+ desperation desper despis despi
+ despise despis despised despis
+ despiser despis despiseth despiseth
+ despising despis despite despit
+ despiteful despit despoiled despoil
+ dest dest destin destin
+ destined destin destinies destini
+ destiny destini destitute destitut
+ destroy destroi destroyed destroi
+ destroyer destroy destroyers destroy
+ destroying destroi destroys destroi
+ destruction destruct destructions destruct
+ det det detain detain
+ detains detain detect detect
+ detected detect detecting detect
+ detection detect detector detector
+ detects detect detention detent
+ determin determin determinate determin
+ determination determin determinations determin
+ determine determin determined determin
+ determines determin detest detest
+ detestable detest detested detest
+ detesting detest detests detest
+ detract detract detraction detract
+ detractions detract deucalion deucalion
+ deuce deuc deum deum
+ deux deux devant devant
+ devesting devest device devic
+ devices devic devil devil
+ devilish devilish devils devil
+ devis devi devise devis
+ devised devis devises devis
+ devising devis devoid devoid
+ devonshire devonshir devote devot
+ devoted devot devotion devot
+ devour devour devoured devour
+ devourers devour devouring devour
+ devours devour devout devout
+ devoutly devoutli dew dew
+ dewberries dewberri dewdrops dewdrop
+ dewlap dewlap dewlapp dewlapp
+ dews dew dewy dewi
+ dexter dexter dexteriously dexteri
+ dexterity dexter di di
+ diable diabl diablo diablo
+ diadem diadem dial dial
+ dialect dialect dialogue dialogu
+ dialogued dialogu dials dial
+ diameter diamet diamond diamond
+ diamonds diamond dian dian
+ diana diana diaper diaper
+ dibble dibbl dic dic
+ dice dice dicers dicer
+ dich dich dick dick
+ dickens dicken dickon dickon
+ dicky dicki dictator dictat
+ diction diction dictynna dictynna
+ did did diddle diddl
+ didest didest dido dido
+ didst didst die die
+ died di diedst diedst
+ dies di diest diest
+ diet diet dieted diet
+ dieter dieter dieu dieu
+ diff diff differ differ
+ difference differ differences differ
+ differency differ different differ
+ differing differ differs differ
+ difficile difficil difficult difficult
+ difficulties difficulti difficulty difficulti
+ diffidence diffid diffidences diffid
+ diffus diffu diffused diffus
+ diffusest diffusest dig dig
+ digest digest digested digest
+ digestion digest digestions digest
+ digg digg digging dig
+ dighton dighton dignified dignifi
+ dignifies dignifi dignify dignifi
+ dignities digniti dignity digniti
+ digress digress digressing digress
+ digression digress digs dig
+ digt digt dilate dilat
+ dilated dilat dilations dilat
+ dilatory dilatori dild dild
+ dildos dildo dilemma dilemma
+ dilemmas dilemma diligence dilig
+ diligent dilig diluculo diluculo
+ dim dim dimension dimens
+ dimensions dimens diminish diminish
+ diminishing diminish diminution diminut
+ diminutive diminut diminutives diminut
+ dimm dimm dimmed dim
+ dimming dim dimpled dimpl
+ dimples dimpl dims dim
+ din din dine dine
+ dined dine diner diner
+ dines dine ding ding
+ dining dine dinner dinner
+ dinners dinner dinnertime dinnertim
+ dint dint diomed diom
+ diomede diomed diomedes diomed
+ dion dion dip dip
+ dipp dipp dipping dip
+ dips dip dir dir
+ dire dire direct direct
+ directed direct directing direct
+ direction direct directions direct
+ directitude directitud directive direct
+ directly directli directs direct
+ direful dire direness dire
+ direst direst dirge dirg
+ dirges dirg dirt dirt
+ dirty dirti dis di
+ disability disabl disable disabl
+ disabled disabl disabling disabl
+ disadvantage disadvantag disagree disagre
+ disallow disallow disanimates disanim
+ disannul disannul disannuls disannul
+ disappointed disappoint disarm disarm
+ disarmed disarm disarmeth disarmeth
+ disarms disarm disaster disast
+ disasters disast disastrous disastr
+ disbench disbench disbranch disbranch
+ disburdened disburden disburs disbur
+ disburse disburs disbursed disburs
+ discandy discandi discandying discandi
+ discard discard discarded discard
+ discase discas discased discas
+ discern discern discerner discern
+ discerning discern discernings discern
+ discerns discern discharg discharg
+ discharge discharg discharged discharg
+ discharging discharg discipled discipl
+ disciples discipl disciplin disciplin
+ discipline disciplin disciplined disciplin
+ disciplines disciplin disclaim disclaim
+ disclaiming disclaim disclaims disclaim
+ disclos disclo disclose disclos
+ disclosed disclos discloses disclos
+ discolour discolour discoloured discolour
+ discolours discolour discomfit discomfit
+ discomfited discomfit discomfiture discomfitur
+ discomfort discomfort discomfortable discomfort
+ discommend discommend disconsolate disconsol
+ discontent discont discontented discont
+ discontentedly discontentedli discontenting discont
+ discontents discont discontinue discontinu
+ discontinued discontinu discord discord
+ discordant discord discords discord
+ discourse discours discoursed discours
+ discourser discours discourses discours
+ discoursive discours discourtesy discourtesi
+ discov discov discover discov
+ discovered discov discoverers discover
+ discoveries discoveri discovering discov
+ discovers discov discovery discoveri
+ discredit discredit discredited discredit
+ discredits discredit discreet discreet
+ discreetly discreetli discretion discret
+ discretions discret discuss discuss
+ disdain disdain disdained disdain
+ disdaineth disdaineth disdainful disdain
+ disdainfully disdainfulli disdaining disdain
+ disdains disdain disdnguish disdnguish
+ diseas disea disease diseas
+ diseased diseas diseases diseas
+ disedg disedg disembark disembark
+ disfigure disfigur disfigured disfigur
+ disfurnish disfurnish disgorge disgorg
+ disgrac disgrac disgrace disgrac
+ disgraced disgrac disgraceful disgrac
+ disgraces disgrac disgracing disgrac
+ disgracious disgraci disguis disgui
+ disguise disguis disguised disguis
+ disguiser disguis disguises disguis
+ disguising disguis dish dish
+ dishabited dishabit dishclout dishclout
+ dishearten dishearten disheartens dishearten
+ dishes dish dishonest dishonest
+ dishonestly dishonestli dishonesty dishonesti
+ dishonor dishonor dishonorable dishonor
+ dishonors dishonor dishonour dishonour
+ dishonourable dishonour dishonoured dishonour
+ dishonours dishonour disinherit disinherit
+ disinherited disinherit disjoin disjoin
+ disjoining disjoin disjoins disjoin
+ disjoint disjoint disjunction disjunct
+ dislik dislik dislike dislik
+ disliken disliken dislikes dislik
+ dislimns dislimn dislocate disloc
+ dislodg dislodg disloyal disloy
+ disloyalty disloyalti dismal dismal
+ dismantle dismantl dismantled dismantl
+ dismask dismask dismay dismai
+ dismayed dismai dismemb dismemb
+ dismember dismemb dismes dism
+ dismiss dismiss dismissed dismiss
+ dismissing dismiss dismission dismiss
+ dismount dismount dismounted dismount
+ disnatur disnatur disobedience disobedi
+ disobedient disobedi disobey disobei
+ disobeys disobei disorb disorb
+ disorder disord disordered disord
+ disorderly disorderli disorders disord
+ disparage disparag disparagement disparag
+ disparagements disparag dispark dispark
+ dispatch dispatch dispensation dispens
+ dispense dispens dispenses dispens
+ dispers disper disperse dispers
+ dispersed dispers dispersedly dispersedli
+ dispersing dispers dispiteous dispit
+ displac displac displace displac
+ displaced displac displant displant
+ displanting displant display displai
+ displayed displai displeas displea
+ displease displeas displeased displeas
+ displeasing displeas displeasure displeasur
+ displeasures displeasur disponge dispong
+ disport disport disports disport
+ dispos dispo dispose dispos
+ disposed dispos disposer dispos
+ disposing dispos disposition disposit
+ dispositions disposit dispossess dispossess
+ dispossessing dispossess disprais disprai
+ dispraise disprais dispraising disprais
+ dispraisingly dispraisingli dispropertied disproperti
+ disproportion disproport disproportioned disproport
+ disprov disprov disprove disprov
+ disproved disprov dispursed dispurs
+ disputable disput disputation disput
+ disputations disput dispute disput
+ disputed disput disputes disput
+ disputing disput disquantity disquant
+ disquiet disquiet disquietly disquietli
+ disrelish disrelish disrobe disrob
+ disseat disseat dissemble dissembl
+ dissembled dissembl dissembler dissembl
+ dissemblers dissembl dissembling dissembl
+ dissembly dissembl dissension dissens
+ dissensions dissens dissentious dissenti
+ dissever dissev dissipation dissip
+ dissolute dissolut dissolutely dissolut
+ dissolution dissolut dissolutions dissolut
+ dissolv dissolv dissolve dissolv
+ dissolved dissolv dissolves dissolv
+ dissuade dissuad dissuaded dissuad
+ distaff distaff distaffs distaff
+ distain distain distains distain
+ distance distanc distant distant
+ distaste distast distasted distast
+ distasteful distast distemp distemp
+ distemper distemp distemperature distemperatur
+ distemperatures distemperatur distempered distemp
+ distempering distemp distil distil
+ distill distil distillation distil
+ distilled distil distills distil
+ distilment distil distinct distinct
+ distinction distinct distinctly distinctli
+ distingue distingu distinguish distinguish
+ distinguishes distinguish distinguishment distinguish
+ distract distract distracted distract
+ distractedly distractedli distraction distract
+ distractions distract distracts distract
+ distrain distrain distraught distraught
+ distress distress distressed distress
+ distresses distress distressful distress
+ distribute distribut distributed distribut
+ distribution distribut distrust distrust
+ distrustful distrust disturb disturb
+ disturbed disturb disturbers disturb
+ disturbing disturb disunite disunit
+ disvalued disvalu disvouch disvouch
+ dit dit ditch ditch
+ ditchers ditcher ditches ditch
+ dites dite ditties ditti
+ ditty ditti diurnal diurnal
+ div div dive dive
+ diver diver divers diver
+ diversely divers diversity divers
+ divert divert diverted divert
+ diverts divert dives dive
+ divest divest dividable divid
+ dividant divid divide divid
+ divided divid divides divid
+ divideth divideth divin divin
+ divination divin divine divin
+ divinely divin divineness divin
+ diviner divin divines divin
+ divinest divinest divining divin
+ divinity divin division divis
+ divisions divis divorc divorc
+ divorce divorc divorced divorc
+ divorcement divorc divorcing divorc
+ divulg divulg divulge divulg
+ divulged divulg divulging divulg
+ dizy dizi dizzy dizzi
+ do do doating doat
+ dobbin dobbin dock dock
+ docks dock doct doct
+ doctor doctor doctors doctor
+ doctrine doctrin document document
+ dodge dodg doe doe
+ doer doer doers doer
+ does doe doest doest
+ doff doff dog dog
+ dogberry dogberri dogfish dogfish
+ dogg dogg dogged dog
+ dogs dog doigts doigt
+ doing do doings do
+ doit doit doits doit
+ dolabella dolabella dole dole
+ doleful dole doll doll
+ dollar dollar dollars dollar
+ dolor dolor dolorous dolor
+ dolour dolour dolours dolour
+ dolphin dolphin dolt dolt
+ dolts dolt domestic domest
+ domestics domest dominance domin
+ dominations domin dominator domin
+ domine domin domineer domin
+ domineering domin dominical domin
+ dominion dominion dominions dominion
+ domitius domitiu dommelton dommelton
+ don don donalbain donalbain
+ donation donat donc donc
+ doncaster doncast done done
+ dong dong donn donn
+ donne donn donner donner
+ donnerai donnerai doom doom
+ doomsday doomsdai door door
+ doorkeeper doorkeep doors door
+ dorcas dorca doreus doreu
+ doricles doricl dormouse dormous
+ dorothy dorothi dorset dorset
+ dorsetshire dorsetshir dost dost
+ dotage dotag dotant dotant
+ dotard dotard dotards dotard
+ dote dote doted dote
+ doters doter dotes dote
+ doteth doteth doth doth
+ doting dote double doubl
+ doubled doubl doubleness doubl
+ doubler doubler doublet doublet
+ doublets doublet doubling doubl
+ doubly doubli doubt doubt
+ doubted doubt doubtful doubt
+ doubtfully doubtfulli doubting doubt
+ doubtless doubtless doubts doubt
+ doug doug dough dough
+ doughty doughti doughy doughi
+ douglas dougla dout dout
+ doute dout douts dout
+ dove dove dovehouse dovehous
+ dover dover doves dove
+ dow dow dowager dowag
+ dowdy dowdi dower dower
+ dowerless dowerless dowers dower
+ dowlas dowla dowle dowl
+ down down downfall downfal
+ downright downright downs down
+ downstairs downstair downtrod downtrod
+ downward downward downwards downward
+ downy downi dowries dowri
+ dowry dowri dowsabel dowsabel
+ doxy doxi dozed doze
+ dozen dozen dozens dozen
+ dozy dozi drab drab
+ drabbing drab drabs drab
+ drachma drachma drachmas drachma
+ draff draff drag drag
+ dragg dragg dragged drag
+ dragging drag dragon dragon
+ dragonish dragonish dragons dragon
+ drain drain drained drain
+ drains drain drake drake
+ dram dram dramatis dramati
+ drank drank draught draught
+ draughts draught drave drave
+ draw draw drawbridge drawbridg
+ drawer drawer drawers drawer
+ draweth draweth drawing draw
+ drawling drawl drawn drawn
+ draws draw drayman drayman
+ draymen draymen dread dread
+ dreaded dread dreadful dread
+ dreadfully dreadfulli dreading dread
+ dreads dread dream dream
+ dreamer dreamer dreamers dreamer
+ dreaming dream dreams dream
+ dreamt dreamt drearning drearn
+ dreary dreari dreg dreg
+ dregs dreg drench drench
+ drenched drench dress dress
+ dressed dress dresser dresser
+ dressing dress dressings dress
+ drest drest drew drew
+ dribbling dribbl dried dri
+ drier drier dries dri
+ drift drift drily drili
+ drink drink drinketh drinketh
+ drinking drink drinkings drink
+ drinks drink driv driv
+ drive drive drivelling drivel
+ driven driven drives drive
+ driveth driveth driving drive
+ drizzle drizzl drizzled drizzl
+ drizzles drizzl droit droit
+ drollery drolleri dromio dromio
+ dromios dromio drone drone
+ drones drone droop droop
+ droopeth droopeth drooping droop
+ droops droop drop drop
+ dropheir dropheir droplets droplet
+ dropp dropp dropper dropper
+ droppeth droppeth dropping drop
+ droppings drop drops drop
+ dropsied dropsi dropsies dropsi
+ dropsy dropsi dropt dropt
+ dross dross drossy drossi
+ drought drought drove drove
+ droven droven drovier drovier
+ drown drown drowned drown
+ drowning drown drowns drown
+ drows drow drowse drows
+ drowsily drowsili drowsiness drowsi
+ drowsy drowsi drudge drudg
+ drudgery drudgeri drudges drudg
+ drug drug drugg drugg
+ drugs drug drum drum
+ drumble drumbl drummer drummer
+ drumming drum drums drum
+ drunk drunk drunkard drunkard
+ drunkards drunkard drunken drunken
+ drunkenly drunkenli drunkenness drunken
+ dry dry dryness dryness
+ dst dst du du
+ dub dub dubb dubb
+ ducat ducat ducats ducat
+ ducdame ducdam duchess duchess
+ duchies duchi duchy duchi
+ duck duck ducking duck
+ ducks duck dudgeon dudgeon
+ due due duellist duellist
+ duello duello duer duer
+ dues due duff duff
+ dug dug dugs dug
+ duke duke dukedom dukedom
+ dukedoms dukedom dukes duke
+ dulcet dulcet dulche dulch
+ dull dull dullard dullard
+ duller duller dullest dullest
+ dulling dull dullness dull
+ dulls dull dully dulli
+ dulness dul duly duli
+ dumain dumain dumb dumb
+ dumbe dumb dumbly dumbl
+ dumbness dumb dump dump
+ dumps dump dun dun
+ duncan duncan dung dung
+ dungeon dungeon dungeons dungeon
+ dunghill dunghil dunghills dunghil
+ dungy dungi dunnest dunnest
+ dunsinane dunsinan dunsmore dunsmor
+ dunstable dunstabl dupp dupp
+ durance duranc during dure
+ durst durst dusky duski
+ dust dust dusted dust
+ dusty dusti dutch dutch
+ dutchman dutchman duteous duteou
+ duties duti dutiful duti
+ duty duti dwarf dwarf
+ dwarfish dwarfish dwell dwell
+ dwellers dweller dwelling dwell
+ dwells dwell dwelt dwelt
+ dwindle dwindl dy dy
+ dye dye dyed dy
+ dyer dyer dying dy
+ e e each each
+ eager eager eagerly eagerli
+ eagerness eager eagle eagl
+ eagles eagl eaning ean
+ eanlings eanl ear ear
+ earing ear earl earl
+ earldom earldom earlier earlier
+ earliest earliest earliness earli
+ earls earl early earli
+ earn earn earned earn
+ earnest earnest earnestly earnestli
+ earnestness earnest earns earn
+ ears ear earth earth
+ earthen earthen earthlier earthlier
+ earthly earthli earthquake earthquak
+ earthquakes earthquak earthy earthi
+ eas ea ease eas
+ eased eas easeful eas
+ eases eas easier easier
+ easiest easiest easiliest easiliest
+ easily easili easiness easi
+ easing eas east east
+ eastcheap eastcheap easter easter
+ eastern eastern eastward eastward
+ easy easi eat eat
+ eaten eaten eater eater
+ eaters eater eating eat
+ eats eat eaux eaux
+ eaves eav ebb ebb
+ ebbing eb ebbs ebb
+ ebon ebon ebony eboni
+ ebrew ebrew ecce ecc
+ echapper echapp echo echo
+ echoes echo eclips eclip
+ eclipse eclips eclipses eclips
+ ecolier ecoli ecoutez ecoutez
+ ecstacy ecstaci ecstasies ecstasi
+ ecstasy ecstasi ecus ecu
+ eden eden edg edg
+ edgar edgar edge edg
+ edged edg edgeless edgeless
+ edges edg edict edict
+ edicts edict edifice edific
+ edifices edific edified edifi
+ edifies edifi edition edit
+ edm edm edmund edmund
+ edmunds edmund edmundsbury edmundsburi
+ educate educ educated educ
+ education educ edward edward
+ eel eel eels eel
+ effect effect effected effect
+ effectless effectless effects effect
+ effectual effectu effectually effectu
+ effeminate effemin effigies effigi
+ effus effu effuse effus
+ effusion effus eftest eftest
+ egal egal egally egal
+ eget eget egeus egeu
+ egg egg eggs egg
+ eggshell eggshel eglamour eglamour
+ eglantine eglantin egma egma
+ ego ego egregious egregi
+ egregiously egregi egress egress
+ egypt egypt egyptian egyptian
+ egyptians egyptian eie eie
+ eight eight eighteen eighteen
+ eighth eighth eightpenny eightpenni
+ eighty eighti eisel eisel
+ either either eject eject
+ eke ek el el
+ elbe elb elbow elbow
+ elbows elbow eld eld
+ elder elder elders elder
+ eldest eldest eleanor eleanor
+ elect elect elected elect
+ election elect elegancy eleg
+ elegies elegi element element
+ elements element elephant eleph
+ elephants eleph elevated elev
+ eleven eleven eleventh eleventh
+ elf elf elflocks elflock
+ eliads eliad elinor elinor
+ elizabeth elizabeth ell ell
+ elle ell ellen ellen
+ elm elm eloquence eloqu
+ eloquent eloqu else els
+ elsewhere elsewher elsinore elsinor
+ eltham eltham elves elv
+ elvish elvish ely eli
+ elysium elysium em em
+ emballing embal embalm embalm
+ embalms embalm embark embark
+ embarked embark embarquements embarqu
+ embassade embassad embassage embassag
+ embassies embassi embassy embassi
+ embattailed embattail embattl embattl
+ embattle embattl embay embai
+ embellished embellish embers ember
+ emblaze emblaz emblem emblem
+ emblems emblem embodied embodi
+ embold embold emboldens embolden
+ emboss emboss embossed emboss
+ embounded embound embowel embowel
+ embowell embowel embrac embrac
+ embrace embrac embraced embrac
+ embracement embrac embracements embrac
+ embraces embrac embracing embrac
+ embrasures embrasur embroider embroid
+ embroidery embroideri emhracing emhrac
+ emilia emilia eminence emin
+ eminent emin eminently emin
+ emmanuel emmanuel emnity emniti
+ empale empal emperal emper
+ emperess emperess emperial emperi
+ emperor emperor empery emperi
+ emphasis emphasi empire empir
+ empirics empir empiricutic empiricut
+ empleached empleach employ emploi
+ employed emploi employer employ
+ employment employ employments employ
+ empoison empoison empress empress
+ emptied empti emptier emptier
+ empties empti emptiness empti
+ empty empti emptying empti
+ emulate emul emulation emul
+ emulations emul emulator emul
+ emulous emul en en
+ enact enact enacted enact
+ enacts enact enactures enactur
+ enamell enamel enamelled enamel
+ enamour enamour enamoured enamour
+ enanmour enanmour encamp encamp
+ encamped encamp encave encav
+ enceladus enceladu enchaf enchaf
+ enchafed enchaf enchant enchant
+ enchanted enchant enchanting enchant
+ enchantingly enchantingli enchantment enchant
+ enchantress enchantress enchants enchant
+ enchas encha encircle encircl
+ encircled encircl enclos enclo
+ enclose enclos enclosed enclos
+ encloses enclos encloseth encloseth
+ enclosing enclos enclouded encloud
+ encompass encompass encompassed encompass
+ encompasseth encompasseth encompassment encompass
+ encore encor encorporal encorpor
+ encount encount encounter encount
+ encountered encount encounters encount
+ encourage encourag encouraged encourag
+ encouragement encourag encrimsoned encrimson
+ encroaching encroach encumb encumb
+ end end endamage endamag
+ endamagement endamag endanger endang
+ endart endart endear endear
+ endeared endear endeavour endeavour
+ endeavours endeavour ended end
+ ender ender ending end
+ endings end endite endit
+ endless endless endow endow
+ endowed endow endowments endow
+ endows endow ends end
+ endu endu endue endu
+ endur endur endurance endur
+ endure endur endured endur
+ endures endur enduring endur
+ endymion endymion eneas enea
+ enemies enemi enemy enemi
+ enernies enerni enew enew
+ enfeebled enfeebl enfeebles enfeebl
+ enfeoff enfeoff enfetter enfett
+ enfoldings enfold enforc enforc
+ enforce enforc enforced enforc
+ enforcedly enforcedli enforcement enforc
+ enforces enforc enforcest enforcest
+ enfranched enfranch enfranchis enfranchi
+ enfranchise enfranchis enfranchised enfranchis
+ enfranchisement enfranchis enfreed enfre
+ enfreedoming enfreedom engag engag
+ engage engag engaged engag
+ engagements engag engaging engag
+ engaol engaol engend engend
+ engender engend engenders engend
+ engilds engild engine engin
+ engineer engin enginer engin
+ engines engin engirt engirt
+ england england english english
+ englishman englishman englishmen englishmen
+ engluts englut englutted englut
+ engraffed engraf engraft engraft
+ engrafted engraft engrav engrav
+ engrave engrav engross engross
+ engrossed engross engrossest engrossest
+ engrossing engross engrossments engross
+ enguard enguard enigma enigma
+ enigmatical enigmat enjoin enjoin
+ enjoined enjoin enjoy enjoi
+ enjoyed enjoi enjoyer enjoy
+ enjoying enjoi enjoys enjoi
+ enkindle enkindl enkindled enkindl
+ enlard enlard enlarg enlarg
+ enlarge enlarg enlarged enlarg
+ enlargement enlarg enlargeth enlargeth
+ enlighten enlighten enlink enlink
+ enmesh enmesh enmities enmiti
+ enmity enmiti ennoble ennobl
+ ennobled ennobl enobarb enobarb
+ enobarbus enobarbu enon enon
+ enormity enorm enormous enorm
+ enough enough enow enow
+ enpatron enpatron enpierced enpierc
+ enquir enquir enquire enquir
+ enquired enquir enrag enrag
+ enrage enrag enraged enrag
+ enrages enrag enrank enrank
+ enrapt enrapt enrich enrich
+ enriched enrich enriches enrich
+ enridged enridg enrings enr
+ enrob enrob enrobe enrob
+ enroll enrol enrolled enrol
+ enrooted enroot enrounded enround
+ enschedul enschedul ensconce ensconc
+ ensconcing ensconc enseamed enseam
+ ensear ensear enseigne enseign
+ enseignez enseignez ensemble ensembl
+ enshelter enshelt enshielded enshield
+ enshrines enshrin ensign ensign
+ ensigns ensign enskied enski
+ ensman ensman ensnare ensnar
+ ensnared ensnar ensnareth ensnareth
+ ensteep ensteep ensu ensu
+ ensue ensu ensued ensu
+ ensues ensu ensuing ensu
+ enswathed enswath ent ent
+ entail entail entame entam
+ entangled entangl entangles entangl
+ entendre entendr enter enter
+ entered enter entering enter
+ enterprise enterpris enterprises enterpris
+ enters enter entertain entertain
+ entertained entertain entertainer entertain
+ entertaining entertain entertainment entertain
+ entertainments entertain enthrall enthral
+ enthralled enthral enthron enthron
+ enthroned enthron entice entic
+ enticements entic enticing entic
+ entire entir entirely entir
+ entitle entitl entitled entitl
+ entitling entitl entomb entomb
+ entombed entomb entrails entrail
+ entrance entranc entrances entranc
+ entrap entrap entrapp entrapp
+ entre entr entreat entreat
+ entreated entreat entreaties entreati
+ entreating entreat entreatments entreat
+ entreats entreat entreaty entreati
+ entrench entrench entry entri
+ entwist entwist envelop envelop
+ envenom envenom envenomed envenom
+ envenoms envenom envied envi
+ envies envi envious enviou
+ enviously envious environ environ
+ environed environ envoy envoi
+ envy envi envying envi
+ enwheel enwheel enwombed enwomb
+ enwraps enwrap ephesian ephesian
+ ephesians ephesian ephesus ephesu
+ epicure epicur epicurean epicurean
+ epicures epicur epicurism epicur
+ epicurus epicuru epidamnum epidamnum
+ epidaurus epidauru epigram epigram
+ epilepsy epilepsi epileptic epilept
+ epilogue epilogu epilogues epilogu
+ epistles epistl epistrophus epistrophu
+ epitaph epitaph epitaphs epitaph
+ epithet epithet epitheton epitheton
+ epithets epithet epitome epitom
+ equal equal equalities equal
+ equality equal equall equal
+ equally equal equalness equal
+ equals equal equinoctial equinocti
+ equinox equinox equipage equipag
+ equity equiti equivocal equivoc
+ equivocate equivoc equivocates equivoc
+ equivocation equivoc equivocator equivoc
+ er er erbear erbear
+ erbearing erbear erbears erbear
+ erbeat erbeat erblows erblow
+ erboard erboard erborne erborn
+ ercame ercam ercast ercast
+ ercharg ercharg ercharged ercharg
+ ercharging ercharg ercles ercl
+ ercome ercom ercover ercov
+ ercrows ercrow erdoing erdo
+ ere er erebus erebu
+ erect erect erected erect
+ erecting erect erection erect
+ erects erect erewhile erewhil
+ erflourish erflourish erflow erflow
+ erflowing erflow erflows erflow
+ erfraught erfraught erga erga
+ ergalled ergal erglanced erglanc
+ ergo ergo ergone ergon
+ ergrow ergrow ergrown ergrown
+ ergrowth ergrowth erhang erhang
+ erhanging erhang erhasty erhasti
+ erhear erhear erheard erheard
+ eringoes eringo erjoy erjoi
+ erleap erleap erleaps erleap
+ erleavens erleaven erlook erlook
+ erlooking erlook ermaster ermast
+ ermengare ermengar ermount ermount
+ ern ern ernight ernight
+ eros ero erpaid erpaid
+ erparted erpart erpast erpast
+ erpays erpai erpeer erpeer
+ erperch erperch erpicturing erpictur
+ erpingham erpingham erposting erpost
+ erpow erpow erpress erpress
+ erpressed erpress err err
+ errand errand errands errand
+ errant errant errate errat
+ erraught erraught erreaches erreach
+ erred er errest errest
+ erring er erroneous erron
+ error error errors error
+ errs err errule errul
+ errun errun erset erset
+ ershade ershad ershades ershad
+ ershine ershin ershot ershot
+ ersized ersiz erskip erskip
+ erslips erslip erspreads erspread
+ erst erst erstare erstar
+ erstep erstep erstunk erstunk
+ ersway erswai ersways erswai
+ erswell erswel erta erta
+ ertake ertak erteemed erteem
+ erthrow erthrow erthrown erthrown
+ erthrows erthrow ertook ertook
+ ertop ertop ertopping ertop
+ ertrip ertrip erturn erturn
+ erudition erudit eruption erupt
+ eruptions erupt ervalues ervalu
+ erwalk erwalk erwatch erwatch
+ erween erween erweens erween
+ erweigh erweigh erweighs erweigh
+ erwhelm erwhelm erwhelmed erwhelm
+ erworn erworn es es
+ escalus escalu escap escap
+ escape escap escaped escap
+ escapes escap eschew eschew
+ escoted escot esill esil
+ especial especi especially especi
+ esperance esper espials espial
+ espied espi espies espi
+ espous espou espouse espous
+ espy espi esquire esquir
+ esquires esquir essay essai
+ essays essai essence essenc
+ essential essenti essentially essenti
+ esses ess essex essex
+ est est establish establish
+ established establish estate estat
+ estates estat esteem esteem
+ esteemed esteem esteemeth esteemeth
+ esteeming esteem esteems esteem
+ estimable estim estimate estim
+ estimation estim estimations estim
+ estime estim estranged estrang
+ estridge estridg estridges estridg
+ et et etc etc
+ etceteras etcetera ete et
+ eternal etern eternally etern
+ eterne etern eternity etern
+ eterniz eterniz etes et
+ ethiop ethiop ethiope ethiop
+ ethiopes ethiop ethiopian ethiopian
+ etna etna eton eton
+ etre etr eunuch eunuch
+ eunuchs eunuch euphrates euphrat
+ euphronius euphroniu euriphile euriphil
+ europa europa europe europ
+ ev ev evade evad
+ evades evad evans evan
+ evasion evas evasions evas
+ eve ev even even
+ evening even evenly evenli
+ event event eventful event
+ events event ever ever
+ everlasting everlast everlastingly everlastingli
+ evermore evermor every everi
+ everyone everyon everything everyth
+ everywhere everywher evidence evid
+ evidences evid evident evid
+ evil evil evilly evilli
+ evils evil evitate evit
+ ewe ew ewer ewer
+ ewers ewer ewes ew
+ exact exact exacted exact
+ exactest exactest exacting exact
+ exaction exact exactions exact
+ exactly exactli exacts exact
+ exalt exalt exalted exalt
+ examin examin examination examin
+ examinations examin examine examin
+ examined examin examines examin
+ exampl exampl example exampl
+ exampled exampl examples exampl
+ exasperate exasper exasperates exasper
+ exceed exce exceeded exceed
+ exceedeth exceedeth exceeding exceed
+ exceedingly exceedingli exceeds exce
+ excel excel excelled excel
+ excellence excel excellencies excel
+ excellency excel excellent excel
+ excellently excel excelling excel
+ excels excel except except
+ excepted except excepting except
+ exception except exceptions except
+ exceptless exceptless excess excess
+ excessive excess exchang exchang
+ exchange exchang exchanged exchang
+ exchequer exchequ exchequers exchequ
+ excite excit excited excit
+ excitements excit excites excit
+ exclaim exclaim exclaims exclaim
+ exclamation exclam exclamations exclam
+ excludes exclud excommunicate excommun
+ excommunication excommun excrement excrement
+ excrements excrement excursion excurs
+ excursions excurs excus excu
+ excusable excus excuse excus
+ excused excus excuses excus
+ excusez excusez excusing excus
+ execrable execr execrations execr
+ execute execut executed execut
+ executing execut execution execut
+ executioner execution executioners execution
+ executor executor executors executor
+ exempt exempt exempted exempt
+ exequies exequi exercise exercis
+ exercises exercis exeter exet
+ exeunt exeunt exhal exhal
+ exhalation exhal exhalations exhal
+ exhale exhal exhales exhal
+ exhaust exhaust exhibit exhibit
+ exhibiters exhibit exhibition exhibit
+ exhort exhort exhortation exhort
+ exigent exig exil exil
+ exile exil exiled exil
+ exion exion exist exist
+ exists exist exit exit
+ exits exit exorciser exorcis
+ exorcisms exorc exorcist exorcist
+ expect expect expectance expect
+ expectancy expect expectation expect
+ expectations expect expected expect
+ expecters expect expecting expect
+ expects expect expedience expedi
+ expedient expedi expediently expedi
+ expedition expedit expeditious expediti
+ expel expel expell expel
+ expelling expel expels expel
+ expend expend expense expens
+ expenses expens experienc experienc
+ experience experi experiences experi
+ experiment experi experimental experiment
+ experiments experi expert expert
+ expertness expert expiate expiat
+ expiation expiat expir expir
+ expiration expir expire expir
+ expired expir expires expir
+ expiring expir explication explic
+ exploit exploit exploits exploit
+ expos expo expose expos
+ exposing expos exposition exposit
+ expositor expositor expostulate expostul
+ expostulation expostul exposture expostur
+ exposure exposur expound expound
+ expounded expound express express
+ expressed express expresseth expresseth
+ expressing express expressive express
+ expressly expressli expressure expressur
+ expuls expul expulsion expuls
+ exquisite exquisit exsufflicate exsuffl
+ extant extant extemporal extempor
+ extemporally extempor extempore extempor
+ extend extend extended extend
+ extends extend extent extent
+ extenuate extenu extenuated extenu
+ extenuates extenu extenuation extenu
+ exterior exterior exteriorly exteriorli
+ exteriors exterior extermin extermin
+ extern extern external extern
+ extinct extinct extincted extinct
+ extincture extinctur extinguish extinguish
+ extirp extirp extirpate extirp
+ extirped extirp extol extol
+ extoll extol extolment extol
+ exton exton extort extort
+ extorted extort extortion extort
+ extortions extort extra extra
+ extract extract extracted extract
+ extracting extract extraordinarily extraordinarili
+ extraordinary extraordinari extraught extraught
+ extravagancy extravag extravagant extravag
+ extreme extrem extremely extrem
+ extremes extrem extremest extremest
+ extremities extrem extremity extrem
+ exuent exuent exult exult
+ exultation exult ey ey
+ eyas eya eyases eyas
+ eye ey eyeball eyebal
+ eyeballs eyebal eyebrow eyebrow
+ eyebrows eyebrow eyed ei
+ eyeless eyeless eyelid eyelid
+ eyelids eyelid eyes ey
+ eyesight eyesight eyestrings eyestr
+ eying ei eyne eyn
+ eyrie eyri fa fa
+ fabian fabian fable fabl
+ fables fabl fabric fabric
+ fabulous fabul fac fac
+ face face faced face
+ facere facer faces face
+ faciant faciant facile facil
+ facility facil facinerious facineri
+ facing face facit facit
+ fact fact faction faction
+ factionary factionari factions faction
+ factious factiou factor factor
+ factors factor faculties faculti
+ faculty faculti fade fade
+ faded fade fadeth fadeth
+ fadge fadg fading fade
+ fadings fade fadom fadom
+ fadoms fadom fagot fagot
+ fagots fagot fail fail
+ failing fail fails fail
+ fain fain faint faint
+ fainted faint fainter fainter
+ fainting faint faintly faintli
+ faintness faint faints faint
+ fair fair fairer fairer
+ fairest fairest fairies fairi
+ fairing fair fairings fair
+ fairly fairli fairness fair
+ fairs fair fairwell fairwel
+ fairy fairi fais fai
+ fait fait faites fait
+ faith faith faithful faith
+ faithfull faithful faithfully faithfulli
+ faithless faithless faiths faith
+ faitors faitor fal fal
+ falchion falchion falcon falcon
+ falconbridge falconbridg falconer falcon
+ falconers falcon fall fall
+ fallacy fallaci fallen fallen
+ falleth falleth falliable falliabl
+ fallible fallibl falling fall
+ fallow fallow fallows fallow
+ falls fall fally falli
+ falorous falor false fals
+ falsehood falsehood falsely fals
+ falseness fals falser falser
+ falsify falsifi falsing fals
+ falstaff falstaff falstaffs falstaff
+ falter falter fam fam
+ fame fame famed fame
+ familiar familiar familiarity familiar
+ familiarly familiarli familiars familiar
+ family famili famine famin
+ famish famish famished famish
+ famous famou famoused famous
+ famously famous fan fan
+ fanatical fanat fancies fanci
+ fancy fanci fane fane
+ fanes fane fang fang
+ fangled fangl fangless fangless
+ fangs fang fann fann
+ fanning fan fans fan
+ fantasied fantasi fantasies fantasi
+ fantastic fantast fantastical fantast
+ fantastically fantast fantasticoes fantastico
+ fantasy fantasi fap fap
+ far far farborough farborough
+ farced farc fardel fardel
+ fardels fardel fare fare
+ fares fare farewell farewel
+ farewells farewel fariner farin
+ faring fare farm farm
+ farmer farmer farmhouse farmhous
+ farms farm farre farr
+ farrow farrow farther farther
+ farthest farthest farthing farth
+ farthingale farthingal farthingales farthingal
+ farthings farth fartuous fartuou
+ fas fa fashion fashion
+ fashionable fashion fashioning fashion
+ fashions fashion fast fast
+ fasted fast fasten fasten
+ fastened fasten faster faster
+ fastest fastest fasting fast
+ fastly fastli fastolfe fastolf
+ fasts fast fat fat
+ fatal fatal fatally fatal
+ fate fate fated fate
+ fates fate father father
+ fathered father fatherless fatherless
+ fatherly fatherli fathers father
+ fathom fathom fathomless fathomless
+ fathoms fathom fatigate fatig
+ fatness fat fats fat
+ fatted fat fatter fatter
+ fattest fattest fatting fat
+ fatuus fatuu fauconbridge fauconbridg
+ faulconbridge faulconbridg fault fault
+ faultiness faulti faultless faultless
+ faults fault faulty faulti
+ fausse fauss fauste faust
+ faustuses faustus faut faut
+ favor favor favorable favor
+ favorably favor favors favor
+ favour favour favourable favour
+ favoured favour favouredly favouredli
+ favourer favour favourers favour
+ favouring favour favourite favourit
+ favourites favourit favours favour
+ favout favout fawn fawn
+ fawneth fawneth fawning fawn
+ fawns fawn fay fai
+ fe fe fealty fealti
+ fear fear feared fear
+ fearest fearest fearful fear
+ fearfull fearful fearfully fearfulli
+ fearfulness fear fearing fear
+ fearless fearless fears fear
+ feast feast feasted feast
+ feasting feast feasts feast
+ feat feat feated feat
+ feater feater feather feather
+ feathered feather feathers feather
+ featly featli feats feat
+ featur featur feature featur
+ featured featur featureless featureless
+ features featur february februari
+ fecks feck fed fed
+ fedary fedari federary federari
+ fee fee feeble feebl
+ feebled feebl feebleness feebl
+ feebling feebl feebly feebli
+ feed feed feeder feeder
+ feeders feeder feedeth feedeth
+ feeding feed feeds feed
+ feel feel feeler feeler
+ feeling feel feelingly feelingli
+ feels feel fees fee
+ feet feet fehemently fehement
+ feign feign feigned feign
+ feigning feign feil feil
+ feith feith felicitate felicit
+ felicity felic fell fell
+ fellest fellest fellies felli
+ fellow fellow fellowly fellowli
+ fellows fellow fellowship fellowship
+ fellowships fellowship fells fell
+ felon felon felonious feloni
+ felony feloni felt felt
+ female femal females femal
+ feminine feminin fen fen
+ fenc fenc fence fenc
+ fencer fencer fencing fenc
+ fends fend fennel fennel
+ fenny fenni fens fen
+ fenton fenton fer fer
+ ferdinand ferdinand fere fere
+ fernseed fernse ferrara ferrara
+ ferrers ferrer ferret ferret
+ ferry ferri ferryman ferryman
+ fertile fertil fertility fertil
+ fervency fervenc fervour fervour
+ fery feri fest fest
+ feste fest fester fester
+ festinate festin festinately festin
+ festival festiv festivals festiv
+ fet fet fetch fetch
+ fetches fetch fetching fetch
+ fetlock fetlock fetlocks fetlock
+ fett fett fetter fetter
+ fettering fetter fetters fetter
+ fettle fettl feu feu
+ feud feud fever fever
+ feverous fever fevers fever
+ few few fewer fewer
+ fewest fewest fewness few
+ fickle fickl fickleness fickl
+ fico fico fiction fiction
+ fiddle fiddl fiddler fiddler
+ fiddlestick fiddlestick fidele fidel
+ fidelicet fidelicet fidelity fidel
+ fidius fidiu fie fie
+ field field fielded field
+ fields field fiend fiend
+ fiends fiend fierce fierc
+ fiercely fierc fierceness fierc
+ fiery fieri fife fife
+ fifes fife fifteen fifteen
+ fifteens fifteen fifteenth fifteenth
+ fifth fifth fifty fifti
+ fiftyfold fiftyfold fig fig
+ fight fight fighter fighter
+ fightest fightest fighteth fighteth
+ fighting fight fights fight
+ figo figo figs fig
+ figur figur figure figur
+ figured figur figures figur
+ figuring figur fike fike
+ fil fil filberts filbert
+ filch filch filches filch
+ filching filch file file
+ filed file files file
+ filial filial filius filiu
+ fill fill filled fill
+ fillet fillet filling fill
+ fillip fillip fills fill
+ filly filli film film
+ fils fil filth filth
+ filths filth filthy filthi
+ fin fin finally final
+ finch finch find find
+ finder finder findeth findeth
+ finding find findings find
+ finds find fine fine
+ fineless fineless finely fine
+ finem finem fineness fine
+ finer finer fines fine
+ finest finest fing fing
+ finger finger fingering finger
+ fingers finger fingre fingr
+ fingres fingr finical finic
+ finish finish finished finish
+ finisher finish finless finless
+ finn finn fins fin
+ finsbury finsburi fir fir
+ firago firago fire fire
+ firebrand firebrand firebrands firebrand
+ fired fire fires fire
+ firework firework fireworks firework
+ firing fire firk firk
+ firm firm firmament firmament
+ firmly firmli firmness firm
+ first first firstlings firstl
+ fish fish fisher fisher
+ fishermen fishermen fishers fisher
+ fishes fish fishified fishifi
+ fishmonger fishmong fishpond fishpond
+ fisnomy fisnomi fist fist
+ fisting fist fists fist
+ fistula fistula fit fit
+ fitchew fitchew fitful fit
+ fitly fitli fitment fitment
+ fitness fit fits fit
+ fitted fit fitter fitter
+ fittest fittest fitteth fitteth
+ fitting fit fitzwater fitzwat
+ five five fivepence fivep
+ fives five fix fix
+ fixed fix fixes fix
+ fixeth fixeth fixing fix
+ fixture fixtur fl fl
+ flag flag flagging flag
+ flagon flagon flagons flagon
+ flags flag flail flail
+ flakes flake flaky flaki
+ flam flam flame flame
+ flamen flamen flamens flamen
+ flames flame flaming flame
+ flaminius flaminiu flanders flander
+ flannel flannel flap flap
+ flaring flare flash flash
+ flashes flash flashing flash
+ flask flask flat flat
+ flatly flatli flatness flat
+ flats flat flatt flatt
+ flatter flatter flattered flatter
+ flatterer flatter flatterers flatter
+ flatterest flatterest flatteries flatteri
+ flattering flatter flatters flatter
+ flattery flatteri flaunts flaunt
+ flavio flavio flavius flaviu
+ flaw flaw flaws flaw
+ flax flax flaxen flaxen
+ flay flai flaying flai
+ flea flea fleance fleanc
+ fleas flea flecked fleck
+ fled fled fledge fledg
+ flee flee fleec fleec
+ fleece fleec fleeces fleec
+ fleer fleer fleering fleer
+ fleers fleer fleet fleet
+ fleeter fleeter fleeting fleet
+ fleming fleme flemish flemish
+ flesh flesh fleshes flesh
+ fleshly fleshli fleshment fleshment
+ fleshmonger fleshmong flew flew
+ flexible flexibl flexure flexur
+ flibbertigibbet flibbertigibbet flickering flicker
+ flidge flidg fliers flier
+ flies fli flieth flieth
+ flight flight flights flight
+ flighty flighti flinch flinch
+ fling fling flint flint
+ flints flint flinty flinti
+ flirt flirt float float
+ floated float floating float
+ flock flock flocks flock
+ flood flood floodgates floodgat
+ floods flood floor floor
+ flora flora florence florenc
+ florentine florentin florentines florentin
+ florentius florentiu florizel florizel
+ flote flote floulish floulish
+ flour flour flourish flourish
+ flourishes flourish flourisheth flourisheth
+ flourishing flourish flout flout
+ flouted flout flouting flout
+ flouts flout flow flow
+ flowed flow flower flower
+ flowerets floweret flowers flower
+ flowing flow flown flown
+ flows flow fluellen fluellen
+ fluent fluent flung flung
+ flush flush flushing flush
+ fluster fluster flute flute
+ flutes flute flutter flutter
+ flux flux fluxive fluxiv
+ fly fly flying fly
+ fo fo foal foal
+ foals foal foam foam
+ foamed foam foaming foam
+ foams foam foamy foami
+ fob fob focative foc
+ fodder fodder foe foe
+ foeman foeman foemen foemen
+ foes foe fog fog
+ foggy foggi fogs fog
+ foh foh foi foi
+ foil foil foiled foil
+ foils foil foin foin
+ foining foin foins foin
+ fois foi foison foison
+ foisons foison foist foist
+ foix foix fold fold
+ folded fold folds fold
+ folio folio folk folk
+ folks folk follies folli
+ follow follow followed follow
+ follower follow followers follow
+ followest followest following follow
+ follows follow folly folli
+ fond fond fonder fonder
+ fondly fondli fondness fond
+ font font fontibell fontibel
+ food food fool fool
+ fooleries fooleri foolery fooleri
+ foolhardy foolhardi fooling fool
+ foolish foolish foolishly foolishli
+ foolishness foolish fools fool
+ foot foot football footbal
+ footboy footboi footboys footboi
+ footed foot footfall footfal
+ footing foot footman footman
+ footmen footmen footpath footpath
+ footsteps footstep footstool footstool
+ fopp fopp fopped fop
+ foppery fopperi foppish foppish
+ fops fop for for
+ forage forag foragers forag
+ forbade forbad forbear forbear
+ forbearance forbear forbears forbear
+ forbid forbid forbidden forbidden
+ forbiddenly forbiddenli forbids forbid
+ forbod forbod forborne forborn
+ forc forc force forc
+ forced forc forceful forc
+ forceless forceless forces forc
+ forcible forcibl forcibly forcibl
+ forcing forc ford ford
+ fordid fordid fordo fordo
+ fordoes fordo fordone fordon
+ fore fore forecast forecast
+ forefather forefath forefathers forefath
+ forefinger forefing forego forego
+ foregone foregon forehand forehand
+ forehead forehead foreheads forehead
+ forehorse forehors foreign foreign
+ foreigner foreign foreigners foreign
+ foreknowing foreknow foreknowledge foreknowledg
+ foremost foremost forenamed forenam
+ forenoon forenoon forerun forerun
+ forerunner forerunn forerunning forerun
+ foreruns forerun foresaid foresaid
+ foresaw foresaw foresay foresai
+ foresee forese foreseeing forese
+ foresees forese foreshow foreshow
+ foreskirt foreskirt forespent foresp
+ forest forest forestall forestal
+ forestalled forestal forester forest
+ foresters forest forests forest
+ foretell foretel foretelling foretel
+ foretells foretel forethink forethink
+ forethought forethought foretold foretold
+ forever forev foreward foreward
+ forewarn forewarn forewarned forewarn
+ forewarning forewarn forfeit forfeit
+ forfeited forfeit forfeiters forfeit
+ forfeiting forfeit forfeits forfeit
+ forfeiture forfeitur forfeitures forfeitur
+ forfend forfend forfended forfend
+ forg forg forgave forgav
+ forge forg forged forg
+ forgeries forgeri forgery forgeri
+ forges forg forget forget
+ forgetful forget forgetfulness forget
+ forgetive forget forgets forget
+ forgetting forget forgive forgiv
+ forgiven forgiven forgiveness forgiv
+ forgo forgo forgoing forgo
+ forgone forgon forgot forgot
+ forgotten forgotten fork fork
+ forked fork forks fork
+ forlorn forlorn form form
+ formal formal formally formal
+ formed form former former
+ formerly formerli formless formless
+ forms form fornication fornic
+ fornications fornic fornicatress fornicatress
+ forres forr forrest forrest
+ forsake forsak forsaken forsaken
+ forsaketh forsaketh forslow forslow
+ forsook forsook forsooth forsooth
+ forspent forspent forspoke forspok
+ forswear forswear forswearing forswear
+ forswore forswor forsworn forsworn
+ fort fort forted fort
+ forth forth forthcoming forthcom
+ forthlight forthlight forthright forthright
+ forthwith forthwith fortification fortif
+ fortifications fortif fortified fortifi
+ fortifies fortifi fortify fortifi
+ fortinbras fortinbra fortitude fortitud
+ fortnight fortnight fortress fortress
+ fortresses fortress forts fort
+ fortun fortun fortuna fortuna
+ fortunate fortun fortunately fortun
+ fortune fortun fortuned fortun
+ fortunes fortun fortward fortward
+ forty forti forum forum
+ forward forward forwarding forward
+ forwardness forward forwards forward
+ forwearied forweari fosset fosset
+ fost fost foster foster
+ fostered foster fought fought
+ foughten foughten foul foul
+ fouler fouler foulest foulest
+ foully foulli foulness foul
+ found found foundation foundat
+ foundations foundat founded found
+ founder founder fount fount
+ fountain fountain fountains fountain
+ founts fount four four
+ fourscore fourscor fourteen fourteen
+ fourth fourth foutra foutra
+ fowl fowl fowler fowler
+ fowling fowl fowls fowl
+ fox fox foxes fox
+ foxship foxship fracted fract
+ fraction fraction fractions fraction
+ fragile fragil fragment fragment
+ fragments fragment fragrant fragrant
+ frail frail frailer frailer
+ frailties frailti frailty frailti
+ fram fram frame frame
+ framed frame frames frame
+ frampold frampold fran fran
+ francais francai france franc
+ frances franc franchise franchis
+ franchised franchis franchisement franchis
+ franchises franchis franciae francia
+ francis franci francisca francisca
+ franciscan franciscan francisco francisco
+ frank frank franker franker
+ frankfort frankfort franklin franklin
+ franklins franklin frankly frankli
+ frankness frank frantic frantic
+ franticly franticli frateretto frateretto
+ fratrum fratrum fraud fraud
+ fraudful fraud fraught fraught
+ fraughtage fraughtag fraughting fraught
+ fray frai frays frai
+ freckl freckl freckled freckl
+ freckles freckl frederick frederick
+ free free freed freed
+ freedom freedom freedoms freedom
+ freehearted freeheart freelier freelier
+ freely freeli freeman freeman
+ freemen freemen freeness freeness
+ freer freer frees free
+ freestone freeston freetown freetown
+ freeze freez freezes freez
+ freezing freez freezings freez
+ french french frenchman frenchman
+ frenchmen frenchmen frenchwoman frenchwoman
+ frenzy frenzi frequent frequent
+ frequents frequent fresh fresh
+ fresher fresher freshes fresh
+ freshest freshest freshly freshli
+ freshness fresh fret fret
+ fretful fret frets fret
+ fretted fret fretten fretten
+ fretting fret friar friar
+ friars friar friday fridai
+ fridays fridai friend friend
+ friended friend friending friend
+ friendless friendless friendliness friendli
+ friendly friendli friends friend
+ friendship friendship friendships friendship
+ frieze friez fright fright
+ frighted fright frightened frighten
+ frightful fright frighting fright
+ frights fright fringe fring
+ fringed fring frippery fripperi
+ frisk frisk fritters fritter
+ frivolous frivol fro fro
+ frock frock frog frog
+ frogmore frogmor froissart froissart
+ frolic frolic from from
+ front front fronted front
+ frontier frontier frontiers frontier
+ fronting front frontlet frontlet
+ fronts front frost frost
+ frosts frost frosty frosti
+ froth froth froward froward
+ frown frown frowning frown
+ frowningly frowningli frowns frown
+ froze froze frozen frozen
+ fructify fructifi frugal frugal
+ fruit fruit fruiterer fruiter
+ fruitful fruit fruitfully fruitfulli
+ fruitfulness fruit fruition fruition
+ fruitless fruitless fruits fruit
+ frush frush frustrate frustrat
+ frutify frutifi fry fry
+ fubb fubb fuel fuel
+ fugitive fugit fulfil fulfil
+ fulfill fulfil fulfilling fulfil
+ fulfils fulfil full full
+ fullam fullam fuller fuller
+ fullers fuller fullest fullest
+ fullness full fully fulli
+ fulness ful fulsome fulsom
+ fulvia fulvia fum fum
+ fumble fumbl fumbles fumbl
+ fumblest fumblest fumbling fumbl
+ fume fume fumes fume
+ fuming fume fumiter fumit
+ fumitory fumitori fun fun
+ function function functions function
+ fundamental fundament funeral funer
+ funerals funer fur fur
+ furbish furbish furies furi
+ furious furiou furlongs furlong
+ furnace furnac furnaces furnac
+ furnish furnish furnished furnish
+ furnishings furnish furniture furnitur
+ furnival furniv furor furor
+ furr furr furrow furrow
+ furrowed furrow furrows furrow
+ furth furth further further
+ furtherance further furtherer further
+ furthermore furthermor furthest furthest
+ fury furi furze furz
+ furzes furz fust fust
+ fustian fustian fustilarian fustilarian
+ fusty fusti fut fut
+ future futur futurity futur
+ g g gabble gabbl
+ gaberdine gaberdin gabriel gabriel
+ gad gad gadding gad
+ gads gad gadshill gadshil
+ gag gag gage gage
+ gaged gage gagg gagg
+ gaging gage gagne gagn
+ gain gain gained gain
+ gainer gainer gaingiving gaingiv
+ gains gain gainsaid gainsaid
+ gainsay gainsai gainsaying gainsai
+ gainsays gainsai gainst gainst
+ gait gait gaited gait
+ galathe galath gale gale
+ galen galen gales gale
+ gall gall gallant gallant
+ gallantly gallantli gallantry gallantri
+ gallants gallant galled gall
+ gallery galleri galley gallei
+ galleys gallei gallia gallia
+ gallian gallian galliard galliard
+ galliasses galliass gallimaufry gallimaufri
+ galling gall gallons gallon
+ gallop gallop galloping gallop
+ gallops gallop gallow gallow
+ galloway gallowai gallowglasses gallowglass
+ gallows gallow gallowses gallows
+ galls gall gallus gallu
+ gam gam gambol gambol
+ gambold gambold gambols gambol
+ gamboys gamboi game game
+ gamers gamer games game
+ gamesome gamesom gamester gamest
+ gaming game gammon gammon
+ gamut gamut gan gan
+ gangren gangren ganymede ganymed
+ gaol gaol gaoler gaoler
+ gaolers gaoler gaols gaol
+ gap gap gape gape
+ gapes gape gaping gape
+ gar gar garb garb
+ garbage garbag garboils garboil
+ garcon garcon gard gard
+ garde gard garden garden
+ gardener garden gardeners garden
+ gardens garden gardez gardez
+ gardiner gardin gardon gardon
+ gargantua gargantua gargrave gargrav
+ garish garish garland garland
+ garlands garland garlic garlic
+ garment garment garments garment
+ garmet garmet garner garner
+ garners garner garnish garnish
+ garnished garnish garret garret
+ garrison garrison garrisons garrison
+ gart gart garter garter
+ garterd garterd gartering garter
+ garters garter gascony gasconi
+ gash gash gashes gash
+ gaskins gaskin gasp gasp
+ gasping gasp gasted gast
+ gastness gast gat gat
+ gate gate gated gate
+ gates gate gath gath
+ gather gather gathered gather
+ gathering gather gathers gather
+ gatories gatori gatory gatori
+ gaud gaud gaudeo gaudeo
+ gaudy gaudi gauge gaug
+ gaul gaul gaultree gaultre
+ gaunt gaunt gauntlet gauntlet
+ gauntlets gauntlet gav gav
+ gave gave gavest gavest
+ gawded gawd gawds gawd
+ gawsey gawsei gay gai
+ gayness gay gaz gaz
+ gaze gaze gazed gaze
+ gazer gazer gazers gazer
+ gazes gaze gazeth gazeth
+ gazing gaze gear gear
+ geck geck geese gees
+ geffrey geffrei geld geld
+ gelded geld gelding geld
+ gelida gelida gelidus gelidu
+ gelt gelt gem gem
+ geminy gemini gems gem
+ gen gen gender gender
+ genders gender general gener
+ generally gener generals gener
+ generation gener generations gener
+ generative gener generosity generos
+ generous gener genitive genit
+ genitivo genitivo genius geniu
+ gennets gennet genoa genoa
+ genoux genoux gens gen
+ gent gent gentilhomme gentilhomm
+ gentility gentil gentle gentl
+ gentlefolks gentlefolk gentleman gentleman
+ gentlemanlike gentlemanlik gentlemen gentlemen
+ gentleness gentl gentler gentler
+ gentles gentl gentlest gentlest
+ gentlewoman gentlewoman gentlewomen gentlewomen
+ gently gentli gentry gentri
+ george georg gerard gerard
+ germaines germain germains germain
+ german german germane german
+ germans german germany germani
+ gertrude gertrud gest gest
+ gests gest gesture gestur
+ gestures gestur get get
+ getrude getrud gets get
+ getter getter getting get
+ ghastly ghastli ghost ghost
+ ghosted ghost ghostly ghostli
+ ghosts ghost gi gi
+ giant giant giantess giantess
+ giantlike giantlik giants giant
+ gib gib gibber gibber
+ gibbet gibbet gibbets gibbet
+ gibe gibe giber giber
+ gibes gibe gibing gibe
+ gibingly gibingli giddily giddili
+ giddiness giddi giddy giddi
+ gift gift gifts gift
+ gig gig giglets giglet
+ giglot giglot gilbert gilbert
+ gild gild gilded gild
+ gilding gild gilliams gilliam
+ gillian gillian gills gill
+ gillyvors gillyvor gilt gilt
+ gimmal gimmal gimmers gimmer
+ gin gin ging ging
+ ginger ginger gingerbread gingerbread
+ gingerly gingerli ginn ginn
+ gins gin gioucestershire gioucestershir
+ gipes gipe gipsies gipsi
+ gipsy gipsi gird gird
+ girded gird girdle girdl
+ girdled girdl girdles girdl
+ girdling girdl girl girl
+ girls girl girt girt
+ girth girth gis gi
+ giv giv give give
+ given given giver giver
+ givers giver gives give
+ givest givest giveth giveth
+ giving give givings give
+ glad glad gladded glad
+ gladding glad gladly gladli
+ gladness glad glamis glami
+ glanc glanc glance glanc
+ glanced glanc glances glanc
+ glancing glanc glanders glander
+ glansdale glansdal glare glare
+ glares glare glass glass
+ glasses glass glassy glassi
+ glaz glaz glazed glaze
+ gleams gleam glean glean
+ gleaned glean gleaning glean
+ gleeful gleeful gleek gleek
+ gleeking gleek gleeks gleek
+ glend glend glendower glendow
+ glib glib glide glide
+ glided glide glides glide
+ glideth glideth gliding glide
+ glimmer glimmer glimmering glimmer
+ glimmers glimmer glimpse glimps
+ glimpses glimps glist glist
+ glistening glisten glister glister
+ glistering glister glisters glister
+ glitt glitt glittering glitter
+ globe globe globes globe
+ glooming gloom gloomy gloomi
+ glories glori glorified glorifi
+ glorify glorifi glorious gloriou
+ gloriously glorious glory glori
+ glose glose gloss gloss
+ glosses gloss glou glou
+ glouceste gloucest gloucester gloucest
+ gloucestershire gloucestershir glove glove
+ glover glover gloves glove
+ glow glow glowed glow
+ glowing glow glowworm glowworm
+ gloz gloz gloze gloze
+ glozes gloze glu glu
+ glue glue glued glu
+ glues glue glut glut
+ glutt glutt glutted glut
+ glutton glutton gluttoning glutton
+ gluttony gluttoni gnarled gnarl
+ gnarling gnarl gnat gnat
+ gnats gnat gnaw gnaw
+ gnawing gnaw gnawn gnawn
+ gnaws gnaw go go
+ goad goad goaded goad
+ goads goad goal goal
+ goat goat goatish goatish
+ goats goat gobbets gobbet
+ gobbo gobbo goblet goblet
+ goblets goblet goblin goblin
+ goblins goblin god god
+ godded god godden godden
+ goddess goddess goddesses goddess
+ goddild goddild godfather godfath
+ godfathers godfath godhead godhead
+ godlike godlik godliness godli
+ godly godli godmother godmoth
+ gods god godson godson
+ goer goer goers goer
+ goes goe goest goest
+ goeth goeth goffe goff
+ gogs gog going go
+ gold gold golden golden
+ goldenly goldenli goldsmith goldsmith
+ goldsmiths goldsmith golgotha golgotha
+ goliases golias goliath goliath
+ gon gon gondola gondola
+ gondolier gondoli gone gone
+ goneril goneril gong gong
+ gonzago gonzago gonzalo gonzalo
+ good good goodfellow goodfellow
+ goodlier goodlier goodliest goodliest
+ goodly goodli goodman goodman
+ goodness good goodnight goodnight
+ goodrig goodrig goods good
+ goodwife goodwif goodwill goodwil
+ goodwin goodwin goodwins goodwin
+ goodyear goodyear goodyears goodyear
+ goose goos gooseberry gooseberri
+ goosequills goosequil goot goot
+ gor gor gorbellied gorbelli
+ gorboduc gorboduc gordian gordian
+ gore gore gored gore
+ gorg gorg gorge gorg
+ gorgeous gorgeou gorget gorget
+ gorging gorg gorgon gorgon
+ gormandize gormand gormandizing gormand
+ gory gori gosling gosl
+ gospel gospel gospels gospel
+ goss goss gossamer gossam
+ gossip gossip gossiping gossip
+ gossiplike gossiplik gossips gossip
+ got got goth goth
+ goths goth gotten gotten
+ gourd gourd gout gout
+ gouts gout gouty gouti
+ govern govern governance govern
+ governed govern governess gover
+ government govern governor governor
+ governors governor governs govern
+ gower gower gown gown
+ gowns gown grac grac
+ grace grace graced grace
+ graceful grace gracefully gracefulli
+ graceless graceless graces grace
+ gracing grace gracious graciou
+ graciously gracious gradation gradat
+ graff graff graffing graf
+ graft graft grafted graft
+ grafters grafter grain grain
+ grained grain grains grain
+ gramercies gramerci gramercy gramerci
+ grammar grammar grand grand
+ grandam grandam grandame grandam
+ grandchild grandchild grande grand
+ grandeur grandeur grandfather grandfath
+ grandjurors grandjuror grandmother grandmoth
+ grandpre grandpr grandsir grandsir
+ grandsire grandsir grandsires grandsir
+ grange grang grant grant
+ granted grant granting grant
+ grants grant grape grape
+ grapes grape grapple grappl
+ grapples grappl grappling grappl
+ grasp grasp grasped grasp
+ grasps grasp grass grass
+ grasshoppers grasshopp grassy grassi
+ grate grate grated grate
+ grateful grate grates grate
+ gratiano gratiano gratify gratifi
+ gratii gratii gratillity gratil
+ grating grate gratis grati
+ gratitude gratitud gratulate gratul
+ grav grav grave grave
+ gravediggers gravedigg gravel gravel
+ graveless graveless gravell gravel
+ gravely grave graven graven
+ graveness grave graver graver
+ graves grave gravest gravest
+ gravestone graveston gravities graviti
+ gravity graviti gravy gravi
+ gray grai graymalkin graymalkin
+ graz graz graze graze
+ grazed graze grazing graze
+ grease greas greases greas
+ greasily greasili greasy greasi
+ great great greater greater
+ greatest greatest greatly greatli
+ greatness great grecian grecian
+ grecians grecian gree gree
+ greece greec greed greed
+ greedily greedili greediness greedi
+ greedy greedi greeing gree
+ greek greek greekish greekish
+ greeks greek green green
+ greener greener greenly greenli
+ greens green greensleeves greensleev
+ greenwich greenwich greenwood greenwood
+ greet greet greeted greet
+ greeting greet greetings greet
+ greets greet greg greg
+ gregory gregori gremio gremio
+ grew grew grey grei
+ greybeard greybeard greybeards greybeard
+ greyhound greyhound greyhounds greyhound
+ grief grief griefs grief
+ griev griev grievance grievanc
+ grievances grievanc grieve griev
+ grieved griev grieves griev
+ grievest grievest grieving griev
+ grievingly grievingli grievous grievou
+ grievously grievous griffin griffin
+ griffith griffith grim grim
+ grime grime grimly grimli
+ grin grin grind grind
+ grinding grind grindstone grindston
+ grinning grin grip grip
+ gripe gripe gripes gripe
+ griping gripe grise grise
+ grisly grisli grissel grissel
+ grize grize grizzle grizzl
+ grizzled grizzl groan groan
+ groaning groan groans groan
+ groat groat groats groat
+ groin groin groom groom
+ grooms groom grop grop
+ groping grope gros gro
+ gross gross grosser grosser
+ grossly grossli grossness gross
+ ground ground grounded ground
+ groundlings groundl grounds ground
+ grove grove grovel grovel
+ grovelling grovel groves grove
+ grow grow groweth groweth
+ growing grow grown grown
+ grows grow growth growth
+ grub grub grubb grubb
+ grubs grub grudge grudg
+ grudged grudg grudges grudg
+ grudging grudg gruel gruel
+ grumble grumbl grumblest grumblest
+ grumbling grumbl grumblings grumbl
+ grumio grumio grund grund
+ grunt grunt gualtier gualtier
+ guard guard guardage guardag
+ guardant guardant guarded guard
+ guardian guardian guardians guardian
+ guards guard guardsman guardsman
+ gud gud gudgeon gudgeon
+ guerdon guerdon guerra guerra
+ guess guess guesses guess
+ guessingly guessingli guest guest
+ guests guest guiana guiana
+ guichard guichard guide guid
+ guided guid guider guider
+ guiderius guideriu guides guid
+ guiding guid guidon guidon
+ guienne guienn guil guil
+ guildenstern guildenstern guilders guilder
+ guildford guildford guildhall guildhal
+ guile guil guiled guil
+ guileful guil guilfords guilford
+ guilt guilt guiltian guiltian
+ guiltier guiltier guiltily guiltili
+ guiltiness guilti guiltless guiltless
+ guilts guilt guilty guilti
+ guinea guinea guinever guinev
+ guise guis gul gul
+ gules gule gulf gulf
+ gulfs gulf gull gull
+ gulls gull gum gum
+ gumm gumm gums gum
+ gun gun gunner gunner
+ gunpowder gunpowd guns gun
+ gurnet gurnet gurney gurnei
+ gust gust gusts gust
+ gusty gusti guts gut
+ gutter gutter guy gui
+ guynes guyn guysors guysor
+ gypsy gypsi gyve gyve
+ gyved gyve gyves gyve
+ h h ha ha
+ haberdasher haberdash habiliment habili
+ habiliments habili habit habit
+ habitation habit habited habit
+ habits habit habitude habitud
+ hack hack hacket hacket
+ hackney hacknei hacks hack
+ had had hadst hadst
+ haec haec haeres haer
+ hag hag hagar hagar
+ haggard haggard haggards haggard
+ haggish haggish haggled haggl
+ hags hag hail hail
+ hailed hail hailstone hailston
+ hailstones hailston hair hair
+ hairless hairless hairs hair
+ hairy hairi hal hal
+ halberd halberd halberds halberd
+ halcyon halcyon hale hale
+ haled hale hales hale
+ half half halfcan halfcan
+ halfpence halfpenc halfpenny halfpenni
+ halfpennyworth halfpennyworth halfway halfwai
+ halidom halidom hall hall
+ halloa halloa halloing hallo
+ hallond hallond halloo halloo
+ hallooing halloo hallow hallow
+ hallowed hallow hallowmas hallowma
+ hallown hallown hals hal
+ halt halt halter halter
+ halters halter halting halt
+ halts halt halves halv
+ ham ham hames hame
+ hamlet hamlet hammer hammer
+ hammered hammer hammering hammer
+ hammers hammer hamper hamper
+ hampton hampton hams ham
+ hamstring hamstr hand hand
+ handed hand handful hand
+ handicraft handicraft handicraftsmen handicraftsmen
+ handing hand handiwork handiwork
+ handkercher handkerch handkerchers handkerch
+ handkerchief handkerchief handle handl
+ handled handl handles handl
+ handless handless handlest handlest
+ handling handl handmaid handmaid
+ handmaids handmaid hands hand
+ handsaw handsaw handsome handsom
+ handsomely handsom handsomeness handsom
+ handwriting handwrit handy handi
+ hang hang hanged hang
+ hangers hanger hangeth hangeth
+ hanging hang hangings hang
+ hangman hangman hangmen hangmen
+ hangs hang hannibal hannib
+ hap hap hapless hapless
+ haply hapli happ happ
+ happen happen happened happen
+ happier happier happies happi
+ happiest happiest happily happili
+ happiness happi happy happi
+ haps hap harbinger harbing
+ harbingers harbing harbor harbor
+ harbour harbour harbourage harbourag
+ harbouring harbour harbours harbour
+ harcourt harcourt hard hard
+ harder harder hardest hardest
+ hardiest hardiest hardiment hardiment
+ hardiness hardi hardly hardli
+ hardness hard hardocks hardock
+ hardy hardi hare hare
+ harelip harelip hares hare
+ harfleur harfleur hark hark
+ harlot harlot harlotry harlotri
+ harlots harlot harm harm
+ harmed harm harmful harm
+ harming harm harmless harmless
+ harmonious harmoni harmony harmoni
+ harms harm harness har
+ harp harp harper harper
+ harpier harpier harping harp
+ harpy harpi harried harri
+ harrow harrow harrows harrow
+ harry harri harsh harsh
+ harshly harshli harshness harsh
+ hart hart harts hart
+ harum harum harvest harvest
+ has ha hast hast
+ haste hast hasted hast
+ hasten hasten hastes hast
+ hastily hastili hasting hast
+ hastings hast hasty hasti
+ hat hat hatch hatch
+ hatches hatch hatchet hatchet
+ hatching hatch hatchment hatchment
+ hate hate hated hate
+ hateful hate hater hater
+ haters hater hates hate
+ hateth hateth hatfield hatfield
+ hath hath hating hate
+ hatred hatr hats hat
+ haud haud hauf hauf
+ haught haught haughtiness haughti
+ haughty haughti haunch haunch
+ haunches haunch haunt haunt
+ haunted haunt haunting haunt
+ haunts haunt hautboy hautboi
+ hautboys hautboi have have
+ haven haven havens haven
+ haver haver having have
+ havings have havior havior
+ haviour haviour havoc havoc
+ hawk hawk hawking hawk
+ hawks hawk hawthorn hawthorn
+ hawthorns hawthorn hay hai
+ hazard hazard hazarded hazard
+ hazards hazard hazel hazel
+ hazelnut hazelnut he he
+ head head headborough headborough
+ headed head headier headier
+ heading head headland headland
+ headless headless headlong headlong
+ heads head headsman headsman
+ headstrong headstrong heady headi
+ heal heal healed heal
+ healing heal heals heal
+ health health healthful health
+ healths health healthsome healthsom
+ healthy healthi heap heap
+ heaping heap heaps heap
+ hear hear heard heard
+ hearer hearer hearers hearer
+ hearest hearest heareth heareth
+ hearing hear hearings hear
+ heark heark hearken hearken
+ hearkens hearken hears hear
+ hearsay hearsai hearse hears
+ hearsed hears hearst hearst
+ heart heart heartache heartach
+ heartbreak heartbreak heartbreaking heartbreak
+ hearted heart hearten hearten
+ hearth hearth hearths hearth
+ heartily heartili heartiness hearti
+ heartless heartless heartlings heartl
+ heartly heartli hearts heart
+ heartsick heartsick heartstrings heartstr
+ hearty hearti heat heat
+ heated heat heath heath
+ heathen heathen heathenish heathenish
+ heating heat heats heat
+ heauties heauti heav heav
+ heave heav heaved heav
+ heaven heaven heavenly heavenli
+ heavens heaven heaves heav
+ heavier heavier heaviest heaviest
+ heavily heavili heaviness heavi
+ heaving heav heavings heav
+ heavy heavi hebona hebona
+ hebrew hebrew hecate hecat
+ hectic hectic hector hector
+ hectors hector hecuba hecuba
+ hedg hedg hedge hedg
+ hedgehog hedgehog hedgehogs hedgehog
+ hedges hedg heed heed
+ heeded heed heedful heed
+ heedfull heedful heedfully heedfulli
+ heedless heedless heel heel
+ heels heel hefted heft
+ hefts heft heifer heifer
+ heifers heifer heigh heigh
+ height height heighten heighten
+ heinous heinou heinously heinous
+ heir heir heiress heiress
+ heirless heirless heirs heir
+ held held helen helen
+ helena helena helenus helenu
+ helias helia helicons helicon
+ hell hell hellespont hellespont
+ hellfire hellfir hellish hellish
+ helm helm helmed helm
+ helmet helmet helmets helmet
+ helms helm help help
+ helper helper helpers helper
+ helpful help helping help
+ helpless helpless helps help
+ helter helter hem hem
+ heme heme hemlock hemlock
+ hemm hemm hemp hemp
+ hempen hempen hems hem
+ hen hen hence henc
+ henceforth henceforth henceforward henceforward
+ henchman henchman henri henri
+ henricus henricu henry henri
+ hens hen hent hent
+ henton henton her her
+ herald herald heraldry heraldri
+ heralds herald herb herb
+ herbert herbert herblets herblet
+ herbs herb herculean herculean
+ hercules hercul herd herd
+ herds herd herdsman herdsman
+ herdsmen herdsmen here here
+ hereabout hereabout hereabouts hereabout
+ hereafter hereaft hereby herebi
+ hereditary hereditari hereford hereford
+ herefordshire herefordshir herein herein
+ hereof hereof heresies heresi
+ heresy heresi heretic heret
+ heretics heret hereto hereto
+ hereupon hereupon heritage heritag
+ heritier heriti hermes herm
+ hermia hermia hermione hermion
+ hermit hermit hermitage hermitag
+ hermits hermit herne hern
+ hero hero herod herod
+ herods herod heroes hero
+ heroic heroic heroical heroic
+ herring her herrings her
+ hers her herself herself
+ hesperides hesperid hesperus hesperu
+ hest hest hests hest
+ heure heur heureux heureux
+ hew hew hewgh hewgh
+ hewing hew hewn hewn
+ hews hew hey hei
+ heyday heydai hibocrates hibocr
+ hic hic hiccups hiccup
+ hick hick hid hid
+ hidden hidden hide hide
+ hideous hideou hideously hideous
+ hideousness hideous hides hide
+ hidest hidest hiding hide
+ hie hie hied hi
+ hiems hiem hies hi
+ hig hig high high
+ higher higher highest highest
+ highly highli highmost highmost
+ highness high hight hight
+ highway highwai highways highwai
+ hilding hild hildings hild
+ hill hill hillo hillo
+ hilloa hilloa hills hill
+ hilt hilt hilts hilt
+ hily hili him him
+ himself himself hinc hinc
+ hinckley hincklei hind hind
+ hinder hinder hindered hinder
+ hinders hinder hindmost hindmost
+ hinds hind hing hing
+ hinge hing hinges hing
+ hint hint hip hip
+ hipp hipp hipparchus hipparchu
+ hippolyta hippolyta hips hip
+ hir hir hire hire
+ hired hire hiren hiren
+ hirtius hirtiu his hi
+ hisperia hisperia hiss hiss
+ hisses hiss hissing hiss
+ hist hist historical histor
+ history histori hit hit
+ hither hither hitherto hitherto
+ hitherward hitherward hitherwards hitherward
+ hits hit hitting hit
+ hive hive hives hive
+ hizzing hizz ho ho
+ hoa hoa hoar hoar
+ hoard hoard hoarded hoard
+ hoarding hoard hoars hoar
+ hoarse hoars hoary hoari
+ hob hob hobbididence hobbidid
+ hobby hobbi hobbyhorse hobbyhors
+ hobgoblin hobgoblin hobnails hobnail
+ hoc hoc hod hod
+ hodge hodg hog hog
+ hogs hog hogshead hogshead
+ hogsheads hogshead hois hoi
+ hoise hois hoist hoist
+ hoisted hoist hoists hoist
+ holborn holborn hold hold
+ holden holden holder holder
+ holdeth holdeth holdfast holdfast
+ holding hold holds hold
+ hole hole holes hole
+ holidam holidam holidame holidam
+ holiday holidai holidays holidai
+ holier holier holiest holiest
+ holily holili holiness holi
+ holla holla holland holland
+ hollander holland hollanders holland
+ holloa holloa holloaing holloa
+ hollow hollow hollowly hollowli
+ hollowness hollow holly holli
+ holmedon holmedon holofernes holofern
+ holp holp holy holi
+ homage homag homager homag
+ home home homely home
+ homes home homespuns homespun
+ homeward homeward homewards homeward
+ homicide homicid homicides homicid
+ homily homili hominem hominem
+ hommes homm homo homo
+ honest honest honester honest
+ honestest honestest honestly honestli
+ honesty honesti honey honei
+ honeycomb honeycomb honeying honei
+ honeyless honeyless honeysuckle honeysuckl
+ honeysuckles honeysuckl honi honi
+ honneur honneur honor honor
+ honorable honor honorably honor
+ honorato honorato honorificabilitudinitatibus honorificabilitudinitatibu
+ honors honor honour honour
+ honourable honour honourably honour
+ honoured honour honourest honourest
+ honourible honour honouring honour
+ honours honour hoo hoo
+ hood hood hooded hood
+ hoodman hoodman hoods hood
+ hoodwink hoodwink hoof hoof
+ hoofs hoof hook hook
+ hooking hook hooks hook
+ hoop hoop hoops hoop
+ hoot hoot hooted hoot
+ hooting hoot hoots hoot
+ hop hop hope hope
+ hopeful hope hopeless hopeless
+ hopes hope hopest hopest
+ hoping hope hopkins hopkin
+ hoppedance hopped hor hor
+ horace horac horatio horatio
+ horizon horizon horn horn
+ hornbook hornbook horned horn
+ horner horner horning horn
+ hornpipes hornpip horns horn
+ horologe horolog horrible horribl
+ horribly horribl horrid horrid
+ horrider horrid horridly horridli
+ horror horror horrors horror
+ hors hor horse hors
+ horseback horseback horsed hors
+ horsehairs horsehair horseman horseman
+ horsemanship horsemanship horsemen horsemen
+ horses hors horseway horsewai
+ horsing hors hortensio hortensio
+ hortensius hortensiu horum horum
+ hose hose hospitable hospit
+ hospital hospit hospitality hospit
+ host host hostage hostag
+ hostages hostag hostess hostess
+ hostile hostil hostility hostil
+ hostilius hostiliu hosts host
+ hot hot hotly hotli
+ hotspur hotspur hotter hotter
+ hottest hottest hound hound
+ hounds hound hour hour
+ hourly hourli hours hour
+ hous hou house hous
+ household household householder household
+ householders household households household
+ housekeeper housekeep housekeepers housekeep
+ housekeeping housekeep houseless houseless
+ houses hous housewife housewif
+ housewifery housewiferi housewives housew
+ hovel hovel hover hover
+ hovered hover hovering hover
+ hovers hover how how
+ howbeit howbeit howe how
+ howeer howeer however howev
+ howl howl howled howl
+ howlet howlet howling howl
+ howls howl howsoe howso
+ howsoever howsoev howsome howsom
+ hoxes hox hoy hoi
+ hoyday hoydai hubert hubert
+ huddled huddl huddling huddl
+ hue hue hued hu
+ hues hue hug hug
+ huge huge hugely huge
+ hugeness huge hugg hugg
+ hugger hugger hugh hugh
+ hugs hug hujus huju
+ hulk hulk hulks hulk
+ hull hull hulling hull
+ hullo hullo hum hum
+ human human humane human
+ humanely human humanity human
+ humble humbl humbled humbl
+ humbleness humbl humbler humbler
+ humbles humbl humblest humblest
+ humbling humbl humbly humbl
+ hume hume humh humh
+ humidity humid humility humil
+ humming hum humor humor
+ humorous humor humors humor
+ humour humour humourists humourist
+ humours humour humphrey humphrei
+ humphry humphri hums hum
+ hundred hundr hundreds hundr
+ hundredth hundredth hung hung
+ hungarian hungarian hungary hungari
+ hunger hunger hungerford hungerford
+ hungerly hungerli hungry hungri
+ hunt hunt hunted hunt
+ hunter hunter hunters hunter
+ hunteth hunteth hunting hunt
+ huntington huntington huntress huntress
+ hunts hunt huntsman huntsman
+ huntsmen huntsmen hurdle hurdl
+ hurl hurl hurling hurl
+ hurls hurl hurly hurli
+ hurlyburly hurlyburli hurricano hurricano
+ hurricanoes hurricano hurried hurri
+ hurries hurri hurry hurri
+ hurt hurt hurting hurt
+ hurtled hurtl hurtless hurtless
+ hurtling hurtl hurts hurt
+ husband husband husbanded husband
+ husbandless husbandless husbandry husbandri
+ husbands husband hush hush
+ hushes hush husht husht
+ husks husk huswife huswif
+ huswifes huswif hutch hutch
+ hybla hybla hydra hydra
+ hyen hyen hymen hymen
+ hymenaeus hymenaeu hymn hymn
+ hymns hymn hyperboles hyperbol
+ hyperbolical hyperbol hyperion hyperion
+ hypocrisy hypocrisi hypocrite hypocrit
+ hypocrites hypocrit hyrcan hyrcan
+ hyrcania hyrcania hyrcanian hyrcanian
+ hyssop hyssop hysterica hysterica
+ i i iachimo iachimo
+ iaculis iaculi iago iago
+ iament iament ibat ibat
+ icarus icaru ice ic
+ iceland iceland ici ici
+ icicle icicl icicles icicl
+ icy ici idea idea
+ ideas idea idem idem
+ iden iden ides id
+ idiot idiot idiots idiot
+ idle idl idleness idl
+ idles idl idly idli
+ idol idol idolatrous idolatr
+ idolatry idolatri ield ield
+ if if ifs if
+ ignis igni ignoble ignobl
+ ignobly ignobl ignominious ignomini
+ ignominy ignomini ignomy ignomi
+ ignorance ignor ignorant ignor
+ ii ii iii iii
+ iiii iiii il il
+ ilbow ilbow ild ild
+ ilion ilion ilium ilium
+ ill ill illegitimate illegitim
+ illiterate illiter illness ill
+ illo illo ills ill
+ illume illum illumin illumin
+ illuminate illumin illumineth illumineth
+ illusion illus illusions illus
+ illustrate illustr illustrated illustr
+ illustrious illustri illyria illyria
+ illyrian illyrian ils il
+ im im image imag
+ imagery imageri images imag
+ imagin imagin imaginary imaginari
+ imagination imagin imaginations imagin
+ imagine imagin imagining imagin
+ imaginings imagin imbar imbar
+ imbecility imbecil imbrue imbru
+ imitari imitari imitate imit
+ imitated imit imitation imit
+ imitations imit immaculate immacul
+ immanity imman immask immask
+ immaterial immateri immediacy immediaci
+ immediate immedi immediately immedi
+ imminence immin imminent immin
+ immoderate immoder immoderately immoder
+ immodest immodest immoment immoment
+ immortal immort immortaliz immortaliz
+ immortally immort immur immur
+ immured immur immures immur
+ imogen imogen imp imp
+ impaint impaint impair impair
+ impairing impair impale impal
+ impaled impal impanelled impanel
+ impart impart imparted impart
+ impartial imparti impartment impart
+ imparts impart impasted impast
+ impatience impati impatient impati
+ impatiently impati impawn impawn
+ impeach impeach impeached impeach
+ impeachment impeach impeachments impeach
+ impedes imped impediment impedi
+ impediments impedi impenetrable impenetr
+ imperator imper imperceiverant imperceiver
+ imperfect imperfect imperfection imperfect
+ imperfections imperfect imperfectly imperfectli
+ imperial imperi imperious imperi
+ imperiously imperi impertinency impertin
+ impertinent impertin impeticos impetico
+ impetuosity impetuos impetuous impetu
+ impieties impieti impiety impieti
+ impious impiou implacable implac
+ implements implement implies impli
+ implor implor implorators implor
+ implore implor implored implor
+ imploring implor impon impon
+ import import importance import
+ importancy import important import
+ importantly importantli imported import
+ importeth importeth importing import
+ importless importless imports import
+ importun importun importunacy importunaci
+ importunate importun importune importun
+ importunes importun importunity importun
+ impos impo impose impos
+ imposed impos imposition imposit
+ impositions imposit impossibilities imposs
+ impossibility imposs impossible imposs
+ imposthume imposthum impostor impostor
+ impostors impostor impotence impot
+ impotent impot impounded impound
+ impregnable impregn imprese impres
+ impress impress impressed impress
+ impressest impressest impression impress
+ impressure impressur imprimendum imprimendum
+ imprimis imprimi imprint imprint
+ imprinted imprint imprison imprison
+ imprisoned imprison imprisoning imprison
+ imprisonment imprison improbable improb
+ improper improp improve improv
+ improvident improvid impudence impud
+ impudency impud impudent impud
+ impudently impud impudique impudiqu
+ impugn impugn impugns impugn
+ impure impur imputation imput
+ impute imput in in
+ inaccessible inaccess inaidable inaid
+ inaudible inaud inauspicious inauspici
+ incaged incag incantations incant
+ incapable incap incardinate incardin
+ incarnadine incarnadin incarnate incarn
+ incarnation incarn incens incen
+ incense incens incensed incens
+ incensement incens incenses incens
+ incensing incens incertain incertain
+ incertainties incertainti incertainty incertainti
+ incessant incess incessantly incessantli
+ incest incest incestuous incestu
+ inch inch incharitable incharit
+ inches inch incidency incid
+ incident incid incision incis
+ incite incit incites incit
+ incivil incivil incivility incivil
+ inclin inclin inclinable inclin
+ inclination inclin incline inclin
+ inclined inclin inclines inclin
+ inclining inclin inclips inclip
+ include includ included includ
+ includes includ inclusive inclus
+ incomparable incompar incomprehensible incomprehens
+ inconsiderate inconsider inconstancy inconst
+ inconstant inconst incontinency incontin
+ incontinent incontin incontinently incontin
+ inconvenience inconveni inconveniences inconveni
+ inconvenient inconveni incony inconi
+ incorporate incorpor incorps incorp
+ incorrect incorrect increas increa
+ increase increas increases increas
+ increaseth increaseth increasing increas
+ incredible incred incredulous incredul
+ incur incur incurable incur
+ incurr incurr incurred incur
+ incursions incurs ind ind
+ inde ind indebted indebt
+ indeed inde indent indent
+ indented indent indenture indentur
+ indentures indentur index index
+ indexes index india india
+ indian indian indict indict
+ indicted indict indictment indict
+ indies indi indifferency indiffer
+ indifferent indiffer indifferently indiffer
+ indigent indig indigest indigest
+ indigested indigest indign indign
+ indignation indign indignations indign
+ indigne indign indignities indign
+ indignity indign indirect indirect
+ indirection indirect indirections indirect
+ indirectly indirectli indiscreet indiscreet
+ indiscretion indiscret indispos indispo
+ indisposition indisposit indissoluble indissolubl
+ indistinct indistinct indistinguish indistinguish
+ indistinguishable indistinguish indited indit
+ individable individ indrench indrench
+ indu indu indubitate indubit
+ induc induc induce induc
+ induced induc inducement induc
+ induction induct inductions induct
+ indue indu indued indu
+ indues indu indulgence indulg
+ indulgences indulg indulgent indulg
+ indurance indur industrious industri
+ industriously industri industry industri
+ inequality inequ inestimable inestim
+ inevitable inevit inexecrable inexecr
+ inexorable inexor inexplicable inexplic
+ infallible infal infallibly infal
+ infamonize infamon infamous infam
+ infamy infami infancy infanc
+ infant infant infants infant
+ infect infect infected infect
+ infecting infect infection infect
+ infections infect infectious infecti
+ infectiously infecti infects infect
+ infer infer inference infer
+ inferior inferior inferiors inferior
+ infernal infern inferr inferr
+ inferreth inferreth inferring infer
+ infest infest infidel infidel
+ infidels infidel infinite infinit
+ infinitely infinit infinitive infinit
+ infirm infirm infirmities infirm
+ infirmity infirm infixed infix
+ infixing infix inflam inflam
+ inflame inflam inflaming inflam
+ inflammation inflamm inflict inflict
+ infliction inflict influence influenc
+ influences influenc infold infold
+ inform inform informal inform
+ information inform informations inform
+ informed inform informer inform
+ informs inform infortunate infortun
+ infring infr infringe infring
+ infringed infring infus infu
+ infuse infus infused infus
+ infusing infus infusion infus
+ ingener ingen ingenious ingeni
+ ingeniously ingeni inglorious inglori
+ ingots ingot ingraffed ingraf
+ ingraft ingraft ingrate ingrat
+ ingrated ingrat ingrateful ingrat
+ ingratitude ingratitud ingratitudes ingratitud
+ ingredient ingredi ingredients ingredi
+ ingross ingross inhabit inhabit
+ inhabitable inhabit inhabitants inhabit
+ inhabited inhabit inhabits inhabit
+ inhearse inhears inhearsed inhears
+ inherent inher inherit inherit
+ inheritance inherit inherited inherit
+ inheriting inherit inheritor inheritor
+ inheritors inheritor inheritrix inheritrix
+ inherits inherit inhibited inhibit
+ inhibition inhibit inhoop inhoop
+ inhuman inhuman iniquities iniqu
+ iniquity iniqu initiate initi
+ injointed injoint injunction injunct
+ injunctions injunct injur injur
+ injure injur injurer injur
+ injuries injuri injurious injuri
+ injury injuri injustice injustic
+ ink ink inkhorn inkhorn
+ inkle inkl inkles inkl
+ inkling inkl inky inki
+ inlaid inlaid inland inland
+ inlay inlai inly inli
+ inmost inmost inn inn
+ inner inner innkeeper innkeep
+ innocence innoc innocency innoc
+ innocent innoc innocents innoc
+ innovation innov innovator innov
+ inns inn innumerable innumer
+ inoculate inocul inordinate inordin
+ inprimis inprimi inquir inquir
+ inquire inquir inquiry inquiri
+ inquisition inquisit inquisitive inquisit
+ inroads inroad insane insan
+ insanie insani insatiate insati
+ insconce insconc inscrib inscrib
+ inscription inscript inscriptions inscript
+ inscroll inscrol inscrutable inscrut
+ insculp insculp insculpture insculptur
+ insensible insens inseparable insepar
+ inseparate insepar insert insert
+ inserted insert inset inset
+ inshell inshel inshipp inshipp
+ inside insid insinewed insinew
+ insinuate insinu insinuateth insinuateth
+ insinuating insinu insinuation insinu
+ insisted insist insisting insist
+ insisture insistur insociable insoci
+ insolence insol insolent insol
+ insomuch insomuch inspir inspir
+ inspiration inspir inspirations inspir
+ inspire inspir inspired inspir
+ install instal installed instal
+ instalment instal instance instanc
+ instances instanc instant instant
+ instantly instantli instate instat
+ instead instead insteeped insteep
+ instigate instig instigated instig
+ instigation instig instigations instig
+ instigator instig instinct instinct
+ instinctively instinct institute institut
+ institutions institut instruct instruct
+ instructed instruct instruction instruct
+ instructions instruct instructs instruct
+ instrument instrument instrumental instrument
+ instruments instrument insubstantial insubstanti
+ insufficience insuffici insufficiency insuffici
+ insult insult insulted insult
+ insulting insult insultment insult
+ insults insult insupportable insupport
+ insuppressive insuppress insurrection insurrect
+ insurrections insurrect int int
+ integer integ integritas integrita
+ integrity integr intellect intellect
+ intellects intellect intellectual intellectu
+ intelligence intellig intelligencer intelligenc
+ intelligencing intelligenc intelligent intellig
+ intelligis intelligi intelligo intelligo
+ intemperance intemper intemperate intemper
+ intend intend intended intend
+ intendeth intendeth intending intend
+ intendment intend intends intend
+ intenible inten intent intent
+ intention intent intentively intent
+ intents intent inter inter
+ intercept intercept intercepted intercept
+ intercepter intercept interception intercept
+ intercepts intercept intercession intercess
+ intercessors intercessor interchained interchain
+ interchang interchang interchange interchang
+ interchangeably interchang interchangement interchang
+ interchanging interchang interdiction interdict
+ interest interest interim interim
+ interims interim interior interior
+ interjections interject interjoin interjoin
+ interlude interlud intermingle intermingl
+ intermission intermiss intermissive intermiss
+ intermit intermit intermix intermix
+ intermixed intermix interpose interpos
+ interposer interpos interposes interpos
+ interpret interpret interpretation interpret
+ interpreted interpret interpreter interpret
+ interpreters interpret interprets interpret
+ interr interr interred inter
+ interrogatories interrogatori interrupt interrupt
+ interrupted interrupt interrupter interrupt
+ interruptest interruptest interruption interrupt
+ interrupts interrupt intertissued intertissu
+ intervallums intervallum interview interview
+ intestate intest intestine intestin
+ intil intil intimate intim
+ intimation intim intitled intitl
+ intituled intitul into into
+ intolerable intoler intoxicates intox
+ intreasured intreasur intreat intreat
+ intrench intrench intrenchant intrench
+ intricate intric intrinse intrins
+ intrinsicate intrins intrude intrud
+ intruder intrud intruding intrud
+ intrusion intrus inundation inund
+ inure inur inurn inurn
+ invade invad invades invad
+ invasion invas invasive invas
+ invectively invect invectives invect
+ inveigled inveigl invent invent
+ invented invent invention invent
+ inventions invent inventor inventor
+ inventorially inventori inventoried inventori
+ inventors inventor inventory inventori
+ inverness inver invert invert
+ invest invest invested invest
+ investing invest investments invest
+ inveterate inveter invincible invinc
+ inviolable inviol invised invis
+ invisible invis invitation invit
+ invite invit invited invit
+ invites invit inviting invit
+ invitis inviti invocate invoc
+ invocation invoc invoke invok
+ invoked invok invulnerable invulner
+ inward inward inwardly inwardli
+ inwardness inward inwards inward
+ ionia ionia ionian ionian
+ ipse ips ipswich ipswich
+ ira ira irae ira
+ iras ira ire ir
+ ireful ir ireland ireland
+ iris iri irish irish
+ irishman irishman irishmen irishmen
+ irks irk irksome irksom
+ iron iron irons iron
+ irreconcil irreconcil irrecoverable irrecover
+ irregular irregular irregulous irregul
+ irreligious irreligi irremovable irremov
+ irreparable irrepar irresolute irresolut
+ irrevocable irrevoc is is
+ isabel isabel isabella isabella
+ isbel isbel isbels isbel
+ iscariot iscariot ise is
+ ish ish isidore isidor
+ isis isi island island
+ islander island islanders island
+ islands island isle isl
+ isles isl israel israel
+ issu issu issue issu
+ issued issu issueless issueless
+ issues issu issuing issu
+ ist ist ista ista
+ it it italian italian
+ italy itali itch itch
+ itches itch itching itch
+ item item items item
+ iteration iter ithaca ithaca
+ its it itself itself
+ itshall itshal iv iv
+ ivory ivori ivy ivi
+ iwis iwi ix ix
+ j j jacet jacet
+ jack jack jackanapes jackanap
+ jacks jack jacksauce jacksauc
+ jackslave jackslav jacob jacob
+ jade jade jaded jade
+ jades jade jail jail
+ jakes jake jamany jamani
+ james jame jamy jami
+ jane jane jangled jangl
+ jangling jangl january januari
+ janus janu japhet japhet
+ jaquenetta jaquenetta jaques jaqu
+ jar jar jarring jar
+ jars jar jarteer jarteer
+ jasons jason jaunce jaunc
+ jauncing jaunc jaundice jaundic
+ jaundies jaundi jaw jaw
+ jawbone jawbon jaws jaw
+ jay jai jays jai
+ jc jc je je
+ jealous jealou jealousies jealousi
+ jealousy jealousi jeer jeer
+ jeering jeer jelly jelli
+ jenny jenni jeopardy jeopardi
+ jephtha jephtha jephthah jephthah
+ jerkin jerkin jerkins jerkin
+ jerks jerk jeronimy jeronimi
+ jerusalem jerusalem jeshu jeshu
+ jesses jess jessica jessica
+ jest jest jested jest
+ jester jester jesters jester
+ jesting jest jests jest
+ jesu jesu jesus jesu
+ jet jet jets jet
+ jew jew jewel jewel
+ jeweller jewel jewels jewel
+ jewess jewess jewish jewish
+ jewry jewri jews jew
+ jezebel jezebel jig jig
+ jigging jig jill jill
+ jills jill jingling jingl
+ joan joan job job
+ jockey jockei jocund jocund
+ jog jog jogging jog
+ john john johns john
+ join join joinder joinder
+ joined join joiner joiner
+ joineth joineth joins join
+ joint joint jointed joint
+ jointing joint jointly jointli
+ jointress jointress joints joint
+ jointure jointur jollity jolliti
+ jolly jolli jolt jolt
+ joltheads jolthead jordan jordan
+ joseph joseph joshua joshua
+ jot jot jour jour
+ jourdain jourdain journal journal
+ journey journei journeying journei
+ journeyman journeyman journeymen journeymen
+ journeys journei jove jove
+ jovem jovem jovial jovial
+ jowl jowl jowls jowl
+ joy joi joyed joi
+ joyful joy joyfully joyfulli
+ joyless joyless joyous joyou
+ joys joi juan juan
+ jud jud judas juda
+ judases judas jude jude
+ judg judg judge judg
+ judged judg judgement judgement
+ judges judg judgest judgest
+ judging judg judgment judgment
+ judgments judgment judicious judici
+ jug jug juggle juggl
+ juggled juggl juggler juggler
+ jugglers juggler juggling juggl
+ jugs jug juice juic
+ juiced juic jul jul
+ jule jule julia julia
+ juliet juliet julietta julietta
+ julio julio julius juliu
+ july juli jump jump
+ jumpeth jumpeth jumping jump
+ jumps jump june june
+ junes june junior junior
+ junius juniu junkets junket
+ juno juno jupiter jupit
+ jure jure jurement jurement
+ jurisdiction jurisdict juror juror
+ jurors juror jury juri
+ jurymen jurymen just just
+ justeius justeiu justest justest
+ justice justic justicer justic
+ justicers justic justices justic
+ justification justif justified justifi
+ justify justifi justle justl
+ justled justl justles justl
+ justling justl justly justli
+ justness just justs just
+ jutting jut jutty jutti
+ juvenal juven kam kam
+ kate kate kated kate
+ kates kate katharine katharin
+ katherina katherina katherine katherin
+ kecksies kecksi keech keech
+ keel keel keels keel
+ keen keen keenness keen
+ keep keep keepdown keepdown
+ keeper keeper keepers keeper
+ keepest keepest keeping keep
+ keeps keep keiser keiser
+ ken ken kendal kendal
+ kennel kennel kent kent
+ kentish kentish kentishman kentishman
+ kentishmen kentishmen kept kept
+ kerchief kerchief kerely kere
+ kern kern kernal kernal
+ kernel kernel kernels kernel
+ kerns kern kersey kersei
+ kettle kettl kettledrum kettledrum
+ kettledrums kettledrum key kei
+ keys kei kibe kibe
+ kibes kibe kick kick
+ kicked kick kickshaws kickshaw
+ kickshawses kickshaws kicky kicki
+ kid kid kidney kidnei
+ kikely kike kildare kildar
+ kill kill killed kill
+ killer killer killeth killeth
+ killing kill killingworth killingworth
+ kills kill kiln kiln
+ kimbolton kimbolton kin kin
+ kind kind kinder kinder
+ kindest kindest kindle kindl
+ kindled kindl kindless kindless
+ kindlier kindlier kindling kindl
+ kindly kindli kindness kind
+ kindnesses kind kindred kindr
+ kindreds kindr kinds kind
+ kine kine king king
+ kingdom kingdom kingdoms kingdom
+ kingly kingli kings king
+ kinred kinr kins kin
+ kinsman kinsman kinsmen kinsmen
+ kinswoman kinswoman kirtle kirtl
+ kirtles kirtl kiss kiss
+ kissed kiss kisses kiss
+ kissing kiss kitchen kitchen
+ kitchens kitchen kite kite
+ kites kite kitten kitten
+ kj kj kl kl
+ klll klll knack knack
+ knacks knack knapp knapp
+ knav knav knave knave
+ knaveries knaveri knavery knaveri
+ knaves knave knavish knavish
+ knead knead kneaded knead
+ kneading knead knee knee
+ kneel kneel kneeling kneel
+ kneels kneel knees knee
+ knell knell knew knew
+ knewest knewest knife knife
+ knight knight knighted knight
+ knighthood knighthood knighthoods knighthood
+ knightly knightli knights knight
+ knit knit knits knit
+ knitters knitter knitteth knitteth
+ knives knive knobs knob
+ knock knock knocking knock
+ knocks knock knog knog
+ knoll knoll knot knot
+ knots knot knotted knot
+ knotty knotti know know
+ knower knower knowest knowest
+ knowing know knowingly knowingli
+ knowings know knowledge knowledg
+ known known knows know
+ l l la la
+ laban laban label label
+ labell label labienus labienu
+ labio labio labor labor
+ laboring labor labors labor
+ labour labour laboured labour
+ labourer labour labourers labour
+ labouring labour labours labour
+ laboursome laboursom labras labra
+ labyrinth labyrinth lac lac
+ lace lace laced lace
+ lacedaemon lacedaemon laces lace
+ lacies laci lack lack
+ lackbeard lackbeard lacked lack
+ lackey lackei lackeying lackei
+ lackeys lackei lacking lack
+ lacks lack lad lad
+ ladder ladder ladders ladder
+ lade lade laden laden
+ ladies ladi lading lade
+ lads lad lady ladi
+ ladybird ladybird ladyship ladyship
+ ladyships ladyship laer laer
+ laertes laert lafeu lafeu
+ lag lag lagging lag
+ laid laid lain lain
+ laissez laissez lake lake
+ lakes lake lakin lakin
+ lam lam lamb lamb
+ lambert lambert lambkin lambkin
+ lambkins lambkin lambs lamb
+ lame lame lamely lame
+ lameness lame lament lament
+ lamentable lament lamentably lament
+ lamentation lament lamentations lament
+ lamented lament lamenting lament
+ lamentings lament laments lament
+ lames lame laming lame
+ lammas lamma lammastide lammastid
+ lamound lamound lamp lamp
+ lampass lampass lamps lamp
+ lanc lanc lancaster lancast
+ lance lanc lances lanc
+ lanceth lanceth lanch lanch
+ land land landed land
+ landing land landless landless
+ landlord landlord landmen landmen
+ lands land lane lane
+ lanes lane langage langag
+ langley langlei langton langton
+ language languag languageless languageless
+ languages languag langues langu
+ languish languish languished languish
+ languishes languish languishing languish
+ languishings languish languishment languish
+ languor languor lank lank
+ lantern lantern lanterns lantern
+ lanthorn lanthorn lap lap
+ lapis lapi lapland lapland
+ lapp lapp laps lap
+ lapse laps lapsed laps
+ lapsing laps lapwing lapw
+ laquais laquai larded lard
+ larder larder larding lard
+ lards lard large larg
+ largely larg largeness larg
+ larger larger largess largess
+ largest largest lark lark
+ larks lark larron larron
+ lartius lartiu larum larum
+ larums larum las la
+ lascivious lascivi lash lash
+ lass lass lasses lass
+ last last lasted last
+ lasting last lastly lastli
+ lasts last latch latch
+ latches latch late late
+ lated late lately late
+ later later latest latest
+ lath lath latin latin
+ latten latten latter latter
+ lattice lattic laud laud
+ laudable laudabl laudis laudi
+ laugh laugh laughable laughabl
+ laughed laugh laugher laugher
+ laughest laughest laughing laugh
+ laughs laugh laughter laughter
+ launce launc launcelot launcelot
+ launces launc launch launch
+ laund laund laundress laundress
+ laundry laundri laur laur
+ laura laura laurel laurel
+ laurels laurel laurence laurenc
+ laus lau lavache lavach
+ lave lave lavee lave
+ lavender lavend lavina lavina
+ lavinia lavinia lavish lavish
+ lavishly lavishli lavolt lavolt
+ lavoltas lavolta law law
+ lawful law lawfully lawfulli
+ lawless lawless lawlessly lawlessli
+ lawn lawn lawns lawn
+ lawrence lawrenc laws law
+ lawyer lawyer lawyers lawyer
+ lay lai layer layer
+ layest layest laying lai
+ lays lai lazar lazar
+ lazars lazar lazarus lazaru
+ lazy lazi lc lc
+ ld ld ldst ldst
+ le le lead lead
+ leaden leaden leader leader
+ leaders leader leadest leadest
+ leading lead leads lead
+ leaf leaf leagu leagu
+ league leagu leagued leagu
+ leaguer leaguer leagues leagu
+ leah leah leak leak
+ leaky leaki lean lean
+ leander leander leaner leaner
+ leaning lean leanness lean
+ leans lean leap leap
+ leaped leap leaping leap
+ leaps leap leapt leapt
+ lear lear learn learn
+ learned learn learnedly learnedli
+ learning learn learnings learn
+ learns learn learnt learnt
+ leas lea lease leas
+ leases leas leash leash
+ leasing leas least least
+ leather leather leathern leathern
+ leav leav leave leav
+ leaven leaven leavening leaven
+ leaver leaver leaves leav
+ leaving leav leavy leavi
+ lecher lecher lecherous lecher
+ lechers lecher lechery lecheri
+ lecon lecon lecture lectur
+ lectures lectur led led
+ leda leda leech leech
+ leeches leech leek leek
+ leeks leek leer leer
+ leers leer lees lee
+ leese lees leet leet
+ leets leet left left
+ leg leg legacies legaci
+ legacy legaci legate legat
+ legatine legatin lege lege
+ legerity leger leges lege
+ legg legg legion legion
+ legions legion legitimate legitim
+ legitimation legitim legs leg
+ leicester leicest leicestershire leicestershir
+ leiger leiger leigers leiger
+ leisure leisur leisurely leisur
+ leisures leisur leman leman
+ lemon lemon lena lena
+ lend lend lender lender
+ lending lend lendings lend
+ lends lend length length
+ lengthen lengthen lengthens lengthen
+ lengths length lenity leniti
+ lennox lennox lent lent
+ lenten lenten lentus lentu
+ leo leo leon leon
+ leonardo leonardo leonati leonati
+ leonato leonato leonatus leonatu
+ leontes leont leopard leopard
+ leopards leopard leper leper
+ leperous leper lepidus lepidu
+ leprosy leprosi lequel lequel
+ lers ler les le
+ less less lessen lessen
+ lessens lessen lesser lesser
+ lesson lesson lessoned lesson
+ lessons lesson lest lest
+ lestrake lestrak let let
+ lethargied lethargi lethargies lethargi
+ lethargy lethargi lethe leth
+ lets let lett lett
+ letter letter letters letter
+ letting let lettuce lettuc
+ leur leur leve leve
+ level level levell level
+ levelled level levels level
+ leven leven levers lever
+ leviathan leviathan leviathans leviathan
+ levied levi levies levi
+ levity leviti levy levi
+ levying levi lewd lewd
+ lewdly lewdli lewdness lewd
+ lewdsters lewdster lewis lewi
+ liable liabl liar liar
+ liars liar libbard libbard
+ libelling libel libels libel
+ liberal liber liberality liber
+ liberte libert liberties liberti
+ libertine libertin libertines libertin
+ liberty liberti library librari
+ libya libya licence licenc
+ licens licen license licens
+ licentious licenti lichas licha
+ licio licio lick lick
+ licked lick licker licker
+ lictors lictor lid lid
+ lids lid lie lie
+ lied li lief lief
+ liefest liefest liege lieg
+ liegeman liegeman liegemen liegemen
+ lien lien lies li
+ liest liest lieth lieth
+ lieu lieu lieutenant lieuten
+ lieutenantry lieutenantri lieutenants lieuten
+ lieve liev life life
+ lifeblood lifeblood lifeless lifeless
+ lifelings lifel lift lift
+ lifted lift lifter lifter
+ lifteth lifteth lifting lift
+ lifts lift lig lig
+ ligarius ligariu liggens liggen
+ light light lighted light
+ lighten lighten lightens lighten
+ lighter lighter lightest lightest
+ lightly lightli lightness light
+ lightning lightn lightnings lightn
+ lights light lik lik
+ like like liked like
+ likeliest likeliest likelihood likelihood
+ likelihoods likelihood likely like
+ likeness like liker liker
+ likes like likest likest
+ likewise likewis liking like
+ likings like lilies lili
+ lily lili lim lim
+ limander limand limb limb
+ limbeck limbeck limbecks limbeck
+ limber limber limbo limbo
+ limbs limb lime lime
+ limed lime limehouse limehous
+ limekilns limekiln limit limit
+ limitation limit limited limit
+ limits limit limn limn
+ limp limp limping limp
+ limps limp lin lin
+ lincoln lincoln lincolnshire lincolnshir
+ line line lineal lineal
+ lineally lineal lineament lineament
+ lineaments lineament lined line
+ linen linen linens linen
+ lines line ling ling
+ lingare lingar linger linger
+ lingered linger lingers linger
+ linguist linguist lining line
+ link link links link
+ linsey linsei linstock linstock
+ linta linta lion lion
+ lionel lionel lioness lioness
+ lions lion lip lip
+ lipp lipp lips lip
+ lipsbury lipsburi liquid liquid
+ liquor liquor liquorish liquorish
+ liquors liquor lirra lirra
+ lisbon lisbon lisp lisp
+ lisping lisp list list
+ listen listen listening listen
+ lists list literatured literatur
+ lither lither litter litter
+ little littl littlest littlest
+ liv liv live live
+ lived live livelier liveli
+ livelihood livelihood livelong livelong
+ lively live liver liver
+ liveries liveri livers liver
+ livery liveri lives live
+ livest livest liveth liveth
+ livia livia living live
+ livings live lizard lizard
+ lizards lizard ll ll
+ lll lll llous llou
+ lnd lnd lo lo
+ loa loa loach loach
+ load load loaden loaden
+ loading load loads load
+ loaf loaf loam loam
+ loan loan loath loath
+ loathe loath loathed loath
+ loather loather loathes loath
+ loathing loath loathly loathli
+ loathness loath loathsome loathsom
+ loathsomeness loathsom loathsomest loathsomest
+ loaves loav lob lob
+ lobbies lobbi lobby lobbi
+ local local lochaber lochab
+ lock lock locked lock
+ locking lock lockram lockram
+ locks lock locusts locust
+ lode lode lodg lodg
+ lodge lodg lodged lodg
+ lodgers lodger lodges lodg
+ lodging lodg lodgings lodg
+ lodovico lodovico lodowick lodowick
+ lofty lofti log log
+ logger logger loggerhead loggerhead
+ loggerheads loggerhead loggets logget
+ logic logic logs log
+ loins loin loiter loiter
+ loiterer loiter loiterers loiter
+ loitering loiter lolling loll
+ lolls loll lombardy lombardi
+ london london londoners london
+ lone lone loneliness loneli
+ lonely lone long long
+ longaville longavil longboat longboat
+ longed long longer longer
+ longest longest longeth longeth
+ longing long longings long
+ longly longli longs long
+ longtail longtail loo loo
+ loof loof look look
+ looked look looker looker
+ lookers looker lookest lookest
+ looking look looks look
+ loon loon loop loop
+ loos loo loose loos
+ loosed loos loosely loos
+ loosen loosen loosing loos
+ lop lop lopp lopp
+ loquitur loquitur lord lord
+ lorded lord lording lord
+ lordings lord lordliness lordli
+ lordly lordli lords lord
+ lordship lordship lordships lordship
+ lorenzo lorenzo lorn lorn
+ lorraine lorrain lorship lorship
+ los lo lose lose
+ loser loser losers loser
+ loses lose losest losest
+ loseth loseth losing lose
+ loss loss losses loss
+ lost lost lot lot
+ lots lot lott lott
+ lottery lotteri loud loud
+ louder louder loudly loudli
+ lour lour loureth loureth
+ louring lour louse lous
+ louses lous lousy lousi
+ lout lout louted lout
+ louts lout louvre louvr
+ lov lov love love
+ loved love lovedst lovedst
+ lovel lovel lovelier loveli
+ loveliness loveli lovell lovel
+ lovely love lover lover
+ lovered lover lovers lover
+ loves love lovest lovest
+ loveth loveth loving love
+ lovingly lovingli low low
+ lowe low lower lower
+ lowest lowest lowing low
+ lowliness lowli lowly lowli
+ lown lown lowness low
+ loyal loyal loyally loyal
+ loyalties loyalti loyalty loyalti
+ lozel lozel lt lt
+ lubber lubber lubberly lubberli
+ luc luc luccicos luccico
+ luce luce lucentio lucentio
+ luces luce lucetta lucetta
+ luciana luciana lucianus lucianu
+ lucifer lucif lucifier lucifi
+ lucilius luciliu lucina lucina
+ lucio lucio lucius luciu
+ luck luck luckier luckier
+ luckiest luckiest luckily luckili
+ luckless luckless lucky lucki
+ lucre lucr lucrece lucrec
+ lucretia lucretia lucullius luculliu
+ lucullus lucullu lucy luci
+ lud lud ludlow ludlow
+ lug lug lugg lugg
+ luggage luggag luke luke
+ lukewarm lukewarm lull lull
+ lulla lulla lullaby lullabi
+ lulls lull lumbert lumbert
+ lump lump lumpish lumpish
+ luna luna lunacies lunaci
+ lunacy lunaci lunatic lunat
+ lunatics lunat lunes lune
+ lungs lung lupercal luperc
+ lurch lurch lure lure
+ lurk lurk lurketh lurketh
+ lurking lurk lurks lurk
+ luscious lusciou lush lush
+ lust lust lusted lust
+ luster luster lustful lust
+ lustier lustier lustiest lustiest
+ lustig lustig lustihood lustihood
+ lustily lustili lustre lustr
+ lustrous lustrou lusts lust
+ lusty lusti lute lute
+ lutes lute lutestring lutestr
+ lutheran lutheran luxurious luxuri
+ luxuriously luxuri luxury luxuri
+ ly ly lycaonia lycaonia
+ lycurguses lycurgus lydia lydia
+ lye lye lyen lyen
+ lying ly lym lym
+ lymoges lymog lynn lynn
+ lysander lysand m m
+ ma ma maan maan
+ mab mab macbeth macbeth
+ maccabaeus maccabaeu macdonwald macdonwald
+ macduff macduff mace mace
+ macedon macedon maces mace
+ machiavel machiavel machination machin
+ machinations machin machine machin
+ mack mack macmorris macmorri
+ maculate macul maculation macul
+ mad mad madam madam
+ madame madam madams madam
+ madcap madcap madded mad
+ madding mad made made
+ madeira madeira madly madli
+ madman madman madmen madmen
+ madness mad madonna madonna
+ madrigals madrig mads mad
+ maecenas maecena maggot maggot
+ maggots maggot magic magic
+ magical magic magician magician
+ magistrate magistr magistrates magistr
+ magnanimity magnanim magnanimous magnanim
+ magni magni magnifi magnifi
+ magnificence magnific magnificent magnific
+ magnifico magnifico magnificoes magnifico
+ magnus magnu mahomet mahomet
+ mahu mahu maid maid
+ maiden maiden maidenhead maidenhead
+ maidenheads maidenhead maidenhood maidenhood
+ maidenhoods maidenhood maidenliest maidenliest
+ maidenly maidenli maidens maiden
+ maidhood maidhood maids maid
+ mail mail mailed mail
+ mails mail maim maim
+ maimed maim maims maim
+ main main maincourse maincours
+ maine main mainly mainli
+ mainmast mainmast mains main
+ maintain maintain maintained maintain
+ maintains maintain maintenance mainten
+ mais mai maison maison
+ majestas majesta majestee majeste
+ majestic majest majestical majest
+ majestically majest majesties majesti
+ majesty majesti major major
+ majority major mak mak
+ make make makeless makeless
+ maker maker makers maker
+ makes make makest makest
+ maketh maketh making make
+ makings make mal mal
+ mala mala maladies maladi
+ malady maladi malapert malapert
+ malcolm malcolm malcontent malcont
+ malcontents malcont male male
+ maledictions maledict malefactions malefact
+ malefactor malefactor malefactors malefactor
+ males male malevolence malevol
+ malevolent malevol malhecho malhecho
+ malice malic malicious malici
+ maliciously malici malign malign
+ malignancy malign malignant malign
+ malignantly malignantli malkin malkin
+ mall mall mallard mallard
+ mallet mallet mallows mallow
+ malmsey malmsei malt malt
+ maltworms maltworm malvolio malvolio
+ mamillius mamilliu mammering mammer
+ mammet mammet mammets mammet
+ mammock mammock man man
+ manacle manacl manacles manacl
+ manage manag managed manag
+ manager manag managing manag
+ manakin manakin manchus manchu
+ mandate mandat mandragora mandragora
+ mandrake mandrak mandrakes mandrak
+ mane mane manent manent
+ manes mane manet manet
+ manfully manfulli mangle mangl
+ mangled mangl mangles mangl
+ mangling mangl mangy mangi
+ manhood manhood manhoods manhood
+ manifest manifest manifested manifest
+ manifests manifest manifold manifold
+ manifoldly manifoldli manka manka
+ mankind mankind manlike manlik
+ manly manli mann mann
+ manna manna manner manner
+ mannerly mannerli manners manner
+ manningtree manningtre mannish mannish
+ manor manor manors manor
+ mans man mansion mansion
+ mansionry mansionri mansions mansion
+ manslaughter manslaught mantle mantl
+ mantled mantl mantles mantl
+ mantua mantua mantuan mantuan
+ manual manual manure manur
+ manured manur manus manu
+ many mani map map
+ mapp mapp maps map
+ mar mar marble marbl
+ marbled marbl marcade marcad
+ marcellus marcellu march march
+ marches march marcheth marcheth
+ marching march marchioness marchio
+ marchpane marchpan marcians marcian
+ marcius marciu marcus marcu
+ mardian mardian mare mare
+ mares mare marg marg
+ margarelon margarelon margaret margaret
+ marge marg margent margent
+ margery margeri maria maria
+ marian marian mariana mariana
+ maries mari marigold marigold
+ mariner marin mariners marin
+ maritime maritim marjoram marjoram
+ mark mark marked mark
+ market market marketable market
+ marketplace marketplac markets market
+ marking mark markman markman
+ marks mark marl marl
+ marle marl marmoset marmoset
+ marquess marquess marquis marqui
+ marr marr marriage marriag
+ marriages marriag married marri
+ marries marri marring mar
+ marrow marrow marrowless marrowless
+ marrows marrow marry marri
+ marrying marri mars mar
+ marseilles marseil marsh marsh
+ marshal marshal marshalsea marshalsea
+ marshalship marshalship mart mart
+ marted mart martem martem
+ martext martext martial martial
+ martin martin martino martino
+ martius martiu martlemas martlema
+ martlet martlet marts mart
+ martyr martyr martyrs martyr
+ marullus marullu marv marv
+ marvel marvel marvell marvel
+ marvellous marvel marvellously marvel
+ marvels marvel mary mari
+ mas ma masculine masculin
+ masham masham mask mask
+ masked mask masker masker
+ maskers masker masking mask
+ masks mask mason mason
+ masonry masonri masons mason
+ masque masqu masquers masquer
+ masques masqu masquing masqu
+ mass mass massacre massacr
+ massacres massacr masses mass
+ massy massi mast mast
+ mastcr mastcr master master
+ masterdom masterdom masterest masterest
+ masterless masterless masterly masterli
+ masterpiece masterpiec masters master
+ mastership mastership mastic mastic
+ mastiff mastiff mastiffs mastiff
+ masts mast match match
+ matches match matcheth matcheth
+ matching match matchless matchless
+ mate mate mated mate
+ mater mater material materi
+ mates mate mathematics mathemat
+ matin matin matron matron
+ matrons matron matter matter
+ matters matter matthew matthew
+ mattock mattock mattress mattress
+ mature matur maturity matur
+ maud maud maudlin maudlin
+ maugre maugr maul maul
+ maund maund mauri mauri
+ mauritania mauritania mauvais mauvai
+ maw maw maws maw
+ maxim maxim may mai
+ mayday maydai mayest mayest
+ mayor mayor maypole maypol
+ mayst mayst maz maz
+ maze maze mazed maze
+ mazes maze mazzard mazzard
+ me me meacock meacock
+ mead mead meadow meadow
+ meadows meadow meads mead
+ meagre meagr meal meal
+ meals meal mealy meali
+ mean mean meanders meander
+ meaner meaner meanest meanest
+ meaneth meaneth meaning mean
+ meanings mean meanly meanli
+ means mean meant meant
+ meantime meantim meanwhile meanwhil
+ measles measl measur measur
+ measurable measur measure measur
+ measured measur measureless measureless
+ measures measur measuring measur
+ meat meat meats meat
+ mechanic mechan mechanical mechan
+ mechanicals mechan mechanics mechan
+ mechante mechant med med
+ medal medal meddle meddl
+ meddler meddler meddling meddl
+ mede mede medea medea
+ media media mediation mediat
+ mediators mediat medice medic
+ medicinal medicin medicine medicin
+ medicines medicin meditate medit
+ meditates medit meditating medit
+ meditation medit meditations medit
+ mediterranean mediterranean mediterraneum mediterraneum
+ medlar medlar medlars medlar
+ meed meed meeds meed
+ meek meek meekly meekli
+ meekness meek meet meet
+ meeter meeter meetest meetest
+ meeting meet meetings meet
+ meetly meetli meetness meet
+ meets meet meg meg
+ mehercle mehercl meilleur meilleur
+ meiny meini meisen meisen
+ melancholies melancholi melancholy melancholi
+ melford melford mell mell
+ mellifluous melliflu mellow mellow
+ mellowing mellow melodious melodi
+ melody melodi melt melt
+ melted melt melteth melteth
+ melting melt melts melt
+ melun melun member member
+ members member memento memento
+ memorable memor memorandums memorandum
+ memorial memori memorials memori
+ memories memori memoriz memoriz
+ memorize memor memory memori
+ memphis memphi men men
+ menac menac menace menac
+ menaces menac menaphon menaphon
+ menas mena mend mend
+ mended mend mender mender
+ mending mend mends mend
+ menecrates menecr menelaus menelau
+ menenius meneniu mental mental
+ menteith menteith mention mention
+ mentis menti menton menton
+ mephostophilus mephostophilu mer mer
+ mercatante mercatant mercatio mercatio
+ mercenaries mercenari mercenary mercenari
+ mercer mercer merchandise merchandis
+ merchandized merchand merchant merchant
+ merchants merchant mercies merci
+ merciful merci mercifully mercifulli
+ merciless merciless mercurial mercuri
+ mercuries mercuri mercury mercuri
+ mercutio mercutio mercy merci
+ mere mere mered mere
+ merely mere merest merest
+ meridian meridian merit merit
+ merited merit meritorious meritori
+ merits merit merlin merlin
+ mermaid mermaid mermaids mermaid
+ merops merop merrier merrier
+ merriest merriest merrily merrili
+ merriman merriman merriment merriment
+ merriments merriment merriness merri
+ merry merri mervailous mervail
+ mes me mesh mesh
+ meshes mesh mesopotamia mesopotamia
+ mess mess message messag
+ messages messag messala messala
+ messaline messalin messenger messeng
+ messengers messeng messes mess
+ messina messina met met
+ metal metal metals metal
+ metamorphis metamorphi metamorphoses metamorphos
+ metaphor metaphor metaphysical metaphys
+ metaphysics metaphys mete mete
+ metellus metellu meteor meteor
+ meteors meteor meteyard meteyard
+ metheglin metheglin metheglins metheglin
+ methink methink methinks methink
+ method method methods method
+ methought methought methoughts methought
+ metre metr metres metr
+ metropolis metropoli mette mett
+ mettle mettl mettled mettl
+ meus meu mew mew
+ mewed mew mewling mewl
+ mexico mexico mi mi
+ mice mice michael michael
+ michaelmas michaelma micher micher
+ miching mich mickle mickl
+ microcosm microcosm mid mid
+ midas mida middest middest
+ middle middl middleham middleham
+ midnight midnight midriff midriff
+ midst midst midsummer midsumm
+ midway midwai midwife midwif
+ midwives midwiv mienne mienn
+ might might mightful might
+ mightier mightier mightiest mightiest
+ mightily mightili mightiness mighti
+ mightst mightst mighty mighti
+ milan milan milch milch
+ mild mild milder milder
+ mildest mildest mildew mildew
+ mildews mildew mildly mildli
+ mildness mild mile mile
+ miles mile milford milford
+ militarist militarist military militari
+ milk milk milking milk
+ milkmaid milkmaid milks milk
+ milksops milksop milky milki
+ mill mill mille mill
+ miller miller milliner millin
+ million million millioned million
+ millions million mills mill
+ millstones millston milo milo
+ mimic mimic minc minc
+ mince minc minces minc
+ mincing minc mind mind
+ minded mind minding mind
+ mindless mindless minds mind
+ mine mine mineral miner
+ minerals miner minerva minerva
+ mines mine mingle mingl
+ mingled mingl mingling mingl
+ minikin minikin minim minim
+ minime minim minimo minimo
+ minimus minimu mining mine
+ minion minion minions minion
+ minist minist minister minist
+ ministers minist ministration ministr
+ minnow minnow minnows minnow
+ minola minola minority minor
+ minos mino minotaurs minotaur
+ minstrel minstrel minstrels minstrel
+ minstrelsy minstrelsi mint mint
+ mints mint minute minut
+ minutely minut minutes minut
+ minx minx mio mio
+ mir mir mirable mirabl
+ miracle miracl miracles miracl
+ miraculous miracul miranda miranda
+ mire mire mirror mirror
+ mirrors mirror mirth mirth
+ mirthful mirth miry miri
+ mis mi misadventur misadventur
+ misadventure misadventur misanthropos misanthropo
+ misapplied misappli misbecame misbecam
+ misbecom misbecom misbecome misbecom
+ misbegot misbegot misbegotten misbegotten
+ misbeliever misbeliev misbelieving misbeliev
+ misbhav misbhav miscall miscal
+ miscalled miscal miscarried miscarri
+ miscarries miscarri miscarry miscarri
+ miscarrying miscarri mischance mischanc
+ mischances mischanc mischief mischief
+ mischiefs mischief mischievous mischiev
+ misconceived misconceiv misconst misconst
+ misconster misconst misconstruction misconstruct
+ misconstrued misconstru misconstrues misconstru
+ miscreant miscreant miscreate miscreat
+ misdeed misde misdeeds misde
+ misdemean misdemean misdemeanours misdemeanour
+ misdoubt misdoubt misdoubteth misdoubteth
+ misdoubts misdoubt misenum misenum
+ miser miser miserable miser
+ miserably miser misericorde misericord
+ miseries miseri misers miser
+ misery miseri misfortune misfortun
+ misfortunes misfortun misgive misgiv
+ misgives misgiv misgiving misgiv
+ misgoverned misgovern misgovernment misgovern
+ misgraffed misgraf misguide misguid
+ mishap mishap mishaps mishap
+ misheard misheard misinterpret misinterpret
+ mislead mislead misleader mislead
+ misleaders mislead misleading mislead
+ misled misl mislike mislik
+ misord misord misplac misplac
+ misplaced misplac misplaces misplac
+ mispris mispri misprised mispris
+ misprision mispris misprizing mispriz
+ misproud misproud misquote misquot
+ misreport misreport miss miss
+ missed miss misses miss
+ misshap misshap misshapen misshapen
+ missheathed missheath missing miss
+ missingly missingli missions mission
+ missive missiv missives missiv
+ misspoke misspok mist mist
+ mista mista mistak mistak
+ mistake mistak mistaken mistaken
+ mistakes mistak mistaketh mistaketh
+ mistaking mistak mistakings mistak
+ mistemp mistemp mistempered mistemp
+ misterm misterm mistful mist
+ misthink misthink misthought misthought
+ mistletoe mistleto mistook mistook
+ mistreadings mistread mistress mistress
+ mistresses mistress mistresss mistresss
+ mistriship mistriship mistrust mistrust
+ mistrusted mistrust mistrustful mistrust
+ mistrusting mistrust mists mist
+ misty misti misus misu
+ misuse misus misused misus
+ misuses misus mites mite
+ mithridates mithrid mitigate mitig
+ mitigation mitig mix mix
+ mixed mix mixture mixtur
+ mixtures mixtur mm mm
+ mnd mnd moan moan
+ moans moan moat moat
+ moated moat mobled mobl
+ mock mock mockable mockabl
+ mocker mocker mockeries mockeri
+ mockers mocker mockery mockeri
+ mocking mock mocks mock
+ mockvater mockvat mockwater mockwat
+ model model modena modena
+ moderate moder moderately moder
+ moderation moder modern modern
+ modest modest modesties modesti
+ modestly modestli modesty modesti
+ modicums modicum modo modo
+ module modul moe moe
+ moi moi moiety moieti
+ moist moist moisten moisten
+ moisture moistur moldwarp moldwarp
+ mole mole molehill molehil
+ moles mole molest molest
+ molestation molest mollification mollif
+ mollis molli molten molten
+ molto molto mome mome
+ moment moment momentary momentari
+ moming mome mon mon
+ monachum monachum monarch monarch
+ monarchies monarchi monarchize monarch
+ monarcho monarcho monarchs monarch
+ monarchy monarchi monast monast
+ monastery monasteri monastic monast
+ monday mondai monde mond
+ money monei moneys monei
+ mong mong monger monger
+ mongers monger monging mong
+ mongrel mongrel mongrels mongrel
+ mongst mongst monk monk
+ monkey monkei monkeys monkei
+ monks monk monmouth monmouth
+ monopoly monopoli mons mon
+ monsieur monsieur monsieurs monsieur
+ monster monster monsters monster
+ monstrous monstrou monstrously monstrous
+ monstrousness monstrous monstruosity monstruos
+ montacute montacut montage montag
+ montague montagu montagues montagu
+ montano montano montant montant
+ montez montez montferrat montferrat
+ montgomery montgomeri month month
+ monthly monthli months month
+ montjoy montjoi monument monument
+ monumental monument monuments monument
+ mood mood moods mood
+ moody moodi moon moon
+ moonbeams moonbeam moonish moonish
+ moonlight moonlight moons moon
+ moonshine moonshin moonshines moonshin
+ moor moor moorfields moorfield
+ moors moor moorship moorship
+ mop mop mope mope
+ moping mope mopping mop
+ mopsa mopsa moral moral
+ moraler moral morality moral
+ moralize moral mordake mordak
+ more more moreover moreov
+ mores more morgan morgan
+ mori mori morisco morisco
+ morn morn morning morn
+ mornings morn morocco morocco
+ morris morri morrow morrow
+ morrows morrow morsel morsel
+ morsels morsel mort mort
+ mortal mortal mortality mortal
+ mortally mortal mortals mortal
+ mortar mortar mortgaged mortgag
+ mortified mortifi mortifying mortifi
+ mortimer mortim mortimers mortim
+ mortis morti mortise mortis
+ morton morton mose mose
+ moss moss mossgrown mossgrown
+ most most mote mote
+ moth moth mother mother
+ mothers mother moths moth
+ motion motion motionless motionless
+ motions motion motive motiv
+ motives motiv motley motlei
+ mots mot mought mought
+ mould mould moulded mould
+ mouldeth mouldeth moulds mould
+ mouldy mouldi moult moult
+ moulten moulten mounch mounch
+ mounseur mounseur mounsieur mounsieur
+ mount mount mountain mountain
+ mountaineer mountain mountaineers mountain
+ mountainous mountain mountains mountain
+ mountant mountant mountanto mountanto
+ mountebank mountebank mountebanks mountebank
+ mounted mount mounteth mounteth
+ mounting mount mounts mount
+ mourn mourn mourned mourn
+ mourner mourner mourners mourner
+ mournful mourn mournfully mournfulli
+ mourning mourn mourningly mourningli
+ mournings mourn mourns mourn
+ mous mou mouse mous
+ mousetrap mousetrap mousing mous
+ mouth mouth mouthed mouth
+ mouths mouth mov mov
+ movables movabl move move
+ moveable moveabl moveables moveabl
+ moved move mover mover
+ movers mover moves move
+ moveth moveth moving move
+ movingly movingli movousus movousu
+ mow mow mowbray mowbrai
+ mower mower mowing mow
+ mows mow moy moi
+ moys moi moyses moys
+ mrs mr much much
+ muck muck mud mud
+ mudded mud muddied muddi
+ muddy muddi muffins muffin
+ muffl muffl muffle muffl
+ muffled muffl muffler muffler
+ muffling muffl mugger mugger
+ mugs mug mulberries mulberri
+ mulberry mulberri mule mule
+ mules mule muleteers mulet
+ mulier mulier mulieres mulier
+ muliteus muliteu mull mull
+ mulmutius mulmutiu multiplied multipli
+ multiply multipli multiplying multipli
+ multipotent multipot multitude multitud
+ multitudes multitud multitudinous multitudin
+ mum mum mumble mumbl
+ mumbling mumbl mummers mummer
+ mummy mummi mun mun
+ munch munch muniments muniment
+ munition munit murd murd
+ murder murder murdered murder
+ murderer murder murderers murder
+ murdering murder murderous murder
+ murders murder mure mure
+ murk murk murkiest murkiest
+ murky murki murmur murmur
+ murmurers murmur murmuring murmur
+ murrain murrain murray murrai
+ murrion murrion murther murther
+ murtherer murther murtherers murther
+ murthering murther murtherous murther
+ murthers murther mus mu
+ muscadel muscadel muscovites muscovit
+ muscovits muscovit muscovy muscovi
+ muse muse muses muse
+ mush mush mushrooms mushroom
+ music music musical music
+ musician musician musicians musician
+ musics music musing muse
+ musings muse musk musk
+ musket musket muskets musket
+ muskos musko muss muss
+ mussel mussel mussels mussel
+ must must mustachio mustachio
+ mustard mustard mustardseed mustardse
+ muster muster mustering muster
+ musters muster musty musti
+ mutability mutabl mutable mutabl
+ mutation mutat mutations mutat
+ mute mute mutes mute
+ mutest mutest mutine mutin
+ mutineer mutin mutineers mutin
+ mutines mutin mutinies mutini
+ mutinous mutin mutiny mutini
+ mutius mutiu mutter mutter
+ muttered mutter mutton mutton
+ muttons mutton mutual mutual
+ mutualities mutual mutually mutual
+ muzzl muzzl muzzle muzzl
+ muzzled muzzl mv mv
+ mww mww my my
+ mynheers mynheer myrmidon myrmidon
+ myrmidons myrmidon myrtle myrtl
+ myself myself myst myst
+ mysteries mysteri mystery mysteri
+ n n nag nag
+ nage nage nags nag
+ naiads naiad nail nail
+ nails nail nak nak
+ naked nake nakedness naked
+ nal nal nam nam
+ name name named name
+ nameless nameless namely name
+ names name namest namest
+ naming name nan nan
+ nance nanc nap nap
+ nape nape napes nape
+ napkin napkin napkins napkin
+ naples napl napless napless
+ napping nap naps nap
+ narbon narbon narcissus narcissu
+ narines narin narrow narrow
+ narrowly narrowli naso naso
+ nasty nasti nathaniel nathaniel
+ natifs natif nation nation
+ nations nation native nativ
+ nativity nativ natur natur
+ natural natur naturalize natur
+ naturally natur nature natur
+ natured natur natures natur
+ natus natu naught naught
+ naughtily naughtili naughty naughti
+ navarre navarr nave nave
+ navel navel navigation navig
+ navy navi nay nai
+ nayward nayward nayword nayword
+ nazarite nazarit ne ne
+ neaf neaf neamnoins neamnoin
+ neanmoins neanmoin neapolitan neapolitan
+ neapolitans neapolitan near near
+ nearer nearer nearest nearest
+ nearly nearli nearness near
+ neat neat neatly neatli
+ neb neb nebour nebour
+ nebuchadnezzar nebuchadnezzar nec nec
+ necessaries necessari necessarily necessarili
+ necessary necessari necessitied necess
+ necessities necess necessity necess
+ neck neck necklace necklac
+ necks neck nectar nectar
+ ned ned nedar nedar
+ need need needed need
+ needer needer needful need
+ needfull needful needing need
+ needle needl needles needl
+ needless needless needly needli
+ needs need needy needi
+ neer neer neeze neez
+ nefas nefa negation negat
+ negative neg negatives neg
+ neglect neglect neglected neglect
+ neglecting neglect neglectingly neglectingli
+ neglection neglect negligence neglig
+ negligent neglig negotiate negoti
+ negotiations negoti negro negro
+ neigh neigh neighbors neighbor
+ neighbour neighbour neighbourhood neighbourhood
+ neighbouring neighbour neighbourly neighbourli
+ neighbours neighbour neighing neigh
+ neighs neigh neither neither
+ nell nell nemean nemean
+ nemesis nemesi neoptolemus neoptolemu
+ nephew nephew nephews nephew
+ neptune neptun ner ner
+ nereides nereid nerissa nerissa
+ nero nero neroes nero
+ ners ner nerve nerv
+ nerves nerv nervii nervii
+ nervy nervi nessus nessu
+ nest nest nestor nestor
+ nests nest net net
+ nether nether netherlands netherland
+ nets net nettle nettl
+ nettled nettl nettles nettl
+ neuter neuter neutral neutral
+ nev nev never never
+ nevil nevil nevils nevil
+ new new newborn newborn
+ newer newer newest newest
+ newgate newgat newly newli
+ newness new news new
+ newsmongers newsmong newt newt
+ newts newt next next
+ nibbling nibbl nicanor nicanor
+ nice nice nicely nice
+ niceness nice nicer nicer
+ nicety niceti nicholas nichola
+ nick nick nickname nicknam
+ nicks nick niece niec
+ nieces niec niggard niggard
+ niggarding niggard niggardly niggardli
+ nigh nigh night night
+ nightcap nightcap nightcaps nightcap
+ nighted night nightgown nightgown
+ nightingale nightingal nightingales nightingal
+ nightly nightli nightmare nightmar
+ nights night nightwork nightwork
+ nihil nihil nile nile
+ nill nill nilus nilu
+ nimble nimbl nimbleness nimbl
+ nimbler nimbler nimbly nimbl
+ nine nine nineteen nineteen
+ ning ning ningly ningli
+ ninny ninni ninth ninth
+ ninus ninu niobe niob
+ niobes niob nip nip
+ nipp nipp nipping nip
+ nipple nippl nips nip
+ nit nit nly nly
+ nnight nnight nnights nnight
+ no no noah noah
+ nob nob nobility nobil
+ nobis nobi noble nobl
+ nobleman nobleman noblemen noblemen
+ nobleness nobl nobler nobler
+ nobles nobl noblesse nobless
+ noblest noblest nobly nobli
+ nobody nobodi noces noce
+ nod nod nodded nod
+ nodding nod noddle noddl
+ noddles noddl noddy noddi
+ nods nod noes noe
+ nointed noint nois noi
+ noise nois noiseless noiseless
+ noisemaker noisemak noises nois
+ noisome noisom nole nole
+ nominate nomin nominated nomin
+ nomination nomin nominativo nominativo
+ non non nonage nonag
+ nonce nonc none none
+ nonino nonino nonny nonni
+ nonpareil nonpareil nonsuits nonsuit
+ nony noni nook nook
+ nooks nook noon noon
+ noonday noondai noontide noontid
+ nor nor norbery norberi
+ norfolk norfolk norman norman
+ normandy normandi normans norman
+ north north northampton northampton
+ northamptonshire northamptonshir northerly northerli
+ northern northern northgate northgat
+ northumberland northumberland northumberlands northumberland
+ northward northward norway norwai
+ norways norwai norwegian norwegian
+ norweyan norweyan nos no
+ nose nose nosegays nosegai
+ noseless noseless noses nose
+ noster noster nostra nostra
+ nostril nostril nostrils nostril
+ not not notable notabl
+ notably notabl notary notari
+ notch notch note note
+ notebook notebook noted note
+ notedly notedli notes note
+ notest notest noteworthy noteworthi
+ nothing noth nothings noth
+ notice notic notify notifi
+ noting note notion notion
+ notorious notori notoriously notori
+ notre notr notwithstanding notwithstand
+ nought nought noun noun
+ nouns noun nourish nourish
+ nourished nourish nourisher nourish
+ nourishes nourish nourisheth nourisheth
+ nourishing nourish nourishment nourish
+ nous nou novel novel
+ novelties novelti novelty novelti
+ noverbs noverb novi novi
+ novice novic novices novic
+ novum novum now now
+ nowhere nowher noyance noyanc
+ ns ns nt nt
+ nubibus nubibu numa numa
+ numb numb number number
+ numbered number numbering number
+ numberless numberless numbers number
+ numbness numb nun nun
+ nuncio nuncio nuncle nuncl
+ nunnery nunneri nuns nun
+ nuntius nuntiu nuptial nuptial
+ nurs nur nurse nurs
+ nursed nurs nurser nurser
+ nursery nurseri nurses nurs
+ nurseth nurseth nursh nursh
+ nursing nurs nurtur nurtur
+ nurture nurtur nut nut
+ nuthook nuthook nutmeg nutmeg
+ nutmegs nutmeg nutriment nutriment
+ nuts nut nutshell nutshel
+ ny ny nym nym
+ nymph nymph nymphs nymph
+ o o oak oak
+ oaken oaken oaks oak
+ oared oar oars oar
+ oatcake oatcak oaten oaten
+ oath oath oathable oathabl
+ oaths oath oats oat
+ ob ob obduracy obduraci
+ obdurate obdur obedience obedi
+ obedient obedi obeisance obeis
+ oberon oberon obey obei
+ obeyed obei obeying obei
+ obeys obei obidicut obidicut
+ object object objected object
+ objections object objects object
+ oblation oblat oblations oblat
+ obligation oblig obligations oblig
+ obliged oblig oblique obliqu
+ oblivion oblivion oblivious oblivi
+ obloquy obloqui obscene obscen
+ obscenely obscen obscur obscur
+ obscure obscur obscured obscur
+ obscurely obscur obscures obscur
+ obscuring obscur obscurity obscur
+ obsequies obsequi obsequious obsequi
+ obsequiously obsequi observ observ
+ observance observ observances observ
+ observancy observ observant observ
+ observants observ observation observ
+ observe observ observed observ
+ observer observ observers observ
+ observing observ observingly observingli
+ obsque obsqu obstacle obstacl
+ obstacles obstacl obstinacy obstinaci
+ obstinate obstin obstinately obstin
+ obstruct obstruct obstruction obstruct
+ obstructions obstruct obtain obtain
+ obtained obtain obtaining obtain
+ occasion occas occasions occas
+ occident occid occidental occident
+ occulted occult occupat occupat
+ occupation occup occupations occup
+ occupied occupi occupies occupi
+ occupy occupi occurrence occurr
+ occurrences occurr occurrents occurr
+ ocean ocean oceans ocean
+ octavia octavia octavius octaviu
+ ocular ocular od od
+ odd odd oddest oddest
+ oddly oddli odds odd
+ ode od odes od
+ odious odiou odoriferous odorifer
+ odorous odor odour odour
+ odours odour ods od
+ oeillades oeillad oes oe
+ oeuvres oeuvr of of
+ ofephesus ofephesu off off
+ offal offal offence offenc
+ offenceful offenc offences offenc
+ offend offend offended offend
+ offendendo offendendo offender offend
+ offenders offend offendeth offendeth
+ offending offend offendress offendress
+ offends offend offense offens
+ offenseless offenseless offenses offens
+ offensive offens offer offer
+ offered offer offering offer
+ offerings offer offers offer
+ offert offert offic offic
+ office offic officed offic
+ officer offic officers offic
+ offices offic official offici
+ officious offici offspring offspr
+ oft oft often often
+ oftener often oftentimes oftentim
+ oh oh oil oil
+ oils oil oily oili
+ old old oldcastle oldcastl
+ olden olden older older
+ oldest oldest oldness old
+ olive oliv oliver oliv
+ olivers oliv olives oliv
+ olivia olivia olympian olympian
+ olympus olympu oman oman
+ omans oman omen omen
+ ominous omin omission omiss
+ omit omit omittance omitt
+ omitted omit omitting omit
+ omne omn omnes omn
+ omnipotent omnipot on on
+ once onc one on
+ ones on oneyers oney
+ ongles ongl onion onion
+ onions onion only onli
+ onset onset onward onward
+ onwards onward oo oo
+ ooze ooz oozes ooz
+ oozy oozi op op
+ opal opal ope op
+ open open opener open
+ opening open openly openli
+ openness open opens open
+ operant oper operate oper
+ operation oper operations oper
+ operative oper opes op
+ oph oph ophelia ophelia
+ opinion opinion opinions opinion
+ opportune opportun opportunities opportun
+ opportunity opportun oppos oppo
+ oppose oppos opposed oppos
+ opposeless opposeless opposer oppos
+ opposers oppos opposes oppos
+ opposing oppos opposite opposit
+ opposites opposit opposition opposit
+ oppositions opposit oppress oppress
+ oppressed oppress oppresses oppress
+ oppresseth oppresseth oppressing oppress
+ oppression oppress oppressor oppressor
+ opprest opprest opprobriously opprobri
+ oppugnancy oppugn opulency opul
+ opulent opul or or
+ oracle oracl oracles oracl
+ orange orang oration orat
+ orator orat orators orat
+ oratory oratori orb orb
+ orbed orb orbs orb
+ orchard orchard orchards orchard
+ ord ord ordain ordain
+ ordained ordain ordaining ordain
+ order order ordered order
+ ordering order orderless orderless
+ orderly orderli orders order
+ ordinance ordin ordinant ordin
+ ordinaries ordinari ordinary ordinari
+ ordnance ordnanc ords ord
+ ordure ordur ore or
+ organ organ organs organ
+ orgillous orgil orient orient
+ orifex orifex origin origin
+ original origin orisons orison
+ ork ork orlando orlando
+ orld orld orleans orlean
+ ornament ornament ornaments ornament
+ orodes orod orphan orphan
+ orphans orphan orpheus orpheu
+ orsino orsino ort ort
+ orthography orthographi orts ort
+ oscorbidulchos oscorbidulcho osier osier
+ osiers osier osprey osprei
+ osr osr osric osric
+ ossa ossa ost ost
+ ostent ostent ostentare ostentar
+ ostentation ostent ostents ostent
+ ostler ostler ostlers ostler
+ ostrich ostrich osw osw
+ oswald oswald othello othello
+ other other othergates otherg
+ others other otherwhere otherwher
+ otherwhiles otherwhil otherwise otherwis
+ otter otter ottoman ottoman
+ ottomites ottomit oublie oubli
+ ouches ouch ought ought
+ oui oui ounce ounc
+ ounces ounc ouphes ouph
+ our our ours our
+ ourself ourself ourselves ourselv
+ ousel ousel out out
+ outbids outbid outbrave outbrav
+ outbraves outbrav outbreak outbreak
+ outcast outcast outcries outcri
+ outcry outcri outdar outdar
+ outdare outdar outdares outdar
+ outdone outdon outfac outfac
+ outface outfac outfaced outfac
+ outfacing outfac outfly outfli
+ outfrown outfrown outgo outgo
+ outgoes outgo outgrown outgrown
+ outjest outjest outlaw outlaw
+ outlawry outlawri outlaws outlaw
+ outliv outliv outlive outliv
+ outlives outliv outliving outliv
+ outlook outlook outlustres outlustr
+ outpriz outpriz outrage outrag
+ outrageous outrag outrages outrag
+ outran outran outright outright
+ outroar outroar outrun outrun
+ outrunning outrun outruns outrun
+ outscold outscold outscorn outscorn
+ outsell outsel outsells outsel
+ outside outsid outsides outsid
+ outspeaks outspeak outsport outsport
+ outstare outstar outstay outstai
+ outstood outstood outstretch outstretch
+ outstretched outstretch outstrike outstrik
+ outstrip outstrip outstripped outstrip
+ outswear outswear outvenoms outvenom
+ outward outward outwardly outwardli
+ outwards outward outwear outwear
+ outweighs outweigh outwent outwent
+ outworn outworn outworths outworth
+ oven oven over over
+ overawe overaw overbear overbear
+ overblown overblown overboard overboard
+ overbold overbold overborne overborn
+ overbulk overbulk overbuys overbui
+ overcame overcam overcast overcast
+ overcharg overcharg overcharged overcharg
+ overcome overcom overcomes overcom
+ overdone overdon overearnest overearnest
+ overfar overfar overflow overflow
+ overflown overflown overglance overgl
+ overgo overgo overgone overgon
+ overgorg overgorg overgrown overgrown
+ overhead overhead overhear overhear
+ overheard overheard overhold overhold
+ overjoyed overjoi overkind overkind
+ overland overland overleather overleath
+ overlive overl overlook overlook
+ overlooking overlook overlooks overlook
+ overmaster overmast overmounting overmount
+ overmuch overmuch overpass overpass
+ overpeer overp overpeering overp
+ overplus overplu overrul overrul
+ overrun overrun overscutch overscutch
+ overset overset overshades overshad
+ overshine overshin overshines overshin
+ overshot overshot oversights oversight
+ overspread overspread overstain overstain
+ overswear overswear overt overt
+ overta overta overtake overtak
+ overtaketh overtaketh overthrow overthrow
+ overthrown overthrown overthrows overthrow
+ overtook overtook overtopp overtopp
+ overture overtur overturn overturn
+ overwatch overwatch overween overween
+ overweening overween overweigh overweigh
+ overwhelm overwhelm overwhelming overwhelm
+ overworn overworn ovid ovid
+ ovidius ovidiu ow ow
+ owe ow owed ow
+ owedst owedst owen owen
+ owes ow owest owest
+ oweth oweth owing ow
+ owl owl owls owl
+ own own owner owner
+ owners owner owning own
+ owns own owy owi
+ ox ox oxen oxen
+ oxford oxford oxfordshire oxfordshir
+ oxlips oxlip oyes oy
+ oyster oyster p p
+ pabble pabbl pabylon pabylon
+ pac pac pace pace
+ paced pace paces pace
+ pacified pacifi pacify pacifi
+ pacing pace pack pack
+ packet packet packets packet
+ packhorses packhors packing pack
+ packings pack packs pack
+ packthread packthread pacorus pacoru
+ paction paction pad pad
+ paddle paddl paddling paddl
+ paddock paddock padua padua
+ pagan pagan pagans pagan
+ page page pageant pageant
+ pageants pageant pages page
+ pah pah paid paid
+ pail pail pailfuls pail
+ pails pail pain pain
+ pained pain painful pain
+ painfully painfulli pains pain
+ paint paint painted paint
+ painter painter painting paint
+ paintings paint paints paint
+ pair pair paired pair
+ pairs pair pajock pajock
+ pal pal palabras palabra
+ palace palac palaces palac
+ palamedes palamed palate palat
+ palates palat palatine palatin
+ palating palat pale pale
+ paled pale paleness pale
+ paler paler pales pale
+ palestine palestin palfrey palfrei
+ palfreys palfrei palisadoes palisado
+ pall pall pallabris pallabri
+ pallas palla pallets pallet
+ palm palm palmer palmer
+ palmers palmer palms palm
+ palmy palmi palpable palpabl
+ palsied palsi palsies palsi
+ palsy palsi palt palt
+ palter palter paltry paltri
+ paly pali pamp pamp
+ pamper pamper pamphlets pamphlet
+ pan pan pancackes pancack
+ pancake pancak pancakes pancak
+ pandar pandar pandars pandar
+ pandarus pandaru pander pander
+ panderly panderli panders pander
+ pandulph pandulph panel panel
+ pang pang panging pang
+ pangs pang pannier pannier
+ pannonians pannonian pansa pansa
+ pansies pansi pant pant
+ pantaloon pantaloon panted pant
+ pantheon pantheon panther panther
+ panthino panthino panting pant
+ pantingly pantingli pantler pantler
+ pantry pantri pants pant
+ pap pap papal papal
+ paper paper papers paper
+ paphlagonia paphlagonia paphos papho
+ papist papist paps pap
+ par par parable parabl
+ paracelsus paracelsu paradise paradis
+ paradox paradox paradoxes paradox
+ paragon paragon paragons paragon
+ parallel parallel parallels parallel
+ paramour paramour paramours paramour
+ parapets parapet paraquito paraquito
+ parasite parasit parasites parasit
+ parca parca parcel parcel
+ parcell parcel parcels parcel
+ parch parch parched parch
+ parching parch parchment parchment
+ pard pard pardon pardon
+ pardona pardona pardoned pardon
+ pardoner pardon pardoning pardon
+ pardonne pardonn pardonner pardonn
+ pardonnez pardonnez pardons pardon
+ pare pare pared pare
+ parel parel parent parent
+ parentage parentag parents parent
+ parfect parfect paring pare
+ parings pare paris pari
+ parish parish parishioners parishion
+ parisians parisian paritors paritor
+ park park parks park
+ parle parl parler parler
+ parles parl parley parlei
+ parlez parlez parliament parliament
+ parlors parlor parlour parlour
+ parlous parlou parmacity parmac
+ parolles parol parricide parricid
+ parricides parricid parrot parrot
+ parrots parrot parsley parslei
+ parson parson part part
+ partake partak partaken partaken
+ partaker partak partakers partak
+ parted part parthia parthia
+ parthian parthian parthians parthian
+ parti parti partial partial
+ partialize partial partially partial
+ participate particip participation particip
+ particle particl particular particular
+ particularities particular particularize particular
+ particularly particularli particulars particular
+ parties parti parting part
+ partisan partisan partisans partisan
+ partition partit partizan partizan
+ partlet partlet partly partli
+ partner partner partners partner
+ partridge partridg parts part
+ party parti pas pa
+ pash pash pashed pash
+ pashful pash pass pass
+ passable passabl passado passado
+ passage passag passages passag
+ passant passant passed pass
+ passenger passeng passengers passeng
+ passes pass passeth passeth
+ passing pass passio passio
+ passion passion passionate passion
+ passioning passion passions passion
+ passive passiv passport passport
+ passy passi past past
+ paste past pasterns pastern
+ pasties pasti pastime pastim
+ pastimes pastim pastoral pastor
+ pastorals pastor pastors pastor
+ pastry pastri pasture pastur
+ pastures pastur pasty pasti
+ pat pat patay patai
+ patch patch patchery patcheri
+ patches patch pate pate
+ pated pate patent patent
+ patents patent paternal patern
+ pates pate path path
+ pathetical pathet paths path
+ pathway pathwai pathways pathwai
+ patience patienc patient patient
+ patiently patient patients patient
+ patines patin patrician patrician
+ patricians patrician patrick patrick
+ patrimony patrimoni patroclus patroclu
+ patron patron patronage patronag
+ patroness patro patrons patron
+ patrum patrum patter patter
+ pattern pattern patterns pattern
+ pattle pattl pauca pauca
+ paucas pauca paul paul
+ paulina paulina paunch paunch
+ paunches paunch pause paus
+ pauser pauser pauses paus
+ pausingly pausingli pauvres pauvr
+ pav pav paved pave
+ pavement pavement pavilion pavilion
+ pavilions pavilion pavin pavin
+ paw paw pawn pawn
+ pawns pawn paws paw
+ pax pax pay pai
+ payest payest paying pai
+ payment payment payments payment
+ pays pai paysan paysan
+ paysans paysan pe pe
+ peace peac peaceable peaceabl
+ peaceably peaceabl peaceful peac
+ peacemakers peacemak peaces peac
+ peach peach peaches peach
+ peacock peacock peacocks peacock
+ peak peak peaking peak
+ peal peal peals peal
+ pear pear peard peard
+ pearl pearl pearls pearl
+ pears pear peas pea
+ peasant peasant peasantry peasantri
+ peasants peasant peascod peascod
+ pease peas peaseblossom peaseblossom
+ peat peat peaten peaten
+ peating peat pebble pebbl
+ pebbled pebbl pebbles pebbl
+ peck peck pecks peck
+ peculiar peculiar pecus pecu
+ pedant pedant pedantical pedant
+ pedascule pedascul pede pede
+ pedestal pedest pedigree pedigre
+ pedlar pedlar pedlars pedlar
+ pedro pedro peds ped
+ peel peel peep peep
+ peeped peep peeping peep
+ peeps peep peer peer
+ peereth peereth peering peer
+ peerless peerless peers peer
+ peesel peesel peevish peevish
+ peevishly peevishli peflur peflur
+ peg peg pegasus pegasu
+ pegs peg peise peis
+ peised peis peize peiz
+ pelf pelf pelican pelican
+ pelion pelion pell pell
+ pella pella pelleted pellet
+ peloponnesus peloponnesu pelt pelt
+ pelting pelt pembroke pembrok
+ pen pen penalties penalti
+ penalty penalti penance penanc
+ pence penc pencil pencil
+ pencill pencil pencils pencil
+ pendant pendant pendent pendent
+ pendragon pendragon pendulous pendul
+ penelope penelop penetrable penetr
+ penetrate penetr penetrative penetr
+ penitence penit penitent penit
+ penitential penitenti penitently penit
+ penitents penit penker penker
+ penknife penknif penn penn
+ penned pen penning pen
+ pennons pennon penny penni
+ pennyworth pennyworth pennyworths pennyworth
+ pens pen pense pens
+ pension pension pensioners pension
+ pensive pensiv pensived pensiv
+ pensively pensiv pent pent
+ pentecost pentecost penthesilea penthesilea
+ penthouse penthous penurious penuri
+ penury penuri peopl peopl
+ people peopl peopled peopl
+ peoples peopl pepin pepin
+ pepper pepper peppercorn peppercorn
+ peppered pepper per per
+ peradventure peradventur peradventures peradventur
+ perceiv perceiv perceive perceiv
+ perceived perceiv perceives perceiv
+ perceiveth perceiveth perch perch
+ perchance perchanc percies perci
+ percussion percuss percy perci
+ perdie perdi perdita perdita
+ perdition perdit perdonato perdonato
+ perdu perdu perdurable perdur
+ perdurably perdur perdy perdi
+ pere pere peregrinate peregrin
+ peremptorily peremptorili peremptory peremptori
+ perfect perfect perfected perfect
+ perfecter perfect perfectest perfectest
+ perfection perfect perfections perfect
+ perfectly perfectli perfectness perfect
+ perfidious perfidi perfidiously perfidi
+ perforce perforc perform perform
+ performance perform performances perform
+ performed perform performer perform
+ performers perform performing perform
+ performs perform perfum perfum
+ perfume perfum perfumed perfum
+ perfumer perfum perfumes perfum
+ perge perg perhaps perhap
+ periapts periapt perigort perigort
+ perigouna perigouna peril peril
+ perilous peril perils peril
+ period period periods period
+ perish perish perished perish
+ perishest perishest perisheth perisheth
+ perishing perish periwig periwig
+ perjur perjur perjure perjur
+ perjured perjur perjuries perjuri
+ perjury perjuri perk perk
+ perkes perk permafoy permafoi
+ permanent perman permission permiss
+ permissive permiss permit permit
+ permitted permit pernicious pernici
+ perniciously pernici peroration peror
+ perpend perpend perpendicular perpendicular
+ perpendicularly perpendicularli perpetual perpetu
+ perpetually perpetu perpetuity perpetu
+ perplex perplex perplexed perplex
+ perplexity perplex pers per
+ persecuted persecut persecutions persecut
+ persecutor persecutor perseus perseu
+ persever persev perseverance persever
+ persevers persev persia persia
+ persian persian persist persist
+ persisted persist persistency persist
+ persistive persist persists persist
+ person person personae persona
+ personage personag personages personag
+ personal person personally person
+ personate person personated person
+ personates person personating person
+ persons person perspective perspect
+ perspectively perspect perspectives perspect
+ perspicuous perspicu persuade persuad
+ persuaded persuad persuades persuad
+ persuading persuad persuasion persuas
+ persuasions persuas pert pert
+ pertain pertain pertaining pertain
+ pertains pertain pertaunt pertaunt
+ pertinent pertin pertly pertli
+ perturb perturb perturbation perturb
+ perturbations perturb perturbed perturb
+ perus peru perusal perus
+ peruse perus perused perus
+ perusing perus perverse pervers
+ perversely pervers perverseness pervers
+ pervert pervert perverted pervert
+ peseech peseech pest pest
+ pester pester pestiferous pestifer
+ pestilence pestil pestilent pestil
+ pet pet petar petar
+ peter peter petit petit
+ petition petit petitionary petitionari
+ petitioner petition petitioners petition
+ petitions petit peto peto
+ petrarch petrarch petruchio petruchio
+ petter petter petticoat petticoat
+ petticoats petticoat pettiness petti
+ pettish pettish pettitoes pettito
+ petty petti peu peu
+ pew pew pewter pewter
+ pewterer pewter phaethon phaethon
+ phaeton phaeton phantasime phantasim
+ phantasimes phantasim phantasma phantasma
+ pharamond pharamond pharaoh pharaoh
+ pharsalia pharsalia pheasant pheasant
+ pheazar pheazar phebe phebe
+ phebes phebe pheebus pheebu
+ pheeze pheez phibbus phibbu
+ philadelphos philadelpho philario philario
+ philarmonus philarmonu philemon philemon
+ philip philip philippan philippan
+ philippe philipp philippi philippi
+ phillida phillida philo philo
+ philomel philomel philomela philomela
+ philosopher philosoph philosophers philosoph
+ philosophical philosoph philosophy philosophi
+ philostrate philostr philotus philotu
+ phlegmatic phlegmat phoebe phoeb
+ phoebus phoebu phoenicia phoenicia
+ phoenicians phoenician phoenix phoenix
+ phorbus phorbu photinus photinu
+ phrase phrase phraseless phraseless
+ phrases phrase phrygia phrygia
+ phrygian phrygian phrynia phrynia
+ physic physic physical physic
+ physician physician physicians physician
+ physics physic pia pia
+ pibble pibbl pible pibl
+ picardy picardi pick pick
+ pickaxe pickax pickaxes pickax
+ pickbone pickbon picked pick
+ pickers picker picking pick
+ pickle pickl picklock picklock
+ pickpurse pickpurs picks pick
+ pickt pickt pickthanks pickthank
+ pictur pictur picture pictur
+ pictured pictur pictures pictur
+ pid pid pie pie
+ piec piec piece piec
+ pieces piec piecing piec
+ pied pi piedness pied
+ pier pier pierc pierc
+ pierce pierc pierced pierc
+ pierces pierc pierceth pierceth
+ piercing pierc piercy pierci
+ piers pier pies pi
+ piety pieti pig pig
+ pigeon pigeon pigeons pigeon
+ pight pight pigmy pigmi
+ pigrogromitus pigrogromitu pike pike
+ pikes pike pil pil
+ pilate pilat pilates pilat
+ pilchers pilcher pile pile
+ piles pile pilf pilf
+ pilfering pilfer pilgrim pilgrim
+ pilgrimage pilgrimag pilgrims pilgrim
+ pill pill pillage pillag
+ pillagers pillag pillar pillar
+ pillars pillar pillicock pillicock
+ pillory pillori pillow pillow
+ pillows pillow pills pill
+ pilot pilot pilots pilot
+ pimpernell pimpernel pin pin
+ pinch pinch pinched pinch
+ pinches pinch pinching pinch
+ pindarus pindaru pine pine
+ pined pine pines pine
+ pinfold pinfold pining pine
+ pinion pinion pink pink
+ pinn pinn pinnace pinnac
+ pins pin pinse pins
+ pint pint pintpot pintpot
+ pioned pion pioneers pioneer
+ pioner pioner pioners pioner
+ pious piou pip pip
+ pipe pipe piper piper
+ pipers piper pipes pipe
+ piping pipe pippin pippin
+ pippins pippin pirate pirat
+ pirates pirat pisa pisa
+ pisanio pisanio pish pish
+ pismires pismir piss piss
+ pissing piss pistol pistol
+ pistols pistol pit pit
+ pitch pitch pitched pitch
+ pitcher pitcher pitchers pitcher
+ pitchy pitchi piteous piteou
+ piteously piteous pitfall pitfal
+ pith pith pithless pithless
+ pithy pithi pitie piti
+ pitied piti pities piti
+ pitiful piti pitifully pitifulli
+ pitiless pitiless pits pit
+ pittance pittanc pittie pitti
+ pittikins pittikin pity piti
+ pitying piti pius piu
+ plac plac place place
+ placed place placentio placentio
+ places place placeth placeth
+ placid placid placing place
+ plack plack placket placket
+ plackets placket plagu plagu
+ plague plagu plagued plagu
+ plagues plagu plaguing plagu
+ plaguy plagui plain plain
+ plainer plainer plainest plainest
+ plaining plain plainings plain
+ plainly plainli plainness plain
+ plains plain plainsong plainsong
+ plaintful plaint plaintiff plaintiff
+ plaintiffs plaintiff plaints plaint
+ planched planch planet planet
+ planetary planetari planets planet
+ planks plank plant plant
+ plantage plantag plantagenet plantagenet
+ plantagenets plantagenet plantain plantain
+ plantation plantat planted plant
+ planteth planteth plants plant
+ plash plash plashy plashi
+ plast plast plaster plaster
+ plasterer plaster plat plat
+ plate plate plated plate
+ plates plate platform platform
+ platforms platform plats plat
+ platted plat plausible plausibl
+ plausive plausiv plautus plautu
+ play plai played plai
+ player player players player
+ playeth playeth playfellow playfellow
+ playfellows playfellow playhouse playhous
+ playing plai plays plai
+ plea plea pleach pleach
+ pleached pleach plead plead
+ pleaded plead pleader pleader
+ pleaders pleader pleading plead
+ pleads plead pleas plea
+ pleasance pleasanc pleasant pleasant
+ pleasantly pleasantli please pleas
+ pleased pleas pleaser pleaser
+ pleasers pleaser pleases pleas
+ pleasest pleasest pleaseth pleaseth
+ pleasing pleas pleasure pleasur
+ pleasures pleasur plebeians plebeian
+ plebeii plebeii plebs pleb
+ pledge pledg pledges pledg
+ pleines plein plenitude plenitud
+ plenteous plenteou plenteously plenteous
+ plenties plenti plentiful plenti
+ plentifully plentifulli plenty plenti
+ pless pless plessed pless
+ plessing pless pliant pliant
+ plied pli plies pli
+ plight plight plighted plight
+ plighter plighter plod plod
+ plodded plod plodders plodder
+ plodding plod plods plod
+ plood plood ploody ploodi
+ plot plot plots plot
+ plotted plot plotter plotter
+ plough plough ploughed plough
+ ploughman ploughman ploughmen ploughmen
+ plow plow plows plow
+ pluck pluck plucked pluck
+ plucker plucker plucking pluck
+ plucks pluck plue plue
+ plum plum plume plume
+ plumed plume plumes plume
+ plummet plummet plump plump
+ plumpy plumpi plums plum
+ plung plung plunge plung
+ plunged plung plural plural
+ plurisy plurisi plus plu
+ pluto pluto plutus plutu
+ ply ply po po
+ pocket pocket pocketing pocket
+ pockets pocket pocky pocki
+ pody podi poem poem
+ poesy poesi poet poet
+ poetical poetic poetry poetri
+ poets poet poictiers poictier
+ poinards poinard poins poin
+ point point pointblank pointblank
+ pointed point pointing point
+ points point pois poi
+ poise pois poising pois
+ poison poison poisoned poison
+ poisoner poison poisoning poison
+ poisonous poison poisons poison
+ poke poke poking poke
+ pol pol polack polack
+ polacks polack poland poland
+ pold pold pole pole
+ poleaxe poleax polecat polecat
+ polecats polecat polemon polemon
+ poles pole poli poli
+ policies polici policy polici
+ polish polish polished polish
+ politic polit politician politician
+ politicians politician politicly politicli
+ polixenes polixen poll poll
+ polluted pollut pollution pollut
+ polonius poloniu poltroons poltroon
+ polusion polus polydamus polydamu
+ polydore polydor polyxena polyxena
+ pomander pomand pomegranate pomegran
+ pomewater pomewat pomfret pomfret
+ pomgarnet pomgarnet pommel pommel
+ pomp pomp pompeius pompeiu
+ pompey pompei pompion pompion
+ pompous pompou pomps pomp
+ pond pond ponder ponder
+ ponderous ponder ponds pond
+ poniard poniard poniards poniard
+ pont pont pontic pontic
+ pontifical pontif ponton ponton
+ pooh pooh pool pool
+ poole pool poop poop
+ poor poor poorer poorer
+ poorest poorest poorly poorli
+ pop pop pope pope
+ popedom popedom popilius popiliu
+ popingay popingai popish popish
+ popp popp poppy poppi
+ pops pop popular popular
+ popularity popular populous popul
+ porch porch porches porch
+ pore pore poring pore
+ pork pork porn porn
+ porpentine porpentin porridge porridg
+ porringer porring port port
+ portable portabl portage portag
+ portal portal portance portanc
+ portcullis portculli portend portend
+ portends portend portent portent
+ portentous portent portents portent
+ porter porter porters porter
+ portia portia portion portion
+ portly portli portotartarossa portotartarossa
+ portrait portrait portraiture portraitur
+ ports port portugal portug
+ pose pose posied posi
+ posies posi position posit
+ positive posit positively posit
+ posse poss possess possess
+ possessed possess possesses possess
+ possesseth possesseth possessing possess
+ possession possess possessions possess
+ possessor possessor posset posset
+ possets posset possibilities possibl
+ possibility possibl possible possibl
+ possibly possibl possitable possit
+ post post poste post
+ posted post posterior posterior
+ posteriors posterior posterity poster
+ postern postern posterns postern
+ posters poster posthorse posthors
+ posthorses posthors posthumus posthumu
+ posting post postmaster postmast
+ posts post postscript postscript
+ posture postur postures postur
+ posy posi pot pot
+ potable potabl potations potat
+ potato potato potatoes potato
+ potch potch potency potenc
+ potent potent potentates potent
+ potential potenti potently potent
+ potents potent pothecary pothecari
+ pother pother potion potion
+ potions potion potpan potpan
+ pots pot potter potter
+ potting pot pottle pottl
+ pouch pouch poulter poulter
+ poultice poultic poultney poultnei
+ pouncet pouncet pound pound
+ pounds pound pour pour
+ pourest pourest pouring pour
+ pourquoi pourquoi pours pour
+ pout pout poverty poverti
+ pow pow powd powd
+ powder powder power power
+ powerful power powerfully powerfulli
+ powerless powerless powers power
+ pox pox poys poi
+ poysam poysam prabbles prabbl
+ practic practic practice practic
+ practiced practic practicer practic
+ practices practic practicing practic
+ practis practi practisants practis
+ practise practis practiser practis
+ practisers practis practises practis
+ practising practis praeclarissimus praeclarissimu
+ praemunire praemunir praetor praetor
+ praetors praetor pragging prag
+ prague pragu prain prain
+ prains prain prais prai
+ praise prais praised prais
+ praises prais praisest praisest
+ praiseworthy praiseworthi praising prais
+ prancing pranc prank prank
+ pranks prank prat prat
+ prate prate prated prate
+ prater prater prating prate
+ prattle prattl prattler prattler
+ prattling prattl prave prave
+ prawls prawl prawns prawn
+ pray prai prayer prayer
+ prayers prayer praying prai
+ prays prai pre pre
+ preach preach preached preach
+ preachers preacher preaches preach
+ preaching preach preachment preachment
+ pread pread preambulate preambul
+ precedence preced precedent preced
+ preceding preced precept precept
+ preceptial precepti precepts precept
+ precinct precinct precious preciou
+ preciously precious precipice precipic
+ precipitating precipit precipitation precipit
+ precise precis precisely precis
+ preciseness precis precisian precisian
+ precor precor precurse precurs
+ precursors precursor predeceased predeceas
+ predecessor predecessor predecessors predecessor
+ predestinate predestin predicament predica
+ predict predict prediction predict
+ predictions predict predominance predomin
+ predominant predomin predominate predomin
+ preeches preech preeminence preemin
+ preface prefac prefer prefer
+ preferment prefer preferments prefer
+ preferr preferr preferreth preferreth
+ preferring prefer prefers prefer
+ prefiguring prefigur prefix prefix
+ prefixed prefix preformed preform
+ pregnancy pregnanc pregnant pregnant
+ pregnantly pregnantli prejudicates prejud
+ prejudice prejudic prejudicial prejudici
+ prelate prelat premeditated premedit
+ premeditation premedit premised premis
+ premises premis prenez prenez
+ prenominate prenomin prentice prentic
+ prentices prentic preordinance preordin
+ prepar prepar preparation prepar
+ preparations prepar prepare prepar
+ prepared prepar preparedly preparedli
+ prepares prepar preparing prepar
+ prepost prepost preposterous preposter
+ preposterously preposter prerogatifes prerogatif
+ prerogative prerog prerogatived prerogativ
+ presage presag presagers presag
+ presages presag presageth presageth
+ presaging presag prescience prescienc
+ prescribe prescrib prescript prescript
+ prescription prescript prescriptions prescript
+ prescripts prescript presence presenc
+ presences presenc present present
+ presentation present presented present
+ presenter present presenters present
+ presenteth presenteth presenting present
+ presently present presentment present
+ presents present preserv preserv
+ preservation preserv preservative preserv
+ preserve preserv preserved preserv
+ preserver preserv preservers preserv
+ preserving preserv president presid
+ press press pressed press
+ presser presser presses press
+ pressing press pressure pressur
+ pressures pressur prest prest
+ prester prester presume presum
+ presumes presum presuming presum
+ presumption presumpt presumptuous presumptu
+ presuppos presuppo pret pret
+ pretence pretenc pretences pretenc
+ pretend pretend pretended pretend
+ pretending pretend pretense pretens
+ pretext pretext pretia pretia
+ prettier prettier prettiest prettiest
+ prettily prettili prettiness pretti
+ pretty pretti prevail prevail
+ prevailed prevail prevaileth prevaileth
+ prevailing prevail prevailment prevail
+ prevails prevail prevent prevent
+ prevented prevent prevention prevent
+ preventions prevent prevents prevent
+ prey prei preyful prey
+ preys prei priam priam
+ priami priami priamus priamu
+ pribbles pribbl price price
+ prick prick pricked prick
+ pricket pricket pricking prick
+ pricks prick pricksong pricksong
+ pride pride prides pride
+ pridge pridg prie prie
+ pried pri prief prief
+ pries pri priest priest
+ priesthood priesthood priests priest
+ prig prig primal primal
+ prime prime primer primer
+ primero primero primest primest
+ primitive primit primo primo
+ primogenity primogen primrose primros
+ primroses primros primy primi
+ prince princ princely princ
+ princes princ princess princess
+ principal princip principalities princip
+ principality princip principle principl
+ principles principl princox princox
+ prings pring print print
+ printed print printing print
+ printless printless prints print
+ prioress prioress priories priori
+ priority prioriti priory priori
+ priscian priscian prison prison
+ prisoner prison prisoners prison
+ prisonment prison prisonnier prisonni
+ prisons prison pristine pristin
+ prithe prith prithee prithe
+ privacy privaci private privat
+ privately privat privates privat
+ privilage privilag privileg privileg
+ privilege privileg privileged privileg
+ privileges privileg privilegio privilegio
+ privily privili privity priviti
+ privy privi priz priz
+ prize prize prized prize
+ prizer prizer prizes prize
+ prizest prizest prizing prize
+ pro pro probable probabl
+ probal probal probation probat
+ proceed proce proceeded proceed
+ proceeders proceed proceeding proceed
+ proceedings proceed proceeds proce
+ process process procession process
+ proclaim proclaim proclaimed proclaim
+ proclaimeth proclaimeth proclaims proclaim
+ proclamation proclam proclamations proclam
+ proconsul proconsul procrastinate procrastin
+ procreant procreant procreants procreant
+ procreation procreat procrus procru
+ proculeius proculeiu procur procur
+ procurator procur procure procur
+ procured procur procures procur
+ procuring procur prodigal prodig
+ prodigality prodig prodigally prodig
+ prodigals prodig prodigies prodigi
+ prodigious prodigi prodigiously prodigi
+ prodigy prodigi proditor proditor
+ produc produc produce produc
+ produced produc produces produc
+ producing produc proface profac
+ profan profan profanation profan
+ profane profan profaned profan
+ profanely profan profaneness profan
+ profaners profan profaning profan
+ profess profess professed profess
+ professes profess profession profess
+ professions profess professors professor
+ proffer proffer proffered proffer
+ profferer proffer proffers proffer
+ proficient profici profit profit
+ profitable profit profitably profit
+ profited profit profiting profit
+ profitless profitless profits profit
+ profound profound profoundest profoundest
+ profoundly profoundli progenitors progenitor
+ progeny progeni progne progn
+ prognosticate prognost prognostication prognost
+ progress progress progression progress
+ prohibit prohibit prohibition prohibit
+ project project projection project
+ projects project prolixious prolixi
+ prolixity prolix prologue prologu
+ prologues prologu prolong prolong
+ prolongs prolong promethean promethean
+ prometheus prometheu promis promi
+ promise promis promised promis
+ promises promis promiseth promiseth
+ promising promis promontory promontori
+ promotion promot promotions promot
+ prompt prompt prompted prompt
+ promptement promptement prompter prompter
+ prompting prompt prompts prompt
+ prompture promptur promulgate promulg
+ prone prone prononcer prononc
+ prononcez prononcez pronoun pronoun
+ pronounc pronounc pronounce pronounc
+ pronounced pronounc pronouncing pronounc
+ pronouns pronoun proof proof
+ proofs proof prop prop
+ propagate propag propagation propag
+ propend propend propension propens
+ proper proper properer proper
+ properly properli propertied properti
+ properties properti property properti
+ prophecies propheci prophecy propheci
+ prophesied prophesi prophesier prophesi
+ prophesy prophesi prophesying prophesi
+ prophet prophet prophetess prophetess
+ prophetic prophet prophetically prophet
+ prophets prophet propinquity propinqu
+ propontic propont proportion proport
+ proportionable proportion proportions proport
+ propos propo propose propos
+ proposed propos proposer propos
+ proposes propos proposing propos
+ proposition proposit propositions proposit
+ propounded propound propp propp
+ propre propr propriety proprieti
+ props prop propugnation propugn
+ prorogue prorogu prorogued prorogu
+ proscription proscript proscriptions proscript
+ prose prose prosecute prosecut
+ prosecution prosecut proselytes proselyt
+ proserpina proserpina prosp prosp
+ prospect prospect prosper prosper
+ prosperity prosper prospero prospero
+ prosperous prosper prosperously prosper
+ prospers prosper prostitute prostitut
+ prostrate prostrat protect protect
+ protected protect protection protect
+ protector protector protectors protector
+ protectorship protectorship protectress protectress
+ protects protect protest protest
+ protestation protest protestations protest
+ protested protest protester protest
+ protesting protest protests protest
+ proteus proteu protheus protheu
+ protract protract protractive protract
+ proud proud prouder prouder
+ proudest proudest proudlier proudlier
+ proudly proudli prouds proud
+ prov prov provand provand
+ prove prove proved prove
+ provender provend proverb proverb
+ proverbs proverb proves prove
+ proveth proveth provide provid
+ provided provid providence provid
+ provident provid providently provid
+ provider provid provides provid
+ province provinc provinces provinc
+ provincial provinci proving prove
+ provision provis proviso proviso
+ provocation provoc provok provok
+ provoke provok provoked provok
+ provoker provok provokes provok
+ provoketh provoketh provoking provok
+ provost provost prowess prowess
+ prudence prudenc prudent prudent
+ prun prun prune prune
+ prunes prune pruning prune
+ pry pry prying pry
+ psalm psalm psalmist psalmist
+ psalms psalm psalteries psalteri
+ ptolemies ptolemi ptolemy ptolemi
+ public public publican publican
+ publication public publicly publicli
+ publicola publicola publish publish
+ published publish publisher publish
+ publishing publish publius publiu
+ pucelle pucel puck puck
+ pudder pudder pudding pud
+ puddings pud puddle puddl
+ puddled puddl pudency pudenc
+ pueritia pueritia puff puff
+ puffing puf puffs puff
+ pugging pug puis pui
+ puissance puissanc puissant puissant
+ puke puke puking puke
+ pulcher pulcher puling pule
+ pull pull puller puller
+ pullet pullet pulling pull
+ pulls pull pulpit pulpit
+ pulpiter pulpit pulpits pulpit
+ pulse puls pulsidge pulsidg
+ pump pump pumpion pumpion
+ pumps pump pun pun
+ punched punch punish punish
+ punished punish punishes punish
+ punishment punish punishments punish
+ punk punk punto punto
+ puny puni pupil pupil
+ pupils pupil puppet puppet
+ puppets puppet puppies puppi
+ puppy puppi pur pur
+ purblind purblind purchas purcha
+ purchase purchas purchased purchas
+ purchases purchas purchaseth purchaseth
+ purchasing purchas pure pure
+ purely pure purer purer
+ purest purest purg purg
+ purgation purgat purgative purg
+ purgatory purgatori purge purg
+ purged purg purgers purger
+ purging purg purifies purifi
+ purifying purifi puritan puritan
+ purity puriti purlieus purlieu
+ purple purpl purpled purpl
+ purples purpl purport purport
+ purpos purpo purpose purpos
+ purposed purpos purposely purpos
+ purposes purpos purposeth purposeth
+ purposing purpos purr purr
+ purs pur purse purs
+ pursents pursent purses purs
+ pursu pursu pursue pursu
+ pursued pursu pursuers pursuer
+ pursues pursu pursuest pursuest
+ pursueth pursueth pursuing pursu
+ pursuit pursuit pursuivant pursuiv
+ pursuivants pursuiv pursy pursi
+ purus puru purveyor purveyor
+ push push pushes push
+ pusillanimity pusillanim put put
+ putrefy putrefi putrified putrifi
+ puts put putter putter
+ putting put puttock puttock
+ puzzel puzzel puzzle puzzl
+ puzzled puzzl puzzles puzzl
+ py py pygmalion pygmalion
+ pygmies pygmi pygmy pygmi
+ pyramid pyramid pyramides pyramid
+ pyramids pyramid pyramis pyrami
+ pyramises pyramis pyramus pyramu
+ pyrenean pyrenean pyrrhus pyrrhu
+ pythagoras pythagora qu qu
+ quadrangle quadrangl quae quae
+ quaff quaff quaffing quaf
+ quagmire quagmir quail quail
+ quailing quail quails quail
+ quaint quaint quaintly quaintli
+ quak quak quake quak
+ quakes quak qualification qualif
+ qualified qualifi qualifies qualifi
+ qualify qualifi qualifying qualifi
+ qualite qualit qualities qualiti
+ quality qualiti qualm qualm
+ qualmish qualmish quam quam
+ quand quand quando quando
+ quantities quantiti quantity quantiti
+ quare quar quarrel quarrel
+ quarrell quarrel quarreller quarrel
+ quarrelling quarrel quarrelous quarrel
+ quarrels quarrel quarrelsome quarrelsom
+ quarries quarri quarry quarri
+ quart quart quarter quarter
+ quartered quarter quartering quarter
+ quarters quarter quarts quart
+ quasi quasi quat quat
+ quatch quatch quay quai
+ que que quean quean
+ queas quea queasiness queasi
+ queasy queasi queen queen
+ queens queen quell quell
+ queller queller quench quench
+ quenched quench quenching quench
+ quenchless quenchless quern quern
+ quest quest questant questant
+ question question questionable question
+ questioned question questioning question
+ questionless questionless questions question
+ questrists questrist quests quest
+ queubus queubu qui qui
+ quick quick quicken quicken
+ quickens quicken quicker quicker
+ quicklier quicklier quickly quickli
+ quickness quick quicksand quicksand
+ quicksands quicksand quicksilverr quicksilverr
+ quid quid quiddities quidditi
+ quiddits quiddit quier quier
+ quiet quiet quieter quieter
+ quietly quietli quietness quiet
+ quietus quietu quill quill
+ quillets quillet quills quill
+ quilt quilt quinapalus quinapalu
+ quince quinc quinces quinc
+ quintain quintain quintessence quintess
+ quintus quintu quip quip
+ quips quip quire quir
+ quiring quir quirk quirk
+ quirks quirk quis qui
+ quit quit quite quit
+ quits quit quittance quittanc
+ quitted quit quitting quit
+ quiver quiver quivering quiver
+ quivers quiver quo quo
+ quod quod quoifs quoif
+ quoint quoint quoit quoit
+ quoits quoit quondam quondam
+ quoniam quoniam quote quot
+ quoted quot quotes quot
+ quoth quoth quotidian quotidian
+ r r rabbit rabbit
+ rabble rabbl rabblement rabblement
+ race race rack rack
+ rackers racker racket racket
+ rackets racket racking rack
+ racks rack radiance radianc
+ radiant radiant radish radish
+ rafe rafe raft raft
+ rag rag rage rage
+ rages rage rageth rageth
+ ragg ragg ragged rag
+ raggedness ragged raging rage
+ ragozine ragozin rags rag
+ rah rah rail rail
+ railed rail railer railer
+ railest railest raileth raileth
+ railing rail rails rail
+ raiment raiment rain rain
+ rainbow rainbow raineth raineth
+ raining rain rainold rainold
+ rains rain rainy raini
+ rais rai raise rais
+ raised rais raises rais
+ raising rais raisins raisin
+ rak rak rake rake
+ rakers raker rakes rake
+ ral ral rald rald
+ ralph ralph ram ram
+ rambures rambur ramm ramm
+ rampallian rampallian rampant rampant
+ ramping ramp rampir rampir
+ ramps ramp rams ram
+ ramsey ramsei ramston ramston
+ ran ran rance ranc
+ rancorous rancor rancors rancor
+ rancour rancour random random
+ rang rang range rang
+ ranged rang rangers ranger
+ ranges rang ranging rang
+ rank rank ranker ranker
+ rankest rankest ranking rank
+ rankle rankl rankly rankli
+ rankness rank ranks rank
+ ransack ransack ransacking ransack
+ ransom ransom ransomed ransom
+ ransoming ransom ransomless ransomless
+ ransoms ransom rant rant
+ ranting rant rap rap
+ rape rape rapes rape
+ rapier rapier rapiers rapier
+ rapine rapin raps rap
+ rapt rapt rapture raptur
+ raptures raptur rar rar
+ rare rare rarely rare
+ rareness rare rarer rarer
+ rarest rarest rarities rariti
+ rarity rariti rascal rascal
+ rascalliest rascalliest rascally rascal
+ rascals rascal rased rase
+ rash rash rasher rasher
+ rashly rashli rashness rash
+ rat rat ratcatcher ratcatch
+ ratcliff ratcliff rate rate
+ rated rate rately rate
+ rates rate rather rather
+ ratherest ratherest ratified ratifi
+ ratifiers ratifi ratify ratifi
+ rating rate rational ration
+ ratolorum ratolorum rats rat
+ ratsbane ratsban rattle rattl
+ rattles rattl rattling rattl
+ rature ratur raught raught
+ rav rav rave rave
+ ravel ravel raven raven
+ ravening raven ravenous raven
+ ravens raven ravenspurgh ravenspurgh
+ raves rave ravin ravin
+ raving rave ravish ravish
+ ravished ravish ravisher ravish
+ ravishing ravish ravishments ravish
+ raw raw rawer rawer
+ rawly rawli rawness raw
+ ray rai rayed rai
+ rays rai raz raz
+ raze raze razed raze
+ razes raze razeth razeth
+ razing raze razor razor
+ razorable razor razors razor
+ razure razur re re
+ reach reach reaches reach
+ reacheth reacheth reaching reach
+ read read reader reader
+ readiest readiest readily readili
+ readiness readi reading read
+ readins readin reads read
+ ready readi real real
+ really realli realm realm
+ realms realm reap reap
+ reapers reaper reaping reap
+ reaps reap rear rear
+ rears rear rearward rearward
+ reason reason reasonable reason
+ reasonably reason reasoned reason
+ reasoning reason reasonless reasonless
+ reasons reason reave reav
+ rebate rebat rebato rebato
+ rebeck rebeck rebel rebel
+ rebell rebel rebelling rebel
+ rebellion rebellion rebellious rebelli
+ rebels rebel rebound rebound
+ rebuk rebuk rebuke rebuk
+ rebukeable rebuk rebuked rebuk
+ rebukes rebuk rebus rebu
+ recall recal recant recant
+ recantation recant recanter recant
+ recanting recant receipt receipt
+ receipts receipt receiv receiv
+ receive receiv received receiv
+ receiver receiv receives receiv
+ receivest receivest receiveth receiveth
+ receiving receiv receptacle receptacl
+ rechate rechat reciprocal reciproc
+ reciprocally reciproc recite recit
+ recited recit reciterai reciterai
+ reck reck recking reck
+ reckless reckless reckon reckon
+ reckoned reckon reckoning reckon
+ reckonings reckon recks reck
+ reclaim reclaim reclaims reclaim
+ reclusive reclus recognizance recogniz
+ recognizances recogniz recoil recoil
+ recoiling recoil recollected recollect
+ recomforted recomfort recomforture recomfortur
+ recommend recommend recommended recommend
+ recommends recommend recompens recompen
+ recompense recompens reconcil reconcil
+ reconcile reconcil reconciled reconcil
+ reconcilement reconcil reconciler reconcil
+ reconciles reconcil reconciliation reconcili
+ record record recordation record
+ recorded record recorder record
+ recorders record records record
+ recount recount recounted recount
+ recounting recount recountments recount
+ recounts recount recourse recours
+ recov recov recover recov
+ recoverable recover recovered recov
+ recoveries recoveri recovers recov
+ recovery recoveri recreant recreant
+ recreants recreant recreate recreat
+ recreation recreat rectify rectifi
+ rector rector rectorship rectorship
+ recure recur recured recur
+ red red redbreast redbreast
+ redder redder reddest reddest
+ rede rede redeem redeem
+ redeemed redeem redeemer redeem
+ redeeming redeem redeems redeem
+ redeliver redeliv redemption redempt
+ redime redim redness red
+ redoubled redoubl redoubted redoubt
+ redound redound redress redress
+ redressed redress redresses redress
+ reduce reduc reechy reechi
+ reed reed reeds reed
+ reek reek reeking reek
+ reeks reek reeky reeki
+ reel reel reeleth reeleth
+ reeling reel reels reel
+ refell refel refer refer
+ reference refer referr referr
+ referred refer refigured refigur
+ refin refin refined refin
+ reflect reflect reflecting reflect
+ reflection reflect reflex reflex
+ reform reform reformation reform
+ reformed reform refractory refractori
+ refrain refrain refresh refresh
+ refreshing refresh reft reft
+ refts reft refuge refug
+ refus refu refusal refus
+ refuse refus refused refus
+ refusest refusest refusing refus
+ reg reg regal regal
+ regalia regalia regan regan
+ regard regard regardance regard
+ regarded regard regardfully regardfulli
+ regarding regard regards regard
+ regenerate regener regent regent
+ regentship regentship regia regia
+ regiment regiment regiments regiment
+ regina regina region region
+ regions region regist regist
+ register regist registers regist
+ regreet regreet regreets regreet
+ regress regress reguerdon reguerdon
+ regular regular rehears rehear
+ rehearsal rehears rehearse rehears
+ reign reign reigned reign
+ reignier reignier reigning reign
+ reigns reign rein rein
+ reinforc reinforc reinforce reinforc
+ reinforcement reinforc reins rein
+ reiterate reiter reject reject
+ rejected reject rejoic rejoic
+ rejoice rejoic rejoices rejoic
+ rejoiceth rejoiceth rejoicing rejoic
+ rejoicingly rejoicingli rejoindure rejoindur
+ rejourn rejourn rel rel
+ relapse relaps relate relat
+ relates relat relation relat
+ relations relat relative rel
+ releas relea release releas
+ released releas releasing releas
+ relent relent relenting relent
+ relents relent reliances relianc
+ relics relic relief relief
+ reliev reliev relieve reliev
+ relieved reliev relieves reliev
+ relieving reliev religion religion
+ religions religion religious religi
+ religiously religi relinquish relinquish
+ reliques reliqu reliquit reliquit
+ relish relish relume relum
+ rely reli relying reli
+ remain remain remainder remaind
+ remainders remaind remained remain
+ remaineth remaineth remaining remain
+ remains remain remark remark
+ remarkable remark remediate remedi
+ remedied remedi remedies remedi
+ remedy remedi rememb rememb
+ remember rememb remembered rememb
+ remembers rememb remembrance remembr
+ remembrancer remembranc remembrances remembr
+ remercimens remercimen remiss remiss
+ remission remiss remissness remiss
+ remit remit remnant remnant
+ remnants remnant remonstrance remonstr
+ remorse remors remorseful remors
+ remorseless remorseless remote remot
+ remotion remot remov remov
+ remove remov removed remov
+ removedness removed remover remov
+ removes remov removing remov
+ remunerate remuner remuneration remuner
+ rence renc rend rend
+ render render rendered render
+ renders render rendezvous rendezv
+ renegado renegado renege reneg
+ reneges reneg renew renew
+ renewed renew renewest renewest
+ renounce renounc renouncement renounc
+ renouncing renounc renowmed renowm
+ renown renown renowned renown
+ rent rent rents rent
+ repaid repaid repair repair
+ repaired repair repairing repair
+ repairs repair repass repass
+ repast repast repasture repastur
+ repay repai repaying repai
+ repays repai repeal repeal
+ repealing repeal repeals repeal
+ repeat repeat repeated repeat
+ repeating repeat repeats repeat
+ repel repel repent repent
+ repentance repent repentant repent
+ repented repent repenting repent
+ repents repent repetition repetit
+ repetitions repetit repin repin
+ repine repin repining repin
+ replant replant replenish replenish
+ replenished replenish replete replet
+ replication replic replied repli
+ replies repli repliest repliest
+ reply repli replying repli
+ report report reported report
+ reporter report reportest reportest
+ reporting report reportingly reportingli
+ reports report reposal repos
+ repose repos reposeth reposeth
+ reposing repos repossess repossess
+ reprehend reprehend reprehended reprehend
+ reprehending reprehend represent repres
+ representing repres reprieve repriev
+ reprieves repriev reprisal repris
+ reproach reproach reproaches reproach
+ reproachful reproach reproachfully reproachfulli
+ reprobate reprob reprobation reprob
+ reproof reproof reprov reprov
+ reprove reprov reproveable reprov
+ reproves reprov reproving reprov
+ repugn repugn repugnancy repugn
+ repugnant repugn repulse repuls
+ repulsed repuls repurchas repurcha
+ repured repur reputation reput
+ repute reput reputed reput
+ reputeless reputeless reputes reput
+ reputing reput request request
+ requested request requesting request
+ requests request requiem requiem
+ requir requir require requir
+ required requir requires requir
+ requireth requireth requiring requir
+ requisite requisit requisites requisit
+ requit requit requital requit
+ requite requit requited requit
+ requites requit rer rer
+ rere rere rers rer
+ rescu rescu rescue rescu
+ rescued rescu rescues rescu
+ rescuing rescu resemblance resembl
+ resemble resembl resembled resembl
+ resembles resembl resembleth resembleth
+ resembling resembl reserv reserv
+ reservation reserv reserve reserv
+ reserved reserv reserves reserv
+ reside resid residence resid
+ resident resid resides resid
+ residing resid residue residu
+ resign resign resignation resign
+ resist resist resistance resist
+ resisted resist resisting resist
+ resists resist resolute resolut
+ resolutely resolut resolutes resolut
+ resolution resolut resolv resolv
+ resolve resolv resolved resolv
+ resolvedly resolvedli resolves resolv
+ resolveth resolveth resort resort
+ resorted resort resounding resound
+ resounds resound respeaking respeak
+ respect respect respected respect
+ respecting respect respective respect
+ respectively respect respects respect
+ respice respic respite respit
+ respites respit responsive respons
+ respose respos ress ress
+ rest rest rested rest
+ resteth resteth restful rest
+ resting rest restitution restitut
+ restless restless restor restor
+ restoration restor restorative restor
+ restore restor restored restor
+ restores restor restoring restor
+ restrain restrain restrained restrain
+ restraining restrain restrains restrain
+ restraint restraint rests rest
+ resty resti resum resum
+ resume resum resumes resum
+ resurrections resurrect retail retail
+ retails retail retain retain
+ retainers retain retaining retain
+ retell retel retention retent
+ retentive retent retinue retinu
+ retir retir retire retir
+ retired retir retirement retir
+ retires retir retiring retir
+ retold retold retort retort
+ retorts retort retourne retourn
+ retract retract retreat retreat
+ retrograde retrograd rets ret
+ return return returned return
+ returnest returnest returneth returneth
+ returning return returns return
+ revania revania reveal reveal
+ reveals reveal revel revel
+ reveler revel revell revel
+ reveller revel revellers revel
+ revelling revel revelry revelri
+ revels revel reveng reveng
+ revenge reveng revenged reveng
+ revengeful reveng revengement reveng
+ revenger reveng revengers reveng
+ revenges reveng revenging reveng
+ revengingly revengingli revenue revenu
+ revenues revenu reverb reverb
+ reverberate reverber reverbs reverb
+ reverenc reverenc reverence rever
+ reverend reverend reverent rever
+ reverently rever revers rever
+ reverse revers reversion revers
+ reverted revert review review
+ reviewest reviewest revil revil
+ revile revil revisits revisit
+ reviv reviv revive reviv
+ revives reviv reviving reviv
+ revok revok revoke revok
+ revokement revok revolt revolt
+ revolted revolt revolting revolt
+ revolts revolt revolution revolut
+ revolutions revolut revolve revolv
+ revolving revolv reward reward
+ rewarded reward rewarder reward
+ rewarding reward rewards reward
+ reword reword reworded reword
+ rex rex rey rei
+ reynaldo reynaldo rford rford
+ rful rful rfull rfull
+ rhapsody rhapsodi rheims rheim
+ rhenish rhenish rhesus rhesu
+ rhetoric rhetor rheum rheum
+ rheumatic rheumat rheums rheum
+ rheumy rheumi rhinoceros rhinocero
+ rhodes rhode rhodope rhodop
+ rhubarb rhubarb rhym rhym
+ rhyme rhyme rhymers rhymer
+ rhymes rhyme rhyming rhyme
+ rialto rialto rib rib
+ ribald ribald riband riband
+ ribands riband ribaudred ribaudr
+ ribb ribb ribbed rib
+ ribbon ribbon ribbons ribbon
+ ribs rib rice rice
+ rich rich richard richard
+ richer richer riches rich
+ richest richest richly richli
+ richmond richmond richmonds richmond
+ rid rid riddance riddanc
+ ridden ridden riddle riddl
+ riddles riddl riddling riddl
+ ride ride rider rider
+ riders rider rides ride
+ ridest ridest rideth rideth
+ ridge ridg ridges ridg
+ ridiculous ridicul riding ride
+ rids rid rien rien
+ ries ri rifle rifl
+ rift rift rifted rift
+ rig rig rigg rigg
+ riggish riggish right right
+ righteous righteou righteously righteous
+ rightful right rightfully rightfulli
+ rightly rightli rights right
+ rigol rigol rigorous rigor
+ rigorously rigor rigour rigour
+ ril ril rim rim
+ rin rin rinaldo rinaldo
+ rind rind ring ring
+ ringing ring ringleader ringlead
+ ringlets ringlet rings ring
+ ringwood ringwood riot riot
+ rioter rioter rioting riot
+ riotous riotou riots riot
+ rip rip ripe ripe
+ ripely ripe ripen ripen
+ ripened ripen ripeness ripe
+ ripening ripen ripens ripen
+ riper riper ripest ripest
+ riping ripe ripp ripp
+ ripping rip rise rise
+ risen risen rises rise
+ riseth riseth rish rish
+ rising rise rite rite
+ rites rite rivage rivag
+ rival rival rivality rival
+ rivall rival rivals rival
+ rive rive rived rive
+ rivelled rivel river river
+ rivers river rivet rivet
+ riveted rivet rivets rivet
+ rivo rivo rj rj
+ rless rless road road
+ roads road roam roam
+ roaming roam roan roan
+ roar roar roared roar
+ roarers roarer roaring roar
+ roars roar roast roast
+ roasted roast rob rob
+ roba roba robas roba
+ robb robb robbed rob
+ robber robber robbers robber
+ robbery robberi robbing rob
+ robe robe robed robe
+ robert robert robes robe
+ robin robin robs rob
+ robustious robusti rochester rochest
+ rochford rochford rock rock
+ rocks rock rocky rocki
+ rod rod rode rode
+ roderigo roderigo rods rod
+ roe roe roes roe
+ roger roger rogero rogero
+ rogue rogu roguery rogueri
+ rogues rogu roguish roguish
+ roi roi roisting roist
+ roll roll rolled roll
+ rolling roll rolls roll
+ rom rom romage romag
+ roman roman romano romano
+ romanos romano romans roman
+ rome rome romeo romeo
+ romish romish rondure rondur
+ ronyon ronyon rood rood
+ roof roof roofs roof
+ rook rook rooks rook
+ rooky rooki room room
+ rooms room root root
+ rooted root rootedly rootedli
+ rooteth rooteth rooting root
+ roots root rope rope
+ ropery roperi ropes rope
+ roping rope ros ro
+ rosalind rosalind rosalinda rosalinda
+ rosalinde rosalind rosaline rosalin
+ roscius rosciu rose rose
+ rosed rose rosemary rosemari
+ rosencrantz rosencrantz roses rose
+ ross ross rosy rosi
+ rot rot rote rote
+ roted rote rother rother
+ rotherham rotherham rots rot
+ rotted rot rotten rotten
+ rottenness rotten rotting rot
+ rotundity rotund rouen rouen
+ rough rough rougher rougher
+ roughest roughest roughly roughli
+ roughness rough round round
+ rounded round roundel roundel
+ rounder rounder roundest roundest
+ rounding round roundly roundli
+ rounds round roundure roundur
+ rous rou rouse rous
+ roused rous rousillon rousillon
+ rously rousli roussi roussi
+ rout rout routed rout
+ routs rout rove rove
+ rover rover row row
+ rowel rowel rowland rowland
+ rowlands rowland roy roi
+ royal royal royalize royal
+ royally royal royalties royalti
+ royalty royalti roynish roynish
+ rs rs rt rt
+ rub rub rubb rubb
+ rubbing rub rubbish rubbish
+ rubies rubi rubious rubiou
+ rubs rub ruby rubi
+ rud rud rudand rudand
+ rudder rudder ruddiness ruddi
+ ruddock ruddock ruddy ruddi
+ rude rude rudely rude
+ rudeness rude ruder ruder
+ rudesby rudesbi rudest rudest
+ rudiments rudiment rue rue
+ rued ru ruff ruff
+ ruffian ruffian ruffians ruffian
+ ruffle ruffl ruffling ruffl
+ ruffs ruff rug rug
+ rugby rugbi rugemount rugemount
+ rugged rug ruin ruin
+ ruinate ruinat ruined ruin
+ ruining ruin ruinous ruinou
+ ruins ruin rul rul
+ rule rule ruled rule
+ ruler ruler rulers ruler
+ rules rule ruling rule
+ rumble rumbl ruminaies ruminai
+ ruminat ruminat ruminate rumin
+ ruminated rumin ruminates rumin
+ rumination rumin rumor rumor
+ rumour rumour rumourer rumour
+ rumours rumour rump rump
+ run run runagate runag
+ runagates runag runaway runawai
+ runaways runawai rung rung
+ runn runn runner runner
+ runners runner running run
+ runs run rupture ruptur
+ ruptures ruptur rural rural
+ rush rush rushes rush
+ rushing rush rushling rushl
+ rushy rushi russet russet
+ russia russia russian russian
+ russians russian rust rust
+ rusted rust rustic rustic
+ rustically rustic rustics rustic
+ rustle rustl rustling rustl
+ rusts rust rusty rusti
+ rut rut ruth ruth
+ ruthful ruth ruthless ruthless
+ rutland rutland ruttish ruttish
+ ry ry rye rye
+ rything ryth s s
+ sa sa saba saba
+ sabbath sabbath sable sabl
+ sables sabl sack sack
+ sackbuts sackbut sackcloth sackcloth
+ sacked sack sackerson sackerson
+ sacks sack sacrament sacrament
+ sacred sacr sacrific sacrif
+ sacrifice sacrific sacrificers sacrific
+ sacrifices sacrific sacrificial sacrifici
+ sacrificing sacrif sacrilegious sacrilegi
+ sacring sacr sad sad
+ sadder sadder saddest saddest
+ saddle saddl saddler saddler
+ saddles saddl sadly sadli
+ sadness sad saf saf
+ safe safe safeguard safeguard
+ safely safe safer safer
+ safest safest safeties safeti
+ safety safeti saffron saffron
+ sag sag sage sage
+ sagittary sagittari said said
+ saidst saidst sail sail
+ sailing sail sailmaker sailmak
+ sailor sailor sailors sailor
+ sails sail sain sain
+ saint saint sainted saint
+ saintlike saintlik saints saint
+ saith saith sake sake
+ sakes sake sala sala
+ salad salad salamander salamand
+ salary salari sale sale
+ salerio salerio salicam salicam
+ salique saliqu salisbury salisburi
+ sall sall sallet sallet
+ sallets sallet sallies salli
+ sallow sallow sally salli
+ salmon salmon salmons salmon
+ salt salt salter salter
+ saltiers saltier saltness salt
+ saltpetre saltpetr salutation salut
+ salutations salut salute salut
+ saluted salut salutes salut
+ saluteth saluteth salv salv
+ salvation salvat salve salv
+ salving salv same same
+ samingo samingo samp samp
+ sampire sampir sample sampl
+ sampler sampler sampson sampson
+ samson samson samsons samson
+ sancta sancta sanctified sanctifi
+ sanctifies sanctifi sanctify sanctifi
+ sanctimonies sanctimoni sanctimonious sanctimoni
+ sanctimony sanctimoni sanctities sanctiti
+ sanctity sanctiti sanctuarize sanctuar
+ sanctuary sanctuari sand sand
+ sandal sandal sandbag sandbag
+ sanded sand sands sand
+ sandy sandi sandys sandi
+ sang sang sanguine sanguin
+ sanguis sangui sanity saniti
+ sans san santrailles santrail
+ sap sap sapient sapient
+ sapit sapit sapless sapless
+ sapling sapl sapphire sapphir
+ sapphires sapphir saracens saracen
+ sarcenet sarcenet sard sard
+ sardians sardian sardinia sardinia
+ sardis sardi sarum sarum
+ sat sat satan satan
+ satchel satchel sate sate
+ sated sate satiate satiat
+ satiety satieti satin satin
+ satire satir satirical satir
+ satis sati satisfaction satisfact
+ satisfied satisfi satisfies satisfi
+ satisfy satisfi satisfying satisfi
+ saturday saturdai saturdays saturdai
+ saturn saturn saturnine saturnin
+ saturninus saturninu satyr satyr
+ satyrs satyr sauc sauc
+ sauce sauc sauced sauc
+ saucers saucer sauces sauc
+ saucily saucili sauciness sauci
+ saucy sauci sauf sauf
+ saunder saunder sav sav
+ savage savag savagely savag
+ savageness savag savagery savageri
+ savages savag save save
+ saved save saves save
+ saving save saviour saviour
+ savory savori savour savour
+ savouring savour savours savour
+ savoury savouri savoy savoi
+ saw saw sawed saw
+ sawest sawest sawn sawn
+ sawpit sawpit saws saw
+ sawyer sawyer saxons saxon
+ saxony saxoni saxton saxton
+ say sai sayest sayest
+ saying sai sayings sai
+ says sai sayst sayst
+ sblood sblood sc sc
+ scab scab scabbard scabbard
+ scabs scab scaffold scaffold
+ scaffoldage scaffoldag scal scal
+ scald scald scalded scald
+ scalding scald scale scale
+ scaled scale scales scale
+ scaling scale scall scall
+ scalp scalp scalps scalp
+ scaly scali scamble scambl
+ scambling scambl scamels scamel
+ scan scan scandal scandal
+ scandaliz scandaliz scandalous scandal
+ scandy scandi scann scann
+ scant scant scanted scant
+ scanter scanter scanting scant
+ scantling scantl scants scant
+ scap scap scape scape
+ scaped scape scapes scape
+ scapeth scapeth scar scar
+ scarce scarc scarcely scarc
+ scarcity scarciti scare scare
+ scarecrow scarecrow scarecrows scarecrow
+ scarf scarf scarfed scarf
+ scarfs scarf scaring scare
+ scarlet scarlet scarr scarr
+ scarre scarr scars scar
+ scarus scaru scath scath
+ scathe scath scathful scath
+ scatt scatt scatter scatter
+ scattered scatter scattering scatter
+ scatters scatter scelera scelera
+ scelerisque scelerisqu scene scene
+ scenes scene scent scent
+ scented scent scept scept
+ scepter scepter sceptre sceptr
+ sceptred sceptr sceptres sceptr
+ schedule schedul schedules schedul
+ scholar scholar scholarly scholarli
+ scholars scholar school school
+ schoolboy schoolboi schoolboys schoolboi
+ schoolfellows schoolfellow schooling school
+ schoolmaster schoolmast schoolmasters schoolmast
+ schools school sciatica sciatica
+ sciaticas sciatica science scienc
+ sciences scienc scimitar scimitar
+ scion scion scions scion
+ scissors scissor scoff scoff
+ scoffer scoffer scoffing scof
+ scoffs scoff scoggin scoggin
+ scold scold scolding scold
+ scolds scold sconce sconc
+ scone scone scope scope
+ scopes scope scorch scorch
+ scorched scorch score score
+ scored score scores score
+ scoring score scorn scorn
+ scorned scorn scornful scorn
+ scornfully scornfulli scorning scorn
+ scorns scorn scorpion scorpion
+ scorpions scorpion scot scot
+ scotch scotch scotches scotch
+ scotland scotland scots scot
+ scottish scottish scoundrels scoundrel
+ scour scour scoured scour
+ scourg scourg scourge scourg
+ scouring scour scout scout
+ scouts scout scowl scowl
+ scrap scrap scrape scrape
+ scraping scrape scraps scrap
+ scratch scratch scratches scratch
+ scratching scratch scream scream
+ screams scream screech screech
+ screeching screech screen screen
+ screens screen screw screw
+ screws screw scribbl scribbl
+ scribbled scribbl scribe scribe
+ scribes scribe scrimers scrimer
+ scrip scrip scrippage scrippag
+ scripture scriptur scriptures scriptur
+ scrivener scriven scroll scroll
+ scrolls scroll scroop scroop
+ scrowl scrowl scroyles scroyl
+ scrubbed scrub scruple scrupl
+ scruples scrupl scrupulous scrupul
+ scuffles scuffl scuffling scuffl
+ scullion scullion sculls scull
+ scum scum scurril scurril
+ scurrility scurril scurrilous scurril
+ scurvy scurvi scuse scuse
+ scut scut scutcheon scutcheon
+ scutcheons scutcheon scylla scylla
+ scythe scyth scythed scyth
+ scythia scythia scythian scythian
+ sdeath sdeath se se
+ sea sea seacoal seacoal
+ seafaring seafar seal seal
+ sealed seal sealing seal
+ seals seal seam seam
+ seamen seamen seamy seami
+ seaport seaport sear sear
+ searce searc search search
+ searchers searcher searches search
+ searcheth searcheth searching search
+ seared sear seas sea
+ seasick seasick seaside seasid
+ season season seasoned season
+ seasons season seat seat
+ seated seat seats seat
+ sebastian sebastian second second
+ secondarily secondarili secondary secondari
+ seconded second seconds second
+ secrecy secreci secret secret
+ secretaries secretari secretary secretari
+ secretly secretli secrets secret
+ sect sect sectary sectari
+ sects sect secundo secundo
+ secure secur securely secur
+ securing secur security secur
+ sedg sedg sedge sedg
+ sedges sedg sedgy sedgi
+ sedition sedit seditious sediti
+ seduc seduc seduce seduc
+ seduced seduc seducer seduc
+ seducing seduc see see
+ seed seed seeded seed
+ seedness seed seeds seed
+ seedsman seedsman seein seein
+ seeing see seek seek
+ seeking seek seeks seek
+ seel seel seeling seel
+ seely seeli seem seem
+ seemed seem seemers seemer
+ seemest seemest seemeth seemeth
+ seeming seem seemingly seemingli
+ seemly seemli seems seem
+ seen seen seer seer
+ sees see seese sees
+ seest seest seethe seeth
+ seethes seeth seething seeth
+ seeting seet segregation segreg
+ seigneur seigneur seigneurs seigneur
+ seiz seiz seize seiz
+ seized seiz seizes seiz
+ seizeth seizeth seizing seiz
+ seizure seizur seld seld
+ seldom seldom select select
+ seleucus seleucu self self
+ selfsame selfsam sell sell
+ seller seller selling sell
+ sells sell selves selv
+ semblable semblabl semblably semblabl
+ semblance semblanc semblances semblanc
+ semblative sembl semi semi
+ semicircle semicircl semiramis semirami
+ semper semper sempronius semproniu
+ senate senat senator senat
+ senators senat send send
+ sender sender sendeth sendeth
+ sending send sends send
+ seneca seneca senior senior
+ seniory seniori senis seni
+ sennet sennet senoys senoi
+ sense sens senseless senseless
+ senses sens sensible sensibl
+ sensibly sensibl sensual sensual
+ sensuality sensual sent sent
+ sentenc sentenc sentence sentenc
+ sentences sentenc sententious sententi
+ sentinel sentinel sentinels sentinel
+ separable separ separate separ
+ separated separ separates separ
+ separation separ septentrion septentrion
+ sepulchre sepulchr sepulchres sepulchr
+ sepulchring sepulchr sequel sequel
+ sequence sequenc sequent sequent
+ sequest sequest sequester sequest
+ sequestration sequestr sere sere
+ serenis sereni serge serg
+ sergeant sergeant serious seriou
+ seriously serious sermon sermon
+ sermons sermon serpent serpent
+ serpentine serpentin serpents serpent
+ serpigo serpigo serv serv
+ servant servant servanted servant
+ servants servant serve serv
+ served serv server server
+ serves serv serveth serveth
+ service servic serviceable servic
+ services servic servile servil
+ servility servil servilius serviliu
+ serving serv servingman servingman
+ servingmen servingmen serviteur serviteur
+ servitor servitor servitors servitor
+ servitude servitud sessa sessa
+ session session sessions session
+ sestos sesto set set
+ setebos setebo sets set
+ setter setter setting set
+ settle settl settled settl
+ settlest settlest settling settl
+ sev sev seven seven
+ sevenfold sevenfold sevennight sevennight
+ seventeen seventeen seventh seventh
+ seventy seventi sever sever
+ several sever severally sever
+ severals sever severe sever
+ severed sever severely sever
+ severest severest severing sever
+ severity sever severn severn
+ severs sever sew sew
+ seward seward sewer sewer
+ sewing sew sex sex
+ sexes sex sexton sexton
+ sextus sextu seymour seymour
+ seyton seyton sfoot sfoot
+ sh sh shackle shackl
+ shackles shackl shade shade
+ shades shade shadow shadow
+ shadowed shadow shadowing shadow
+ shadows shadow shadowy shadowi
+ shady shadi shafalus shafalu
+ shaft shaft shafts shaft
+ shag shag shak shak
+ shake shake shaked shake
+ shaken shaken shakes shake
+ shaking shake shales shale
+ shall shall shallenge shalleng
+ shallow shallow shallowest shallowest
+ shallowly shallowli shallows shallow
+ shalt shalt sham sham
+ shambles shambl shame shame
+ shamed shame shameful shame
+ shamefully shamefulli shameless shameless
+ shames shame shamest shamest
+ shaming shame shank shank
+ shanks shank shap shap
+ shape shape shaped shape
+ shapeless shapeless shapen shapen
+ shapes shape shaping shape
+ shar shar shard shard
+ sharded shard shards shard
+ share share shared share
+ sharers sharer shares share
+ sharing share shark shark
+ sharp sharp sharpen sharpen
+ sharpened sharpen sharpens sharpen
+ sharper sharper sharpest sharpest
+ sharply sharpli sharpness sharp
+ sharps sharp shatter shatter
+ shav shav shave shave
+ shaven shaven shaw shaw
+ she she sheaf sheaf
+ sheal sheal shear shear
+ shearers shearer shearing shear
+ shearman shearman shears shear
+ sheath sheath sheathe sheath
+ sheathed sheath sheathes sheath
+ sheathing sheath sheaved sheav
+ sheaves sheav shed shed
+ shedding shed sheds shed
+ sheen sheen sheep sheep
+ sheepcote sheepcot sheepcotes sheepcot
+ sheeps sheep sheepskins sheepskin
+ sheer sheer sheet sheet
+ sheeted sheet sheets sheet
+ sheffield sheffield shelf shelf
+ shell shell shells shell
+ shelt shelt shelter shelter
+ shelters shelter shelves shelv
+ shelving shelv shelvy shelvi
+ shent shent shepherd shepherd
+ shepherdes shepherd shepherdess shepherdess
+ shepherdesses shepherdess shepherds shepherd
+ sher sher sheriff sheriff
+ sherris sherri shes she
+ sheweth sheweth shield shield
+ shielded shield shields shield
+ shift shift shifted shift
+ shifting shift shifts shift
+ shilling shill shillings shill
+ shin shin shine shine
+ shines shine shineth shineth
+ shining shine shins shin
+ shiny shini ship ship
+ shipboard shipboard shipman shipman
+ shipmaster shipmast shipmen shipmen
+ shipp shipp shipped ship
+ shipping ship ships ship
+ shipt shipt shipwreck shipwreck
+ shipwrecking shipwreck shipwright shipwright
+ shipwrights shipwright shire shire
+ shirley shirlei shirt shirt
+ shirts shirt shive shive
+ shiver shiver shivering shiver
+ shivers shiver shoal shoal
+ shoals shoal shock shock
+ shocks shock shod shod
+ shoe shoe shoeing shoe
+ shoemaker shoemak shoes shoe
+ shog shog shone shone
+ shook shook shoon shoon
+ shoot shoot shooter shooter
+ shootie shooti shooting shoot
+ shoots shoot shop shop
+ shops shop shore shore
+ shores shore shorn shorn
+ short short shortcake shortcak
+ shorten shorten shortened shorten
+ shortens shorten shorter shorter
+ shortly shortli shortness short
+ shot shot shotten shotten
+ shoughs shough should should
+ shoulder shoulder shouldering shoulder
+ shoulders shoulder shouldst shouldst
+ shout shout shouted shout
+ shouting shout shouts shout
+ shov shov shove shove
+ shovel shovel shovels shovel
+ show show showed show
+ shower shower showers shower
+ showest showest showing show
+ shown shown shows show
+ shreds shred shrew shrew
+ shrewd shrewd shrewdly shrewdli
+ shrewdness shrewd shrewish shrewish
+ shrewishly shrewishli shrewishness shrewish
+ shrews shrew shrewsbury shrewsburi
+ shriek shriek shrieking shriek
+ shrieks shriek shrieve shriev
+ shrift shrift shrill shrill
+ shriller shriller shrills shrill
+ shrilly shrilli shrimp shrimp
+ shrine shrine shrink shrink
+ shrinking shrink shrinks shrink
+ shriv shriv shrive shrive
+ shriver shriver shrives shrive
+ shriving shrive shroud shroud
+ shrouded shroud shrouding shroud
+ shrouds shroud shrove shrove
+ shrow shrow shrows shrow
+ shrub shrub shrubs shrub
+ shrug shrug shrugs shrug
+ shrunk shrunk shudd shudd
+ shudders shudder shuffl shuffl
+ shuffle shuffl shuffled shuffl
+ shuffling shuffl shun shun
+ shunless shunless shunn shunn
+ shunned shun shunning shun
+ shuns shun shut shut
+ shuts shut shuttle shuttl
+ shy shy shylock shylock
+ si si sibyl sibyl
+ sibylla sibylla sibyls sibyl
+ sicil sicil sicilia sicilia
+ sicilian sicilian sicilius siciliu
+ sicils sicil sicily sicili
+ sicinius siciniu sick sick
+ sicken sicken sickens sicken
+ sicker sicker sickle sickl
+ sicklemen sicklemen sicklied sickli
+ sickliness sickli sickly sickli
+ sickness sick sicles sicl
+ sicyon sicyon side side
+ sided side sides side
+ siege sieg sieges sieg
+ sienna sienna sies si
+ sieve siev sift sift
+ sifted sift sigeia sigeia
+ sigh sigh sighed sigh
+ sighing sigh sighs sigh
+ sight sight sighted sight
+ sightless sightless sightly sightli
+ sights sight sign sign
+ signal signal signet signet
+ signieur signieur significant signific
+ significants signific signified signifi
+ signifies signifi signify signifi
+ signifying signifi signior signior
+ signiories signiori signiors signior
+ signiory signiori signor signor
+ signories signori signs sign
+ signum signum silenc silenc
+ silence silenc silenced silenc
+ silencing silenc silent silent
+ silently silent silius siliu
+ silk silk silken silken
+ silkman silkman silks silk
+ silliest silliest silliness silli
+ silling sill silly silli
+ silva silva silver silver
+ silvered silver silverly silverli
+ silvia silvia silvius silviu
+ sima sima simile simil
+ similes simil simois simoi
+ simon simon simony simoni
+ simp simp simpcox simpcox
+ simple simpl simpleness simpl
+ simpler simpler simples simpl
+ simplicity simplic simply simpli
+ simular simular simulation simul
+ sin sin since sinc
+ sincere sincer sincerely sincer
+ sincerity sincer sinel sinel
+ sinew sinew sinewed sinew
+ sinews sinew sinewy sinewi
+ sinful sin sinfully sinfulli
+ sing sing singe sing
+ singeing sing singer singer
+ singes sing singeth singeth
+ singing sing single singl
+ singled singl singleness singl
+ singly singli sings sing
+ singular singular singulariter singularit
+ singularities singular singularity singular
+ singuled singul sinister sinist
+ sink sink sinking sink
+ sinks sink sinn sinn
+ sinner sinner sinners sinner
+ sinning sin sinon sinon
+ sins sin sip sip
+ sipping sip sir sir
+ sire sire siren siren
+ sirrah sirrah sirs sir
+ sist sist sister sister
+ sisterhood sisterhood sisterly sisterli
+ sisters sister sit sit
+ sith sith sithence sithenc
+ sits sit sitting sit
+ situate situat situation situat
+ situations situat siward siward
+ six six sixpence sixpenc
+ sixpences sixpenc sixpenny sixpenni
+ sixteen sixteen sixth sixth
+ sixty sixti siz siz
+ size size sizes size
+ sizzle sizzl skains skain
+ skamble skambl skein skein
+ skelter skelter skies ski
+ skilful skil skilfully skilfulli
+ skill skill skilless skilless
+ skillet skillet skillful skill
+ skills skill skim skim
+ skimble skimbl skin skin
+ skinker skinker skinny skinni
+ skins skin skip skip
+ skipp skipp skipper skipper
+ skipping skip skirmish skirmish
+ skirmishes skirmish skirr skirr
+ skirted skirt skirts skirt
+ skittish skittish skulking skulk
+ skull skull skulls skull
+ sky sky skyey skyei
+ skyish skyish slab slab
+ slack slack slackly slackli
+ slackness slack slain slain
+ slake slake sland sland
+ slander slander slandered slander
+ slanderer slander slanderers slander
+ slandering slander slanderous slander
+ slanders slander slash slash
+ slaught slaught slaughter slaughter
+ slaughtered slaughter slaughterer slaughter
+ slaughterman slaughterman slaughtermen slaughtermen
+ slaughterous slaughter slaughters slaughter
+ slave slave slaver slaver
+ slavery slaveri slaves slave
+ slavish slavish slay slai
+ slayeth slayeth slaying slai
+ slays slai sleave sleav
+ sledded sled sleek sleek
+ sleekly sleekli sleep sleep
+ sleeper sleeper sleepers sleeper
+ sleepest sleepest sleeping sleep
+ sleeps sleep sleepy sleepi
+ sleeve sleev sleeves sleev
+ sleid sleid sleided sleid
+ sleight sleight sleights sleight
+ slender slender slenderer slender
+ slenderly slenderli slept slept
+ slew slew slewest slewest
+ slice slice slid slid
+ slide slide slides slide
+ sliding slide slight slight
+ slighted slight slightest slightest
+ slightly slightli slightness slight
+ slights slight slily slili
+ slime slime slimy slimi
+ slings sling slink slink
+ slip slip slipp slipp
+ slipper slipper slippers slipper
+ slippery slipperi slips slip
+ slish slish slit slit
+ sliver sliver slobb slobb
+ slomber slomber slop slop
+ slope slope slops slop
+ sloth sloth slothful sloth
+ slough slough slovenly slovenli
+ slovenry slovenri slow slow
+ slower slower slowly slowli
+ slowness slow slubber slubber
+ slug slug sluggard sluggard
+ sluggardiz sluggardiz sluggish sluggish
+ sluic sluic slumb slumb
+ slumber slumber slumbers slumber
+ slumbery slumberi slunk slunk
+ slut slut sluts slut
+ sluttery slutteri sluttish sluttish
+ sluttishness sluttish sly sly
+ slys sly smack smack
+ smacking smack smacks smack
+ small small smaller smaller
+ smallest smallest smallness small
+ smalus smalu smart smart
+ smarting smart smartly smartli
+ smatch smatch smatter smatter
+ smear smear smell smell
+ smelling smell smells smell
+ smelt smelt smil smil
+ smile smile smiled smile
+ smiles smile smilest smilest
+ smilets smilet smiling smile
+ smilingly smilingli smirch smirch
+ smirched smirch smit smit
+ smite smite smites smite
+ smith smith smithfield smithfield
+ smock smock smocks smock
+ smok smok smoke smoke
+ smoked smoke smokes smoke
+ smoking smoke smoky smoki
+ smooth smooth smoothed smooth
+ smoothing smooth smoothly smoothli
+ smoothness smooth smooths smooth
+ smote smote smoth smoth
+ smother smother smothered smother
+ smothering smother smug smug
+ smulkin smulkin smutch smutch
+ snaffle snaffl snail snail
+ snails snail snake snake
+ snakes snake snaky snaki
+ snap snap snapp snapp
+ snapper snapper snar snar
+ snare snare snares snare
+ snarl snarl snarleth snarleth
+ snarling snarl snatch snatch
+ snatchers snatcher snatches snatch
+ snatching snatch sneak sneak
+ sneaking sneak sneap sneap
+ sneaping sneap sneck sneck
+ snip snip snipe snipe
+ snipt snipt snore snore
+ snores snore snoring snore
+ snorting snort snout snout
+ snow snow snowballs snowbal
+ snowed snow snowy snowi
+ snuff snuff snuffs snuff
+ snug snug so so
+ soak soak soaking soak
+ soaks soak soar soar
+ soaring soar soars soar
+ sob sob sobbing sob
+ sober sober soberly soberli
+ sobriety sobrieti sobs sob
+ sociable sociabl societies societi
+ society societi socks sock
+ socrates socrat sod sod
+ sodden sodden soe soe
+ soever soever soft soft
+ soften soften softens soften
+ softer softer softest softest
+ softly softli softness soft
+ soil soil soiled soil
+ soilure soilur soit soit
+ sojourn sojourn sol sol
+ sola sola solace solac
+ solanio solanio sold sold
+ soldat soldat solder solder
+ soldest soldest soldier soldier
+ soldiers soldier soldiership soldiership
+ sole sole solely sole
+ solem solem solemn solemn
+ solemness solem solemnities solemn
+ solemnity solemn solemniz solemniz
+ solemnize solemn solemnized solemn
+ solemnly solemnli soles sole
+ solicit solicit solicitation solicit
+ solicited solicit soliciting solicit
+ solicitings solicit solicitor solicitor
+ solicits solicit solid solid
+ solidares solidar solidity solid
+ solinus solinu solitary solitari
+ solomon solomon solon solon
+ solum solum solus solu
+ solyman solyman some some
+ somebody somebodi someone someon
+ somerset somerset somerville somervil
+ something someth sometime sometim
+ sometimes sometim somever somev
+ somewhat somewhat somewhere somewher
+ somewhither somewhith somme somm
+ son son sonance sonanc
+ song song songs song
+ sonnet sonnet sonneting sonnet
+ sonnets sonnet sons son
+ sont sont sonties sonti
+ soon soon sooner sooner
+ soonest soonest sooth sooth
+ soothe sooth soothers soother
+ soothing sooth soothsay soothsai
+ soothsayer soothsay sooty sooti
+ sop sop sophister sophist
+ sophisticated sophist sophy sophi
+ sops sop sorcerer sorcer
+ sorcerers sorcer sorceress sorceress
+ sorceries sorceri sorcery sorceri
+ sore sore sorel sorel
+ sorely sore sorer sorer
+ sores sore sorrier sorrier
+ sorriest sorriest sorrow sorrow
+ sorrowed sorrow sorrowest sorrowest
+ sorrowful sorrow sorrowing sorrow
+ sorrows sorrow sorry sorri
+ sort sort sortance sortanc
+ sorted sort sorting sort
+ sorts sort sossius sossiu
+ sot sot soto soto
+ sots sot sottish sottish
+ soud soud sought sought
+ soul soul sould sould
+ soulless soulless souls soul
+ sound sound sounded sound
+ sounder sounder soundest soundest
+ sounding sound soundless soundless
+ soundly soundli soundness sound
+ soundpost soundpost sounds sound
+ sour sour source sourc
+ sources sourc sourest sourest
+ sourly sourli sours sour
+ sous sou souse sous
+ south south southam southam
+ southampton southampton southerly southerli
+ southern southern southward southward
+ southwark southwark southwell southwel
+ souviendrai souviendrai sov sov
+ sovereign sovereign sovereignest sovereignest
+ sovereignly sovereignli sovereignty sovereignti
+ sovereignvours sovereignvour sow sow
+ sowing sow sowl sowl
+ sowter sowter space space
+ spaces space spacious spaciou
+ spade spade spades spade
+ spain spain spak spak
+ spake spake spakest spakest
+ span span spangle spangl
+ spangled spangl spaniard spaniard
+ spaniel spaniel spaniels spaniel
+ spanish spanish spann spann
+ spans span spar spar
+ spare spare spares spare
+ sparing spare sparingly sparingli
+ spark spark sparkle sparkl
+ sparkles sparkl sparkling sparkl
+ sparks spark sparrow sparrow
+ sparrows sparrow sparta sparta
+ spartan spartan spavin spavin
+ spavins spavin spawn spawn
+ speak speak speaker speaker
+ speakers speaker speakest speakest
+ speaketh speaketh speaking speak
+ speaks speak spear spear
+ speargrass speargrass spears spear
+ special special specialities special
+ specially special specialties specialti
+ specialty specialti specify specifi
+ speciously specious spectacle spectacl
+ spectacled spectacl spectacles spectacl
+ spectators spectat spectatorship spectatorship
+ speculation specul speculations specul
+ speculative specul sped sped
+ speech speech speeches speech
+ speechless speechless speed speed
+ speeded speed speedier speedier
+ speediest speediest speedily speedili
+ speediness speedi speeding speed
+ speeds speed speedy speedi
+ speens speen spell spell
+ spelling spell spells spell
+ spelt spelt spencer spencer
+ spend spend spendest spendest
+ spending spend spends spend
+ spendthrift spendthrift spent spent
+ sperato sperato sperm sperm
+ spero spero sperr sperr
+ spher spher sphere sphere
+ sphered sphere spheres sphere
+ spherical spheric sphery spheri
+ sphinx sphinx spice spice
+ spiced spice spicery spiceri
+ spices spice spider spider
+ spiders spider spied spi
+ spies spi spieth spieth
+ spightfully spightfulli spigot spigot
+ spill spill spilling spill
+ spills spill spilt spilt
+ spilth spilth spin spin
+ spinii spinii spinners spinner
+ spinster spinster spinsters spinster
+ spire spire spirit spirit
+ spirited spirit spiritless spiritless
+ spirits spirit spiritual spiritu
+ spiritualty spiritualti spirt spirt
+ spit spit spital spital
+ spite spite spited spite
+ spiteful spite spites spite
+ spits spit spitted spit
+ spitting spit splay splai
+ spleen spleen spleenful spleen
+ spleens spleen spleeny spleeni
+ splendour splendour splenitive splenit
+ splinter splinter splinters splinter
+ split split splits split
+ splitted split splitting split
+ spoil spoil spoils spoil
+ spok spok spoke spoke
+ spoken spoken spokes spoke
+ spokesman spokesman sponge spong
+ spongy spongi spoon spoon
+ spoons spoon sport sport
+ sportful sport sporting sport
+ sportive sportiv sports sport
+ spot spot spotless spotless
+ spots spot spotted spot
+ spousal spousal spouse spous
+ spout spout spouting spout
+ spouts spout sprag sprag
+ sprang sprang sprat sprat
+ sprawl sprawl spray sprai
+ sprays sprai spread spread
+ spreading spread spreads spread
+ sprighted spright sprightful spright
+ sprightly sprightli sprigs sprig
+ spring spring springe spring
+ springes spring springeth springeth
+ springhalt springhalt springing spring
+ springs spring springtime springtim
+ sprinkle sprinkl sprinkles sprinkl
+ sprite sprite sprited sprite
+ spritely sprite sprites sprite
+ spriting sprite sprout sprout
+ spruce spruce sprung sprung
+ spun spun spur spur
+ spurio spurio spurn spurn
+ spurns spurn spurr spurr
+ spurrer spurrer spurring spur
+ spurs spur spy spy
+ spying spy squabble squabbl
+ squadron squadron squadrons squadron
+ squand squand squar squar
+ square squar squarer squarer
+ squares squar squash squash
+ squeak squeak squeaking squeak
+ squeal squeal squealing squeal
+ squeezes squeez squeezing squeez
+ squele squel squier squier
+ squints squint squiny squini
+ squire squir squires squir
+ squirrel squirrel st st
+ stab stab stabb stabb
+ stabbed stab stabbing stab
+ stable stabl stableness stabl
+ stables stabl stablish stablish
+ stablishment stablish stabs stab
+ stacks stack staff staff
+ stafford stafford staffords stafford
+ staffordshire staffordshir stag stag
+ stage stage stages stage
+ stagger stagger staggering stagger
+ staggers stagger stags stag
+ staid staid staider staider
+ stain stain stained stain
+ staines stain staineth staineth
+ staining stain stainless stainless
+ stains stain stair stair
+ stairs stair stake stake
+ stakes stake stale stale
+ staled stale stalk stalk
+ stalking stalk stalks stalk
+ stall stall stalling stall
+ stalls stall stamford stamford
+ stammer stammer stamp stamp
+ stamped stamp stamps stamp
+ stanch stanch stanchless stanchless
+ stand stand standard standard
+ standards standard stander stander
+ standers stander standest standest
+ standeth standeth standing stand
+ stands stand staniel staniel
+ stanley stanlei stanze stanz
+ stanzo stanzo stanzos stanzo
+ staple stapl staples stapl
+ star star stare stare
+ stared stare stares stare
+ staring stare starings stare
+ stark stark starkly starkli
+ starlight starlight starling starl
+ starr starr starry starri
+ stars star start start
+ started start starting start
+ startingly startingli startle startl
+ startles startl starts start
+ starv starv starve starv
+ starved starv starvelackey starvelackei
+ starveling starvel starveth starveth
+ starving starv state state
+ statelier stateli stately state
+ states state statesman statesman
+ statesmen statesmen statilius statiliu
+ station station statist statist
+ statists statist statue statu
+ statues statu stature statur
+ statures statur statute statut
+ statutes statut stave stave
+ staves stave stay stai
+ stayed stai stayest stayest
+ staying stai stays stai
+ stead stead steaded stead
+ steadfast steadfast steadier steadier
+ steads stead steal steal
+ stealer stealer stealers stealer
+ stealing steal steals steal
+ stealth stealth stealthy stealthi
+ steed steed steeds steed
+ steel steel steeled steel
+ steely steeli steep steep
+ steeped steep steeple steepl
+ steeples steepl steeps steep
+ steepy steepi steer steer
+ steerage steerag steering steer
+ steers steer stelled stell
+ stem stem stemming stem
+ stench stench step step
+ stepdame stepdam stephano stephano
+ stephen stephen stepmothers stepmoth
+ stepp stepp stepping step
+ steps step sterile steril
+ sterility steril sterling sterl
+ stern stern sternage sternag
+ sterner sterner sternest sternest
+ sternness stern steterat steterat
+ stew stew steward steward
+ stewards steward stewardship stewardship
+ stewed stew stews stew
+ stick stick sticking stick
+ stickler stickler sticks stick
+ stiff stiff stiffen stiffen
+ stiffly stiffli stifle stifl
+ stifled stifl stifles stifl
+ stigmatic stigmat stigmatical stigmat
+ stile stile still still
+ stiller stiller stillest stillest
+ stillness still stilly stilli
+ sting sting stinging sting
+ stingless stingless stings sting
+ stink stink stinking stink
+ stinkingly stinkingli stinks stink
+ stint stint stinted stint
+ stints stint stir stir
+ stirr stirr stirred stir
+ stirrer stirrer stirrers stirrer
+ stirreth stirreth stirring stir
+ stirrup stirrup stirrups stirrup
+ stirs stir stitchery stitcheri
+ stitches stitch stithied stithi
+ stithy stithi stoccadoes stoccado
+ stoccata stoccata stock stock
+ stockfish stockfish stocking stock
+ stockings stock stockish stockish
+ stocks stock stog stog
+ stogs stog stoics stoic
+ stokesly stokesli stol stol
+ stole stole stolen stolen
+ stolest stolest stomach stomach
+ stomachers stomach stomaching stomach
+ stomachs stomach ston ston
+ stone stone stonecutter stonecutt
+ stones stone stonish stonish
+ stony stoni stood stood
+ stool stool stools stool
+ stoop stoop stooping stoop
+ stoops stoop stop stop
+ stope stope stopp stopp
+ stopped stop stopping stop
+ stops stop stor stor
+ store store storehouse storehous
+ storehouses storehous stores store
+ stories stori storm storm
+ stormed storm storming storm
+ storms storm stormy stormi
+ story stori stoup stoup
+ stoups stoup stout stout
+ stouter stouter stoutly stoutli
+ stoutness stout stover stover
+ stow stow stowage stowag
+ stowed stow strachy strachi
+ stragglers straggler straggling straggl
+ straight straight straightest straightest
+ straightway straightwai strain strain
+ strained strain straining strain
+ strains strain strait strait
+ straited strait straiter straiter
+ straitly straitli straitness strait
+ straits strait strand strand
+ strange strang strangely strang
+ strangeness strang stranger stranger
+ strangers stranger strangest strangest
+ strangle strangl strangled strangl
+ strangler strangler strangles strangl
+ strangling strangl strappado strappado
+ straps strap stratagem stratagem
+ stratagems stratagem stratford stratford
+ strato strato straw straw
+ strawberries strawberri strawberry strawberri
+ straws straw strawy strawi
+ stray strai straying strai
+ strays strai streak streak
+ streaks streak stream stream
+ streamers streamer streaming stream
+ streams stream streching strech
+ street street streets street
+ strength strength strengthen strengthen
+ strengthened strengthen strengthless strengthless
+ strengths strength stretch stretch
+ stretched stretch stretches stretch
+ stretching stretch strew strew
+ strewing strew strewings strew
+ strewments strewment stricken stricken
+ strict strict stricter stricter
+ strictest strictest strictly strictli
+ stricture strictur stride stride
+ strides stride striding stride
+ strife strife strifes strife
+ strik strik strike strike
+ strikers striker strikes strike
+ strikest strikest striking strike
+ string string stringless stringless
+ strings string strip strip
+ stripes stripe stripling stripl
+ striplings stripl stripp stripp
+ stripping strip striv striv
+ strive strive strives strive
+ striving strive strok strok
+ stroke stroke strokes stroke
+ strond strond stronds strond
+ strong strong stronger stronger
+ strongest strongest strongly strongli
+ strooke strook strossers strosser
+ strove strove strown strown
+ stroy stroi struck struck
+ strucken strucken struggle struggl
+ struggles struggl struggling struggl
+ strumpet strumpet strumpeted strumpet
+ strumpets strumpet strung strung
+ strut strut struts strut
+ strutted strut strutting strut
+ stubble stubbl stubborn stubborn
+ stubbornest stubbornest stubbornly stubbornli
+ stubbornness stubborn stuck stuck
+ studded stud student student
+ students student studied studi
+ studies studi studious studiou
+ studiously studious studs stud
+ study studi studying studi
+ stuff stuff stuffing stuf
+ stuffs stuff stumble stumbl
+ stumbled stumbl stumblest stumblest
+ stumbling stumbl stump stump
+ stumps stump stung stung
+ stupefy stupefi stupid stupid
+ stupified stupifi stuprum stuprum
+ sturdy sturdi sty sty
+ styga styga stygian stygian
+ styl styl style style
+ styx styx su su
+ sub sub subcontracted subcontract
+ subdu subdu subdue subdu
+ subdued subdu subduements subduement
+ subdues subdu subduing subdu
+ subject subject subjected subject
+ subjection subject subjects subject
+ submerg submerg submission submiss
+ submissive submiss submit submit
+ submits submit submitting submit
+ suborn suborn subornation suborn
+ suborned suborn subscrib subscrib
+ subscribe subscrib subscribed subscrib
+ subscribes subscrib subscription subscript
+ subsequent subsequ subsidies subsidi
+ subsidy subsidi subsist subsist
+ subsisting subsist substance substanc
+ substances substanc substantial substanti
+ substitute substitut substituted substitut
+ substitutes substitut substitution substitut
+ subtile subtil subtilly subtilli
+ subtle subtl subtleties subtleti
+ subtlety subtleti subtly subtli
+ subtractors subtractor suburbs suburb
+ subversion subvers subverts subvert
+ succedant succed succeed succe
+ succeeded succeed succeeders succeed
+ succeeding succeed succeeds succe
+ success success successantly successantli
+ successes success successful success
+ successfully successfulli succession success
+ successive success successively success
+ successor successor successors successor
+ succour succour succours succour
+ such such suck suck
+ sucker sucker suckers sucker
+ sucking suck suckle suckl
+ sucks suck sudden sudden
+ suddenly suddenli sue sue
+ sued su suerly suerli
+ sues sue sueth sueth
+ suff suff suffer suffer
+ sufferance suffer sufferances suffer
+ suffered suffer suffering suffer
+ suffers suffer suffic suffic
+ suffice suffic sufficed suffic
+ suffices suffic sufficeth sufficeth
+ sufficiency suffici sufficient suffici
+ sufficiently suffici sufficing suffic
+ sufficit sufficit suffigance suffig
+ suffocate suffoc suffocating suffoc
+ suffocation suffoc suffolk suffolk
+ suffrage suffrag suffrages suffrag
+ sug sug sugar sugar
+ sugarsop sugarsop suggest suggest
+ suggested suggest suggesting suggest
+ suggestion suggest suggestions suggest
+ suggests suggest suis sui
+ suit suit suitable suitabl
+ suited suit suiting suit
+ suitor suitor suitors suitor
+ suits suit suivez suivez
+ sullen sullen sullens sullen
+ sullied sulli sullies sulli
+ sully sulli sulph sulph
+ sulpherous sulpher sulphur sulphur
+ sulphurous sulphur sultan sultan
+ sultry sultri sum sum
+ sumless sumless summ summ
+ summa summa summary summari
+ summer summer summers summer
+ summit summit summon summon
+ summoners summon summons summon
+ sumpter sumpter sumptuous sumptuou
+ sumptuously sumptuous sums sum
+ sun sun sunbeams sunbeam
+ sunburning sunburn sunburnt sunburnt
+ sund sund sunday sundai
+ sundays sundai sunder sunder
+ sunders sunder sundry sundri
+ sung sung sunk sunk
+ sunken sunken sunny sunni
+ sunrising sunris suns sun
+ sunset sunset sunshine sunshin
+ sup sup super super
+ superficial superfici superficially superfici
+ superfluity superflu superfluous superflu
+ superfluously superflu superflux superflux
+ superior superior supernal supern
+ supernatural supernatur superpraise superprais
+ superscript superscript superscription superscript
+ superserviceable superservic superstition superstit
+ superstitious superstiti superstitiously superstiti
+ supersubtle supersubtl supervise supervis
+ supervisor supervisor supp supp
+ supper supper suppers supper
+ suppertime suppertim supping sup
+ supplant supplant supple suppl
+ suppler suppler suppliance supplianc
+ suppliant suppliant suppliants suppliant
+ supplicant supplic supplication supplic
+ supplications supplic supplie suppli
+ supplied suppli supplies suppli
+ suppliest suppliest supply suppli
+ supplyant supplyant supplying suppli
+ supplyment supplyment support support
+ supportable support supportance support
+ supported support supporter support
+ supporters support supporting support
+ supportor supportor suppos suppo
+ supposal suppos suppose suppos
+ supposed suppos supposes suppos
+ supposest supposest supposing suppos
+ supposition supposit suppress suppress
+ suppressed suppress suppresseth suppresseth
+ supremacy supremaci supreme suprem
+ sups sup sur sur
+ surance suranc surcease surceas
+ surd surd sure sure
+ surecard surecard surely sure
+ surer surer surest surest
+ sureties sureti surety sureti
+ surfeit surfeit surfeited surfeit
+ surfeiter surfeit surfeiting surfeit
+ surfeits surfeit surge surg
+ surgeon surgeon surgeons surgeon
+ surgere surger surgery surgeri
+ surges surg surly surli
+ surmis surmi surmise surmis
+ surmised surmis surmises surmis
+ surmount surmount surmounted surmount
+ surmounts surmount surnam surnam
+ surname surnam surnamed surnam
+ surpasseth surpasseth surpassing surpass
+ surplice surplic surplus surplu
+ surpris surpri surprise surpris
+ surprised surpris surrender surrend
+ surrey surrei surreys surrei
+ survey survei surveyest surveyest
+ surveying survei surveyor surveyor
+ surveyors surveyor surveys survei
+ survive surviv survives surviv
+ survivor survivor susan susan
+ suspect suspect suspected suspect
+ suspecting suspect suspects suspect
+ suspend suspend suspense suspens
+ suspicion suspicion suspicions suspicion
+ suspicious suspici suspiration suspir
+ suspire suspir sust sust
+ sustain sustain sustaining sustain
+ sutler sutler sutton sutton
+ suum suum swabber swabber
+ swaddling swaddl swag swag
+ swagg swagg swagger swagger
+ swaggerer swagger swaggerers swagger
+ swaggering swagger swain swain
+ swains swain swallow swallow
+ swallowed swallow swallowing swallow
+ swallows swallow swam swam
+ swan swan swans swan
+ sward sward sware sware
+ swarm swarm swarming swarm
+ swart swart swarth swarth
+ swarths swarth swarthy swarthi
+ swashers swasher swashing swash
+ swath swath swathing swath
+ swathling swathl sway swai
+ swaying swai sways swai
+ swear swear swearer swearer
+ swearers swearer swearest swearest
+ swearing swear swearings swear
+ swears swear sweat sweat
+ sweaten sweaten sweating sweat
+ sweats sweat sweaty sweati
+ sweep sweep sweepers sweeper
+ sweeps sweep sweet sweet
+ sweeten sweeten sweetens sweeten
+ sweeter sweeter sweetest sweetest
+ sweetheart sweetheart sweeting sweet
+ sweetly sweetli sweetmeats sweetmeat
+ sweetness sweet sweets sweet
+ swell swell swelling swell
+ swellings swell swells swell
+ swelter swelter sweno sweno
+ swept swept swerve swerv
+ swerver swerver swerving swerv
+ swift swift swifter swifter
+ swiftest swiftest swiftly swiftli
+ swiftness swift swill swill
+ swills swill swim swim
+ swimmer swimmer swimmers swimmer
+ swimming swim swims swim
+ swine swine swineherds swineherd
+ swing swing swinge swing
+ swinish swinish swinstead swinstead
+ switches switch swits swit
+ switzers switzer swol swol
+ swoll swoll swoln swoln
+ swoon swoon swooned swoon
+ swooning swoon swoons swoon
+ swoop swoop swoopstake swoopstak
+ swor swor sword sword
+ sworder sworder swords sword
+ swore swore sworn sworn
+ swounded swound swounds swound
+ swum swum swung swung
+ sy sy sycamore sycamor
+ sycorax sycorax sylla sylla
+ syllable syllabl syllables syllabl
+ syllogism syllog symbols symbol
+ sympathise sympathis sympathiz sympathiz
+ sympathize sympath sympathized sympath
+ sympathy sympathi synagogue synagogu
+ synod synod synods synod
+ syracuse syracus syracusian syracusian
+ syracusians syracusian syria syria
+ syrups syrup t t
+ ta ta taber taber
+ table tabl tabled tabl
+ tables tabl tablet tablet
+ tabor tabor taborer tabor
+ tabors tabor tabourines tabourin
+ taciturnity taciturn tack tack
+ tackle tackl tackled tackl
+ tackles tackl tackling tackl
+ tacklings tackl taddle taddl
+ tadpole tadpol taffeta taffeta
+ taffety taffeti tag tag
+ tagrag tagrag tah tah
+ tail tail tailor tailor
+ tailors tailor tails tail
+ taint taint tainted taint
+ tainting taint taints taint
+ tainture taintur tak tak
+ take take taken taken
+ taker taker takes take
+ takest takest taketh taketh
+ taking take tal tal
+ talbot talbot talbotites talbotit
+ talbots talbot tale tale
+ talent talent talents talent
+ taleporter taleport tales tale
+ talk talk talked talk
+ talker talker talkers talker
+ talkest talkest talking talk
+ talks talk tall tall
+ taller taller tallest tallest
+ tallies talli tallow tallow
+ tally talli talons talon
+ tam tam tambourines tambourin
+ tame tame tamed tame
+ tamely tame tameness tame
+ tamer tamer tames tame
+ taming tame tamora tamora
+ tamworth tamworth tan tan
+ tang tang tangle tangl
+ tangled tangl tank tank
+ tanlings tanl tann tann
+ tanned tan tanner tanner
+ tanquam tanquam tanta tanta
+ tantaene tantaen tap tap
+ tape tape taper taper
+ tapers taper tapestries tapestri
+ tapestry tapestri taphouse taphous
+ tapp tapp tapster tapster
+ tapsters tapster tar tar
+ tardied tardi tardily tardili
+ tardiness tardi tardy tardi
+ tarentum tarentum targe targ
+ targes targ target target
+ targets target tarpeian tarpeian
+ tarquin tarquin tarquins tarquin
+ tarr tarr tarre tarr
+ tarriance tarrianc tarried tarri
+ tarries tarri tarry tarri
+ tarrying tarri tart tart
+ tartar tartar tartars tartar
+ tartly tartli tartness tart
+ task task tasker tasker
+ tasking task tasks task
+ tassel tassel taste tast
+ tasted tast tastes tast
+ tasting tast tatt tatt
+ tatter tatter tattered tatter
+ tatters tatter tattle tattl
+ tattling tattl tattlings tattl
+ taught taught taunt taunt
+ taunted taunt taunting taunt
+ tauntingly tauntingli taunts taunt
+ taurus tauru tavern tavern
+ taverns tavern tavy tavi
+ tawdry tawdri tawny tawni
+ tax tax taxation taxat
+ taxations taxat taxes tax
+ taxing tax tc tc
+ te te teach teach
+ teacher teacher teachers teacher
+ teaches teach teachest teachest
+ teacheth teacheth teaching teach
+ team team tear tear
+ tearful tear tearing tear
+ tears tear tearsheet tearsheet
+ teat teat tedious tediou
+ tediously tedious tediousness tedious
+ teem teem teeming teem
+ teems teem teen teen
+ teeth teeth teipsum teipsum
+ telamon telamon telamonius telamoniu
+ tell tell teller teller
+ telling tell tells tell
+ tellus tellu temp temp
+ temper temper temperality temper
+ temperance temper temperate temper
+ temperately temper tempers temper
+ tempest tempest tempests tempest
+ tempestuous tempestu temple templ
+ temples templ temporal tempor
+ temporary temporari temporiz temporiz
+ temporize tempor temporizer tempor
+ temps temp tempt tempt
+ temptation temptat temptations temptat
+ tempted tempt tempter tempter
+ tempters tempter tempteth tempteth
+ tempting tempt tempts tempt
+ ten ten tenable tenabl
+ tenant tenant tenantius tenantiu
+ tenantless tenantless tenants tenant
+ tench tench tend tend
+ tendance tendanc tended tend
+ tender tender tendered tender
+ tenderly tenderli tenderness tender
+ tenders tender tending tend
+ tends tend tenedos tenedo
+ tenement tenement tenements tenement
+ tenfold tenfold tennis tenni
+ tenour tenour tenours tenour
+ tens ten tent tent
+ tented tent tenth tenth
+ tenths tenth tents tent
+ tenure tenur tenures tenur
+ tercel tercel tereus tereu
+ term term termagant termag
+ termed term terminations termin
+ termless termless terms term
+ terra terra terrace terrac
+ terram terram terras terra
+ terre terr terrene terren
+ terrestrial terrestri terrible terribl
+ terribly terribl territories territori
+ territory territori terror terror
+ terrors terror tertian tertian
+ tertio tertio test test
+ testament testament tested test
+ tester tester testern testern
+ testify testifi testimonied testimoni
+ testimonies testimoni testimony testimoni
+ testiness testi testril testril
+ testy testi tetchy tetchi
+ tether tether tetter tetter
+ tevil tevil tewksbury tewksburi
+ text text tgv tgv
+ th th thaes thae
+ thames thame than than
+ thane thane thanes thane
+ thank thank thanked thank
+ thankful thank thankfully thankfulli
+ thankfulness thank thanking thank
+ thankings thank thankless thankless
+ thanks thank thanksgiving thanksgiv
+ thasos thaso that that
+ thatch thatch thaw thaw
+ thawing thaw thaws thaw
+ the the theatre theatr
+ theban theban thebes thebe
+ thee thee theft theft
+ thefts theft thein thein
+ their their theirs their
+ theise theis them them
+ theme theme themes theme
+ themselves themselv then then
+ thence thenc thenceforth thenceforth
+ theoric theoric there there
+ thereabout thereabout thereabouts thereabout
+ thereafter thereaft thereat thereat
+ thereby therebi therefore therefor
+ therein therein thereof thereof
+ thereon thereon thereto thereto
+ thereunto thereunto thereupon thereupon
+ therewith therewith therewithal therewith
+ thersites thersit these these
+ theseus theseu thessalian thessalian
+ thessaly thessali thetis theti
+ thews thew they thei
+ thick thick thicken thicken
+ thickens thicken thicker thicker
+ thickest thickest thicket thicket
+ thickskin thickskin thief thief
+ thievery thieveri thieves thiev
+ thievish thievish thigh thigh
+ thighs thigh thimble thimbl
+ thimbles thimbl thin thin
+ thine thine thing thing
+ things thing think think
+ thinkest thinkest thinking think
+ thinkings think thinks think
+ thinkst thinkst thinly thinli
+ third third thirdly thirdli
+ thirds third thirst thirst
+ thirsting thirst thirsts thirst
+ thirsty thirsti thirteen thirteen
+ thirties thirti thirtieth thirtieth
+ thirty thirti this thi
+ thisby thisbi thisne thisn
+ thistle thistl thistles thistl
+ thither thither thitherward thitherward
+ thoas thoa thomas thoma
+ thorn thorn thorns thorn
+ thorny thorni thorough thorough
+ thoroughly thoroughli those those
+ thou thou though though
+ thought thought thoughtful thought
+ thoughts thought thousand thousand
+ thousands thousand thracian thracian
+ thraldom thraldom thrall thrall
+ thralled thrall thralls thrall
+ thrash thrash thrasonical thrason
+ thread thread threadbare threadbar
+ threaden threaden threading thread
+ threat threat threaten threaten
+ threatening threaten threatens threaten
+ threatest threatest threats threat
+ three three threefold threefold
+ threepence threepenc threepile threepil
+ threes three threescore threescor
+ thresher thresher threshold threshold
+ threw threw thrice thrice
+ thrift thrift thriftless thriftless
+ thrifts thrift thrifty thrifti
+ thrill thrill thrilling thrill
+ thrills thrill thrive thrive
+ thrived thrive thrivers thriver
+ thrives thrive thriving thrive
+ throat throat throats throat
+ throbbing throb throbs throb
+ throca throca throe throe
+ throes throe thromuldo thromuldo
+ thron thron throne throne
+ throned throne thrones throne
+ throng throng thronging throng
+ throngs throng throstle throstl
+ throttle throttl through through
+ throughfare throughfar throughfares throughfar
+ throughly throughli throughout throughout
+ throw throw thrower thrower
+ throwest throwest throwing throw
+ thrown thrown throws throw
+ thrum thrum thrumm thrumm
+ thrush thrush thrust thrust
+ thrusteth thrusteth thrusting thrust
+ thrusts thrust thumb thumb
+ thumbs thumb thump thump
+ thund thund thunder thunder
+ thunderbolt thunderbolt thunderbolts thunderbolt
+ thunderer thunder thunders thunder
+ thunderstone thunderston thunderstroke thunderstrok
+ thurio thurio thursday thursdai
+ thus thu thwack thwack
+ thwart thwart thwarted thwart
+ thwarting thwart thwartings thwart
+ thy thy thyme thyme
+ thymus thymu thyreus thyreu
+ thyself thyself ti ti
+ tib tib tiber tiber
+ tiberio tiberio tibey tibei
+ ticed tice tick tick
+ tickl tickl tickle tickl
+ tickled tickl tickles tickl
+ tickling tickl ticklish ticklish
+ tiddle tiddl tide tide
+ tides tide tidings tide
+ tidy tidi tie tie
+ tied ti ties ti
+ tiff tiff tiger tiger
+ tigers tiger tight tight
+ tightly tightli tike tike
+ til til tile tile
+ till till tillage tillag
+ tilly tilli tilt tilt
+ tilter tilter tilth tilth
+ tilting tilt tilts tilt
+ tiltyard tiltyard tim tim
+ timandra timandra timber timber
+ time time timeless timeless
+ timelier timeli timely time
+ times time timon timon
+ timor timor timorous timor
+ timorously timor tinct tinct
+ tincture tinctur tinctures tinctur
+ tinder tinder tingling tingl
+ tinker tinker tinkers tinker
+ tinsel tinsel tiny tini
+ tip tip tipp tipp
+ tippling tippl tips tip
+ tipsy tipsi tiptoe tipto
+ tir tir tire tire
+ tired tire tires tire
+ tirest tirest tiring tire
+ tirra tirra tirrits tirrit
+ tis ti tish tish
+ tisick tisick tissue tissu
+ titan titan titania titania
+ tithe tith tithed tith
+ tithing tith titinius titiniu
+ title titl titled titl
+ titleless titleless titles titl
+ tittle tittl tittles tittl
+ titular titular titus titu
+ tn tn to to
+ toad toad toads toad
+ toadstool toadstool toast toast
+ toasted toast toasting toast
+ toasts toast toaze toaz
+ toby tobi tock tock
+ tod tod today todai
+ todpole todpol tods tod
+ toe toe toes toe
+ tofore tofor toge toge
+ toged toge together togeth
+ toil toil toiled toil
+ toiling toil toils toil
+ token token tokens token
+ told told toledo toledo
+ tolerable toler toll toll
+ tolling toll tom tom
+ tomb tomb tombe tomb
+ tombed tomb tombless tombless
+ tomboys tomboi tombs tomb
+ tomorrow tomorrow tomyris tomyri
+ ton ton tongs tong
+ tongu tongu tongue tongu
+ tongued tongu tongueless tongueless
+ tongues tongu tonight tonight
+ too too took took
+ tool tool tools tool
+ tooth tooth toothache toothach
+ toothpick toothpick toothpicker toothpick
+ top top topas topa
+ topful top topgallant topgal
+ topless topless topmast topmast
+ topp topp topping top
+ topple toppl topples toppl
+ tops top topsail topsail
+ topsy topsi torch torch
+ torchbearer torchbear torchbearers torchbear
+ torcher torcher torches torch
+ torchlight torchlight tore tore
+ torment torment tormenta tormenta
+ tormente torment tormented torment
+ tormenting torment tormentors tormentor
+ torments torment torn torn
+ torrent torrent tortive tortiv
+ tortoise tortois tortur tortur
+ torture tortur tortured tortur
+ torturer tortur torturers tortur
+ tortures tortur torturest torturest
+ torturing tortur toryne toryn
+ toss toss tossed toss
+ tosseth tosseth tossing toss
+ tot tot total total
+ totally total tott tott
+ tottered totter totters totter
+ tou tou touch touch
+ touched touch touches touch
+ toucheth toucheth touching touch
+ touchstone touchston tough tough
+ tougher tougher toughness tough
+ touraine tourain tournaments tournament
+ tours tour tous tou
+ tout tout touze touz
+ tow tow toward toward
+ towardly towardli towards toward
+ tower tower towering tower
+ towers tower town town
+ towns town township township
+ townsman townsman townsmen townsmen
+ towton towton toy toi
+ toys toi trace trace
+ traces trace track track
+ tract tract tractable tractabl
+ trade trade traded trade
+ traders trader trades trade
+ tradesman tradesman tradesmen tradesmen
+ trading trade tradition tradit
+ traditional tradit traduc traduc
+ traduced traduc traducement traduc
+ traffic traffic traffickers traffick
+ traffics traffic tragedian tragedian
+ tragedians tragedian tragedies tragedi
+ tragedy tragedi tragic tragic
+ tragical tragic trail trail
+ train train trained train
+ training train trains train
+ trait trait traitor traitor
+ traitorly traitorli traitorous traitor
+ traitorously traitor traitors traitor
+ traitress traitress traject traject
+ trammel trammel trample trampl
+ trampled trampl trampling trampl
+ tranc tranc trance tranc
+ tranio tranio tranquil tranquil
+ tranquillity tranquil transcendence transcend
+ transcends transcend transferred transfer
+ transfigur transfigur transfix transfix
+ transform transform transformation transform
+ transformations transform transformed transform
+ transgress transgress transgresses transgress
+ transgressing transgress transgression transgress
+ translate translat translated translat
+ translates translat translation translat
+ transmigrates transmigr transmutation transmut
+ transparent transpar transport transport
+ transportance transport transported transport
+ transporting transport transports transport
+ transpose transpos transshape transshap
+ trap trap trapp trapp
+ trappings trap traps trap
+ trash trash travail travail
+ travails travail travel travel
+ traveler travel traveling travel
+ travell travel travelled travel
+ traveller travel travellers travel
+ travellest travellest travelling travel
+ travels travel travers traver
+ traverse travers tray trai
+ treacherous treacher treacherously treacher
+ treachers treacher treachery treacheri
+ tread tread treading tread
+ treads tread treason treason
+ treasonable treason treasonous treason
+ treasons treason treasure treasur
+ treasurer treasur treasures treasur
+ treasuries treasuri treasury treasuri
+ treat treat treaties treati
+ treatise treatis treats treat
+ treaty treati treble trebl
+ trebled trebl trebles trebl
+ trebonius treboniu tree tree
+ trees tree tremble trembl
+ trembled trembl trembles trembl
+ tremblest tremblest trembling trembl
+ tremblingly tremblingli tremor tremor
+ trempling trempl trench trench
+ trenchant trenchant trenched trench
+ trencher trencher trenchering trencher
+ trencherman trencherman trenchers trencher
+ trenches trench trenching trench
+ trent trent tres tre
+ trespass trespass trespasses trespass
+ tressel tressel tresses tress
+ treys trei trial trial
+ trials trial trib trib
+ tribe tribe tribes tribe
+ tribulation tribul tribunal tribun
+ tribune tribun tribunes tribun
+ tributaries tributari tributary tributari
+ tribute tribut tributes tribut
+ trice trice trick trick
+ tricking trick trickling trickl
+ tricks trick tricksy tricksi
+ trident trident tried tri
+ trier trier trifle trifl
+ trifled trifl trifler trifler
+ trifles trifl trifling trifl
+ trigon trigon trill trill
+ trim trim trimly trimli
+ trimm trimm trimmed trim
+ trimming trim trims trim
+ trinculo trinculo trinculos trinculo
+ trinkets trinket trip trip
+ tripartite tripartit tripe tripe
+ triple tripl triplex triplex
+ tripoli tripoli tripolis tripoli
+ tripp tripp tripping trip
+ trippingly trippingli trips trip
+ tristful trist triton triton
+ triumph triumph triumphant triumphant
+ triumphantly triumphantli triumpher triumpher
+ triumphers triumpher triumphing triumph
+ triumphs triumph triumvir triumvir
+ triumvirate triumvir triumvirs triumvir
+ triumviry triumviri trivial trivial
+ troat troat trod trod
+ trodden trodden troiant troiant
+ troien troien troilus troilu
+ troiluses troilus trojan trojan
+ trojans trojan troll troll
+ tromperies tromperi trompet trompet
+ troop troop trooping troop
+ troops troop trop trop
+ trophies trophi trophy trophi
+ tropically tropic trot trot
+ troth troth trothed troth
+ troths troth trots trot
+ trotting trot trouble troubl
+ troubled troubl troubler troubler
+ troubles troubl troublesome troublesom
+ troublest troublest troublous troublou
+ trough trough trout trout
+ trouts trout trovato trovato
+ trow trow trowel trowel
+ trowest trowest troy troi
+ troyan troyan troyans troyan
+ truant truant truce truce
+ truckle truckl trudge trudg
+ true true trueborn trueborn
+ truepenny truepenni truer truer
+ truest truest truie truie
+ trull trull trulls trull
+ truly truli trump trump
+ trumpery trumperi trumpet trumpet
+ trumpeter trumpet trumpeters trumpet
+ trumpets trumpet truncheon truncheon
+ truncheoners truncheon trundle trundl
+ trunk trunk trunks trunk
+ trust trust trusted trust
+ truster truster trusters truster
+ trusting trust trusts trust
+ trusty trusti truth truth
+ truths truth try try
+ ts ts tu tu
+ tuae tuae tub tub
+ tubal tubal tubs tub
+ tuck tuck tucket tucket
+ tuesday tuesdai tuft tuft
+ tufts tuft tug tug
+ tugg tugg tugging tug
+ tuition tuition tullus tullu
+ tully tulli tumble tumbl
+ tumbled tumbl tumbler tumbler
+ tumbling tumbl tumult tumult
+ tumultuous tumultu tun tun
+ tune tune tuneable tuneabl
+ tuned tune tuners tuner
+ tunes tune tunis tuni
+ tuns tun tupping tup
+ turban turban turbans turban
+ turbulence turbul turbulent turbul
+ turd turd turf turf
+ turfy turfi turk turk
+ turkey turkei turkeys turkei
+ turkish turkish turks turk
+ turlygod turlygod turmoil turmoil
+ turmoiled turmoil turn turn
+ turnbull turnbul turncoat turncoat
+ turncoats turncoat turned turn
+ turneth turneth turning turn
+ turnips turnip turns turn
+ turph turph turpitude turpitud
+ turquoise turquois turret turret
+ turrets turret turtle turtl
+ turtles turtl turvy turvi
+ tuscan tuscan tush tush
+ tut tut tutor tutor
+ tutored tutor tutors tutor
+ tutto tutto twain twain
+ twang twang twangling twangl
+ twas twa tway twai
+ tweaks tweak tween tween
+ twelfth twelfth twelve twelv
+ twelvemonth twelvemonth twentieth twentieth
+ twenty twenti twere twere
+ twice twice twig twig
+ twiggen twiggen twigs twig
+ twilight twilight twill twill
+ twilled twill twin twin
+ twine twine twink twink
+ twinkle twinkl twinkled twinkl
+ twinkling twinkl twinn twinn
+ twins twin twire twire
+ twist twist twisted twist
+ twit twit twits twit
+ twitting twit twixt twixt
+ two two twofold twofold
+ twopence twopenc twopences twopenc
+ twos two twould twould
+ tyb tyb tybalt tybalt
+ tybalts tybalt tyburn tyburn
+ tying ty tyke tyke
+ tymbria tymbria type type
+ types type typhon typhon
+ tyrannical tyrann tyrannically tyrann
+ tyrannize tyrann tyrannous tyrann
+ tyranny tyranni tyrant tyrant
+ tyrants tyrant tyrian tyrian
+ tyrrel tyrrel u u
+ ubique ubiqu udders udder
+ udge udg uds ud
+ uglier uglier ugliest ugliest
+ ugly ugli ulcer ulcer
+ ulcerous ulcer ulysses ulyss
+ um um umber umber
+ umbra umbra umbrage umbrag
+ umfrevile umfrevil umpire umpir
+ umpires umpir un un
+ unable unabl unaccommodated unaccommod
+ unaccompanied unaccompani unaccustom unaccustom
+ unaching unach unacquainted unacquaint
+ unactive unact unadvis unadvi
+ unadvised unadvis unadvisedly unadvisedli
+ unagreeable unagre unanel unanel
+ unanswer unansw unappeas unappea
+ unapproved unapprov unapt unapt
+ unaptness unapt unarm unarm
+ unarmed unarm unarms unarm
+ unassail unassail unassailable unassail
+ unattainted unattaint unattempted unattempt
+ unattended unattend unauspicious unauspici
+ unauthorized unauthor unavoided unavoid
+ unawares unawar unback unback
+ unbak unbak unbanded unband
+ unbar unbar unbarb unbarb
+ unbashful unbash unbated unbat
+ unbatter unbatt unbecoming unbecom
+ unbefitting unbefit unbegot unbegot
+ unbegotten unbegotten unbelieved unbeliev
+ unbend unbend unbent unbent
+ unbewail unbewail unbid unbid
+ unbidden unbidden unbind unbind
+ unbinds unbind unbitted unbit
+ unbless unbless unblest unblest
+ unbloodied unbloodi unblown unblown
+ unbodied unbodi unbolt unbolt
+ unbolted unbolt unbonneted unbonnet
+ unbookish unbookish unborn unborn
+ unbosom unbosom unbound unbound
+ unbounded unbound unbow unbow
+ unbowed unbow unbrac unbrac
+ unbraced unbrac unbraided unbraid
+ unbreathed unbreath unbred unbr
+ unbreech unbreech unbridled unbridl
+ unbroke unbrok unbruis unbrui
+ unbruised unbruis unbuckle unbuckl
+ unbuckles unbuckl unbuckling unbuckl
+ unbuild unbuild unburden unburden
+ unburdens unburden unburied unburi
+ unburnt unburnt unburthen unburthen
+ unbutton unbutton unbuttoning unbutton
+ uncapable uncap uncape uncap
+ uncase uncas uncasing uncas
+ uncaught uncaught uncertain uncertain
+ uncertainty uncertainti unchain unchain
+ unchanging unchang uncharge uncharg
+ uncharged uncharg uncharitably uncharit
+ unchary unchari unchaste unchast
+ uncheck uncheck unchilded unchild
+ uncivil uncivil unclaim unclaim
+ unclasp unclasp uncle uncl
+ unclean unclean uncleanliness uncleanli
+ uncleanly uncleanli uncleanness unclean
+ uncles uncl unclew unclew
+ unclog unclog uncoined uncoin
+ uncolted uncolt uncomeliness uncomeli
+ uncomfortable uncomfort uncompassionate uncompassion
+ uncomprehensive uncomprehens unconfinable unconfin
+ unconfirm unconfirm unconfirmed unconfirm
+ unconquer unconqu unconquered unconqu
+ unconsidered unconsid unconstant unconst
+ unconstrain unconstrain unconstrained unconstrain
+ uncontemn uncontemn uncontroll uncontrol
+ uncorrected uncorrect uncounted uncount
+ uncouple uncoupl uncourteous uncourt
+ uncouth uncouth uncover uncov
+ uncovered uncov uncropped uncrop
+ uncross uncross uncrown uncrown
+ unction unction unctuous unctuou
+ uncuckolded uncuckold uncurable uncur
+ uncurbable uncurb uncurbed uncurb
+ uncurls uncurl uncurrent uncurr
+ uncurse uncurs undaunted undaunt
+ undeaf undeaf undeck undeck
+ undeeded undeed under under
+ underbearing underbear underborne underborn
+ undercrest undercrest underfoot underfoot
+ undergo undergo undergoes undergo
+ undergoing undergo undergone undergon
+ underground underground underhand underhand
+ underlings underl undermine undermin
+ underminers undermin underneath underneath
+ underprizing underpr underprop underprop
+ understand understand understandeth understandeth
+ understanding understand understandings understand
+ understands understand understood understood
+ underta underta undertake undertak
+ undertakeing undertak undertaker undertak
+ undertakes undertak undertaking undertak
+ undertakings undertak undertook undertook
+ undervalu undervalu undervalued undervalu
+ underwent underw underwrit underwrit
+ underwrite underwrit undescried undescri
+ undeserved undeserv undeserver undeserv
+ undeservers undeserv undeserving undeserv
+ undetermin undetermin undid undid
+ undinted undint undiscernible undiscern
+ undiscover undiscov undishonoured undishonour
+ undispos undispo undistinguishable undistinguish
+ undistinguished undistinguish undividable undivid
+ undivided undivid undivulged undivulg
+ undo undo undoes undo
+ undoing undo undone undon
+ undoubted undoubt undoubtedly undoubtedli
+ undream undream undress undress
+ undressed undress undrown undrown
+ unduteous undut undutiful unduti
+ une un uneared unear
+ unearned unearn unearthly unearthli
+ uneasines uneasin uneasy uneasi
+ uneath uneath uneducated uneduc
+ uneffectual uneffectu unelected unelect
+ unequal unequ uneven uneven
+ unexamin unexamin unexecuted unexecut
+ unexpected unexpect unexperienc unexperienc
+ unexperient unexperi unexpressive unexpress
+ unfair unfair unfaithful unfaith
+ unfallible unfal unfam unfam
+ unfashionable unfashion unfasten unfasten
+ unfather unfath unfathered unfath
+ unfed unf unfeed unfe
+ unfeeling unfeel unfeigned unfeign
+ unfeignedly unfeignedli unfellowed unfellow
+ unfelt unfelt unfenced unfenc
+ unfilial unfili unfill unfil
+ unfinish unfinish unfirm unfirm
+ unfit unfit unfitness unfit
+ unfix unfix unfledg unfledg
+ unfold unfold unfolded unfold
+ unfoldeth unfoldeth unfolding unfold
+ unfolds unfold unfool unfool
+ unforc unforc unforced unforc
+ unforfeited unforfeit unfortified unfortifi
+ unfortunate unfortun unfought unfought
+ unfrequented unfrequ unfriended unfriend
+ unfurnish unfurnish ungain ungain
+ ungalled ungal ungart ungart
+ ungarter ungart ungenitur ungenitur
+ ungentle ungentl ungentleness ungentl
+ ungently ungent ungird ungird
+ ungodly ungodli ungor ungor
+ ungot ungot ungotten ungotten
+ ungovern ungovern ungracious ungraci
+ ungrateful ungrat ungravely ungrav
+ ungrown ungrown unguarded unguard
+ unguem unguem unguided unguid
+ unhack unhack unhair unhair
+ unhallow unhallow unhallowed unhallow
+ unhand unhand unhandled unhandl
+ unhandsome unhandsom unhang unhang
+ unhappied unhappi unhappily unhappili
+ unhappiness unhappi unhappy unhappi
+ unhardened unharden unharm unharm
+ unhatch unhatch unheard unheard
+ unhearts unheart unheedful unheed
+ unheedfully unheedfulli unheedy unheedi
+ unhelpful unhelp unhidden unhidden
+ unholy unholi unhop unhop
+ unhopefullest unhopefullest unhorse unhors
+ unhospitable unhospit unhous unhou
+ unhoused unhous unhurtful unhurt
+ unicorn unicorn unicorns unicorn
+ unimproved unimprov uninhabitable uninhabit
+ uninhabited uninhabit unintelligent unintellig
+ union union unions union
+ unite unit united unit
+ unity uniti universal univers
+ universe univers universities univers
+ university univers unjointed unjoint
+ unjust unjust unjustice unjustic
+ unjustly unjustli unkennel unkennel
+ unkept unkept unkind unkind
+ unkindest unkindest unkindly unkindli
+ unkindness unkind unking unk
+ unkinglike unkinglik unkiss unkiss
+ unknit unknit unknowing unknow
+ unknown unknown unlace unlac
+ unlaid unlaid unlawful unlaw
+ unlawfully unlawfulli unlearn unlearn
+ unlearned unlearn unless unless
+ unlesson unlesson unletter unlett
+ unlettered unlett unlick unlick
+ unlike unlik unlikely unlik
+ unlimited unlimit unlineal unlin
+ unlink unlink unload unload
+ unloaded unload unloading unload
+ unloads unload unlock unlock
+ unlocks unlock unlook unlook
+ unlooked unlook unloos unloo
+ unloose unloos unlov unlov
+ unloving unlov unluckily unluckili
+ unlucky unlucki unmade unmad
+ unmake unmak unmanly unmanli
+ unmann unmann unmanner unmann
+ unmannerd unmannerd unmannerly unmannerli
+ unmarried unmarri unmask unmask
+ unmasked unmask unmasking unmask
+ unmasks unmask unmast unmast
+ unmatch unmatch unmatchable unmatch
+ unmatched unmatch unmeasurable unmeasur
+ unmeet unmeet unmellowed unmellow
+ unmerciful unmerci unmeritable unmerit
+ unmeriting unmerit unminded unmind
+ unmindfull unmindful unmingled unmingl
+ unmitigable unmitig unmitigated unmitig
+ unmix unmix unmoan unmoan
+ unmov unmov unmoved unmov
+ unmoving unmov unmuffles unmuffl
+ unmuffling unmuffl unmusical unmus
+ unmuzzle unmuzzl unmuzzled unmuzzl
+ unnatural unnatur unnaturally unnatur
+ unnaturalness unnatur unnecessarily unnecessarili
+ unnecessary unnecessari unneighbourly unneighbourli
+ unnerved unnerv unnoble unnobl
+ unnoted unnot unnumb unnumb
+ unnumber unnumb unowed unow
+ unpack unpack unpaid unpaid
+ unparagon unparagon unparallel unparallel
+ unpartial unparti unpath unpath
+ unpaved unpav unpay unpai
+ unpeaceable unpeac unpeg unpeg
+ unpeople unpeopl unpeopled unpeopl
+ unperfect unperfect unperfectness unperfect
+ unpick unpick unpin unpin
+ unpink unpink unpitied unpiti
+ unpitifully unpitifulli unplagu unplagu
+ unplausive unplaus unpleas unplea
+ unpleasant unpleas unpleasing unpleas
+ unpolicied unpolici unpolish unpolish
+ unpolished unpolish unpolluted unpollut
+ unpossess unpossess unpossessing unpossess
+ unpossible unposs unpractis unpracti
+ unpregnant unpregn unpremeditated unpremedit
+ unprepar unprepar unprepared unprepar
+ unpress unpress unprevailing unprevail
+ unprevented unprev unpriz unpriz
+ unprizable unpriz unprofitable unprofit
+ unprofited unprofit unproper unprop
+ unproperly unproperli unproportion unproport
+ unprovide unprovid unprovided unprovid
+ unprovident unprovid unprovokes unprovok
+ unprun unprun unpruned unprun
+ unpublish unpublish unpurged unpurg
+ unpurpos unpurpo unqualitied unqual
+ unqueen unqueen unquestion unquest
+ unquestionable unquestion unquiet unquiet
+ unquietly unquietli unquietness unquiet
+ unraised unrais unrak unrak
+ unread unread unready unreadi
+ unreal unreal unreasonable unreason
+ unreasonably unreason unreclaimed unreclaim
+ unreconciled unreconcil unreconciliable unreconcili
+ unrecounted unrecount unrecuring unrecur
+ unregarded unregard unregist unregist
+ unrelenting unrel unremovable unremov
+ unremovably unremov unreprievable unrepriev
+ unresolv unresolv unrespected unrespect
+ unrespective unrespect unrest unrest
+ unrestor unrestor unrestrained unrestrain
+ unreveng unreveng unreverend unreverend
+ unreverent unrever unrevers unrev
+ unrewarded unreward unrighteous unright
+ unrightful unright unripe unrip
+ unripp unripp unrivall unrival
+ unroll unrol unroof unroof
+ unroosted unroost unroot unroot
+ unrough unrough unruly unruli
+ unsafe unsaf unsaluted unsalut
+ unsanctified unsanctifi unsatisfied unsatisfi
+ unsavoury unsavouri unsay unsai
+ unscalable unscal unscann unscann
+ unscarr unscarr unschool unschool
+ unscorch unscorch unscour unscour
+ unscratch unscratch unseal unseal
+ unseam unseam unsearch unsearch
+ unseason unseason unseasonable unseason
+ unseasonably unseason unseasoned unseason
+ unseconded unsecond unsecret unsecret
+ unseduc unseduc unseeing unse
+ unseeming unseem unseemly unseemli
+ unseen unseen unseminar unseminar
+ unseparable unsepar unserviceable unservic
+ unset unset unsettle unsettl
+ unsettled unsettl unsever unsev
+ unsex unsex unshak unshak
+ unshaked unshak unshaken unshaken
+ unshaped unshap unshapes unshap
+ unsheath unsheath unsheathe unsheath
+ unshorn unshorn unshout unshout
+ unshown unshown unshrinking unshrink
+ unshrubb unshrubb unshunn unshunn
+ unshunnable unshunn unsifted unsift
+ unsightly unsightli unsinew unsinew
+ unsisting unsist unskilful unskil
+ unskilfully unskilfulli unskillful unskil
+ unslipping unslip unsmirched unsmirch
+ unsoil unsoil unsolicited unsolicit
+ unsorted unsort unsought unsought
+ unsound unsound unsounded unsound
+ unspeak unspeak unspeakable unspeak
+ unspeaking unspeak unsphere unspher
+ unspoke unspok unspoken unspoken
+ unspotted unspot unsquar unsquar
+ unstable unstabl unstaid unstaid
+ unstain unstain unstained unstain
+ unstanched unstanch unstate unstat
+ unsteadfast unsteadfast unstooping unstoop
+ unstringed unstring unstuff unstuff
+ unsubstantial unsubstanti unsuitable unsuit
+ unsuiting unsuit unsullied unsulli
+ unsunn unsunn unsur unsur
+ unsure unsur unsuspected unsuspect
+ unsway unswai unswayable unsway
+ unswayed unswai unswear unswear
+ unswept unswept unsworn unsworn
+ untainted untaint untalk untalk
+ untangle untangl untangled untangl
+ untasted untast untaught untaught
+ untempering untemp untender untend
+ untent untent untented untent
+ unthankful unthank unthankfulness unthank
+ unthink unthink unthought unthought
+ unthread unthread unthrift unthrift
+ unthrifts unthrift unthrifty unthrifti
+ untie unti untied unti
+ until until untimber untimb
+ untimely untim untir untir
+ untirable untir untired untir
+ untitled untitl unto unto
+ untold untold untouch untouch
+ untoward untoward untowardly untowardli
+ untraded untrad untrain untrain
+ untrained untrain untread untread
+ untreasur untreasur untried untri
+ untrimmed untrim untrod untrod
+ untrodden untrodden untroubled untroubl
+ untrue untru untrussing untruss
+ untruth untruth untruths untruth
+ untucked untuck untun untun
+ untune untun untuneable untun
+ untutor untutor untutored untutor
+ untwine untwin unurg unurg
+ unus unu unused unus
+ unusual unusu unvalued unvalu
+ unvanquish unvanquish unvarnish unvarnish
+ unveil unveil unveiling unveil
+ unvenerable unvener unvex unvex
+ unviolated unviol unvirtuous unvirtu
+ unvisited unvisit unvulnerable unvulner
+ unwares unwar unwarily unwarili
+ unwash unwash unwatch unwatch
+ unwearied unweari unwed unw
+ unwedgeable unwedg unweeded unweed
+ unweighed unweigh unweighing unweigh
+ unwelcome unwelcom unwept unwept
+ unwhipp unwhipp unwholesome unwholesom
+ unwieldy unwieldi unwilling unwil
+ unwillingly unwillingli unwillingness unwilling
+ unwind unwind unwiped unwip
+ unwise unwis unwisely unwis
+ unwish unwish unwished unwish
+ unwitted unwit unwittingly unwittingli
+ unwonted unwont unwooed unwoo
+ unworthier unworthi unworthiest unworthiest
+ unworthily unworthili unworthiness unworthi
+ unworthy unworthi unwrung unwrung
+ unyok unyok unyoke unyok
+ up up upbraid upbraid
+ upbraided upbraid upbraidings upbraid
+ upbraids upbraid uphoarded uphoard
+ uphold uphold upholdeth upholdeth
+ upholding uphold upholds uphold
+ uplift uplift uplifted uplift
+ upmost upmost upon upon
+ upper upper uprear uprear
+ upreared uprear upright upright
+ uprighteously upright uprightness upright
+ uprise upris uprising upris
+ uproar uproar uproars uproar
+ uprous uprou upshoot upshoot
+ upshot upshot upside upsid
+ upspring upspr upstairs upstair
+ upstart upstart upturned upturn
+ upward upward upwards upward
+ urchin urchin urchinfield urchinfield
+ urchins urchin urg urg
+ urge urg urged urg
+ urgent urgent urges urg
+ urgest urgest urging urg
+ urinal urin urinals urin
+ urine urin urn urn
+ urns urn urs ur
+ ursa ursa ursley urslei
+ ursula ursula urswick urswick
+ us us usage usag
+ usance usanc usances usanc
+ use us used us
+ useful us useless useless
+ user user uses us
+ usest usest useth useth
+ usher usher ushered usher
+ ushering usher ushers usher
+ using us usual usual
+ usually usual usurer usur
+ usurers usur usuries usuri
+ usuring usur usurp usurp
+ usurpation usurp usurped usurp
+ usurper usurp usurpers usurp
+ usurping usurp usurpingly usurpingli
+ usurps usurp usury usuri
+ ut ut utensil utensil
+ utensils utensil utility util
+ utmost utmost utt utt
+ utter utter utterance utter
+ uttered utter uttereth uttereth
+ uttering utter utterly utterli
+ uttermost uttermost utters utter
+ uy uy v v
+ va va vacancy vacanc
+ vacant vacant vacation vacat
+ vade vade vagabond vagabond
+ vagabonds vagabond vagram vagram
+ vagrom vagrom vail vail
+ vailed vail vailing vail
+ vaillant vaillant vain vain
+ vainer vainer vainglory vainglori
+ vainly vainli vainness vain
+ vais vai valanc valanc
+ valance valanc vale vale
+ valence valenc valentine valentin
+ valentinus valentinu valentio valentio
+ valeria valeria valerius valeriu
+ vales vale valiant valiant
+ valiantly valiantli valiantness valiant
+ validity valid vallant vallant
+ valley vallei valleys vallei
+ vally valli valor valor
+ valorous valor valorously valor
+ valour valour valu valu
+ valuation valuat value valu
+ valued valu valueless valueless
+ values valu valuing valu
+ vane vane vanish vanish
+ vanished vanish vanishes vanish
+ vanishest vanishest vanishing vanish
+ vanities vaniti vanity vaniti
+ vanquish vanquish vanquished vanquish
+ vanquisher vanquish vanquishest vanquishest
+ vanquisheth vanquisheth vant vant
+ vantage vantag vantages vantag
+ vantbrace vantbrac vapians vapian
+ vapor vapor vaporous vapor
+ vapour vapour vapours vapour
+ vara vara variable variabl
+ variance varianc variation variat
+ variations variat varied vari
+ variest variest variety varieti
+ varld varld varlet varlet
+ varletry varletri varlets varlet
+ varletto varletto varnish varnish
+ varrius varriu varro varro
+ vary vari varying vari
+ vassal vassal vassalage vassalag
+ vassals vassal vast vast
+ vastidity vastid vasty vasti
+ vat vat vater vater
+ vaudemont vaudemont vaughan vaughan
+ vault vault vaultages vaultag
+ vaulted vault vaulting vault
+ vaults vault vaulty vaulti
+ vaumond vaumond vaunt vaunt
+ vaunted vaunt vaunter vaunter
+ vaunting vaunt vauntingly vauntingli
+ vaunts vaunt vauvado vauvado
+ vaux vaux vaward vaward
+ ve ve veal veal
+ vede vede vehemence vehem
+ vehemency vehem vehement vehement
+ vehor vehor veil veil
+ veiled veil veiling veil
+ vein vein veins vein
+ vell vell velure velur
+ velutus velutu velvet velvet
+ vendible vendibl venerable vener
+ venereal vener venetia venetia
+ venetian venetian venetians venetian
+ veneys venei venge veng
+ vengeance vengeanc vengeances vengeanc
+ vengeful veng veni veni
+ venial venial venice venic
+ venison venison venit venit
+ venom venom venomous venom
+ venomously venom vent vent
+ ventages ventag vented vent
+ ventidius ventidiu ventricle ventricl
+ vents vent ventur ventur
+ venture ventur ventured ventur
+ ventures ventur venturing ventur
+ venturous ventur venue venu
+ venus venu venuto venuto
+ ver ver verb verb
+ verba verba verbal verbal
+ verbatim verbatim verbosity verbos
+ verdict verdict verdun verdun
+ verdure verdur vere vere
+ verefore verefor verg verg
+ verge verg vergers verger
+ verges verg verier verier
+ veriest veriest verified verifi
+ verify verifi verily verili
+ veritable verit verite verit
+ verities veriti verity veriti
+ vermilion vermilion vermin vermin
+ vernon vernon verona verona
+ veronesa veronesa versal versal
+ verse vers verses vers
+ versing vers vert vert
+ very veri vesper vesper
+ vessel vessel vessels vessel
+ vestal vestal vestments vestment
+ vesture vestur vetch vetch
+ vetches vetch veux veux
+ vex vex vexation vexat
+ vexations vexat vexed vex
+ vexes vex vexest vexest
+ vexeth vexeth vexing vex
+ vi vi via via
+ vial vial vials vial
+ viand viand viands viand
+ vic vic vicar vicar
+ vice vice vicegerent viceger
+ vicentio vicentio viceroy viceroi
+ viceroys viceroi vices vice
+ vici vici vicious viciou
+ viciousness vicious vict vict
+ victims victim victor victor
+ victoress victoress victories victori
+ victorious victori victors victor
+ victory victori victual victual
+ victuall victual victuals victual
+ videlicet videlicet video video
+ vides vide videsne videsn
+ vidi vidi vie vie
+ vied vi vienna vienna
+ view view viewest viewest
+ vieweth vieweth viewing view
+ viewless viewless views view
+ vigil vigil vigilance vigil
+ vigilant vigil vigitant vigit
+ vigour vigour vii vii
+ viii viii vile vile
+ vilely vile vileness vile
+ viler viler vilest vilest
+ vill vill village villag
+ villager villag villagery villageri
+ villages villag villain villain
+ villainies villaini villainous villain
+ villainously villain villains villain
+ villainy villaini villanies villani
+ villanous villan villany villani
+ villiago villiago villian villian
+ villianda villianda villians villian
+ vinaigre vinaigr vincentio vincentio
+ vincere vincer vindicative vindic
+ vine vine vinegar vinegar
+ vines vine vineyard vineyard
+ vineyards vineyard vint vint
+ vintner vintner viol viol
+ viola viola violate violat
+ violated violat violates violat
+ violation violat violator violat
+ violence violenc violent violent
+ violenta violenta violenteth violenteth
+ violently violent violet violet
+ violets violet viper viper
+ viperous viper vipers viper
+ vir vir virgilia virgilia
+ virgin virgin virginal virgin
+ virginalling virginal virginity virgin
+ virginius virginiu virgins virgin
+ virgo virgo virtue virtu
+ virtues virtu virtuous virtuou
+ virtuously virtuous visag visag
+ visage visag visages visag
+ visard visard viscount viscount
+ visible visibl visibly visibl
+ vision vision visions vision
+ visit visit visitation visit
+ visitations visit visited visit
+ visiting visit visitings visit
+ visitor visitor visitors visitor
+ visits visit visor visor
+ vita vita vitae vita
+ vital vital vitement vitement
+ vitruvio vitruvio vitx vitx
+ viva viva vivant vivant
+ vive vive vixen vixen
+ viz viz vizaments vizament
+ vizard vizard vizarded vizard
+ vizards vizard vizor vizor
+ vlouting vlout vocation vocat
+ vocativo vocativo vocatur vocatur
+ voce voce voic voic
+ voice voic voices voic
+ void void voided void
+ voiding void voke voke
+ volable volabl volant volant
+ volivorco volivorco volley vollei
+ volquessen volquessen volsce volsc
+ volsces volsc volscian volscian
+ volscians volscian volt volt
+ voltemand voltemand volubility volubl
+ voluble volubl volume volum
+ volumes volum volumnia volumnia
+ volumnius volumniu voluntaries voluntari
+ voluntary voluntari voluptuously voluptu
+ voluptuousness voluptu vomissement vomiss
+ vomit vomit vomits vomit
+ vor vor vore vore
+ vortnight vortnight vot vot
+ votaries votari votarist votarist
+ votarists votarist votary votari
+ votre votr vouch vouch
+ voucher voucher vouchers voucher
+ vouches vouch vouching vouch
+ vouchsaf vouchsaf vouchsafe vouchsaf
+ vouchsafed vouchsaf vouchsafes vouchsaf
+ vouchsafing vouchsaf voudrais voudrai
+ vour vour vous vou
+ voutsafe voutsaf vow vow
+ vowed vow vowel vowel
+ vowels vowel vowing vow
+ vows vow vox vox
+ voyage voyag voyages voyag
+ vraiment vraiment vulcan vulcan
+ vulgar vulgar vulgarly vulgarli
+ vulgars vulgar vulgo vulgo
+ vulnerable vulner vulture vultur
+ vultures vultur vurther vurther
+ w w wad wad
+ waddled waddl wade wade
+ waded wade wafer wafer
+ waft waft waftage waftag
+ wafting waft wafts waft
+ wag wag wage wage
+ wager wager wagers wager
+ wages wage wagging wag
+ waggish waggish waggling waggl
+ waggon waggon waggoner waggon
+ wagon wagon wagoner wagon
+ wags wag wagtail wagtail
+ wail wail wailful wail
+ wailing wail wails wail
+ wain wain wainropes wainrop
+ wainscot wainscot waist waist
+ wait wait waited wait
+ waiter waiter waiteth waiteth
+ waiting wait waits wait
+ wak wak wake wake
+ waked wake wakefield wakefield
+ waken waken wakened waken
+ wakes wake wakest wakest
+ waking wake wales wale
+ walk walk walked walk
+ walking walk walks walk
+ wall wall walled wall
+ wallet wallet wallets wallet
+ wallon wallon walloon walloon
+ wallow wallow walls wall
+ walnut walnut walter walter
+ wan wan wand wand
+ wander wander wanderer wander
+ wanderers wander wandering wander
+ wanders wander wands wand
+ wane wane waned wane
+ wanes wane waning wane
+ wann wann want want
+ wanted want wanteth wanteth
+ wanting want wanton wanton
+ wantonly wantonli wantonness wanton
+ wantons wanton wants want
+ wappen wappen war war
+ warble warbl warbling warbl
+ ward ward warded ward
+ warden warden warder warder
+ warders warder wardrobe wardrob
+ wardrop wardrop wards ward
+ ware ware wares ware
+ warily warili warkworth warkworth
+ warlike warlik warm warm
+ warmed warm warmer warmer
+ warming warm warms warm
+ warmth warmth warn warn
+ warned warn warning warn
+ warnings warn warns warn
+ warp warp warped warp
+ warr warr warrant warrant
+ warranted warrant warranteth warranteth
+ warrantise warrantis warrantize warrant
+ warrants warrant warranty warranti
+ warren warren warrener warren
+ warring war warrior warrior
+ warriors warrior wars war
+ wart wart warwick warwick
+ warwickshire warwickshir wary wari
+ was wa wash wash
+ washed wash washer washer
+ washes wash washford washford
+ washing wash wasp wasp
+ waspish waspish wasps wasp
+ wassail wassail wassails wassail
+ wast wast waste wast
+ wasted wast wasteful wast
+ wasters waster wastes wast
+ wasting wast wat wat
+ watch watch watched watch
+ watchers watcher watches watch
+ watchful watch watching watch
+ watchings watch watchman watchman
+ watchmen watchmen watchword watchword
+ water water waterdrops waterdrop
+ watered water waterfly waterfli
+ waterford waterford watering water
+ waterish waterish waterpots waterpot
+ waterrugs waterrug waters water
+ waterton waterton watery wateri
+ wav wav wave wave
+ waved wave waver waver
+ waverer waver wavering waver
+ waves wave waving wave
+ waw waw wawl wawl
+ wax wax waxed wax
+ waxen waxen waxes wax
+ waxing wax way wai
+ waylaid waylaid waylay waylai
+ ways wai wayward wayward
+ waywarder wayward waywardness wayward
+ we we weak weak
+ weaken weaken weakens weaken
+ weaker weaker weakest weakest
+ weakling weakl weakly weakli
+ weakness weak weal weal
+ wealsmen wealsmen wealth wealth
+ wealthiest wealthiest wealthily wealthili
+ wealthy wealthi wealtlly wealtlli
+ wean wean weapon weapon
+ weapons weapon wear wear
+ wearer wearer wearers wearer
+ wearied weari wearies weari
+ weariest weariest wearily wearili
+ weariness weari wearing wear
+ wearisome wearisom wears wear
+ weary weari weasel weasel
+ weather weather weathercock weathercock
+ weathers weather weav weav
+ weave weav weaver weaver
+ weavers weaver weaves weav
+ weaving weav web web
+ wed wed wedded wed
+ wedding wed wedg wedg
+ wedged wedg wedges wedg
+ wedlock wedlock wednesday wednesdai
+ weed weed weeded weed
+ weeder weeder weeding weed
+ weeds weed weedy weedi
+ week week weeke week
+ weekly weekli weeks week
+ ween ween weening ween
+ weep weep weeper weeper
+ weeping weep weepingly weepingli
+ weepings weep weeps weep
+ weet weet weigh weigh
+ weighed weigh weighing weigh
+ weighs weigh weight weight
+ weightier weightier weightless weightless
+ weights weight weighty weighti
+ weird weird welcom welcom
+ welcome welcom welcomer welcom
+ welcomes welcom welcomest welcomest
+ welfare welfar welkin welkin
+ well well wells well
+ welsh welsh welshman welshman
+ welshmen welshmen welshwomen welshwomen
+ wench wench wenches wench
+ wenching wench wend wend
+ went went wept wept
+ weraday weradai were were
+ wert wert west west
+ western western westminster westminst
+ westmoreland westmoreland westward westward
+ wet wet wether wether
+ wetting wet wezand wezand
+ whale whale whales whale
+ wharf wharf wharfs wharf
+ what what whate whate
+ whatever whatev whatsoe whatso
+ whatsoever whatsoev whatsome whatsom
+ whe whe wheat wheat
+ wheaten wheaten wheel wheel
+ wheeling wheel wheels wheel
+ wheer wheer wheeson wheeson
+ wheezing wheez whelk whelk
+ whelks whelk whelm whelm
+ whelp whelp whelped whelp
+ whelps whelp when when
+ whenas whena whence whenc
+ whencesoever whencesoev whene whene
+ whenever whenev whensoever whensoev
+ where where whereabout whereabout
+ whereas wherea whereat whereat
+ whereby wherebi wherefore wherefor
+ wherein wherein whereinto whereinto
+ whereof whereof whereon whereon
+ whereout whereout whereso whereso
+ wheresoe whereso wheresoever wheresoev
+ wheresome wheresom whereto whereto
+ whereuntil whereuntil whereunto whereunto
+ whereupon whereupon wherever wherev
+ wherewith wherewith wherewithal wherewith
+ whet whet whether whether
+ whetstone whetston whetted whet
+ whew whew whey whei
+ which which whiff whiff
+ whiffler whiffler while while
+ whiles while whilst whilst
+ whin whin whine whine
+ whined whine whinid whinid
+ whining whine whip whip
+ whipp whipp whippers whipper
+ whipping whip whips whip
+ whipster whipster whipstock whipstock
+ whipt whipt whirl whirl
+ whirled whirl whirligig whirligig
+ whirling whirl whirlpool whirlpool
+ whirls whirl whirlwind whirlwind
+ whirlwinds whirlwind whisp whisp
+ whisper whisper whispering whisper
+ whisperings whisper whispers whisper
+ whist whist whistle whistl
+ whistles whistl whistling whistl
+ whit whit white white
+ whitehall whitehal whitely white
+ whiteness white whiter whiter
+ whites white whitest whitest
+ whither whither whiting white
+ whitmore whitmor whitsters whitster
+ whitsun whitsun whittle whittl
+ whizzing whizz who who
+ whoa whoa whoe whoe
+ whoever whoever whole whole
+ wholesom wholesom wholesome wholesom
+ wholly wholli whom whom
+ whoobub whoobub whoop whoop
+ whooping whoop whor whor
+ whore whore whoremaster whoremast
+ whoremasterly whoremasterli whoremonger whoremong
+ whores whore whoreson whoreson
+ whoresons whoreson whoring whore
+ whorish whorish whose whose
+ whoso whoso whosoe whoso
+ whosoever whosoev why why
+ wi wi wick wick
+ wicked wick wickednes wickedn
+ wickedness wicked wicket wicket
+ wicky wicki wid wid
+ wide wide widens widen
+ wider wider widow widow
+ widowed widow widower widow
+ widowhood widowhood widows widow
+ wield wield wife wife
+ wight wight wights wight
+ wild wild wildcats wildcat
+ wilder wilder wilderness wilder
+ wildest wildest wildfire wildfir
+ wildly wildli wildness wild
+ wilds wild wiles wile
+ wilful wil wilfull wilful
+ wilfully wilfulli wilfulnes wilfuln
+ wilfulness wil will will
+ willed will willers willer
+ willeth willeth william william
+ williams william willing will
+ willingly willingli willingness willing
+ willoughby willoughbi willow willow
+ wills will wilt wilt
+ wiltshire wiltshir wimpled wimpl
+ win win wince winc
+ winch winch winchester winchest
+ wincot wincot wind wind
+ winded wind windgalls windgal
+ winding wind windlasses windlass
+ windmill windmil window window
+ windows window windpipe windpip
+ winds wind windsor windsor
+ windy windi wine wine
+ wing wing winged wing
+ wingfield wingfield wingham wingham
+ wings wing wink wink
+ winking wink winks wink
+ winner winner winners winner
+ winning win winnow winnow
+ winnowed winnow winnows winnow
+ wins win winter winter
+ winterly winterli winters winter
+ wip wip wipe wipe
+ wiped wipe wipes wipe
+ wiping wipe wire wire
+ wires wire wiry wiri
+ wisdom wisdom wisdoms wisdom
+ wise wise wiselier wiseli
+ wisely wise wiser wiser
+ wisest wisest wish wish
+ wished wish wisher wisher
+ wishers wisher wishes wish
+ wishest wishest wisheth wisheth
+ wishful wish wishing wish
+ wishtly wishtli wisp wisp
+ wist wist wit wit
+ witb witb witch witch
+ witchcraft witchcraft witches witch
+ witching witch with with
+ withal withal withdraw withdraw
+ withdrawing withdraw withdrawn withdrawn
+ withdrew withdrew wither wither
+ withered wither withering wither
+ withers wither withheld withheld
+ withhold withhold withholds withhold
+ within within withold withold
+ without without withstand withstand
+ withstanding withstand withstood withstood
+ witless witless witness wit
+ witnesses wit witnesseth witnesseth
+ witnessing wit wits wit
+ witted wit wittenberg wittenberg
+ wittiest wittiest wittily wittili
+ witting wit wittingly wittingli
+ wittol wittol wittolly wittolli
+ witty witti wiv wiv
+ wive wive wived wive
+ wives wive wiving wive
+ wizard wizard wizards wizard
+ wo wo woe woe
+ woeful woeful woefull woeful
+ woefullest woefullest woes woe
+ woful woful wolf wolf
+ wolfish wolfish wolsey wolsei
+ wolves wolv wolvish wolvish
+ woman woman womanhood womanhood
+ womanish womanish womankind womankind
+ womanly womanli womb womb
+ wombs womb womby wombi
+ women women won won
+ woncot woncot wond wond
+ wonder wonder wondered wonder
+ wonderful wonder wonderfully wonderfulli
+ wondering wonder wonders wonder
+ wondrous wondrou wondrously wondrous
+ wont wont wonted wont
+ woo woo wood wood
+ woodbine woodbin woodcock woodcock
+ woodcocks woodcock wooden wooden
+ woodland woodland woodman woodman
+ woodmonger woodmong woods wood
+ woodstock woodstock woodville woodvil
+ wooed woo wooer wooer
+ wooers wooer wooes wooe
+ woof woof wooing woo
+ wooingly wooingli wool wool
+ woollen woollen woolly woolli
+ woolsack woolsack woolsey woolsei
+ woolward woolward woos woo
+ wor wor worcester worcest
+ word word words word
+ wore wore worins worin
+ work work workers worker
+ working work workings work
+ workman workman workmanly workmanli
+ workmanship workmanship workmen workmen
+ works work worky worki
+ world world worldlings worldl
+ worldly worldli worlds world
+ worm worm worms worm
+ wormwood wormwood wormy wormi
+ worn worn worried worri
+ worries worri worry worri
+ worrying worri worse wors
+ worser worser worship worship
+ worshipful worship worshipfully worshipfulli
+ worshipp worshipp worshipper worshipp
+ worshippers worshipp worshippest worshippest
+ worships worship worst worst
+ worsted worst wort wort
+ worth worth worthied worthi
+ worthier worthier worthies worthi
+ worthiest worthiest worthily worthili
+ worthiness worthi worthless worthless
+ worths worth worthy worthi
+ worts wort wot wot
+ wots wot wotting wot
+ wouid wouid would would
+ wouldest wouldest wouldst wouldst
+ wound wound wounded wound
+ wounding wound woundings wound
+ woundless woundless wounds wound
+ wouns woun woven woven
+ wow wow wrack wrack
+ wrackful wrack wrangle wrangl
+ wrangler wrangler wranglers wrangler
+ wrangling wrangl wrap wrap
+ wrapp wrapp wraps wrap
+ wrapt wrapt wrath wrath
+ wrathful wrath wrathfully wrathfulli
+ wraths wrath wreak wreak
+ wreakful wreak wreaks wreak
+ wreath wreath wreathed wreath
+ wreathen wreathen wreaths wreath
+ wreck wreck wrecked wreck
+ wrecks wreck wren wren
+ wrench wrench wrenching wrench
+ wrens wren wrest wrest
+ wrested wrest wresting wrest
+ wrestle wrestl wrestled wrestl
+ wrestler wrestler wrestling wrestl
+ wretch wretch wretchcd wretchcd
+ wretched wretch wretchedness wretched
+ wretches wretch wring wring
+ wringer wringer wringing wring
+ wrings wring wrinkle wrinkl
+ wrinkled wrinkl wrinkles wrinkl
+ wrist wrist wrists wrist
+ writ writ write write
+ writer writer writers writer
+ writes write writhled writhl
+ writing write writings write
+ writs writ written written
+ wrong wrong wronged wrong
+ wronger wronger wrongful wrong
+ wrongfully wrongfulli wronging wrong
+ wrongly wrongli wrongs wrong
+ wronk wronk wrote wrote
+ wroth wroth wrought wrought
+ wrung wrung wry wry
+ wrying wry wt wt
+ wul wul wye wye
+ x x xanthippe xanthipp
+ xi xi xii xii
+ xiii xiii xiv xiv
+ xv xv y y
+ yard yard yards yard
+ yare yare yarely yare
+ yarn yarn yaughan yaughan
+ yaw yaw yawn yawn
+ yawning yawn ycleped yclepe
+ ycliped yclipe ye ye
+ yea yea yead yead
+ year year yearly yearli
+ yearn yearn yearns yearn
+ years year yeas yea
+ yeast yeast yedward yedward
+ yell yell yellow yellow
+ yellowed yellow yellowing yellow
+ yellowness yellow yellows yellow
+ yells yell yelping yelp
+ yeoman yeoman yeomen yeomen
+ yerk yerk yes ye
+ yesterday yesterdai yesterdays yesterdai
+ yesternight yesternight yesty yesti
+ yet yet yew yew
+ yicld yicld yield yield
+ yielded yield yielder yielder
+ yielders yielder yielding yield
+ yields yield yok yok
+ yoke yoke yoked yoke
+ yokefellow yokefellow yokes yoke
+ yoketh yoketh yon yon
+ yond yond yonder yonder
+ yongrey yongrei yore yore
+ yorick yorick york york
+ yorkists yorkist yorks york
+ yorkshire yorkshir you you
+ young young younger younger
+ youngest youngest youngling youngl
+ younglings youngl youngly youngli
+ younker younker your your
+ yours your yourself yourself
+ yourselves yourselv youth youth
+ youthful youth youths youth
+ youtli youtli zanies zani
+ zany zani zeal zeal
+ zealous zealou zeals zeal
+ zed zed zenelophon zenelophon
+ zenith zenith zephyrs zephyr
+ zir zir zo zo
+ zodiac zodiac zodiacs zodiac
+ zone zone zounds zound
+ zwagger zwagger
+}
+
+
+set i 0
+foreach {in out} $test_vocab {
+ do_test "1.$i.($in -> $out)" {
+ lindex [sqlite3_fts5_tokenize db porter $in] 0
+ } $out
+ incr i
+}
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5prefix.test b/ext/fts5/test/fts5prefix.test
new file mode 100644
index 000000000..44c21a744
--- /dev/null
+++ b/ext/fts5/test/fts5prefix.test
@@ -0,0 +1,60 @@
+# 2015 Jan 13
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5prefix
+
+
+#-------------------------------------------------------------------------
+# Check that prefix indexes really do index n-character prefixes, not
+# n-byte prefixes. Use the ascii tokenizer so as not to be confused by
+# diacritic removal.
+#
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = ascii, prefix = 2)
+}
+
+do_test 1.2 {
+ foreach {rowid string} {
+ 1 "\xCA\xCB\xCC\xCD"
+ 2 "\u1234\u5678\u4321\u8765"
+ } {
+ execsql { INSERT INTO t1(rowid, x) VALUES($rowid, $string) }
+ }
+} {}
+
+do_execsql_test 1.1.2 {
+ INSERT INTO t1(t1) VALUES('integrity-check');
+}
+
+#db eval { select fts5_decode(id, block) AS d FROM t1_data; } { puts $d }
+
+foreach o {1 2} {
+ if {$o==2} breakpoint
+ foreach {tn q res} {
+ 1 "SELECT rowid FROM t1 WHERE t1 MATCH '\xCA\xCB*'" 1
+ 2 "SELECT rowid FROM t1 WHERE t1 MATCH '\u1234\u5678*'" 2
+ } {
+ do_execsql_test 1.$o.$tn $q $res
+ }
+
+ execsql {
+ DELETE FROM t1_data WHERE
+ rowid>=fts5_rowid('start-of-index', 0) AND
+ rowid<fts5_rowid('start-of-index', 1);
+ }
+}
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5rebuild.test b/ext/fts5/test/fts5rebuild.test
new file mode 100644
index 000000000..dfaf28bc6
--- /dev/null
+++ b/ext/fts5/test/fts5rebuild.test
@@ -0,0 +1,50 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5rebuild
+
+do_execsql_test 1.1 {
+ CREATE VIRTUAL TABLE f1 USING fts5(a, b);
+ INSERT INTO f1(a, b) VALUES('one', 'o n e');
+ INSERT INTO f1(a, b) VALUES('two', 't w o');
+ INSERT INTO f1(a, b) VALUES('three', 't h r e e');
+}
+
+do_execsql_test 1.2 {
+ INSERT INTO f1(f1) VALUES('integrity-check');
+} {}
+
+do_execsql_test 1.3 {
+ INSERT INTO f1(f1) VALUES('rebuild');
+} {}
+
+do_execsql_test 1.4 {
+ INSERT INTO f1(f1) VALUES('integrity-check');
+} {}
+
+do_execsql_test 1.5 {
+ DELETE FROM f1_data;
+} {}
+
+do_catchsql_test 1.6 {
+ INSERT INTO f1(f1) VALUES('integrity-check');
+} {1 {SQL logic error or missing database}}
+
+do_execsql_test 1.7 {
+ INSERT INTO f1(f1) VALUES('rebuild');
+ INSERT INTO f1(f1) VALUES('integrity-check');
+} {}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5rowid.test b/ext/fts5/test/fts5rowid.test
new file mode 100644
index 000000000..7ffd2977b
--- /dev/null
+++ b/ext/fts5/test/fts5rowid.test
@@ -0,0 +1,183 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Tests of the scalar fts5_rowid() and fts5_decode() functions.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5rowid
+
+do_catchsql_test 1.1 {
+ SELECT fts5_rowid()
+} {1 {should be: fts5_rowid(subject, ....)}}
+
+do_catchsql_test 1.2 {
+ SELECT fts5_rowid('segment')
+} {1 {should be: fts5_rowid('segment', idx, segid, height, pgno))}}
+
+do_execsql_test 1.3 {
+ SELECT fts5_rowid('segment', 1, 1, 1, 1)
+} {4503670494330881}
+
+do_catchsql_test 1.4 {
+ SELECT fts5_rowid('start-of-index');
+} {1 {should be: fts5_rowid('start-of-index', idx)}}
+
+do_execsql_test 1.5 {
+ SELECT fts5_rowid('start-of-index', 1);
+} {4503668346847232}
+
+do_catchsql_test 1.4 {
+ SELECT fts5_rowid('nosucharg');
+} {1 {first arg to fts5_rowid() must be 'segment' or 'start-of-index'}}
+
+
+#-------------------------------------------------------------------------
+# Tests of the fts5_decode() function.
+#
+reset_db
+do_execsql_test 2.1 {
+ CREATE VIRTUAL TABLE x1 USING fts5(a, b);
+ INSERT INTO x1(x1, rank) VALUES('pgsz', 32);
+} {}
+
+proc rnddoc {n} {
+ set map [list 0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j]
+ set doc [list]
+ for {set i 0} {$i < $n} {incr i} {
+ lappend doc [string map $map [format %.3d [expr int(rand()*100)]]]
+ }
+ set doc
+}
+db func rnddoc rnddoc
+
+do_execsql_test 2.2 {
+ WITH r(a, b) AS (
+ SELECT rnddoc(6), rnddoc(6) UNION ALL
+ SELECT rnddoc(6), rnddoc(6) FROM r
+ )
+ INSERT INTO x1 SELECT * FROM r LIMIT 10000;
+}
+
+set res [db one {SELECT count(*) FROM x1_data}]
+do_execsql_test 2.3 {
+ SELECT count(fts5_decode(rowid, block)) FROM x1_data;
+} $res
+do_execsql_test 2.4 {
+ UPDATE x1_data SET block = X'';
+ SELECT count(fts5_decode(rowid, block)) FROM x1_data;
+} $res
+
+do_execsql_test 2.5 {
+ INSERT INTO x1(x1, rank) VALUES('pgsz', 1024);
+ INSERT INTO x1(x1) VALUES('rebuild');
+}
+
+set res [db one {SELECT count(*) FROM x1_data}]
+do_execsql_test 2.6 {
+ SELECT count(fts5_decode(rowid, block)) FROM x1_data;
+} $res
+do_execsql_test 2.7 {
+ UPDATE x1_data SET block = X'';
+ SELECT count(fts5_decode(rowid, block)) FROM x1_data;
+} $res
+
+#-------------------------------------------------------------------------
+# Tests with very large tokens.
+#
+set strlist [list \
+ "[string repeat x 400]" \
+ "[string repeat x 300][string repeat w 100]" \
+ "[string repeat x 300][string repeat y 100]" \
+ "[string repeat x 300][string repeat z 600]" \
+]
+do_test 3.0 {
+ execsql {
+ BEGIN;
+ CREATE VIRTUAL TABLE x2 USING fts5(a);
+ }
+ foreach str $strlist { execsql { INSERT INTO x2 VALUES($str) } }
+ execsql COMMIT
+} {}
+
+for {set tn 0} {$tn<[llength $strlist]} {incr tn} {
+ set str [lindex $strlist $tn]
+ do_execsql_test 3.1.$tn {
+ SELECT rowid FROM x2 WHERE x2 MATCH $str
+ } [expr $tn+1]
+}
+
+set res [db one {SELECT count(*) FROM x2_data}]
+do_execsql_test 3.2 {
+ SELECT count(fts5_decode(rowid, block)) FROM x2_data;
+} $res
+
+#-------------------------------------------------------------------------
+# Leaf pages with no terms or rowids at all.
+#
+set strlist [list \
+ "[string repeat {w } 400]" \
+ "[string repeat {x } 400]" \
+ "[string repeat {y } 400]" \
+ "[string repeat {z } 400]" \
+]
+do_test 4.0 {
+ execsql {
+ BEGIN;
+ CREATE VIRTUAL TABLE x3 USING fts5(a);
+ INSERT INTO x3(x3, rank) VALUES('pgsz', 32);
+ }
+ foreach str $strlist { execsql { INSERT INTO x3 VALUES($str) } }
+ execsql COMMIT
+} {}
+
+for {set tn 0} {$tn<[llength $strlist]} {incr tn} {
+ set str [lindex $strlist $tn]
+ do_execsql_test 4.1.$tn {
+ SELECT rowid FROM x3 WHERE x3 MATCH $str
+ } [expr $tn+1]
+}
+
+set res [db one {SELECT count(*) FROM x3_data}]
+do_execsql_test 4.2 {
+ SELECT count(fts5_decode(rowid, block)) FROM x3_data;
+} $res
+
+#-------------------------------------------------------------------------
+# Position lists with large values.
+#
+set strlist [list \
+ "[string repeat {w } 400]a" \
+ "[string repeat {x } 400]a" \
+ "[string repeat {y } 400]a" \
+ "[string repeat {z } 400]a" \
+]
+do_test 5.0 {
+ execsql {
+ BEGIN;
+ CREATE VIRTUAL TABLE x4 USING fts5(a);
+ INSERT INTO x4(x4, rank) VALUES('pgsz', 32);
+ }
+ foreach str $strlist { execsql { INSERT INTO x4 VALUES($str) } }
+ execsql COMMIT
+} {}
+
+do_execsql_test 5.1 {
+ SELECT rowid FROM x4 WHERE x4 MATCH 'a'
+} {1 2 3 4}
+
+set res [db one {SELECT count(*) FROM x4_data}]
+do_execsql_test 5.2 {
+ SELECT count(fts5_decode(rowid, block)) FROM x4_data;
+} $res
+
+finish_test
+
diff --git a/ext/fts5/test/fts5tokenizer.test b/ext/fts5/test/fts5tokenizer.test
new file mode 100644
index 000000000..d8c4f20f0
--- /dev/null
+++ b/ext/fts5/test/fts5tokenizer.test
@@ -0,0 +1,97 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Tests focusing on the fts5 tokenizers
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5tokenizer
+
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(x, tokenize=porter);
+ DROP TABLE ft1;
+}
+do_execsql_test 1.1 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(x, tokenize='porter');
+ DROP TABLE ft1;
+}
+do_execsql_test 1.2 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(x, tokenize = porter);
+ DROP TABLE ft1;
+}
+do_execsql_test 1.3 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(x, tokenize = 'porter');
+ DROP TABLE ft1;
+}
+do_execsql_test 1.4 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(x, tokenize = 'porter ascii');
+ DROP TABLE ft1;
+}
+
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE ft1 USING fts5(x, tokenize=porter);
+ INSERT INTO ft1 VALUES('embedded databases');
+}
+do_execsql_test 2.1 { SELECT rowid FROM ft1 WHERE ft1 MATCH 'embedding' } 1
+do_execsql_test 2.2 { SELECT rowid FROM ft1 WHERE ft1 MATCH 'database' } 1
+do_execsql_test 2.3 {
+ SELECT rowid FROM ft1 WHERE ft1 MATCH 'database embedding'
+} 1
+
+proc tcl_create {args} {
+ set ::targs $args
+ error "failed"
+}
+sqlite3_fts5_create_tokenizer db tcl tcl_create
+
+foreach {tn directive expected} {
+ 1 {tokenize='tcl a b c'} {a b c}
+ 2 {tokenize='tcl ''d'' ''e'' ''f'''} {d e f}
+ 3 {tokenize="tcl 'g' 'h' 'i'"} {g h i}
+ 4 {tokenize = tcl} {}
+} {
+ do_catchsql_test 3.$tn.1 "
+ CREATE VIRTUAL TABLE ft2 USING fts5(x, $directive)
+ " {1 {error in tokenizer constructor}}
+ do_test 3.$tn.2 { set ::targs } $expected
+}
+
+do_catchsql_test 4.1 {
+ CREATE VIRTUAL TABLE ft2 USING fts5(x, tokenize = tcl abc);
+} {1 {parse error in "tokenize = tcl abc"}}
+do_catchsql_test 4.2 {
+ CREATE VIRTUAL TABLE ft2 USING fts5(x y)
+} {1 {parse error in "x y"}}
+
+#-------------------------------------------------------------------------
+# Test the "separators" and "tokenchars" options a bit.
+#
+foreach {tn tokenizer} {1 ascii 2 unicode61} {
+ reset_db
+ set T "$tokenizer tokenchars ',.:' separators 'xyz'"
+ execsql "CREATE VIRTUAL TABLE t1 USING fts5(x, tokenize = \"$T\")"
+ do_execsql_test 5.$tn.1 {
+ INSERT INTO t1 VALUES('abcxdefyghizjkl.mno,pqr:stu/vwx+yz');
+ }
+ foreach {tn2 token res} {
+ 1 abc 1 2 def 1 3 ghi 1 4 jkl {}
+ 5 mno {} 6 pqr {} 7 stu {} 8 jkl.mno,pqr:stu 1
+ 9 vw 1
+ } {
+ do_execsql_test 5.$tn.2.$tn2 "
+ SELECT rowid FROM t1 WHERE t1 MATCH '\"$token\"'
+ " $res
+ }
+}
+
+finish_test
+
diff --git a/ext/fts5/test/fts5unicode.test b/ext/fts5/test/fts5unicode.test
new file mode 100644
index 000000000..0018a4903
--- /dev/null
+++ b/ext/fts5/test/fts5unicode.test
@@ -0,0 +1,56 @@
+# 2014 Dec 20
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Tests focusing on the fts5 tokenizers
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5unicode
+
+proc tokenize_test {tn tokenizer input output} {
+ uplevel [list do_test $tn [subst -nocommands {
+ set ret {}
+ foreach {z s e} [sqlite3_fts5_tokenize db {$tokenizer} {$input}] {
+ lappend ret [set z]
+ }
+ set ret
+ }] [list {*}$output]]
+}
+
+foreach {tn t} {1 ascii 2 unicode61} {
+ tokenize_test 1.$tn.0 $t {A B C D} {a b c d}
+ tokenize_test 1.$tn.1 $t {May you share freely,} {may you share freely}
+ tokenize_test 1.$tn.2 $t {..May...you.shAre.freely} {may you share freely}
+ tokenize_test 1.$tn.3 $t {} {}
+}
+
+#-------------------------------------------------------------------------
+# Check that "unicode61" really is the default tokenizer.
+#
+
+do_execsql_test 2.0 "
+ CREATE VIRTUAL TABLE t1 USING fts5(x);
+ CREATE VIRTUAL TABLE t2 USING fts5(x, tokenize = unicode61);
+ CREATE VIRTUAL TABLE t3 USING fts5(x, tokenize = ascii);
+ INSERT INTO t1 VALUES('\xC0\xC8\xCC');
+ INSERT INTO t2 VALUES('\xC0\xC8\xCC');
+ INSERT INTO t3 VALUES('\xC0\xC8\xCC');
+"
+breakpoint
+do_execsql_test 2.1 "
+ SELECT 't1' FROM t1 WHERE t1 MATCH '\xE0\xE8\xEC';
+ SELECT 't2' FROM t2 WHERE t2 MATCH '\xE0\xE8\xEC';
+ SELECT 't3' FROM t3 WHERE t3 MATCH '\xE0\xE8\xEC';
+" {t1 t2}
+
+
+finish_test
+
diff --git a/ext/fts5/test/fts5unicode2.test b/ext/fts5/test/fts5unicode2.test
new file mode 100644
index 000000000..056106e18
--- /dev/null
+++ b/ext/fts5/test/fts5unicode2.test
@@ -0,0 +1,564 @@
+# 2012 May 25
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+#
+# The tests in this file focus on testing the "unicode" FTS tokenizer.
+#
+# This is a modified copy of FTS4 test file "fts4_unicode.test".
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5unicode2
+
+proc do_unicode_token_test {tn input res} {
+ uplevel [list do_test $tn [list \
+ sqlite3_fts5_tokenize -subst db "unicode61 remove_diacritics 0" $input
+ ] [list {*}$res]]
+}
+
+proc do_unicode_token_test2 {tn input res} {
+ uplevel [list do_test $tn [list \
+ sqlite3_fts5_tokenize -subst db "unicode61" $input
+ ] [list {*}$res]]
+}
+
+proc do_unicode_token_test3 {tn args} {
+ set tokenizer [concat unicode61 {*}[lrange $args 0 end-2]]
+ set input [lindex $args end-1]
+ set res [lindex $args end]
+ uplevel [list do_test $tn [list \
+ sqlite3_fts5_tokenize -subst db $tokenizer $input
+ ] [list {*}$res]]
+}
+
+do_unicode_token_test 1.0 {a B c D} {a a b B c c d D}
+
+do_unicode_token_test 1.1 "\uC4 \uD6 \uDC" \
+ "\uE4 \uC4 \uF6 \uD6 \uFC \uDC"
+
+do_unicode_token_test 1.2 "x\uC4x x\uD6x x\uDCx" \
+ "x\uE4x x\uC4x x\uF6x x\uD6x x\uFCx x\uDCx"
+
+# 0x00DF is a small "sharp s". 0x1E9E is a capital sharp s.
+do_unicode_token_test 1.3 "\uDF" "\uDF \uDF"
+do_unicode_token_test 1.4 "\u1E9E" "\uDF \u1E9E"
+
+do_unicode_token_test 1.5 "The quick brown fox" {
+ the The quick quick brown brown fox fox
+}
+do_unicode_token_test 1.6 "The\u00bfquick\u224ebrown\u2263fox" {
+ the The quick quick brown brown fox fox
+}
+
+do_unicode_token_test2 1.7 {a B c D} {a a b B c c d D}
+do_unicode_token_test2 1.8 "\uC4 \uD6 \uDC" "a \uC4 o \uD6 u \uDC"
+
+do_unicode_token_test2 1.9 "x\uC4x x\uD6x x\uDCx" \
+ "xax x\uC4x xox x\uD6x xux x\uDCx"
+
+# Check that diacritics are removed if remove_diacritics=1 is specified.
+# And that they do not break tokens.
+do_unicode_token_test2 1.10 "xx\u0301xx" "xxxx xx\u301xx"
+
+# Title-case mappings work
+do_unicode_token_test 1.11 "\u01c5" "\u01c6 \u01c5"
+
+#-------------------------------------------------------------------------
+#
+set docs [list {
+ Enhance the INSERT syntax to allow multiple rows to be inserted via the
+ VALUES clause.
+} {
+ Enhance the CREATE VIRTUAL TABLE command to support the IF NOT EXISTS clause.
+} {
+ Added the sqlite3_stricmp() interface as a counterpart to sqlite3_strnicmp().
+} {
+ Added the sqlite3_db_readonly() interface.
+} {
+ Added the SQLITE_FCNTL_PRAGMA file control, giving VFS implementations the
+ ability to add new PRAGMA statements or to override built-in PRAGMAs.
+} {
+ Queries of the form: "SELECT max(x), y FROM table" returns the value of y on
+ the same row that contains the maximum x value.
+} {
+ Added support for the FTS4 languageid option.
+} {
+ Documented support for the FTS4 content option. This feature has actually
+ been in the code since version 3.7.9 but is only now considered to be
+ officially supported.
+} {
+ Pending statements no longer block ROLLBACK. Instead, the pending statement
+ will return SQLITE_ABORT upon next access after the ROLLBACK.
+} {
+ Improvements to the handling of CSV inputs in the command-line shell
+} {
+ Fix a bug introduced in version 3.7.10 that might cause a LEFT JOIN to be
+ incorrectly converted into an INNER JOIN if the WHERE clause indexable terms
+ connected by OR.
+}]
+
+set map(a) [list "\u00C4" "\u00E4"] ; # LATIN LETTER A WITH DIAERESIS
+set map(e) [list "\u00CB" "\u00EB"] ; # LATIN LETTER E WITH DIAERESIS
+set map(i) [list "\u00CF" "\u00EF"] ; # LATIN LETTER I WITH DIAERESIS
+set map(o) [list "\u00D6" "\u00F6"] ; # LATIN LETTER O WITH DIAERESIS
+set map(u) [list "\u00DC" "\u00FC"] ; # LATIN LETTER U WITH DIAERESIS
+set map(y) [list "\u0178" "\u00FF"] ; # LATIN LETTER Y WITH DIAERESIS
+set map(h) [list "\u1E26" "\u1E27"] ; # LATIN LETTER H WITH DIAERESIS
+set map(w) [list "\u1E84" "\u1E85"] ; # LATIN LETTER W WITH DIAERESIS
+set map(x) [list "\u1E8C" "\u1E8D"] ; # LATIN LETTER X WITH DIAERESIS
+foreach k [array names map] {
+ lappend mappings [string toupper $k] [lindex $map($k) 0]
+ lappend mappings $k [lindex $map($k) 1]
+}
+proc mapdoc {doc} {
+ set doc [regsub -all {[[:space:]]+} $doc " "]
+ string map $::mappings [string trim $doc]
+}
+
+do_test 2.0 {
+ execsql { CREATE VIRTUAL TABLE t2 USING fts5(tokenize=unicode61, x); }
+ foreach doc $docs {
+ set d [mapdoc $doc]
+ execsql { INSERT INTO t2 VALUES($d) }
+ }
+} {}
+
+do_test 2.1 {
+ set q [mapdoc "row"]
+ execsql { SELECT * FROM t2 WHERE t2 MATCH $q }
+} [list [mapdoc {
+ Queries of the form: "SELECT max(x), y FROM table" returns the value of y on
+ the same row that contains the maximum x value.
+}]]
+
+foreach {tn query snippet} {
+ 2 "row" {
+ ...returns the value of y on the same [row] that contains
+ the maximum x value.
+ }
+ 3 "ROW" {
+ ...returns the value of y on the same [row] that contains
+ the maximum x value.
+ }
+ 4 "rollback" {
+ ...[ROLLBACK]. Instead, the pending statement
+ will return SQLITE_ABORT upon next access after the [ROLLBACK].
+ }
+ 5 "rOllback" {
+ ...[ROLLBACK]. Instead, the pending statement
+ will return SQLITE_ABORT upon next access after the [ROLLBACK].
+ }
+ 6 "lang*" {
+ Added support for the FTS4 [languageid] option.
+ }
+} {
+ do_test 2.$tn {
+ set q [mapdoc $query]
+ execsql {
+ SELECT snippet(t2, -1, '[', ']', '...', 15) FROM t2 WHERE t2 MATCH $q
+ }
+ } [list [mapdoc $snippet]]
+}
+
+#-------------------------------------------------------------------------
+# Make sure the unicode61 tokenizer does not crash if it is passed a
+# NULL pointer.
+reset_db
+do_execsql_test 3.1 {
+ CREATE VIRTUAL TABLE t1 USING fts5(tokenize=unicode61, x, y);
+ INSERT INTO t1 VALUES(NULL, 'a b c');
+}
+
+do_execsql_test 3.2 {
+ SELECT snippet(t1, -1, '[', ']', '...', 15) FROM t1 WHERE t1 MATCH 'b'
+} {{a [b] c}}
+
+do_execsql_test 3.3 {
+ BEGIN;
+ DELETE FROM t1;
+ INSERT INTO t1 VALUES('b b b b b b b b b b b', 'b b b b b b b b b b b b b');
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 VALUES('a b c', NULL);
+ INSERT INTO t1 VALUES('a x c', NULL);
+ COMMIT;
+}
+
+do_execsql_test 3.4 {
+ SELECT * FROM t1 WHERE t1 MATCH 'a b';
+} {{a b c} {}}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+
+do_test 4.1 {
+ set a "abc\uFFFEdef"
+ set b "abc\uD800def"
+ set c "\uFFFEdef"
+ set d "\uD800def"
+ execsql {
+ CREATE VIRTUAL TABLE t1 USING fts5(tokenize=unicode61, x);
+ INSERT INTO t1 VALUES($a);
+ INSERT INTO t1 VALUES($b);
+ INSERT INTO t1 VALUES($c);
+ INSERT INTO t1 VALUES($d);
+ }
+} {}
+
+do_test 4.2 {
+ set a [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0x62}]
+ set b [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0x62}]
+ set c [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0xBF 0x62}]
+ set d [binary format c* {0x61 0xF7 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF 0x62}]
+ execsql {
+ INSERT INTO t1 VALUES($a);
+ INSERT INTO t1 VALUES($b);
+ INSERT INTO t1 VALUES($c);
+ INSERT INTO t1 VALUES($d);
+ }
+} {}
+
+do_test 4.3 {
+ set a [binary format c* {0xF7 0xBF 0xBF 0xBF}]
+ set b [binary format c* {0xF7 0xBF 0xBF 0xBF 0xBF}]
+ set c [binary format c* {0xF7 0xBF 0xBF 0xBF 0xBF 0xBF}]
+ set d [binary format c* {0xF7 0xBF 0xBF 0xBF 0xBF 0xBF 0xBF}]
+ execsql {
+ INSERT INTO t1 VALUES($a);
+ INSERT INTO t1 VALUES($b);
+ INSERT INTO t1 VALUES($c);
+ INSERT INTO t1 VALUES($d);
+ }
+} {}
+
+
+#-------------------------------------------------------------------------
+
+breakpoint
+do_unicode_token_test3 5.1 {tokenchars {}} {
+ sqlite3_reset sqlite3_column_int
+} {
+ sqlite3 sqlite3
+ reset reset
+ sqlite3 sqlite3
+ column column
+ int int
+}
+
+do_unicode_token_test3 5.2 {tokenchars _} {
+ sqlite3_reset sqlite3_column_int
+} {
+ sqlite3_reset sqlite3_reset
+ sqlite3_column_int sqlite3_column_int
+}
+
+do_unicode_token_test3 5.3 {separators xyz} {
+ Laotianxhorseyrunszfast
+} {
+ laotian Laotian
+ horse horse
+ runs runs
+ fast fast
+}
+
+do_unicode_token_test3 5.4 {tokenchars xyz} {
+ Laotianxhorseyrunszfast
+} {
+ laotianxhorseyrunszfast Laotianxhorseyrunszfast
+}
+
+do_unicode_token_test3 5.5 {tokenchars _} {separators zyx} {
+ sqlite3_resetxsqlite3_column_intyhonda_phantom
+} {
+ sqlite3_reset sqlite3_reset
+ sqlite3_column_int sqlite3_column_int
+ honda_phantom honda_phantom
+}
+
+do_unicode_token_test3 5.6 "separators \u05D1" "abc\u05D1def" {
+ abc abc def def
+}
+
+do_unicode_token_test3 5.7 \
+ "tokenchars \u2444\u2445" \
+ "separators \u05D0\u05D1\u05D2" \
+ "\u2444fre\u2445sh\u05D0water\u05D2fish.\u2445timer" \
+ [list \
+ \u2444fre\u2445sh \u2444fre\u2445sh \
+ water water \
+ fish fish \
+ \u2445timer \u2445timer \
+ ]
+
+# Check that it is not possible to add a standalone diacritic codepoint
+# to either separators or tokenchars.
+do_unicode_token_test3 5.8 "separators \u0301" \
+ "hello\u0301world \u0301helloworld" \
+ "helloworld hello\u0301world helloworld helloworld"
+
+do_unicode_token_test3 5.9 "tokenchars \u0301" \
+ "hello\u0301world \u0301helloworld" \
+ "helloworld hello\u0301world helloworld helloworld"
+
+do_unicode_token_test3 5.10 "separators \u0301" \
+ "remove_diacritics 0" \
+ "hello\u0301world \u0301helloworld" \
+ "hello\u0301world hello\u0301world helloworld helloworld"
+
+do_unicode_token_test3 5.11 "tokenchars \u0301" \
+ "remove_diacritics 0" \
+ "hello\u0301world \u0301helloworld" \
+ "hello\u0301world hello\u0301world helloworld helloworld"
+
+#-------------------------------------------------------------------------
+
+proc do_tokenize {tokenizer txt} {
+ set res [list]
+ foreach {b c} [sqlite3_fts5_tokenize -subst db $tokenizer $txt] {
+ lappend res $b
+ }
+ set res
+}
+
+# Argument $lCodepoint must be a list of codepoints (integers) that
+# correspond to whitespace characters. This command creates a string
+# $W from the codepoints, then tokenizes "${W}hello{$W}world${W}"
+# using tokenizer $tokenizer. The test passes if the tokenizer successfully
+# extracts the two 5 character tokens.
+#
+proc do_isspace_test {tn tokenizer lCp} {
+ set whitespace [format [string repeat %c [llength $lCp]] {*}$lCp]
+ set txt "${whitespace}hello${whitespace}world${whitespace}"
+ uplevel [list do_test $tn [list do_tokenize $tokenizer $txt] {hello world}]
+}
+
+set tokenizers [list unicode61]
+ifcapable icu { lappend tokenizers icu }
+
+# Some tests to check that the tokenizers can both identify white-space
+# codepoints. All codepoints tested below are of type "Zs" in the
+# UnicodeData.txt file.
+foreach T $tokenizers {
+ do_isspace_test 6.$T.1 $T 32
+ do_isspace_test 6.$T.2 $T 160
+ do_isspace_test 6.$T.3 $T 5760
+ do_isspace_test 6.$T.4 $T 6158
+ do_isspace_test 6.$T.5 $T 8192
+ do_isspace_test 6.$T.6 $T 8193
+ do_isspace_test 6.$T.7 $T 8194
+ do_isspace_test 6.$T.8 $T 8195
+ do_isspace_test 6.$T.9 $T 8196
+ do_isspace_test 6.$T.10 $T 8197
+ do_isspace_test 6.$T.11 $T 8198
+ do_isspace_test 6.$T.12 $T 8199
+ do_isspace_test 6.$T.13 $T 8200
+ do_isspace_test 6.$T.14 $T 8201
+ do_isspace_test 6.$T.15 $T 8202
+ do_isspace_test 6.$T.16 $T 8239
+ do_isspace_test 6.$T.17 $T 8287
+ do_isspace_test 6.$T.18 $T 12288
+
+ do_isspace_test 6.$T.19 $T {32 160 5760 6158}
+ do_isspace_test 6.$T.20 $T {8192 8193 8194 8195}
+ do_isspace_test 6.$T.21 $T {8196 8197 8198 8199}
+ do_isspace_test 6.$T.22 $T {8200 8201 8202 8239}
+ do_isspace_test 6.$T.23 $T {8287 12288}
+}
+
+
+#-------------------------------------------------------------------------
+# Test that the private use ranges are treated as alphanumeric.
+#
+foreach {tn1 c} {
+ 1 \ue000 2 \ue001 3 \uf000 4 \uf8fe 5 \uf8ff
+} {
+ foreach {tn2 config res} {
+ 1 "" "hello*world hello*world"
+ 2 "separators *" "hello hello world world"
+ } {
+ set config [string map [list * $c] $config]
+ set input [string map [list * $c] "hello*world"]
+ set output [string map [list * $c] $res]
+ do_unicode_token_test3 7.$tn1.$tn2 {*}$config $input $output
+ }
+}
+
+#-------------------------------------------------------------------------
+# Cursory test of remove_diacritics=0.
+#
+# 00C4;LATIN CAPITAL LETTER A WITH DIAERESIS
+# 00D6;LATIN CAPITAL LETTER O WITH DIAERESIS
+# 00E4;LATIN SMALL LETTER A WITH DIAERESIS
+# 00F6;LATIN SMALL LETTER O WITH DIAERESIS
+#
+do_execsql_test 8.1.1 "
+ CREATE VIRTUAL TABLE t3 USING fts5(
+ content, tokenize='unicode61 remove_diacritics 1'
+ );
+ INSERT INTO t3 VALUES('o');
+ INSERT INTO t3 VALUES('a');
+ INSERT INTO t3 VALUES('O');
+ INSERT INTO t3 VALUES('A');
+ INSERT INTO t3 VALUES('\xD6');
+ INSERT INTO t3 VALUES('\xC4');
+ INSERT INTO t3 VALUES('\xF6');
+ INSERT INTO t3 VALUES('\xE4');
+"
+do_execsql_test 8.1.2 {
+ SELECT rowid FROM t3 WHERE t3 MATCH 'o' ORDER BY rowid ASC;
+} {1 3 5 7}
+do_execsql_test 8.1.3 {
+ SELECT rowid FROM t3 WHERE t3 MATCH 'a' ORDER BY rowid ASC;
+} {2 4 6 8}
+do_execsql_test 8.2.1 {
+ CREATE VIRTUAL TABLE t4 USING fts5(
+ content, tokenize='unicode61 remove_diacritics 0'
+ );
+ INSERT INTO t4 SELECT * FROM t3 ORDER BY rowid ASC;
+}
+do_execsql_test 8.2.2 {
+ SELECT rowid FROM t4 WHERE t4 MATCH 'o' ORDER BY rowid ASC;
+} {1 3}
+do_execsql_test 8.2.3 {
+ SELECT rowid FROM t4 WHERE t4 MATCH 'a' ORDER BY rowid ASC;
+} {2 4}
+
+#-------------------------------------------------------------------------
+#
+if 0 {
+foreach {tn sql} {
+ 1 {
+ CREATE VIRTUAL TABLE t5 USING fts4(tokenize=unicode61 [tokenchars= .]);
+ CREATE VIRTUAL TABLE t6 USING fts4(
+ tokenize=unicode61 [tokenchars=="] "tokenchars=[]");
+ CREATE VIRTUAL TABLE t7 USING fts4(tokenize=unicode61 [separators=x\xC4]);
+ }
+ 2 {
+ CREATE VIRTUAL TABLE t5 USING fts4(tokenize=unicode61 "tokenchars= .");
+ CREATE VIRTUAL TABLE t6 USING fts4(tokenize=unicode61 "tokenchars=[=""]");
+ CREATE VIRTUAL TABLE t7 USING fts4(tokenize=unicode61 "separators=x\xC4");
+ }
+ 3 {
+ CREATE VIRTUAL TABLE t5 USING fts4(tokenize=unicode61 'tokenchars= .');
+ CREATE VIRTUAL TABLE t6 USING fts4(tokenize=unicode61 'tokenchars=="[]');
+ CREATE VIRTUAL TABLE t7 USING fts4(tokenize=unicode61 'separators=x\xC4');
+ }
+ 4 {
+ CREATE VIRTUAL TABLE t5 USING fts4(tokenize=unicode61 `tokenchars= .`);
+ CREATE VIRTUAL TABLE t6 USING fts4(tokenize=unicode61 `tokenchars=[="]`);
+ CREATE VIRTUAL TABLE t7 USING fts4(tokenize=unicode61 `separators=x\xC4`);
+ }
+} {
+ do_execsql_test 9.$tn.0 {
+ DROP TABLE IF EXISTS t5;
+ DROP TABLE IF EXISTS t5aux;
+ DROP TABLE IF EXISTS t6;
+ DROP TABLE IF EXISTS t6aux;
+ DROP TABLE IF EXISTS t7;
+ DROP TABLE IF EXISTS t7aux;
+ }
+ do_execsql_test 9.$tn.1 $sql
+
+ do_execsql_test 9.$tn.2 {
+ CREATE VIRTUAL TABLE t5aux USING fts4aux(t5);
+ INSERT INTO t5 VALUES('one two three/four.five.six');
+ SELECT * FROM t5aux;
+ } {
+ four.five.six * 1 1 four.five.six 0 1 1
+ {one two three} * 1 1 {one two three} 0 1 1
+ }
+
+ do_execsql_test 9.$tn.3 {
+ CREATE VIRTUAL TABLE t6aux USING fts4aux(t6);
+ INSERT INTO t6 VALUES('alpha=beta"gamma/delta[epsilon]zeta');
+ SELECT * FROM t6aux;
+ } {
+ {alpha=beta"gamma} * 1 1 {alpha=beta"gamma} 0 1 1
+ {delta[epsilon]zeta} * 1 1 {delta[epsilon]zeta} 0 1 1
+ }
+
+ do_execsql_test 9.$tn.4 {
+ CREATE VIRTUAL TABLE t7aux USING fts4aux(t7);
+ INSERT INTO t7 VALUES('alephxbeth\xC4gimel');
+ SELECT * FROM t7aux;
+ } {
+ aleph * 1 1 aleph 0 1 1
+ beth * 1 1 beth 0 1 1
+ gimel * 1 1 gimel 0 1 1
+ }
+}
+
+# Check that multiple options are handled correctly.
+#
+do_execsql_test 10.1 {
+ DROP TABLE IF EXISTS t1;
+ CREATE VIRTUAL TABLE t1 USING fts4(tokenize=unicode61
+ "tokenchars=xyz" "tokenchars=.=" "separators=.=" "separators=xy"
+ "separators=a" "separators=a" "tokenchars=a" "tokenchars=a"
+ );
+
+ INSERT INTO t1 VALUES('oneatwoxthreeyfour');
+ INSERT INTO t1 VALUES('a.single=word');
+ CREATE VIRTUAL TABLE t1aux USING fts4aux(t1);
+ SELECT * FROM t1aux;
+} {
+ .single=word * 1 1 .single=word 0 1 1
+ four * 1 1 four 0 1 1
+ one * 1 1 one 0 1 1
+ three * 1 1 three 0 1 1
+ two * 1 1 two 0 1 1
+}
+
+# Test that case folding happens after tokenization, not before.
+#
+do_execsql_test 10.2 {
+ DROP TABLE IF EXISTS t2;
+ CREATE VIRTUAL TABLE t2 USING fts4(tokenize=unicode61 "separators=aB");
+ INSERT INTO t2 VALUES('oneatwoBthree');
+ INSERT INTO t2 VALUES('onebtwoAthree');
+ CREATE VIRTUAL TABLE t2aux USING fts4aux(t2);
+ SELECT * FROM t2aux;
+} {
+ one * 1 1 one 0 1 1
+ onebtwoathree * 1 1 onebtwoathree 0 1 1
+ three * 1 1 three 0 1 1
+ two * 1 1 two 0 1 1
+}
+
+# Test that the tokenchars and separators options work with the
+# fts3tokenize table.
+#
+do_execsql_test 11.1 {
+ CREATE VIRTUAL TABLE ft1 USING fts3tokenize(
+ "unicode61", "tokenchars=@.", "separators=1234567890"
+ );
+ SELECT token FROM ft1 WHERE input = 'berlin@street123sydney.road';
+} {
+ berlin@street sydney.road
+}
+
+}
+
+finish_test
diff --git a/ext/fts5/tool/loadfts5.tcl b/ext/fts5/tool/loadfts5.tcl
new file mode 100644
index 000000000..feb92ec16
--- /dev/null
+++ b/ext/fts5/tool/loadfts5.tcl
@@ -0,0 +1,121 @@
+
+
+proc loadfile {f} {
+ set fd [open $f]
+ set data [read $fd]
+ close $fd
+ return $data
+}
+
+set ::nRow 0
+set ::nRowPerDot 1000
+
+proc load_hierachy {dir} {
+ foreach f [glob -nocomplain -dir $dir *] {
+ if {$::O(limit) && $::nRow>=$::O(limit)} break
+ if {[file isdir $f]} {
+ load_hierachy $f
+ } else {
+ db eval { INSERT INTO t1 VALUES($f, loadfile($f)) }
+ incr ::nRow
+
+ if {($::nRow % $::nRowPerDot)==0} {
+ puts -nonewline .
+ if {($::nRow % (65*$::nRowPerDot))==0} { puts "" }
+ flush stdout
+ }
+
+ }
+ }
+}
+
+proc usage {} {
+ puts stderr "Usage: $::argv0 ?SWITCHES? DATABASE PATH"
+ puts stderr ""
+ puts stderr "Switches are:"
+ puts stderr " -fts4 (use fts4 instead of fts5)"
+ puts stderr " -fts5 (use fts5)"
+ puts stderr " -porter (use porter tokenizer)"
+ puts stderr " -delete (delete the database file before starting)"
+ puts stderr " -limit N (load no more than N documents)"
+ puts stderr " -automerge N (set the automerge parameter to N)"
+ puts stderr " -crisismerge N (set the crisismerge parameter to N)"
+ exit 1
+}
+
+set O(vtab) fts5
+set O(tok) ""
+set O(limit) 0
+set O(delete) 0
+set O(automerge) -1
+set O(crisismerge) -1
+
+if {[llength $argv]<2} usage
+set nOpt [expr {[llength $argv]-2}]
+for {set i 0} {$i < $nOpt} {incr i} {
+ set arg [lindex $argv $i]
+ switch -- [lindex $argv $i] {
+ -fts4 {
+ set O(vtab) fts4
+ }
+
+ -fts5 {
+ set O(vtab) fts5
+ }
+
+ -porter {
+ set O(tok) ", tokenize=porter"
+ }
+
+ -delete {
+ set O(delete) 1
+ }
+
+ -limit {
+ if { [incr i]>=$nOpt } usage
+ set O(limit) [lindex $argv $i]
+ }
+
+ -automerge {
+ if { [incr i]>=$nOpt } usage
+ set O(automerge) [lindex $argv $i]
+ }
+
+ -crisismerge {
+ if { [incr i]>=$nOpt } usage
+ set O(crisismerge) [lindex $argv $i]
+ }
+
+ default {
+ usage
+ }
+ }
+}
+
+set dbfile [lindex $argv end-1]
+if {$O(delete)} { file delete -force $dbfile }
+sqlite3 db $dbfile
+db func loadfile loadfile
+
+db transaction {
+ catch {
+ db eval "CREATE VIRTUAL TABLE t1 USING $O(vtab) (path, content$O(tok))"
+ }
+ if {$O(automerge)>=0} {
+ if {$O(vtab) == "fts5"} {
+ db eval { INSERT INTO t1(t1, rank) VALUES('automerge', $O(automerge)) }
+ } else {
+ db eval { INSERT INTO t1(t1) VALUES('automerge=' || $O(automerge)) }
+ }
+ }
+ if {$O(crisismerge)>=0} {
+ if {$O(vtab) == "fts5"} {
+ db eval {INSERT INTO t1(t1, rank) VALUES('crisismerge', $O(crisismerge))}
+ } else {
+ }
+ }
+ load_hierachy [lindex $argv end]
+}
+
+
+
diff --git a/main.mk b/main.mk
index 22c4f3e02..2129a6c21 100644
--- a/main.mk
+++ b/main.mk
@@ -47,6 +47,7 @@
TCCX = $(TCC) $(OPTS) -I. -I$(TOP)/src -I$(TOP)
TCCX += -I$(TOP)/ext/rtree -I$(TOP)/ext/icu -I$(TOP)/ext/fts3
TCCX += -I$(TOP)/ext/async -I$(TOP)/ext/userauth
+TCCX += -I$(TOP)/ext/fts5
# Object files for the SQLite library.
#
@@ -71,6 +72,18 @@ LIBOBJ+= vdbe.o parse.o \
vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
vdbetrace.o wal.o walker.o where.o utf.o vtab.o
+LIBOBJ += fts5.o
+LIBOBJ += fts5_aux.o
+LIBOBJ += fts5_buffer.o
+LIBOBJ += fts5_config.o
+LIBOBJ += fts5_expr.o
+LIBOBJ += fts5_hash.o
+LIBOBJ += fts5_index.o
+LIBOBJ += fts5_storage.o
+LIBOBJ += fts5_tokenize.o
+LIBOBJ += fts5_unicode2.o
+LIBOBJ += fts5parse.o
+
# All of the source code files.
@@ -220,6 +233,21 @@ SRC += \
SRC += \
$(TOP)/ext/userauth/userauth.c \
$(TOP)/ext/userauth/sqlite3userauth.h
+SRC += \
+ $(TOP)/ext/fts5/fts5.h \
+ $(TOP)/ext/fts5/fts5Int.h \
+ $(TOP)/ext/fts5/fts5_aux.c \
+ $(TOP)/ext/fts5/fts5_buffer.c \
+ $(TOP)/ext/fts5/fts5.c \
+ $(TOP)/ext/fts5/fts5_config.c \
+ $(TOP)/ext/fts5/fts5_expr.c \
+ $(TOP)/ext/fts5/fts5_hash.c \
+ $(TOP)/ext/fts5/fts5_index.c \
+ fts5parse.c fts5parse.h \
+ $(TOP)/ext/fts5/fts5_storage.c \
+ $(TOP)/ext/fts5/fts5_tokenize.c \
+ $(TOP)/ext/fts5/fts5_unicode2.c
+
# Generated source code files
#
@@ -294,7 +322,8 @@ TESTSRC += \
$(TOP)/ext/misc/spellfix.c \
$(TOP)/ext/misc/totype.c \
$(TOP)/ext/misc/wholenumber.c \
- $(TOP)/ext/misc/vfslog.c
+ $(TOP)/ext/misc/vfslog.c \
+ $(TOP)/ext/fts5/fts5_tcl.c
#TESTSRC += $(TOP)/ext/fts2/fts2_tokenizer.c
@@ -388,6 +417,10 @@ EXTHDR += \
EXTHDR += \
$(TOP)/ext/icu/sqliteicu.h
EXTHDR += \
+ $(TOP)/ext/fts5/fts5Int.h \
+ fts5parse.h \
+ $(TOP)/ext/fts5/fts5.h
+EXTHDR += \
$(TOP)/ext/userauth/sqlite3userauth.h
# This is the default Makefile target. The objects listed here
@@ -588,9 +621,52 @@ fts3_unicode2.o: $(TOP)/ext/fts3/fts3_unicode2.c $(HDR) $(EXTHDR)
fts3_write.o: $(TOP)/ext/fts3/fts3_write.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts3/fts3_write.c
+fts5.o: $(TOP)/ext/fts5/fts5.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5.c
+
rtree.o: $(TOP)/ext/rtree/rtree.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/rtree/rtree.c
+# FTS5 things
+#
+fts5_aux.o: $(TOP)/ext/fts5/fts5_aux.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_aux.c
+
+fts5_buffer.o: $(TOP)/ext/fts5/fts5_buffer.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_buffer.c
+
+fts5_config.o: $(TOP)/ext/fts5/fts5_config.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_config.c
+
+fts5_expr.o: $(TOP)/ext/fts5/fts5_expr.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_expr.c
+
+fts5_hash.o: $(TOP)/ext/fts5/fts5_hash.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_hash.c
+
+fts5_index.o: $(TOP)/ext/fts5/fts5_index.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_index.c
+
+fts5_storage.o: $(TOP)/ext/fts5/fts5_storage.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_storage.c
+
+fts5_tokenize.o: $(TOP)/ext/fts5/fts5_tokenize.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_tokenize.c
+
+fts5_unicode2.o: $(TOP)/ext/fts5/fts5_unicode2.c $(HDR) $(EXTHDR)
+ $(TCCX) -DSQLITE_CORE -c $(TOP)/ext/fts5/fts5_unicode2.c
+
+fts5parse.c: $(TOP)/ext/fts5/fts5parse.y lemon
+ cp $(TOP)/ext/fts5/fts5parse.y .
+ rm -f fts5parse.h
+ ./lemon $(OPTS) fts5parse.y
+ mv fts5parse.c fts5parse.c.orig
+ echo "#ifdef SQLITE_ENABLE_FTS5" > fts5parse.c
+ cat fts5parse.c.orig | sed 's/yy/fts5yy/g' | sed 's/YY/fts5YY/g' \
+ | sed 's/TOKEN/FTS5TOKEN/g' >> fts5parse.c
+ echo "#endif /* SQLITE_ENABLE_FTS5 */" >> fts5parse.c
+
+
userauth.o: $(TOP)/ext/userauth/userauth.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/userauth/userauth.c
@@ -706,6 +782,9 @@ wordcount$(EXE): $(TOP)/test/wordcount.c sqlite3.c
speedtest1$(EXE): $(TOP)/test/speedtest1.c sqlite3.o
$(TCC) -I. -o speedtest1$(EXE) $(TOP)/test/speedtest1.c sqlite3.o $(THREADLIB)
+loadfts: $(TOP)/tool/loadfts.c libsqlite3.a
+ $(TCC) $(TOP)/tool/loadfts.c libsqlite3.a -o loadfts $(THREADLIB)
+
# This target will fail if the SQLite amalgamation contains any exported
# symbols that do not begin with "sqlite3_". It is run as part of the
# releasetest.tcl script.
diff --git a/manifest b/manifest
index f62c28390..151780783 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C When\scompiling\sfor\sUAP,\slink\sagainst\sthe\snew\sminimal\sMSVC\sruntime.
-D 2015-04-22T01:33:53.959
+C Update\sthis\sbranch\swith\slatest\strunk\schanges.
+D 2015-04-22T09:40:35.867
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in faaf75b89840659d74501bea269c7e33414761c1
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -79,7 +79,7 @@ F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
F ext/fts3/README.tokenizers e0a8b81383ea60d0334d274fadf305ea14a8c314
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
F ext/fts3/fts3.c 1b198ddb76cd706722dacbbaeb17a2fde6fca2cc
-F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
+F ext/fts3/fts3.h 62a77d880cf06a2865052726f8325c8fabcecad7
F ext/fts3/fts3Int.h 3626655d6ba903a3919bb44e1c38e5f0f9d6be82
F ext/fts3/fts3_aux.c 5c211e17a64885faeb16b9ba7772f9d5445c2365
F ext/fts3/fts3_expr.c 40123785eaa3ebd4c45c9b23407cc44ac0c49905
@@ -102,7 +102,54 @@ F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
F ext/fts3/tool/fts3view.c 8e53d0190a7b3443764bbd32ad47be2bd852026d
F ext/fts3/unicode/CaseFolding.txt 8c678ca52ecc95e16bc7afc2dbf6fc9ffa05db8c
F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
-F ext/fts3/unicode/mkunicode.tcl a2567f9d6ad6779879a2e394c120ad8718557e65
+F ext/fts3/unicode/mkunicode.tcl 159c1194da0bc72f51b3c2eb71022568006dc5ad
+F ext/fts5/extract_api_docs.tcl 55a6d648d516f35d9a1e580ac00de27154e1904a
+F ext/fts5/fts5.c 1eb8ca073be5222c43e4eee5408764c2cbb4200b
+F ext/fts5/fts5.h 24a2cc35b5e76eec57b37ba48c12d9d2cb522b3a
+F ext/fts5/fts5Int.h 1b537736f8838df7fca10245c0f70a23cfddc7f5
+F ext/fts5/fts5_aux.c fcea18b1a2a3f95a498b52aba2983557d7678a22
+F ext/fts5/fts5_buffer.c 3ba56cc6824c9f7b1e0695159e0a9c636f6b4a23
+F ext/fts5/fts5_config.c 0847facc8914f57ea4452c43ce109200dc65e894
+F ext/fts5/fts5_expr.c 5215137efab527577d36bdf9e44bfc2ec3e1be98
+F ext/fts5/fts5_hash.c 3cb5a3d04dd2030eb0ac8d544711dfd37c0e6529
+F ext/fts5/fts5_index.c 6ae86ef3f266c303cbf4a04fe63e8da54d91cd09
+F ext/fts5/fts5_storage.c ac0f0937059c8d4f38a1f13aa5f2c2cd7edf3e0d
+F ext/fts5/fts5_tcl.c 617b6bb96545be8d9045de6967c688cd9cd15541
+F ext/fts5/fts5_tokenize.c c07f2c2f749282c1dbbf46bde1f6d7095c740b8b
+F ext/fts5/fts5_unicode2.c f74f53316377068812a1fa5a37819e6b8124631d
+F ext/fts5/fts5parse.y 777da8e5819f75c217982c79c29d014c293acac9
+F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
+F ext/fts5/test/fts5_common.tcl d9ea79fdbc9ecbb3541bf89d13ee0e03a8dc3d32
+F ext/fts5/test/fts5aa.test 91f22b3cc7b372a2903c828e907a1e52f1177b8a
+F ext/fts5/test/fts5ab.test 5da2e92a8047860b9e22b6fd3990549639d631b1
+F ext/fts5/test/fts5ac.test 8b3c2938840da8f3f6a53b1324fb03e0bac12d1e
+F ext/fts5/test/fts5ad.test 2141b0360dc4397bfed30f0b0d700fa64b44835d
+F ext/fts5/test/fts5ae.test 9175201baf8c885fc1cbb2da11a0c61fd11224db
+F ext/fts5/test/fts5af.test c2501ec2b61d6b179c305f5d2b8782ab3d4f832a
+F ext/fts5/test/fts5ag.test ec3e119b728196620a31507ef503c455a7a73505
+F ext/fts5/test/fts5ah.test d74cf8b7de5b8424f732acef69fe12122a12f2bf
+F ext/fts5/test/fts5ai.test f20e53bbf0c55bc596f1fd47f2740dae028b8f37
+F ext/fts5/test/fts5aj.test 05b569f5c16ea3098fb1984eec5cf50dbdaae5d8
+F ext/fts5/test/fts5ak.test 7b8c5df96df599293f920b7e5521ebc79f647592
+F ext/fts5/test/fts5al.test 6a5717faaf7f1e0e866360022d284903f3a4eede
+F ext/fts5/test/fts5auxdata.test c69b86092bf1a157172de5f9169731af3403179b
+F ext/fts5/test/fts5bigpl.test b1cfd00561350ab04994ba7dd9d48468e5e0ec3b
+F ext/fts5/test/fts5content.test 8dc302fccdff834d946497e9d862750ea87d4517
+F ext/fts5/test/fts5corrupt.test dbdcfe75749ed2f2eb3915cf68fd55d3dc3b058d
+F ext/fts5/test/fts5dlidx.test 710d1eaf44e6fbb09dfa73b7fd488227d8cc751a
+F ext/fts5/test/fts5ea.test 04695560a444fcc00c3c4f27783bdcfbf71f030c
+F ext/fts5/test/fts5eb.test 728a1f23f263548f5c29b29dfb851b5f2dbe723e
+F ext/fts5/test/fts5fault1.test ed71717a479bef32d05f02d9c48691011d160d4d
+F ext/fts5/test/fts5near.test 3f9f64e16cac82725d03d4e04c661090f0b3b947
+F ext/fts5/test/fts5optimize.test 0028c90a7817d3e576d1148fc8dff17d89054e54
+F ext/fts5/test/fts5porter.test 50322599823cb8080a99f0ec0c39f7d0c12bcb5e
+F ext/fts5/test/fts5prefix.test 4610dfba4460d92f23a8014874a46493f1be77b5
+F ext/fts5/test/fts5rebuild.test 2a5e98205393487b4a732c8290999af7c0b907b4
+F ext/fts5/test/fts5rowid.test a1b2a6d76648c734c1aab11ee1a619067e8d90e6
+F ext/fts5/test/fts5tokenizer.test b34ae592db66f6e89546d791ce1f905ba0b3395c
+F ext/fts5/test/fts5unicode.test 79b3e34eb29ce4929628aa514a40cb467fdabe4d
+F ext/fts5/test/fts5unicode2.test 64a5267fd6082fcb46439892ebd0cbaa5c38acee
+F ext/fts5/tool/loadfts5.tcl 1e126891d14ab85dcdb0fac7755a4cd5ba52e8b8
F ext/icu/README.txt d9fbbad0c2f647c3fdf715fc9fd64af53aedfc43
F ext/icu/icu.c d415ccf984defeb9df2c0e1afcfaa2f6dc05eacb
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
@@ -152,7 +199,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 60aba7d38b9bd73e249693344dd8405aa24ac02a
+F main.mk 0bdbbda2133675d0624b333f55e5d38a46ece577
F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
F mkopcodeh.awk d5e22023b5238985bb54a72d33e0ac71fe4f8a32
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@ -195,7 +242,7 @@ F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e
F src/lempar.c 7274c97d24bb46631e504332ccd3bd1b37841770
F src/loadext.c 86bd4e2fccd520b748cba52492ab60c4a770f660
-F src/main.c 40e333960d53f7d50ee8ce09d40431c87ea653f2
+F src/main.c 43cb916046dc9dfde761380a764190a4a4223bc6
F src/malloc.c 6a370b83d54e4bbf6f94021221c2a311cff26a18
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
F src/mem1.c abe6ee469b6c5a35c7f22bfeb9c9bac664a1c987
@@ -239,7 +286,7 @@ F src/sqliteInt.h 8abcea1295138f10ef8f7ed38db5f1b573b93ece
F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179
F src/table.c e7a09215315a978057fb42c640f890160dbcc45e
-F src/tclsqlite.c 14f1992dd6100bfeb1a3dec7e7f449e1c814b8ee
+F src/tclsqlite.c 3d89752d9a58039c2e82412a85387fe49eec6f72
F src/test1.c 90fbedce75330d48d99eadb7d5f4223e86969585
F src/test2.c 577961fe48961b2f2e5c8b56ee50c3f459d3359d
F src/test3.c 64d2afdd68feac1bb5e2ffb8226c8c639f798622
@@ -254,7 +301,7 @@ F src/test_autoext.c dea8a01a7153b9adc97bd26161e4226329546e12
F src/test_backup.c 2e6e6a081870150f20c526a2e9d0d29cda47d803
F src/test_blob.c 1f2e3e25255b731c4fcf15ee7990d06347cb6c09
F src/test_btree.c 2e9978eca99a9a4bfa8cae949efb00886860a64f
-F src/test_config.c c2d3ff6c129d50183900c7eff14158ff7e9b3f03
+F src/test_config.c ca734889a4ece295d3ed129dc532f7fefc63711d
F src/test_demovfs.c 0de72c2c89551629f58486fde5734b7d90758852
F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
F src/test_fs.c ced436e3d4b8e4681328409b8081051ce614e28f
@@ -302,7 +349,7 @@ F src/vdbeblob.c 4f2e8e075d238392df98c5e03a64342465b03f90
F src/vdbemem.c b5256445b318b0f2b3bc429028469cfbb08f19a5
F src/vdbesort.c 2e7f683464fd5db3be4beaa1ff2d39e24fcb64b8
F src/vdbetrace.c f95c2dff9041fcf07f871789c22ebb0648ea0b7c
-F src/vtab.c 5f81f8a59c1f5ddb94c918f25ed5d83578fcc633
+F src/vtab.c fcbf338e0e8a91906aaf24bda8120e9f00cb4576
F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb
F src/wal.c 753995db83247f20361a8e8a874990b21a75abd9
F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
@@ -733,7 +780,7 @@ F test/mallocI.test a88c2b9627c8506bf4703d8397420043a786cdb6
F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e
F test/mallocK.test da01dcdd316767b8356741f8d33a23a06a23def5
F test/mallocL.test 252ddc7eb4fbf75364eab17b938816085ff1fc17
-F test/malloc_common.tcl 3663f9001ce3e29bbaa9677ffe15cd468e3ec7e3
+F test/malloc_common.tcl a644f12e2da20ddfabb8bd077ec610a44113450e
F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e
F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f
F test/memdb.test fcb5297b321b562084fc79d64d5a12a1cd2b639b
@@ -794,7 +841,7 @@ F test/pagesize.test 1dd51367e752e742f58e861e65ed7390603827a0
F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d
F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
-F test/permutations.test f9cc1dd987986c9d4949211c7a4ed55ec9aecba1
+F test/permutations.test 62ff8c49738c72a70b034ecc31957bee437f76ff
F test/pragma.test be7195f0aa72bdb8a512133e9640ac40f15b57a2
F test/pragma2.test f624a496a95ee878e81e59961eade66d5c00c028
F test/pragma3.test 6f849ccffeee7e496d2f2b5e74152306c0b8757c
@@ -1210,6 +1257,7 @@ F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5
F tool/getlock.c f4c39b651370156cae979501a7b156bdba50e7ce
F tool/lemon.c b9109f59b57e7b6f101c4fe644c8361ba6dee969
F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc
+F tool/loadfts.c 76b6589ab5efcdc9cfe16d43ab5a6c2618e44bd4
F tool/logest.c eef612f8adf4d0993dafed0416064cf50d5d33c6
F tool/mkautoconfamal.sh d1a2da0e15b2ed33d60af35c7e9d483f13a8eb9f
F tool/mkkeywordhash.c dfff09dbbfaf950e89af294f48f902181b144670
@@ -1217,7 +1265,7 @@ F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
F tool/mkpragmatab.tcl 94f196c9961e0ca3513e29f57125a3197808be2d
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
F tool/mksqlite3c-noext.tcl 69bae8ce4aa52d2ff82d4a8a856bf283ec035b2e
-F tool/mksqlite3c.tcl 52a3352f7aa15f1db851e45ac3a5e2173d6fe93c
+F tool/mksqlite3c.tcl e3136f007fcdaac00c207306ef4b352ca87bf9af
F tool/mksqlite3h.tcl 44730d586c9031638cdd2eb443b801c0d2dbd9f8
F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
F tool/mkvsix.tcl 3b58b9398f91c7dbf18d49eb87cefeee9efdbce1
@@ -1252,7 +1300,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 623ddbdbf48d26dac58c593bcb9e7b184334ddfc
-R 647b9c5f348c3c64b56250400a815823
-U mistachkin
-Z 2676785d203eabdaf7fb118930189562
+P a21d60cb2ac6463c012d82d1970d90da5da2a14a 2cb945116e7a5b78741b19839899826b539d5868
+R ce8dc868a2df13b90cea4f2e6b631af2
+U dan
+Z da83630e09cb54023a2663ea2cafcf24
diff --git a/manifest.uuid b/manifest.uuid
index 936272626..98e518697 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-2cb945116e7a5b78741b19839899826b539d5868 \ No newline at end of file
+9797482ded7de985e3b20aedec5e4d81f55065c8 \ No newline at end of file
diff --git a/src/main.c b/src/main.c
index d9ee77fab..704bb38cb 100644
--- a/src/main.c
+++ b/src/main.c
@@ -19,6 +19,9 @@
#ifdef SQLITE_ENABLE_FTS3
# include "fts3.h"
#endif
+#ifdef SQLITE_ENABLE_FTS5
+int sqlite3Fts5Init(sqlite3*);
+#endif
#ifdef SQLITE_ENABLE_RTREE
# include "rtree.h"
#endif
@@ -2852,6 +2855,12 @@ static int openDatabase(
}
#endif
+#ifdef SQLITE_ENABLE_FTS5
+ if( !db->mallocFailed && rc==SQLITE_OK ){
+ rc = sqlite3Fts5Init(db);
+ }
+#endif
+
#ifdef SQLITE_ENABLE_ICU
if( !db->mallocFailed && rc==SQLITE_OK ){
rc = sqlite3IcuInit(db);
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index e38c1dd08..cf576bbef 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -3771,6 +3771,7 @@ static void init_all(Tcl_Interp *interp){
extern int Sqlitemultiplex_Init(Tcl_Interp*);
extern int SqliteSuperlock_Init(Tcl_Interp*);
extern int SqlitetestSyscall_Init(Tcl_Interp*);
+ extern int Fts5tcl_Init(Tcl_Interp *);
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
@@ -3814,6 +3815,7 @@ static void init_all(Tcl_Interp *interp){
Sqlitemultiplex_Init(interp);
SqliteSuperlock_Init(interp);
SqlitetestSyscall_Init(interp);
+ Fts5tcl_Init(interp);
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
Sqlitetestfts3_Init(interp);
diff --git a/src/test_config.c b/src/test_config.c
index 0be2a23d3..ad2f9d810 100644
--- a/src/test_config.c
+++ b/src/test_config.c
@@ -340,6 +340,12 @@ static void set_options(Tcl_Interp *interp){
Tcl_SetVar2(interp, "sqlite_options", "fts3", "0", TCL_GLOBAL_ONLY);
#endif
+#ifdef SQLITE_ENABLE_FTS5
+ Tcl_SetVar2(interp, "sqlite_options", "fts5", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "fts5", "0", TCL_GLOBAL_ONLY);
+#endif
+
#if defined(SQLITE_ENABLE_FTS3) && !defined(SQLITE_DISABLE_FTS3_UNICODE)
Tcl_SetVar2(interp, "sqlite_options", "fts3_unicode", "1", TCL_GLOBAL_ONLY);
#else
diff --git a/src/vtab.c b/src/vtab.c
index 2c6d10679..8a3d580bf 100644
--- a/src/vtab.c
+++ b/src/vtab.c
@@ -837,8 +837,10 @@ int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab){
static void callFinaliser(sqlite3 *db, int offset){
int i;
if( db->aVTrans ){
+ VTable **aVTrans = db->aVTrans;
+ db->aVTrans = 0;
for(i=0; i<db->nVTrans; i++){
- VTable *pVTab = db->aVTrans[i];
+ VTable *pVTab = aVTrans[i];
sqlite3_vtab *p = pVTab->pVtab;
if( p ){
int (*x)(sqlite3_vtab *);
@@ -848,9 +850,8 @@ static void callFinaliser(sqlite3 *db, int offset){
pVTab->iSavepoint = 0;
sqlite3VtabUnlock(pVTab);
}
- sqlite3DbFree(db, db->aVTrans);
+ sqlite3DbFree(db, aVTrans);
db->nVTrans = 0;
- db->aVTrans = 0;
}
}
diff --git a/test/malloc_common.tcl b/test/malloc_common.tcl
index b586c88d1..625dd4322 100644
--- a/test/malloc_common.tcl
+++ b/test/malloc_common.tcl
@@ -129,6 +129,8 @@ proc do_faultsim_test {name args} {
set DEFAULT(-test) ""
set DEFAULT(-install) ""
set DEFAULT(-uninstall) ""
+ set DEFAULT(-start) 1
+ set DEFAULT(-end) 0
fix_testname name
@@ -146,7 +148,8 @@ proc do_faultsim_test {name args} {
}
set testspec [list -prep $O(-prep) -body $O(-body) \
- -test $O(-test) -install $O(-install) -uninstall $O(-uninstall)
+ -test $O(-test) -install $O(-install) -uninstall $O(-uninstall) \
+ -start $O(-start) -end $O(-end)
]
foreach f [lsort -unique $faultlist] {
eval do_one_faultsim_test "$name-$f" $FAULTSIM($f) $testspec
@@ -318,6 +321,8 @@ proc faultsim_test_result_int {args} {
#
# -test Script to execute after -body.
#
+# -start Index of first fault to inject (default 1)
+#
proc do_one_faultsim_test {testname args} {
set DEFAULT(-injectstart) "expr"
@@ -330,6 +335,8 @@ proc do_one_faultsim_test {testname args} {
set DEFAULT(-test) ""
set DEFAULT(-install) ""
set DEFAULT(-uninstall) ""
+ set DEFAULT(-start) 1
+ set DEFAULT(-end) 0
array set O [array get DEFAULT]
array set O $args
@@ -346,7 +353,10 @@ proc do_one_faultsim_test {testname args} {
eval $O(-install)
set stop 0
- for {set iFail 1} {!$stop} {incr iFail} {
+ for {set iFail $O(-start)} \
+ {!$stop && ($O(-end)==0 || $iFail<=$O(-end))} \
+ {incr iFail} \
+ {
# Evaluate the -prep script.
#
diff --git a/test/permutations.test b/test/permutations.test
index 44f62e806..2ee3953d5 100644
--- a/test/permutations.test
+++ b/test/permutations.test
@@ -238,6 +238,10 @@ test_suite "fts3" -prefix "" -description {
fts4growth.test fts4growth2.test
}
+test_suite "fts5" -prefix "" -description {
+ All FTS5 tests.
+} -files [glob -nocomplain $::testdir/../ext/fts5/test/*.test]
+
test_suite "nofaultsim" -prefix "" -description {
"Very" quick test suite. Runs in less than 5 minutes on a workstation.
This test suite is the same as the "quick" tests, except that some files
diff --git a/tool/loadfts.c b/tool/loadfts.c
new file mode 100644
index 000000000..5b2ed5dc6
--- /dev/null
+++ b/tool/loadfts.c
@@ -0,0 +1,238 @@
+/*
+** 2013-06-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.
+**
+*************************************************************************
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include "sqlite3.h"
+
+/*
+** Implementation of the "readtext(X)" SQL function. The entire content
+** of the file named X is read and returned as a TEXT value. It is assumed
+** the file contains UTF-8 text. NULL is returned if the file does not
+** exist or is unreadable.
+*/
+static void readfileFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ const char *zName;
+ FILE *in;
+ long nIn;
+ void *pBuf;
+
+ zName = (const char*)sqlite3_value_text(argv[0]);
+ if( zName==0 ) return;
+ in = fopen(zName, "rb");
+ if( in==0 ) return;
+ fseek(in, 0, SEEK_END);
+ nIn = ftell(in);
+ rewind(in);
+ pBuf = sqlite3_malloc( nIn );
+ if( pBuf && 1==fread(pBuf, nIn, 1, in) ){
+ sqlite3_result_text(context, pBuf, nIn, sqlite3_free);
+ }else{
+ sqlite3_free(pBuf);
+ }
+ fclose(in);
+}
+
+/*
+** Print usage text for this program and exit.
+*/
+static void showHelp(const char *zArgv0){
+ printf("\n"
+"Usage: %s SWITCHES... DB\n"
+"\n"
+" This program opens the database named on the command line and attempts to\n"
+" create an FTS table named \"fts\" with a single column. If successful, it\n"
+" recursively traverses the directory named by the -dir option and inserts\n"
+" the contents of each file into the fts table. All files are assumed to\n"
+" contain UTF-8 text.\n"
+"\n"
+"Switches are:\n"
+" -fts [345] FTS version to use (default=5)\n"
+" -idx [01] Create a mapping from filename to rowid (default=0)\n"
+" -dir <path> Root of directory tree to load data from (default=.)\n"
+" -trans <integer> Number of inserts per transaction (default=1)\n"
+, zArgv0
+);
+ exit(1);
+}
+
+/*
+** Exit with a message based on the argument and the current value of errno.
+*/
+static void error_out(const char *zText){
+ fprintf(stderr, "%s: %s\n", zText, strerror(errno));
+ exit(-1);
+}
+
+/*
+** Exit with a message based on the first argument and the error message
+** currently stored in database handle db.
+*/
+static void sqlite_error_out(const char *zText, sqlite3 *db){
+ fprintf(stderr, "%s: %s\n", zText, sqlite3_errmsg(db));
+ exit(-1);
+}
+
+/*
+** Context object for visit_file().
+*/
+typedef struct VisitContext VisitContext;
+struct VisitContext {
+ int nRowPerTrans;
+ sqlite3 *db; /* Database handle */
+ sqlite3_stmt *pInsert; /* INSERT INTO fts VALUES(readtext(:1)) */
+};
+
+/*
+** Callback used with traverse(). The first argument points to an object
+** of type VisitContext. This function inserts the contents of the text
+** file zPath into the FTS table.
+*/
+void visit_file(void *pCtx, const char *zPath){
+ int rc;
+ VisitContext *p = (VisitContext*)pCtx;
+ /* printf("%s\n", zPath); */
+ sqlite3_bind_text(p->pInsert, 1, zPath, -1, SQLITE_STATIC);
+ sqlite3_step(p->pInsert);
+ rc = sqlite3_reset(p->pInsert);
+ if( rc!=SQLITE_OK ){
+ sqlite_error_out("insert", p->db);
+ }else if( p->nRowPerTrans>0
+ && (sqlite3_last_insert_rowid(p->db) % p->nRowPerTrans)==0
+ ){
+ sqlite3_exec(p->db, "COMMIT ; BEGIN", 0, 0, 0);
+ }
+}
+
+/*
+** Recursively traverse directory zDir. For each file that is not a
+** directory, invoke the supplied callback with its path.
+*/
+static void traverse(
+ const char *zDir, /* Directory to traverse */
+ void *pCtx, /* First argument passed to callback */
+ void (*xCallback)(void*, const char *zPath)
+){
+ DIR *d;
+ struct dirent *e;
+
+ d = opendir(zDir);
+ if( d==0 ) error_out("opendir()");
+
+ for(e=readdir(d); e; e=readdir(d)){
+ if( strcmp(e->d_name, ".")==0 || strcmp(e->d_name, "..")==0 ) continue;
+ char *zPath = sqlite3_mprintf("%s/%s", zDir, e->d_name);
+ if (e->d_type & DT_DIR) {
+ traverse(zPath, pCtx, xCallback);
+ }else{
+ xCallback(pCtx, zPath);
+ }
+ sqlite3_free(zPath);
+ }
+
+ closedir(d);
+}
+
+int main(int argc, char **argv){
+ int iFts = 5; /* Value of -fts option */
+ int bMap = 0; /* True to create mapping table */
+ const char *zDir = "."; /* Directory to scan */
+ int i;
+ int rc;
+ int nRowPerTrans = 0;
+ sqlite3 *db;
+ char *zSql;
+ VisitContext sCtx;
+
+ int nCmd = 0;
+ char **aCmd = 0;
+
+ if( argc % 2 ) showHelp(argv[0]);
+
+ for(i=1; i<(argc-1); i+=2){
+ char *zOpt = argv[i];
+ char *zArg = argv[i+1];
+ if( strcmp(zOpt, "-fts")==0 ){
+ iFts = atoi(zArg);
+ if( iFts!=3 && iFts!=4 && iFts!= 5) showHelp(argv[0]);
+ }
+ else if( strcmp(zOpt, "-trans")==0 ){
+ nRowPerTrans = atoi(zArg);
+ }
+ else if( strcmp(zOpt, "-idx")==0 ){
+ bMap = atoi(zArg);
+ if( bMap!=0 && bMap!=1 ) showHelp(argv[0]);
+ }
+ else if( strcmp(zOpt, "-dir")==0 ){
+ zDir = zArg;
+ }
+ else if( strcmp(zOpt, "-special")==0 ){
+ nCmd++;
+ aCmd = sqlite3_realloc(aCmd, sizeof(char*) * nCmd);
+ aCmd[nCmd-1] = zArg;
+ }
+ else{
+ showHelp(argv[0]);
+ }
+ }
+
+ /* Open the database file */
+ rc = sqlite3_open(argv[argc-1], &db);
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_open()", db);
+
+ rc = sqlite3_create_function(db, "readtext", 1, SQLITE_UTF8, 0,
+ readfileFunc, 0, 0);
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_create_function()", db);
+
+ /* Create the FTS table */
+ zSql = sqlite3_mprintf("CREATE VIRTUAL TABLE fts USING fts%d(content)", iFts);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_exec(1)", db);
+ sqlite3_free(zSql);
+
+ for(i=0; i<nCmd; i++){
+ zSql = sqlite3_mprintf("INSERT INTO fts(fts) VALUES(%Q)", aCmd[i]);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_exec(1)", db);
+ sqlite3_free(zSql);
+ }
+
+ /* Compile the INSERT statement to write data to the FTS table. */
+ memset(&sCtx, 0, sizeof(VisitContext));
+ sCtx.db = db;
+ sCtx.nRowPerTrans = nRowPerTrans;
+ rc = sqlite3_prepare_v2(db,
+ "INSERT INTO fts VALUES(readtext(?))", -1, &sCtx.pInsert, 0
+ );
+ if( rc!=SQLITE_OK ) sqlite_error_out("sqlite3_prepare_v2(1)", db);
+
+ /* Load all files in the directory hierarchy into the FTS table. */
+ if( sCtx.nRowPerTrans>0 ) sqlite3_exec(db, "BEGIN", 0, 0, 0);
+ traverse(zDir, (void*)&sCtx, visit_file);
+ if( sCtx.nRowPerTrans>0 ) sqlite3_exec(db, "COMMIT", 0, 0, 0);
+
+ /* Clean up and exit. */
+ sqlite3_finalize(sCtx.pInsert);
+ sqlite3_close(db);
+ sqlite3_free(aCmd);
+ return 0;
+}
diff --git a/tool/mksqlite3c.tcl b/tool/mksqlite3c.tcl
index 136155089..ca0eb0259 100644
--- a/tool/mksqlite3c.tcl
+++ b/tool/mksqlite3c.tcl
@@ -94,6 +94,9 @@ foreach hdr {
fts3Int.h
fts3_hash.h
fts3_tokenizer.h
+ fts5.h
+ fts5Int.h
+ fts5parse.h
hash.h
hwtime.h
keywordhash.h
@@ -363,6 +366,18 @@ foreach file {
fts3_unicode.c
fts3_unicode2.c
+ fts5_aux.c
+ fts5_buffer.c
+ fts5.c
+ fts5_config.c
+ fts5_expr.c
+ fts5_hash.c
+ fts5_index.c
+ fts5parse.c
+ fts5_storage.c
+ fts5_tokenize.c
+ fts5_unicode2.c
+
rtree.c
icu.c
fts3_icu.c