summaryrefslogtreecommitdiffstats
path: root/ext/rtree
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:28:19 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:28:19 +0000
commit18657a960e125336f704ea058e25c27bd3900dcb (patch)
tree17b438b680ed45a996d7b59951e6aa34023783f2 /ext/rtree
parentInitial commit. (diff)
downloadsqlite3-upstream.tar.xz
sqlite3-upstream.zip
Adding upstream version 3.40.1.upstream/3.40.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--ext/rtree/README120
-rw-r--r--ext/rtree/geopoly.c1814
-rw-r--r--ext/rtree/rtree.c4582
-rw-r--r--ext/rtree/rtree.h30
-rw-r--r--ext/rtree/rtree1.test759
-rw-r--r--ext/rtree/rtree2.test147
-rw-r--r--ext/rtree/rtree3.test270
-rw-r--r--ext/rtree/rtree4.test254
-rw-r--r--ext/rtree/rtree5.test83
-rw-r--r--ext/rtree/rtree6.test181
-rw-r--r--ext/rtree/rtree7.test73
-rw-r--r--ext/rtree/rtree8.test209
-rw-r--r--ext/rtree/rtree9.test129
-rw-r--r--ext/rtree/rtreeA.test262
-rw-r--r--ext/rtree/rtreeB.test50
-rw-r--r--ext/rtree/rtreeC.test378
-rw-r--r--ext/rtree/rtreeD.test55
-rw-r--r--ext/rtree/rtreeE.test143
-rw-r--r--ext/rtree/rtreeF.test84
-rw-r--r--ext/rtree/rtreeG.test69
-rw-r--r--ext/rtree/rtreeH.test103
-rw-r--r--ext/rtree/rtreeI.test74
-rw-r--r--ext/rtree/rtree_perf.tcl74
-rw-r--r--ext/rtree/rtree_util.tcl197
-rw-r--r--ext/rtree/rtreecheck.test160
-rw-r--r--ext/rtree/rtreecirc.test66
-rw-r--r--ext/rtree/rtreeconnect.test56
-rw-r--r--ext/rtree/rtreedoc.test1583
-rw-r--r--ext/rtree/rtreedoc2.test346
-rw-r--r--ext/rtree/rtreedoc3.test292
-rw-r--r--ext/rtree/rtreefuzz001.test1211
-rw-r--r--ext/rtree/sqlite3rtree.h117
-rw-r--r--ext/rtree/test_rtreedoc.c348
-rw-r--r--ext/rtree/tkt3363.test50
-rw-r--r--ext/rtree/util/randomshape.tcl87
-rw-r--r--ext/rtree/viewrtree.tcl188
-rw-r--r--ext/rtree/visual01.txt602
37 files changed, 15246 insertions, 0 deletions
diff --git a/ext/rtree/README b/ext/rtree/README
new file mode 100644
index 0000000..3736f45
--- /dev/null
+++ b/ext/rtree/README
@@ -0,0 +1,120 @@
+
+This directory contains an SQLite extension that implements a virtual
+table type that allows users to create, query and manipulate r-tree[1]
+data structures inside of SQLite databases. Users create, populate
+and query r-tree structures using ordinary SQL statements.
+
+ 1. SQL Interface
+
+ 1.1 Table Creation
+ 1.2 Data Manipulation
+ 1.3 Data Querying
+ 1.4 Introspection and Analysis
+
+ 2. Compilation and Deployment
+
+ 3. References
+
+
+1. SQL INTERFACE
+
+ 1.1 Table Creation.
+
+ All r-tree virtual tables have an odd number of columns between
+ 3 and 11. Unlike regular SQLite tables, r-tree tables are strongly
+ typed.
+
+ The leftmost column is always the pimary key and contains 64-bit
+ integer values. Each subsequent column contains a 32-bit real
+ value. For each pair of real values, the first (leftmost) must be
+ less than or equal to the second. R-tree tables may be
+ constructed using the following syntax:
+
+ CREATE VIRTUAL TABLE <name> USING rtree(<column-names>)
+
+ For example:
+
+ CREATE VIRTUAL TABLE boxes USING rtree(boxno, xmin, xmax, ymin, ymax);
+ INSERT INTO boxes VALUES(1, 1.0, 3.0, 2.0, 4.0);
+
+ Constructing a virtual r-tree table <name> creates the following three
+ real tables in the database to store the data structure:
+
+ <name>_node
+ <name>_rowid
+ <name>_parent
+
+ Dropping or modifying the contents of these tables directly will
+ corrupt the r-tree structure. To delete an r-tree from a database,
+ use a regular DROP TABLE statement:
+
+ DROP TABLE <name>;
+
+ Dropping the main r-tree table automatically drops the automatically
+ created tables.
+
+ 1.2 Data Manipulation (INSERT, UPDATE, DELETE).
+
+ The usual INSERT, UPDATE or DELETE syntax is used to manipulate data
+ stored in an r-tree table. Please note the following:
+
+ * Inserting a NULL value into the primary key column has the
+ same effect as inserting a NULL into an INTEGER PRIMARY KEY
+ column of a regular table. The system automatically assigns
+ an unused integer key value to the new record. Usually, this
+ is one greater than the largest primary key value currently
+ present in the table.
+
+ * Attempting to insert a duplicate primary key value fails with
+ an SQLITE_CONSTRAINT error.
+
+ * Attempting to insert or modify a record such that the value
+ stored in the (N*2)th column is greater than that stored in
+ the (N*2+1)th column fails with an SQLITE_CONSTRAINT error.
+
+ * When a record is inserted, values are always converted to
+ the required type (64-bit integer or 32-bit real) as if they
+ were part of an SQL CAST expression. Non-numeric strings are
+ converted to zero.
+
+ 1.3 Queries.
+
+ R-tree tables may be queried using all of the same SQL syntax supported
+ by regular tables. However, some query patterns are more efficient
+ than others.
+
+ R-trees support fast lookup by primary key value (O(logN), like
+ regular tables).
+
+ Any combination of equality and range (<, <=, >, >=) constraints
+ on spatial data columns may be used to optimize other queries. This
+ is the key advantage to using r-tree tables instead of creating
+ indices on regular tables.
+
+ 1.4 Introspection and Analysis.
+
+ TODO: Describe rtreenode() and rtreedepth() functions.
+
+
+2. COMPILATION AND USAGE
+
+ The easiest way to compile and use the RTREE extension is to build
+ and use it as a dynamically loadable SQLite extension. To do this
+ using gcc on *nix:
+
+ gcc -shared rtree.c -o libSqliteRtree.so
+
+ You may need to add "-I" flags so that gcc can find sqlite3ext.h
+ and sqlite3.h. The resulting shared lib, libSqliteRtree.so, may be
+ loaded into sqlite in the same way as any other dynamicly loadable
+ extension.
+
+
+3. REFERENCES
+
+ [1] Atonin Guttman, "R-trees - A Dynamic Index Structure For Spatial
+ Searching", University of California Berkeley, 1984.
+
+ [2] Norbert Beckmann, Hans-Peter Kriegel, Ralf Schneider, Bernhard Seeger,
+ "The R*-tree: An Efficient and Robust Access Method for Points and
+ Rectangles", Universitaet Bremen, 1990.
diff --git a/ext/rtree/geopoly.c b/ext/rtree/geopoly.c
new file mode 100644
index 0000000..7b41e79
--- /dev/null
+++ b/ext/rtree/geopoly.c
@@ -0,0 +1,1814 @@
+/*
+** 2018-05-25
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This file implements an alternative R-Tree virtual table that
+** uses polygons to express the boundaries of 2-dimensional objects.
+**
+** This file is #include-ed onto the end of "rtree.c" so that it has
+** access to all of the R-Tree internals.
+*/
+#include <stdlib.h>
+
+/* Enable -DGEOPOLY_ENABLE_DEBUG for debugging facilities */
+#ifdef GEOPOLY_ENABLE_DEBUG
+ static int geo_debug = 0;
+# define GEODEBUG(X) if(geo_debug)printf X
+#else
+# define GEODEBUG(X)
+#endif
+
+/* Character class routines */
+#ifdef sqlite3Isdigit
+ /* Use the SQLite core versions if this routine is part of the
+ ** SQLite amalgamation */
+# define safe_isdigit(x) sqlite3Isdigit(x)
+# define safe_isalnum(x) sqlite3Isalnum(x)
+# define safe_isxdigit(x) sqlite3Isxdigit(x)
+#else
+ /* Use the standard library for separate compilation */
+#include <ctype.h> /* amalgamator: keep */
+# define safe_isdigit(x) isdigit((unsigned char)(x))
+# define safe_isalnum(x) isalnum((unsigned char)(x))
+# define safe_isxdigit(x) isxdigit((unsigned char)(x))
+#endif
+
+#ifndef JSON_NULL /* The following stuff repeats things found in json1 */
+/*
+** Growing our own isspace() routine this way is twice as fast as
+** the library isspace() function.
+*/
+static const char geopolyIsSpace[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+#define fast_isspace(x) (geopolyIsSpace[(unsigned char)x])
+#endif /* JSON NULL - back to original code */
+
+/* Compiler and version */
+#ifndef GCC_VERSION
+#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__)
+#else
+# define GCC_VERSION 0
+#endif
+#endif
+#ifndef MSVC_VERSION
+#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define MSVC_VERSION _MSC_VER
+#else
+# define MSVC_VERSION 0
+#endif
+#endif
+
+/* Datatype for coordinates
+*/
+typedef float GeoCoord;
+
+/*
+** Internal representation of a polygon.
+**
+** The polygon consists of a sequence of vertexes. There is a line
+** segment between each pair of vertexes, and one final segment from
+** the last vertex back to the first. (This differs from the GeoJSON
+** standard in which the final vertex is a repeat of the first.)
+**
+** The polygon follows the right-hand rule. The area to the right of
+** each segment is "outside" and the area to the left is "inside".
+**
+** The on-disk representation consists of a 4-byte header followed by
+** the values. The 4-byte header is:
+**
+** encoding (1 byte) 0=big-endian, 1=little-endian
+** nvertex (3 bytes) Number of vertexes as a big-endian integer
+**
+** Enough space is allocated for 4 coordinates, to work around over-zealous
+** warnings coming from some compiler (notably, clang). In reality, the size
+** of each GeoPoly memory allocate is adjusted as necessary so that the
+** GeoPoly.a[] array at the end is the appropriate size.
+*/
+typedef struct GeoPoly GeoPoly;
+struct GeoPoly {
+ int nVertex; /* Number of vertexes */
+ unsigned char hdr[4]; /* Header for on-disk representation */
+ GeoCoord a[8]; /* 2*nVertex values. X (longitude) first, then Y */
+};
+
+/* The size of a memory allocation needed for a GeoPoly object sufficient
+** to hold N coordinate pairs.
+*/
+#define GEOPOLY_SZ(N) (sizeof(GeoPoly) + sizeof(GeoCoord)*2*((N)-4))
+
+/* Macros to access coordinates of a GeoPoly.
+** We have to use these macros, rather than just say p->a[i] in order
+** to silence (incorrect) UBSAN warnings if the array index is too large.
+*/
+#define GeoX(P,I) (((GeoCoord*)(P)->a)[(I)*2])
+#define GeoY(P,I) (((GeoCoord*)(P)->a)[(I)*2+1])
+
+
+/*
+** State of a parse of a GeoJSON input.
+*/
+typedef struct GeoParse GeoParse;
+struct GeoParse {
+ const unsigned char *z; /* Unparsed input */
+ int nVertex; /* Number of vertexes in a[] */
+ int nAlloc; /* Space allocated to a[] */
+ int nErr; /* Number of errors encountered */
+ GeoCoord *a; /* Array of vertexes. From sqlite3_malloc64() */
+};
+
+/* Do a 4-byte byte swap */
+static void geopolySwab32(unsigned char *a){
+ unsigned char t = a[0];
+ a[0] = a[3];
+ a[3] = t;
+ t = a[1];
+ a[1] = a[2];
+ a[2] = t;
+}
+
+/* Skip whitespace. Return the next non-whitespace character. */
+static char geopolySkipSpace(GeoParse *p){
+ while( fast_isspace(p->z[0]) ) p->z++;
+ return p->z[0];
+}
+
+/* Parse out a number. Write the value into *pVal if pVal!=0.
+** return non-zero on success and zero if the next token is not a number.
+*/
+static int geopolyParseNumber(GeoParse *p, GeoCoord *pVal){
+ char c = geopolySkipSpace(p);
+ const unsigned char *z = p->z;
+ int j = 0;
+ int seenDP = 0;
+ int seenE = 0;
+ if( c=='-' ){
+ j = 1;
+ c = z[j];
+ }
+ if( c=='0' && z[j+1]>='0' && z[j+1]<='9' ) return 0;
+ for(;; j++){
+ c = z[j];
+ if( safe_isdigit(c) ) continue;
+ if( c=='.' ){
+ if( z[j-1]=='-' ) return 0;
+ if( seenDP ) return 0;
+ seenDP = 1;
+ continue;
+ }
+ if( c=='e' || c=='E' ){
+ if( z[j-1]<'0' ) return 0;
+ if( seenE ) return -1;
+ seenDP = seenE = 1;
+ c = z[j+1];
+ if( c=='+' || c=='-' ){
+ j++;
+ c = z[j+1];
+ }
+ if( c<'0' || c>'9' ) return 0;
+ continue;
+ }
+ break;
+ }
+ if( z[j-1]<'0' ) return 0;
+ if( pVal ){
+#ifdef SQLITE_AMALGAMATION
+ /* The sqlite3AtoF() routine is much much faster than atof(), if it
+ ** is available */
+ double r;
+ (void)sqlite3AtoF((const char*)p->z, &r, j, SQLITE_UTF8);
+ *pVal = r;
+#else
+ *pVal = (GeoCoord)atof((const char*)p->z);
+#endif
+ }
+ p->z += j;
+ return 1;
+}
+
+/*
+** If the input is a well-formed JSON array of coordinates with at least
+** four coordinates and where each coordinate is itself a two-value array,
+** then convert the JSON into a GeoPoly object and return a pointer to
+** that object.
+**
+** If any error occurs, return NULL.
+*/
+static GeoPoly *geopolyParseJson(const unsigned char *z, int *pRc){
+ GeoParse s;
+ int rc = SQLITE_OK;
+ memset(&s, 0, sizeof(s));
+ s.z = z;
+ if( geopolySkipSpace(&s)=='[' ){
+ s.z++;
+ while( geopolySkipSpace(&s)=='[' ){
+ int ii = 0;
+ char c;
+ s.z++;
+ if( s.nVertex>=s.nAlloc ){
+ GeoCoord *aNew;
+ s.nAlloc = s.nAlloc*2 + 16;
+ aNew = sqlite3_realloc64(s.a, s.nAlloc*sizeof(GeoCoord)*2 );
+ if( aNew==0 ){
+ rc = SQLITE_NOMEM;
+ s.nErr++;
+ break;
+ }
+ s.a = aNew;
+ }
+ while( geopolyParseNumber(&s, ii<=1 ? &s.a[s.nVertex*2+ii] : 0) ){
+ ii++;
+ if( ii==2 ) s.nVertex++;
+ c = geopolySkipSpace(&s);
+ s.z++;
+ if( c==',' ) continue;
+ if( c==']' && ii>=2 ) break;
+ s.nErr++;
+ rc = SQLITE_ERROR;
+ goto parse_json_err;
+ }
+ if( geopolySkipSpace(&s)==',' ){
+ s.z++;
+ continue;
+ }
+ break;
+ }
+ if( geopolySkipSpace(&s)==']'
+ && s.nVertex>=4
+ && s.a[0]==s.a[s.nVertex*2-2]
+ && s.a[1]==s.a[s.nVertex*2-1]
+ && (s.z++, geopolySkipSpace(&s)==0)
+ ){
+ GeoPoly *pOut;
+ int x = 1;
+ s.nVertex--; /* Remove the redundant vertex at the end */
+ pOut = sqlite3_malloc64( GEOPOLY_SZ((sqlite3_int64)s.nVertex) );
+ x = 1;
+ if( pOut==0 ) goto parse_json_err;
+ pOut->nVertex = s.nVertex;
+ memcpy(pOut->a, s.a, s.nVertex*2*sizeof(GeoCoord));
+ pOut->hdr[0] = *(unsigned char*)&x;
+ pOut->hdr[1] = (s.nVertex>>16)&0xff;
+ pOut->hdr[2] = (s.nVertex>>8)&0xff;
+ pOut->hdr[3] = s.nVertex&0xff;
+ sqlite3_free(s.a);
+ if( pRc ) *pRc = SQLITE_OK;
+ return pOut;
+ }else{
+ s.nErr++;
+ rc = SQLITE_ERROR;
+ }
+ }
+parse_json_err:
+ if( pRc ) *pRc = rc;
+ sqlite3_free(s.a);
+ return 0;
+}
+
+/*
+** Given a function parameter, try to interpret it as a polygon, either
+** in the binary format or JSON text. Compute a GeoPoly object and
+** return a pointer to that object. Or if the input is not a well-formed
+** polygon, put an error message in sqlite3_context and return NULL.
+*/
+static GeoPoly *geopolyFuncParam(
+ sqlite3_context *pCtx, /* Context for error messages */
+ sqlite3_value *pVal, /* The value to decode */
+ int *pRc /* Write error here */
+){
+ GeoPoly *p = 0;
+ int nByte;
+ testcase( pCtx==0 );
+ if( sqlite3_value_type(pVal)==SQLITE_BLOB
+ && (nByte = sqlite3_value_bytes(pVal))>=(4+6*sizeof(GeoCoord))
+ ){
+ const unsigned char *a = sqlite3_value_blob(pVal);
+ int nVertex;
+ if( a==0 ){
+ if( pCtx ) sqlite3_result_error_nomem(pCtx);
+ return 0;
+ }
+ nVertex = (a[1]<<16) + (a[2]<<8) + a[3];
+ if( (a[0]==0 || a[0]==1)
+ && (nVertex*2*sizeof(GeoCoord) + 4)==(unsigned int)nByte
+ ){
+ p = sqlite3_malloc64( sizeof(*p) + (nVertex-1)*2*sizeof(GeoCoord) );
+ if( p==0 ){
+ if( pRc ) *pRc = SQLITE_NOMEM;
+ if( pCtx ) sqlite3_result_error_nomem(pCtx);
+ }else{
+ int x = 1;
+ p->nVertex = nVertex;
+ memcpy(p->hdr, a, nByte);
+ if( a[0] != *(unsigned char*)&x ){
+ int ii;
+ for(ii=0; ii<nVertex; ii++){
+ geopolySwab32((unsigned char*)&GeoX(p,ii));
+ geopolySwab32((unsigned char*)&GeoY(p,ii));
+ }
+ p->hdr[0] ^= 1;
+ }
+ }
+ }
+ if( pRc ) *pRc = SQLITE_OK;
+ return p;
+ }else if( sqlite3_value_type(pVal)==SQLITE_TEXT ){
+ const unsigned char *zJson = sqlite3_value_text(pVal);
+ if( zJson==0 ){
+ if( pRc ) *pRc = SQLITE_NOMEM;
+ return 0;
+ }
+ return geopolyParseJson(zJson, pRc);
+ }else{
+ if( pRc ) *pRc = SQLITE_ERROR;
+ return 0;
+ }
+}
+
+/*
+** Implementation of the geopoly_blob(X) function.
+**
+** If the input is a well-formed Geopoly BLOB or JSON string
+** then return the BLOB representation of the polygon. Otherwise
+** return NULL.
+*/
+static void geopolyBlobFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ sqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** SQL function: geopoly_json(X)
+**
+** Interpret X as a polygon and render it as a JSON array
+** of coordinates. Or, if X is not a valid polygon, return NULL.
+*/
+static void geopolyJsonFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ sqlite3_str *x = sqlite3_str_new(db);
+ int i;
+ sqlite3_str_append(x, "[", 1);
+ for(i=0; i<p->nVertex; i++){
+ sqlite3_str_appendf(x, "[%!g,%!g],", GeoX(p,i), GeoY(p,i));
+ }
+ sqlite3_str_appendf(x, "[%!g,%!g]]", GeoX(p,0), GeoY(p,0));
+ sqlite3_result_text(context, sqlite3_str_finish(x), -1, sqlite3_free);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** SQL function: geopoly_svg(X, ....)
+**
+** Interpret X as a polygon and render it as a SVG <polyline>.
+** Additional arguments are added as attributes to the <polyline>.
+*/
+static void geopolySvgFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p;
+ if( argc<1 ) return;
+ p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ sqlite3 *db = sqlite3_context_db_handle(context);
+ sqlite3_str *x = sqlite3_str_new(db);
+ int i;
+ char cSep = '\'';
+ sqlite3_str_appendf(x, "<polyline points=");
+ for(i=0; i<p->nVertex; i++){
+ sqlite3_str_appendf(x, "%c%g,%g", cSep, GeoX(p,i), GeoY(p,i));
+ cSep = ' ';
+ }
+ sqlite3_str_appendf(x, " %g,%g'", GeoX(p,0), GeoY(p,0));
+ for(i=1; i<argc; i++){
+ const char *z = (const char*)sqlite3_value_text(argv[i]);
+ if( z && z[0] ){
+ sqlite3_str_appendf(x, " %s", z);
+ }
+ }
+ sqlite3_str_appendf(x, "></polyline>");
+ sqlite3_result_text(context, sqlite3_str_finish(x), -1, sqlite3_free);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** SQL Function: geopoly_xform(poly, A, B, C, D, E, F)
+**
+** Transform and/or translate a polygon as follows:
+**
+** x1 = A*x0 + B*y0 + E
+** y1 = C*x0 + D*y0 + F
+**
+** For a translation:
+**
+** geopoly_xform(poly, 1, 0, 0, 1, x-offset, y-offset)
+**
+** Rotate by R around the point (0,0):
+**
+** geopoly_xform(poly, cos(R), sin(R), -sin(R), cos(R), 0, 0)
+*/
+static void geopolyXformFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ double A = sqlite3_value_double(argv[1]);
+ double B = sqlite3_value_double(argv[2]);
+ double C = sqlite3_value_double(argv[3]);
+ double D = sqlite3_value_double(argv[4]);
+ double E = sqlite3_value_double(argv[5]);
+ double F = sqlite3_value_double(argv[6]);
+ GeoCoord x1, y1, x0, y0;
+ int ii;
+ if( p ){
+ for(ii=0; ii<p->nVertex; ii++){
+ x0 = GeoX(p,ii);
+ y0 = GeoY(p,ii);
+ x1 = (GeoCoord)(A*x0 + B*y0 + E);
+ y1 = (GeoCoord)(C*x0 + D*y0 + F);
+ GeoX(p,ii) = x1;
+ GeoY(p,ii) = y1;
+ }
+ sqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Compute the area enclosed by the polygon.
+**
+** This routine can also be used to detect polygons that rotate in
+** the wrong direction. Polygons are suppose to be counter-clockwise (CCW).
+** This routine returns a negative value for clockwise (CW) polygons.
+*/
+static double geopolyArea(GeoPoly *p){
+ double rArea = 0.0;
+ int ii;
+ for(ii=0; ii<p->nVertex-1; ii++){
+ rArea += (GeoX(p,ii) - GeoX(p,ii+1)) /* (x0 - x1) */
+ * (GeoY(p,ii) + GeoY(p,ii+1)) /* (y0 + y1) */
+ * 0.5;
+ }
+ rArea += (GeoX(p,ii) - GeoX(p,0)) /* (xN - x0) */
+ * (GeoY(p,ii) + GeoY(p,0)) /* (yN + y0) */
+ * 0.5;
+ return rArea;
+}
+
+/*
+** Implementation of the geopoly_area(X) function.
+**
+** If the input is a well-formed Geopoly BLOB then return the area
+** enclosed by the polygon. If the polygon circulates clockwise instead
+** of counterclockwise (as it should) then return the negative of the
+** enclosed area. Otherwise return NULL.
+*/
+static void geopolyAreaFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ sqlite3_result_double(context, geopolyArea(p));
+ sqlite3_free(p);
+ }
+}
+
+/*
+** Implementation of the geopoly_ccw(X) function.
+**
+** If the rotation of polygon X is clockwise (incorrect) instead of
+** counter-clockwise (the correct winding order according to RFC7946)
+** then reverse the order of the vertexes in polygon X.
+**
+** In other words, this routine returns a CCW polygon regardless of the
+** winding order of its input.
+**
+** Use this routine to sanitize historical inputs that that sometimes
+** contain polygons that wind in the wrong direction.
+*/
+static void geopolyCcwFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p = geopolyFuncParam(context, argv[0], 0);
+ if( p ){
+ if( geopolyArea(p)<0.0 ){
+ int ii, jj;
+ for(ii=1, jj=p->nVertex-1; ii<jj; ii++, jj--){
+ GeoCoord t = GeoX(p,ii);
+ GeoX(p,ii) = GeoX(p,jj);
+ GeoX(p,jj) = t;
+ t = GeoY(p,ii);
+ GeoY(p,ii) = GeoY(p,jj);
+ GeoY(p,jj) = t;
+ }
+ }
+ sqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ sqlite3_free(p);
+ }
+}
+
+#define GEOPOLY_PI 3.1415926535897932385
+
+/* Fast approximation for sine(X) for X between -0.5*pi and 2*pi
+*/
+static double geopolySine(double r){
+ assert( r>=-0.5*GEOPOLY_PI && r<=2.0*GEOPOLY_PI );
+ if( r>=1.5*GEOPOLY_PI ){
+ r -= 2.0*GEOPOLY_PI;
+ }
+ if( r>=0.5*GEOPOLY_PI ){
+ return -geopolySine(r-GEOPOLY_PI);
+ }else{
+ double r2 = r*r;
+ double r3 = r2*r;
+ double r5 = r3*r2;
+ return 0.9996949*r - 0.1656700*r3 + 0.0075134*r5;
+ }
+}
+
+/*
+** Function: geopoly_regular(X,Y,R,N)
+**
+** Construct a simple, convex, regular polygon centered at X, Y
+** with circumradius R and with N sides.
+*/
+static void geopolyRegularFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ double x = sqlite3_value_double(argv[0]);
+ double y = sqlite3_value_double(argv[1]);
+ double r = sqlite3_value_double(argv[2]);
+ int n = sqlite3_value_int(argv[3]);
+ int i;
+ GeoPoly *p;
+
+ if( n<3 || r<=0.0 ) return;
+ if( n>1000 ) n = 1000;
+ p = sqlite3_malloc64( sizeof(*p) + (n-1)*2*sizeof(GeoCoord) );
+ if( p==0 ){
+ sqlite3_result_error_nomem(context);
+ return;
+ }
+ i = 1;
+ p->hdr[0] = *(unsigned char*)&i;
+ p->hdr[1] = 0;
+ p->hdr[2] = (n>>8)&0xff;
+ p->hdr[3] = n&0xff;
+ for(i=0; i<n; i++){
+ double rAngle = 2.0*GEOPOLY_PI*i/n;
+ GeoX(p,i) = x - r*geopolySine(rAngle-0.5*GEOPOLY_PI);
+ GeoY(p,i) = y + r*geopolySine(rAngle);
+ }
+ sqlite3_result_blob(context, p->hdr, 4+8*n, SQLITE_TRANSIENT);
+ sqlite3_free(p);
+}
+
+/*
+** If pPoly is a polygon, compute its bounding box. Then:
+**
+** (1) if aCoord!=0 store the bounding box in aCoord, returning NULL
+** (2) otherwise, compute a GeoPoly for the bounding box and return the
+** new GeoPoly
+**
+** If pPoly is NULL but aCoord is not NULL, then compute a new GeoPoly from
+** the bounding box in aCoord and return a pointer to that GeoPoly.
+*/
+static GeoPoly *geopolyBBox(
+ sqlite3_context *context, /* For recording the error */
+ sqlite3_value *pPoly, /* The polygon */
+ RtreeCoord *aCoord, /* Results here */
+ int *pRc /* Error code here */
+){
+ GeoPoly *pOut = 0;
+ GeoPoly *p;
+ float mnX, mxX, mnY, mxY;
+ if( pPoly==0 && aCoord!=0 ){
+ p = 0;
+ mnX = aCoord[0].f;
+ mxX = aCoord[1].f;
+ mnY = aCoord[2].f;
+ mxY = aCoord[3].f;
+ goto geopolyBboxFill;
+ }else{
+ p = geopolyFuncParam(context, pPoly, pRc);
+ }
+ if( p ){
+ int ii;
+ mnX = mxX = GeoX(p,0);
+ mnY = mxY = GeoY(p,0);
+ for(ii=1; ii<p->nVertex; ii++){
+ double r = GeoX(p,ii);
+ if( r<mnX ) mnX = (float)r;
+ else if( r>mxX ) mxX = (float)r;
+ r = GeoY(p,ii);
+ if( r<mnY ) mnY = (float)r;
+ else if( r>mxY ) mxY = (float)r;
+ }
+ if( pRc ) *pRc = SQLITE_OK;
+ if( aCoord==0 ){
+ geopolyBboxFill:
+ pOut = sqlite3_realloc64(p, GEOPOLY_SZ(4));
+ if( pOut==0 ){
+ sqlite3_free(p);
+ if( context ) sqlite3_result_error_nomem(context);
+ if( pRc ) *pRc = SQLITE_NOMEM;
+ return 0;
+ }
+ pOut->nVertex = 4;
+ ii = 1;
+ pOut->hdr[0] = *(unsigned char*)&ii;
+ pOut->hdr[1] = 0;
+ pOut->hdr[2] = 0;
+ pOut->hdr[3] = 4;
+ GeoX(pOut,0) = mnX;
+ GeoY(pOut,0) = mnY;
+ GeoX(pOut,1) = mxX;
+ GeoY(pOut,1) = mnY;
+ GeoX(pOut,2) = mxX;
+ GeoY(pOut,2) = mxY;
+ GeoX(pOut,3) = mnX;
+ GeoY(pOut,3) = mxY;
+ }else{
+ sqlite3_free(p);
+ aCoord[0].f = mnX;
+ aCoord[1].f = mxX;
+ aCoord[2].f = mnY;
+ aCoord[3].f = mxY;
+ }
+ }else if( aCoord ){
+ memset(aCoord, 0, sizeof(RtreeCoord)*4);
+ }
+ return pOut;
+}
+
+/*
+** Implementation of the geopoly_bbox(X) SQL function.
+*/
+static void geopolyBBoxFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p = geopolyBBox(context, argv[0], 0, 0);
+ if( p ){
+ sqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ sqlite3_free(p);
+ }
+}
+
+/*
+** State vector for the geopoly_group_bbox() aggregate function.
+*/
+typedef struct GeoBBox GeoBBox;
+struct GeoBBox {
+ int isInit;
+ RtreeCoord a[4];
+};
+
+
+/*
+** Implementation of the geopoly_group_bbox(X) aggregate SQL function.
+*/
+static void geopolyBBoxStep(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ RtreeCoord a[4];
+ int rc = SQLITE_OK;
+ (void)geopolyBBox(context, argv[0], a, &rc);
+ if( rc==SQLITE_OK ){
+ GeoBBox *pBBox;
+ pBBox = (GeoBBox*)sqlite3_aggregate_context(context, sizeof(*pBBox));
+ if( pBBox==0 ) return;
+ if( pBBox->isInit==0 ){
+ pBBox->isInit = 1;
+ memcpy(pBBox->a, a, sizeof(RtreeCoord)*4);
+ }else{
+ if( a[0].f < pBBox->a[0].f ) pBBox->a[0] = a[0];
+ if( a[1].f > pBBox->a[1].f ) pBBox->a[1] = a[1];
+ if( a[2].f < pBBox->a[2].f ) pBBox->a[2] = a[2];
+ if( a[3].f > pBBox->a[3].f ) pBBox->a[3] = a[3];
+ }
+ }
+}
+static void geopolyBBoxFinal(
+ sqlite3_context *context
+){
+ GeoPoly *p;
+ GeoBBox *pBBox;
+ pBBox = (GeoBBox*)sqlite3_aggregate_context(context, 0);
+ if( pBBox==0 ) return;
+ p = geopolyBBox(context, 0, pBBox->a, 0);
+ if( p ){
+ sqlite3_result_blob(context, p->hdr,
+ 4+8*p->nVertex, SQLITE_TRANSIENT);
+ sqlite3_free(p);
+ }
+}
+
+
+/*
+** Determine if point (x0,y0) is beneath line segment (x1,y1)->(x2,y2).
+** Returns:
+**
+** +2 x0,y0 is on the line segement
+**
+** +1 x0,y0 is beneath line segment
+**
+** 0 x0,y0 is not on or beneath the line segment or the line segment
+** is vertical and x0,y0 is not on the line segment
+**
+** The left-most coordinate min(x1,x2) is not considered to be part of
+** the line segment for the purposes of this analysis.
+*/
+static int pointBeneathLine(
+ double x0, double y0,
+ double x1, double y1,
+ double x2, double y2
+){
+ double y;
+ if( x0==x1 && y0==y1 ) return 2;
+ if( x1<x2 ){
+ if( x0<=x1 || x0>x2 ) return 0;
+ }else if( x1>x2 ){
+ if( x0<=x2 || x0>x1 ) return 0;
+ }else{
+ /* Vertical line segment */
+ if( x0!=x1 ) return 0;
+ if( y0<y1 && y0<y2 ) return 0;
+ if( y0>y1 && y0>y2 ) return 0;
+ return 2;
+ }
+ y = y1 + (y2-y1)*(x0-x1)/(x2-x1);
+ if( y0==y ) return 2;
+ if( y0<y ) return 1;
+ return 0;
+}
+
+/*
+** SQL function: geopoly_contains_point(P,X,Y)
+**
+** Return +2 if point X,Y is within polygon P.
+** Return +1 if point X,Y is on the polygon boundary.
+** Return 0 if point X,Y is outside the polygon
+*/
+static void geopolyContainsPointFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0);
+ double x0 = sqlite3_value_double(argv[1]);
+ double y0 = sqlite3_value_double(argv[2]);
+ int v = 0;
+ int cnt = 0;
+ int ii;
+ if( p1==0 ) return;
+ for(ii=0; ii<p1->nVertex-1; ii++){
+ v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii),
+ GeoX(p1,ii+1),GeoY(p1,ii+1));
+ if( v==2 ) break;
+ cnt += v;
+ }
+ if( v!=2 ){
+ v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii),
+ GeoX(p1,0), GeoY(p1,0));
+ }
+ if( v==2 ){
+ sqlite3_result_int(context, 1);
+ }else if( ((v+cnt)&1)==0 ){
+ sqlite3_result_int(context, 0);
+ }else{
+ sqlite3_result_int(context, 2);
+ }
+ sqlite3_free(p1);
+}
+
+/* Forward declaration */
+static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2);
+
+/*
+** SQL function: geopoly_within(P1,P2)
+**
+** Return +2 if P1 and P2 are the same polygon
+** Return +1 if P2 is contained within P1
+** Return 0 if any part of P2 is on the outside of P1
+**
+*/
+static void geopolyWithinFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0);
+ GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0);
+ if( p1 && p2 ){
+ int x = geopolyOverlap(p1, p2);
+ if( x<0 ){
+ sqlite3_result_error_nomem(context);
+ }else{
+ sqlite3_result_int(context, x==2 ? 1 : x==4 ? 2 : 0);
+ }
+ }
+ sqlite3_free(p1);
+ sqlite3_free(p2);
+}
+
+/* Objects used by the overlap algorihm. */
+typedef struct GeoEvent GeoEvent;
+typedef struct GeoSegment GeoSegment;
+typedef struct GeoOverlap GeoOverlap;
+struct GeoEvent {
+ double x; /* X coordinate at which event occurs */
+ int eType; /* 0 for ADD, 1 for REMOVE */
+ GeoSegment *pSeg; /* The segment to be added or removed */
+ GeoEvent *pNext; /* Next event in the sorted list */
+};
+struct GeoSegment {
+ double C, B; /* y = C*x + B */
+ double y; /* Current y value */
+ float y0; /* Initial y value */
+ unsigned char side; /* 1 for p1, 2 for p2 */
+ unsigned int idx; /* Which segment within the side */
+ GeoSegment *pNext; /* Next segment in a list sorted by y */
+};
+struct GeoOverlap {
+ GeoEvent *aEvent; /* Array of all events */
+ GeoSegment *aSegment; /* Array of all segments */
+ int nEvent; /* Number of events */
+ int nSegment; /* Number of segments */
+};
+
+/*
+** Add a single segment and its associated events.
+*/
+static void geopolyAddOneSegment(
+ GeoOverlap *p,
+ GeoCoord x0,
+ GeoCoord y0,
+ GeoCoord x1,
+ GeoCoord y1,
+ unsigned char side,
+ unsigned int idx
+){
+ GeoSegment *pSeg;
+ GeoEvent *pEvent;
+ if( x0==x1 ) return; /* Ignore vertical segments */
+ if( x0>x1 ){
+ GeoCoord t = x0;
+ x0 = x1;
+ x1 = t;
+ t = y0;
+ y0 = y1;
+ y1 = t;
+ }
+ pSeg = p->aSegment + p->nSegment;
+ p->nSegment++;
+ pSeg->C = (y1-y0)/(x1-x0);
+ pSeg->B = y1 - x1*pSeg->C;
+ pSeg->y0 = y0;
+ pSeg->side = side;
+ pSeg->idx = idx;
+ pEvent = p->aEvent + p->nEvent;
+ p->nEvent++;
+ pEvent->x = x0;
+ pEvent->eType = 0;
+ pEvent->pSeg = pSeg;
+ pEvent = p->aEvent + p->nEvent;
+ p->nEvent++;
+ pEvent->x = x1;
+ pEvent->eType = 1;
+ pEvent->pSeg = pSeg;
+}
+
+
+
+/*
+** Insert all segments and events for polygon pPoly.
+*/
+static void geopolyAddSegments(
+ GeoOverlap *p, /* Add segments to this Overlap object */
+ GeoPoly *pPoly, /* Take all segments from this polygon */
+ unsigned char side /* The side of pPoly */
+){
+ unsigned int i;
+ GeoCoord *x;
+ for(i=0; i<(unsigned)pPoly->nVertex-1; i++){
+ x = &GeoX(pPoly,i);
+ geopolyAddOneSegment(p, x[0], x[1], x[2], x[3], side, i);
+ }
+ x = &GeoX(pPoly,i);
+ geopolyAddOneSegment(p, x[0], x[1], pPoly->a[0], pPoly->a[1], side, i);
+}
+
+/*
+** Merge two lists of sorted events by X coordinate
+*/
+static GeoEvent *geopolyEventMerge(GeoEvent *pLeft, GeoEvent *pRight){
+ GeoEvent head, *pLast;
+ head.pNext = 0;
+ pLast = &head;
+ while( pRight && pLeft ){
+ if( pRight->x <= pLeft->x ){
+ pLast->pNext = pRight;
+ pLast = pRight;
+ pRight = pRight->pNext;
+ }else{
+ pLast->pNext = pLeft;
+ pLast = pLeft;
+ pLeft = pLeft->pNext;
+ }
+ }
+ pLast->pNext = pRight ? pRight : pLeft;
+ return head.pNext;
+}
+
+/*
+** Sort an array of nEvent event objects into a list.
+*/
+static GeoEvent *geopolySortEventsByX(GeoEvent *aEvent, int nEvent){
+ int mx = 0;
+ int i, j;
+ GeoEvent *p;
+ GeoEvent *a[50];
+ for(i=0; i<nEvent; i++){
+ p = &aEvent[i];
+ p->pNext = 0;
+ for(j=0; j<mx && a[j]; j++){
+ p = geopolyEventMerge(a[j], p);
+ a[j] = 0;
+ }
+ a[j] = p;
+ if( j>=mx ) mx = j+1;
+ }
+ p = 0;
+ for(i=0; i<mx; i++){
+ p = geopolyEventMerge(a[i], p);
+ }
+ return p;
+}
+
+/*
+** Merge two lists of sorted segments by Y, and then by C.
+*/
+static GeoSegment *geopolySegmentMerge(GeoSegment *pLeft, GeoSegment *pRight){
+ GeoSegment head, *pLast;
+ head.pNext = 0;
+ pLast = &head;
+ while( pRight && pLeft ){
+ double r = pRight->y - pLeft->y;
+ if( r==0.0 ) r = pRight->C - pLeft->C;
+ if( r<0.0 ){
+ pLast->pNext = pRight;
+ pLast = pRight;
+ pRight = pRight->pNext;
+ }else{
+ pLast->pNext = pLeft;
+ pLast = pLeft;
+ pLeft = pLeft->pNext;
+ }
+ }
+ pLast->pNext = pRight ? pRight : pLeft;
+ return head.pNext;
+}
+
+/*
+** Sort a list of GeoSegments in order of increasing Y and in the event of
+** a tie, increasing C (slope).
+*/
+static GeoSegment *geopolySortSegmentsByYAndC(GeoSegment *pList){
+ int mx = 0;
+ int i;
+ GeoSegment *p;
+ GeoSegment *a[50];
+ while( pList ){
+ p = pList;
+ pList = pList->pNext;
+ p->pNext = 0;
+ for(i=0; i<mx && a[i]; i++){
+ p = geopolySegmentMerge(a[i], p);
+ a[i] = 0;
+ }
+ a[i] = p;
+ if( i>=mx ) mx = i+1;
+ }
+ p = 0;
+ for(i=0; i<mx; i++){
+ p = geopolySegmentMerge(a[i], p);
+ }
+ return p;
+}
+
+/*
+** Determine the overlap between two polygons
+*/
+static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){
+ sqlite3_int64 nVertex = p1->nVertex + p2->nVertex + 2;
+ GeoOverlap *p;
+ sqlite3_int64 nByte;
+ GeoEvent *pThisEvent;
+ double rX;
+ int rc = 0;
+ int needSort = 0;
+ GeoSegment *pActive = 0;
+ GeoSegment *pSeg;
+ unsigned char aOverlap[4];
+
+ nByte = sizeof(GeoEvent)*nVertex*2
+ + sizeof(GeoSegment)*nVertex
+ + sizeof(GeoOverlap);
+ p = sqlite3_malloc64( nByte );
+ if( p==0 ) return -1;
+ p->aEvent = (GeoEvent*)&p[1];
+ p->aSegment = (GeoSegment*)&p->aEvent[nVertex*2];
+ p->nEvent = p->nSegment = 0;
+ geopolyAddSegments(p, p1, 1);
+ geopolyAddSegments(p, p2, 2);
+ pThisEvent = geopolySortEventsByX(p->aEvent, p->nEvent);
+ rX = pThisEvent && pThisEvent->x==0.0 ? -1.0 : 0.0;
+ memset(aOverlap, 0, sizeof(aOverlap));
+ while( pThisEvent ){
+ if( pThisEvent->x!=rX ){
+ GeoSegment *pPrev = 0;
+ int iMask = 0;
+ GEODEBUG(("Distinct X: %g\n", pThisEvent->x));
+ rX = pThisEvent->x;
+ if( needSort ){
+ GEODEBUG(("SORT\n"));
+ pActive = geopolySortSegmentsByYAndC(pActive);
+ needSort = 0;
+ }
+ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){
+ if( pPrev ){
+ if( pPrev->y!=pSeg->y ){
+ GEODEBUG(("MASK: %d\n", iMask));
+ aOverlap[iMask] = 1;
+ }
+ }
+ iMask ^= pSeg->side;
+ pPrev = pSeg;
+ }
+ pPrev = 0;
+ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){
+ double y = pSeg->C*rX + pSeg->B;
+ GEODEBUG(("Segment %d.%d %g->%g\n", pSeg->side, pSeg->idx, pSeg->y, y));
+ pSeg->y = y;
+ if( pPrev ){
+ if( pPrev->y>pSeg->y && pPrev->side!=pSeg->side ){
+ rc = 1;
+ GEODEBUG(("Crossing: %d.%d and %d.%d\n",
+ pPrev->side, pPrev->idx,
+ pSeg->side, pSeg->idx));
+ goto geopolyOverlapDone;
+ }else if( pPrev->y!=pSeg->y ){
+ GEODEBUG(("MASK: %d\n", iMask));
+ aOverlap[iMask] = 1;
+ }
+ }
+ iMask ^= pSeg->side;
+ pPrev = pSeg;
+ }
+ }
+ GEODEBUG(("%s %d.%d C=%g B=%g\n",
+ pThisEvent->eType ? "RM " : "ADD",
+ pThisEvent->pSeg->side, pThisEvent->pSeg->idx,
+ pThisEvent->pSeg->C,
+ pThisEvent->pSeg->B));
+ if( pThisEvent->eType==0 ){
+ /* Add a segment */
+ pSeg = pThisEvent->pSeg;
+ pSeg->y = pSeg->y0;
+ pSeg->pNext = pActive;
+ pActive = pSeg;
+ needSort = 1;
+ }else{
+ /* Remove a segment */
+ if( pActive==pThisEvent->pSeg ){
+ pActive = ALWAYS(pActive) ? pActive->pNext : 0;
+ }else{
+ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){
+ if( pSeg->pNext==pThisEvent->pSeg ){
+ pSeg->pNext = ALWAYS(pSeg->pNext) ? pSeg->pNext->pNext : 0;
+ break;
+ }
+ }
+ }
+ }
+ pThisEvent = pThisEvent->pNext;
+ }
+ if( aOverlap[3]==0 ){
+ rc = 0;
+ }else if( aOverlap[1]!=0 && aOverlap[2]==0 ){
+ rc = 3;
+ }else if( aOverlap[1]==0 && aOverlap[2]!=0 ){
+ rc = 2;
+ }else if( aOverlap[1]==0 && aOverlap[2]==0 ){
+ rc = 4;
+ }else{
+ rc = 1;
+ }
+
+geopolyOverlapDone:
+ sqlite3_free(p);
+ return rc;
+}
+
+/*
+** SQL function: geopoly_overlap(P1,P2)
+**
+** Determine whether or not P1 and P2 overlap. Return value:
+**
+** 0 The two polygons are disjoint
+** 1 They overlap
+** 2 P1 is completely contained within P2
+** 3 P2 is completely contained within P1
+** 4 P1 and P2 are the same polygon
+** NULL Either P1 or P2 or both are not valid polygons
+*/
+static void geopolyOverlapFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0);
+ GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0);
+ if( p1 && p2 ){
+ int x = geopolyOverlap(p1, p2);
+ if( x<0 ){
+ sqlite3_result_error_nomem(context);
+ }else{
+ sqlite3_result_int(context, x);
+ }
+ }
+ sqlite3_free(p1);
+ sqlite3_free(p2);
+}
+
+/*
+** Enable or disable debugging output
+*/
+static void geopolyDebugFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+#ifdef GEOPOLY_ENABLE_DEBUG
+ geo_debug = sqlite3_value_int(argv[0]);
+#endif
+}
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the geopoly virtual table.
+**
+** argv[0] -> module name
+** argv[1] -> database name
+** argv[2] -> table name
+** argv[...] -> column names...
+*/
+static int geopolyInit(
+ sqlite3 *db, /* Database connection */
+ void *pAux, /* One of the RTREE_COORD_* constants */
+ int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */
+ sqlite3_vtab **ppVtab, /* OUT: New virtual table */
+ char **pzErr, /* OUT: Error message, if any */
+ int isCreate /* True for xCreate, false for xConnect */
+){
+ int rc = SQLITE_OK;
+ Rtree *pRtree;
+ sqlite3_int64 nDb; /* Length of string argv[1] */
+ sqlite3_int64 nName; /* Length of string argv[2] */
+ sqlite3_str *pSql;
+ char *zSql;
+ int ii;
+
+ sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+
+ /* Allocate the sqlite3_vtab structure */
+ nDb = strlen(argv[1]);
+ nName = strlen(argv[2]);
+ pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
+ if( !pRtree ){
+ return SQLITE_NOMEM;
+ }
+ memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
+ pRtree->nBusy = 1;
+ pRtree->base.pModule = &rtreeModule;
+ pRtree->zDb = (char *)&pRtree[1];
+ pRtree->zName = &pRtree->zDb[nDb+1];
+ pRtree->eCoordType = RTREE_COORD_REAL32;
+ pRtree->nDim = 2;
+ pRtree->nDim2 = 4;
+ memcpy(pRtree->zDb, argv[1], nDb);
+ memcpy(pRtree->zName, argv[2], nName);
+
+
+ /* Create/Connect to the underlying relational database schema. If
+ ** that is successful, call sqlite3_declare_vtab() to configure
+ ** the r-tree table schema.
+ */
+ pSql = sqlite3_str_new(db);
+ sqlite3_str_appendf(pSql, "CREATE TABLE x(_shape");
+ pRtree->nAux = 1; /* Add one for _shape */
+ pRtree->nAuxNotNull = 1; /* The _shape column is always not-null */
+ for(ii=3; ii<argc; ii++){
+ pRtree->nAux++;
+ sqlite3_str_appendf(pSql, ",%s", argv[ii]);
+ }
+ sqlite3_str_appendf(pSql, ");");
+ zSql = sqlite3_str_finish(pSql);
+ if( !zSql ){
+ rc = SQLITE_NOMEM;
+ }else if( SQLITE_OK!=(rc = sqlite3_declare_vtab(db, zSql)) ){
+ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ }
+ sqlite3_free(zSql);
+ if( rc ) goto geopolyInit_fail;
+ pRtree->nBytesPerCell = 8 + pRtree->nDim2*4;
+
+ /* Figure out the node size to use. */
+ rc = getNodeSize(db, pRtree, isCreate, pzErr);
+ if( rc ) goto geopolyInit_fail;
+ rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate);
+ if( rc ){
+ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ goto geopolyInit_fail;
+ }
+
+ *ppVtab = (sqlite3_vtab *)pRtree;
+ return SQLITE_OK;
+
+geopolyInit_fail:
+ if( rc==SQLITE_OK ) rc = SQLITE_ERROR;
+ assert( *ppVtab==0 );
+ assert( pRtree->nBusy==1 );
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+
+/*
+** GEOPOLY virtual table module xCreate method.
+*/
+static int geopolyCreate(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ return geopolyInit(db, pAux, argc, argv, ppVtab, pzErr, 1);
+}
+
+/*
+** GEOPOLY virtual table module xConnect method.
+*/
+static int geopolyConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ return geopolyInit(db, pAux, argc, argv, ppVtab, pzErr, 0);
+}
+
+
+/*
+** GEOPOLY virtual table module xFilter method.
+**
+** Query plans:
+**
+** 1 rowid lookup
+** 2 search for objects overlapping the same bounding box
+** that contains polygon argv[0]
+** 3 search for objects overlapping the same bounding box
+** that contains polygon argv[0]
+** 4 full table scan
+*/
+static int geopolyFilter(
+ sqlite3_vtab_cursor *pVtabCursor, /* The cursor to initialize */
+ int idxNum, /* Query plan */
+ const char *idxStr, /* Not Used */
+ int argc, sqlite3_value **argv /* Parameters to the query plan */
+){
+ Rtree *pRtree = (Rtree *)pVtabCursor->pVtab;
+ RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
+ RtreeNode *pRoot = 0;
+ int rc = SQLITE_OK;
+ int iCell = 0;
+
+ rtreeReference(pRtree);
+
+ /* Reset the cursor to the same state as rtreeOpen() leaves it in. */
+ resetCursor(pCsr);
+
+ pCsr->iStrategy = idxNum;
+ if( idxNum==1 ){
+ /* Special case - lookup by rowid. */
+ RtreeNode *pLeaf; /* Leaf on which the required cell resides */
+ RtreeSearchPoint *p; /* Search point for the leaf */
+ i64 iRowid = sqlite3_value_int64(argv[0]);
+ i64 iNode = 0;
+ rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode);
+ if( rc==SQLITE_OK && pLeaf!=0 ){
+ p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0);
+ assert( p!=0 ); /* Always returns pCsr->sPoint */
+ pCsr->aNode[0] = pLeaf;
+ p->id = iNode;
+ p->eWithin = PARTLY_WITHIN;
+ rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell);
+ p->iCell = (u8)iCell;
+ RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:");
+ }else{
+ pCsr->atEOF = 1;
+ }
+ }else{
+ /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array
+ ** with the configured constraints.
+ */
+ rc = nodeAcquire(pRtree, 1, 0, &pRoot);
+ if( rc==SQLITE_OK && idxNum<=3 ){
+ RtreeCoord bbox[4];
+ RtreeConstraint *p;
+ assert( argc==1 );
+ assert( argv[0]!=0 );
+ geopolyBBox(0, argv[0], bbox, &rc);
+ if( rc ){
+ goto geopoly_filter_end;
+ }
+ pCsr->aConstraint = p = sqlite3_malloc(sizeof(RtreeConstraint)*4);
+ pCsr->nConstraint = 4;
+ if( p==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*4);
+ memset(pCsr->anQueue, 0, sizeof(u32)*(pRtree->iDepth + 1));
+ if( idxNum==2 ){
+ /* Overlap query */
+ p->op = 'B';
+ p->iCoord = 0;
+ p->u.rValue = bbox[1].f;
+ p++;
+ p->op = 'D';
+ p->iCoord = 1;
+ p->u.rValue = bbox[0].f;
+ p++;
+ p->op = 'B';
+ p->iCoord = 2;
+ p->u.rValue = bbox[3].f;
+ p++;
+ p->op = 'D';
+ p->iCoord = 3;
+ p->u.rValue = bbox[2].f;
+ }else{
+ /* Within query */
+ p->op = 'D';
+ p->iCoord = 0;
+ p->u.rValue = bbox[0].f;
+ p++;
+ p->op = 'B';
+ p->iCoord = 1;
+ p->u.rValue = bbox[1].f;
+ p++;
+ p->op = 'D';
+ p->iCoord = 2;
+ p->u.rValue = bbox[2].f;
+ p++;
+ p->op = 'B';
+ p->iCoord = 3;
+ p->u.rValue = bbox[3].f;
+ }
+ }
+ }
+ if( rc==SQLITE_OK ){
+ RtreeSearchPoint *pNew;
+ pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1));
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ goto geopoly_filter_end;
+ }
+ pNew->id = 1;
+ pNew->iCell = 0;
+ pNew->eWithin = PARTLY_WITHIN;
+ assert( pCsr->bPoint==1 );
+ pCsr->aNode[0] = pRoot;
+ pRoot = 0;
+ RTREE_QUEUE_TRACE(pCsr, "PUSH-Fm:");
+ rc = rtreeStepToLeaf(pCsr);
+ }
+ }
+
+geopoly_filter_end:
+ nodeRelease(pRtree, pRoot);
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+/*
+** Rtree virtual table module xBestIndex method. There are three
+** table scan strategies to choose from (in order from most to
+** least desirable):
+**
+** idxNum idxStr Strategy
+** ------------------------------------------------
+** 1 "rowid" Direct lookup by rowid.
+** 2 "rtree" R-tree overlap query using geopoly_overlap()
+** 3 "rtree" R-tree within query using geopoly_within()
+** 4 "fullscan" full-table scan.
+** ------------------------------------------------
+*/
+static int geopolyBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+ int ii;
+ int iRowidTerm = -1;
+ int iFuncTerm = -1;
+ int idxNum = 0;
+
+ for(ii=0; ii<pIdxInfo->nConstraint; ii++){
+ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
+ if( !p->usable ) continue;
+ if( p->iColumn<0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){
+ iRowidTerm = ii;
+ break;
+ }
+ if( p->iColumn==0 && p->op>=SQLITE_INDEX_CONSTRAINT_FUNCTION ){
+ /* p->op==SQLITE_INDEX_CONSTRAINT_FUNCTION for geopoly_overlap()
+ ** p->op==(SQLITE_INDEX_CONTRAINT_FUNCTION+1) for geopoly_within().
+ ** See geopolyFindFunction() */
+ iFuncTerm = ii;
+ idxNum = p->op - SQLITE_INDEX_CONSTRAINT_FUNCTION + 2;
+ }
+ }
+
+ if( iRowidTerm>=0 ){
+ pIdxInfo->idxNum = 1;
+ pIdxInfo->idxStr = "rowid";
+ pIdxInfo->aConstraintUsage[iRowidTerm].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[iRowidTerm].omit = 1;
+ pIdxInfo->estimatedCost = 30.0;
+ pIdxInfo->estimatedRows = 1;
+ pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE;
+ return SQLITE_OK;
+ }
+ if( iFuncTerm>=0 ){
+ pIdxInfo->idxNum = idxNum;
+ pIdxInfo->idxStr = "rtree";
+ pIdxInfo->aConstraintUsage[iFuncTerm].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[iFuncTerm].omit = 0;
+ pIdxInfo->estimatedCost = 300.0;
+ pIdxInfo->estimatedRows = 10;
+ return SQLITE_OK;
+ }
+ pIdxInfo->idxNum = 4;
+ pIdxInfo->idxStr = "fullscan";
+ pIdxInfo->estimatedCost = 3000000.0;
+ pIdxInfo->estimatedRows = 100000;
+ return SQLITE_OK;
+}
+
+
+/*
+** GEOPOLY virtual table module xColumn method.
+*/
+static int geopolyColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+ Rtree *pRtree = (Rtree *)cur->pVtab;
+ RtreeCursor *pCsr = (RtreeCursor *)cur;
+ RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
+ int rc = SQLITE_OK;
+ RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
+
+ if( rc ) return rc;
+ if( p==0 ) return SQLITE_OK;
+ if( i==0 && sqlite3_vtab_nochange(ctx) ) return SQLITE_OK;
+ if( i<=pRtree->nAux ){
+ if( !pCsr->bAuxValid ){
+ if( pCsr->pReadAux==0 ){
+ rc = sqlite3_prepare_v3(pRtree->db, pRtree->zReadAuxSql, -1, 0,
+ &pCsr->pReadAux, 0);
+ if( rc ) return rc;
+ }
+ sqlite3_bind_int64(pCsr->pReadAux, 1,
+ nodeGetRowid(pRtree, pNode, p->iCell));
+ rc = sqlite3_step(pCsr->pReadAux);
+ if( rc==SQLITE_ROW ){
+ pCsr->bAuxValid = 1;
+ }else{
+ sqlite3_reset(pCsr->pReadAux);
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ return rc;
+ }
+ }
+ sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pReadAux, i+2));
+ }
+ return SQLITE_OK;
+}
+
+
+/*
+** The xUpdate method for GEOPOLY module virtual tables.
+**
+** For DELETE:
+**
+** argv[0] = the rowid to be deleted
+**
+** For INSERT:
+**
+** argv[0] = SQL NULL
+** argv[1] = rowid to insert, or an SQL NULL to select automatically
+** argv[2] = _shape column
+** argv[3] = first application-defined column....
+**
+** For UPDATE:
+**
+** argv[0] = rowid to modify. Never NULL
+** argv[1] = rowid after the change. Never NULL
+** argv[2] = new value for _shape
+** argv[3] = new value for first application-defined column....
+*/
+static int geopolyUpdate(
+ sqlite3_vtab *pVtab,
+ int nData,
+ sqlite3_value **aData,
+ sqlite_int64 *pRowid
+){
+ Rtree *pRtree = (Rtree *)pVtab;
+ int rc = SQLITE_OK;
+ RtreeCell cell; /* New cell to insert if nData>1 */
+ i64 oldRowid; /* The old rowid */
+ int oldRowidValid; /* True if oldRowid is valid */
+ i64 newRowid; /* The new rowid */
+ int newRowidValid; /* True if newRowid is valid */
+ int coordChange = 0; /* Change in coordinates */
+
+ if( pRtree->nNodeRef ){
+ /* Unable to write to the btree while another cursor is reading from it,
+ ** since the write might do a rebalance which would disrupt the read
+ ** cursor. */
+ return SQLITE_LOCKED_VTAB;
+ }
+ rtreeReference(pRtree);
+ assert(nData>=1);
+
+ oldRowidValid = sqlite3_value_type(aData[0])!=SQLITE_NULL;;
+ oldRowid = oldRowidValid ? sqlite3_value_int64(aData[0]) : 0;
+ newRowidValid = nData>1 && sqlite3_value_type(aData[1])!=SQLITE_NULL;
+ newRowid = newRowidValid ? sqlite3_value_int64(aData[1]) : 0;
+ cell.iRowid = newRowid;
+
+ if( nData>1 /* not a DELETE */
+ && (!oldRowidValid /* INSERT */
+ || !sqlite3_value_nochange(aData[2]) /* UPDATE _shape */
+ || oldRowid!=newRowid) /* Rowid change */
+ ){
+ assert( aData[2]!=0 );
+ geopolyBBox(0, aData[2], cell.aCoord, &rc);
+ if( rc ){
+ if( rc==SQLITE_ERROR ){
+ pVtab->zErrMsg =
+ sqlite3_mprintf("_shape does not contain a valid polygon");
+ }
+ goto geopoly_update_end;
+ }
+ coordChange = 1;
+
+ /* If a rowid value was supplied, check if it is already present in
+ ** the table. If so, the constraint has failed. */
+ if( newRowidValid && (!oldRowidValid || oldRowid!=newRowid) ){
+ int steprc;
+ sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
+ steprc = sqlite3_step(pRtree->pReadRowid);
+ rc = sqlite3_reset(pRtree->pReadRowid);
+ if( SQLITE_ROW==steprc ){
+ if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
+ rc = rtreeDeleteRowid(pRtree, cell.iRowid);
+ }else{
+ rc = rtreeConstraintError(pRtree, 0);
+ }
+ }
+ }
+ }
+
+ /* If aData[0] is not an SQL NULL value, it is the rowid of a
+ ** record to delete from the r-tree table. The following block does
+ ** just that.
+ */
+ if( rc==SQLITE_OK && (nData==1 || (coordChange && oldRowidValid)) ){
+ rc = rtreeDeleteRowid(pRtree, oldRowid);
+ }
+
+ /* If the aData[] array contains more than one element, elements
+ ** (aData[2]..aData[argc-1]) contain a new record to insert into
+ ** the r-tree structure.
+ */
+ if( rc==SQLITE_OK && nData>1 && coordChange ){
+ /* Insert the new record into the r-tree */
+ RtreeNode *pLeaf = 0;
+ if( !newRowidValid ){
+ rc = rtreeNewRowid(pRtree, &cell.iRowid);
+ }
+ *pRowid = cell.iRowid;
+ if( rc==SQLITE_OK ){
+ rc = ChooseLeaf(pRtree, &cell, 0, &pLeaf);
+ }
+ if( rc==SQLITE_OK ){
+ int rc2;
+ pRtree->iReinsertHeight = -1;
+ rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
+ rc2 = nodeRelease(pRtree, pLeaf);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ }
+ }
+
+ /* Change the data */
+ if( rc==SQLITE_OK && nData>1 ){
+ sqlite3_stmt *pUp = pRtree->pWriteAux;
+ int jj;
+ int nChange = 0;
+ sqlite3_bind_int64(pUp, 1, cell.iRowid);
+ assert( pRtree->nAux>=1 );
+ if( sqlite3_value_nochange(aData[2]) ){
+ sqlite3_bind_null(pUp, 2);
+ }else{
+ GeoPoly *p = 0;
+ if( sqlite3_value_type(aData[2])==SQLITE_TEXT
+ && (p = geopolyFuncParam(0, aData[2], &rc))!=0
+ && rc==SQLITE_OK
+ ){
+ sqlite3_bind_blob(pUp, 2, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_bind_value(pUp, 2, aData[2]);
+ }
+ sqlite3_free(p);
+ nChange = 1;
+ }
+ for(jj=1; jj<nData-2; jj++){
+ nChange++;
+ sqlite3_bind_value(pUp, jj+2, aData[jj+2]);
+ }
+ if( nChange ){
+ sqlite3_step(pUp);
+ rc = sqlite3_reset(pUp);
+ }
+ }
+
+geopoly_update_end:
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+/*
+** Report that geopoly_overlap() is an overloaded function suitable
+** for use in xBestIndex.
+*/
+static int geopolyFindFunction(
+ sqlite3_vtab *pVtab,
+ int nArg,
+ const char *zName,
+ void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
+ void **ppArg
+){
+ if( sqlite3_stricmp(zName, "geopoly_overlap")==0 ){
+ *pxFunc = geopolyOverlapFunc;
+ *ppArg = 0;
+ return SQLITE_INDEX_CONSTRAINT_FUNCTION;
+ }
+ if( sqlite3_stricmp(zName, "geopoly_within")==0 ){
+ *pxFunc = geopolyWithinFunc;
+ *ppArg = 0;
+ return SQLITE_INDEX_CONSTRAINT_FUNCTION+1;
+ }
+ return 0;
+}
+
+
+static sqlite3_module geopolyModule = {
+ 3, /* iVersion */
+ geopolyCreate, /* xCreate - create a table */
+ geopolyConnect, /* xConnect - connect to an existing table */
+ geopolyBestIndex, /* xBestIndex - Determine search strategy */
+ rtreeDisconnect, /* xDisconnect - Disconnect from a table */
+ rtreeDestroy, /* xDestroy - Drop a table */
+ rtreeOpen, /* xOpen - open a cursor */
+ rtreeClose, /* xClose - close a cursor */
+ geopolyFilter, /* xFilter - configure scan constraints */
+ rtreeNext, /* xNext - advance a cursor */
+ rtreeEof, /* xEof */
+ geopolyColumn, /* xColumn - read data */
+ rtreeRowid, /* xRowid - read data */
+ geopolyUpdate, /* xUpdate - write data */
+ rtreeBeginTransaction, /* xBegin - begin transaction */
+ rtreeEndTransaction, /* xSync - sync transaction */
+ rtreeEndTransaction, /* xCommit - commit transaction */
+ rtreeEndTransaction, /* xRollback - rollback transaction */
+ geopolyFindFunction, /* xFindFunction - function overloading */
+ rtreeRename, /* xRename - rename the table */
+ rtreeSavepoint, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ rtreeShadowName /* xShadowName */
+};
+
+static int sqlite3_geopoly_init(sqlite3 *db){
+ int rc = SQLITE_OK;
+ static const struct {
+ void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
+ signed char nArg;
+ unsigned char bPure;
+ const char *zName;
+ } aFunc[] = {
+ { geopolyAreaFunc, 1, 1, "geopoly_area" },
+ { geopolyBlobFunc, 1, 1, "geopoly_blob" },
+ { geopolyJsonFunc, 1, 1, "geopoly_json" },
+ { geopolySvgFunc, -1, 1, "geopoly_svg" },
+ { geopolyWithinFunc, 2, 1, "geopoly_within" },
+ { geopolyContainsPointFunc, 3, 1, "geopoly_contains_point" },
+ { geopolyOverlapFunc, 2, 1, "geopoly_overlap" },
+ { geopolyDebugFunc, 1, 0, "geopoly_debug" },
+ { geopolyBBoxFunc, 1, 1, "geopoly_bbox" },
+ { geopolyXformFunc, 7, 1, "geopoly_xform" },
+ { geopolyRegularFunc, 4, 1, "geopoly_regular" },
+ { geopolyCcwFunc, 1, 1, "geopoly_ccw" },
+ };
+ static const struct {
+ void (*xStep)(sqlite3_context*,int,sqlite3_value**);
+ void (*xFinal)(sqlite3_context*);
+ const char *zName;
+ } aAgg[] = {
+ { geopolyBBoxStep, geopolyBBoxFinal, "geopoly_group_bbox" },
+ };
+ int i;
+ for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){
+ int enc;
+ if( aFunc[i].bPure ){
+ enc = SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS;
+ }else{
+ enc = SQLITE_UTF8|SQLITE_DIRECTONLY;
+ }
+ rc = sqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg,
+ enc, 0,
+ aFunc[i].xFunc, 0, 0);
+ }
+ for(i=0; i<sizeof(aAgg)/sizeof(aAgg[0]) && rc==SQLITE_OK; i++){
+ rc = sqlite3_create_function(db, aAgg[i].zName, 1,
+ SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, 0,
+ 0, aAgg[i].xStep, aAgg[i].xFinal);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_module_v2(db, "geopoly", &geopolyModule, 0, 0);
+ }
+ return rc;
+}
diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c
new file mode 100644
index 0000000..7daae9b
--- /dev/null
+++ b/ext/rtree/rtree.c
@@ -0,0 +1,4582 @@
+/*
+** 2001 September 15
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+** This file contains code for implementations of the r-tree and r*-tree
+** algorithms packaged as an SQLite virtual table module.
+*/
+
+/*
+** Database Format of R-Tree Tables
+** --------------------------------
+**
+** The data structure for a single virtual r-tree table is stored in three
+** native SQLite tables declared as follows. In each case, the '%' character
+** in the table name is replaced with the user-supplied name of the r-tree
+** table.
+**
+** CREATE TABLE %_node(nodeno INTEGER PRIMARY KEY, data BLOB)
+** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER)
+** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER, ...)
+**
+** The data for each node of the r-tree structure is stored in the %_node
+** table. For each node that is not the root node of the r-tree, there is
+** an entry in the %_parent table associating the node with its parent.
+** And for each row of data in the table, there is an entry in the %_rowid
+** table that maps from the entries rowid to the id of the node that it
+** is stored on. If the r-tree contains auxiliary columns, those are stored
+** on the end of the %_rowid table.
+**
+** The root node of an r-tree always exists, even if the r-tree table is
+** empty. The nodeno of the root node is always 1. All other nodes in the
+** table must be the same size as the root node. The content of each node
+** is formatted as follows:
+**
+** 1. If the node is the root node (node 1), then the first 2 bytes
+** of the node contain the tree depth as a big-endian integer.
+** For non-root nodes, the first 2 bytes are left unused.
+**
+** 2. The next 2 bytes contain the number of entries currently
+** stored in the node.
+**
+** 3. The remainder of the node contains the node entries. Each entry
+** consists of a single 8-byte integer followed by an even number
+** of 4-byte coordinates. For leaf nodes the integer is the rowid
+** of a record. For internal nodes it is the node number of a
+** child page.
+*/
+
+#if !defined(SQLITE_CORE) \
+ || (defined(SQLITE_ENABLE_RTREE) && !defined(SQLITE_OMIT_VIRTUALTABLE))
+
+#ifndef SQLITE_CORE
+ #include "sqlite3ext.h"
+ SQLITE_EXTENSION_INIT1
+#else
+ #include "sqlite3.h"
+#endif
+int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */
+
+/*
+** If building separately, we will need some setup that is normally
+** found in sqliteInt.h
+*/
+#if !defined(SQLITE_AMALGAMATION)
+#include "sqlite3rtree.h"
+typedef sqlite3_int64 i64;
+typedef sqlite3_uint64 u64;
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
+# define NDEBUG 1
+#endif
+#if defined(NDEBUG) && defined(SQLITE_DEBUG)
+# undef NDEBUG
+#endif
+#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
+# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1
+#endif
+#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS)
+# define ALWAYS(X) (1)
+# define NEVER(X) (0)
+#elif !defined(NDEBUG)
+# define ALWAYS(X) ((X)?1:(assert(0),0))
+# define NEVER(X) ((X)?(assert(0),1):0)
+#else
+# define ALWAYS(X) (X)
+# define NEVER(X) (X)
+#endif
+#endif /* !defined(SQLITE_AMALGAMATION) */
+
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+
+/* The following macro is used to suppress compiler warnings.
+*/
+#ifndef UNUSED_PARAMETER
+# define UNUSED_PARAMETER(x) (void)(x)
+#endif
+
+typedef struct Rtree Rtree;
+typedef struct RtreeCursor RtreeCursor;
+typedef struct RtreeNode RtreeNode;
+typedef struct RtreeCell RtreeCell;
+typedef struct RtreeConstraint RtreeConstraint;
+typedef struct RtreeMatchArg RtreeMatchArg;
+typedef struct RtreeGeomCallback RtreeGeomCallback;
+typedef union RtreeCoord RtreeCoord;
+typedef struct RtreeSearchPoint RtreeSearchPoint;
+
+/* The rtree may have between 1 and RTREE_MAX_DIMENSIONS dimensions. */
+#define RTREE_MAX_DIMENSIONS 5
+
+/* Maximum number of auxiliary columns */
+#define RTREE_MAX_AUX_COLUMN 100
+
+/* Size of hash table Rtree.aHash. This hash table is not expected to
+** ever contain very many entries, so a fixed number of buckets is
+** used.
+*/
+#define HASHSIZE 97
+
+/* The xBestIndex method of this virtual table requires an estimate of
+** the number of rows in the virtual table to calculate the costs of
+** various strategies. If possible, this estimate is loaded from the
+** sqlite_stat1 table (with RTREE_MIN_ROWEST as a hard-coded minimum).
+** Otherwise, if no sqlite_stat1 entry is available, use
+** RTREE_DEFAULT_ROWEST.
+*/
+#define RTREE_DEFAULT_ROWEST 1048576
+#define RTREE_MIN_ROWEST 100
+
+/*
+** An rtree virtual-table object.
+*/
+struct Rtree {
+ sqlite3_vtab base; /* Base class. Must be first */
+ sqlite3 *db; /* Host database connection */
+ int iNodeSize; /* Size in bytes of each node in the node table */
+ u8 nDim; /* Number of dimensions */
+ u8 nDim2; /* Twice the number of dimensions */
+ u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */
+ u8 nBytesPerCell; /* Bytes consumed per cell */
+ u8 inWrTrans; /* True if inside write transaction */
+ u8 nAux; /* # of auxiliary columns in %_rowid */
+#ifdef SQLITE_ENABLE_GEOPOLY
+ u8 nAuxNotNull; /* Number of initial not-null aux columns */
+#endif
+#ifdef SQLITE_DEBUG
+ u8 bCorrupt; /* Shadow table corruption detected */
+#endif
+ int iDepth; /* Current depth of the r-tree structure */
+ char *zDb; /* Name of database containing r-tree table */
+ char *zName; /* Name of r-tree table */
+ u32 nBusy; /* Current number of users of this structure */
+ i64 nRowEst; /* Estimated number of rows in this table */
+ u32 nCursor; /* Number of open cursors */
+ u32 nNodeRef; /* Number RtreeNodes with positive nRef */
+ char *zReadAuxSql; /* SQL for statement to read aux data */
+
+ /* List of nodes removed during a CondenseTree operation. List is
+ ** linked together via the pointer normally used for hash chains -
+ ** RtreeNode.pNext. RtreeNode.iNode stores the depth of the sub-tree
+ ** headed by the node (leaf nodes have RtreeNode.iNode==0).
+ */
+ RtreeNode *pDeleted;
+ int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */
+
+ /* Blob I/O on xxx_node */
+ sqlite3_blob *pNodeBlob;
+
+ /* Statements to read/write/delete a record from xxx_node */
+ sqlite3_stmt *pWriteNode;
+ sqlite3_stmt *pDeleteNode;
+
+ /* Statements to read/write/delete a record from xxx_rowid */
+ sqlite3_stmt *pReadRowid;
+ sqlite3_stmt *pWriteRowid;
+ sqlite3_stmt *pDeleteRowid;
+
+ /* Statements to read/write/delete a record from xxx_parent */
+ sqlite3_stmt *pReadParent;
+ sqlite3_stmt *pWriteParent;
+ sqlite3_stmt *pDeleteParent;
+
+ /* Statement for writing to the "aux:" fields, if there are any */
+ sqlite3_stmt *pWriteAux;
+
+ RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */
+};
+
+/* Possible values for Rtree.eCoordType: */
+#define RTREE_COORD_REAL32 0
+#define RTREE_COORD_INT32 1
+
+/*
+** If SQLITE_RTREE_INT_ONLY is defined, then this virtual table will
+** only deal with integer coordinates. No floating point operations
+** will be done.
+*/
+#ifdef SQLITE_RTREE_INT_ONLY
+ typedef sqlite3_int64 RtreeDValue; /* High accuracy coordinate */
+ typedef int RtreeValue; /* Low accuracy coordinate */
+# define RTREE_ZERO 0
+#else
+ typedef double RtreeDValue; /* High accuracy coordinate */
+ typedef float RtreeValue; /* Low accuracy coordinate */
+# define RTREE_ZERO 0.0
+#endif
+
+/*
+** Set the Rtree.bCorrupt flag
+*/
+#ifdef SQLITE_DEBUG
+# define RTREE_IS_CORRUPT(X) ((X)->bCorrupt = 1)
+#else
+# define RTREE_IS_CORRUPT(X)
+#endif
+
+/*
+** When doing a search of an r-tree, instances of the following structure
+** record intermediate results from the tree walk.
+**
+** The id is always a node-id. For iLevel>=1 the id is the node-id of
+** the node that the RtreeSearchPoint represents. When iLevel==0, however,
+** the id is of the parent node and the cell that RtreeSearchPoint
+** represents is the iCell-th entry in the parent node.
+*/
+struct RtreeSearchPoint {
+ RtreeDValue rScore; /* The score for this node. Smallest goes first. */
+ sqlite3_int64 id; /* Node ID */
+ u8 iLevel; /* 0=entries. 1=leaf node. 2+ for higher */
+ u8 eWithin; /* PARTLY_WITHIN or FULLY_WITHIN */
+ u8 iCell; /* Cell index within the node */
+};
+
+/*
+** The minimum number of cells allowed for a node is a third of the
+** maximum. In Gutman's notation:
+**
+** m = M/3
+**
+** If an R*-tree "Reinsert" operation is required, the same number of
+** cells are removed from the overfull node and reinserted into the tree.
+*/
+#define RTREE_MINCELLS(p) ((((p)->iNodeSize-4)/(p)->nBytesPerCell)/3)
+#define RTREE_REINSERT(p) RTREE_MINCELLS(p)
+#define RTREE_MAXCELLS 51
+
+/*
+** The smallest possible node-size is (512-64)==448 bytes. And the largest
+** supported cell size is 48 bytes (8 byte rowid + ten 4 byte coordinates).
+** Therefore all non-root nodes must contain at least 3 entries. Since
+** 3^40 is greater than 2^64, an r-tree structure always has a depth of
+** 40 or less.
+*/
+#define RTREE_MAX_DEPTH 40
+
+
+/*
+** Number of entries in the cursor RtreeNode cache. The first entry is
+** used to cache the RtreeNode for RtreeCursor.sPoint. The remaining
+** entries cache the RtreeNode for the first elements of the priority queue.
+*/
+#define RTREE_CACHE_SZ 5
+
+/*
+** An rtree cursor object.
+*/
+struct RtreeCursor {
+ sqlite3_vtab_cursor base; /* Base class. Must be first */
+ u8 atEOF; /* True if at end of search */
+ u8 bPoint; /* True if sPoint is valid */
+ u8 bAuxValid; /* True if pReadAux is valid */
+ int iStrategy; /* Copy of idxNum search parameter */
+ int nConstraint; /* Number of entries in aConstraint */
+ RtreeConstraint *aConstraint; /* Search constraints. */
+ int nPointAlloc; /* Number of slots allocated for aPoint[] */
+ int nPoint; /* Number of slots used in aPoint[] */
+ int mxLevel; /* iLevel value for root of the tree */
+ RtreeSearchPoint *aPoint; /* Priority queue for search points */
+ sqlite3_stmt *pReadAux; /* Statement to read aux-data */
+ RtreeSearchPoint sPoint; /* Cached next search point */
+ RtreeNode *aNode[RTREE_CACHE_SZ]; /* Rtree node cache */
+ u32 anQueue[RTREE_MAX_DEPTH+1]; /* Number of queued entries by iLevel */
+};
+
+/* Return the Rtree of a RtreeCursor */
+#define RTREE_OF_CURSOR(X) ((Rtree*)((X)->base.pVtab))
+
+/*
+** A coordinate can be either a floating point number or a integer. All
+** coordinates within a single R-Tree are always of the same time.
+*/
+union RtreeCoord {
+ RtreeValue f; /* Floating point value */
+ int i; /* Integer value */
+ u32 u; /* Unsigned for byte-order conversions */
+};
+
+/*
+** The argument is an RtreeCoord. Return the value stored within the RtreeCoord
+** formatted as a RtreeDValue (double or int64). This macro assumes that local
+** variable pRtree points to the Rtree structure associated with the
+** RtreeCoord.
+*/
+#ifdef SQLITE_RTREE_INT_ONLY
+# define DCOORD(coord) ((RtreeDValue)coord.i)
+#else
+# define DCOORD(coord) ( \
+ (pRtree->eCoordType==RTREE_COORD_REAL32) ? \
+ ((double)coord.f) : \
+ ((double)coord.i) \
+ )
+#endif
+
+/*
+** A search constraint.
+*/
+struct RtreeConstraint {
+ int iCoord; /* Index of constrained coordinate */
+ int op; /* Constraining operation */
+ union {
+ RtreeDValue rValue; /* Constraint value. */
+ int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*);
+ int (*xQueryFunc)(sqlite3_rtree_query_info*);
+ } u;
+ sqlite3_rtree_query_info *pInfo; /* xGeom and xQueryFunc argument */
+};
+
+/* Possible values for RtreeConstraint.op */
+#define RTREE_EQ 0x41 /* A */
+#define RTREE_LE 0x42 /* B */
+#define RTREE_LT 0x43 /* C */
+#define RTREE_GE 0x44 /* D */
+#define RTREE_GT 0x45 /* E */
+#define RTREE_MATCH 0x46 /* F: Old-style sqlite3_rtree_geometry_callback() */
+#define RTREE_QUERY 0x47 /* G: New-style sqlite3_rtree_query_callback() */
+
+/* Special operators available only on cursors. Needs to be consecutive
+** with the normal values above, but must be less than RTREE_MATCH. These
+** are used in the cursor for contraints such as x=NULL (RTREE_FALSE) or
+** x<'xyz' (RTREE_TRUE) */
+#define RTREE_TRUE 0x3f /* ? */
+#define RTREE_FALSE 0x40 /* @ */
+
+/*
+** An rtree structure node.
+*/
+struct RtreeNode {
+ RtreeNode *pParent; /* Parent node */
+ i64 iNode; /* The node number */
+ int nRef; /* Number of references to this node */
+ int isDirty; /* True if the node needs to be written to disk */
+ u8 *zData; /* Content of the node, as should be on disk */
+ RtreeNode *pNext; /* Next node in this hash collision chain */
+};
+
+/* Return the number of cells in a node */
+#define NCELL(pNode) readInt16(&(pNode)->zData[2])
+
+/*
+** A single cell from a node, deserialized
+*/
+struct RtreeCell {
+ i64 iRowid; /* Node or entry ID */
+ RtreeCoord aCoord[RTREE_MAX_DIMENSIONS*2]; /* Bounding box coordinates */
+};
+
+
+/*
+** This object becomes the sqlite3_user_data() for the SQL functions
+** that are created by sqlite3_rtree_geometry_callback() and
+** sqlite3_rtree_query_callback() and which appear on the right of MATCH
+** operators in order to constrain a search.
+**
+** xGeom and xQueryFunc are the callback functions. Exactly one of
+** xGeom and xQueryFunc fields is non-NULL, depending on whether the
+** SQL function was created using sqlite3_rtree_geometry_callback() or
+** sqlite3_rtree_query_callback().
+**
+** This object is deleted automatically by the destructor mechanism in
+** sqlite3_create_function_v2().
+*/
+struct RtreeGeomCallback {
+ int (*xGeom)(sqlite3_rtree_geometry*, int, RtreeDValue*, int*);
+ int (*xQueryFunc)(sqlite3_rtree_query_info*);
+ void (*xDestructor)(void*);
+ void *pContext;
+};
+
+/*
+** An instance of this structure (in the form of a BLOB) is returned by
+** the SQL functions that sqlite3_rtree_geometry_callback() and
+** sqlite3_rtree_query_callback() create, and is read as the right-hand
+** operand to the MATCH operator of an R-Tree.
+*/
+struct RtreeMatchArg {
+ u32 iSize; /* Size of this object */
+ RtreeGeomCallback cb; /* Info about the callback functions */
+ int nParam; /* Number of parameters to the SQL function */
+ sqlite3_value **apSqlParam; /* Original SQL parameter values */
+ RtreeDValue aParam[1]; /* Values for parameters to the SQL function */
+};
+
+#ifndef MAX
+# define MAX(x,y) ((x) < (y) ? (y) : (x))
+#endif
+#ifndef MIN
+# define MIN(x,y) ((x) > (y) ? (y) : (x))
+#endif
+
+/* What version of GCC is being used. 0 means GCC is not being used .
+** Note that the GCC_VERSION macro will also be set correctly when using
+** clang, since clang works hard to be gcc compatible. So the gcc
+** optimizations will also work when compiling with clang.
+*/
+#ifndef GCC_VERSION
+#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__)
+#else
+# define GCC_VERSION 0
+#endif
+#endif
+
+/* The testcase() macro should already be defined in the amalgamation. If
+** it is not, make it a no-op.
+*/
+#ifndef SQLITE_AMALGAMATION
+# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG)
+ unsigned int sqlite3RtreeTestcase = 0;
+# define testcase(X) if( X ){ sqlite3RtreeTestcase += __LINE__; }
+# else
+# define testcase(X)
+# endif
+#endif
+
+/*
+** Make sure that the compiler intrinsics we desire are enabled when
+** compiling with an appropriate version of MSVC unless prevented by
+** the SQLITE_DISABLE_INTRINSIC define.
+*/
+#if !defined(SQLITE_DISABLE_INTRINSIC)
+# if defined(_MSC_VER) && _MSC_VER>=1400
+# if !defined(_WIN32_WCE)
+# include <intrin.h>
+# pragma intrinsic(_byteswap_ulong)
+# pragma intrinsic(_byteswap_uint64)
+# else
+# include <cmnintrin.h>
+# endif
+# endif
+#endif
+
+/*
+** Macros to determine whether the machine is big or little endian,
+** and whether or not that determination is run-time or compile-time.
+**
+** For best performance, an attempt is made to guess at the byte-order
+** using C-preprocessor macros. If that is unsuccessful, or if
+** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined
+** at run-time.
+*/
+#ifndef SQLITE_BYTEORDER
+#if defined(i386) || defined(__i386__) || defined(_M_IX86) || \
+ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \
+ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \
+ defined(__arm__)
+# define SQLITE_BYTEORDER 1234
+#elif defined(sparc) || defined(__ppc__)
+# define SQLITE_BYTEORDER 4321
+#else
+# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */
+#endif
+#endif
+
+
+/* What version of MSVC is being used. 0 means MSVC is not being used */
+#ifndef MSVC_VERSION
+#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC)
+# define MSVC_VERSION _MSC_VER
+#else
+# define MSVC_VERSION 0
+#endif
+#endif
+
+/*
+** Functions to deserialize a 16 bit integer, 32 bit real number and
+** 64 bit integer. The deserialized value is returned.
+*/
+static int readInt16(u8 *p){
+ return (p[0]<<8) + p[1];
+}
+static void readCoord(u8 *p, RtreeCoord *pCoord){
+ assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */
+#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+ pCoord->u = _byteswap_ulong(*(u32*)p);
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+ pCoord->u = __builtin_bswap32(*(u32*)p);
+#elif SQLITE_BYTEORDER==4321
+ pCoord->u = *(u32*)p;
+#else
+ pCoord->u = (
+ (((u32)p[0]) << 24) +
+ (((u32)p[1]) << 16) +
+ (((u32)p[2]) << 8) +
+ (((u32)p[3]) << 0)
+ );
+#endif
+}
+static i64 readInt64(u8 *p){
+#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+ u64 x;
+ memcpy(&x, p, 8);
+ return (i64)_byteswap_uint64(x);
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+ u64 x;
+ memcpy(&x, p, 8);
+ return (i64)__builtin_bswap64(x);
+#elif SQLITE_BYTEORDER==4321
+ i64 x;
+ memcpy(&x, p, 8);
+ return x;
+#else
+ return (i64)(
+ (((u64)p[0]) << 56) +
+ (((u64)p[1]) << 48) +
+ (((u64)p[2]) << 40) +
+ (((u64)p[3]) << 32) +
+ (((u64)p[4]) << 24) +
+ (((u64)p[5]) << 16) +
+ (((u64)p[6]) << 8) +
+ (((u64)p[7]) << 0)
+ );
+#endif
+}
+
+/*
+** Functions to serialize a 16 bit integer, 32 bit real number and
+** 64 bit integer. The value returned is the number of bytes written
+** to the argument buffer (always 2, 4 and 8 respectively).
+*/
+static void writeInt16(u8 *p, int i){
+ p[0] = (i>> 8)&0xFF;
+ p[1] = (i>> 0)&0xFF;
+}
+static int writeCoord(u8 *p, RtreeCoord *pCoord){
+ u32 i;
+ assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */
+ assert( sizeof(RtreeCoord)==4 );
+ assert( sizeof(u32)==4 );
+#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+ i = __builtin_bswap32(pCoord->u);
+ memcpy(p, &i, 4);
+#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+ i = _byteswap_ulong(pCoord->u);
+ memcpy(p, &i, 4);
+#elif SQLITE_BYTEORDER==4321
+ i = pCoord->u;
+ memcpy(p, &i, 4);
+#else
+ i = pCoord->u;
+ p[0] = (i>>24)&0xFF;
+ p[1] = (i>>16)&0xFF;
+ p[2] = (i>> 8)&0xFF;
+ p[3] = (i>> 0)&0xFF;
+#endif
+ return 4;
+}
+static int writeInt64(u8 *p, i64 i){
+#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+ i = (i64)__builtin_bswap64((u64)i);
+ memcpy(p, &i, 8);
+#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+ i = (i64)_byteswap_uint64((u64)i);
+ memcpy(p, &i, 8);
+#elif SQLITE_BYTEORDER==4321
+ memcpy(p, &i, 8);
+#else
+ p[0] = (i>>56)&0xFF;
+ p[1] = (i>>48)&0xFF;
+ p[2] = (i>>40)&0xFF;
+ p[3] = (i>>32)&0xFF;
+ p[4] = (i>>24)&0xFF;
+ p[5] = (i>>16)&0xFF;
+ p[6] = (i>> 8)&0xFF;
+ p[7] = (i>> 0)&0xFF;
+#endif
+ return 8;
+}
+
+/*
+** Increment the reference count of node p.
+*/
+static void nodeReference(RtreeNode *p){
+ if( p ){
+ assert( p->nRef>0 );
+ p->nRef++;
+ }
+}
+
+/*
+** Clear the content of node p (set all bytes to 0x00).
+*/
+static void nodeZero(Rtree *pRtree, RtreeNode *p){
+ memset(&p->zData[2], 0, pRtree->iNodeSize-2);
+ p->isDirty = 1;
+}
+
+/*
+** Given a node number iNode, return the corresponding key to use
+** in the Rtree.aHash table.
+*/
+static unsigned int nodeHash(i64 iNode){
+ return ((unsigned)iNode) % HASHSIZE;
+}
+
+/*
+** Search the node hash table for node iNode. If found, return a pointer
+** to it. Otherwise, return 0.
+*/
+static RtreeNode *nodeHashLookup(Rtree *pRtree, i64 iNode){
+ RtreeNode *p;
+ for(p=pRtree->aHash[nodeHash(iNode)]; p && p->iNode!=iNode; p=p->pNext);
+ return p;
+}
+
+/*
+** Add node pNode to the node hash table.
+*/
+static void nodeHashInsert(Rtree *pRtree, RtreeNode *pNode){
+ int iHash;
+ assert( pNode->pNext==0 );
+ iHash = nodeHash(pNode->iNode);
+ pNode->pNext = pRtree->aHash[iHash];
+ pRtree->aHash[iHash] = pNode;
+}
+
+/*
+** Remove node pNode from the node hash table.
+*/
+static void nodeHashDelete(Rtree *pRtree, RtreeNode *pNode){
+ RtreeNode **pp;
+ if( pNode->iNode!=0 ){
+ pp = &pRtree->aHash[nodeHash(pNode->iNode)];
+ for( ; (*pp)!=pNode; pp = &(*pp)->pNext){ assert(*pp); }
+ *pp = pNode->pNext;
+ pNode->pNext = 0;
+ }
+}
+
+/*
+** Allocate and return new r-tree node. Initially, (RtreeNode.iNode==0),
+** indicating that node has not yet been assigned a node number. It is
+** assigned a node number when nodeWrite() is called to write the
+** node contents out to the database.
+*/
+static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){
+ RtreeNode *pNode;
+ pNode = (RtreeNode *)sqlite3_malloc64(sizeof(RtreeNode) + pRtree->iNodeSize);
+ if( pNode ){
+ memset(pNode, 0, sizeof(RtreeNode) + pRtree->iNodeSize);
+ pNode->zData = (u8 *)&pNode[1];
+ pNode->nRef = 1;
+ pRtree->nNodeRef++;
+ pNode->pParent = pParent;
+ pNode->isDirty = 1;
+ nodeReference(pParent);
+ }
+ return pNode;
+}
+
+/*
+** Clear the Rtree.pNodeBlob object
+*/
+static void nodeBlobReset(Rtree *pRtree){
+ if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){
+ sqlite3_blob *pBlob = pRtree->pNodeBlob;
+ pRtree->pNodeBlob = 0;
+ sqlite3_blob_close(pBlob);
+ }
+}
+
+/*
+** Obtain a reference to an r-tree node.
+*/
+static int nodeAcquire(
+ Rtree *pRtree, /* R-tree structure */
+ i64 iNode, /* Node number to load */
+ RtreeNode *pParent, /* Either the parent node or NULL */
+ RtreeNode **ppNode /* OUT: Acquired node */
+){
+ int rc = SQLITE_OK;
+ RtreeNode *pNode = 0;
+
+ /* Check if the requested node is already in the hash table. If so,
+ ** increase its reference count and return it.
+ */
+ if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){
+ if( pParent && pParent!=pNode->pParent ){
+ RTREE_IS_CORRUPT(pRtree);
+ return SQLITE_CORRUPT_VTAB;
+ }
+ pNode->nRef++;
+ *ppNode = pNode;
+ return SQLITE_OK;
+ }
+
+ if( pRtree->pNodeBlob ){
+ sqlite3_blob *pBlob = pRtree->pNodeBlob;
+ pRtree->pNodeBlob = 0;
+ rc = sqlite3_blob_reopen(pBlob, iNode);
+ pRtree->pNodeBlob = pBlob;
+ if( rc ){
+ nodeBlobReset(pRtree);
+ if( rc==SQLITE_NOMEM ) return SQLITE_NOMEM;
+ }
+ }
+ if( pRtree->pNodeBlob==0 ){
+ char *zTab = sqlite3_mprintf("%s_node", pRtree->zName);
+ if( zTab==0 ) return SQLITE_NOMEM;
+ rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0,
+ &pRtree->pNodeBlob);
+ sqlite3_free(zTab);
+ }
+ if( rc ){
+ nodeBlobReset(pRtree);
+ *ppNode = 0;
+ /* If unable to open an sqlite3_blob on the desired row, that can only
+ ** be because the shadow tables hold erroneous data. */
+ if( rc==SQLITE_ERROR ){
+ rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
+ }
+ }else if( pRtree->iNodeSize==sqlite3_blob_bytes(pRtree->pNodeBlob) ){
+ pNode = (RtreeNode *)sqlite3_malloc64(sizeof(RtreeNode)+pRtree->iNodeSize);
+ if( !pNode ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pNode->pParent = pParent;
+ pNode->zData = (u8 *)&pNode[1];
+ pNode->nRef = 1;
+ pRtree->nNodeRef++;
+ pNode->iNode = iNode;
+ pNode->isDirty = 0;
+ pNode->pNext = 0;
+ rc = sqlite3_blob_read(pRtree->pNodeBlob, pNode->zData,
+ pRtree->iNodeSize, 0);
+ }
+ }
+
+ /* If the root node was just loaded, set pRtree->iDepth to the height
+ ** of the r-tree structure. A height of zero means all data is stored on
+ ** the root node. A height of one means the children of the root node
+ ** are the leaves, and so on. If the depth as specified on the root node
+ ** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt.
+ */
+ if( rc==SQLITE_OK && pNode && iNode==1 ){
+ pRtree->iDepth = readInt16(pNode->zData);
+ if( pRtree->iDepth>RTREE_MAX_DEPTH ){
+ rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
+ }
+ }
+
+ /* If no error has occurred so far, check if the "number of entries"
+ ** field on the node is too large. If so, set the return code to
+ ** SQLITE_CORRUPT_VTAB.
+ */
+ if( pNode && rc==SQLITE_OK ){
+ if( NCELL(pNode)>((pRtree->iNodeSize-4)/pRtree->nBytesPerCell) ){
+ rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ if( pNode!=0 ){
+ nodeReference(pParent);
+ nodeHashInsert(pRtree, pNode);
+ }else{
+ rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
+ }
+ *ppNode = pNode;
+ }else{
+ if( pNode ){
+ pRtree->nNodeRef--;
+ sqlite3_free(pNode);
+ }
+ *ppNode = 0;
+ }
+
+ return rc;
+}
+
+/*
+** Overwrite cell iCell of node pNode with the contents of pCell.
+*/
+static void nodeOverwriteCell(
+ Rtree *pRtree, /* The overall R-Tree */
+ RtreeNode *pNode, /* The node into which the cell is to be written */
+ RtreeCell *pCell, /* The cell to write */
+ int iCell /* Index into pNode into which pCell is written */
+){
+ int ii;
+ u8 *p = &pNode->zData[4 + pRtree->nBytesPerCell*iCell];
+ p += writeInt64(p, pCell->iRowid);
+ for(ii=0; ii<pRtree->nDim2; ii++){
+ p += writeCoord(p, &pCell->aCoord[ii]);
+ }
+ pNode->isDirty = 1;
+}
+
+/*
+** Remove the cell with index iCell from node pNode.
+*/
+static void nodeDeleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell){
+ u8 *pDst = &pNode->zData[4 + pRtree->nBytesPerCell*iCell];
+ u8 *pSrc = &pDst[pRtree->nBytesPerCell];
+ int nByte = (NCELL(pNode) - iCell - 1) * pRtree->nBytesPerCell;
+ memmove(pDst, pSrc, nByte);
+ writeInt16(&pNode->zData[2], NCELL(pNode)-1);
+ pNode->isDirty = 1;
+}
+
+/*
+** Insert the contents of cell pCell into node pNode. If the insert
+** is successful, return SQLITE_OK.
+**
+** If there is not enough free space in pNode, return SQLITE_FULL.
+*/
+static int nodeInsertCell(
+ Rtree *pRtree, /* The overall R-Tree */
+ RtreeNode *pNode, /* Write new cell into this node */
+ RtreeCell *pCell /* The cell to be inserted */
+){
+ int nCell; /* Current number of cells in pNode */
+ int nMaxCell; /* Maximum number of cells for pNode */
+
+ nMaxCell = (pRtree->iNodeSize-4)/pRtree->nBytesPerCell;
+ nCell = NCELL(pNode);
+
+ assert( nCell<=nMaxCell );
+ if( nCell<nMaxCell ){
+ nodeOverwriteCell(pRtree, pNode, pCell, nCell);
+ writeInt16(&pNode->zData[2], nCell+1);
+ pNode->isDirty = 1;
+ }
+
+ return (nCell==nMaxCell);
+}
+
+/*
+** If the node is dirty, write it out to the database.
+*/
+static int nodeWrite(Rtree *pRtree, RtreeNode *pNode){
+ int rc = SQLITE_OK;
+ if( pNode->isDirty ){
+ sqlite3_stmt *p = pRtree->pWriteNode;
+ if( pNode->iNode ){
+ sqlite3_bind_int64(p, 1, pNode->iNode);
+ }else{
+ sqlite3_bind_null(p, 1);
+ }
+ sqlite3_bind_blob(p, 2, pNode->zData, pRtree->iNodeSize, SQLITE_STATIC);
+ sqlite3_step(p);
+ pNode->isDirty = 0;
+ rc = sqlite3_reset(p);
+ sqlite3_bind_null(p, 2);
+ if( pNode->iNode==0 && rc==SQLITE_OK ){
+ pNode->iNode = sqlite3_last_insert_rowid(pRtree->db);
+ nodeHashInsert(pRtree, pNode);
+ }
+ }
+ return rc;
+}
+
+/*
+** Release a reference to a node. If the node is dirty and the reference
+** count drops to zero, the node data is written to the database.
+*/
+static int nodeRelease(Rtree *pRtree, RtreeNode *pNode){
+ int rc = SQLITE_OK;
+ if( pNode ){
+ assert( pNode->nRef>0 );
+ assert( pRtree->nNodeRef>0 );
+ pNode->nRef--;
+ if( pNode->nRef==0 ){
+ pRtree->nNodeRef--;
+ if( pNode->iNode==1 ){
+ pRtree->iDepth = -1;
+ }
+ if( pNode->pParent ){
+ rc = nodeRelease(pRtree, pNode->pParent);
+ }
+ if( rc==SQLITE_OK ){
+ rc = nodeWrite(pRtree, pNode);
+ }
+ nodeHashDelete(pRtree, pNode);
+ sqlite3_free(pNode);
+ }
+ }
+ return rc;
+}
+
+/*
+** Return the 64-bit integer value associated with cell iCell of
+** node pNode. If pNode is a leaf node, this is a rowid. If it is
+** an internal node, then the 64-bit integer is a child page number.
+*/
+static i64 nodeGetRowid(
+ Rtree *pRtree, /* The overall R-Tree */
+ RtreeNode *pNode, /* The node from which to extract the ID */
+ int iCell /* The cell index from which to extract the ID */
+){
+ assert( iCell<NCELL(pNode) );
+ return readInt64(&pNode->zData[4 + pRtree->nBytesPerCell*iCell]);
+}
+
+/*
+** Return coordinate iCoord from cell iCell in node pNode.
+*/
+static void nodeGetCoord(
+ Rtree *pRtree, /* The overall R-Tree */
+ RtreeNode *pNode, /* The node from which to extract a coordinate */
+ int iCell, /* The index of the cell within the node */
+ int iCoord, /* Which coordinate to extract */
+ RtreeCoord *pCoord /* OUT: Space to write result to */
+){
+ readCoord(&pNode->zData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord);
+}
+
+/*
+** Deserialize cell iCell of node pNode. Populate the structure pointed
+** to by pCell with the results.
+*/
+static void nodeGetCell(
+ Rtree *pRtree, /* The overall R-Tree */
+ RtreeNode *pNode, /* The node containing the cell to be read */
+ int iCell, /* Index of the cell within the node */
+ RtreeCell *pCell /* OUT: Write the cell contents here */
+){
+ u8 *pData;
+ RtreeCoord *pCoord;
+ int ii = 0;
+ pCell->iRowid = nodeGetRowid(pRtree, pNode, iCell);
+ pData = pNode->zData + (12 + pRtree->nBytesPerCell*iCell);
+ pCoord = pCell->aCoord;
+ do{
+ readCoord(pData, &pCoord[ii]);
+ readCoord(pData+4, &pCoord[ii+1]);
+ pData += 8;
+ ii += 2;
+ }while( ii<pRtree->nDim2 );
+}
+
+
+/* Forward declaration for the function that does the work of
+** the virtual table module xCreate() and xConnect() methods.
+*/
+static int rtreeInit(
+ sqlite3 *, void *, int, const char *const*, sqlite3_vtab **, char **, int
+);
+
+/*
+** Rtree virtual table module xCreate method.
+*/
+static int rtreeCreate(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ return rtreeInit(db, pAux, argc, argv, ppVtab, pzErr, 1);
+}
+
+/*
+** Rtree virtual table module xConnect method.
+*/
+static int rtreeConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ return rtreeInit(db, pAux, argc, argv, ppVtab, pzErr, 0);
+}
+
+/*
+** Increment the r-tree reference count.
+*/
+static void rtreeReference(Rtree *pRtree){
+ pRtree->nBusy++;
+}
+
+/*
+** Decrement the r-tree reference count. When the reference count reaches
+** zero the structure is deleted.
+*/
+static void rtreeRelease(Rtree *pRtree){
+ pRtree->nBusy--;
+ if( pRtree->nBusy==0 ){
+ pRtree->inWrTrans = 0;
+ assert( pRtree->nCursor==0 );
+ nodeBlobReset(pRtree);
+ assert( pRtree->nNodeRef==0 || pRtree->bCorrupt );
+ sqlite3_finalize(pRtree->pWriteNode);
+ sqlite3_finalize(pRtree->pDeleteNode);
+ sqlite3_finalize(pRtree->pReadRowid);
+ sqlite3_finalize(pRtree->pWriteRowid);
+ sqlite3_finalize(pRtree->pDeleteRowid);
+ sqlite3_finalize(pRtree->pReadParent);
+ sqlite3_finalize(pRtree->pWriteParent);
+ sqlite3_finalize(pRtree->pDeleteParent);
+ sqlite3_finalize(pRtree->pWriteAux);
+ sqlite3_free(pRtree->zReadAuxSql);
+ sqlite3_free(pRtree);
+ }
+}
+
+/*
+** Rtree virtual table module xDisconnect method.
+*/
+static int rtreeDisconnect(sqlite3_vtab *pVtab){
+ rtreeRelease((Rtree *)pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** Rtree virtual table module xDestroy method.
+*/
+static int rtreeDestroy(sqlite3_vtab *pVtab){
+ Rtree *pRtree = (Rtree *)pVtab;
+ int rc;
+ char *zCreate = sqlite3_mprintf(
+ "DROP TABLE '%q'.'%q_node';"
+ "DROP TABLE '%q'.'%q_rowid';"
+ "DROP TABLE '%q'.'%q_parent';",
+ pRtree->zDb, pRtree->zName,
+ pRtree->zDb, pRtree->zName,
+ pRtree->zDb, pRtree->zName
+ );
+ if( !zCreate ){
+ rc = SQLITE_NOMEM;
+ }else{
+ nodeBlobReset(pRtree);
+ rc = sqlite3_exec(pRtree->db, zCreate, 0, 0, 0);
+ sqlite3_free(zCreate);
+ }
+ if( rc==SQLITE_OK ){
+ rtreeRelease(pRtree);
+ }
+
+ return rc;
+}
+
+/*
+** Rtree virtual table module xOpen method.
+*/
+static int rtreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+ int rc = SQLITE_NOMEM;
+ Rtree *pRtree = (Rtree *)pVTab;
+ RtreeCursor *pCsr;
+
+ pCsr = (RtreeCursor *)sqlite3_malloc64(sizeof(RtreeCursor));
+ if( pCsr ){
+ memset(pCsr, 0, sizeof(RtreeCursor));
+ pCsr->base.pVtab = pVTab;
+ rc = SQLITE_OK;
+ pRtree->nCursor++;
+ }
+ *ppCursor = (sqlite3_vtab_cursor *)pCsr;
+
+ return rc;
+}
+
+
+/*
+** Reset a cursor back to its initial state.
+*/
+static void resetCursor(RtreeCursor *pCsr){
+ Rtree *pRtree = (Rtree *)(pCsr->base.pVtab);
+ int ii;
+ sqlite3_stmt *pStmt;
+ if( pCsr->aConstraint ){
+ int i; /* Used to iterate through constraint array */
+ for(i=0; i<pCsr->nConstraint; i++){
+ sqlite3_rtree_query_info *pInfo = pCsr->aConstraint[i].pInfo;
+ if( pInfo ){
+ if( pInfo->xDelUser ) pInfo->xDelUser(pInfo->pUser);
+ sqlite3_free(pInfo);
+ }
+ }
+ sqlite3_free(pCsr->aConstraint);
+ pCsr->aConstraint = 0;
+ }
+ for(ii=0; ii<RTREE_CACHE_SZ; ii++) nodeRelease(pRtree, pCsr->aNode[ii]);
+ sqlite3_free(pCsr->aPoint);
+ pStmt = pCsr->pReadAux;
+ memset(pCsr, 0, sizeof(RtreeCursor));
+ pCsr->base.pVtab = (sqlite3_vtab*)pRtree;
+ pCsr->pReadAux = pStmt;
+
+}
+
+/*
+** Rtree virtual table module xClose method.
+*/
+static int rtreeClose(sqlite3_vtab_cursor *cur){
+ Rtree *pRtree = (Rtree *)(cur->pVtab);
+ RtreeCursor *pCsr = (RtreeCursor *)cur;
+ assert( pRtree->nCursor>0 );
+ resetCursor(pCsr);
+ sqlite3_finalize(pCsr->pReadAux);
+ sqlite3_free(pCsr);
+ pRtree->nCursor--;
+ nodeBlobReset(pRtree);
+ return SQLITE_OK;
+}
+
+/*
+** Rtree virtual table module xEof method.
+**
+** Return non-zero if the cursor does not currently point to a valid
+** record (i.e if the scan has finished), or zero otherwise.
+*/
+static int rtreeEof(sqlite3_vtab_cursor *cur){
+ RtreeCursor *pCsr = (RtreeCursor *)cur;
+ return pCsr->atEOF;
+}
+
+/*
+** Convert raw bits from the on-disk RTree record into a coordinate value.
+** The on-disk format is big-endian and needs to be converted for little-
+** endian platforms. The on-disk record stores integer coordinates if
+** eInt is true and it stores 32-bit floating point records if eInt is
+** false. a[] is the four bytes of the on-disk record to be decoded.
+** Store the results in "r".
+**
+** There are five versions of this macro. The last one is generic. The
+** other four are various architectures-specific optimizations.
+*/
+#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300
+#define RTREE_DECODE_COORD(eInt, a, r) { \
+ RtreeCoord c; /* Coordinate decoded */ \
+ c.u = _byteswap_ulong(*(u32*)a); \
+ r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \
+}
+#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000
+#define RTREE_DECODE_COORD(eInt, a, r) { \
+ RtreeCoord c; /* Coordinate decoded */ \
+ c.u = __builtin_bswap32(*(u32*)a); \
+ r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \
+}
+#elif SQLITE_BYTEORDER==1234
+#define RTREE_DECODE_COORD(eInt, a, r) { \
+ RtreeCoord c; /* Coordinate decoded */ \
+ memcpy(&c.u,a,4); \
+ c.u = ((c.u>>24)&0xff)|((c.u>>8)&0xff00)| \
+ ((c.u&0xff)<<24)|((c.u&0xff00)<<8); \
+ r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \
+}
+#elif SQLITE_BYTEORDER==4321
+#define RTREE_DECODE_COORD(eInt, a, r) { \
+ RtreeCoord c; /* Coordinate decoded */ \
+ memcpy(&c.u,a,4); \
+ r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \
+}
+#else
+#define RTREE_DECODE_COORD(eInt, a, r) { \
+ RtreeCoord c; /* Coordinate decoded */ \
+ c.u = ((u32)a[0]<<24) + ((u32)a[1]<<16) \
+ +((u32)a[2]<<8) + a[3]; \
+ r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \
+}
+#endif
+
+/*
+** Check the RTree node or entry given by pCellData and p against the MATCH
+** constraint pConstraint.
+*/
+static int rtreeCallbackConstraint(
+ RtreeConstraint *pConstraint, /* The constraint to test */
+ int eInt, /* True if RTree holding integer coordinates */
+ u8 *pCellData, /* Raw cell content */
+ RtreeSearchPoint *pSearch, /* Container of this cell */
+ sqlite3_rtree_dbl *prScore, /* OUT: score for the cell */
+ int *peWithin /* OUT: visibility of the cell */
+){
+ sqlite3_rtree_query_info *pInfo = pConstraint->pInfo; /* Callback info */
+ int nCoord = pInfo->nCoord; /* No. of coordinates */
+ int rc; /* Callback return code */
+ RtreeCoord c; /* Translator union */
+ sqlite3_rtree_dbl aCoord[RTREE_MAX_DIMENSIONS*2]; /* Decoded coordinates */
+
+ assert( pConstraint->op==RTREE_MATCH || pConstraint->op==RTREE_QUERY );
+ assert( nCoord==2 || nCoord==4 || nCoord==6 || nCoord==8 || nCoord==10 );
+
+ if( pConstraint->op==RTREE_QUERY && pSearch->iLevel==1 ){
+ pInfo->iRowid = readInt64(pCellData);
+ }
+ pCellData += 8;
+#ifndef SQLITE_RTREE_INT_ONLY
+ if( eInt==0 ){
+ switch( nCoord ){
+ case 10: readCoord(pCellData+36, &c); aCoord[9] = c.f;
+ readCoord(pCellData+32, &c); aCoord[8] = c.f;
+ case 8: readCoord(pCellData+28, &c); aCoord[7] = c.f;
+ readCoord(pCellData+24, &c); aCoord[6] = c.f;
+ case 6: readCoord(pCellData+20, &c); aCoord[5] = c.f;
+ readCoord(pCellData+16, &c); aCoord[4] = c.f;
+ case 4: readCoord(pCellData+12, &c); aCoord[3] = c.f;
+ readCoord(pCellData+8, &c); aCoord[2] = c.f;
+ default: readCoord(pCellData+4, &c); aCoord[1] = c.f;
+ readCoord(pCellData, &c); aCoord[0] = c.f;
+ }
+ }else
+#endif
+ {
+ switch( nCoord ){
+ case 10: readCoord(pCellData+36, &c); aCoord[9] = c.i;
+ readCoord(pCellData+32, &c); aCoord[8] = c.i;
+ case 8: readCoord(pCellData+28, &c); aCoord[7] = c.i;
+ readCoord(pCellData+24, &c); aCoord[6] = c.i;
+ case 6: readCoord(pCellData+20, &c); aCoord[5] = c.i;
+ readCoord(pCellData+16, &c); aCoord[4] = c.i;
+ case 4: readCoord(pCellData+12, &c); aCoord[3] = c.i;
+ readCoord(pCellData+8, &c); aCoord[2] = c.i;
+ default: readCoord(pCellData+4, &c); aCoord[1] = c.i;
+ readCoord(pCellData, &c); aCoord[0] = c.i;
+ }
+ }
+ if( pConstraint->op==RTREE_MATCH ){
+ int eWithin = 0;
+ rc = pConstraint->u.xGeom((sqlite3_rtree_geometry*)pInfo,
+ nCoord, aCoord, &eWithin);
+ if( eWithin==0 ) *peWithin = NOT_WITHIN;
+ *prScore = RTREE_ZERO;
+ }else{
+ pInfo->aCoord = aCoord;
+ pInfo->iLevel = pSearch->iLevel - 1;
+ pInfo->rScore = pInfo->rParentScore = pSearch->rScore;
+ pInfo->eWithin = pInfo->eParentWithin = pSearch->eWithin;
+ rc = pConstraint->u.xQueryFunc(pInfo);
+ if( pInfo->eWithin<*peWithin ) *peWithin = pInfo->eWithin;
+ if( pInfo->rScore<*prScore || *prScore<RTREE_ZERO ){
+ *prScore = pInfo->rScore;
+ }
+ }
+ return rc;
+}
+
+/*
+** Check the internal RTree node given by pCellData against constraint p.
+** If this constraint cannot be satisfied by any child within the node,
+** set *peWithin to NOT_WITHIN.
+*/
+static void rtreeNonleafConstraint(
+ RtreeConstraint *p, /* The constraint to test */
+ int eInt, /* True if RTree holds integer coordinates */
+ u8 *pCellData, /* Raw cell content as appears on disk */
+ int *peWithin /* Adjust downward, as appropriate */
+){
+ sqlite3_rtree_dbl val; /* Coordinate value convert to a double */
+
+ /* p->iCoord might point to either a lower or upper bound coordinate
+ ** in a coordinate pair. But make pCellData point to the lower bound.
+ */
+ pCellData += 8 + 4*(p->iCoord&0xfe);
+
+ assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE
+ || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
+ || p->op==RTREE_FALSE );
+ assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */
+ switch( p->op ){
+ case RTREE_TRUE: return; /* Always satisfied */
+ case RTREE_FALSE: break; /* Never satisfied */
+ case RTREE_EQ:
+ RTREE_DECODE_COORD(eInt, pCellData, val);
+ /* val now holds the lower bound of the coordinate pair */
+ if( p->u.rValue>=val ){
+ pCellData += 4;
+ RTREE_DECODE_COORD(eInt, pCellData, val);
+ /* val now holds the upper bound of the coordinate pair */
+ if( p->u.rValue<=val ) return;
+ }
+ break;
+ case RTREE_LE:
+ case RTREE_LT:
+ RTREE_DECODE_COORD(eInt, pCellData, val);
+ /* val now holds the lower bound of the coordinate pair */
+ if( p->u.rValue>=val ) return;
+ break;
+
+ default:
+ pCellData += 4;
+ RTREE_DECODE_COORD(eInt, pCellData, val);
+ /* val now holds the upper bound of the coordinate pair */
+ if( p->u.rValue<=val ) return;
+ break;
+ }
+ *peWithin = NOT_WITHIN;
+}
+
+/*
+** Check the leaf RTree cell given by pCellData against constraint p.
+** If this constraint is not satisfied, set *peWithin to NOT_WITHIN.
+** If the constraint is satisfied, leave *peWithin unchanged.
+**
+** The constraint is of the form: xN op $val
+**
+** The op is given by p->op. The xN is p->iCoord-th coordinate in
+** pCellData. $val is given by p->u.rValue.
+*/
+static void rtreeLeafConstraint(
+ RtreeConstraint *p, /* The constraint to test */
+ int eInt, /* True if RTree holds integer coordinates */
+ u8 *pCellData, /* Raw cell content as appears on disk */
+ int *peWithin /* Adjust downward, as appropriate */
+){
+ RtreeDValue xN; /* Coordinate value converted to a double */
+
+ assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE
+ || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE
+ || p->op==RTREE_FALSE );
+ pCellData += 8 + p->iCoord*4;
+ assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */
+ RTREE_DECODE_COORD(eInt, pCellData, xN);
+ switch( p->op ){
+ case RTREE_TRUE: return; /* Always satisfied */
+ case RTREE_FALSE: break; /* Never satisfied */
+ case RTREE_LE: if( xN <= p->u.rValue ) return; break;
+ case RTREE_LT: if( xN < p->u.rValue ) return; break;
+ case RTREE_GE: if( xN >= p->u.rValue ) return; break;
+ case RTREE_GT: if( xN > p->u.rValue ) return; break;
+ default: if( xN == p->u.rValue ) return; break;
+ }
+ *peWithin = NOT_WITHIN;
+}
+
+/*
+** One of the cells in node pNode is guaranteed to have a 64-bit
+** integer value equal to iRowid. Return the index of this cell.
+*/
+static int nodeRowidIndex(
+ Rtree *pRtree,
+ RtreeNode *pNode,
+ i64 iRowid,
+ int *piIndex
+){
+ int ii;
+ int nCell = NCELL(pNode);
+ assert( nCell<200 );
+ for(ii=0; ii<nCell; ii++){
+ if( nodeGetRowid(pRtree, pNode, ii)==iRowid ){
+ *piIndex = ii;
+ return SQLITE_OK;
+ }
+ }
+ RTREE_IS_CORRUPT(pRtree);
+ return SQLITE_CORRUPT_VTAB;
+}
+
+/*
+** Return the index of the cell containing a pointer to node pNode
+** in its parent. If pNode is the root node, return -1.
+*/
+static int nodeParentIndex(Rtree *pRtree, RtreeNode *pNode, int *piIndex){
+ RtreeNode *pParent = pNode->pParent;
+ if( ALWAYS(pParent) ){
+ return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex);
+ }else{
+ *piIndex = -1;
+ return SQLITE_OK;
+ }
+}
+
+/*
+** Compare two search points. Return negative, zero, or positive if the first
+** is less than, equal to, or greater than the second.
+**
+** The rScore is the primary key. Smaller rScore values come first.
+** If the rScore is a tie, then use iLevel as the tie breaker with smaller
+** iLevel values coming first. In this way, if rScore is the same for all
+** SearchPoints, then iLevel becomes the deciding factor and the result
+** is a depth-first search, which is the desired default behavior.
+*/
+static int rtreeSearchPointCompare(
+ const RtreeSearchPoint *pA,
+ const RtreeSearchPoint *pB
+){
+ if( pA->rScore<pB->rScore ) return -1;
+ if( pA->rScore>pB->rScore ) return +1;
+ if( pA->iLevel<pB->iLevel ) return -1;
+ if( pA->iLevel>pB->iLevel ) return +1;
+ return 0;
+}
+
+/*
+** Interchange two search points in a cursor.
+*/
+static void rtreeSearchPointSwap(RtreeCursor *p, int i, int j){
+ RtreeSearchPoint t = p->aPoint[i];
+ assert( i<j );
+ p->aPoint[i] = p->aPoint[j];
+ p->aPoint[j] = t;
+ i++; j++;
+ if( i<RTREE_CACHE_SZ ){
+ if( j>=RTREE_CACHE_SZ ){
+ nodeRelease(RTREE_OF_CURSOR(p), p->aNode[i]);
+ p->aNode[i] = 0;
+ }else{
+ RtreeNode *pTemp = p->aNode[i];
+ p->aNode[i] = p->aNode[j];
+ p->aNode[j] = pTemp;
+ }
+ }
+}
+
+/*
+** Return the search point with the lowest current score.
+*/
+static RtreeSearchPoint *rtreeSearchPointFirst(RtreeCursor *pCur){
+ return pCur->bPoint ? &pCur->sPoint : pCur->nPoint ? pCur->aPoint : 0;
+}
+
+/*
+** Get the RtreeNode for the search point with the lowest score.
+*/
+static RtreeNode *rtreeNodeOfFirstSearchPoint(RtreeCursor *pCur, int *pRC){
+ sqlite3_int64 id;
+ int ii = 1 - pCur->bPoint;
+ assert( ii==0 || ii==1 );
+ assert( pCur->bPoint || pCur->nPoint );
+ if( pCur->aNode[ii]==0 ){
+ assert( pRC!=0 );
+ id = ii ? pCur->aPoint[0].id : pCur->sPoint.id;
+ *pRC = nodeAcquire(RTREE_OF_CURSOR(pCur), id, 0, &pCur->aNode[ii]);
+ }
+ return pCur->aNode[ii];
+}
+
+/*
+** Push a new element onto the priority queue
+*/
+static RtreeSearchPoint *rtreeEnqueue(
+ RtreeCursor *pCur, /* The cursor */
+ RtreeDValue rScore, /* Score for the new search point */
+ u8 iLevel /* Level for the new search point */
+){
+ int i, j;
+ RtreeSearchPoint *pNew;
+ if( pCur->nPoint>=pCur->nPointAlloc ){
+ int nNew = pCur->nPointAlloc*2 + 8;
+ pNew = sqlite3_realloc64(pCur->aPoint, nNew*sizeof(pCur->aPoint[0]));
+ if( pNew==0 ) return 0;
+ pCur->aPoint = pNew;
+ pCur->nPointAlloc = nNew;
+ }
+ i = pCur->nPoint++;
+ pNew = pCur->aPoint + i;
+ pNew->rScore = rScore;
+ pNew->iLevel = iLevel;
+ assert( iLevel<=RTREE_MAX_DEPTH );
+ while( i>0 ){
+ RtreeSearchPoint *pParent;
+ j = (i-1)/2;
+ pParent = pCur->aPoint + j;
+ if( rtreeSearchPointCompare(pNew, pParent)>=0 ) break;
+ rtreeSearchPointSwap(pCur, j, i);
+ i = j;
+ pNew = pParent;
+ }
+ return pNew;
+}
+
+/*
+** Allocate a new RtreeSearchPoint and return a pointer to it. Return
+** NULL if malloc fails.
+*/
+static RtreeSearchPoint *rtreeSearchPointNew(
+ RtreeCursor *pCur, /* The cursor */
+ RtreeDValue rScore, /* Score for the new search point */
+ u8 iLevel /* Level for the new search point */
+){
+ RtreeSearchPoint *pNew, *pFirst;
+ pFirst = rtreeSearchPointFirst(pCur);
+ pCur->anQueue[iLevel]++;
+ if( pFirst==0
+ || pFirst->rScore>rScore
+ || (pFirst->rScore==rScore && pFirst->iLevel>iLevel)
+ ){
+ if( pCur->bPoint ){
+ int ii;
+ pNew = rtreeEnqueue(pCur, rScore, iLevel);
+ if( pNew==0 ) return 0;
+ ii = (int)(pNew - pCur->aPoint) + 1;
+ assert( ii==1 );
+ if( ALWAYS(ii<RTREE_CACHE_SZ) ){
+ assert( pCur->aNode[ii]==0 );
+ pCur->aNode[ii] = pCur->aNode[0];
+ }else{
+ nodeRelease(RTREE_OF_CURSOR(pCur), pCur->aNode[0]);
+ }
+ pCur->aNode[0] = 0;
+ *pNew = pCur->sPoint;
+ }
+ pCur->sPoint.rScore = rScore;
+ pCur->sPoint.iLevel = iLevel;
+ pCur->bPoint = 1;
+ return &pCur->sPoint;
+ }else{
+ return rtreeEnqueue(pCur, rScore, iLevel);
+ }
+}
+
+#if 0
+/* Tracing routines for the RtreeSearchPoint queue */
+static void tracePoint(RtreeSearchPoint *p, int idx, RtreeCursor *pCur){
+ if( idx<0 ){ printf(" s"); }else{ printf("%2d", idx); }
+ printf(" %d.%05lld.%02d %g %d",
+ p->iLevel, p->id, p->iCell, p->rScore, p->eWithin
+ );
+ idx++;
+ if( idx<RTREE_CACHE_SZ ){
+ printf(" %p\n", pCur->aNode[idx]);
+ }else{
+ printf("\n");
+ }
+}
+static void traceQueue(RtreeCursor *pCur, const char *zPrefix){
+ int ii;
+ printf("=== %9s ", zPrefix);
+ if( pCur->bPoint ){
+ tracePoint(&pCur->sPoint, -1, pCur);
+ }
+ for(ii=0; ii<pCur->nPoint; ii++){
+ if( ii>0 || pCur->bPoint ) printf(" ");
+ tracePoint(&pCur->aPoint[ii], ii, pCur);
+ }
+}
+# define RTREE_QUEUE_TRACE(A,B) traceQueue(A,B)
+#else
+# define RTREE_QUEUE_TRACE(A,B) /* no-op */
+#endif
+
+/* Remove the search point with the lowest current score.
+*/
+static void rtreeSearchPointPop(RtreeCursor *p){
+ int i, j, k, n;
+ i = 1 - p->bPoint;
+ assert( i==0 || i==1 );
+ if( p->aNode[i] ){
+ nodeRelease(RTREE_OF_CURSOR(p), p->aNode[i]);
+ p->aNode[i] = 0;
+ }
+ if( p->bPoint ){
+ p->anQueue[p->sPoint.iLevel]--;
+ p->bPoint = 0;
+ }else if( ALWAYS(p->nPoint) ){
+ p->anQueue[p->aPoint[0].iLevel]--;
+ n = --p->nPoint;
+ p->aPoint[0] = p->aPoint[n];
+ if( n<RTREE_CACHE_SZ-1 ){
+ p->aNode[1] = p->aNode[n+1];
+ p->aNode[n+1] = 0;
+ }
+ i = 0;
+ while( (j = i*2+1)<n ){
+ k = j+1;
+ if( k<n && rtreeSearchPointCompare(&p->aPoint[k], &p->aPoint[j])<0 ){
+ if( rtreeSearchPointCompare(&p->aPoint[k], &p->aPoint[i])<0 ){
+ rtreeSearchPointSwap(p, i, k);
+ i = k;
+ }else{
+ break;
+ }
+ }else{
+ if( rtreeSearchPointCompare(&p->aPoint[j], &p->aPoint[i])<0 ){
+ rtreeSearchPointSwap(p, i, j);
+ i = j;
+ }else{
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+/*
+** Continue the search on cursor pCur until the front of the queue
+** contains an entry suitable for returning as a result-set row,
+** or until the RtreeSearchPoint queue is empty, indicating that the
+** query has completed.
+*/
+static int rtreeStepToLeaf(RtreeCursor *pCur){
+ RtreeSearchPoint *p;
+ Rtree *pRtree = RTREE_OF_CURSOR(pCur);
+ RtreeNode *pNode;
+ int eWithin;
+ int rc = SQLITE_OK;
+ int nCell;
+ int nConstraint = pCur->nConstraint;
+ int ii;
+ int eInt;
+ RtreeSearchPoint x;
+
+ eInt = pRtree->eCoordType==RTREE_COORD_INT32;
+ while( (p = rtreeSearchPointFirst(pCur))!=0 && p->iLevel>0 ){
+ u8 *pCellData;
+ pNode = rtreeNodeOfFirstSearchPoint(pCur, &rc);
+ if( rc ) return rc;
+ nCell = NCELL(pNode);
+ assert( nCell<200 );
+ pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell);
+ while( p->iCell<nCell ){
+ sqlite3_rtree_dbl rScore = (sqlite3_rtree_dbl)-1;
+ eWithin = FULLY_WITHIN;
+ for(ii=0; ii<nConstraint; ii++){
+ RtreeConstraint *pConstraint = pCur->aConstraint + ii;
+ if( pConstraint->op>=RTREE_MATCH ){
+ rc = rtreeCallbackConstraint(pConstraint, eInt, pCellData, p,
+ &rScore, &eWithin);
+ if( rc ) return rc;
+ }else if( p->iLevel==1 ){
+ rtreeLeafConstraint(pConstraint, eInt, pCellData, &eWithin);
+ }else{
+ rtreeNonleafConstraint(pConstraint, eInt, pCellData, &eWithin);
+ }
+ if( eWithin==NOT_WITHIN ){
+ p->iCell++;
+ pCellData += pRtree->nBytesPerCell;
+ break;
+ }
+ }
+ if( eWithin==NOT_WITHIN ) continue;
+ p->iCell++;
+ x.iLevel = p->iLevel - 1;
+ if( x.iLevel ){
+ x.id = readInt64(pCellData);
+ for(ii=0; ii<pCur->nPoint; ii++){
+ if( pCur->aPoint[ii].id==x.id ){
+ RTREE_IS_CORRUPT(pRtree);
+ return SQLITE_CORRUPT_VTAB;
+ }
+ }
+ x.iCell = 0;
+ }else{
+ x.id = p->id;
+ x.iCell = p->iCell - 1;
+ }
+ if( p->iCell>=nCell ){
+ RTREE_QUEUE_TRACE(pCur, "POP-S:");
+ rtreeSearchPointPop(pCur);
+ }
+ if( rScore<RTREE_ZERO ) rScore = RTREE_ZERO;
+ p = rtreeSearchPointNew(pCur, rScore, x.iLevel);
+ if( p==0 ) return SQLITE_NOMEM;
+ p->eWithin = (u8)eWithin;
+ p->id = x.id;
+ p->iCell = x.iCell;
+ RTREE_QUEUE_TRACE(pCur, "PUSH-S:");
+ break;
+ }
+ if( p->iCell>=nCell ){
+ RTREE_QUEUE_TRACE(pCur, "POP-Se:");
+ rtreeSearchPointPop(pCur);
+ }
+ }
+ pCur->atEOF = p==0;
+ return SQLITE_OK;
+}
+
+/*
+** Rtree virtual table module xNext method.
+*/
+static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){
+ RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
+ int rc = SQLITE_OK;
+
+ /* Move to the next entry that matches the configured constraints. */
+ RTREE_QUEUE_TRACE(pCsr, "POP-Nx:");
+ if( pCsr->bAuxValid ){
+ pCsr->bAuxValid = 0;
+ sqlite3_reset(pCsr->pReadAux);
+ }
+ rtreeSearchPointPop(pCsr);
+ rc = rtreeStepToLeaf(pCsr);
+ return rc;
+}
+
+/*
+** Rtree virtual table module xRowid method.
+*/
+static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
+ RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
+ RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
+ int rc = SQLITE_OK;
+ RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
+ if( rc==SQLITE_OK && ALWAYS(p) ){
+ *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
+ }
+ return rc;
+}
+
+/*
+** Rtree virtual table module xColumn method.
+*/
+static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
+ Rtree *pRtree = (Rtree *)cur->pVtab;
+ RtreeCursor *pCsr = (RtreeCursor *)cur;
+ RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr);
+ RtreeCoord c;
+ int rc = SQLITE_OK;
+ RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
+
+ if( rc ) return rc;
+ if( NEVER(p==0) ) return SQLITE_OK;
+ if( i==0 ){
+ sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
+ }else if( i<=pRtree->nDim2 ){
+ nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c);
+#ifndef SQLITE_RTREE_INT_ONLY
+ if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
+ sqlite3_result_double(ctx, c.f);
+ }else
+#endif
+ {
+ assert( pRtree->eCoordType==RTREE_COORD_INT32 );
+ sqlite3_result_int(ctx, c.i);
+ }
+ }else{
+ if( !pCsr->bAuxValid ){
+ if( pCsr->pReadAux==0 ){
+ rc = sqlite3_prepare_v3(pRtree->db, pRtree->zReadAuxSql, -1, 0,
+ &pCsr->pReadAux, 0);
+ if( rc ) return rc;
+ }
+ sqlite3_bind_int64(pCsr->pReadAux, 1,
+ nodeGetRowid(pRtree, pNode, p->iCell));
+ rc = sqlite3_step(pCsr->pReadAux);
+ if( rc==SQLITE_ROW ){
+ pCsr->bAuxValid = 1;
+ }else{
+ sqlite3_reset(pCsr->pReadAux);
+ if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+ return rc;
+ }
+ }
+ sqlite3_result_value(ctx,
+ sqlite3_column_value(pCsr->pReadAux, i - pRtree->nDim2 + 1));
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Use nodeAcquire() to obtain the leaf node containing the record with
+** rowid iRowid. If successful, set *ppLeaf to point to the node and
+** return SQLITE_OK. If there is no such record in the table, set
+** *ppLeaf to 0 and return SQLITE_OK. If an error occurs, set *ppLeaf
+** to zero and return an SQLite error code.
+*/
+static int findLeafNode(
+ Rtree *pRtree, /* RTree to search */
+ i64 iRowid, /* The rowid searching for */
+ RtreeNode **ppLeaf, /* Write the node here */
+ sqlite3_int64 *piNode /* Write the node-id here */
+){
+ int rc;
+ *ppLeaf = 0;
+ sqlite3_bind_int64(pRtree->pReadRowid, 1, iRowid);
+ if( sqlite3_step(pRtree->pReadRowid)==SQLITE_ROW ){
+ i64 iNode = sqlite3_column_int64(pRtree->pReadRowid, 0);
+ if( piNode ) *piNode = iNode;
+ rc = nodeAcquire(pRtree, iNode, 0, ppLeaf);
+ sqlite3_reset(pRtree->pReadRowid);
+ }else{
+ rc = sqlite3_reset(pRtree->pReadRowid);
+ }
+ return rc;
+}
+
+/*
+** This function is called to configure the RtreeConstraint object passed
+** as the second argument for a MATCH constraint. The value passed as the
+** first argument to this function is the right-hand operand to the MATCH
+** operator.
+*/
+static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){
+ RtreeMatchArg *pBlob, *pSrc; /* BLOB returned by geometry function */
+ sqlite3_rtree_query_info *pInfo; /* Callback information */
+
+ pSrc = sqlite3_value_pointer(pValue, "RtreeMatchArg");
+ if( pSrc==0 ) return SQLITE_ERROR;
+ pInfo = (sqlite3_rtree_query_info*)
+ sqlite3_malloc64( sizeof(*pInfo)+pSrc->iSize );
+ if( !pInfo ) return SQLITE_NOMEM;
+ memset(pInfo, 0, sizeof(*pInfo));
+ pBlob = (RtreeMatchArg*)&pInfo[1];
+ memcpy(pBlob, pSrc, pSrc->iSize);
+ pInfo->pContext = pBlob->cb.pContext;
+ pInfo->nParam = pBlob->nParam;
+ pInfo->aParam = pBlob->aParam;
+ pInfo->apSqlParam = pBlob->apSqlParam;
+
+ if( pBlob->cb.xGeom ){
+ pCons->u.xGeom = pBlob->cb.xGeom;
+ }else{
+ pCons->op = RTREE_QUERY;
+ pCons->u.xQueryFunc = pBlob->cb.xQueryFunc;
+ }
+ pCons->pInfo = pInfo;
+ return SQLITE_OK;
+}
+
+/*
+** Rtree virtual table module xFilter method.
+*/
+static int rtreeFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ Rtree *pRtree = (Rtree *)pVtabCursor->pVtab;
+ RtreeCursor *pCsr = (RtreeCursor *)pVtabCursor;
+ RtreeNode *pRoot = 0;
+ int ii;
+ int rc = SQLITE_OK;
+ int iCell = 0;
+
+ rtreeReference(pRtree);
+
+ /* Reset the cursor to the same state as rtreeOpen() leaves it in. */
+ resetCursor(pCsr);
+
+ pCsr->iStrategy = idxNum;
+ if( idxNum==1 ){
+ /* Special case - lookup by rowid. */
+ RtreeNode *pLeaf; /* Leaf on which the required cell resides */
+ RtreeSearchPoint *p; /* Search point for the leaf */
+ i64 iRowid = sqlite3_value_int64(argv[0]);
+ i64 iNode = 0;
+ int eType = sqlite3_value_numeric_type(argv[0]);
+ if( eType==SQLITE_INTEGER
+ || (eType==SQLITE_FLOAT && sqlite3_value_double(argv[0])==iRowid)
+ ){
+ rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode);
+ }else{
+ rc = SQLITE_OK;
+ pLeaf = 0;
+ }
+ if( rc==SQLITE_OK && pLeaf!=0 ){
+ p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0);
+ assert( p!=0 ); /* Always returns pCsr->sPoint */
+ pCsr->aNode[0] = pLeaf;
+ p->id = iNode;
+ p->eWithin = PARTLY_WITHIN;
+ rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell);
+ p->iCell = (u8)iCell;
+ RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:");
+ }else{
+ pCsr->atEOF = 1;
+ }
+ }else{
+ /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array
+ ** with the configured constraints.
+ */
+ rc = nodeAcquire(pRtree, 1, 0, &pRoot);
+ if( rc==SQLITE_OK && argc>0 ){
+ pCsr->aConstraint = sqlite3_malloc64(sizeof(RtreeConstraint)*argc);
+ pCsr->nConstraint = argc;
+ if( !pCsr->aConstraint ){
+ rc = SQLITE_NOMEM;
+ }else{
+ memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc);
+ memset(pCsr->anQueue, 0, sizeof(u32)*(pRtree->iDepth + 1));
+ assert( (idxStr==0 && argc==0)
+ || (idxStr && (int)strlen(idxStr)==argc*2) );
+ for(ii=0; ii<argc; ii++){
+ RtreeConstraint *p = &pCsr->aConstraint[ii];
+ int eType = sqlite3_value_numeric_type(argv[ii]);
+ p->op = idxStr[ii*2];
+ p->iCoord = idxStr[ii*2+1]-'0';
+ if( p->op>=RTREE_MATCH ){
+ /* A MATCH operator. The right-hand-side must be a blob that
+ ** can be cast into an RtreeMatchArg object. One created using
+ ** an sqlite3_rtree_geometry_callback() SQL user function.
+ */
+ rc = deserializeGeometry(argv[ii], p);
+ if( rc!=SQLITE_OK ){
+ break;
+ }
+ p->pInfo->nCoord = pRtree->nDim2;
+ p->pInfo->anQueue = pCsr->anQueue;
+ p->pInfo->mxLevel = pRtree->iDepth + 1;
+ }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
+#ifdef SQLITE_RTREE_INT_ONLY
+ p->u.rValue = sqlite3_value_int64(argv[ii]);
+#else
+ p->u.rValue = sqlite3_value_double(argv[ii]);
+#endif
+ }else{
+ p->u.rValue = RTREE_ZERO;
+ if( eType==SQLITE_NULL ){
+ p->op = RTREE_FALSE;
+ }else if( p->op==RTREE_LT || p->op==RTREE_LE ){
+ p->op = RTREE_TRUE;
+ }else{
+ p->op = RTREE_FALSE;
+ }
+ }
+ }
+ }
+ }
+ if( rc==SQLITE_OK ){
+ RtreeSearchPoint *pNew;
+ assert( pCsr->bPoint==0 ); /* Due to the resetCursor() call above */
+ pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1));
+ if( NEVER(pNew==0) ){ /* Because pCsr->bPoint was FALSE */
+ return SQLITE_NOMEM;
+ }
+ pNew->id = 1;
+ pNew->iCell = 0;
+ pNew->eWithin = PARTLY_WITHIN;
+ assert( pCsr->bPoint==1 );
+ pCsr->aNode[0] = pRoot;
+ pRoot = 0;
+ RTREE_QUEUE_TRACE(pCsr, "PUSH-Fm:");
+ rc = rtreeStepToLeaf(pCsr);
+ }
+ }
+
+ nodeRelease(pRtree, pRoot);
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+/*
+** Rtree virtual table module xBestIndex method. There are three
+** table scan strategies to choose from (in order from most to
+** least desirable):
+**
+** idxNum idxStr Strategy
+** ------------------------------------------------
+** 1 Unused Direct lookup by rowid.
+** 2 See below R-tree query or full-table scan.
+** ------------------------------------------------
+**
+** If strategy 1 is used, then idxStr is not meaningful. If strategy
+** 2 is used, idxStr is formatted to contain 2 bytes for each
+** constraint used. The first two bytes of idxStr correspond to
+** the constraint in sqlite3_index_info.aConstraintUsage[] with
+** (argvIndex==1) etc.
+**
+** The first of each pair of bytes in idxStr identifies the constraint
+** operator as follows:
+**
+** Operator Byte Value
+** ----------------------
+** = 0x41 ('A')
+** <= 0x42 ('B')
+** < 0x43 ('C')
+** >= 0x44 ('D')
+** > 0x45 ('E')
+** MATCH 0x46 ('F')
+** ----------------------
+**
+** The second of each pair of bytes identifies the coordinate column
+** to which the constraint applies. The leftmost coordinate column
+** is 'a', the second from the left 'b' etc.
+*/
+static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+ Rtree *pRtree = (Rtree*)tab;
+ int rc = SQLITE_OK;
+ int ii;
+ int bMatch = 0; /* True if there exists a MATCH constraint */
+ i64 nRow; /* Estimated rows returned by this scan */
+
+ int iIdx = 0;
+ char zIdxStr[RTREE_MAX_DIMENSIONS*8+1];
+ memset(zIdxStr, 0, sizeof(zIdxStr));
+
+ /* Check if there exists a MATCH constraint - even an unusable one. If there
+ ** is, do not consider the lookup-by-rowid plan as using such a plan would
+ ** require the VDBE to evaluate the MATCH constraint, which is not currently
+ ** possible. */
+ for(ii=0; ii<pIdxInfo->nConstraint; ii++){
+ if( pIdxInfo->aConstraint[ii].op==SQLITE_INDEX_CONSTRAINT_MATCH ){
+ bMatch = 1;
+ }
+ }
+
+ assert( pIdxInfo->idxStr==0 );
+ for(ii=0; ii<pIdxInfo->nConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){
+ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii];
+
+ if( bMatch==0 && p->usable
+ && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ
+ ){
+ /* We have an equality constraint on the rowid. Use strategy 1. */
+ int jj;
+ for(jj=0; jj<ii; jj++){
+ pIdxInfo->aConstraintUsage[jj].argvIndex = 0;
+ pIdxInfo->aConstraintUsage[jj].omit = 0;
+ }
+ pIdxInfo->idxNum = 1;
+ pIdxInfo->aConstraintUsage[ii].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[jj].omit = 1;
+
+ /* This strategy involves a two rowid lookups on an B-Tree structures
+ ** and then a linear search of an R-Tree node. This should be
+ ** considered almost as quick as a direct rowid lookup (for which
+ ** sqlite uses an internal cost of 0.0). It is expected to return
+ ** a single row.
+ */
+ pIdxInfo->estimatedCost = 30.0;
+ pIdxInfo->estimatedRows = 1;
+ pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE;
+ return SQLITE_OK;
+ }
+
+ if( p->usable
+ && ((p->iColumn>0 && p->iColumn<=pRtree->nDim2)
+ || p->op==SQLITE_INDEX_CONSTRAINT_MATCH)
+ ){
+ u8 op;
+ switch( p->op ){
+ case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break;
+ case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break;
+ case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break;
+ case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break;
+ case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break;
+ case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break;
+ default: op = 0; break;
+ }
+ if( op ){
+ zIdxStr[iIdx++] = op;
+ zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0');
+ pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2);
+ pIdxInfo->aConstraintUsage[ii].omit = 1;
+ }
+ }
+ }
+
+ pIdxInfo->idxNum = 2;
+ pIdxInfo->needToFreeIdxStr = 1;
+ if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){
+ return SQLITE_NOMEM;
+ }
+
+ nRow = pRtree->nRowEst >> (iIdx/2);
+ pIdxInfo->estimatedCost = (double)6.0 * (double)nRow;
+ pIdxInfo->estimatedRows = nRow;
+
+ return rc;
+}
+
+/*
+** Return the N-dimensional volumn of the cell stored in *p.
+*/
+static RtreeDValue cellArea(Rtree *pRtree, RtreeCell *p){
+ RtreeDValue area = (RtreeDValue)1;
+ assert( pRtree->nDim>=1 && pRtree->nDim<=5 );
+#ifndef SQLITE_RTREE_INT_ONLY
+ if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
+ switch( pRtree->nDim ){
+ case 5: area = p->aCoord[9].f - p->aCoord[8].f;
+ case 4: area *= p->aCoord[7].f - p->aCoord[6].f;
+ case 3: area *= p->aCoord[5].f - p->aCoord[4].f;
+ case 2: area *= p->aCoord[3].f - p->aCoord[2].f;
+ default: area *= p->aCoord[1].f - p->aCoord[0].f;
+ }
+ }else
+#endif
+ {
+ switch( pRtree->nDim ){
+ case 5: area = (i64)p->aCoord[9].i - (i64)p->aCoord[8].i;
+ case 4: area *= (i64)p->aCoord[7].i - (i64)p->aCoord[6].i;
+ case 3: area *= (i64)p->aCoord[5].i - (i64)p->aCoord[4].i;
+ case 2: area *= (i64)p->aCoord[3].i - (i64)p->aCoord[2].i;
+ default: area *= (i64)p->aCoord[1].i - (i64)p->aCoord[0].i;
+ }
+ }
+ return area;
+}
+
+/*
+** Return the margin length of cell p. The margin length is the sum
+** of the objects size in each dimension.
+*/
+static RtreeDValue cellMargin(Rtree *pRtree, RtreeCell *p){
+ RtreeDValue margin = 0;
+ int ii = pRtree->nDim2 - 2;
+ do{
+ margin += (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii]));
+ ii -= 2;
+ }while( ii>=0 );
+ return margin;
+}
+
+/*
+** Store the union of cells p1 and p2 in p1.
+*/
+static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
+ int ii = 0;
+ if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
+ do{
+ p1->aCoord[ii].f = MIN(p1->aCoord[ii].f, p2->aCoord[ii].f);
+ p1->aCoord[ii+1].f = MAX(p1->aCoord[ii+1].f, p2->aCoord[ii+1].f);
+ ii += 2;
+ }while( ii<pRtree->nDim2 );
+ }else{
+ do{
+ p1->aCoord[ii].i = MIN(p1->aCoord[ii].i, p2->aCoord[ii].i);
+ p1->aCoord[ii+1].i = MAX(p1->aCoord[ii+1].i, p2->aCoord[ii+1].i);
+ ii += 2;
+ }while( ii<pRtree->nDim2 );
+ }
+}
+
+/*
+** Return true if the area covered by p2 is a subset of the area covered
+** by p1. False otherwise.
+*/
+static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){
+ int ii;
+ int isInt = (pRtree->eCoordType==RTREE_COORD_INT32);
+ for(ii=0; ii<pRtree->nDim2; ii+=2){
+ RtreeCoord *a1 = &p1->aCoord[ii];
+ RtreeCoord *a2 = &p2->aCoord[ii];
+ if( (!isInt && (a2[0].f<a1[0].f || a2[1].f>a1[1].f))
+ || ( isInt && (a2[0].i<a1[0].i || a2[1].i>a1[1].i))
+ ){
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/*
+** Return the amount cell p would grow by if it were unioned with pCell.
+*/
+static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){
+ RtreeDValue area;
+ RtreeCell cell;
+ memcpy(&cell, p, sizeof(RtreeCell));
+ area = cellArea(pRtree, &cell);
+ cellUnion(pRtree, &cell, pCell);
+ return (cellArea(pRtree, &cell)-area);
+}
+
+static RtreeDValue cellOverlap(
+ Rtree *pRtree,
+ RtreeCell *p,
+ RtreeCell *aCell,
+ int nCell
+){
+ int ii;
+ RtreeDValue overlap = RTREE_ZERO;
+ for(ii=0; ii<nCell; ii++){
+ int jj;
+ RtreeDValue o = (RtreeDValue)1;
+ for(jj=0; jj<pRtree->nDim2; jj+=2){
+ RtreeDValue x1, x2;
+ x1 = MAX(DCOORD(p->aCoord[jj]), DCOORD(aCell[ii].aCoord[jj]));
+ x2 = MIN(DCOORD(p->aCoord[jj+1]), DCOORD(aCell[ii].aCoord[jj+1]));
+ if( x2<x1 ){
+ o = (RtreeDValue)0;
+ break;
+ }else{
+ o = o * (x2-x1);
+ }
+ }
+ overlap += o;
+ }
+ return overlap;
+}
+
+
+/*
+** This function implements the ChooseLeaf algorithm from Gutman[84].
+** ChooseSubTree in r*tree terminology.
+*/
+static int ChooseLeaf(
+ Rtree *pRtree, /* Rtree table */
+ RtreeCell *pCell, /* Cell to insert into rtree */
+ int iHeight, /* Height of sub-tree rooted at pCell */
+ RtreeNode **ppLeaf /* OUT: Selected leaf page */
+){
+ int rc;
+ int ii;
+ RtreeNode *pNode = 0;
+ rc = nodeAcquire(pRtree, 1, 0, &pNode);
+
+ for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){
+ int iCell;
+ sqlite3_int64 iBest = 0;
+
+ RtreeDValue fMinGrowth = RTREE_ZERO;
+ RtreeDValue fMinArea = RTREE_ZERO;
+
+ int nCell = NCELL(pNode);
+ RtreeCell cell;
+ RtreeNode *pChild = 0;
+
+ RtreeCell *aCell = 0;
+
+ /* Select the child node which will be enlarged the least if pCell
+ ** is inserted into it. Resolve ties by choosing the entry with
+ ** the smallest area.
+ */
+ for(iCell=0; iCell<nCell; iCell++){
+ int bBest = 0;
+ RtreeDValue growth;
+ RtreeDValue area;
+ nodeGetCell(pRtree, pNode, iCell, &cell);
+ growth = cellGrowth(pRtree, &cell, pCell);
+ area = cellArea(pRtree, &cell);
+ if( iCell==0||growth<fMinGrowth||(growth==fMinGrowth && area<fMinArea) ){
+ bBest = 1;
+ }
+ if( bBest ){
+ fMinGrowth = growth;
+ fMinArea = area;
+ iBest = cell.iRowid;
+ }
+ }
+
+ sqlite3_free(aCell);
+ rc = nodeAcquire(pRtree, iBest, pNode, &pChild);
+ nodeRelease(pRtree, pNode);
+ pNode = pChild;
+ }
+
+ *ppLeaf = pNode;
+ return rc;
+}
+
+/*
+** A cell with the same content as pCell has just been inserted into
+** the node pNode. This function updates the bounding box cells in
+** all ancestor elements.
+*/
+static int AdjustTree(
+ Rtree *pRtree, /* Rtree table */
+ RtreeNode *pNode, /* Adjust ancestry of this node. */
+ RtreeCell *pCell /* This cell was just inserted */
+){
+ RtreeNode *p = pNode;
+ int cnt = 0;
+ int rc;
+ while( p->pParent ){
+ RtreeNode *pParent = p->pParent;
+ RtreeCell cell;
+ int iCell;
+
+ cnt++;
+ if( NEVER(cnt>100) ){
+ RTREE_IS_CORRUPT(pRtree);
+ return SQLITE_CORRUPT_VTAB;
+ }
+ rc = nodeParentIndex(pRtree, p, &iCell);
+ if( NEVER(rc!=SQLITE_OK) ){
+ RTREE_IS_CORRUPT(pRtree);
+ return SQLITE_CORRUPT_VTAB;
+ }
+
+ nodeGetCell(pRtree, pParent, iCell, &cell);
+ if( !cellContains(pRtree, &cell, pCell) ){
+ cellUnion(pRtree, &cell, pCell);
+ nodeOverwriteCell(pRtree, pParent, &cell, iCell);
+ }
+
+ p = pParent;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Write mapping (iRowid->iNode) to the <rtree>_rowid table.
+*/
+static int rowidWrite(Rtree *pRtree, sqlite3_int64 iRowid, sqlite3_int64 iNode){
+ sqlite3_bind_int64(pRtree->pWriteRowid, 1, iRowid);
+ sqlite3_bind_int64(pRtree->pWriteRowid, 2, iNode);
+ sqlite3_step(pRtree->pWriteRowid);
+ return sqlite3_reset(pRtree->pWriteRowid);
+}
+
+/*
+** Write mapping (iNode->iPar) to the <rtree>_parent table.
+*/
+static int parentWrite(Rtree *pRtree, sqlite3_int64 iNode, sqlite3_int64 iPar){
+ sqlite3_bind_int64(pRtree->pWriteParent, 1, iNode);
+ sqlite3_bind_int64(pRtree->pWriteParent, 2, iPar);
+ sqlite3_step(pRtree->pWriteParent);
+ return sqlite3_reset(pRtree->pWriteParent);
+}
+
+static int rtreeInsertCell(Rtree *, RtreeNode *, RtreeCell *, int);
+
+
+/*
+** Arguments aIdx, aDistance and aSpare all point to arrays of size
+** nIdx. The aIdx array contains the set of integers from 0 to
+** (nIdx-1) in no particular order. This function sorts the values
+** in aIdx according to the indexed values in aDistance. For
+** example, assuming the inputs:
+**
+** aIdx = { 0, 1, 2, 3 }
+** aDistance = { 5.0, 2.0, 7.0, 6.0 }
+**
+** this function sets the aIdx array to contain:
+**
+** aIdx = { 0, 1, 2, 3 }
+**
+** The aSpare array is used as temporary working space by the
+** sorting algorithm.
+*/
+static void SortByDistance(
+ int *aIdx,
+ int nIdx,
+ RtreeDValue *aDistance,
+ int *aSpare
+){
+ if( nIdx>1 ){
+ int iLeft = 0;
+ int iRight = 0;
+
+ int nLeft = nIdx/2;
+ int nRight = nIdx-nLeft;
+ int *aLeft = aIdx;
+ int *aRight = &aIdx[nLeft];
+
+ SortByDistance(aLeft, nLeft, aDistance, aSpare);
+ SortByDistance(aRight, nRight, aDistance, aSpare);
+
+ memcpy(aSpare, aLeft, sizeof(int)*nLeft);
+ aLeft = aSpare;
+
+ while( iLeft<nLeft || iRight<nRight ){
+ if( iLeft==nLeft ){
+ aIdx[iLeft+iRight] = aRight[iRight];
+ iRight++;
+ }else if( iRight==nRight ){
+ aIdx[iLeft+iRight] = aLeft[iLeft];
+ iLeft++;
+ }else{
+ RtreeDValue fLeft = aDistance[aLeft[iLeft]];
+ RtreeDValue fRight = aDistance[aRight[iRight]];
+ if( fLeft<fRight ){
+ aIdx[iLeft+iRight] = aLeft[iLeft];
+ iLeft++;
+ }else{
+ aIdx[iLeft+iRight] = aRight[iRight];
+ iRight++;
+ }
+ }
+ }
+
+#if 0
+ /* Check that the sort worked */
+ {
+ int jj;
+ for(jj=1; jj<nIdx; jj++){
+ RtreeDValue left = aDistance[aIdx[jj-1]];
+ RtreeDValue right = aDistance[aIdx[jj]];
+ assert( left<=right );
+ }
+ }
+#endif
+ }
+}
+
+/*
+** Arguments aIdx, aCell and aSpare all point to arrays of size
+** nIdx. The aIdx array contains the set of integers from 0 to
+** (nIdx-1) in no particular order. This function sorts the values
+** in aIdx according to dimension iDim of the cells in aCell. The
+** minimum value of dimension iDim is considered first, the
+** maximum used to break ties.
+**
+** The aSpare array is used as temporary working space by the
+** sorting algorithm.
+*/
+static void SortByDimension(
+ Rtree *pRtree,
+ int *aIdx,
+ int nIdx,
+ int iDim,
+ RtreeCell *aCell,
+ int *aSpare
+){
+ if( nIdx>1 ){
+
+ int iLeft = 0;
+ int iRight = 0;
+
+ int nLeft = nIdx/2;
+ int nRight = nIdx-nLeft;
+ int *aLeft = aIdx;
+ int *aRight = &aIdx[nLeft];
+
+ SortByDimension(pRtree, aLeft, nLeft, iDim, aCell, aSpare);
+ SortByDimension(pRtree, aRight, nRight, iDim, aCell, aSpare);
+
+ memcpy(aSpare, aLeft, sizeof(int)*nLeft);
+ aLeft = aSpare;
+ while( iLeft<nLeft || iRight<nRight ){
+ RtreeDValue xleft1 = DCOORD(aCell[aLeft[iLeft]].aCoord[iDim*2]);
+ RtreeDValue xleft2 = DCOORD(aCell[aLeft[iLeft]].aCoord[iDim*2+1]);
+ RtreeDValue xright1 = DCOORD(aCell[aRight[iRight]].aCoord[iDim*2]);
+ RtreeDValue xright2 = DCOORD(aCell[aRight[iRight]].aCoord[iDim*2+1]);
+ if( (iLeft!=nLeft) && ((iRight==nRight)
+ || (xleft1<xright1)
+ || (xleft1==xright1 && xleft2<xright2)
+ )){
+ aIdx[iLeft+iRight] = aLeft[iLeft];
+ iLeft++;
+ }else{
+ aIdx[iLeft+iRight] = aRight[iRight];
+ iRight++;
+ }
+ }
+
+#if 0
+ /* Check that the sort worked */
+ {
+ int jj;
+ for(jj=1; jj<nIdx; jj++){
+ RtreeDValue xleft1 = aCell[aIdx[jj-1]].aCoord[iDim*2];
+ RtreeDValue xleft2 = aCell[aIdx[jj-1]].aCoord[iDim*2+1];
+ RtreeDValue xright1 = aCell[aIdx[jj]].aCoord[iDim*2];
+ RtreeDValue xright2 = aCell[aIdx[jj]].aCoord[iDim*2+1];
+ assert( xleft1<=xright1 && (xleft1<xright1 || xleft2<=xright2) );
+ }
+ }
+#endif
+ }
+}
+
+/*
+** Implementation of the R*-tree variant of SplitNode from Beckman[1990].
+*/
+static int splitNodeStartree(
+ Rtree *pRtree,
+ RtreeCell *aCell,
+ int nCell,
+ RtreeNode *pLeft,
+ RtreeNode *pRight,
+ RtreeCell *pBboxLeft,
+ RtreeCell *pBboxRight
+){
+ int **aaSorted;
+ int *aSpare;
+ int ii;
+
+ int iBestDim = 0;
+ int iBestSplit = 0;
+ RtreeDValue fBestMargin = RTREE_ZERO;
+
+ sqlite3_int64 nByte = (pRtree->nDim+1)*(sizeof(int*)+nCell*sizeof(int));
+
+ aaSorted = (int **)sqlite3_malloc64(nByte);
+ if( !aaSorted ){
+ return SQLITE_NOMEM;
+ }
+
+ aSpare = &((int *)&aaSorted[pRtree->nDim])[pRtree->nDim*nCell];
+ memset(aaSorted, 0, nByte);
+ for(ii=0; ii<pRtree->nDim; ii++){
+ int jj;
+ aaSorted[ii] = &((int *)&aaSorted[pRtree->nDim])[ii*nCell];
+ for(jj=0; jj<nCell; jj++){
+ aaSorted[ii][jj] = jj;
+ }
+ SortByDimension(pRtree, aaSorted[ii], nCell, ii, aCell, aSpare);
+ }
+
+ for(ii=0; ii<pRtree->nDim; ii++){
+ RtreeDValue margin = RTREE_ZERO;
+ RtreeDValue fBestOverlap = RTREE_ZERO;
+ RtreeDValue fBestArea = RTREE_ZERO;
+ int iBestLeft = 0;
+ int nLeft;
+
+ for(
+ nLeft=RTREE_MINCELLS(pRtree);
+ nLeft<=(nCell-RTREE_MINCELLS(pRtree));
+ nLeft++
+ ){
+ RtreeCell left;
+ RtreeCell right;
+ int kk;
+ RtreeDValue overlap;
+ RtreeDValue area;
+
+ memcpy(&left, &aCell[aaSorted[ii][0]], sizeof(RtreeCell));
+ memcpy(&right, &aCell[aaSorted[ii][nCell-1]], sizeof(RtreeCell));
+ for(kk=1; kk<(nCell-1); kk++){
+ if( kk<nLeft ){
+ cellUnion(pRtree, &left, &aCell[aaSorted[ii][kk]]);
+ }else{
+ cellUnion(pRtree, &right, &aCell[aaSorted[ii][kk]]);
+ }
+ }
+ margin += cellMargin(pRtree, &left);
+ margin += cellMargin(pRtree, &right);
+ overlap = cellOverlap(pRtree, &left, &right, 1);
+ area = cellArea(pRtree, &left) + cellArea(pRtree, &right);
+ if( (nLeft==RTREE_MINCELLS(pRtree))
+ || (overlap<fBestOverlap)
+ || (overlap==fBestOverlap && area<fBestArea)
+ ){
+ iBestLeft = nLeft;
+ fBestOverlap = overlap;
+ fBestArea = area;
+ }
+ }
+
+ if( ii==0 || margin<fBestMargin ){
+ iBestDim = ii;
+ fBestMargin = margin;
+ iBestSplit = iBestLeft;
+ }
+ }
+
+ memcpy(pBboxLeft, &aCell[aaSorted[iBestDim][0]], sizeof(RtreeCell));
+ memcpy(pBboxRight, &aCell[aaSorted[iBestDim][iBestSplit]], sizeof(RtreeCell));
+ for(ii=0; ii<nCell; ii++){
+ RtreeNode *pTarget = (ii<iBestSplit)?pLeft:pRight;
+ RtreeCell *pBbox = (ii<iBestSplit)?pBboxLeft:pBboxRight;
+ RtreeCell *pCell = &aCell[aaSorted[iBestDim][ii]];
+ nodeInsertCell(pRtree, pTarget, pCell);
+ cellUnion(pRtree, pBbox, pCell);
+ }
+
+ sqlite3_free(aaSorted);
+ return SQLITE_OK;
+}
+
+
+static int updateMapping(
+ Rtree *pRtree,
+ i64 iRowid,
+ RtreeNode *pNode,
+ int iHeight
+){
+ int (*xSetMapping)(Rtree *, sqlite3_int64, sqlite3_int64);
+ xSetMapping = ((iHeight==0)?rowidWrite:parentWrite);
+ if( iHeight>0 ){
+ RtreeNode *pChild = nodeHashLookup(pRtree, iRowid);
+ RtreeNode *p;
+ for(p=pNode; p; p=p->pParent){
+ if( p==pChild ) return SQLITE_CORRUPT_VTAB;
+ }
+ if( pChild ){
+ nodeRelease(pRtree, pChild->pParent);
+ nodeReference(pNode);
+ pChild->pParent = pNode;
+ }
+ }
+ if( NEVER(pNode==0) ) return SQLITE_ERROR;
+ return xSetMapping(pRtree, iRowid, pNode->iNode);
+}
+
+static int SplitNode(
+ Rtree *pRtree,
+ RtreeNode *pNode,
+ RtreeCell *pCell,
+ int iHeight
+){
+ int i;
+ int newCellIsRight = 0;
+
+ int rc = SQLITE_OK;
+ int nCell = NCELL(pNode);
+ RtreeCell *aCell;
+ int *aiUsed;
+
+ RtreeNode *pLeft = 0;
+ RtreeNode *pRight = 0;
+
+ RtreeCell leftbbox;
+ RtreeCell rightbbox;
+
+ /* Allocate an array and populate it with a copy of pCell and
+ ** all cells from node pLeft. Then zero the original node.
+ */
+ aCell = sqlite3_malloc64((sizeof(RtreeCell)+sizeof(int))*(nCell+1));
+ if( !aCell ){
+ rc = SQLITE_NOMEM;
+ goto splitnode_out;
+ }
+ aiUsed = (int *)&aCell[nCell+1];
+ memset(aiUsed, 0, sizeof(int)*(nCell+1));
+ for(i=0; i<nCell; i++){
+ nodeGetCell(pRtree, pNode, i, &aCell[i]);
+ }
+ nodeZero(pRtree, pNode);
+ memcpy(&aCell[nCell], pCell, sizeof(RtreeCell));
+ nCell++;
+
+ if( pNode->iNode==1 ){
+ pRight = nodeNew(pRtree, pNode);
+ pLeft = nodeNew(pRtree, pNode);
+ pRtree->iDepth++;
+ pNode->isDirty = 1;
+ writeInt16(pNode->zData, pRtree->iDepth);
+ }else{
+ pLeft = pNode;
+ pRight = nodeNew(pRtree, pLeft->pParent);
+ pLeft->nRef++;
+ }
+
+ if( !pLeft || !pRight ){
+ rc = SQLITE_NOMEM;
+ goto splitnode_out;
+ }
+
+ memset(pLeft->zData, 0, pRtree->iNodeSize);
+ memset(pRight->zData, 0, pRtree->iNodeSize);
+
+ rc = splitNodeStartree(pRtree, aCell, nCell, pLeft, pRight,
+ &leftbbox, &rightbbox);
+ if( rc!=SQLITE_OK ){
+ goto splitnode_out;
+ }
+
+ /* Ensure both child nodes have node numbers assigned to them by calling
+ ** nodeWrite(). Node pRight always needs a node number, as it was created
+ ** by nodeNew() above. But node pLeft sometimes already has a node number.
+ ** In this case avoid the all to nodeWrite().
+ */
+ if( SQLITE_OK!=(rc = nodeWrite(pRtree, pRight))
+ || (0==pLeft->iNode && SQLITE_OK!=(rc = nodeWrite(pRtree, pLeft)))
+ ){
+ goto splitnode_out;
+ }
+
+ rightbbox.iRowid = pRight->iNode;
+ leftbbox.iRowid = pLeft->iNode;
+
+ if( pNode->iNode==1 ){
+ rc = rtreeInsertCell(pRtree, pLeft->pParent, &leftbbox, iHeight+1);
+ if( rc!=SQLITE_OK ){
+ goto splitnode_out;
+ }
+ }else{
+ RtreeNode *pParent = pLeft->pParent;
+ int iCell;
+ rc = nodeParentIndex(pRtree, pLeft, &iCell);
+ if( ALWAYS(rc==SQLITE_OK) ){
+ nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell);
+ rc = AdjustTree(pRtree, pParent, &leftbbox);
+ assert( rc==SQLITE_OK );
+ }
+ if( NEVER(rc!=SQLITE_OK) ){
+ goto splitnode_out;
+ }
+ }
+ if( (rc = rtreeInsertCell(pRtree, pRight->pParent, &rightbbox, iHeight+1)) ){
+ goto splitnode_out;
+ }
+
+ for(i=0; i<NCELL(pRight); i++){
+ i64 iRowid = nodeGetRowid(pRtree, pRight, i);
+ rc = updateMapping(pRtree, iRowid, pRight, iHeight);
+ if( iRowid==pCell->iRowid ){
+ newCellIsRight = 1;
+ }
+ if( rc!=SQLITE_OK ){
+ goto splitnode_out;
+ }
+ }
+ if( pNode->iNode==1 ){
+ for(i=0; i<NCELL(pLeft); i++){
+ i64 iRowid = nodeGetRowid(pRtree, pLeft, i);
+ rc = updateMapping(pRtree, iRowid, pLeft, iHeight);
+ if( rc!=SQLITE_OK ){
+ goto splitnode_out;
+ }
+ }
+ }else if( newCellIsRight==0 ){
+ rc = updateMapping(pRtree, pCell->iRowid, pLeft, iHeight);
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = nodeRelease(pRtree, pRight);
+ pRight = 0;
+ }
+ if( rc==SQLITE_OK ){
+ rc = nodeRelease(pRtree, pLeft);
+ pLeft = 0;
+ }
+
+splitnode_out:
+ nodeRelease(pRtree, pRight);
+ nodeRelease(pRtree, pLeft);
+ sqlite3_free(aCell);
+ return rc;
+}
+
+/*
+** If node pLeaf is not the root of the r-tree and its pParent pointer is
+** still NULL, load all ancestor nodes of pLeaf into memory and populate
+** the pLeaf->pParent chain all the way up to the root node.
+**
+** This operation is required when a row is deleted (or updated - an update
+** is implemented as a delete followed by an insert). SQLite provides the
+** rowid of the row to delete, which can be used to find the leaf on which
+** the entry resides (argument pLeaf). Once the leaf is located, this
+** function is called to determine its ancestry.
+*/
+static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){
+ int rc = SQLITE_OK;
+ RtreeNode *pChild = pLeaf;
+ while( rc==SQLITE_OK && pChild->iNode!=1 && pChild->pParent==0 ){
+ int rc2 = SQLITE_OK; /* sqlite3_reset() return code */
+ sqlite3_bind_int64(pRtree->pReadParent, 1, pChild->iNode);
+ rc = sqlite3_step(pRtree->pReadParent);
+ if( rc==SQLITE_ROW ){
+ RtreeNode *pTest; /* Used to test for reference loops */
+ i64 iNode; /* Node number of parent node */
+
+ /* Before setting pChild->pParent, test that we are not creating a
+ ** loop of references (as we would if, say, pChild==pParent). We don't
+ ** want to do this as it leads to a memory leak when trying to delete
+ ** the referenced counted node structures.
+ */
+ iNode = sqlite3_column_int64(pRtree->pReadParent, 0);
+ for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent);
+ if( pTest==0 ){
+ rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent);
+ }
+ }
+ rc = sqlite3_reset(pRtree->pReadParent);
+ if( rc==SQLITE_OK ) rc = rc2;
+ if( rc==SQLITE_OK && !pChild->pParent ){
+ RTREE_IS_CORRUPT(pRtree);
+ rc = SQLITE_CORRUPT_VTAB;
+ }
+ pChild = pChild->pParent;
+ }
+ return rc;
+}
+
+static int deleteCell(Rtree *, RtreeNode *, int, int);
+
+static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){
+ int rc;
+ int rc2;
+ RtreeNode *pParent = 0;
+ int iCell;
+
+ assert( pNode->nRef==1 );
+
+ /* Remove the entry in the parent cell. */
+ rc = nodeParentIndex(pRtree, pNode, &iCell);
+ if( rc==SQLITE_OK ){
+ pParent = pNode->pParent;
+ pNode->pParent = 0;
+ rc = deleteCell(pRtree, pParent, iCell, iHeight+1);
+ testcase( rc!=SQLITE_OK );
+ }
+ rc2 = nodeRelease(pRtree, pParent);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ /* Remove the xxx_node entry. */
+ sqlite3_bind_int64(pRtree->pDeleteNode, 1, pNode->iNode);
+ sqlite3_step(pRtree->pDeleteNode);
+ if( SQLITE_OK!=(rc = sqlite3_reset(pRtree->pDeleteNode)) ){
+ return rc;
+ }
+
+ /* Remove the xxx_parent entry. */
+ sqlite3_bind_int64(pRtree->pDeleteParent, 1, pNode->iNode);
+ sqlite3_step(pRtree->pDeleteParent);
+ if( SQLITE_OK!=(rc = sqlite3_reset(pRtree->pDeleteParent)) ){
+ return rc;
+ }
+
+ /* Remove the node from the in-memory hash table and link it into
+ ** the Rtree.pDeleted list. Its contents will be re-inserted later on.
+ */
+ nodeHashDelete(pRtree, pNode);
+ pNode->iNode = iHeight;
+ pNode->pNext = pRtree->pDeleted;
+ pNode->nRef++;
+ pRtree->pDeleted = pNode;
+
+ return SQLITE_OK;
+}
+
+static int fixBoundingBox(Rtree *pRtree, RtreeNode *pNode){
+ RtreeNode *pParent = pNode->pParent;
+ int rc = SQLITE_OK;
+ if( pParent ){
+ int ii;
+ int nCell = NCELL(pNode);
+ RtreeCell box; /* Bounding box for pNode */
+ nodeGetCell(pRtree, pNode, 0, &box);
+ for(ii=1; ii<nCell; ii++){
+ RtreeCell cell;
+ nodeGetCell(pRtree, pNode, ii, &cell);
+ cellUnion(pRtree, &box, &cell);
+ }
+ box.iRowid = pNode->iNode;
+ rc = nodeParentIndex(pRtree, pNode, &ii);
+ if( rc==SQLITE_OK ){
+ nodeOverwriteCell(pRtree, pParent, &box, ii);
+ rc = fixBoundingBox(pRtree, pParent);
+ }
+ }
+ return rc;
+}
+
+/*
+** Delete the cell at index iCell of node pNode. After removing the
+** cell, adjust the r-tree data structure if required.
+*/
+static int deleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell, int iHeight){
+ RtreeNode *pParent;
+ int rc;
+
+ if( SQLITE_OK!=(rc = fixLeafParent(pRtree, pNode)) ){
+ return rc;
+ }
+
+ /* Remove the cell from the node. This call just moves bytes around
+ ** the in-memory node image, so it cannot fail.
+ */
+ nodeDeleteCell(pRtree, pNode, iCell);
+
+ /* If the node is not the tree root and now has less than the minimum
+ ** number of cells, remove it from the tree. Otherwise, update the
+ ** cell in the parent node so that it tightly contains the updated
+ ** node.
+ */
+ pParent = pNode->pParent;
+ assert( pParent || pNode->iNode==1 );
+ if( pParent ){
+ if( NCELL(pNode)<RTREE_MINCELLS(pRtree) ){
+ rc = removeNode(pRtree, pNode, iHeight);
+ }else{
+ rc = fixBoundingBox(pRtree, pNode);
+ }
+ }
+
+ return rc;
+}
+
+static int Reinsert(
+ Rtree *pRtree,
+ RtreeNode *pNode,
+ RtreeCell *pCell,
+ int iHeight
+){
+ int *aOrder;
+ int *aSpare;
+ RtreeCell *aCell;
+ RtreeDValue *aDistance;
+ int nCell;
+ RtreeDValue aCenterCoord[RTREE_MAX_DIMENSIONS];
+ int iDim;
+ int ii;
+ int rc = SQLITE_OK;
+ int n;
+
+ memset(aCenterCoord, 0, sizeof(RtreeDValue)*RTREE_MAX_DIMENSIONS);
+
+ nCell = NCELL(pNode)+1;
+ n = (nCell+1)&(~1);
+
+ /* Allocate the buffers used by this operation. The allocation is
+ ** relinquished before this function returns.
+ */
+ aCell = (RtreeCell *)sqlite3_malloc64(n * (
+ sizeof(RtreeCell) + /* aCell array */
+ sizeof(int) + /* aOrder array */
+ sizeof(int) + /* aSpare array */
+ sizeof(RtreeDValue) /* aDistance array */
+ ));
+ if( !aCell ){
+ return SQLITE_NOMEM;
+ }
+ aOrder = (int *)&aCell[n];
+ aSpare = (int *)&aOrder[n];
+ aDistance = (RtreeDValue *)&aSpare[n];
+
+ for(ii=0; ii<nCell; ii++){
+ if( ii==(nCell-1) ){
+ memcpy(&aCell[ii], pCell, sizeof(RtreeCell));
+ }else{
+ nodeGetCell(pRtree, pNode, ii, &aCell[ii]);
+ }
+ aOrder[ii] = ii;
+ for(iDim=0; iDim<pRtree->nDim; iDim++){
+ aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]);
+ aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]);
+ }
+ }
+ for(iDim=0; iDim<pRtree->nDim; iDim++){
+ aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2));
+ }
+
+ for(ii=0; ii<nCell; ii++){
+ aDistance[ii] = RTREE_ZERO;
+ for(iDim=0; iDim<pRtree->nDim; iDim++){
+ RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) -
+ DCOORD(aCell[ii].aCoord[iDim*2]));
+ aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]);
+ }
+ }
+
+ SortByDistance(aOrder, nCell, aDistance, aSpare);
+ nodeZero(pRtree, pNode);
+
+ for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){
+ RtreeCell *p = &aCell[aOrder[ii]];
+ nodeInsertCell(pRtree, pNode, p);
+ if( p->iRowid==pCell->iRowid ){
+ if( iHeight==0 ){
+ rc = rowidWrite(pRtree, p->iRowid, pNode->iNode);
+ }else{
+ rc = parentWrite(pRtree, p->iRowid, pNode->iNode);
+ }
+ }
+ }
+ if( rc==SQLITE_OK ){
+ rc = fixBoundingBox(pRtree, pNode);
+ }
+ for(; rc==SQLITE_OK && ii<nCell; ii++){
+ /* Find a node to store this cell in. pNode->iNode currently contains
+ ** the height of the sub-tree headed by the cell.
+ */
+ RtreeNode *pInsert;
+ RtreeCell *p = &aCell[aOrder[ii]];
+ rc = ChooseLeaf(pRtree, p, iHeight, &pInsert);
+ if( rc==SQLITE_OK ){
+ int rc2;
+ rc = rtreeInsertCell(pRtree, pInsert, p, iHeight);
+ rc2 = nodeRelease(pRtree, pInsert);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ }
+ }
+
+ sqlite3_free(aCell);
+ return rc;
+}
+
+/*
+** Insert cell pCell into node pNode. Node pNode is the head of a
+** subtree iHeight high (leaf nodes have iHeight==0).
+*/
+static int rtreeInsertCell(
+ Rtree *pRtree,
+ RtreeNode *pNode,
+ RtreeCell *pCell,
+ int iHeight
+){
+ int rc = SQLITE_OK;
+ if( iHeight>0 ){
+ RtreeNode *pChild = nodeHashLookup(pRtree, pCell->iRowid);
+ if( pChild ){
+ nodeRelease(pRtree, pChild->pParent);
+ nodeReference(pNode);
+ pChild->pParent = pNode;
+ }
+ }
+ if( nodeInsertCell(pRtree, pNode, pCell) ){
+ if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){
+ rc = SplitNode(pRtree, pNode, pCell, iHeight);
+ }else{
+ pRtree->iReinsertHeight = iHeight;
+ rc = Reinsert(pRtree, pNode, pCell, iHeight);
+ }
+ }else{
+ rc = AdjustTree(pRtree, pNode, pCell);
+ if( ALWAYS(rc==SQLITE_OK) ){
+ if( iHeight==0 ){
+ rc = rowidWrite(pRtree, pCell->iRowid, pNode->iNode);
+ }else{
+ rc = parentWrite(pRtree, pCell->iRowid, pNode->iNode);
+ }
+ }
+ }
+ return rc;
+}
+
+static int reinsertNodeContent(Rtree *pRtree, RtreeNode *pNode){
+ int ii;
+ int rc = SQLITE_OK;
+ int nCell = NCELL(pNode);
+
+ for(ii=0; rc==SQLITE_OK && ii<nCell; ii++){
+ RtreeNode *pInsert;
+ RtreeCell cell;
+ nodeGetCell(pRtree, pNode, ii, &cell);
+
+ /* Find a node to store this cell in. pNode->iNode currently contains
+ ** the height of the sub-tree headed by the cell.
+ */
+ rc = ChooseLeaf(pRtree, &cell, (int)pNode->iNode, &pInsert);
+ if( rc==SQLITE_OK ){
+ int rc2;
+ rc = rtreeInsertCell(pRtree, pInsert, &cell, (int)pNode->iNode);
+ rc2 = nodeRelease(pRtree, pInsert);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ }
+ }
+ return rc;
+}
+
+/*
+** Select a currently unused rowid for a new r-tree record.
+*/
+static int rtreeNewRowid(Rtree *pRtree, i64 *piRowid){
+ int rc;
+ sqlite3_bind_null(pRtree->pWriteRowid, 1);
+ sqlite3_bind_null(pRtree->pWriteRowid, 2);
+ sqlite3_step(pRtree->pWriteRowid);
+ rc = sqlite3_reset(pRtree->pWriteRowid);
+ *piRowid = sqlite3_last_insert_rowid(pRtree->db);
+ return rc;
+}
+
+/*
+** Remove the entry with rowid=iDelete from the r-tree structure.
+*/
+static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
+ int rc; /* Return code */
+ RtreeNode *pLeaf = 0; /* Leaf node containing record iDelete */
+ int iCell; /* Index of iDelete cell in pLeaf */
+ RtreeNode *pRoot = 0; /* Root node of rtree structure */
+
+
+ /* Obtain a reference to the root node to initialize Rtree.iDepth */
+ rc = nodeAcquire(pRtree, 1, 0, &pRoot);
+
+ /* Obtain a reference to the leaf node that contains the entry
+ ** about to be deleted.
+ */
+ if( rc==SQLITE_OK ){
+ rc = findLeafNode(pRtree, iDelete, &pLeaf, 0);
+ }
+
+#ifdef CORRUPT_DB
+ assert( pLeaf!=0 || rc!=SQLITE_OK || CORRUPT_DB );
+#endif
+
+ /* Delete the cell in question from the leaf node. */
+ if( rc==SQLITE_OK && pLeaf ){
+ int rc2;
+ rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell);
+ if( rc==SQLITE_OK ){
+ rc = deleteCell(pRtree, pLeaf, iCell, 0);
+ }
+ rc2 = nodeRelease(pRtree, pLeaf);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ }
+
+ /* Delete the corresponding entry in the <rtree>_rowid table. */
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
+ sqlite3_step(pRtree->pDeleteRowid);
+ rc = sqlite3_reset(pRtree->pDeleteRowid);
+ }
+
+ /* Check if the root node now has exactly one child. If so, remove
+ ** it, schedule the contents of the child for reinsertion and
+ ** reduce the tree height by one.
+ **
+ ** This is equivalent to copying the contents of the child into
+ ** the root node (the operation that Gutman's paper says to perform
+ ** in this scenario).
+ */
+ if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
+ int rc2;
+ RtreeNode *pChild = 0;
+ i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
+ rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); /* tag-20210916a */
+ if( rc==SQLITE_OK ){
+ rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
+ }
+ rc2 = nodeRelease(pRtree, pChild);
+ if( rc==SQLITE_OK ) rc = rc2;
+ if( rc==SQLITE_OK ){
+ pRtree->iDepth--;
+ writeInt16(pRoot->zData, pRtree->iDepth);
+ pRoot->isDirty = 1;
+ }
+ }
+
+ /* Re-insert the contents of any underfull nodes removed from the tree. */
+ for(pLeaf=pRtree->pDeleted; pLeaf; pLeaf=pRtree->pDeleted){
+ if( rc==SQLITE_OK ){
+ rc = reinsertNodeContent(pRtree, pLeaf);
+ }
+ pRtree->pDeleted = pLeaf->pNext;
+ pRtree->nNodeRef--;
+ sqlite3_free(pLeaf);
+ }
+
+ /* Release the reference to the root node. */
+ if( rc==SQLITE_OK ){
+ rc = nodeRelease(pRtree, pRoot);
+ }else{
+ nodeRelease(pRtree, pRoot);
+ }
+
+ return rc;
+}
+
+/*
+** Rounding constants for float->double conversion.
+*/
+#define RNDTOWARDS (1.0 - 1.0/8388608.0) /* Round towards zero */
+#define RNDAWAY (1.0 + 1.0/8388608.0) /* Round away from zero */
+
+#if !defined(SQLITE_RTREE_INT_ONLY)
+/*
+** Convert an sqlite3_value into an RtreeValue (presumably a float)
+** while taking care to round toward negative or positive, respectively.
+*/
+static RtreeValue rtreeValueDown(sqlite3_value *v){
+ double d = sqlite3_value_double(v);
+ float f = (float)d;
+ if( f>d ){
+ f = (float)(d*(d<0 ? RNDAWAY : RNDTOWARDS));
+ }
+ return f;
+}
+static RtreeValue rtreeValueUp(sqlite3_value *v){
+ double d = sqlite3_value_double(v);
+ float f = (float)d;
+ if( f<d ){
+ f = (float)(d*(d<0 ? RNDTOWARDS : RNDAWAY));
+ }
+ return f;
+}
+#endif /* !defined(SQLITE_RTREE_INT_ONLY) */
+
+/*
+** A constraint has failed while inserting a row into an rtree table.
+** Assuming no OOM error occurs, this function sets the error message
+** (at pRtree->base.zErrMsg) to an appropriate value and returns
+** SQLITE_CONSTRAINT.
+**
+** Parameter iCol is the index of the leftmost column involved in the
+** constraint failure. If it is 0, then the constraint that failed is
+** the unique constraint on the id column. Otherwise, it is the rtree
+** (c1<=c2) constraint on columns iCol and iCol+1 that has failed.
+**
+** If an OOM occurs, SQLITE_NOMEM is returned instead of SQLITE_CONSTRAINT.
+*/
+static int rtreeConstraintError(Rtree *pRtree, int iCol){
+ sqlite3_stmt *pStmt = 0;
+ char *zSql;
+ int rc;
+
+ assert( iCol==0 || iCol%2 );
+ zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", pRtree->zDb, pRtree->zName);
+ if( zSql ){
+ rc = sqlite3_prepare_v2(pRtree->db, zSql, -1, &pStmt, 0);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ sqlite3_free(zSql);
+
+ if( rc==SQLITE_OK ){
+ if( iCol==0 ){
+ const char *zCol = sqlite3_column_name(pStmt, 0);
+ pRtree->base.zErrMsg = sqlite3_mprintf(
+ "UNIQUE constraint failed: %s.%s", pRtree->zName, zCol
+ );
+ }else{
+ const char *zCol1 = sqlite3_column_name(pStmt, iCol);
+ const char *zCol2 = sqlite3_column_name(pStmt, iCol+1);
+ pRtree->base.zErrMsg = sqlite3_mprintf(
+ "rtree constraint failed: %s.(%s<=%s)", pRtree->zName, zCol1, zCol2
+ );
+ }
+ }
+
+ sqlite3_finalize(pStmt);
+ return (rc==SQLITE_OK ? SQLITE_CONSTRAINT : rc);
+}
+
+
+
+/*
+** The xUpdate method for rtree module virtual tables.
+*/
+static int rtreeUpdate(
+ sqlite3_vtab *pVtab,
+ int nData,
+ sqlite3_value **aData,
+ sqlite_int64 *pRowid
+){
+ Rtree *pRtree = (Rtree *)pVtab;
+ int rc = SQLITE_OK;
+ RtreeCell cell; /* New cell to insert if nData>1 */
+ int bHaveRowid = 0; /* Set to 1 after new rowid is determined */
+
+ if( pRtree->nNodeRef ){
+ /* Unable to write to the btree while another cursor is reading from it,
+ ** since the write might do a rebalance which would disrupt the read
+ ** cursor. */
+ return SQLITE_LOCKED_VTAB;
+ }
+ rtreeReference(pRtree);
+ assert(nData>=1);
+
+ memset(&cell, 0, sizeof(cell));
+
+ /* Constraint handling. A write operation on an r-tree table may return
+ ** SQLITE_CONSTRAINT for two reasons:
+ **
+ ** 1. A duplicate rowid value, or
+ ** 2. The supplied data violates the "x2>=x1" constraint.
+ **
+ ** In the first case, if the conflict-handling mode is REPLACE, then
+ ** the conflicting row can be removed before proceeding. In the second
+ ** case, SQLITE_CONSTRAINT must be returned regardless of the
+ ** conflict-handling mode specified by the user.
+ */
+ if( nData>1 ){
+ int ii;
+ int nn = nData - 4;
+
+ if( nn > pRtree->nDim2 ) nn = pRtree->nDim2;
+ /* Populate the cell.aCoord[] array. The first coordinate is aData[3].
+ **
+ ** NB: nData can only be less than nDim*2+3 if the rtree is mis-declared
+ ** with "column" that are interpreted as table constraints.
+ ** Example: CREATE VIRTUAL TABLE bad USING rtree(x,y,CHECK(y>5));
+ ** This problem was discovered after years of use, so we silently ignore
+ ** these kinds of misdeclared tables to avoid breaking any legacy.
+ */
+
+#ifndef SQLITE_RTREE_INT_ONLY
+ if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
+ for(ii=0; ii<nn; ii+=2){
+ cell.aCoord[ii].f = rtreeValueDown(aData[ii+3]);
+ cell.aCoord[ii+1].f = rtreeValueUp(aData[ii+4]);
+ if( cell.aCoord[ii].f>cell.aCoord[ii+1].f ){
+ rc = rtreeConstraintError(pRtree, ii+1);
+ goto constraint;
+ }
+ }
+ }else
+#endif
+ {
+ for(ii=0; ii<nn; ii+=2){
+ cell.aCoord[ii].i = sqlite3_value_int(aData[ii+3]);
+ cell.aCoord[ii+1].i = sqlite3_value_int(aData[ii+4]);
+ if( cell.aCoord[ii].i>cell.aCoord[ii+1].i ){
+ rc = rtreeConstraintError(pRtree, ii+1);
+ goto constraint;
+ }
+ }
+ }
+
+ /* If a rowid value was supplied, check if it is already present in
+ ** the table. If so, the constraint has failed. */
+ if( sqlite3_value_type(aData[2])!=SQLITE_NULL ){
+ cell.iRowid = sqlite3_value_int64(aData[2]);
+ if( sqlite3_value_type(aData[0])==SQLITE_NULL
+ || sqlite3_value_int64(aData[0])!=cell.iRowid
+ ){
+ int steprc;
+ sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
+ steprc = sqlite3_step(pRtree->pReadRowid);
+ rc = sqlite3_reset(pRtree->pReadRowid);
+ if( SQLITE_ROW==steprc ){
+ if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
+ rc = rtreeDeleteRowid(pRtree, cell.iRowid);
+ }else{
+ rc = rtreeConstraintError(pRtree, 0);
+ goto constraint;
+ }
+ }
+ }
+ bHaveRowid = 1;
+ }
+ }
+
+ /* If aData[0] is not an SQL NULL value, it is the rowid of a
+ ** record to delete from the r-tree table. The following block does
+ ** just that.
+ */
+ if( sqlite3_value_type(aData[0])!=SQLITE_NULL ){
+ rc = rtreeDeleteRowid(pRtree, sqlite3_value_int64(aData[0]));
+ }
+
+ /* If the aData[] array contains more than one element, elements
+ ** (aData[2]..aData[argc-1]) contain a new record to insert into
+ ** the r-tree structure.
+ */
+ if( rc==SQLITE_OK && nData>1 ){
+ /* Insert the new record into the r-tree */
+ RtreeNode *pLeaf = 0;
+
+ /* Figure out the rowid of the new row. */
+ if( bHaveRowid==0 ){
+ rc = rtreeNewRowid(pRtree, &cell.iRowid);
+ }
+ *pRowid = cell.iRowid;
+
+ if( rc==SQLITE_OK ){
+ rc = ChooseLeaf(pRtree, &cell, 0, &pLeaf);
+ }
+ if( rc==SQLITE_OK ){
+ int rc2;
+ pRtree->iReinsertHeight = -1;
+ rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0);
+ rc2 = nodeRelease(pRtree, pLeaf);
+ if( rc==SQLITE_OK ){
+ rc = rc2;
+ }
+ }
+ if( rc==SQLITE_OK && pRtree->nAux ){
+ sqlite3_stmt *pUp = pRtree->pWriteAux;
+ int jj;
+ sqlite3_bind_int64(pUp, 1, *pRowid);
+ for(jj=0; jj<pRtree->nAux; jj++){
+ sqlite3_bind_value(pUp, jj+2, aData[pRtree->nDim2+3+jj]);
+ }
+ sqlite3_step(pUp);
+ rc = sqlite3_reset(pUp);
+ }
+ }
+
+constraint:
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+/*
+** Called when a transaction starts.
+*/
+static int rtreeBeginTransaction(sqlite3_vtab *pVtab){
+ Rtree *pRtree = (Rtree *)pVtab;
+ assert( pRtree->inWrTrans==0 );
+ pRtree->inWrTrans++;
+ return SQLITE_OK;
+}
+
+/*
+** Called when a transaction completes (either by COMMIT or ROLLBACK).
+** The sqlite3_blob object should be released at this point.
+*/
+static int rtreeEndTransaction(sqlite3_vtab *pVtab){
+ Rtree *pRtree = (Rtree *)pVtab;
+ pRtree->inWrTrans = 0;
+ nodeBlobReset(pRtree);
+ return SQLITE_OK;
+}
+
+/*
+** The xRename method for rtree module virtual tables.
+*/
+static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){
+ Rtree *pRtree = (Rtree *)pVtab;
+ int rc = SQLITE_NOMEM;
+ char *zSql = sqlite3_mprintf(
+ "ALTER TABLE %Q.'%q_node' RENAME TO \"%w_node\";"
+ "ALTER TABLE %Q.'%q_parent' RENAME TO \"%w_parent\";"
+ "ALTER TABLE %Q.'%q_rowid' RENAME TO \"%w_rowid\";"
+ , pRtree->zDb, pRtree->zName, zNewName
+ , pRtree->zDb, pRtree->zName, zNewName
+ , pRtree->zDb, pRtree->zName, zNewName
+ );
+ if( zSql ){
+ nodeBlobReset(pRtree);
+ rc = sqlite3_exec(pRtree->db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ }
+ return rc;
+}
+
+/*
+** The xSavepoint method.
+**
+** This module does not need to do anything to support savepoints. However,
+** it uses this hook to close any open blob handle. This is done because a
+** DROP TABLE command - which fortunately always opens a savepoint - cannot
+** succeed if there are any open blob handles. i.e. if the blob handle were
+** not closed here, the following would fail:
+**
+** BEGIN;
+** INSERT INTO rtree...
+** DROP TABLE <tablename>; -- Would fail with SQLITE_LOCKED
+** COMMIT;
+*/
+static int rtreeSavepoint(sqlite3_vtab *pVtab, int iSavepoint){
+ Rtree *pRtree = (Rtree *)pVtab;
+ u8 iwt = pRtree->inWrTrans;
+ UNUSED_PARAMETER(iSavepoint);
+ pRtree->inWrTrans = 0;
+ nodeBlobReset(pRtree);
+ pRtree->inWrTrans = iwt;
+ return SQLITE_OK;
+}
+
+/*
+** This function populates the pRtree->nRowEst variable with an estimate
+** of the number of rows in the virtual table. If possible, this is based
+** on sqlite_stat1 data. Otherwise, use RTREE_DEFAULT_ROWEST.
+*/
+static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){
+ const char *zFmt = "SELECT stat FROM %Q.sqlite_stat1 WHERE tbl = '%q_rowid'";
+ char *zSql;
+ sqlite3_stmt *p;
+ int rc;
+ i64 nRow = RTREE_MIN_ROWEST;
+
+ rc = sqlite3_table_column_metadata(
+ db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0
+ );
+ if( rc!=SQLITE_OK ){
+ pRtree->nRowEst = RTREE_DEFAULT_ROWEST;
+ return rc==SQLITE_ERROR ? SQLITE_OK : rc;
+ }
+ zSql = sqlite3_mprintf(zFmt, pRtree->zDb, pRtree->zName);
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v2(db, zSql, -1, &p, 0);
+ if( rc==SQLITE_OK ){
+ if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0);
+ rc = sqlite3_finalize(p);
+ }
+ sqlite3_free(zSql);
+ }
+ pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST);
+ return rc;
+}
+
+
+/*
+** Return true if zName is the extension on one of the shadow tables used
+** by this module.
+*/
+static int rtreeShadowName(const char *zName){
+ static const char *azName[] = {
+ "node", "parent", "rowid"
+ };
+ unsigned int i;
+ for(i=0; i<sizeof(azName)/sizeof(azName[0]); i++){
+ if( sqlite3_stricmp(zName, azName[i])==0 ) return 1;
+ }
+ return 0;
+}
+
+static sqlite3_module rtreeModule = {
+ 3, /* iVersion */
+ rtreeCreate, /* xCreate - create a table */
+ rtreeConnect, /* xConnect - connect to an existing table */
+ rtreeBestIndex, /* xBestIndex - Determine search strategy */
+ rtreeDisconnect, /* xDisconnect - Disconnect from a table */
+ rtreeDestroy, /* xDestroy - Drop a table */
+ rtreeOpen, /* xOpen - open a cursor */
+ rtreeClose, /* xClose - close a cursor */
+ rtreeFilter, /* xFilter - configure scan constraints */
+ rtreeNext, /* xNext - advance a cursor */
+ rtreeEof, /* xEof */
+ rtreeColumn, /* xColumn - read data */
+ rtreeRowid, /* xRowid - read data */
+ rtreeUpdate, /* xUpdate - write data */
+ rtreeBeginTransaction, /* xBegin - begin transaction */
+ rtreeEndTransaction, /* xSync - sync transaction */
+ rtreeEndTransaction, /* xCommit - commit transaction */
+ rtreeEndTransaction, /* xRollback - rollback transaction */
+ 0, /* xFindFunction - function overloading */
+ rtreeRename, /* xRename - rename the table */
+ rtreeSavepoint, /* xSavepoint */
+ 0, /* xRelease */
+ 0, /* xRollbackTo */
+ rtreeShadowName /* xShadowName */
+};
+
+static int rtreeSqlInit(
+ Rtree *pRtree,
+ sqlite3 *db,
+ const char *zDb,
+ const char *zPrefix,
+ int isCreate
+){
+ int rc = SQLITE_OK;
+
+ #define N_STATEMENT 8
+ static const char *azSql[N_STATEMENT] = {
+ /* Write the xxx_node table */
+ "INSERT OR REPLACE INTO '%q'.'%q_node' VALUES(?1, ?2)",
+ "DELETE FROM '%q'.'%q_node' WHERE nodeno = ?1",
+
+ /* Read and write the xxx_rowid table */
+ "SELECT nodeno FROM '%q'.'%q_rowid' WHERE rowid = ?1",
+ "INSERT OR REPLACE INTO '%q'.'%q_rowid' VALUES(?1, ?2)",
+ "DELETE FROM '%q'.'%q_rowid' WHERE rowid = ?1",
+
+ /* Read and write the xxx_parent table */
+ "SELECT parentnode FROM '%q'.'%q_parent' WHERE nodeno = ?1",
+ "INSERT OR REPLACE INTO '%q'.'%q_parent' VALUES(?1, ?2)",
+ "DELETE FROM '%q'.'%q_parent' WHERE nodeno = ?1"
+ };
+ sqlite3_stmt **appStmt[N_STATEMENT];
+ int i;
+ const int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB;
+
+ pRtree->db = db;
+
+ if( isCreate ){
+ char *zCreate;
+ sqlite3_str *p = sqlite3_str_new(db);
+ int ii;
+ sqlite3_str_appendf(p,
+ "CREATE TABLE \"%w\".\"%w_rowid\"(rowid INTEGER PRIMARY KEY,nodeno",
+ zDb, zPrefix);
+ for(ii=0; ii<pRtree->nAux; ii++){
+ sqlite3_str_appendf(p,",a%d",ii);
+ }
+ sqlite3_str_appendf(p,
+ ");CREATE TABLE \"%w\".\"%w_node\"(nodeno INTEGER PRIMARY KEY,data);",
+ zDb, zPrefix);
+ sqlite3_str_appendf(p,
+ "CREATE TABLE \"%w\".\"%w_parent\"(nodeno INTEGER PRIMARY KEY,parentnode);",
+ zDb, zPrefix);
+ sqlite3_str_appendf(p,
+ "INSERT INTO \"%w\".\"%w_node\"VALUES(1,zeroblob(%d))",
+ zDb, zPrefix, pRtree->iNodeSize);
+ zCreate = sqlite3_str_finish(p);
+ if( !zCreate ){
+ return SQLITE_NOMEM;
+ }
+ rc = sqlite3_exec(db, zCreate, 0, 0, 0);
+ sqlite3_free(zCreate);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+ }
+
+ appStmt[0] = &pRtree->pWriteNode;
+ appStmt[1] = &pRtree->pDeleteNode;
+ appStmt[2] = &pRtree->pReadRowid;
+ appStmt[3] = &pRtree->pWriteRowid;
+ appStmt[4] = &pRtree->pDeleteRowid;
+ appStmt[5] = &pRtree->pReadParent;
+ appStmt[6] = &pRtree->pWriteParent;
+ appStmt[7] = &pRtree->pDeleteParent;
+
+ rc = rtreeQueryStat1(db, pRtree);
+ for(i=0; i<N_STATEMENT && rc==SQLITE_OK; i++){
+ char *zSql;
+ const char *zFormat;
+ if( i!=3 || pRtree->nAux==0 ){
+ zFormat = azSql[i];
+ }else {
+ /* An UPSERT is very slightly slower than REPLACE, but it is needed
+ ** if there are auxiliary columns */
+ zFormat = "INSERT INTO\"%w\".\"%w_rowid\"(rowid,nodeno)VALUES(?1,?2)"
+ "ON CONFLICT(rowid)DO UPDATE SET nodeno=excluded.nodeno";
+ }
+ zSql = sqlite3_mprintf(zFormat, zDb, zPrefix);
+ if( zSql ){
+ rc = sqlite3_prepare_v3(db, zSql, -1, f, appStmt[i], 0);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+ sqlite3_free(zSql);
+ }
+ if( pRtree->nAux ){
+ pRtree->zReadAuxSql = sqlite3_mprintf(
+ "SELECT * FROM \"%w\".\"%w_rowid\" WHERE rowid=?1",
+ zDb, zPrefix);
+ if( pRtree->zReadAuxSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ sqlite3_str *p = sqlite3_str_new(db);
+ int ii;
+ char *zSql;
+ sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix);
+ for(ii=0; ii<pRtree->nAux; ii++){
+ if( ii ) sqlite3_str_append(p, ",", 1);
+#ifdef SQLITE_ENABLE_GEOPOLY
+ if( ii<pRtree->nAuxNotNull ){
+ sqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii);
+ }else
+#endif
+ {
+ sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2);
+ }
+ }
+ sqlite3_str_appendf(p, " WHERE rowid=?1");
+ zSql = sqlite3_str_finish(p);
+ if( zSql==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v3(db, zSql, -1, f, &pRtree->pWriteAux, 0);
+ sqlite3_free(zSql);
+ }
+ }
+ }
+
+ return rc;
+}
+
+/*
+** The second argument to this function contains the text of an SQL statement
+** that returns a single integer value. The statement is compiled and executed
+** using database connection db. If successful, the integer value returned
+** is written to *piVal and SQLITE_OK returned. Otherwise, an SQLite error
+** code is returned and the value of *piVal after returning is not defined.
+*/
+static int getIntFromStmt(sqlite3 *db, const char *zSql, int *piVal){
+ int rc = SQLITE_NOMEM;
+ if( zSql ){
+ sqlite3_stmt *pStmt = 0;
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc==SQLITE_OK ){
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ *piVal = sqlite3_column_int(pStmt, 0);
+ }
+ rc = sqlite3_finalize(pStmt);
+ }
+ }
+ return rc;
+}
+
+/*
+** This function is called from within the xConnect() or xCreate() method to
+** determine the node-size used by the rtree table being created or connected
+** to. If successful, pRtree->iNodeSize is populated and SQLITE_OK returned.
+** Otherwise, an SQLite error code is returned.
+**
+** If this function is being called as part of an xConnect(), then the rtree
+** table already exists. In this case the node-size is determined by inspecting
+** the root node of the tree.
+**
+** Otherwise, for an xCreate(), use 64 bytes less than the database page-size.
+** This ensures that each node is stored on a single database page. If the
+** database page-size is so large that more than RTREE_MAXCELLS entries
+** would fit in a single node, use a smaller node-size.
+*/
+static int getNodeSize(
+ sqlite3 *db, /* Database handle */
+ Rtree *pRtree, /* Rtree handle */
+ int isCreate, /* True for xCreate, false for xConnect */
+ char **pzErr /* OUT: Error message, if any */
+){
+ int rc;
+ char *zSql;
+ if( isCreate ){
+ int iPageSize = 0;
+ zSql = sqlite3_mprintf("PRAGMA %Q.page_size", pRtree->zDb);
+ rc = getIntFromStmt(db, zSql, &iPageSize);
+ if( rc==SQLITE_OK ){
+ pRtree->iNodeSize = iPageSize-64;
+ if( (4+pRtree->nBytesPerCell*RTREE_MAXCELLS)<pRtree->iNodeSize ){
+ pRtree->iNodeSize = 4+pRtree->nBytesPerCell*RTREE_MAXCELLS;
+ }
+ }else{
+ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ }
+ }else{
+ zSql = sqlite3_mprintf(
+ "SELECT length(data) FROM '%q'.'%q_node' WHERE nodeno = 1",
+ pRtree->zDb, pRtree->zName
+ );
+ rc = getIntFromStmt(db, zSql, &pRtree->iNodeSize);
+ if( rc!=SQLITE_OK ){
+ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ }else if( pRtree->iNodeSize<(512-64) ){
+ rc = SQLITE_CORRUPT_VTAB;
+ RTREE_IS_CORRUPT(pRtree);
+ *pzErr = sqlite3_mprintf("undersize RTree blobs in \"%q_node\"",
+ pRtree->zName);
+ }
+ }
+
+ sqlite3_free(zSql);
+ return rc;
+}
+
+/*
+** Return the length of a token
+*/
+static int rtreeTokenLength(const char *z){
+ int dummy = 0;
+ return sqlite3GetToken((const unsigned char*)z,&dummy);
+}
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the r-tree virtual table.
+**
+** argv[0] -> module name
+** argv[1] -> database name
+** argv[2] -> table name
+** argv[...] -> column names...
+*/
+static int rtreeInit(
+ sqlite3 *db, /* Database connection */
+ void *pAux, /* One of the RTREE_COORD_* constants */
+ int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */
+ sqlite3_vtab **ppVtab, /* OUT: New virtual table */
+ char **pzErr, /* OUT: Error message, if any */
+ int isCreate /* True for xCreate, false for xConnect */
+){
+ int rc = SQLITE_OK;
+ Rtree *pRtree;
+ int nDb; /* Length of string argv[1] */
+ int nName; /* Length of string argv[2] */
+ int eCoordType = (pAux ? RTREE_COORD_INT32 : RTREE_COORD_REAL32);
+ sqlite3_str *pSql;
+ char *zSql;
+ int ii = 4;
+ int iErr;
+
+ const char *aErrMsg[] = {
+ 0, /* 0 */
+ "Wrong number of columns for an rtree table", /* 1 */
+ "Too few columns for an rtree table", /* 2 */
+ "Too many columns for an rtree table", /* 3 */
+ "Auxiliary rtree columns must be last" /* 4 */
+ };
+
+ assert( RTREE_MAX_AUX_COLUMN<256 ); /* Aux columns counted by a u8 */
+ if( argc<6 || argc>RTREE_MAX_AUX_COLUMN+3 ){
+ *pzErr = sqlite3_mprintf("%s", aErrMsg[2 + (argc>=6)]);
+ return SQLITE_ERROR;
+ }
+
+ sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+
+ /* Allocate the sqlite3_vtab structure */
+ nDb = (int)strlen(argv[1]);
+ nName = (int)strlen(argv[2]);
+ pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2);
+ if( !pRtree ){
+ return SQLITE_NOMEM;
+ }
+ memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2);
+ pRtree->nBusy = 1;
+ pRtree->base.pModule = &rtreeModule;
+ pRtree->zDb = (char *)&pRtree[1];
+ pRtree->zName = &pRtree->zDb[nDb+1];
+ pRtree->eCoordType = (u8)eCoordType;
+ memcpy(pRtree->zDb, argv[1], nDb);
+ memcpy(pRtree->zName, argv[2], nName);
+
+
+ /* Create/Connect to the underlying relational database schema. If
+ ** that is successful, call sqlite3_declare_vtab() to configure
+ ** the r-tree table schema.
+ */
+ pSql = sqlite3_str_new(db);
+ sqlite3_str_appendf(pSql, "CREATE TABLE x(%.*s INT",
+ rtreeTokenLength(argv[3]), argv[3]);
+ for(ii=4; ii<argc; ii++){
+ const char *zArg = argv[ii];
+ if( zArg[0]=='+' ){
+ pRtree->nAux++;
+ sqlite3_str_appendf(pSql, ",%.*s", rtreeTokenLength(zArg+1), zArg+1);
+ }else if( pRtree->nAux>0 ){
+ break;
+ }else{
+ static const char *azFormat[] = {",%.*s REAL", ",%.*s INT"};
+ pRtree->nDim2++;
+ sqlite3_str_appendf(pSql, azFormat[eCoordType],
+ rtreeTokenLength(zArg), zArg);
+ }
+ }
+ sqlite3_str_appendf(pSql, ");");
+ zSql = sqlite3_str_finish(pSql);
+ if( !zSql ){
+ rc = SQLITE_NOMEM;
+ }else if( ii<argc ){
+ *pzErr = sqlite3_mprintf("%s", aErrMsg[4]);
+ rc = SQLITE_ERROR;
+ }else if( SQLITE_OK!=(rc = sqlite3_declare_vtab(db, zSql)) ){
+ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ }
+ sqlite3_free(zSql);
+ if( rc ) goto rtreeInit_fail;
+ pRtree->nDim = pRtree->nDim2/2;
+ if( pRtree->nDim<1 ){
+ iErr = 2;
+ }else if( pRtree->nDim2>RTREE_MAX_DIMENSIONS*2 ){
+ iErr = 3;
+ }else if( pRtree->nDim2 % 2 ){
+ iErr = 1;
+ }else{
+ iErr = 0;
+ }
+ if( iErr ){
+ *pzErr = sqlite3_mprintf("%s", aErrMsg[iErr]);
+ goto rtreeInit_fail;
+ }
+ pRtree->nBytesPerCell = 8 + pRtree->nDim2*4;
+
+ /* Figure out the node size to use. */
+ rc = getNodeSize(db, pRtree, isCreate, pzErr);
+ if( rc ) goto rtreeInit_fail;
+ rc = rtreeSqlInit(pRtree, db, argv[1], argv[2], isCreate);
+ if( rc ){
+ *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db));
+ goto rtreeInit_fail;
+ }
+
+ *ppVtab = (sqlite3_vtab *)pRtree;
+ return SQLITE_OK;
+
+rtreeInit_fail:
+ if( rc==SQLITE_OK ) rc = SQLITE_ERROR;
+ assert( *ppVtab==0 );
+ assert( pRtree->nBusy==1 );
+ rtreeRelease(pRtree);
+ return rc;
+}
+
+
+/*
+** Implementation of a scalar function that decodes r-tree nodes to
+** human readable strings. This can be used for debugging and analysis.
+**
+** The scalar function takes two arguments: (1) the number of dimensions
+** to the rtree (between 1 and 5, inclusive) and (2) a blob of data containing
+** an r-tree node. For a two-dimensional r-tree structure called "rt", to
+** deserialize all nodes, a statement like:
+**
+** SELECT rtreenode(2, data) FROM rt_node;
+**
+** The human readable string takes the form of a Tcl list with one
+** entry for each cell in the r-tree node. Each entry is itself a
+** list, containing the 8-byte rowid/pageno followed by the
+** <num-dimension>*2 coordinates.
+*/
+static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
+ RtreeNode node;
+ Rtree tree;
+ int ii;
+ int nData;
+ int errCode;
+ sqlite3_str *pOut;
+
+ UNUSED_PARAMETER(nArg);
+ memset(&node, 0, sizeof(RtreeNode));
+ memset(&tree, 0, sizeof(Rtree));
+ tree.nDim = (u8)sqlite3_value_int(apArg[0]);
+ if( tree.nDim<1 || tree.nDim>5 ) return;
+ tree.nDim2 = tree.nDim*2;
+ tree.nBytesPerCell = 8 + 8 * tree.nDim;
+ node.zData = (u8 *)sqlite3_value_blob(apArg[1]);
+ if( node.zData==0 ) return;
+ nData = sqlite3_value_bytes(apArg[1]);
+ if( nData<4 ) return;
+ if( nData<NCELL(&node)*tree.nBytesPerCell ) return;
+
+ pOut = sqlite3_str_new(0);
+ for(ii=0; ii<NCELL(&node); ii++){
+ RtreeCell cell;
+ int jj;
+
+ nodeGetCell(&tree, &node, ii, &cell);
+ if( ii>0 ) sqlite3_str_append(pOut, " ", 1);
+ sqlite3_str_appendf(pOut, "{%lld", cell.iRowid);
+ for(jj=0; jj<tree.nDim2; jj++){
+#ifndef SQLITE_RTREE_INT_ONLY
+ sqlite3_str_appendf(pOut, " %g", (double)cell.aCoord[jj].f);
+#else
+ sqlite3_str_appendf(pOut, " %d", cell.aCoord[jj].i);
+#endif
+ }
+ sqlite3_str_append(pOut, "}", 1);
+ }
+ errCode = sqlite3_str_errcode(pOut);
+ sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free);
+ sqlite3_result_error_code(ctx, errCode);
+}
+
+/* This routine implements an SQL function that returns the "depth" parameter
+** from the front of a blob that is an r-tree node. For example:
+**
+** SELECT rtreedepth(data) FROM rt_node WHERE nodeno=1;
+**
+** The depth value is 0 for all nodes other than the root node, and the root
+** node always has nodeno=1, so the example above is the primary use for this
+** routine. This routine is intended for testing and analysis only.
+*/
+static void rtreedepth(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){
+ UNUSED_PARAMETER(nArg);
+ if( sqlite3_value_type(apArg[0])!=SQLITE_BLOB
+ || sqlite3_value_bytes(apArg[0])<2
+
+ ){
+ sqlite3_result_error(ctx, "Invalid argument to rtreedepth()", -1);
+ }else{
+ u8 *zBlob = (u8 *)sqlite3_value_blob(apArg[0]);
+ if( zBlob ){
+ sqlite3_result_int(ctx, readInt16(zBlob));
+ }else{
+ sqlite3_result_error_nomem(ctx);
+ }
+ }
+}
+
+/*
+** Context object passed between the various routines that make up the
+** implementation of integrity-check function rtreecheck().
+*/
+typedef struct RtreeCheck RtreeCheck;
+struct RtreeCheck {
+ sqlite3 *db; /* Database handle */
+ const char *zDb; /* Database containing rtree table */
+ const char *zTab; /* Name of rtree table */
+ int bInt; /* True for rtree_i32 table */
+ int nDim; /* Number of dimensions for this rtree tbl */
+ sqlite3_stmt *pGetNode; /* Statement used to retrieve nodes */
+ sqlite3_stmt *aCheckMapping[2]; /* Statements to query %_parent/%_rowid */
+ int nLeaf; /* Number of leaf cells in table */
+ int nNonLeaf; /* Number of non-leaf cells in table */
+ int rc; /* Return code */
+ char *zReport; /* Message to report */
+ int nErr; /* Number of lines in zReport */
+};
+
+#define RTREE_CHECK_MAX_ERROR 100
+
+/*
+** Reset SQL statement pStmt. If the sqlite3_reset() call returns an error,
+** and RtreeCheck.rc==SQLITE_OK, set RtreeCheck.rc to the error code.
+*/
+static void rtreeCheckReset(RtreeCheck *pCheck, sqlite3_stmt *pStmt){
+ int rc = sqlite3_reset(pStmt);
+ if( pCheck->rc==SQLITE_OK ) pCheck->rc = rc;
+}
+
+/*
+** The second and subsequent arguments to this function are a format string
+** and printf style arguments. This function formats the string and attempts
+** to compile it as an SQL statement.
+**
+** If successful, a pointer to the new SQL statement is returned. Otherwise,
+** NULL is returned and an error code left in RtreeCheck.rc.
+*/
+static sqlite3_stmt *rtreeCheckPrepare(
+ RtreeCheck *pCheck, /* RtreeCheck object */
+ const char *zFmt, ... /* Format string and trailing args */
+){
+ va_list ap;
+ char *z;
+ sqlite3_stmt *pRet = 0;
+
+ va_start(ap, zFmt);
+ z = sqlite3_vmprintf(zFmt, ap);
+
+ if( pCheck->rc==SQLITE_OK ){
+ if( z==0 ){
+ pCheck->rc = SQLITE_NOMEM;
+ }else{
+ pCheck->rc = sqlite3_prepare_v2(pCheck->db, z, -1, &pRet, 0);
+ }
+ }
+
+ sqlite3_free(z);
+ va_end(ap);
+ return pRet;
+}
+
+/*
+** The second and subsequent arguments to this function are a printf()
+** style format string and arguments. This function formats the string and
+** appends it to the report being accumuated in pCheck.
+*/
+static void rtreeCheckAppendMsg(RtreeCheck *pCheck, const char *zFmt, ...){
+ va_list ap;
+ va_start(ap, zFmt);
+ if( pCheck->rc==SQLITE_OK && pCheck->nErr<RTREE_CHECK_MAX_ERROR ){
+ char *z = sqlite3_vmprintf(zFmt, ap);
+ if( z==0 ){
+ pCheck->rc = SQLITE_NOMEM;
+ }else{
+ pCheck->zReport = sqlite3_mprintf("%z%s%z",
+ pCheck->zReport, (pCheck->zReport ? "\n" : ""), z
+ );
+ if( pCheck->zReport==0 ){
+ pCheck->rc = SQLITE_NOMEM;
+ }
+ }
+ pCheck->nErr++;
+ }
+ va_end(ap);
+}
+
+/*
+** This function is a no-op if there is already an error code stored
+** in the RtreeCheck object indicated by the first argument. NULL is
+** returned in this case.
+**
+** Otherwise, the contents of rtree table node iNode are loaded from
+** the database and copied into a buffer obtained from sqlite3_malloc().
+** If no error occurs, a pointer to the buffer is returned and (*pnNode)
+** is set to the size of the buffer in bytes.
+**
+** Or, if an error does occur, NULL is returned and an error code left
+** in the RtreeCheck object. The final value of *pnNode is undefined in
+** this case.
+*/
+static u8 *rtreeCheckGetNode(RtreeCheck *pCheck, i64 iNode, int *pnNode){
+ u8 *pRet = 0; /* Return value */
+
+ if( pCheck->rc==SQLITE_OK && pCheck->pGetNode==0 ){
+ pCheck->pGetNode = rtreeCheckPrepare(pCheck,
+ "SELECT data FROM %Q.'%q_node' WHERE nodeno=?",
+ pCheck->zDb, pCheck->zTab
+ );
+ }
+
+ if( pCheck->rc==SQLITE_OK ){
+ sqlite3_bind_int64(pCheck->pGetNode, 1, iNode);
+ if( sqlite3_step(pCheck->pGetNode)==SQLITE_ROW ){
+ int nNode = sqlite3_column_bytes(pCheck->pGetNode, 0);
+ const u8 *pNode = (const u8*)sqlite3_column_blob(pCheck->pGetNode, 0);
+ pRet = sqlite3_malloc64(nNode);
+ if( pRet==0 ){
+ pCheck->rc = SQLITE_NOMEM;
+ }else{
+ memcpy(pRet, pNode, nNode);
+ *pnNode = nNode;
+ }
+ }
+ rtreeCheckReset(pCheck, pCheck->pGetNode);
+ if( pCheck->rc==SQLITE_OK && pRet==0 ){
+ rtreeCheckAppendMsg(pCheck, "Node %lld missing from database", iNode);
+ }
+ }
+
+ return pRet;
+}
+
+/*
+** This function is used to check that the %_parent (if bLeaf==0) or %_rowid
+** (if bLeaf==1) table contains a specified entry. The schemas of the
+** two tables are:
+**
+** CREATE TABLE %_parent(nodeno INTEGER PRIMARY KEY, parentnode INTEGER)
+** CREATE TABLE %_rowid(rowid INTEGER PRIMARY KEY, nodeno INTEGER, ...)
+**
+** In both cases, this function checks that there exists an entry with
+** IPK value iKey and the second column set to iVal.
+**
+*/
+static void rtreeCheckMapping(
+ RtreeCheck *pCheck, /* RtreeCheck object */
+ int bLeaf, /* True for a leaf cell, false for interior */
+ i64 iKey, /* Key for mapping */
+ i64 iVal /* Expected value for mapping */
+){
+ int rc;
+ sqlite3_stmt *pStmt;
+ const char *azSql[2] = {
+ "SELECT parentnode FROM %Q.'%q_parent' WHERE nodeno=?1",
+ "SELECT nodeno FROM %Q.'%q_rowid' WHERE rowid=?1"
+ };
+
+ assert( bLeaf==0 || bLeaf==1 );
+ if( pCheck->aCheckMapping[bLeaf]==0 ){
+ pCheck->aCheckMapping[bLeaf] = rtreeCheckPrepare(pCheck,
+ azSql[bLeaf], pCheck->zDb, pCheck->zTab
+ );
+ }
+ if( pCheck->rc!=SQLITE_OK ) return;
+
+ pStmt = pCheck->aCheckMapping[bLeaf];
+ sqlite3_bind_int64(pStmt, 1, iKey);
+ rc = sqlite3_step(pStmt);
+ if( rc==SQLITE_DONE ){
+ rtreeCheckAppendMsg(pCheck, "Mapping (%lld -> %lld) missing from %s table",
+ iKey, iVal, (bLeaf ? "%_rowid" : "%_parent")
+ );
+ }else if( rc==SQLITE_ROW ){
+ i64 ii = sqlite3_column_int64(pStmt, 0);
+ if( ii!=iVal ){
+ rtreeCheckAppendMsg(pCheck,
+ "Found (%lld -> %lld) in %s table, expected (%lld -> %lld)",
+ iKey, ii, (bLeaf ? "%_rowid" : "%_parent"), iKey, iVal
+ );
+ }
+ }
+ rtreeCheckReset(pCheck, pStmt);
+}
+
+/*
+** Argument pCell points to an array of coordinates stored on an rtree page.
+** This function checks that the coordinates are internally consistent (no
+** x1>x2 conditions) and adds an error message to the RtreeCheck object
+** if they are not.
+**
+** Additionally, if pParent is not NULL, then it is assumed to point to
+** the array of coordinates on the parent page that bound the page
+** containing pCell. In this case it is also verified that the two
+** sets of coordinates are mutually consistent and an error message added
+** to the RtreeCheck object if they are not.
+*/
+static void rtreeCheckCellCoord(
+ RtreeCheck *pCheck,
+ i64 iNode, /* Node id to use in error messages */
+ int iCell, /* Cell number to use in error messages */
+ u8 *pCell, /* Pointer to cell coordinates */
+ u8 *pParent /* Pointer to parent coordinates */
+){
+ RtreeCoord c1, c2;
+ RtreeCoord p1, p2;
+ int i;
+
+ for(i=0; i<pCheck->nDim; i++){
+ readCoord(&pCell[4*2*i], &c1);
+ readCoord(&pCell[4*(2*i + 1)], &c2);
+
+ /* printf("%e, %e\n", c1.u.f, c2.u.f); */
+ if( pCheck->bInt ? c1.i>c2.i : c1.f>c2.f ){
+ rtreeCheckAppendMsg(pCheck,
+ "Dimension %d of cell %d on node %lld is corrupt", i, iCell, iNode
+ );
+ }
+
+ if( pParent ){
+ readCoord(&pParent[4*2*i], &p1);
+ readCoord(&pParent[4*(2*i + 1)], &p2);
+
+ if( (pCheck->bInt ? c1.i<p1.i : c1.f<p1.f)
+ || (pCheck->bInt ? c2.i>p2.i : c2.f>p2.f)
+ ){
+ rtreeCheckAppendMsg(pCheck,
+ "Dimension %d of cell %d on node %lld is corrupt relative to parent"
+ , i, iCell, iNode
+ );
+ }
+ }
+ }
+}
+
+/*
+** Run rtreecheck() checks on node iNode, which is at depth iDepth within
+** the r-tree structure. Argument aParent points to the array of coordinates
+** that bound node iNode on the parent node.
+**
+** If any problems are discovered, an error message is appended to the
+** report accumulated in the RtreeCheck object.
+*/
+static void rtreeCheckNode(
+ RtreeCheck *pCheck,
+ int iDepth, /* Depth of iNode (0==leaf) */
+ u8 *aParent, /* Buffer containing parent coords */
+ i64 iNode /* Node to check */
+){
+ u8 *aNode = 0;
+ int nNode = 0;
+
+ assert( iNode==1 || aParent!=0 );
+ assert( pCheck->nDim>0 );
+
+ aNode = rtreeCheckGetNode(pCheck, iNode, &nNode);
+ if( aNode ){
+ if( nNode<4 ){
+ rtreeCheckAppendMsg(pCheck,
+ "Node %lld is too small (%d bytes)", iNode, nNode
+ );
+ }else{
+ int nCell; /* Number of cells on page */
+ int i; /* Used to iterate through cells */
+ if( aParent==0 ){
+ iDepth = readInt16(aNode);
+ if( iDepth>RTREE_MAX_DEPTH ){
+ rtreeCheckAppendMsg(pCheck, "Rtree depth out of range (%d)", iDepth);
+ sqlite3_free(aNode);
+ return;
+ }
+ }
+ nCell = readInt16(&aNode[2]);
+ if( (4 + nCell*(8 + pCheck->nDim*2*4))>nNode ){
+ rtreeCheckAppendMsg(pCheck,
+ "Node %lld is too small for cell count of %d (%d bytes)",
+ iNode, nCell, nNode
+ );
+ }else{
+ for(i=0; i<nCell; i++){
+ u8 *pCell = &aNode[4 + i*(8 + pCheck->nDim*2*4)];
+ i64 iVal = readInt64(pCell);
+ rtreeCheckCellCoord(pCheck, iNode, i, &pCell[8], aParent);
+
+ if( iDepth>0 ){
+ rtreeCheckMapping(pCheck, 0, iVal, iNode);
+ rtreeCheckNode(pCheck, iDepth-1, &pCell[8], iVal);
+ pCheck->nNonLeaf++;
+ }else{
+ rtreeCheckMapping(pCheck, 1, iVal, iNode);
+ pCheck->nLeaf++;
+ }
+ }
+ }
+ }
+ sqlite3_free(aNode);
+ }
+}
+
+/*
+** The second argument to this function must be either "_rowid" or
+** "_parent". This function checks that the number of entries in the
+** %_rowid or %_parent table is exactly nExpect. If not, it adds
+** an error message to the report in the RtreeCheck object indicated
+** by the first argument.
+*/
+static void rtreeCheckCount(RtreeCheck *pCheck, const char *zTbl, i64 nExpect){
+ if( pCheck->rc==SQLITE_OK ){
+ sqlite3_stmt *pCount;
+ pCount = rtreeCheckPrepare(pCheck, "SELECT count(*) FROM %Q.'%q%s'",
+ pCheck->zDb, pCheck->zTab, zTbl
+ );
+ if( pCount ){
+ if( sqlite3_step(pCount)==SQLITE_ROW ){
+ i64 nActual = sqlite3_column_int64(pCount, 0);
+ if( nActual!=nExpect ){
+ rtreeCheckAppendMsg(pCheck, "Wrong number of entries in %%%s table"
+ " - expected %lld, actual %lld" , zTbl, nExpect, nActual
+ );
+ }
+ }
+ pCheck->rc = sqlite3_finalize(pCount);
+ }
+ }
+}
+
+/*
+** This function does the bulk of the work for the rtree integrity-check.
+** It is called by rtreecheck(), which is the SQL function implementation.
+*/
+static int rtreeCheckTable(
+ sqlite3 *db, /* Database handle to access db through */
+ const char *zDb, /* Name of db ("main", "temp" etc.) */
+ const char *zTab, /* Name of rtree table to check */
+ char **pzReport /* OUT: sqlite3_malloc'd report text */
+){
+ RtreeCheck check; /* Common context for various routines */
+ sqlite3_stmt *pStmt = 0; /* Used to find column count of rtree table */
+ int bEnd = 0; /* True if transaction should be closed */
+ int nAux = 0; /* Number of extra columns. */
+
+ /* Initialize the context object */
+ memset(&check, 0, sizeof(check));
+ check.db = db;
+ check.zDb = zDb;
+ check.zTab = zTab;
+
+ /* If there is not already an open transaction, open one now. This is
+ ** to ensure that the queries run as part of this integrity-check operate
+ ** on a consistent snapshot. */
+ if( sqlite3_get_autocommit(db) ){
+ check.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0);
+ bEnd = 1;
+ }
+
+ /* Find the number of auxiliary columns */
+ if( check.rc==SQLITE_OK ){
+ pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab);
+ if( pStmt ){
+ nAux = sqlite3_column_count(pStmt) - 2;
+ sqlite3_finalize(pStmt);
+ }else
+ if( check.rc!=SQLITE_NOMEM ){
+ check.rc = SQLITE_OK;
+ }
+ }
+
+ /* Find number of dimensions in the rtree table. */
+ pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.%Q", zDb, zTab);
+ if( pStmt ){
+ int rc;
+ check.nDim = (sqlite3_column_count(pStmt) - 1 - nAux) / 2;
+ if( check.nDim<1 ){
+ rtreeCheckAppendMsg(&check, "Schema corrupt or not an rtree");
+ }else if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ check.bInt = (sqlite3_column_type(pStmt, 1)==SQLITE_INTEGER);
+ }
+ rc = sqlite3_finalize(pStmt);
+ if( rc!=SQLITE_CORRUPT ) check.rc = rc;
+ }
+
+ /* Do the actual integrity-check */
+ if( check.nDim>=1 ){
+ if( check.rc==SQLITE_OK ){
+ rtreeCheckNode(&check, 0, 0, 1);
+ }
+ rtreeCheckCount(&check, "_rowid", check.nLeaf);
+ rtreeCheckCount(&check, "_parent", check.nNonLeaf);
+ }
+
+ /* Finalize SQL statements used by the integrity-check */
+ sqlite3_finalize(check.pGetNode);
+ sqlite3_finalize(check.aCheckMapping[0]);
+ sqlite3_finalize(check.aCheckMapping[1]);
+
+ /* If one was opened, close the transaction */
+ if( bEnd ){
+ int rc = sqlite3_exec(db, "END", 0, 0, 0);
+ if( check.rc==SQLITE_OK ) check.rc = rc;
+ }
+ *pzReport = check.zReport;
+ return check.rc;
+}
+
+/*
+** Usage:
+**
+** rtreecheck(<rtree-table>);
+** rtreecheck(<database>, <rtree-table>);
+**
+** Invoking this SQL function runs an integrity-check on the named rtree
+** table. The integrity-check verifies the following:
+**
+** 1. For each cell in the r-tree structure (%_node table), that:
+**
+** a) for each dimension, (coord1 <= coord2).
+**
+** b) unless the cell is on the root node, that the cell is bounded
+** by the parent cell on the parent node.
+**
+** c) for leaf nodes, that there is an entry in the %_rowid
+** table corresponding to the cell's rowid value that
+** points to the correct node.
+**
+** d) for cells on non-leaf nodes, that there is an entry in the
+** %_parent table mapping from the cell's child node to the
+** node that it resides on.
+**
+** 2. That there are the same number of entries in the %_rowid table
+** as there are leaf cells in the r-tree structure, and that there
+** is a leaf cell that corresponds to each entry in the %_rowid table.
+**
+** 3. That there are the same number of entries in the %_parent table
+** as there are non-leaf cells in the r-tree structure, and that
+** there is a non-leaf cell that corresponds to each entry in the
+** %_parent table.
+*/
+static void rtreecheck(
+ sqlite3_context *ctx,
+ int nArg,
+ sqlite3_value **apArg
+){
+ if( nArg!=1 && nArg!=2 ){
+ sqlite3_result_error(ctx,
+ "wrong number of arguments to function rtreecheck()", -1
+ );
+ }else{
+ int rc;
+ char *zReport = 0;
+ const char *zDb = (const char*)sqlite3_value_text(apArg[0]);
+ const char *zTab;
+ if( nArg==1 ){
+ zTab = zDb;
+ zDb = "main";
+ }else{
+ zTab = (const char*)sqlite3_value_text(apArg[1]);
+ }
+ rc = rtreeCheckTable(sqlite3_context_db_handle(ctx), zDb, zTab, &zReport);
+ if( rc==SQLITE_OK ){
+ sqlite3_result_text(ctx, zReport ? zReport : "ok", -1, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_result_error_code(ctx, rc);
+ }
+ sqlite3_free(zReport);
+ }
+}
+
+/* Conditionally include the geopoly code */
+#ifdef SQLITE_ENABLE_GEOPOLY
+# include "geopoly.c"
+#endif
+
+/*
+** Register the r-tree module with database handle db. This creates the
+** virtual table module "rtree" and the debugging/analysis scalar
+** function "rtreenode".
+*/
+int sqlite3RtreeInit(sqlite3 *db){
+ const int utf8 = SQLITE_UTF8;
+ int rc;
+
+ rc = sqlite3_create_function(db, "rtreenode", 2, utf8, 0, rtreenode, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(db, "rtreedepth", 1, utf8, 0,rtreedepth, 0, 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(db, "rtreecheck", -1, utf8, 0,rtreecheck, 0,0);
+ }
+ if( rc==SQLITE_OK ){
+#ifdef SQLITE_RTREE_INT_ONLY
+ void *c = (void *)RTREE_COORD_INT32;
+#else
+ void *c = (void *)RTREE_COORD_REAL32;
+#endif
+ rc = sqlite3_create_module_v2(db, "rtree", &rtreeModule, c, 0);
+ }
+ if( rc==SQLITE_OK ){
+ void *c = (void *)RTREE_COORD_INT32;
+ rc = sqlite3_create_module_v2(db, "rtree_i32", &rtreeModule, c, 0);
+ }
+#ifdef SQLITE_ENABLE_GEOPOLY
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_geopoly_init(db);
+ }
+#endif
+
+ return rc;
+}
+
+/*
+** This routine deletes the RtreeGeomCallback object that was attached
+** one of the SQL functions create by sqlite3_rtree_geometry_callback()
+** or sqlite3_rtree_query_callback(). In other words, this routine is the
+** destructor for an RtreeGeomCallback objecct. This routine is called when
+** the corresponding SQL function is deleted.
+*/
+static void rtreeFreeCallback(void *p){
+ RtreeGeomCallback *pInfo = (RtreeGeomCallback*)p;
+ if( pInfo->xDestructor ) pInfo->xDestructor(pInfo->pContext);
+ sqlite3_free(p);
+}
+
+/*
+** This routine frees the BLOB that is returned by geomCallback().
+*/
+static void rtreeMatchArgFree(void *pArg){
+ int i;
+ RtreeMatchArg *p = (RtreeMatchArg*)pArg;
+ for(i=0; i<p->nParam; i++){
+ sqlite3_value_free(p->apSqlParam[i]);
+ }
+ sqlite3_free(p);
+}
+
+/*
+** Each call to sqlite3_rtree_geometry_callback() or
+** sqlite3_rtree_query_callback() creates an ordinary SQLite
+** scalar function that is implemented by this routine.
+**
+** All this function does is construct an RtreeMatchArg object that
+** contains the geometry-checking callback routines and a list of
+** parameters to this function, then return that RtreeMatchArg object
+** as a BLOB.
+**
+** The R-Tree MATCH operator will read the returned BLOB, deserialize
+** the RtreeMatchArg object, and use the RtreeMatchArg object to figure
+** out which elements of the R-Tree should be returned by the query.
+*/
+static void geomCallback(sqlite3_context *ctx, int nArg, sqlite3_value **aArg){
+ RtreeGeomCallback *pGeomCtx = (RtreeGeomCallback *)sqlite3_user_data(ctx);
+ RtreeMatchArg *pBlob;
+ sqlite3_int64 nBlob;
+ int memErr = 0;
+
+ nBlob = sizeof(RtreeMatchArg) + (nArg-1)*sizeof(RtreeDValue)
+ + nArg*sizeof(sqlite3_value*);
+ pBlob = (RtreeMatchArg *)sqlite3_malloc64(nBlob);
+ if( !pBlob ){
+ sqlite3_result_error_nomem(ctx);
+ }else{
+ int i;
+ pBlob->iSize = nBlob;
+ pBlob->cb = pGeomCtx[0];
+ pBlob->apSqlParam = (sqlite3_value**)&pBlob->aParam[nArg];
+ pBlob->nParam = nArg;
+ for(i=0; i<nArg; i++){
+ pBlob->apSqlParam[i] = sqlite3_value_dup(aArg[i]);
+ if( pBlob->apSqlParam[i]==0 ) memErr = 1;
+#ifdef SQLITE_RTREE_INT_ONLY
+ pBlob->aParam[i] = sqlite3_value_int64(aArg[i]);
+#else
+ pBlob->aParam[i] = sqlite3_value_double(aArg[i]);
+#endif
+ }
+ if( memErr ){
+ sqlite3_result_error_nomem(ctx);
+ rtreeMatchArgFree(pBlob);
+ }else{
+ sqlite3_result_pointer(ctx, pBlob, "RtreeMatchArg", rtreeMatchArgFree);
+ }
+ }
+}
+
+/*
+** Register a new geometry function for use with the r-tree MATCH operator.
+*/
+int sqlite3_rtree_geometry_callback(
+ sqlite3 *db, /* Register SQL function on this connection */
+ const char *zGeom, /* Name of the new SQL function */
+ int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*), /* Callback */
+ void *pContext /* Extra data associated with the callback */
+){
+ RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */
+
+ /* Allocate and populate the context object. */
+ pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback));
+ if( !pGeomCtx ) return SQLITE_NOMEM;
+ pGeomCtx->xGeom = xGeom;
+ pGeomCtx->xQueryFunc = 0;
+ pGeomCtx->xDestructor = 0;
+ pGeomCtx->pContext = pContext;
+ return sqlite3_create_function_v2(db, zGeom, -1, SQLITE_ANY,
+ (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback
+ );
+}
+
+/*
+** Register a new 2nd-generation geometry function for use with the
+** r-tree MATCH operator.
+*/
+int sqlite3_rtree_query_callback(
+ sqlite3 *db, /* Register SQL function on this connection */
+ const char *zQueryFunc, /* Name of new SQL function */
+ int (*xQueryFunc)(sqlite3_rtree_query_info*), /* Callback */
+ void *pContext, /* Extra data passed into the callback */
+ void (*xDestructor)(void*) /* Destructor for the extra data */
+){
+ RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */
+
+ /* Allocate and populate the context object. */
+ pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback));
+ if( !pGeomCtx ){
+ if( xDestructor ) xDestructor(pContext);
+ return SQLITE_NOMEM;
+ }
+ pGeomCtx->xGeom = 0;
+ pGeomCtx->xQueryFunc = xQueryFunc;
+ pGeomCtx->xDestructor = xDestructor;
+ pGeomCtx->pContext = pContext;
+ return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
+ (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback
+ );
+}
+
+#if !SQLITE_CORE
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_rtree_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ SQLITE_EXTENSION_INIT2(pApi)
+ return sqlite3RtreeInit(db);
+}
+#endif
+
+#endif
diff --git a/ext/rtree/rtree.h b/ext/rtree/rtree.h
new file mode 100644
index 0000000..8f41500
--- /dev/null
+++ b/ext/rtree/rtree.h
@@ -0,0 +1,30 @@
+/*
+** 2008 May 26
+**
+** 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 header file is used by programs that want to link against the
+** RTREE library. All it does is declare the sqlite3RtreeInit() interface.
+*/
+#include "sqlite3.h"
+
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+# undef SQLITE_ENABLE_RTREE
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+int sqlite3RtreeInit(sqlite3 *db);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
diff --git a/ext/rtree/rtree1.test b/ext/rtree/rtree1.test
new file mode 100644
index 0000000..0341553
--- /dev/null
+++ b/ext/rtree/rtree1.test
@@ -0,0 +1,759 @@
+# 2008 Feb 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the r-tree extension.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+set testprefix rtree1
+
+# Test plan:
+#
+# rtree-1.*: Creating/destroying r-tree tables.
+# rtree-2.*: Test the implicit constraints - unique rowid and
+# (coord[N]<=coord[N+1]) for even values of N. Also
+# automatic assigning of rowid values.
+# rtree-3.*: Linear scans of r-tree data.
+# rtree-4.*: Test INSERT
+# rtree-5.*: Test DELETE
+# rtree-6.*: Test UPDATE
+# rtree-7.*: Test renaming an r-tree table.
+# rtree-8.*: Test constrained scans of r-tree data.
+#
+# rtree-12.*: Test that on-conflict clauses are supported.
+# rtree-13.*: Test that bug [d2889096e7bdeac6d] has been fixed.
+# rtree-14.*: Test if a non-integer is inserted into the PK column of an
+# r-tree table, it is converted to an integer before being
+# inserted. Also that if a non-numeric is inserted into one
+# of the min/max dimension columns, it is converted to the
+# required type before being inserted.
+# rtree-15.*: Check that DROP TABLE works within a transaction that
+# writes to an r-tree table.
+#
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+#----------------------------------------------------------------------------
+# Test cases rtree-1.* test CREATE and DROP table statements.
+#
+
+# Test creating and dropping an rtree table.
+#
+do_test rtree-1.1.1 {
+ execsql { CREATE VIRTUAL TABLE t1 USING rtree(ii, x1, x2, y1, y2) }
+} {}
+do_test rtree-1.1.2a {
+ execsql { SELECT name FROM sqlite_master ORDER BY name }
+} {t1 t1_node t1_parent t1_rowid}
+do_execsql_test rtree-1.1.2b {
+ SELECT name FROM pragma_table_list WHERE type='shadow' ORDER BY name;
+} {t1_node t1_parent t1_rowid}
+do_test rtree-1.1.3 {
+ execsql {
+ DROP TABLE t1;
+ SELECT name FROM sqlite_master ORDER BY name;
+ }
+} {}
+
+# Test creating and dropping an rtree table with an odd name in
+# an attached database.
+#
+do_test rtree-1.2.1 {
+ file delete -force test2.db
+ execsql {
+ ATTACH 'test2.db' AS aux;
+ CREATE VIRTUAL TABLE aux.'a" "b' USING rtree(ii, x1, x2, y1, y2);
+ }
+} {}
+do_test rtree-1.2.2 {
+ execsql { SELECT name FROM sqlite_master ORDER BY name }
+} {}
+do_test rtree-1.2.3 {
+ execsql { SELECT name FROM aux.sqlite_master ORDER BY name }
+} {{a" "b} {a" "b_node} {a" "b_parent} {a" "b_rowid}}
+do_test rtree-1.2.4 {
+ execsql {
+ DROP TABLE aux.'a" "b';
+ SELECT name FROM aux.sqlite_master ORDER BY name;
+ }
+} {}
+
+# Test that the logic for checking the number of columns specified
+# for an rtree table. Acceptable values are odd numbers between 3 and
+# 11, inclusive.
+#
+set cols [list i1 i2 i3 i4 i5 i6 i7 i8 i9 iA iB iC iD iE iF iG iH iI iJ iK]
+for {set nCol 1} {$nCol<[llength $cols]} {incr nCol} {
+
+ set columns [join [lrange $cols 0 [expr {$nCol-1}]] ,]
+
+ set X {0 {}}
+ if {$nCol%2 == 0} { set X {1 {Wrong number of columns for an rtree table}} }
+ if {$nCol < 3} { set X {1 {Too few columns for an rtree table}} }
+ if {$nCol > 11} { set X {1 {Too many columns for an rtree table}} }
+
+ do_test rtree-1.3.$nCol {
+ catchsql "
+ CREATE VIRTUAL TABLE t1 USING rtree($columns);
+ "
+ } $X
+
+ catchsql { DROP TABLE t1 }
+}
+do_catchsql_test rtree-1.3.1000 {
+ CREATE VIRTUAL TABLE t1000 USING rtree;
+} {1 {Too few columns for an rtree table}}
+
+# Like execsql except display output as integer where that can be
+# done without loss of information.
+#
+proc execsql_intout {sql} {
+ set out {}
+ foreach term [execsql $sql] {
+ regsub {\.0$} $term {} term
+ lappend out $term
+ }
+ return $out
+}
+
+# Test that it is possible to open an existing database that contains
+# r-tree tables.
+#
+do_execsql_test rtree-1.4.1a {
+ CREATE VIRTUAL TABLE t1 USING rtree(ii, x1, x2);
+ INSERT INTO t1 VALUES(1, 5.0, 10.0);
+ SELECT substr(hex(data),1,40) FROM t1_node;
+} {00000001000000000000000140A0000041200000}
+do_execsql_test rtree-1.4.1b {
+ INSERT INTO t1 VALUES(2, 15.0, 20.0);
+} {}
+do_test rtree-1.4.2 {
+ db close
+ sqlite3 db test.db
+ execsql_intout { SELECT * FROM t1 ORDER BY ii }
+} {1 5 10 2 15 20}
+do_test rtree-1.4.3 {
+ execsql { DROP TABLE t1 }
+} {}
+
+# Test that it is possible to create an r-tree table with ridiculous
+# column names.
+#
+do_test rtree-1.5.1 {
+ execsql_intout {
+ CREATE VIRTUAL TABLE t1 USING rtree("the key", "x dim.", "x2'dim");
+ INSERT INTO t1 VALUES(1, 2, 3);
+ SELECT "the key", "x dim.", "x2'dim" FROM t1;
+ }
+} {1 2 3}
+do_test rtree-1.5.1 {
+ execsql { DROP TABLE t1 }
+} {}
+
+# Force the r-tree constructor to fail.
+#
+do_test rtree-1.6.1 {
+ execsql { CREATE TABLE t1_rowid(a); }
+ catchsql {
+ CREATE VIRTUAL TABLE t1 USING rtree("the key", "x dim.", "x2'dim");
+ }
+} {1 {table "t1_rowid" already exists}}
+do_test rtree-1.6.1 {
+ execsql { DROP TABLE t1_rowid }
+} {}
+
+#----------------------------------------------------------------------------
+# Test cases rtree-2.*
+#
+do_test rtree-2.1.1 {
+ execsql {
+ CREATE VIRTUAL TABLE t1 USING rtree(ii, x1, x2, y1, y2);
+ SELECT * FROM t1;
+ }
+} {}
+
+do_test rtree-2.1.2 {
+ execsql { INSERT INTO t1 VALUES(NULL, 1, 3, 2, 4) }
+ execsql_intout { SELECT * FROM t1 }
+} {1 1 3 2 4}
+do_test rtree-2.1.3 {
+ execsql { INSERT INTO t1 VALUES(NULL, 1, 3, 2, 4) }
+ execsql { SELECT rowid FROM t1 ORDER BY rowid }
+} {1 2}
+do_test rtree-2.1.3 {
+ execsql { INSERT INTO t1 VALUES(NULL, 1, 3, 2, 4) }
+ execsql { SELECT ii FROM t1 ORDER BY ii }
+} {1 2 3}
+
+do_test rtree-2.2.1 {
+ catchsql { INSERT INTO t1 VALUES(2, 1, 3, 2, 4) }
+} {1 {UNIQUE constraint failed: t1.ii}}
+do_test rtree-2.2.2 {
+ catchsql { INSERT INTO t1 VALUES(4, 1, 3, 4, 2) }
+} {1 {rtree constraint failed: t1.(y1<=y2)}}
+do_test rtree-2.2.3 {
+ catchsql { INSERT INTO t1 VALUES(4, 3, 1, 2, 4) }
+} {1 {rtree constraint failed: t1.(x1<=x2)}}
+do_test rtree-2.2.4 {
+ execsql { SELECT ii FROM t1 ORDER BY ii }
+} {1 2 3}
+
+do_test rtree-2.X {
+ execsql { DROP TABLE t1 }
+} {}
+
+#----------------------------------------------------------------------------
+# Test cases rtree-3.* test linear scans of r-tree table data. To test
+# this we have to insert some data into an r-tree, but that is not the
+# focus of these tests.
+#
+do_test rtree-3.1.1 {
+ execsql {
+ CREATE VIRTUAL TABLE t1 USING rtree(ii, x1, x2, y1, y2);
+ SELECT * FROM t1;
+ }
+} {}
+do_test rtree-3.1.2 {
+ execsql_intout {
+ INSERT INTO t1 VALUES(5, 1, 3, 2, 4);
+ SELECT * FROM t1;
+ }
+} {5 1 3 2 4}
+do_test rtree-3.1.3 {
+ execsql_intout {
+ INSERT INTO t1 VALUES(6, 2, 6, 4, 8);
+ SELECT * FROM t1;
+ }
+} {5 1 3 2 4 6 2 6 4 8}
+
+# Test the constraint on the coordinates (c[i]<=c[i+1] where (i%2==0)):
+do_test rtree-3.2.1 {
+ catchsql { INSERT INTO t1 VALUES(7, 2, 6, 4, 3) }
+} {1 {rtree constraint failed: t1.(y1<=y2)}}
+do_test rtree-3.2.2 {
+ catchsql { INSERT INTO t1 VALUES(8, 2, 6, 3, 3) }
+} {0 {}}
+
+#----------------------------------------------------------------------------
+# Test cases rtree-5.* test DELETE operations.
+#
+do_test rtree-5.1.1 {
+ execsql { CREATE VIRTUAL TABLE t2 USING rtree(ii, x1, x2) }
+} {}
+do_test rtree-5.1.2 {
+ execsql_intout {
+ INSERT INTO t2 VALUES(1, 10, 20);
+ INSERT INTO t2 VALUES(2, 30, 40);
+ INSERT INTO t2 VALUES(3, 50, 60);
+ SELECT * FROM t2 ORDER BY ii;
+ }
+} {1 10 20 2 30 40 3 50 60}
+do_test rtree-5.1.3 {
+ execsql_intout {
+ DELETE FROM t2 WHERE ii=2;
+ SELECT * FROM t2 ORDER BY ii;
+ }
+} {1 10 20 3 50 60}
+do_test rtree-5.1.4 {
+ execsql_intout {
+ DELETE FROM t2 WHERE ii=1;
+ SELECT * FROM t2 ORDER BY ii;
+ }
+} {3 50 60}
+do_test rtree-5.1.5 {
+ execsql {
+ DELETE FROM t2 WHERE ii=3;
+ SELECT * FROM t2 ORDER BY ii;
+ }
+} {}
+do_test rtree-5.1.6 {
+ execsql { SELECT * FROM t2_rowid }
+} {}
+
+#----------------------------------------------------------------------------
+# Test cases rtree-5.* test UPDATE operations.
+#
+do_test rtree-6.1.1 {
+ execsql { CREATE VIRTUAL TABLE t3 USING rtree(ii, x1, x2, y1, y2) }
+} {}
+do_test rtree-6.1.2 {
+ execsql_intout {
+ INSERT INTO t3 VALUES(1, 2, 3, 4, 5);
+ UPDATE t3 SET x2=5;
+ SELECT * FROM t3;
+ }
+} {1 2 5 4 5}
+do_test rtree-6.1.3 {
+ execsql { UPDATE t3 SET ii = 2 }
+ execsql_intout { SELECT * FROM t3 }
+} {2 2 5 4 5}
+
+#----------------------------------------------------------------------------
+# Test cases rtree-7.* test rename operations.
+#
+do_test rtree-7.1.1 {
+ execsql {
+ CREATE VIRTUAL TABLE t4 USING rtree(ii, x1, x2, y1, y2, z1, z2);
+ INSERT INTO t4 VALUES(1, 2, 3, 4, 5, 6, 7);
+ }
+} {}
+do_test rtree-7.1.2 {
+ execsql { ALTER TABLE t4 RENAME TO t5 }
+ execsql_intout { SELECT * FROM t5 }
+} {1 2 3 4 5 6 7}
+do_test rtree-7.1.3 {
+ db close
+ sqlite3 db test.db
+ execsql_intout { SELECT * FROM t5 }
+} {1 2 3 4 5 6 7}
+do_test rtree-7.1.4 {
+ execsql { ALTER TABLE t5 RENAME TO 'raisara "one"'''}
+ execsql_intout { SELECT * FROM "raisara ""one""'" }
+} {1 2 3 4 5 6 7}
+do_test rtree-7.1.5 {
+ execsql_intout { SELECT * FROM 'raisara "one"''' }
+} {1 2 3 4 5 6 7}
+do_test rtree-7.1.6 {
+ execsql { ALTER TABLE "raisara ""one""'" RENAME TO "abc 123" }
+ execsql_intout { SELECT * FROM "abc 123" }
+} {1 2 3 4 5 6 7}
+do_test rtree-7.1.7 {
+ db close
+ sqlite3 db test.db
+ execsql_intout { SELECT * FROM "abc 123" }
+} {1 2 3 4 5 6 7}
+
+# An error midway through a rename operation.
+do_test rtree-7.2.1 {
+ execsql {
+ CREATE TABLE t4_node(a);
+ }
+ catchsql { ALTER TABLE "abc 123" RENAME TO t4 }
+} {1 {SQL logic error}}
+do_test rtree-7.2.2 {
+ execsql_intout { SELECT * FROM "abc 123" }
+} {1 2 3 4 5 6 7}
+do_test rtree-7.2.3 {
+ execsql {
+ DROP TABLE t4_node;
+ CREATE TABLE t4_rowid(a);
+ }
+ catchsql { ALTER TABLE "abc 123" RENAME TO t4 }
+} {1 {SQL logic error}}
+do_test rtree-7.2.4 {
+ db close
+ sqlite3 db test.db
+ execsql_intout { SELECT * FROM "abc 123" }
+} {1 2 3 4 5 6 7}
+do_test rtree-7.2.5 {
+ execsql { DROP TABLE t4_rowid }
+ execsql { ALTER TABLE "abc 123" RENAME TO t4 }
+ execsql_intout { SELECT * FROM t4 }
+} {1 2 3 4 5 6 7}
+
+
+#----------------------------------------------------------------------------
+# Test cases rtree-8.*
+#
+
+# Test that the function to determine if a leaf cell is part of the
+# result set works.
+do_test rtree-8.1.1 {
+ execsql {
+ CREATE VIRTUAL TABLE t6 USING rtree(ii, x1, x2);
+ INSERT INTO t6 VALUES(1, 3, 7);
+ INSERT INTO t6 VALUES(2, 4, 6);
+ }
+} {}
+do_test rtree-8.1.2 { execsql { SELECT ii FROM t6 WHERE x1>2 } } {1 2}
+do_test rtree-8.1.3 { execsql { SELECT ii FROM t6 WHERE x1>3 } } {2}
+do_test rtree-8.1.4 { execsql { SELECT ii FROM t6 WHERE x1>4 } } {}
+do_test rtree-8.1.5 { execsql { SELECT ii FROM t6 WHERE x1>5 } } {}
+do_test rtree-8.1.6 { execsql { SELECT ii FROM t6 WHERE x1>''} } {}
+do_test rtree-8.1.7 { execsql { SELECT ii FROM t6 WHERE x1>null}} {}
+do_test rtree-8.1.8 { execsql { SELECT ii FROM t6 WHERE x1>'2'} } {1 2}
+do_test rtree-8.1.9 { execsql { SELECT ii FROM t6 WHERE x1>'3'} } {2}
+do_test rtree-8.2.2 { execsql { SELECT ii FROM t6 WHERE x1>=2 } } {1 2}
+do_test rtree-8.2.3 { execsql { SELECT ii FROM t6 WHERE x1>=3 } } {1 2}
+do_test rtree-8.2.4 { execsql { SELECT ii FROM t6 WHERE x1>=4 } } {2}
+do_test rtree-8.2.5 { execsql { SELECT ii FROM t6 WHERE x1>=5 } } {}
+do_test rtree-8.2.6 { execsql { SELECT ii FROM t6 WHERE x1>=''} } {}
+do_test rtree-8.2.7 { execsql { SELECT ii FROM t6 WHERE x1>=null}} {}
+do_test rtree-8.2.8 { execsql { SELECT ii FROM t6 WHERE x1>='4'} } {2}
+do_test rtree-8.2.9 { execsql { SELECT ii FROM t6 WHERE x1>='5'} } {}
+do_test rtree-8.3.2 { execsql { SELECT ii FROM t6 WHERE x1<2 } } {}
+do_test rtree-8.3.3 { execsql { SELECT ii FROM t6 WHERE x1<3 } } {}
+do_test rtree-8.3.4 { execsql { SELECT ii FROM t6 WHERE x1<4 } } {1}
+do_test rtree-8.3.5 { execsql { SELECT ii FROM t6 WHERE x1<5 } } {1 2}
+do_test rtree-8.3.6 { execsql { SELECT ii FROM t6 WHERE x1<''} } {1 2}
+do_test rtree-8.3.7 { execsql { SELECT ii FROM t6 WHERE x1<null}} {}
+do_test rtree-8.3.8 { execsql { SELECT ii FROM t6 WHERE x1<'3'} } {}
+do_test rtree-8.3.9 { execsql { SELECT ii FROM t6 WHERE x1<'4'} } {1}
+do_test rtree-8.4.2 { execsql { SELECT ii FROM t6 WHERE x1<=2 } } {}
+do_test rtree-8.4.3 { execsql { SELECT ii FROM t6 WHERE x1<=3 } } {1}
+do_test rtree-8.4.4 { execsql { SELECT ii FROM t6 WHERE x1<=4 } } {1 2}
+do_test rtree-8.4.5 { execsql { SELECT ii FROM t6 WHERE x1<=5 } } {1 2}
+do_test rtree-8.4.6 { execsql { SELECT ii FROM t6 WHERE x1<=''} } {1 2}
+do_test rtree-8.4.7 { execsql { SELECT ii FROM t6 WHERE x1<=null}} {}
+do_test rtree-8.5.2 { execsql { SELECT ii FROM t6 WHERE x1=2 } } {}
+do_test rtree-8.5.3 { execsql { SELECT ii FROM t6 WHERE x1=3 } } {1}
+do_test rtree-8.5.4 { execsql { SELECT ii FROM t6 WHERE x1=4 } } {2}
+do_test rtree-8.5.5 { execsql { SELECT ii FROM t6 WHERE x1=5 } } {}
+do_test rtree-8.5.6 { execsql { SELECT ii FROM t6 WHERE x1=''} } {}
+do_test rtree-8.5.7 { execsql { SELECT ii FROM t6 WHERE x1=null}} {}
+
+
+#----------------------------------------------------------------------------
+# Test cases rtree-9.*
+#
+# Test that ticket #3549 is fixed.
+do_test rtree-9.1 {
+ execsql {
+ CREATE TABLE foo (id INTEGER PRIMARY KEY);
+ CREATE VIRTUAL TABLE bar USING rtree (id, minX, maxX, minY, maxY);
+ INSERT INTO foo VALUES (null);
+ INSERT INTO foo SELECT null FROM foo;
+ INSERT INTO foo SELECT null FROM foo;
+ INSERT INTO foo SELECT null FROM foo;
+ INSERT INTO foo SELECT null FROM foo;
+ INSERT INTO foo SELECT null FROM foo;
+ INSERT INTO foo SELECT null FROM foo;
+ DELETE FROM foo WHERE id > 40;
+ INSERT INTO bar SELECT NULL, 0, 0, 0, 0 FROM foo;
+ }
+} {}
+
+# This used to crash.
+do_test rtree-9.2 {
+ execsql {
+ SELECT count(*) FROM bar b1, bar b2, foo s1 WHERE s1.id = b1.id;
+ }
+} {1600}
+do_test rtree-9.3 {
+ execsql {
+ SELECT count(*) FROM bar b1, bar b2, foo s1
+ WHERE b1.minX <= b2.maxX AND s1.id = b1.id;
+ }
+} {1600}
+
+#-------------------------------------------------------------------------
+# Ticket #3970: Check that the error message is meaningful when a
+# keyword is used as a column name.
+#
+do_test rtree-10.1 {
+ catchsql { CREATE VIRTUAL TABLE t7 USING rtree(index, x1, y1, x2, y2) }
+} {1 {near "index": syntax error}}
+
+#-------------------------------------------------------------------------
+# Test last_insert_rowid().
+#
+do_test rtree-11.1 {
+ execsql {
+ CREATE VIRTUAL TABLE t8 USING rtree(idx, x1, x2, y1, y2);
+ INSERT INTO t8 VALUES(1, 1.0, 1.0, 2.0, 2.0);
+ SELECT last_insert_rowid();
+ }
+} {1}
+do_test rtree-11.2 {
+ execsql {
+ INSERT INTO t8 VALUES(NULL, 1.0, 1.0, 2.0, 2.0);
+ SELECT last_insert_rowid();
+ }
+} {2}
+
+#-------------------------------------------------------------------------
+# Test on-conflict clause handling.
+#
+db_delete_and_reopen
+do_execsql_test 12.0.1 {
+ CREATE VIRTUAL TABLE t1 USING rtree_i32(idx, x1, x2, y1, y2);
+ INSERT INTO t1 VALUES(1, 1, 2, 3, 4);
+ SELECT substr(hex(data),1,56) FROM t1_node;
+} {00000001000000000000000100000001000000020000000300000004}
+do_execsql_test 12.0.2 {
+ INSERT INTO t1 VALUES(2, 2, 3, 4, 5);
+ INSERT INTO t1 VALUES(3, 3, 4, 5, 6);
+
+ CREATE TABLE source(idx, x1, x2, y1, y2);
+ INSERT INTO source VALUES(5, 8, 8, 8, 8);
+ INSERT INTO source VALUES(2, 7, 7, 7, 7);
+}
+db_save_and_close
+foreach {tn sql_template testdata} {
+ 1 "INSERT %CONF% INTO t1 VALUES(2, 7, 7, 7, 7)" {
+ ROLLBACK 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
+ ABORT 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ IGNORE 0 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ FAIL 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ REPLACE 0 0 {1 1 2 3 4 2 7 7 7 7 3 3 4 5 6 4 4 5 6 7}
+ }
+
+ 2 "INSERT %CONF% INTO t1 SELECT * FROM source" {
+ ROLLBACK 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
+ ABORT 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ IGNORE 1 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
+ FAIL 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
+ REPLACE 1 0 {1 1 2 3 4 2 7 7 7 7 3 3 4 5 6 4 4 5 6 7 5 8 8 8 8}
+ }
+
+ 3 "UPDATE %CONF% t1 SET idx = 2 WHERE idx = 4" {
+ ROLLBACK 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
+ ABORT 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ IGNORE 0 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ FAIL 0 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ REPLACE 0 0 {1 1 2 3 4 2 4 5 6 7 3 3 4 5 6}
+ }
+
+ 3 "UPDATE %CONF% t1 SET idx = ((idx+1)%5)+1 WHERE idx > 2" {
+ ROLLBACK 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
+ ABORT 1 1 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ IGNORE 1 0 {1 1 2 3 4 2 2 3 4 5 4 4 5 6 7 5 3 4 5 6}
+ FAIL 1 1 {1 1 2 3 4 2 2 3 4 5 4 4 5 6 7 5 3 4 5 6}
+ REPLACE 1 0 {1 4 5 6 7 2 2 3 4 5 5 3 4 5 6}
+ }
+
+ 4 "INSERT %CONF% INTO t1 VALUES(2, 7, 6, 7, 7)" {
+ ROLLBACK 0 2 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6}
+ ABORT 0 2 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ IGNORE 0 0 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ FAIL 0 2 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ REPLACE 0 2 {1 1 2 3 4 2 2 3 4 5 3 3 4 5 6 4 4 5 6 7}
+ }
+
+} {
+ foreach {mode uses error data} $testdata {
+ db_restore_and_reopen
+
+ set sql [string map [list %CONF% "OR $mode"] $sql_template]
+ set testname "12.$tn.[string tolower $mode]"
+
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(4, 4, 5, 6, 7);
+ }
+
+ set res(0) {0 {}}
+ set res(1) {1 {UNIQUE constraint failed: t1.idx}}
+ set res(2) {1 {rtree constraint failed: t1.(x1<=x2)}}
+
+ do_catchsql_test $testname.1 $sql $res($error)
+ do_test $testname.2 [list sql_uses_stmt db $sql] $uses
+ do_execsql_test $testname.3 { SELECT * FROM t1 ORDER BY idx } $data
+
+ do_rtree_integrity_test $testname.4 t1
+ db close
+ }
+}
+
+#-------------------------------------------------------------------------
+# Test that bug [d2889096e7bdeac6d] has been fixed.
+#
+reset_db
+do_execsql_test 13.1 {
+ CREATE VIRTUAL TABLE t9 USING rtree(id, xmin, xmax);
+ INSERT INTO t9 VALUES(1,0,0);
+ INSERT INTO t9 VALUES(2,0,0);
+ SELECT * FROM t9 WHERE id IN (1, 2);
+} {1 0.0 0.0 2 0.0 0.0}
+
+do_execsql_test 13.2 {
+ WITH r(x) AS (
+ SELECT 1 UNION ALL
+ SELECT 2 UNION ALL
+ SELECT 3
+ )
+ SELECT * FROM r CROSS JOIN t9 WHERE id=x;
+} {1 1 0.0 0.0 2 2 0.0 0.0}
+
+#-------------------------------------------------------------------------
+# Test if a non-integer is inserted into the PK column of an r-tree
+# table, it is converted to an integer before being inserted. Also
+# that if a non-numeric is inserted into one of the min/max dimension
+# columns, it is converted to the required type before being inserted.
+#
+do_execsql_test 14.1 {
+ CREATE VIRTUAL TABLE t10 USING rtree(ii, x1, x2);
+}
+
+do_execsql_test 14.2 {
+ INSERT INTO t10 VALUES(NULL, 1, 2);
+ INSERT INTO t10 VALUES(NULL, 2, 3);
+ INSERT INTO t10 VALUES('4xxx', 3, 4);
+ INSERT INTO t10 VALUES(5.0, 4, 5);
+ INSERT INTO t10 VALUES(6.4, 5, 6);
+}
+do_execsql_test 14.3 {
+ SELECT * FROM t10;
+} {
+ 1 1.0 2.0 2 2.0 3.0 4 3.0 4.0 5 4.0 5.0 6 5.0 6.0
+}
+
+do_execsql_test 14.4 {
+ DELETE FROM t10;
+ INSERT INTO t10 VALUES(1, 'one', 'two');
+ INSERT INTO t10 VALUES(2, '52xyz', '81...');
+}
+do_execsql_test 14.5 {
+ SELECT * FROM t10;
+} {
+ 1 0.0 0.0
+ 2 52.0 81.0
+}
+do_execsql_test 14.6 {
+ INSERT INTO t10 VALUES(0,10,20);
+ SELECT * FROM t10 WHERE ii=NULL;
+} {}
+do_execsql_test 14.7 {
+ SELECT * FROM t10 WHERE ii='xyz';
+} {}
+do_execsql_test 14.8 {
+ SELECT * FROM t10 WHERE ii='0.0';
+} {0 10.0 20.0}
+do_execsql_test 14.9 {
+ SELECT * FROM t10 WHERE ii=0.0;
+} {0 10.0 20.0}
+
+
+do_execsql_test 14.104 {
+ DROP TABLE t10;
+ CREATE VIRTUAL TABLE t10 USING rtree_i32(ii, x1, x2);
+ INSERT INTO t10 VALUES(1, 'one', 'two');
+ INSERT INTO t10 VALUES(2, '52xyz', '81...');
+ INSERT INTO t10 VALUES(3, 42.3, 49.9);
+}
+do_execsql_test 14.105 {
+ SELECT * FROM t10;
+} {
+ 1 0 0
+ 2 52 81
+ 3 42 49
+}
+
+#-------------------------------------------------------------------------
+#
+do_execsql_test 15.0 {
+ CREATE VIRTUAL TABLE rt USING rtree(id, x1,x2, y1,y2);
+ CREATE TEMP TABLE t13(a, b, c);
+}
+do_execsql_test 15.1 {
+ BEGIN;
+ INSERT INTO rt VALUES(1,2,3,4,5);
+}
+do_execsql_test 15.2 {
+ DROP TABLE t13;
+ COMMIT;
+}
+
+# Test cases for the new auxiliary columns feature
+#
+do_catchsql_test 16.100 {
+ CREATE VIRTUAL TABLE t16 USING rtree(id,x0,x1,y0,+aux1,x1);
+} {1 {Auxiliary rtree columns must be last}}
+do_test 16.110 {
+ set sql {
+ CREATE VIRTUAL TABLE t16 USING rtree(
+ id, x00, x01, x10, x11, x20, x21, x30, x31, x40, x41
+ }
+ for {set i 12} {$i<=100} {incr i} {
+ append sql ", +a$i"
+ }
+ append sql ");"
+ execsql $sql
+} {}
+do_test 16.120 {
+ set sql {
+ CREATE VIRTUAL TABLE t16b USING rtree(
+ id, x00, x01, x10, x11, x20, x21, x30, x31, x40, x41
+ }
+ for {set i 12} {$i<=101} {incr i} {
+ append sql ", +a$i"
+ }
+ append sql ");"
+ catchsql $sql
+} {1 {Too many columns for an rtree table}}
+
+do_execsql_test 16.130 {
+ DROP TABLE IF EXISTS rt1;
+ CREATE VIRTUAL TABLE rt1 USING rtree(id, x1, x2, +aux);
+ INSERT INTO rt1 VALUES(1, 1, 2, 'aux1');
+ INSERT INTO rt1 VALUES(2, 2, 3, 'aux2');
+ INSERT INTO rt1 VALUES(3, 3, 4, 'aux3');
+ INSERT INTO rt1 VALUES(4, 4, 5, 'aux4');
+ SELECT * FROM rt1 WHERE id IN (1, 2, 3, 4);
+} {1 1.0 2.0 aux1 2 2.0 3.0 aux2 3 3.0 4.0 aux3 4 4.0 5.0 aux4}
+
+reset_db
+do_execsql_test 17.0 {
+ CREATE VIRTUAL TABLE t1 USING rtree(id, x1 PRIMARY KEY, x2, y1, y2);
+ CREATE VIRTUAL TABLE t2 USING rtree(id, x1, x2, y1, y2 UNIQUE);
+}
+do_execsql_test 17.1 {
+ REINDEX t1;
+ REINDEX t2;
+} {}
+
+do_execsql_test 17.2 {
+ REINDEX;
+} {}
+
+reset_db
+do_execsql_test 18.0 {
+ CREATE VIRTUAL TABLE rt0 USING rtree(c0, c1, c2);
+ INSERT INTO rt0(c0,c1,c2) VALUES(9,2,3);
+ SELECT c0 FROM rt0 WHERE rt0.c1 > '-1';
+ SELECT rt0.c1 > '-1' FROM rt0;
+} {9 1}
+
+expand_all_sql db
+
+# 2020-02-28 ticket e63b4d1a65546532
+reset_db
+do_execsql_test 19.0 {
+ CREATE VIRTUAL TABLE rt0 USING rtree(a,b,c);
+ INSERT INTO rt0(a,b,c) VALUES(0,0.0,0.0);
+ CREATE VIEW v0(x) AS SELECT DISTINCT rt0.b FROM rt0;
+ SELECT v0.x FROM v0, rt0;
+} {0.0}
+do_execsql_test 19.1 {
+ SELECT v0.x FROM v0, rt0 WHERE v0.x = rt0.b;
+} {0.0}
+
+# 2022-06-20 https://sqlite.org/forum/forumpost/57bdf2217d
+#
+reset_db
+do_execsql_test 20.0 {
+ CREATE VIRTUAL TABLE rt0 USING rtree(id, x0, x1);
+ CREATE TABLE t0(a INT);
+ CREATE TABLE t1(b INT);
+ INSERT INTO rt0 VALUES(0, 0, 0);
+}
+do_catchsql_test 20.1 {
+ SELECT * FROM t1 JOIN t0 ON x0>a RIGHT JOIN rt0 ON true WHERE +x0 = 0;
+} {1 {ON clause references tables to its right}}
+do_catchsql_test 20.2 {
+ SELECT * FROM t1 JOIN t0 ON x0>a RIGHT JOIN rt0 ON true WHERE x0 = 0;
+} {1 {ON clause references tables to its right}}
+db null -
+do_execsql_test 20.3 {
+ SELECT * FROM t1 JOIN t0 ON true RIGHT JOIN rt0 ON x0>a WHERE +x0 = 0;
+} {- - 0 0.0 0.0}
+do_execsql_test 20.4 {
+ SELECT * FROM t1 JOIN t0 ON true RIGHT JOIN rt0 ON x0>a WHERE x0 = 0;
+} {- - 0 0.0 0.0}
+
+finish_test
diff --git a/ext/rtree/rtree2.test b/ext/rtree/rtree2.test
new file mode 100644
index 0000000..31c1914
--- /dev/null
+++ b/ext/rtree/rtree2.test
@@ -0,0 +1,147 @@
+# 2008 Feb 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the r-tree extension.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+set ::NROW 1000
+set ::NDEL 10
+set ::NSELECT 100
+
+if {[info exists G(isquick)] && $G(isquick)} {
+ set ::NROW 100
+ set ::NSELECT 10
+}
+
+foreach module {rtree_i32 rtree} {
+ if {$module=="rtree_i32"} {set etype INT} {set etype REAL}
+ for {set nDim 1} {$nDim <= 5} {incr nDim} {
+
+ do_test rtree2-$module.$nDim.1 {
+ set cols [list]
+ foreach c [list c0 c1 c2 c3 c4 c5 c6 c7 c8 c9] {
+ lappend cols "$c $etype"
+ }
+ set cols [join [lrange $cols 0 [expr {$nDim*2-1}]] ", "]
+ execsql "
+ CREATE VIRTUAL TABLE t1 USING ${module}(ii, $cols);
+ CREATE TABLE t2 (ii, $cols);
+ "
+ } {}
+
+ do_test rtree2-$module.$nDim.2 {
+ db transaction {
+ for {set ii 0} {$ii < $::NROW} {incr ii} {
+ #puts "Row $ii"
+ set values [list]
+ for {set jj 0} {$jj<$nDim*2} {incr jj} {
+ lappend values [expr int(rand()*1000)]
+ }
+ set values [join $values ,]
+ #puts [rtree_treedump db t1]
+ #puts "INSERT INTO t2 VALUES($ii, $values)"
+ set rc [catch {db eval "INSERT INTO t1 VALUES($ii, $values)"}]
+ if {$rc} {
+ incr ii -1
+ } else {
+ db eval "INSERT INTO t2 VALUES($ii, $values)"
+ }
+ #if {[rtree_check db t1]} {
+ #puts [rtree_treedump db t1]
+ #exit
+ #}
+ }
+ }
+
+ set t1 [execsql {SELECT * FROM t1 ORDER BY ii}]
+ set t2 [execsql {SELECT * FROM t2 ORDER BY ii}]
+ set rc [expr {$t1 eq $t2}]
+ if {$rc != 1} {
+ puts $t1
+ puts $t2
+ }
+ set rc
+ } {1}
+
+ do_rtree_integrity_test rtree2-$module.$nDim.3 t1
+
+ set OPS [list < > <= >= =]
+ for {set ii 0} {$ii < $::NSELECT} {incr ii} {
+ do_test rtree2-$module.$nDim.4.$ii.1 {
+ set where [list]
+ foreach look_three_dots! {. . .} {
+ set colidx [expr int(rand()*($nDim*2+1))-1]
+ if {$colidx<0} {
+ set col ii
+ } else {
+ set col "c$colidx"
+ }
+ set op [lindex $OPS [expr int(rand()*[llength $OPS])]]
+ set val [expr int(rand()*1000)]
+ lappend where "$col $op $val"
+ }
+ set where [join $where " AND "]
+
+ set t1 [execsql "SELECT * FROM t1 WHERE $where ORDER BY ii"]
+ set t2 [execsql "SELECT * FROM t2 WHERE $where ORDER BY ii"]
+ set rc [expr {$t1 eq $t2}]
+ if {$rc != 1} {
+ #puts $where
+ puts $t1
+ puts $t2
+ #puts [rtree_treedump db t1]
+ #breakpoint
+ #set t1 [execsql "SELECT * FROM t1 WHERE $where ORDER BY ii"]
+ #exit
+ }
+ set rc
+ } {1}
+ }
+
+ for {set ii 0} {$ii < $::NROW} {incr ii $::NDEL} {
+ #puts [rtree_treedump db t1]
+ do_test rtree2-$module.$nDim.5.$ii.1 {
+ execsql "DELETE FROM t2 WHERE ii <= $::ii"
+ execsql "DELETE FROM t1 WHERE ii <= $::ii"
+
+ set t1 [execsql {SELECT * FROM t1 ORDER BY ii}]
+ set t2 [execsql {SELECT * FROM t2 ORDER BY ii}]
+ set rc [expr {$t1 eq $t2}]
+ if {$rc != 1} {
+ puts $t1
+ puts $t2
+ }
+ set rc
+ } {1}
+ do_rtree_integrity_test rtree2-$module.$nDim.5.$ii.2 t1
+ }
+
+ do_test rtree2-$module.$nDim.6 {
+ execsql {
+ DROP TABLE t1;
+ DROP TABLE t2;
+ }
+ } {}
+ }
+}
+
+finish_test
diff --git a/ext/rtree/rtree3.test b/ext/rtree/rtree3.test
new file mode 100644
index 0000000..c980863
--- /dev/null
+++ b/ext/rtree/rtree3.test
@@ -0,0 +1,270 @@
+# 2008 Feb 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing that the r-tree correctly handles
+# out-of-memory conditions.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+source $testdir/malloc_common.tcl
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+set ::TMPDBERROR [list 1 \
+ {unable to open a temporary database file for storing temporary tables}
+]
+
+# Test summary:
+#
+# rtree3-1: Test OOM in simple CREATE TABLE, INSERT, DELETE and SELECT
+# commands on an almost empty table.
+#
+# rtree3-2: Test OOM in a DROP TABLE command.
+#
+# rtree3-3a: Test OOM during a transaction to insert 100 pseudo-random rows.
+#
+# rtree3-3b: Test OOM during a transaction deleting all entries in the
+# database constructed in [rtree3-3a] in pseudo-random order.
+#
+# rtree3-4a: OOM during "SELECT count(*) FROM ..." on a big table.
+#
+# rtree3-4b: OOM while deleting rows from a big table.
+#
+# rtree3-5: Test OOM while inserting rows into a big table.
+#
+# rtree3-6: Test OOM while deleting all rows of a table, one at a time.
+#
+# rtree3-7: OOM during an ALTER TABLE RENAME TABLE command.
+#
+# rtree3-8: Test OOM while registering the r-tree module with sqlite.
+#
+# rtree3-11: OOM following a constraint failure
+#
+do_faultsim_test rtree3-1 -faults oom* -prep {
+ faultsim_delete_and_reopen
+} -body {
+ execsql {
+ BEGIN TRANSACTION;
+ CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
+ INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
+ INSERT INTO rt VALUES(NULL, 13, 15, 17, 19);
+ DELETE FROM rt WHERE ii = 1;
+ SELECT * FROM rt;
+ SELECT ii FROM rt WHERE ii = 2;
+ COMMIT;
+ }
+}
+
+do_test rtree3-2.prep {
+ faultsim_delete_and_reopen
+ execsql {
+ CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
+ INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
+ }
+ faultsim_save_and_close
+} {}
+do_faultsim_test rtree3-2 -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { DROP TABLE rt }
+}
+
+do_malloc_test rtree3-3.prep {
+ faultsim_delete_and_reopen
+ execsql {
+ CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2, +a1, +a2);
+ INSERT INTO rt VALUES(NULL, 3, 5, 7, 9);
+ }
+ faultsim_save_and_close
+} {}
+
+do_faultsim_test rtree3-3a -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ db eval BEGIN
+ for {set ii 0} {$ii < 100} {incr ii} {
+ set f [expr rand()]
+ db eval {INSERT INTO rt VALUES(NULL, $f*10.0, $f*10.0, $f*15.0, $f*15.0)}
+ }
+ db eval COMMIT
+}
+faultsim_save_and_close
+
+do_faultsim_test rtree3-3b -faults oom* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ db eval BEGIN
+ for {set ii 0} {$ii < 100} {incr ii} {
+ set f [expr rand()]
+ db eval { DELETE FROM rt WHERE x1<($f*10.0) AND x1>($f*10.5) }
+ }
+ db eval COMMIT
+}
+
+do_test rtree3-4.prep {
+ faultsim_delete_and_reopen
+ execsql {
+ BEGIN;
+ PRAGMA page_size = 512;
+ CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
+ }
+ for {set i 0} {$i < 1500} {incr i} {
+ execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) }
+ }
+ execsql { COMMIT }
+ faultsim_save_and_close
+} {}
+
+do_faultsim_test rtree3-4a -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ db eval { SELECT count(*) FROM rt }
+} -test {
+ faultsim_test_result {0 1500}
+}
+
+do_faultsim_test rtree3-4b -faults oom-transient -prep {
+ faultsim_restore_and_reopen
+} -body {
+ db eval { DELETE FROM rt WHERE ii BETWEEN 1 AND 100 }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+do_test rtree3-5.prep {
+ faultsim_delete_and_reopen
+ execsql {
+ BEGIN;
+ PRAGMA page_size = 512;
+ CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
+ }
+ for {set i 0} {$i < 100} {incr i} {
+ execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) }
+ }
+ execsql { COMMIT }
+ faultsim_save_and_close
+} {}
+do_faultsim_test rtree3-5 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ for {set i 100} {$i < 110} {incr i} {
+ execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) }
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+do_test rtree3-6.prep {
+ faultsim_delete_and_reopen
+ execsql {
+ BEGIN;
+ PRAGMA page_size = 512;
+ CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
+ }
+ for {set i 0} {$i < 50} {incr i} {
+ execsql { INSERT INTO rt VALUES($i, $i, $i+1, $i, $i+1) }
+ }
+ execsql { COMMIT }
+ faultsim_save_and_close
+} {}
+do_faultsim_test rtree3-6 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql BEGIN
+ for {set i 0} {$i < 50} {incr i} {
+ execsql { DELETE FROM rt WHERE ii=$i }
+ }
+ execsql COMMIT
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+do_test rtree3-7.prep {
+ faultsim_delete_and_reopen
+ execsql { CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2) }
+ faultsim_save_and_close
+} {}
+do_faultsim_test rtree3-7 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { ALTER TABLE rt RENAME TO rt2 }
+} -test {
+ faultsim_test_result {0 {}} $::TMPDBERROR
+}
+
+do_faultsim_test rtree3-8 -faults oom-* -prep {
+ catch { db close }
+} -body {
+ sqlite3 db test.db
+}
+
+do_faultsim_test rtree3-9 -faults oom-* -prep {
+ sqlite3 db :memory:
+} -body {
+ set rc [register_cube_geom db]
+ if {$rc != "SQLITE_OK"} { error $rc }
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+}
+
+do_test rtree3-10.prep {
+ faultsim_delete_and_reopen
+ execsql {
+ CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2, z1, z2);
+ INSERT INTO rt VALUES(1, 10, 10, 10, 11, 11, 11);
+ INSERT INTO rt VALUES(2, 5, 6, 6, 7, 7, 8);
+ }
+ faultsim_save_and_close
+} {}
+do_faultsim_test rtree3-10 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+ register_cube_geom db
+ execsql { SELECT * FROM rt }
+} -body {
+ execsql { SELECT ii FROM rt WHERE ii MATCH cube(4.5, 5.5, 6.5, 1, 1, 1) }
+} -test {
+ faultsim_test_result {0 2}
+}
+
+
+do_test rtree3-11.prep {
+ faultsim_delete_and_reopen
+ execsql {
+ CREATE VIRTUAL TABLE rt USING rtree(ii, x1, x2, y1, y2);
+ INSERT INTO rt VALUES(1, 2, 3, 4, 5);
+ }
+ faultsim_save_and_close
+} {}
+do_faultsim_test rtree3-10.1 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+ execsql { SELECT * FROM rt }
+} -body {
+ execsql { INSERT INTO rt VALUES(1, 2, 3, 4, 5) }
+} -test {
+ faultsim_test_result {1 {UNIQUE constraint failed: rt.ii}} \
+ {1 {constraint failed}}
+}
+do_faultsim_test rtree3-10.2 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+ execsql { SELECT * FROM rt }
+} -body {
+ execsql { INSERT INTO rt VALUES(2, 2, 3, 5, 4) }
+} -test {
+ faultsim_test_result {1 {rtree constraint failed: rt.(y1<=y2)}} \
+ {1 {constraint failed}}
+}
+
+finish_test
diff --git a/ext/rtree/rtree4.test b/ext/rtree/rtree4.test
new file mode 100644
index 0000000..a73921d
--- /dev/null
+++ b/ext/rtree/rtree4.test
@@ -0,0 +1,254 @@
+# 2008 May 23
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Randomized test cases for the rtree extension.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+set ::NROW 2500
+if {[info exists G(isquick)] && $G(isquick)} {
+ set ::NROW 250
+}
+
+ifcapable !rtree_int_only {
+ # Return a floating point number between -X and X.
+ #
+ proc rand {X} {
+ return [expr {int((rand()-0.5)*1024.0*$X)/512.0}]
+ }
+
+ # Return a positive floating point number less than or equal to X
+ #
+ proc randincr {X} {
+ while 1 {
+ set r [expr {int(rand()*$X*32.0)/32.0}]
+ if {$r>0.0} {return $r}
+ }
+ }
+} else {
+ # For rtree_int_only, return an number between -X and X.
+ #
+ proc rand {X} {
+ return [expr {int((rand()-0.5)*2*$X)}]
+ }
+
+ # Return a positive integer less than or equal to X
+ #
+ proc randincr {X} {
+ while 1 {
+ set r [expr {int(rand()*$X)+1}]
+ if {$r>0} {return $r}
+ }
+ }
+}
+
+# Scramble the $inlist into a random order.
+#
+proc scramble {inlist} {
+ set y {}
+ foreach x $inlist {
+ lappend y [list [expr {rand()}] $x]
+ }
+ set y [lsort $y]
+ set outlist {}
+ foreach x $y {
+ lappend outlist [lindex $x 1]
+ }
+ return $outlist
+}
+
+# Always use the same random seed so that the sequence of tests
+# is repeatable.
+#
+expr {srand(1234)}
+
+# Run these tests for all number of dimensions between 1 and 5.
+#
+for {set nDim 1} {$nDim<=5} {incr nDim} {
+
+ # Construct an rtree virtual table and an ordinary btree table
+ # to mirror it. The ordinary table should be much slower (since
+ # it has to do a full table scan) but should give the exact same
+ # answers.
+ #
+ do_test rtree4-$nDim.1 {
+ set clist {}
+ set cklist {}
+ for {set i 0} {$i<$nDim} {incr i} {
+ lappend clist mn$i mx$i
+ lappend cklist "mn$i<mx$i"
+ }
+ db eval "DROP TABLE IF EXISTS rx"
+ db eval "DROP TABLE IF EXISTS bx"
+ db eval "CREATE VIRTUAL TABLE rx USING rtree(id, [join $clist ,])"
+ db eval "CREATE TABLE bx(id INTEGER PRIMARY KEY,\
+ [join $clist ,], CHECK( [join $cklist { AND }] ))"
+ } {}
+
+ # Do many insertions of small objects. Do both overlapping and
+ # contained-within queries after each insert to verify that all
+ # is well.
+ #
+ unset -nocomplain where
+ for {set i 1} {$i<$::NROW} {incr i} {
+ # Do a random insert
+ #
+ do_test rtree4-$nDim.2.$i.1 {
+ set vlist {}
+ for {set j 0} {$j<$nDim} {incr j} {
+ set mn [rand 10000]
+ set mx [expr {$mn+[randincr 50]}]
+ lappend vlist $mn $mx
+ }
+ db eval "INSERT INTO rx VALUES(NULL, [join $vlist ,])"
+ db eval "INSERT INTO bx VALUES(NULL, [join $vlist ,])"
+ } {}
+
+ # Do a contained-in query on all dimensions
+ #
+ set where {}
+ for {set j 0} {$j<$nDim} {incr j} {
+ set mn [rand 10000]
+ set mx [expr {$mn+[randincr 500]}]
+ lappend where mn$j>=$mn mx$j<=$mx
+ }
+ set where "WHERE [join $where { AND }]"
+ do_test rtree4-$nDim.2.$i.2 {
+ list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
+ } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
+
+ # Do an overlaps query on all dimensions
+ #
+ set where {}
+ for {set j 0} {$j<$nDim} {incr j} {
+ set mn [rand 10000]
+ set mx [expr {$mn+[randincr 500]}]
+ lappend where mx$j>=$mn mn$j<=$mx
+ }
+ set where "WHERE [join $where { AND }]"
+ do_test rtree4-$nDim.2.$i.3 {
+ list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
+ } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
+
+ # Do a contained-in query with surplus contraints at the beginning.
+ # This should force a full-table scan on the rtree.
+ #
+ set where {}
+ for {set j 0} {$j<$nDim} {incr j} {
+ lappend where mn$j>-10000 mx$j<10000
+ }
+ for {set j 0} {$j<$nDim} {incr j} {
+ set mn [rand 10000]
+ set mx [expr {$mn+[randincr 500]}]
+ lappend where mn$j>=$mn mx$j<=$mx
+ }
+ set where "WHERE [join $where { AND }]"
+ do_test rtree4-$nDim.2.$i.3 {
+ list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
+ } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
+
+ # Do an overlaps query with surplus contraints at the beginning.
+ # This should force a full-table scan on the rtree.
+ #
+ set where {}
+ for {set j 0} {$j<$nDim} {incr j} {
+ lappend where mn$j>=-10000 mx$j<=10000
+ }
+ for {set j 0} {$j<$nDim} {incr j} {
+ set mn [rand 10000]
+ set mx [expr {$mn+[randincr 500]}]
+ lappend where mx$j>$mn mn$j<$mx
+ }
+ set where "WHERE [join $where { AND }]"
+ do_test rtree4-$nDim.2.$i.4 {
+ list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
+ } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
+
+ # Do a contained-in query with surplus contraints at the end
+ #
+ set where {}
+ for {set j 0} {$j<$nDim} {incr j} {
+ set mn [rand 10000]
+ set mx [expr {$mn+[randincr 500]}]
+ lappend where mn$j>=$mn mx$j<$mx
+ }
+ for {set j [expr {$nDim-1}]} {$j>=0} {incr j -1} {
+ lappend where mn$j>=-10000 mx$j<10000
+ }
+ set where "WHERE [join $where { AND }]"
+ do_test rtree4-$nDim.2.$i.5 {
+ list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
+ } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
+
+ # Do an overlaps query with surplus contraints at the end
+ #
+ set where {}
+ for {set j [expr {$nDim-1}]} {$j>=0} {incr j -1} {
+ set mn [rand 10000]
+ set mx [expr {$mn+[randincr 500]}]
+ lappend where mx$j>$mn mn$j<=$mx
+ }
+ for {set j 0} {$j<$nDim} {incr j} {
+ lappend where mx$j>-10000 mn$j<=10000
+ }
+ set where "WHERE [join $where { AND }]"
+ do_test rtree4-$nDim.2.$i.6 {
+ list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
+ } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
+
+ # Do a contained-in query with surplus contraints where the
+ # constraints appear in a random order.
+ #
+ set where {}
+ for {set j 0} {$j<$nDim} {incr j} {
+ set mn1 [rand 10000]
+ set mn2 [expr {$mn1+[randincr 100]}]
+ set mx1 [expr {$mn2+[randincr 400]}]
+ set mx2 [expr {$mx1+[randincr 100]}]
+ lappend where mn$j>=$mn1 mn$j>$mn2 mx$j<$mx1 mx$j<=$mx2
+ }
+ set where "WHERE [join [scramble $where] { AND }]"
+ do_test rtree4-$nDim.2.$i.7 {
+ list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
+ } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
+
+ # Do an overlaps query with surplus contraints where the
+ # constraints appear in a random order.
+ #
+ set where {}
+ for {set j 0} {$j<$nDim} {incr j} {
+ set mn1 [rand 10000]
+ set mn2 [expr {$mn1+[randincr 100]}]
+ set mx1 [expr {$mn2+[randincr 400]}]
+ set mx2 [expr {$mx1+[randincr 100]}]
+ lappend where mx$j>=$mn1 mx$j>$mn2 mn$j<$mx1 mn$j<=$mx2
+ }
+ set where "WHERE [join [scramble $where] { AND }]"
+ do_test rtree4-$nDim.2.$i.8 {
+ list $where [db eval "SELECT id FROM rx $where ORDER BY id"]
+ } [list $where [db eval "SELECT id FROM bx $where ORDER BY id"]]
+ }
+
+ do_rtree_integrity_test rtree4-$nDim.3 rx
+}
+
+expand_all_sql db
+finish_test
diff --git a/ext/rtree/rtree5.test b/ext/rtree/rtree5.test
new file mode 100644
index 0000000..92bb690
--- /dev/null
+++ b/ext/rtree/rtree5.test
@@ -0,0 +1,83 @@
+# 2008 Jul 14
+#
+# 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 focus of this file is testing the r-tree extension when it is
+# configured to store values as 32 bit integers.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+do_test rtree5-1.0 {
+ execsql { CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2, y1, y2) }
+} {}
+do_test rtree5-1.1 {
+ execsql { INSERT INTO t1 VALUES(1, 5, 10, 4, 11.2) }
+} {}
+do_test rtree5-1.2 {
+ execsql { SELECT * FROM t1 }
+} {1 5 10 4 11}
+do_test rtree5-1.3 {
+ execsql { SELECT typeof(x1) FROM t1 }
+} {integer}
+
+do_test rtree5-1.4 {
+ execsql { SELECT x1==5 FROM t1 }
+} {1}
+do_test rtree5-1.5 {
+ execsql { SELECT x1==5.2 FROM t1 }
+} {0}
+do_test rtree5-1.6 {
+ execsql { SELECT x1==5.0 FROM t1 }
+} {1}
+
+do_test rtree5-1.7 {
+ execsql { SELECT count(*) FROM t1 WHERE x1==5 }
+} {1}
+ifcapable !rtree_int_only {
+ do_test rtree5-1.8 {
+ execsql { SELECT count(*) FROM t1 WHERE x1==5.2 }
+ } {0}
+}
+do_test rtree5-1.9 {
+ execsql { SELECT count(*) FROM t1 WHERE x1==5.0 }
+} {1}
+
+do_test rtree5-1.10 {
+ execsql { SELECT (1<<31)-5, (1<<31)-1, -1*(1<<31), -1*(1<<31)+5 }
+} {2147483643 2147483647 -2147483648 -2147483643}
+do_test rtree5-1.11 {
+ execsql {
+ INSERT INTO t1 VALUES(2, (1<<31)-5, (1<<31)-1, -1*(1<<31), -1*(1<<31)+5)
+ }
+} {}
+do_test rtree5-1.12 {
+ execsql { SELECT * FROM t1 WHERE id=2 }
+} {2 2147483643 2147483647 -2147483648 -2147483643}
+do_test rtree5-1.13 {
+ execsql {
+ SELECT * FROM t1 WHERE
+ x1=2147483643 AND x2=2147483647 AND
+ y1=-2147483648 AND y2=-2147483643
+ }
+} {2 2147483643 2147483647 -2147483648 -2147483643}
+do_rtree_integrity_test rtree5-1.14 t1
+
+expand_all_sql db
+finish_test
diff --git a/ext/rtree/rtree6.test b/ext/rtree/rtree6.test
new file mode 100644
index 0000000..b6dfe99
--- /dev/null
+++ b/ext/rtree/rtree6.test
@@ -0,0 +1,181 @@
+# 2008 Sep 1
+#
+# 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
+set testprefix rtree6
+
+ifcapable {!rtree || rtree_int_only} {
+ finish_test
+ return
+}
+
+# Operator Byte Value
+# ----------------------
+# = 0x41 ('A')
+# <= 0x42 ('B')
+# < 0x43 ('C')
+# >= 0x44 ('D')
+# > 0x45 ('E')
+# ----------------------
+
+proc rtree_strategy {sql} {
+ set ret [list]
+ db eval "explain $sql" a {
+ if {$a(opcode) eq "VFilter"} {
+ lappend ret $a(p4)
+ }
+ }
+ set ret
+}
+
+proc query_plan {sql} {
+ set ret [list]
+ db eval "explain query plan $sql" a {
+ lappend ret $a(detail)
+ }
+ set ret
+}
+
+do_test rtree6-1.1 {
+ execsql {
+ CREATE TABLE t2(k INTEGER PRIMARY KEY, v);
+ CREATE VIRTUAL TABLE t1 USING rtree(ii, x1, x2, y1, y2);
+ }
+} {}
+
+do_test rtree6-1.2 {
+ rtree_strategy {SELECT * FROM t1 WHERE x1>10}
+} {E0}
+do_test rtree6-1.2.1 {
+ rtree_strategy {SELECT * FROM t1 WHERE x1>10 AND x2 LIKE '%x%'}
+} {E0}
+
+do_test rtree6-1.3 {
+ rtree_strategy {SELECT * FROM t1 WHERE x1<10}
+} {C0}
+
+do_test rtree6-1.4 {
+ rtree_strategy {SELECT * FROM t1,t2 WHERE k=ii AND x1<10}
+} {C0}
+
+do_test rtree6-1.5 {
+ rtree_strategy {SELECT * FROM t1,t2 WHERE k=+ii AND x1<10}
+} {C0}
+
+do_eqp_test rtree6.2.1 {
+ SELECT * FROM t1,t2 WHERE k=+ii AND x1<10
+} {
+ QUERY PLAN
+ |--SCAN t1 VIRTUAL TABLE INDEX 2:C0
+ `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?)
+}
+
+do_eqp_test rtree6.2.2 {
+ SELECT * FROM t1,t2 WHERE k=ii AND x1<10
+} {
+ QUERY PLAN
+ |--SCAN t1 VIRTUAL TABLE INDEX 2:C0
+ `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?)
+}
+
+do_eqp_test rtree6.2.3 {
+ SELECT * FROM t1,t2 WHERE k=ii
+} {
+ QUERY PLAN
+ |--SCAN t1 VIRTUAL TABLE INDEX 2:
+ `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?)
+}
+
+do_eqp_test rtree6.2.4.1 {
+ SELECT * FROM t1,t2 WHERE v=+ii and x1<10 and x2>10
+} {
+ QUERY PLAN
+ |--SCAN t1 VIRTUAL TABLE INDEX 2:C0E1
+ `--SEARCH t2 USING AUTOMATIC COVERING INDEX (v=?)
+}
+do_eqp_test rtree6.2.4.2 {
+ SELECT * FROM t1,t2 WHERE v=10 and x1<10 and x2>10
+} {
+ QUERY PLAN
+ |--SCAN t1 VIRTUAL TABLE INDEX 2:C0E1
+ `--SEARCH t2 USING AUTOMATIC PARTIAL COVERING INDEX (v=?)
+}
+
+do_eqp_test rtree6.2.5 {
+ SELECT * FROM t1,t2 WHERE k=ii AND x1<v
+} {
+ QUERY PLAN
+ |--SCAN t1 VIRTUAL TABLE INDEX 2:
+ `--SEARCH t2 USING INTEGER PRIMARY KEY (rowid=?)
+}
+
+do_execsql_test rtree6-3.1 {
+ CREATE VIRTUAL TABLE t3 USING rtree(id, x1, x2, y1, y2);
+ INSERT INTO t3 VALUES(NULL, 1, 1, 2, 2);
+ SELECT * FROM t3 WHERE
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5;
+} {1 1.0 1.0 2.0 2.0}
+
+do_test rtree6.3.2 {
+ rtree_strategy {
+ SELECT * FROM t3 WHERE
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5
+ }
+} {E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0}
+do_test rtree6.3.3 {
+ rtree_strategy {
+ SELECT * FROM t3 WHERE
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5
+ }
+} {E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0E0}
+
+do_execsql_test rtree6-3.4 {
+ SELECT * FROM t3 WHERE x1>0.5 AND x1>0.8 AND x1>1.1
+} {}
+do_execsql_test rtree6-3.5 {
+ SELECT * FROM t3 WHERE
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND
+ x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>0.5 AND x1>1.1
+} {}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE t1 USING rtree(id,x0,x1,y0,y1);
+}
+do_execsql_test 4.1 {
+ DELETE FROM t1 WHERE x0>1 AND x1<2 OR y0<92;
+}
+
+expand_all_sql db
+finish_test
diff --git a/ext/rtree/rtree7.test b/ext/rtree/rtree7.test
new file mode 100644
index 0000000..1556179
--- /dev/null
+++ b/ext/rtree/rtree7.test
@@ -0,0 +1,73 @@
+# 2010 February 16
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Test that nothing goes wrong if an rtree table is created, then the
+# database page-size is modified. At one point (3.6.22), this was causing
+# malfunctions.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+
+ifcapable !rtree||!vacuum {
+ finish_test
+ return
+}
+
+# Like execsql except display output as integer where that can be
+# done without loss of information.
+#
+proc execsql_intout {sql} {
+ set out {}
+ foreach term [execsql $sql] {
+ regsub {\.0$} $term {} term
+ lappend out $term
+ }
+ return $out
+}
+
+do_test rtree7-1.1 {
+ execsql {
+ PRAGMA page_size = 1024;
+ CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, y1, y2);
+ INSERT INTO rt VALUES(1, 1, 2, 3, 4);
+ }
+} {}
+do_test rtree7-1.2 {
+ execsql_intout { SELECT * FROM rt }
+} {1 1 2 3 4}
+do_test rtree7-1.3 {
+ execsql_intout {
+ PRAGMA page_size = 2048;
+ VACUUM;
+ SELECT * FROM rt;
+ }
+} {1 1 2 3 4}
+do_test rtree7-1.4 {
+ for {set i 2} {$i <= 51} {incr i} {
+ execsql { INSERT INTO rt VALUES($i, 1, 2, 3, 4) }
+ }
+ execsql_intout { SELECT sum(x1), sum(x2), sum(y1), sum(y2) FROM rt }
+} {51 102 153 204}
+do_test rtree7-1.5 {
+ execsql_intout {
+ PRAGMA page_size = 512;
+ VACUUM;
+ SELECT sum(x1), sum(x2), sum(y1), sum(y2) FROM rt
+ }
+} {51 102 153 204}
+
+do_rtree_integrity_test rtree7-1.6 rt
+
+finish_test
diff --git a/ext/rtree/rtree8.test b/ext/rtree/rtree8.test
new file mode 100644
index 0000000..12e75a6
--- /dev/null
+++ b/ext/rtree/rtree8.test
@@ -0,0 +1,209 @@
+# 2010 February 16
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+
+#-------------------------------------------------------------------------
+# The following block of tests - rtree8-1.* - feature reading and writing
+# an r-tree table while there exist open cursors on it.
+#
+proc populate_t1 {n} {
+ execsql { DELETE FROM t1 }
+ for {set i 1} {$i <= $n} {incr i} {
+ execsql { INSERT INTO t1 VALUES($i, $i, $i+2) }
+ }
+}
+
+# A DELETE while a cursor is reading the table.
+#
+do_test rtree8-1.1.1 {
+ execsql { PRAGMA page_size = 512 }
+ execsql { CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2) }
+ populate_t1 5
+} {}
+do_test rtree8-1.1.2 {
+ set res [list]
+ set rc [catch {
+ db eval { SELECT * FROM t1 } {
+ lappend res $x1 $x2
+ if {$id==3} { db eval { DELETE FROM t1 WHERE id>3 } }
+ }
+ } msg];
+ lappend rc $msg
+ set rc
+} {1 {database table is locked}}
+do_test rtree8-1.1.2b {
+ db eval { SELECT * FROM t1 ORDER BY +id } {
+ if {$id==3} { db eval { DELETE FROM t1 WHERE id>3 } }
+ }
+ db eval {SELECT x1, x2 FROM t1}
+} {1 3 2 4 3 5}
+do_test rtree8-1.1.3 {
+ execsql { SELECT * FROM t1 }
+} {1 1 3 2 2 4 3 3 5}
+
+# Many SELECTs on the same small table.
+#
+proc nested_select {n} {
+ set ::max $n
+ db eval { SELECT * FROM t1 } {
+ if {$id == $n} { nested_select [expr $n+1] }
+ }
+ return $::max
+}
+do_test rtree8-1.2.1 { populate_t1 50 } {}
+do_test rtree8-1.2.2 { nested_select 1 } {51}
+
+# This test runs many SELECT queries simultaneously against a large
+# table, causing a collision in the hash-table used to store r-tree
+# nodes internally.
+#
+populate_t1 1500
+do_rtree_integrity_test rtree8-1.3.0 t1
+do_execsql_test rtree8-1.3.1 { SELECT max(nodeno) FROM t1_node } {164}
+do_test rtree8-1.3.2 {
+ set rowids [execsql {SELECT min(rowid) FROM t1_rowid GROUP BY nodeno}]
+ set stmt_list [list]
+ foreach row $rowids {
+ set stmt [sqlite3_prepare db "SELECT * FROM t1 WHERE id = $row" -1 tail]
+ sqlite3_step $stmt
+ lappend res_list [sqlite3_column_int $stmt 0]
+ lappend stmt_list $stmt
+ }
+} {}
+do_test rtree8-1.3.3 { set res_list } $rowids
+do_execsql_test rtree8-1.3.4 { SELECT count(*) FROM t1 } {1500}
+do_test rtree8-1.3.5 {
+ foreach stmt $stmt_list { sqlite3_finalize $stmt }
+} {}
+
+
+#-------------------------------------------------------------------------
+# The following block of tests - rtree8-2.* - test a couple of database
+# corruption cases. In this case things are not corrupted at the b-tree
+# level, but the contents of the various tables used internally by an
+# r-tree table are inconsistent.
+#
+populate_t1 50
+do_execsql_test rtree8-2.1.1 { SELECT max(nodeno) FROM t1_node } {5}
+sqlite3_db_config db DEFENSIVE 0
+do_execsql_test rtree8-2.1.2 { DELETE FROM t1_node } {}
+for {set i 1} {$i <= 50} {incr i} {
+ do_catchsql_test rtree8-2.1.3.$i {
+ SELECT * FROM t1 WHERE id = $i
+ } {1 {database disk image is malformed}}
+}
+do_catchsql_test rtree8-2.1.4 {
+ SELECT * FROM t1
+} {1 {database disk image is malformed}}
+do_catchsql_test rtree8-2.1.5 {
+ DELETE FROM t1
+} {1 {database disk image is malformed}}
+
+do_execsql_test rtree8-2.1.6 {
+ DROP TABLE t1;
+ CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2);
+} {}
+
+
+populate_t1 50
+sqlite3_db_config db DEFENSIVE 0
+do_execsql_test rtree8-2.2.1 {
+ DELETE FROM t1_parent
+} {}
+do_catchsql_test rtree8-2.2.2 {
+ DELETE FROM t1 WHERE id=25
+} {1 {database disk image is malformed}}
+do_execsql_test rtree8-2.2.3 {
+ DROP TABLE t1;
+ CREATE VIRTUAL TABLE t1 USING rtree_i32(id, x1, x2);
+} {}
+
+
+#-------------------------------------------------------------------------
+# Test that trying to use the MATCH operator with the r-tree module does
+# not confuse it.
+#
+populate_t1 10
+do_catchsql_test rtree8-3.1 {
+ SELECT * FROM t1 WHERE x1 MATCH '1234'
+} {1 {SQL logic error}}
+
+#-------------------------------------------------------------------------
+# Test a couple of invalid arguments to rtreedepth().
+#
+do_catchsql_test rtree8-4.1 {
+ SELECT rtreedepth('hello world')
+} {1 {Invalid argument to rtreedepth()}}
+do_catchsql_test rtree8-4.2 {
+ SELECT rtreedepth(X'00')
+} {1 {Invalid argument to rtreedepth()}}
+
+
+#-------------------------------------------------------------------------
+# Delete half of a lopsided tree.
+#
+do_execsql_test rtree8-5.1 {
+ CREATE VIRTUAL TABLE t2 USING rtree_i32(id, x1, x2)
+} {}
+do_test rtree8-5.2 {
+ execsql BEGIN
+ for {set i 0} {$i < 100} {incr i} {
+ execsql { INSERT INTO t2 VALUES($i, 100, 101) }
+ }
+ for {set i 100} {$i < 200} {incr i} {
+ execsql { INSERT INTO t2 VALUES($i, 1000, 1001) }
+ }
+ execsql COMMIT
+} {}
+do_rtree_integrity_test rtree8-5.3 t2
+do_test rtree8-5.4 {
+ execsql BEGIN
+ for {set i 0} {$i < 200} {incr i} {
+ execsql { DELETE FROM t2 WHERE id = $i }
+ }
+ execsql COMMIT
+} {}
+do_rtree_integrity_test rtree8-5.5 t2
+
+# 2018-05-24
+# The following script caused an assertion fault and/or segfault
+# prior to the fix that prevents simultaneous reads and writes on
+# the same rtree virtual table.
+#
+do_test rtree8-6.1 {
+ db close
+ sqlite3 db :memory:
+ db eval {
+ PRAGMA page_size=512;
+ CREATE VIRTUAL TABLE t1 USING rtree(id,x1,x2,y1,y2);
+ WITH RECURSIVE c(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c WHERE x<49)
+ INSERT INTO t1 SELECT x, x, x+1, x, x+1 FROM c;
+ }
+ set rc [catch {
+ db eval {SELECT id FROM t1} x {
+ db eval {DELETE FROM t1 WHERE id=$x(id)}
+ }
+ } msg]
+ lappend rc $msg
+} {1 {database table is locked}}
+
+
+
+
+finish_test
diff --git a/ext/rtree/rtree9.test b/ext/rtree/rtree9.test
new file mode 100644
index 0000000..f39a82e
--- /dev/null
+++ b/ext/rtree/rtree9.test
@@ -0,0 +1,129 @@
+# 2010 August 28
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file contains tests for the r-tree module. Specifically, it tests
+# that custom r-tree queries (geometry callbacks) work.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+ifcapable rtree_int_only { finish_test; return }
+
+register_cube_geom db
+
+do_execsql_test rtree9-1.1 {
+ CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, y1, y2, z1, z2);
+ INSERT INTO rt VALUES(1, 1, 2, 1, 2, 1, 2);
+} {}
+do_execsql_test rtree9-1.2 {
+ SELECT * FROM rt WHERE id MATCH cube(0, 0, 0, 2, 2, 2);
+} {1 1.0 2.0 1.0 2.0 1.0 2.0}
+do_execsql_test rtree9-1.3 {
+ SELECT * FROM rt WHERE id MATCH cube(3, 3, 3, 2, 2, 2);
+} {}
+do_execsql_test rtree9-1.4 {
+ DELETE FROM rt;
+} {}
+
+unset -nocomplain x
+for {set i 0} {$i < 1000} {incr i} {
+ set x [expr $i%10]
+ set y [expr ($i/10)%10]
+ set z [expr ($i/100)%10]
+ execsql { INSERT INTO rt VALUES($i, $x, $x+1, $y, $y+1, $z, $z+1) }
+}
+do_rtree_integrity_test rtree9-2.0 rt
+do_execsql_test rtree9-2.1 {
+ SELECT id FROM rt WHERE id MATCH cube(2.5, 2.5, 2.5, 1, 1, 1) ORDER BY id;
+} {222 223 232 233 322 323 332 333}
+do_execsql_test rtree9-2.2 {
+ SELECT id FROM rt WHERE id MATCH cube(5.5, 5.5, 5.5, 1, 1, 1) ORDER BY id;
+} {555 556 565 566 655 656 665 666}
+
+
+do_execsql_test rtree9-3.0 {
+ CREATE VIRTUAL TABLE rt32 USING rtree_i32(id, x1, x2, y1, y2, z1, z2);
+} {}
+for {set i 0} {$i < 1000} {incr i} {
+ set x [expr $i%10]
+ set y [expr ($i/10)%10]
+ set z [expr ($i/100)%10]
+ execsql { INSERT INTO rt32 VALUES($i, $x, $x+1, $y, $y+1, $z, $z+1) }
+}
+do_rtree_integrity_test rtree9-3.1 rt32
+do_execsql_test rtree9-3.2 {
+ SELECT id FROM rt32 WHERE id MATCH cube(3, 3, 3, 1, 1, 1) ORDER BY id;
+} {222 223 224 232 233 234 242 243 244 322 323 324 332 333 334 342 343 344 422 423 424 432 433 434 442 443 444}
+do_execsql_test rtree9-3.3 {
+ SELECT id FROM rt32 WHERE id MATCH cube(5.5, 5.5, 5.5, 1, 1, 1) ORDER BY id;
+} {555 556 565 566 655 656 665 666}
+
+
+do_catchsql_test rtree9-4.1 {
+ SELECT id FROM rt32 WHERE id MATCH cube(5.5, 5.5, 1, 1, 1) ORDER BY id;
+} {1 {SQL logic error}}
+for {set x 2} {$x<200} {incr x 2} {
+ do_catchsql_test rtree9-4.2.[expr $x/2] {
+ SELECT id FROM rt WHERE id MATCH randomblob($x)
+ } {1 {SQL logic error}}
+}
+do_catchsql_test rtree9-4.3 {
+ SELECT id FROM rt WHERE id MATCH CAST(
+ (cube(5.5, 5.5, 5.5, 1, 1, 1) || X'1234567812345678') AS blob
+ )
+} {1 {SQL logic error}}
+
+
+#-------------------------------------------------------------------------
+# Test the example 2d "circle" geometry callback.
+#
+register_circle_geom db
+
+do_execsql_test rtree9-5.1 {
+ CREATE VIRTUAL TABLE rt2 USING rtree(id, xmin, xmax, ymin, ymax);
+
+ INSERT INTO rt2 VALUES(1, 1, 2, 1, 2);
+ INSERT INTO rt2 VALUES(2, 1, 2, -2, -1);
+ INSERT INTO rt2 VALUES(3, -2, -1, -2, -1);
+ INSERT INTO rt2 VALUES(4, -2, -1, 1, 2);
+
+ INSERT INTO rt2 VALUES(5, 2, 3, 2, 3);
+ INSERT INTO rt2 VALUES(6, 2, 3, -3, -2);
+ INSERT INTO rt2 VALUES(7, -3, -2, -3, -2);
+ INSERT INTO rt2 VALUES(8, -3, -2, 2, 3);
+
+ INSERT INTO rt2 VALUES(9, 1.8, 3, 1.8, 3);
+ INSERT INTO rt2 VALUES(10, 1.8, 3, -3, -1.8);
+ INSERT INTO rt2 VALUES(11, -3, -1.8, -3, -1.8);
+ INSERT INTO rt2 VALUES(12, -3, -1.8, 1.8, 3);
+
+ INSERT INTO rt2 VALUES(13, -15, 15, 1.8, 2.2);
+ INSERT INTO rt2 VALUES(14, -15, 15, -2.2, -1.8);
+ INSERT INTO rt2 VALUES(15, 1.8, 2.2, -15, 15);
+ INSERT INTO rt2 VALUES(16, -2.2, -1.8, -15, 15);
+
+ INSERT INTO rt2 VALUES(17, -100, 100, -100, 100);
+} {}
+
+do_execsql_test rtree9-5.2 {
+ SELECT id FROM rt2 WHERE id MATCH circle(0.0, 0.0, 2.0);
+} {1 2 3 4 13 14 15 16 17}
+
+do_execsql_test rtree9-5.3 {
+ UPDATE rt2 SET xmin=xmin+5, ymin=ymin+5, xmax=xmax+5, ymax=ymax+5;
+ SELECT id FROM rt2 WHERE id MATCH circle(5.0, 5.0, 2.0);
+} {1 2 3 4 13 14 15 16 17}
+do_rtree_integrity_test rtree9-5.4 rt2
+
+finish_test
diff --git a/ext/rtree/rtreeA.test b/ext/rtree/rtreeA.test
new file mode 100644
index 0000000..301cd4f
--- /dev/null
+++ b/ext/rtree/rtreeA.test
@@ -0,0 +1,262 @@
+# 2010 September 22
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file contains tests for the r-tree module. Specifically, it tests
+# that corrupt or inconsistent databases do not cause crashes in the r-tree
+# module.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+
+proc create_t1 {} {
+ db close
+ forcedelete test.db
+ sqlite3 db test.db
+ execsql {
+ PRAGMA page_size = 1024;
+ CREATE VIRTUAL TABLE t1 USING rtree(id, x1, x2, y1, y2);
+ }
+}
+proc populate_t1 {} {
+ execsql BEGIN
+ for {set i 0} {$i < 500} {incr i} {
+ set x2 [expr $i+5]
+ set y2 [expr $i+5]
+ execsql { INSERT INTO t1 VALUES($i, $i, $x2, $i, $y2) }
+ }
+ execsql COMMIT
+ sqlite3_db_config db DEFENSIVE 0
+}
+
+proc truncate_node {nodeno nTrunc} {
+ set blob [db one {SELECT data FROM t1_node WHERE nodeno=$nodeno}]
+ if {$nTrunc<0} {set nTrunc "end-$nTrunc"}
+ set blob [string range $blob 0 $nTrunc]
+ db eval { UPDATE t1_node SET data = $blob WHERE nodeno=$nodeno }
+}
+
+proc set_tree_depth {tbl {newvalue ""}} {
+ set blob [db one "SELECT data FROM ${tbl}_node WHERE nodeno=1"]
+
+ if {$newvalue == ""} {
+ binary scan $blob Su oldvalue
+ return $oldvalue
+ }
+
+ set blob [binary format Sua* $newvalue [string range $blob 2 end]]
+ db eval "UPDATE ${tbl}_node SET data = \$blob WHERE nodeno=1"
+ return [set_tree_depth $tbl]
+}
+
+proc set_entry_count {tbl nodeno {newvalue ""}} {
+ set blob [db one "SELECT data FROM ${tbl}_node WHERE nodeno=$nodeno"]
+
+ if {$newvalue == ""} {
+ binary scan [string range $blob 2 end] Su oldvalue
+ return $oldvalue
+ }
+
+ set blob [binary format a*Sua* \
+ [string range $blob 0 1] $newvalue [string range $blob 4 end]
+ ]
+ db eval "UPDATE ${tbl}_node SET data = \$blob WHERE nodeno=$nodeno"
+ return [set_entry_count $tbl $nodeno]
+}
+
+
+proc do_corruption_tests {prefix args} {
+ set testarray [lindex $args end]
+ set errormsg {database disk image is malformed}
+
+ foreach {z value} [lrange $args 0 end-1] {
+ set n [string length $z]
+ if {$n>=2 && [string equal -length $n $z "-error"]} {
+ set errormsg $value
+ }
+ }
+
+ foreach {tn sql} $testarray {
+ do_catchsql_test $prefix.$tn $sql [list 1 $errormsg]
+ }
+}
+
+#-------------------------------------------------------------------------
+# Test the libraries response if the %_node table is completely empty
+# (i.e. the root node is missing), or has been removed from the database
+# entirely.
+#
+create_t1
+populate_t1
+do_execsql_test rtreeA-1.0 {
+ DELETE FROM t1_node;
+} {}
+
+do_corruption_tests rtreeA-1.1 {
+ 1 "SELECT * FROM t1"
+ 2 "SELECT * FROM t1 WHERE rowid=5"
+ 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
+ 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
+}
+
+do_execsql_test rtreeA-1.1.1 {
+ SELECT rtreecheck('main', 't1')
+} {{Node 1 missing from database
+Wrong number of entries in %_rowid table - expected 0, actual 500
+Wrong number of entries in %_parent table - expected 0, actual 23}}
+
+do_execsql_test rtreeA-1.2.0 { DROP TABLE t1_node } {}
+do_corruption_tests rtreeA-1.2 -error "database disk image is malformed" {
+ 1 "SELECT * FROM t1"
+ 2 "SELECT * FROM t1 WHERE rowid=5"
+ 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
+ 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
+}
+
+#-------------------------------------------------------------------------
+# Test the libraries response if some of the entries in the %_node table
+# are the wrong size.
+#
+create_t1
+populate_t1
+do_test rtreeA-2.1.0 {
+ set nodes [db eval {select nodeno FROM t1_node}]
+ foreach {a b c} $nodes { truncate_node $c 200 }
+} {}
+do_corruption_tests rtreeA-2.1 {
+ 1 "SELECT * FROM t1"
+ 2 "SELECT * FROM t1 WHERE rowid=5"
+ 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
+ 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
+}
+
+create_t1
+populate_t1
+do_test rtreeA-2.2.0 { truncate_node 1 200 } {}
+do_corruption_tests rtreeA-2.2 {
+ 1 "SELECT * FROM t1"
+ 2 "SELECT * FROM t1 WHERE +rowid=5"
+ 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
+ 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
+}
+
+#-------------------------------------------------------------------------
+# Set the "depth" of the tree stored on the root node incorrectly. Test
+# that this does not cause any problems.
+#
+create_t1
+populate_t1
+do_test rtreeA-3.1.0.1 { set_tree_depth t1 } {1}
+do_test rtreeA-3.1.0.2 { set_tree_depth t1 3 } {3}
+do_corruption_tests rtreeA-3.1 {
+ 1 "SELECT * FROM t1"
+ 2 "SELECT * FROM t1 WHERE +rowid=5"
+ 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
+}
+
+do_execsql_test rtreeA-3.1.0.3 {
+ SELECT rtreecheck('main', 't1')!='ok'
+} {1}
+
+do_test rtreeA-3.2.0 { set_tree_depth t1 1000 } {1000}
+do_corruption_tests rtreeA-3.2 {
+ 1 "SELECT * FROM t1"
+ 2 "SELECT * FROM t1 WHERE +rowid=5"
+ 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
+}
+
+create_t1
+populate_t1
+do_test rtreeA-3.3.0 {
+ execsql { DELETE FROM t1 WHERE rowid = 0 }
+ set_tree_depth t1 65535
+} {65535}
+do_corruption_tests rtreeA-3.3 {
+ 1 "SELECT * FROM t1"
+ 2 "SELECT * FROM t1 WHERE +rowid=5"
+ 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
+}
+
+do_execsql_test rtreeA-3.3.3.4 {
+ SELECT rtreecheck('main', 't1')
+} {{Rtree depth out of range (65535)
+Wrong number of entries in %_rowid table - expected 0, actual 499
+Wrong number of entries in %_parent table - expected 0, actual 23}}
+
+#-------------------------------------------------------------------------
+# Set the "number of entries" field on some nodes incorrectly.
+#
+create_t1
+populate_t1
+do_test rtreeA-4.1.0 {
+ set_entry_count t1 1 4000
+} {4000}
+do_corruption_tests rtreeA-4.1 {
+ 1 "SELECT * FROM t1"
+ 2 "SELECT * FROM t1 WHERE +rowid=5"
+ 3 "INSERT INTO t1 VALUES(1000, 1, 2, 3, 4)"
+ 4 "SELECT * FROM t1 WHERE x1<10 AND x2>12"
+}
+
+#-------------------------------------------------------------------------
+# Remove entries from the %_parent table and check that this does not
+# cause a crash.
+#
+create_t1
+populate_t1
+do_execsql_test rtreeA-5.1.0 { DELETE FROM t1_parent } {}
+do_corruption_tests rtreeA-5.1 {
+ 1 "DELETE FROM t1 WHERE +rowid = 5"
+ 2 "DELETE FROM t1"
+}
+
+do_execsql_test rtreeA-5.2 {
+ SELECT rtreecheck('main', 't1')!='ok'
+} {1}
+
+#-------------------------------------------------------------------------
+# Add some bad entries to the %_parent table.
+#
+create_t1
+populate_t1
+do_execsql_test rtreeA-6.1.0 {
+ UPDATE t1_parent set parentnode = parentnode+1
+} {}
+do_corruption_tests rtreeA-6.1 {
+ 1 "DELETE FROM t1 WHERE rowid = 5"
+ 2 "UPDATE t1 SET x1=x1+1, x2=x2+1"
+}
+
+do_execsql_test rtreeA-6.2 {
+ SELECT rtreecheck('main', 't1')!='ok'
+} {1}
+
+#-------------------------------------------------------------------------
+# Truncated blobs in the _node table.
+#
+create_t1
+populate_t1
+sqlite3 db test.db
+sqlite3_db_config db DEFENSIVE 0
+do_execsql_test rtreeA-7.100 {
+ UPDATE t1_node SET data=x'' WHERE rowid=1;
+} {}
+do_catchsql_test rtreeA-7.110 {
+ SELECT * FROM t1 WHERE x1>0 AND x1<100 AND x2>0 AND x2<100;
+} {1 {undersize RTree blobs in "t1_node"}}
+do_test rtreeA-7.120 {
+ sqlite3_extended_errcode db
+} {SQLITE_CORRUPT_VTAB}
+
+
+finish_test
diff --git a/ext/rtree/rtreeB.test b/ext/rtree/rtreeB.test
new file mode 100644
index 0000000..6fc3104
--- /dev/null
+++ b/ext/rtree/rtreeB.test
@@ -0,0 +1,50 @@
+# 2011 March 2
+#
+# 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.
+#
+#***********************************************************************
+# Make sure the rtreenode() testing function can handle entries with
+# 64-bit rowids.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+
+ifcapable rtree_int_only {
+ do_test rtreeB-1.1-intonly {
+ db eval {
+ CREATE VIRTUAL TABLE t1 USING rtree(ii, x0, y0, x1, y1);
+ INSERT INTO t1 VALUES(1073741824, 0.0, 0.0, 100.0, 100.0);
+ INSERT INTO t1 VALUES(2147483646, 0.0, 0.0, 200.0, 200.0);
+ INSERT INTO t1 VALUES(4294967296, 0.0, 0.0, 300.0, 300.0);
+ INSERT INTO t1 VALUES(8589934592, 20.0, 20.0, 150.0, 150.0);
+ INSERT INTO t1 VALUES(9223372036854775807, 150, 150, 400, 400);
+ SELECT rtreenode(2, data) FROM t1_node;
+ }
+ } {{{1073741824 0 0 100 100} {2147483646 0 0 200 200} {4294967296 0 0 300 300} {8589934592 20 20 150 150} {9223372036854775807 150 150 400 400}}}
+} else {
+ do_test rtreeB-1.1 {
+ db eval {
+ CREATE VIRTUAL TABLE t1 USING rtree(ii, x0, y0, x1, y1);
+ INSERT INTO t1 VALUES(1073741824, 0.0, 0.0, 100.0, 100.0);
+ INSERT INTO t1 VALUES(2147483646, 0.0, 0.0, 200.0, 200.0);
+ INSERT INTO t1 VALUES(4294967296, 0.0, 0.0, 300.0, 300.0);
+ INSERT INTO t1 VALUES(8589934592, 20.0, 20.0, 150.0, 150.0);
+ INSERT INTO t1 VALUES(9223372036854775807, 150, 150, 400, 400);
+ SELECT rtreenode(2, data) FROM t1_node;
+ }
+ } {{{1073741824 0 0 100 100} {2147483646 0 0 200 200} {4294967296 0 0 300 300} {8589934592 20 20 150 150} {9223372036854775807 150 150 400 400}}}
+}
+
+do_rtree_integrity_test rtreeB-1.2 t1
+
+finish_test
diff --git a/ext/rtree/rtreeC.test b/ext/rtree/rtreeC.test
new file mode 100644
index 0000000..bddc7d3
--- /dev/null
+++ b/ext/rtree/rtreeC.test
@@ -0,0 +1,378 @@
+# 2011 March 2
+#
+# 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.
+#
+#***********************************************************************
+# Make sure the rtreenode() testing function can handle entries with
+# 64-bit rowids.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+set testprefix rtreeC
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE r_tree USING rtree(id, min_x, max_x, min_y, max_y);
+ CREATE TABLE t(x, y);
+}
+
+do_eqp_test 1.1 {
+ SELECT * FROM r_tree, t
+ WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y
+} {
+ QUERY PLAN
+ |--SCAN t
+ `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
+}
+
+do_eqp_test 1.2 {
+ SELECT * FROM t, r_tree
+ WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y
+} {
+ QUERY PLAN
+ |--SCAN t
+ `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
+}
+
+do_eqp_test 1.3 {
+ SELECT * FROM t, r_tree
+ WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND ?<=max_y
+} {
+ QUERY PLAN
+ |--SCAN t
+ `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
+}
+
+do_eqp_test 1.5 {
+ SELECT * FROM t, r_tree
+} {
+ QUERY PLAN
+ |--SCAN r_tree VIRTUAL TABLE INDEX 2:
+ `--SCAN t
+}
+
+do_execsql_test 2.0 {
+ INSERT INTO t VALUES(0, 0);
+ INSERT INTO t VALUES(0, 1);
+ INSERT INTO t VALUES(0, 2);
+ INSERT INTO t VALUES(0, 3);
+ INSERT INTO t VALUES(0, 4);
+ INSERT INTO t VALUES(0, 5);
+ INSERT INTO t VALUES(0, 6);
+ INSERT INTO t VALUES(0, 7);
+ INSERT INTO t VALUES(0, 8);
+ INSERT INTO t VALUES(0, 9);
+
+ INSERT INTO t SELECT x+1, y FROM t;
+ INSERT INTO t SELECT x+2, y FROM t;
+ INSERT INTO t SELECT x+4, y FROM t;
+ INSERT INTO r_tree SELECT NULL, x-1, x+1, y-1, y+1 FROM t;
+ ANALYZE;
+}
+
+db close
+sqlite3 db test.db
+
+do_eqp_test 2.1 {
+ SELECT * FROM r_tree, t
+ WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y
+} {
+ QUERY PLAN
+ |--SCAN t
+ `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
+}
+
+do_eqp_test 2.2 {
+ SELECT * FROM t, r_tree
+ WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND t.x<=max_y
+} {
+ QUERY PLAN
+ |--SCAN t
+ `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
+}
+
+do_eqp_test 2.3 {
+ SELECT * FROM t, r_tree
+ WHERE t.x>=min_x AND t.x<=max_x AND t.y>=min_y AND ?<=max_y
+} {
+ QUERY PLAN
+ |--SCAN t
+ `--SCAN r_tree VIRTUAL TABLE INDEX 2:D3B2D1B0
+}
+
+do_eqp_test 2.5 {
+ SELECT * FROM t, r_tree
+} {
+ QUERY PLAN
+ |--SCAN r_tree VIRTUAL TABLE INDEX 2:
+ `--SCAN t
+}
+
+#-------------------------------------------------------------------------
+# Test that the special CROSS JOIN handling works with rtree tables.
+#
+do_execsql_test 3.1 {
+ CREATE TABLE t1(x);
+ CREATE TABLE t2(y);
+ CREATE VIRTUAL TABLE t3 USING rtree(z, x1,x2, y1,y2);
+}
+
+do_eqp_test 3.2.1 { SELECT * FROM t1 CROSS JOIN t2 } {
+ QUERY PLAN
+ |--SCAN t1
+ `--SCAN t2
+}
+do_eqp_test 3.2.2 { SELECT * FROM t2 CROSS JOIN t1 } {
+ QUERY PLAN
+ |--SCAN t2
+ `--SCAN t1
+}
+
+do_eqp_test 3.3.1 { SELECT * FROM t1 CROSS JOIN t3 } {
+ QUERY PLAN
+ |--SCAN t1
+ `--SCAN t3 VIRTUAL TABLE INDEX 2:
+}
+do_eqp_test 3.3.2 { SELECT * FROM t3 CROSS JOIN t1 } {
+ QUERY PLAN
+ |--SCAN t3 VIRTUAL TABLE INDEX 2:
+ `--SCAN t1
+}
+
+#--------------------------------------------------------------------
+# Test that LEFT JOINs are not reordered if the right-hand-side is
+# a virtual table.
+#
+reset_db
+do_execsql_test 4.1 {
+ CREATE TABLE t1(a);
+ CREATE VIRTUAL TABLE t2 USING rtree(b, x1,x2);
+
+ INSERT INTO t1 VALUES(1);
+ INSERT INTO t1 VALUES(2);
+
+ INSERT INTO t2 VALUES(1, 0.0, 0.1);
+ INSERT INTO t2 VALUES(3, 0.0, 0.1);
+}
+
+do_execsql_test 4.2 {
+ SELECT a, b FROM t1 LEFT JOIN t2 ON (+a = +b);
+} {1 1 2 {}}
+
+do_execsql_test 4.3 {
+ SELECT b, a FROM t2 LEFT JOIN t1 ON (+a = +b);
+} {1 1 3 {}}
+
+#--------------------------------------------------------------------
+# Test that the sqlite_stat1 data is used correctly.
+#
+reset_db
+do_execsql_test 5.1 {
+ CREATE TABLE t1(x INT PRIMARY KEY, y);
+ CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, +d1);
+
+ INSERT INTO t1(x) VALUES(1);
+ INSERT INTO t1(x) SELECT x+1 FROM t1; -- 2
+ INSERT INTO t1(x) SELECT x+2 FROM t1; -- 4
+ INSERT INTO t1(x) SELECT x+4 FROM t1; -- 8
+ INSERT INTO t1(x) SELECT x+8 FROM t1; -- 16
+ INSERT INTO t1(x) SELECT x+16 FROM t1; -- 32
+ INSERT INTO t1(x) SELECT x+32 FROM t1; -- 64
+ INSERT INTO t1(x) SELECT x+64 FROM t1; -- 128
+ INSERT INTO t1(x) SELECT x+128 FROM t1; -- 256
+ INSERT INTO t1(x) SELECT x+256 FROM t1; -- 512
+ INSERT INTO t1(x) SELECT x+512 FROM t1; --1024
+
+ INSERT INTO rt SELECT x, x, x+1, printf('x%04xy',x) FROM t1 WHERE x<=5;
+}
+do_rtree_integrity_test 5.1.1 rt
+
+# First test a query with no ANALYZE data at all. The outer loop is
+# real table "t1".
+#
+do_eqp_test 5.2 {
+ SELECT * FROM t1, rt WHERE x==id;
+} {
+ QUERY PLAN
+ |--SCAN t1
+ `--SCAN rt VIRTUAL TABLE INDEX 1:
+}
+
+# Now create enough ANALYZE data to tell SQLite that virtual table "rt"
+# contains very few rows. This causes it to move "rt" to the outer loop.
+#
+do_execsql_test 5.3 {
+ ANALYZE;
+ DELETE FROM sqlite_stat1 WHERE tbl='t1';
+}
+db close
+sqlite3 db test.db
+do_eqp_test 5.4 {
+ SELECT * FROM t1, rt WHERE x==id;
+} {
+ QUERY PLAN
+ |--SCAN rt VIRTUAL TABLE INDEX 2:
+ `--SEARCH t1 USING INDEX sqlite_autoindex_t1_1 (x=?)
+}
+
+# Delete the ANALYZE data. "t1" should be the outer loop again.
+#
+do_execsql_test 5.5 { DROP TABLE sqlite_stat1; }
+db close
+sqlite3 db test.db
+do_eqp_test 5.6 {
+ SELECT * FROM t1, rt WHERE x==id;
+} {
+ QUERY PLAN
+ |--SCAN t1
+ `--SCAN rt VIRTUAL TABLE INDEX 1:
+}
+
+# This time create and attach a database that contains ANALYZE data for
+# tables of the same names as those used internally by virtual table
+# "rt". Check that the rtree module is not fooled into using this data.
+# Table "t1" should remain the outer loop.
+#
+do_test 5.7 {
+ db backup test.db2
+ sqlite3 db2 test.db2
+ db2 eval {
+ ANALYZE;
+ DELETE FROM sqlite_stat1 WHERE tbl='t1';
+ }
+ db2 close
+ db close
+ sqlite3 db test.db
+ execsql { ATTACH 'test.db2' AS aux; }
+} {}
+do_eqp_test 5.8 {
+ SELECT * FROM t1, rt WHERE x==id;
+} {
+ QUERY PLAN
+ |--SCAN t1
+ `--SCAN rt VIRTUAL TABLE INDEX 1:
+}
+
+#--------------------------------------------------------------------
+# Test that having a second connection drop the sqlite_stat1 table
+# before it is required by rtreeConnect() does not cause problems.
+#
+ifcapable rtree {
+ reset_db
+ do_execsql_test 6.1 {
+ CREATE TABLE t1(x);
+ CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2);
+ INSERT INTO t1 VALUES(1);
+ INSERT INTO rt VALUES(1,2,3);
+ ANALYZE;
+ }
+ db close
+ sqlite3 db test.db
+ do_execsql_test 6.2 { SELECT * FROM t1 } {1}
+
+ do_test 6.3 {
+ sqlite3 db2 test.db
+ db2 eval { DROP TABLE sqlite_stat1 }
+ db2 close
+ execsql { SELECT * FROM rt }
+ } {1 2.0 3.0}
+ db close
+}
+
+#--------------------------------------------------------------------
+# Test that queries featuring LEFT or CROSS JOINS are handled correctly.
+# Handled correctly in this case means:
+#
+# * Terms with prereqs that appear to the left of a LEFT JOIN against
+# the virtual table are always available to xBestIndex.
+#
+# * Terms with prereqs that appear to the right of a LEFT JOIN against
+# the virtual table are never available to xBestIndex.
+#
+# And the same behaviour for CROSS joins.
+#
+reset_db
+do_execsql_test 7.0 {
+ CREATE TABLE xdir(x1);
+ CREATE TABLE ydir(y1);
+ CREATE VIRTUAL TABLE rt USING rtree_i32(id, xmin, xmax, ymin, ymax);
+
+ INSERT INTO xdir VALUES(5);
+ INSERT INTO ydir VALUES(10);
+
+ INSERT INTO rt VALUES(1, 2, 7, 12, 14); -- Not a hit
+ INSERT INTO rt VALUES(2, 2, 7, 8, 12); -- A hit!
+ INSERT INTO rt VALUES(3, 7, 11, 8, 12); -- Not a hit!
+ INSERT INTO rt VALUES(4, 5, 5, 10, 10); -- A hit!
+
+}
+
+proc do_eqp_execsql_test {tn sql res1 res2} {
+ do_eqp_test $tn.1 $sql $res1
+ do_execsql_test $tn.2 $sql $res2
+}
+
+do_eqp_execsql_test 7.1 {
+ SELECT id FROM xdir, rt, ydir
+ ON (y1 BETWEEN ymin AND ymax)
+ WHERE (x1 BETWEEN xmin AND xmax);
+} {
+ QUERY PLAN
+ |--SCAN xdir
+ |--SCAN ydir
+ `--SCAN rt VIRTUAL TABLE INDEX 2:B2D3B0D1
+} {
+ 2 4
+}
+
+do_eqp_execsql_test 7.2 {
+ SELECT * FROM xdir, rt LEFT JOIN ydir
+ ON (y1 BETWEEN ymin AND ymax)
+ WHERE (x1 BETWEEN xmin AND xmax);
+} {
+ QUERY PLAN
+ |--SCAN xdir
+ |--SCAN rt VIRTUAL TABLE INDEX 2:B0D1
+ `--SCAN ydir LEFT-JOIN
+} {
+ 5 1 2 7 12 14 {}
+ 5 2 2 7 8 12 10
+ 5 4 5 5 10 10 10
+}
+
+do_eqp_execsql_test 7.3 {
+ SELECT id FROM xdir, rt CROSS JOIN ydir
+ ON (y1 BETWEEN ymin AND ymax)
+ WHERE (x1 BETWEEN xmin AND xmax);
+} {
+ QUERY PLAN
+ |--SCAN xdir
+ |--SCAN rt VIRTUAL TABLE INDEX 2:B0D1
+ `--SCAN ydir
+} {
+ 2 4
+}
+
+do_eqp_execsql_test 7.4 {
+ SELECT id FROM rt, xdir CROSS JOIN ydir
+ ON (y1 BETWEEN ymin AND ymax)
+ WHERE (x1 BETWEEN xmin AND xmax);
+} {
+ QUERY PLAN
+ |--SCAN xdir
+ |--SCAN rt VIRTUAL TABLE INDEX 2:B0D1
+ `--SCAN ydir
+} {
+ 2 4
+}
+
+finish_test
diff --git a/ext/rtree/rtreeD.test b/ext/rtree/rtreeD.test
new file mode 100644
index 0000000..7f92898
--- /dev/null
+++ b/ext/rtree/rtreeD.test
@@ -0,0 +1,55 @@
+# 2014 March 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.
+#
+#***********************************************************************
+#
+# Miscellaneous tests for errors in the rtree constructor.
+#
+
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+ifcapable !rtree||!builtin_test {
+ finish_test
+ return
+}
+set testprefix rtreeD
+
+#-------------------------------------------------------------------------
+# Test that if an SQLITE_BUSY is encountered within the vtable
+# constructor, a relevant error message is returned.
+#
+do_multiclient_test tn {
+ do_test 1.$tn.1 {
+ sql1 {
+ CREATE TABLE t1(a, b);
+ INSERT INTO t1 VALUES(1,2);
+ CREATE VIRTUAL TABLE rt USING rtree(id, minx, maxx, miny, maxy);
+ INSERT INTO rt VALUES(1,2,3,4,5);
+ }
+ } {}
+
+ do_test 1.$tn.2 {
+ sql2 { SELECT * FROM t1; }
+ } {1 2}
+
+ do_test 1.$tn.3 {
+ sql1 { BEGIN EXCLUSIVE; INSERT INTO t1 VALUES(3, 4); }
+ } {}
+
+ do_test 1.$tn.4 {
+ list [catch { sql2 { SELECT * FROM rt } } msg] $msg
+ } {1 {database is locked}}
+}
+
+finish_test
diff --git a/ext/rtree/rtreeE.test b/ext/rtree/rtreeE.test
new file mode 100644
index 0000000..72dcc94
--- /dev/null
+++ b/ext/rtree/rtreeE.test
@@ -0,0 +1,143 @@
+# 2010 August 28
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file contains tests for the r-tree module. Specifically, it tests
+# that new-style custom r-tree queries (geometry callbacks) work.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+ifcapable rtree_int_only { finish_test; return }
+
+
+#-------------------------------------------------------------------------
+# Test the example 2d "circle" geometry callback.
+#
+register_circle_geom db
+
+do_execsql_test rtreeE-1.0.0 {
+ PRAGMA page_size=512;
+ CREATE VIRTUAL TABLE rt1 USING rtree(id,x0,x1,y0,y1);
+
+ /* A tight pattern of small boxes near 0,0 */
+ WITH RECURSIVE
+ x(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM x WHERE x<4),
+ y(y) AS (VALUES(0) UNION ALL SELECT y+1 FROM y WHERE y<4)
+ INSERT INTO rt1 SELECT x+5*y, x, x+2, y, y+2 FROM x, y;
+
+ /* A looser pattern of small boxes near 100, 0 */
+ WITH RECURSIVE
+ x(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM x WHERE x<4),
+ y(y) AS (VALUES(0) UNION ALL SELECT y+1 FROM y WHERE y<4)
+ INSERT INTO rt1 SELECT 100+x+5*y, x*3+100, x*3+102, y*3, y*3+2 FROM x, y;
+
+ /* A looser pattern of larger boxes near 0, 200 */
+ WITH RECURSIVE
+ x(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM x WHERE x<4),
+ y(y) AS (VALUES(0) UNION ALL SELECT y+1 FROM y WHERE y<4)
+ INSERT INTO rt1 SELECT 200+x+5*y, x*7, x*7+15, y*7+200, y*7+215 FROM x, y;
+} {}
+do_rtree_integrity_test rtreeE-1.0.1 rt1
+
+# Queries against each of the three clusters */
+do_execsql_test rtreeE-1.1 {
+ SELECT id FROM rt1 WHERE id MATCH Qcircle(0.0, 0.0, 50.0, 3) ORDER BY id;
+} {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24}
+do_execsql_test rtreeE-1.1x {
+ SELECT id FROM rt1 WHERE id MATCH Qcircle('x:0 y:0 r:50.0 e:3') ORDER BY id;
+} {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24}
+do_execsql_test rtreeE-1.2 {
+ SELECT id FROM rt1 WHERE id MATCH Qcircle(100.0, 0.0, 50.0, 3) ORDER BY id;
+} {100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124}
+do_execsql_test rtreeE-1.3 {
+ SELECT id FROM rt1 WHERE id MATCH Qcircle(0.0, 200.0, 50.0, 3) ORDER BY id;
+} {200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224}
+
+# The Qcircle geometry function gives a lower score to larger leaf-nodes.
+# This causes the 200s to sort before the 100s and the 0s to sort before
+# last.
+#
+do_execsql_test rtreeE-1.4 {
+ SELECT id FROM rt1 WHERE id MATCH Qcircle('r:1000 e:3') AND id%100==0
+} {200 100 0}
+
+# Exclude odd rowids on a depth-first search
+do_execsql_test rtreeE-1.5 {
+ SELECT id FROM rt1 WHERE id MATCH Qcircle('r:1000 e:4') ORDER BY +id
+} {0 2 4 6 8 10 12 14 16 18 20 22 24 100 102 104 106 108 110 112 114 116 118 120 122 124 200 202 204 206 208 210 212 214 216 218 220 222 224}
+
+# Exclude odd rowids on a breadth-first search.
+do_execsql_test rtreeE-1.6 {
+ SELECT id FROM rt1 WHERE id MATCH Qcircle(0,0,1000,5) ORDER BY +id
+} {0 2 4 6 8 10 12 14 16 18 20 22 24 100 102 104 106 108 110 112 114 116 118 120 122 124 200 202 204 206 208 210 212 214 216 218 220 222 224}
+
+# Test that rtree prefers MATCH to lookup-by-rowid.
+#
+do_execsql_test rtreeE-1.7 {
+ SELECT id FROM rt1 WHERE id=18 AND id MATCH Qcircle(0,0,1000,5)
+} {18}
+
+
+# Construct a large 2-D RTree with thousands of random entries.
+#
+do_test rtreeE-2.1 {
+ db eval {
+ CREATE TABLE t2(id,x0,x1,y0,y1);
+ CREATE VIRTUAL TABLE rt2 USING rtree(id,x0,x1,y0,y1);
+ BEGIN;
+ }
+ expr srand(0)
+ for {set i 1} {$i<=10000} {incr i} {
+ set dx [expr {int(rand()*40)+1}]
+ set dy [expr {int(rand()*40)+1}]
+ set x0 [expr {int(rand()*(10000 - $dx))}]
+ set x1 [expr {$x0+$dx}]
+ set y0 [expr {int(rand()*(10000 - $dy))}]
+ set y1 [expr {$y0+$dy}]
+ set id [expr {$i+10000}]
+ db eval {INSERT INTO t2 VALUES($id,$x0,$x1,$y0,$y1)}
+ }
+ db eval {
+ INSERT INTO rt2 SELECT * FROM t2;
+ COMMIT;
+ }
+} {}
+do_rtree_integrity_test rtreeE-2.1.1 rt2
+
+for {set i 1} {$i<=200} {incr i} {
+ set dx [expr {int(rand()*100)}]
+ set dy [expr {int(rand()*100)}]
+ set x0 [expr {int(rand()*(10000 - $dx))}]
+ set x1 [expr {$x0+$dx}]
+ set y0 [expr {int(rand()*(10000 - $dy))}]
+ set y1 [expr {$y0+$dy}]
+ set ans [db eval {SELECT id FROM t2 WHERE x1>=$x0 AND x0<=$x1 AND y1>=$y0 AND y0<=$y1 ORDER BY id}]
+ do_execsql_test rtreeE-2.2.$i {
+ SELECT id FROM rt2 WHERE id MATCH breadthfirstsearch($x0,$x1,$y0,$y1) ORDER BY id
+ } $ans
+}
+
+# Run query that have very deep priority queues
+#
+set ans [db eval {SELECT id FROM t2 WHERE x1>=0 AND x0<=5000 AND y1>=0 AND y0<=5000 ORDER BY id}]
+do_execsql_test rtreeE-2.3 {
+ SELECT id FROM rt2 WHERE id MATCH breadthfirstsearch(0,5000,0,5000) ORDER BY id
+} $ans
+set ans [db eval {SELECT id FROM t2 WHERE x1>=0 AND x0<=10000 AND y1>=0 AND y0<=10000 ORDER BY id}]
+do_execsql_test rtreeE-2.4 {
+ SELECT id FROM rt2 WHERE id MATCH breadthfirstsearch(0,10000,0,10000) ORDER BY id
+} $ans
+
+
+finish_test
diff --git a/ext/rtree/rtreeF.test b/ext/rtree/rtreeF.test
new file mode 100644
index 0000000..561770d
--- /dev/null
+++ b/ext/rtree/rtreeF.test
@@ -0,0 +1,84 @@
+# 2014-08-21
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file contains tests for the r-tree module.
+#
+# This file contains test cases for the ticket
+# [369d57fb8e5ccdff06f197a37147a88f9de95cda] (2014-08-21)
+#
+# The following SQL causes an assertion fault while running
+# sqlite3_prepare() on the DELETE statement:
+#
+# CREATE TABLE t1(x);
+# CREATE TABLE t2(y);
+# CREATE VIRTUAL TABLE t3 USING rtree(a,b,c);
+# CREATE TRIGGER t2del AFTER DELETE ON t2 WHEN (SELECT 1 from t1) BEGIN
+# DELETE FROM t3 WHERE a=old.y;
+# END;
+# DELETE FROM t2 WHERE y=1;
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+
+do_execsql_test rtreeF-1.1 {
+ CREATE TABLE t1(x);
+ CREATE TABLE t2(y);
+ CREATE VIRTUAL TABLE t3 USING rtree(a,b,c);
+ CREATE TRIGGER t2dwl AFTER DELETE ON t2 WHEN (SELECT 1 from t1) BEGIN
+ DELETE FROM t3 WHERE a=old.y;
+ END;
+
+ INSERT INTO t1(x) VALUES(999);
+ INSERT INTO t2(y) VALUES(1),(2),(3),(4),(5);
+ INSERT INTO t3(a,b,c) VALUES(1,2,3),(2,3,4),(3,4,5),(4,5,6),(5,6,7);
+
+ SELECT a FROM t3 ORDER BY a;
+ SELECT '|';
+ SELECT y FROM t2 ORDER BY y;
+} {1 2 3 4 5 | 1 2 3 4 5}
+do_execsql_test rtreeF-1.2 {
+ DELETE FROM t2 WHERE y=3;
+
+ SELECT a FROM t3 ORDER BY a;
+ SELECT '|';
+ SELECT y FROM t2 ORDER BY y;
+} {1 2 4 5 | 1 2 4 5}
+do_execsql_test rtreeF-1.3 {
+ DELETE FROM t1;
+ DELETE FROM t2 WHERE y=5;
+
+ SELECT a FROM t3 ORDER BY a;
+ SELECT '|';
+ SELECT y FROM t2 ORDER BY y;
+} {1 2 4 5 | 1 2 4}
+do_execsql_test rtreeF-1.4 {
+ INSERT INTO t1 DEFAULT VALUES;
+ DELETE FROM t2 WHERE y=5;
+
+ SELECT a FROM t3 ORDER BY a;
+ SELECT '|';
+ SELECT y FROM t2 ORDER BY y;
+} {1 2 4 5 | 1 2 4}
+do_execsql_test rtreeF-1.5 {
+ DELETE FROM t2 WHERE y=2;
+
+ SELECT a FROM t3 ORDER BY a;
+ SELECT '|';
+ SELECT y FROM t2 ORDER BY y;
+} {1 4 5 | 1 4}
+
+do_rtree_integrity_test rtreeF-1.6 t3
+
+finish_test
diff --git a/ext/rtree/rtreeG.test b/ext/rtree/rtreeG.test
new file mode 100644
index 0000000..12225d5
--- /dev/null
+++ b/ext/rtree/rtreeG.test
@@ -0,0 +1,69 @@
+# 2016-05-32
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file contains tests for the r-tree module.
+#
+# Verify that no invalid SQL is run during initialization
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+
+db close
+sqlite3_shutdown
+test_sqlite3_log [list lappend ::log]
+set ::log [list]
+sqlite3 db test.db
+
+
+set ::log {}
+do_execsql_test rtreeG-1.1 {
+ CREATE VIRTUAL TABLE t1 USING rtree(id,x0,x1,y0,y1);
+} {}
+do_test rtreeG-1.1log {
+ set ::log
+} {}
+
+do_execsql_test rtreeG-1.2 {
+ INSERT INTO t1 VALUES(1,10,15,5,23),(2,20,21,5,23),(3,10,15,20,30);
+ SELECT id from t1 WHERE x0>8 AND x1<16 AND y0>2 AND y1<25;
+} {1}
+do_rtree_integrity_test rtreeG-1.2.integrity t1
+do_test rtreeG-1.2log {
+ set ::log
+} {}
+
+db close
+sqlite3 db test.db
+do_execsql_test rtreeG-1.3 {
+ SELECT id from t1 WHERE x0>8 AND x1<16 AND y0>2 AND y1<25;
+} {1}
+do_test rtreeG-1.3log {
+ set ::log
+} {}
+
+do_execsql_test rtreeG-1.4 {
+ DROP TABLE t1;
+} {}
+do_test rtreeG-1.4log {
+ set ::log
+} {}
+
+expand_all_sql db
+db close
+sqlite3_shutdown
+test_sqlite3_log
+sqlite3_initialize
+sqlite3 db test.db
+
+finish_test
diff --git a/ext/rtree/rtreeH.test b/ext/rtree/rtreeH.test
new file mode 100644
index 0000000..e26107f
--- /dev/null
+++ b/ext/rtree/rtreeH.test
@@ -0,0 +1,103 @@
+# 2018-05-16
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file contains tests for the r-tree module, specifically the
+# auxiliary column mechanism.
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+
+do_execsql_test rtreeH-100 {
+ CREATE VIRTUAL TABLE t1 USING rtree(id,x0,x1,y0,y1,+label,+other);
+ INSERT INTO t1(x0,x1,y0,y1,label) VALUES
+ (0,10,0,10,'lower-left corner'),
+ (0,10,90,100,'upper-left corner'),
+ (90,100,0,10,'lower-right corner'),
+ (90,100,90,100,'upper-right corner'),
+ (40,60,40,60,'center'),
+ (0,5,0,100,'left edge'),
+ (95,100,0,100,'right edge'),
+ (0,100,0,5,'bottom edge'),
+ (0,100,95,100,'top edge'),
+ (0,100,0,100,'the whole thing'),
+ (0,50,0,100,'left half'),
+ (51,100,0,100,'right half'),
+ (0,100,0,50,'bottom half'),
+ (0,100,51,100,'top half');
+} {}
+do_execsql_test rtreeH-101 {
+ SELECT * FROM t1_rowid ORDER BY rowid
+} {1 1 {lower-left corner} {} 2 1 {upper-left corner} {} 3 1 {lower-right corner} {} 4 1 {upper-right corner} {} 5 1 center {} 6 1 {left edge} {} 7 1 {right edge} {} 8 1 {bottom edge} {} 9 1 {top edge} {} 10 1 {the whole thing} {} 11 1 {left half} {} 12 1 {right half} {} 13 1 {bottom half} {} 14 1 {top half} {}}
+
+do_execsql_test rtreeH-102 {
+ SELECT * FROM t1 WHERE rowid=5;
+} {5 40.0 60.0 40.0 60.0 center {}}
+do_execsql_test rtreeH-102b {
+ SELECT * FROM t1 WHERE rowid=5.0;
+} {5 40.0 60.0 40.0 60.0 center {}}
+do_execsql_test rtreeH-102c {
+ SELECT * FROM t1 WHERE rowid='5';
+} {5 40.0 60.0 40.0 60.0 center {}}
+do_execsql_test rtreeH-102d {
+ SELECT * FROM t1 WHERE rowid='0005';
+} {5 40.0 60.0 40.0 60.0 center {}}
+do_execsql_test rtreeH-102e {
+ SELECT * FROM t1 WHERE rowid='+5.0e+0';
+} {5 40.0 60.0 40.0 60.0 center {}}
+do_execsql_test rtreeH-103 {
+ SELECT * FROM t1 WHERE label='center';
+} {5 40.0 60.0 40.0 60.0 center {}}
+
+do_execsql_test rtreeH-104 {
+ SELECT * FROM t1 WHERE rowid='+5.0e+0x';
+} {}
+do_execsql_test rtreeH-105 {
+ SELECT * FROM t1 WHERE rowid=x'35';
+} {}
+do_execsql_test rtreeH-106 {
+ SELECT * FROM t1 WHERE rowid=null;
+} {}
+
+
+do_rtree_integrity_test rtreeH-110 t1
+
+do_execsql_test rtreeH-120 {
+ SELECT label FROM t1 WHERE x1<=50 ORDER BY id
+} {{lower-left corner} {upper-left corner} {left edge} {left half}}
+do_execsql_test rtreeH-121 {
+ SELECT label FROM t1 WHERE x1<=50 AND label NOT LIKE '%corner%' ORDER BY id
+} {{left edge} {left half}}
+
+do_execsql_test rtreeH-200 {
+ WITH RECURSIVE
+ c1(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c1 WHERE x<99),
+ c2(y) AS (VALUES(0) UNION ALL SELECT y+1 FROM c2 WHERE y<99)
+ INSERT INTO t1(id, x0,x1,y0,y1,label)
+ SELECT 1000+x+y*100, x, x+1, y, y+1, printf('box-%d,%d',x,y) FROM c1, c2;
+} {}
+
+do_execsql_test rtreeH-210 {
+ SELECT label FROM t1 WHERE x0>=48 AND x1<=50 AND y0>=48 AND y1<=50
+ ORDER BY id;
+} {box-48,48 box-49,48 box-48,49 box-49,49}
+
+do_execsql_test rtreeH-300 {
+ UPDATE t1 SET label='x'||label
+ WHERE x0>=49 AND x1<=50 AND y0>=49 AND y1<=50;
+ SELECT label FROM t1 WHERE x0>=48 AND x1<=50 AND y0>=48 AND y1<=50
+ ORDER BY id;
+} {box-48,48 box-49,48 box-48,49 xbox-49,49}
+
+
+finish_test
diff --git a/ext/rtree/rtreeI.test b/ext/rtree/rtreeI.test
new file mode 100644
index 0000000..0bc910e
--- /dev/null
+++ b/ext/rtree/rtreeI.test
@@ -0,0 +1,74 @@
+# 2019-12-05
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# Additional test cases
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+ifcapable !rtree { finish_test ; return }
+
+# The following is a test of rowvalue handling on virtual tables that
+# deal with inequalities and that set the OMIT flag on terms of the
+# WHERE clause. This is not specific to rtree. We just use rtree because
+# it is a convenient test platform since it has all the right
+# characteristics.
+#
+do_execsql_test rtreeI-1.10 {
+ CREATE TABLE t1(a);
+ INSERT INTO t1 VALUES(2);
+ CREATE VIRTUAL TABLE t2 USING rtree(id,x0,x1);
+ INSERT INTO t2(id,x0,x1) VALUES(1,2,3);
+} {}
+do_execsql_test rtreeI-1.20 {
+ SELECT 123 FROM t1, t2 WHERE (a,0)>(x0,0);
+} {}
+do_execsql_test rtreeI-1.21 {
+ SELECT 123 FROM t1, t2 WHERE (a,0.1)>(x0,0);
+} {123}
+do_execsql_test rtreeI-1.22 {
+ SELECT 123 FROM t1, t2 WHERE (a,0)>=(x0,0);
+} {123}
+do_execsql_test rtreeI-1.23 {
+ SELECT 123 FROM t1, t2 WHERE (a,0)<=(x0,0);
+} {123}
+do_execsql_test rtreeI-1.24 {
+ SELECT 123 FROM t1, t2 WHERE (a,0)<(x0,0);
+} {}
+do_execsql_test rtreeI-1.30 {
+ SELECT 123 FROM t1, t2 WHERE (x0,0)<(a,0);
+} {}
+do_execsql_test rtreeI-1.31 {
+ SELECT 123 FROM t1, t2 WHERE (x0,0)<(a,0.1);
+} {123}
+do_execsql_test rtreeI-1.40 {
+ SELECT 123 FROM t1, t2 WHERE x1<5 AND id<99 AND (a,0)>(x0,0);
+} {}
+do_execsql_test rtreeI-1.41 {
+ SELECT 123 FROM t1, t2 WHERE x1<5 AND id<99 AND (a,0.5)>(x0,0);
+} {123}
+do_execsql_test rtreeI-1.42 {
+ SELECT 123 FROM t1, t2 WHERE x1<5 AND id<99 AND (a,0)>=(x0,0);
+} {123}
+do_execsql_test rtreeI-1.43 {
+ SELECT 123 FROM t1, t2 WHERE x1<5 AND id<99 AND (a,0)<(x0,0);
+} {}
+do_execsql_test rtreeI-1.50 {
+ SELECT 123 FROM t1, t2 WHERE 5>x1 AND 99>id AND (x0,0)<(a,0);
+} {}
+do_execsql_test rtreeI-1.51 {
+ SELECT 123 FROM t1, t2 WHERE 5>x1 AND 99>id AND (x0,0)<(a,0.5);
+} {123}
+
+
+
+finish_test
diff --git a/ext/rtree/rtree_perf.tcl b/ext/rtree/rtree_perf.tcl
new file mode 100644
index 0000000..e42e685
--- /dev/null
+++ b/ext/rtree/rtree_perf.tcl
@@ -0,0 +1,74 @@
+
+set testdir [file join [file dirname $argv0] .. .. test]
+source $testdir/tester.tcl
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+set NROW 10000
+set NQUERY 500
+
+puts "Generating $NROW rows of data..."
+set data [list]
+for {set ii 0} {$ii < $NROW} {incr ii} {
+ set x1 [expr {rand()*1000}]
+ set x2 [expr {$x1+rand()*50}]
+ set y1 [expr {rand()*1000}]
+ set y2 [expr {$y1+rand()*50}]
+ lappend data $x1 $x2 $y1 $y2
+}
+puts "Finished generating data"
+
+
+set sql1 {CREATE TABLE btree(ii INTEGER PRIMARY KEY, x1, x2, y1, y2)}
+set sql2 {CREATE VIRTUAL TABLE rtree USING rtree(ii, x1, x2, y1, y2)}
+puts "Creating tables:"
+puts " $sql1"
+puts " $sql2"
+db eval $sql1
+db eval $sql2
+
+db eval "pragma cache_size=100"
+
+puts -nonewline "Inserting into btree... "
+flush stdout
+set btree_time [time {db transaction {
+ set ii 1
+ foreach {x1 x2 y1 y2} $data {
+ db eval {INSERT INTO btree VALUES($ii, $x1, $x2, $y1, $y2)}
+ incr ii
+ }
+}}]
+puts "$btree_time"
+
+puts -nonewline "Inserting into rtree... "
+flush stdout
+set rtree_time [time {db transaction {
+ set ii 1
+ foreach {x1 x2 y1 y2} $data {
+ incr ii
+ db eval {INSERT INTO rtree VALUES($ii, $x1, $x2, $y1, $y2)}
+ }
+}}]
+puts "$rtree_time"
+
+
+puts -nonewline "Selecting from btree... "
+flush stdout
+set btree_select_time [time {
+ foreach {x1 x2 y1 y2} [lrange $data 0 [expr $NQUERY*4-1]] {
+ db eval {SELECT * FROM btree WHERE x1<$x1 AND x2>$x2 AND y1<$y1 AND y2>$y2}
+ }
+}]
+puts "$btree_select_time"
+
+puts -nonewline "Selecting from rtree... "
+flush stdout
+set rtree_select_time [time {
+ foreach {x1 x2 y1 y2} [lrange $data 0 [expr $NQUERY*4-1]] {
+ db eval {SELECT * FROM rtree WHERE x1<$x1 AND x2>$x2 AND y1<$y1 AND y2>$y2}
+ }
+}]
+puts "$rtree_select_time"
diff --git a/ext/rtree/rtree_util.tcl b/ext/rtree/rtree_util.tcl
new file mode 100644
index 0000000..afa588e
--- /dev/null
+++ b/ext/rtree/rtree_util.tcl
@@ -0,0 +1,197 @@
+# 2008 Feb 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.
+#
+#***********************************************************************
+#
+# This file contains Tcl code that may be useful for testing or
+# analyzing r-tree structures created with this module. It is
+# used by both test procedures and the r-tree viewer application.
+#
+
+
+#--------------------------------------------------------------------------
+# PUBLIC API:
+#
+# rtree_depth
+# rtree_ndim
+# rtree_node
+# rtree_mincells
+# rtree_check
+# rtree_dump
+# rtree_treedump
+#
+
+proc rtree_depth {db zTab} {
+ $db one "SELECT rtreedepth(data) FROM ${zTab}_node WHERE nodeno=1"
+}
+
+proc rtree_nodedepth {db zTab iNode} {
+ set iDepth [rtree_depth $db $zTab]
+
+ set ii $iNode
+ while {$ii != 1} {
+ set sql "SELECT parentnode FROM ${zTab}_parent WHERE nodeno = $ii"
+ set ii [db one $sql]
+ incr iDepth -1
+ }
+
+ return $iDepth
+}
+
+# Return the number of dimensions of the rtree.
+#
+proc rtree_ndim {db zTab} {
+ set nDim [expr {(([llength [$db eval "pragma table_info($zTab)"]]/6)-1)/2}]
+}
+
+# Return the contents of rtree node $iNode.
+#
+proc rtree_node {db zTab iNode {iPrec 6}} {
+ set nDim [rtree_ndim $db $zTab]
+ set sql "
+ SELECT rtreenode($nDim, data) FROM ${zTab}_node WHERE nodeno = $iNode
+ "
+ set node [db one $sql]
+
+ set nCell [llength $node]
+ set nCoord [expr $nDim*2]
+ for {set ii 0} {$ii < $nCell} {incr ii} {
+ for {set jj 1} {$jj <= $nCoord} {incr jj} {
+ set newval [format "%.${iPrec}f" [lindex $node $ii $jj]]
+ lset node $ii $jj $newval
+ }
+ }
+ set node
+}
+
+proc rtree_mincells {db zTab} {
+ set n [$db one "select length(data) FROM ${zTab}_node LIMIT 1"]
+ set nMax [expr {int(($n-4)/(8+[rtree_ndim $db $zTab]*2*4))}]
+ return [expr {int($nMax/3)}]
+}
+
+# An integrity check for the rtree $zTab accessible via database
+# connection $db.
+#
+proc rtree_check {db zTab} {
+ array unset ::checked
+
+ # Check each r-tree node.
+ set rc [catch {
+ rtree_node_check $db $zTab 1 [rtree_depth $db $zTab]
+ } msg]
+ if {$rc && $msg ne ""} { error $msg }
+
+ # Check that the _rowid and _parent tables have the right
+ # number of entries.
+ set nNode [$db one "SELECT count(*) FROM ${zTab}_node"]
+ set nRow [$db one "SELECT count(*) FROM ${zTab}"]
+ set nRowid [$db one "SELECT count(*) FROM ${zTab}_rowid"]
+ set nParent [$db one "SELECT count(*) FROM ${zTab}_parent"]
+
+ if {$nNode != ($nParent+1)} {
+ error "Wrong number of entries in ${zTab}_parent"
+ }
+ if {$nRow != $nRowid} {
+ error "Wrong number of entries in ${zTab}_rowid"
+ }
+
+ return $rc
+}
+
+proc rtree_node_check {db zTab iNode iDepth} {
+ if {[info exists ::checked($iNode)]} { error "Second ref to $iNode" }
+ set ::checked($iNode) 1
+
+ set node [rtree_node $db $zTab $iNode]
+ if {$iNode!=1 && [llength $node]==0} { error "No such node: $iNode" }
+
+ if {$iNode != 1 && [llength $node]<[rtree_mincells $db $zTab]} {
+ puts "Node $iNode: Has only [llength $node] cells"
+ error ""
+ }
+ if {$iNode == 1 && [llength $node]==1 && [rtree_depth $db $zTab]>0} {
+ set depth [rtree_depth $db $zTab]
+ puts "Node $iNode: Has only 1 child (tree depth is $depth)"
+ error ""
+ }
+
+ set nDim [expr {([llength [lindex $node 0]]-1)/2}]
+
+ if {$iDepth > 0} {
+ set d [expr $iDepth-1]
+ foreach cell $node {
+ set shouldbe [rtree_node_check $db $zTab [lindex $cell 0] $d]
+ if {$cell ne $shouldbe} {
+ puts "Node $iNode: Cell is: {$cell}, should be {$shouldbe}"
+ error ""
+ }
+ }
+ }
+
+ set mapping_table "${zTab}_parent"
+ set mapping_sql "SELECT parentnode FROM $mapping_table WHERE rowid = \$rowid"
+ if {$iDepth==0} {
+ set mapping_table "${zTab}_rowid"
+ set mapping_sql "SELECT nodeno FROM $mapping_table WHERE rowid = \$rowid"
+ }
+ foreach cell $node {
+ set rowid [lindex $cell 0]
+ set mapping [db one $mapping_sql]
+ if {$mapping != $iNode} {
+ puts "Node $iNode: $mapping_table entry for cell $rowid is $mapping"
+ error ""
+ }
+ }
+
+ set ret [list $iNode]
+ for {set ii 1} {$ii <= $nDim*2} {incr ii} {
+ set f [lindex $node 0 $ii]
+ foreach cell $node {
+ set f2 [lindex $cell $ii]
+ if {($ii%2)==1 && $f2<$f} {set f $f2}
+ if {($ii%2)==0 && $f2>$f} {set f $f2}
+ }
+ lappend ret $f
+ }
+ return $ret
+}
+
+proc rtree_dump {db zTab} {
+ set zRet ""
+ set nDim [expr {(([llength [$db eval "pragma table_info($zTab)"]]/6)-1)/2}]
+ set sql "SELECT nodeno, rtreenode($nDim, data) AS node FROM ${zTab}_node"
+ $db eval $sql {
+ append zRet [format "% -10s %s\n" $nodeno $node]
+ }
+ set zRet
+}
+
+proc rtree_nodetreedump {db zTab zIndent iDepth iNode} {
+ set ret ""
+ set node [rtree_node $db $zTab $iNode 1]
+ append ret [format "%-3d %s%s\n" $iNode $zIndent $node]
+ if {$iDepth>0} {
+ foreach cell $node {
+ set i [lindex $cell 0]
+ append ret [rtree_nodetreedump $db $zTab "$zIndent " [expr $iDepth-1] $i]
+ }
+ }
+ set ret
+}
+
+proc rtree_treedump {db zTab} {
+ set d [rtree_depth $db $zTab]
+ rtree_nodetreedump $db $zTab "" $d 1
+}
+
+proc do_rtree_integrity_test {tn tbl} {
+ uplevel [list do_execsql_test $tn "SELECT rtreecheck('$tbl')" ok]
+}
+
diff --git a/ext/rtree/rtreecheck.test b/ext/rtree/rtreecheck.test
new file mode 100644
index 0000000..17f359a
--- /dev/null
+++ b/ext/rtree/rtreecheck.test
@@ -0,0 +1,160 @@
+# 2017 August 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.
+#
+#***********************************************************************
+#
+#
+
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+set testprefix rtreecheck
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+proc swap_int32 {blob i0 i1} {
+ binary scan $blob I* L
+
+ set a [lindex $L $i0]
+ set b [lindex $L $i1]
+
+ lset L $i0 $b
+ lset L $i1 $a
+
+ binary format I* $L
+}
+
+proc set_int32 {blob idx val} {
+ binary scan $blob I* L
+ lset L $idx $val
+ binary format I* $L
+}
+
+do_catchsql_test 1.0 {
+ SELECT rtreecheck();
+} {1 {wrong number of arguments to function rtreecheck()}}
+
+do_catchsql_test 1.1 {
+ SELECT rtreecheck(0,0,0);
+} {1 {wrong number of arguments to function rtreecheck()}}
+
+
+proc setup_simple_db {{module rtree}} {
+ reset_db
+ db func swap_int32 swap_int32
+ execsql "
+ CREATE VIRTUAL TABLE r1 USING $module (id, x1, x2, y1, y2);
+ INSERT INTO r1 VALUES(1, 5, 5, 5, 5); -- 3
+ INSERT INTO r1 VALUES(2, 6, 6, 6, 6); -- 9
+ INSERT INTO r1 VALUES(3, 7, 7, 7, 7); -- 15
+ INSERT INTO r1 VALUES(4, 8, 8, 8, 8); -- 21
+ INSERT INTO r1 VALUES(5, 9, 9, 9, 9); -- 27
+ "
+ sqlite3_db_config db DEFENSIVE 0
+}
+
+setup_simple_db
+do_execsql_test 2.1 {
+ SELECT rtreecheck('r1')
+} {ok}
+
+do_execsql_test 2.2 {
+ UPDATE r1_node SET data = swap_int32(data, 3, 9);
+ UPDATE r1_node SET data = swap_int32(data, 23, 29);
+}
+
+do_execsql_test 2.3 {
+ SELECT rtreecheck('r1')
+} {{Dimension 0 of cell 0 on node 1 is corrupt
+Dimension 1 of cell 3 on node 1 is corrupt}}
+
+setup_simple_db
+do_execsql_test 2.4 {
+ DELETE FROM r1_rowid WHERE rowid = 3;
+ SELECT rtreecheck('r1')
+} {{Mapping (3 -> 1) missing from %_rowid table
+Wrong number of entries in %_rowid table - expected 5, actual 4}}
+
+setup_simple_db
+do_execsql_test 2.5 {
+ UPDATE r1_rowid SET nodeno=2 WHERE rowid=3;
+ SELECT rtreecheck('r1')
+} {{Found (3 -> 2) in %_rowid table, expected (3 -> 1)}}
+
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE r1 USING rtree_i32(id, x1, x2);
+ INSERT INTO r1 VALUES(1, 0x7FFFFFFF*-1, 0x7FFFFFFF);
+ INSERT INTO r1 VALUES(2, 0x7FFFFFFF*-1, 5);
+ INSERT INTO r1 VALUES(3, -5, 5);
+ INSERT INTO r1 VALUES(4, 5, 0x11111111);
+ INSERT INTO r1 VALUES(5, 5, 0x00800000);
+ INSERT INTO r1 VALUES(6, 5, 0x00008000);
+ INSERT INTO r1 VALUES(7, 5, 0x00000080);
+ INSERT INTO r1 VALUES(8, 5, 0x40490fdb);
+ INSERT INTO r1 VALUES(9, 0x7f800000, 0x7f900000);
+ SELECT rtreecheck('r1')
+} {ok}
+
+do_execsql_test 3.1 {
+ CREATE VIRTUAL TABLE r2 USING rtree_i32(id, x1, x2);
+ INSERT INTO r2 VALUES(2, -1*(1<<31), -1*(1<<31)+5);
+ SELECT rtreecheck('r2')
+} {ok}
+
+sqlite3_db_config db DEFENSIVE 0
+do_execsql_test 3.2 {
+ BEGIN;
+ UPDATE r2_node SET data = X'123456';
+ SELECT rtreecheck('r2')!='ok';
+} {1}
+
+do_execsql_test 3.3 {
+ ROLLBACK;
+ UPDATE r2_node SET data = X'00001234';
+ SELECT rtreecheck('r2')!='ok';
+} {1}
+
+do_execsql_test 4.0 {
+ CREATE TABLE notanrtree(i);
+ SELECT rtreecheck('notanrtree');
+} {{Schema corrupt or not an rtree}}
+
+#-------------------------------------------------------------------------
+#
+reset_db
+db func set_int32 set_int32
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE r3 USING rtree_i32(id, x1, x2, y1, y2);
+ WITH x(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM x WHERE i<1000
+ )
+ INSERT INTO r3 SELECT i, i, i, i, i FROM x;
+}
+sqlite3_db_config db DEFENSIVE 0
+do_execsql_test 5.1 {
+ BEGIN;
+ UPDATE r3_node SET data = set_int32(data, 3, 5000);
+ UPDATE r3_node SET data = set_int32(data, 4, 5000);
+ SELECT rtreecheck('r3')=='ok'
+} 0
+do_execsql_test 5.2 {
+ ROLLBACK;
+ BEGIN;
+ UPDATE r3_node SET data = set_int32(data, 3, 0);
+ UPDATE r3_node SET data = set_int32(data, 4, 0);
+ SELECT rtreecheck('r3')=='ok'
+} 0
+
+finish_test
diff --git a/ext/rtree/rtreecirc.test b/ext/rtree/rtreecirc.test
new file mode 100644
index 0000000..d77ed04
--- /dev/null
+++ b/ext/rtree/rtreecirc.test
@@ -0,0 +1,66 @@
+# 2018 Dec 22
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the FTS5 module.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+set testprefix rtreecirc
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, y1, y2);
+ SELECT name FROM sqlite_master ORDER BY 1;
+} {
+ rt rt_node rt_parent rt_rowid
+}
+db_save_and_close
+
+foreach {tn schema sql} {
+ 1 {
+ CREATE TRIGGER tr1 AFTER INSERT ON rt_node BEGIN
+ SELECT * FROM rt;
+ END;
+ } {
+ INSERT INTO rt VALUES(1, 2, 3, 4, 5);
+ }
+ 2 {
+ CREATE TRIGGER tr1 AFTER INSERT ON rt_parent BEGIN
+ SELECT * FROM rt;
+ END;
+ } {
+ INSERT INTO rt VALUES(1, 2, 3, 4, 5);
+ }
+ 3 {
+ CREATE TRIGGER tr1 AFTER INSERT ON rt_rowid BEGIN
+ SELECT * FROM rt;
+ END;
+ } {
+ INSERT INTO rt VALUES(1, 2, 3, 4, 5);
+ }
+} {
+ db_restore_and_reopen
+ do_execsql_test 1.1.$tn.1 $schema
+ do_catchsql_test 1.1.$tn.2 $sql {1 {no such table: main.rt}}
+ db close
+}
+
+
+finish_test
+
diff --git a/ext/rtree/rtreeconnect.test b/ext/rtree/rtreeconnect.test
new file mode 100644
index 0000000..16d04d9
--- /dev/null
+++ b/ext/rtree/rtreeconnect.test
@@ -0,0 +1,56 @@
+# 2017 August 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the r-tree extension. Specifically,
+# the impact of an SQLITE_SCHEMA error within the rtree module xConnect
+# callback.
+#
+
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+set testprefix rtreeconnect
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE r1 USING rtree(id, x1, x2, y1, y2);
+ CREATE TABLE t1(id, x1, x2, y1, y2);
+ CREATE TABLE log(l);
+
+ CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN
+ INSERT INTO r1 VALUES(new.id, new.x1, new.x2, new.y1, new.y2);
+ INSERT INTO log VALUES('r1: ' || new.id);
+ END;
+}
+
+db close
+sqlite3 db test.db
+sqlite3 db2 test.db
+
+do_test 1.1 {
+ db eval { INSERT INTO log VALUES('startup'); }
+ db2 eval { CREATE TABLE newtable(x,y); }
+} {}
+
+do_execsql_test 1.2 {
+ INSERT INTO t1 VALUES(1, 2, 3, 4, 5);
+}
+
+db2 close
+db close
+
+finish_test
diff --git a/ext/rtree/rtreedoc.test b/ext/rtree/rtreedoc.test
new file mode 100644
index 0000000..b64faa2
--- /dev/null
+++ b/ext/rtree/rtreedoc.test
@@ -0,0 +1,1583 @@
+# 2021 September 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the r-tree extension.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+set testprefix rtreedoc
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+# This command returns the number of columns in table $tbl within the
+# database opened by database handle $db
+proc column_count {db tbl} {
+ set nCol 0
+ $db eval "PRAGMA table_info = $tbl" { incr nCol }
+ return $nCol
+}
+
+proc column_name_list {db tbl} {
+ set lCol [list]
+ $db eval "PRAGMA table_info = $tbl" {
+ lappend lCol $name
+ }
+ return $lCol
+}
+unset -nocomplain res
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 3 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-1
+
+# EVIDENCE-OF: R-15060-13876 A 1-dimensional R*Tree thus has 3 columns.
+do_execsql_test 1.1.1 { CREATE VIRTUAL TABLE rt1 USING rtree(id, x1,x2) }
+do_test 1.1.2 { column_count db rt1 } 3
+
+# EVIDENCE-OF: R-19353-19546 A 2-dimensional R*Tree has 5 columns.
+do_execsql_test 1.2.1 { CREATE VIRTUAL TABLE rt2 USING rtree(id,x1,x2, y1,y2) }
+do_test 1.2.2 { column_count db rt2 } 5
+
+# EVIDENCE-OF: R-13615-19528 A 3-dimensional R*Tree has 7 columns.
+do_execsql_test 1.3.1 {
+ CREATE VIRTUAL TABLE rt3 USING rtree(id, x1,x2, y1,y2, z1,z2)
+}
+do_test 1.3.2 { column_count db rt3 } 7
+
+# EVIDENCE-OF: R-53479-41922 A 4-dimensional R*Tree has 9 columns.
+do_execsql_test 1.4.1 {
+ CREATE VIRTUAL TABLE rt4 USING rtree(id, x1,x2, y1,y2, z1,z2, v1,v2)
+}
+do_test 1.4.2 { column_count db rt4 } 9
+
+# EVIDENCE-OF: R-13981-28768 And a 5-dimensional R*Tree has 11 columns.
+do_execsql_test 1.5.1 {
+ CREATE VIRTUAL TABLE rt5 USING rtree(id, x1,x2, y1,y2, z1,z2, v1,v2, w1,w2)
+}
+do_test 1.5.2 { column_count db rt5 } 11
+
+
+# Attempt to create r-tree tables with 6 and 7 dimensions.
+#
+# EVIDENCE-OF: R-61533-25862 The SQLite R*Tree implementation does not
+# support R*Trees wider than 5 dimensions.
+do_catchsql_test 2.1.1 {
+ CREATE VIRTUAL TABLE rt6 USING rtree(
+ id, x1,x2, y1,y2, z1,z2, v1,v2, w1,w2, a1,a2
+ )
+} {1 {Too many columns for an rtree table}}
+do_catchsql_test 2.1.2 {
+ CREATE VIRTUAL TABLE rt6 USING rtree(
+ id, x1,x2, y1,y2, z1,z2, v1,v2, w1,w2, a1,a2, b1, b2
+ )
+} {1 {Too many columns for an rtree table}}
+
+# Attempt to create r-tree tables with no columns, a single column, or
+# an even number of columns. This and the tests above establish that:
+#
+# EVIDENCE-OF: R-16717-50504 Each R*Tree index is a virtual table with
+# an odd number of columns between 3 and 11.
+foreach {tn cols err} {
+ 1 "" "Too few columns for an rtree table"
+ 2 "x" "Too few columns for an rtree table"
+ 3 "x,y" "Too few columns for an rtree table"
+ 4 "a,b,c,d" "Wrong number of columns for an rtree table"
+ 5 "a,b,c,d,e,f" "Wrong number of columns for an rtree table"
+ 6 "a,b,c,d,e,f,g,h" "Wrong number of columns for an rtree table"
+ 7 "a,b,c,d,e,f,g,h,i,j" "Wrong number of columns for an rtree table"
+ 8 "a,b,c,d,e,f,g,h,i,j,k,l" "Too many columns for an rtree table"
+} {
+ do_catchsql_test 3.$tn "
+ CREATE VIRTUAL TABLE xyz USING rtree($cols)
+ " [list 1 $err]
+}
+
+# EVIDENCE-OF: R-17874-21123 The first column of an SQLite R*Tree is
+# similar to an integer primary key column of a normal SQLite table.
+#
+# EVIDENCE-OF: R-46619-65417 The first column is always a 64-bit signed
+# integer primary key.
+#
+# EVIDENCE-OF: R-46866-24036 It may only store a 64-bit signed integer
+# value.
+#
+# EVIDENCE-OF: R-00250-64843 If an attempt is made to insert any other
+# non-integer value into this column, the r-tree module silently
+# converts it to an integer before writing it into the database.
+#
+do_execsql_test 4.0 { CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2) }
+foreach {tn val res} {
+ 1 10 10
+ 2 10.6 10
+ 3 10.99 10
+ 4 '123' 123
+ 5 X'313233' 123
+ 6 -10 -10
+ 7 9223372036854775807 9223372036854775807
+ 8 -9223372036854775808 -9223372036854775808
+ 9 '9223372036854775807' 9223372036854775807
+ 10 '-9223372036854775808' -9223372036854775808
+ 11 'hello+world' 0
+} {
+ do_execsql_test 4.$tn.1 "
+ DELETE FROM rt;
+ INSERT INTO rt VALUES($val, 10, 20);
+ "
+ do_execsql_test 4.$tn.2 {
+ SELECT typeof(id), id FROM rt
+ } [list integer $res]
+}
+
+# EVIDENCE-OF: R-15544-29079 Inserting a NULL value into this column
+# causes SQLite to automatically generate a new unique primary key
+# value.
+do_execsql_test 5.1 {
+ DELETE FROM rt;
+ INSERT INTO rt VALUES(100, 1, 2);
+ INSERT INTO rt VALUES(NULL, 1, 2);
+}
+do_execsql_test 5.2 { SELECT id FROM rt } {100 101}
+do_execsql_test 5.3 {
+ INSERT INTO rt VALUES(9223372036854775807, 1, 2);
+ INSERT INTO rt VALUES(NULL, 1, 2);
+}
+do_execsql_test 5.4 {
+ SELECT count(*) FROM rt;
+} 4
+do_execsql_test 5.5 {
+ SELECT id IN(100, 101, 9223372036854775807) FROM rt ORDER BY 1;
+} {0 1 1 1}
+
+
+# EVIDENCE-OF: R-64317-38978 The other columns are pairs, one pair per
+# dimension, containing the minimum and maximum values for that
+# dimension, respectively.
+#
+# Show this by observing that attempts to insert rows with max>min fail.
+#
+do_execsql_test 6.1 {
+ CREATE VIRTUAL TABLE rtF USING rtree(id, x1,x2, y1,y2);
+ CREATE VIRTUAL TABLE rtI USING rtree_i32(id, x1,x2, y1,y2, z1,z2);
+}
+foreach {tn x1 x2 y1 y2 ok} {
+ 1 10.3 20.1 30.9 40.2 1
+ 2 10.3 20.1 40.2 30.9 0
+ 3 10.3 30.9 20.1 40.2 1
+ 4 20.1 10.3 30.9 40.2 0
+} {
+ do_test 6.2.$tn {
+ catch { db eval { INSERT INTO rtF VALUES(NULL, $x1, $x2, $y1, $y2) } }
+ } [expr $ok==0]
+}
+foreach {tn x1 x2 y1 y2 z1 z2 ok} {
+ 1 10 20 30 40 50 60 1
+ 2 10 20 30 40 60 50 0
+ 3 10 20 30 50 40 60 1
+ 4 10 20 40 30 50 60 0
+ 5 10 30 20 40 50 60 1
+ 6 20 10 30 40 50 60 0
+} {
+ do_test 6.3.$tn {
+ catch { db eval { INSERT INTO rtI VALUES(NULL,$x1,$x2,$y1,$y2,$z1,$z2) } }
+ } [expr $ok==0]
+}
+
+# EVIDENCE-OF: R-08054-15429 The min/max-value pair columns are stored
+# as 32-bit floating point values for "rtree" virtual tables or as
+# 32-bit signed integers in "rtree_i32" virtual tables.
+#
+# Show this by showing that large values are rounded in ways consistent
+# with those two 32-bit types.
+do_execsql_test 7.1 {
+ DELETE FROM rtI;
+ INSERT INTO rtI VALUES(
+ 0, -2000000000, 2000000000, -5000000000, 5000000000,
+ -1000000000000, 10000000000000
+ );
+ SELECT * FROM rtI;
+} {
+ 0 -2000000000 2000000000 -705032704 705032704 727379968 1316134912
+}
+do_execsql_test 7.2 {
+ DELETE FROM rtF;
+ INSERT INTO rtF VALUES(
+ 0, -2000000000, 2000000000,
+ -1000000000000, 10000000000000
+ );
+ SELECT * FROM rtF;
+} {
+ 0 -2000000000.0 2000000000.0 -1000000126976.0 10000000876544.0
+}
+
+# EVIDENCE-OF: R-47371-54529 Unlike regular SQLite tables which can
+# store data in a variety of datatypes and formats, the R*Tree rigidly
+# enforce these storage types.
+#
+# EVIDENCE-OF: R-39153-14977 If any other type of value is inserted into
+# such a column, the r-tree module silently converts it to the required
+# type before writing the new record to the database.
+do_execsql_test 8.1 {
+ DELETE FROM rtI;
+ INSERT INTO rtI VALUES(
+ 1, 'hello world', X'616263', NULL, 44.5, 1000, 9999.9999
+ );
+ SELECT * FROM rtI;
+} {
+ 1 0 0 0 44 1000 9999
+}
+
+do_execsql_test 8.2 {
+ SELECT
+ typeof(x1), typeof(x2), typeof(y1), typeof(y2), typeof(z1), typeof(z2)
+ FROM rtI
+} {integer integer integer integer integer integer}
+
+do_execsql_test 8.3 {
+ DELETE FROM rtF;
+ INSERT INTO rtF VALUES(
+ 1, 'hello world', X'616263', NULL, 44
+ );
+ SELECT * FROM rtF;
+} {
+ 1 0.0 0.0 0.0 44.0
+}
+do_execsql_test 8.4 {
+ SELECT
+ typeof(x1), typeof(x2), typeof(y1), typeof(y2)
+ FROM rtF
+} {real real real real}
+
+
+
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 3.1 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-2
+reset_db
+
+foreach {tn name clist} {
+ 1 t1 "id x1 x2"
+ 2 t2 "id x1 x2 y1 y2 z1 z2"
+} {
+# EVIDENCE-OF: R-15142-18077 A new R*Tree index is created as follows:
+# CREATE VIRTUAL TABLE <name> USING rtree(<column-names>);
+ do_execsql_test 1.$tn.1 "
+ CREATE VIRTUAL TABLE $name USING rtree([join $clist ,])
+ "
+
+# EVIDENCE-OF: R-51698-09302 The <name> is the name your
+# application chooses for the R*Tree index and <column-names> is a
+# comma separated list of between 3 and 11 columns.
+ do_test 1.$tn.2 { column_name_list db $name } [list {*}$clist]
+
+# EVIDENCE-OF: R-50130-53472 The virtual <name> table creates
+# three shadow tables to actually store its content.
+ do_execsql_test 1.$tn.3 {
+ SELECT count(*) FROM sqlite_schema
+ } [expr 1+3]
+
+# EVIDENCE-OF: R-45256-35998 The names of these shadow tables are:
+# <name>_node <name>_rowid <name>_parent
+ do_execsql_test 1.$tn.4 {
+ SELECT name FROM sqlite_schema WHERE rootpage>0 ORDER BY 1
+ } [list ${name}_node ${name}_parent ${name}_rowid]
+
+ do_execsql_test 1.$tn.5 "DROP TABLE $name"
+}
+
+# EVIDENCE-OF: R-11241-54478 As an example, consider creating a
+# two-dimensional R*Tree index for use in spatial queries: CREATE
+# VIRTUAL TABLE demo_index USING rtree( id, -- Integer primary key minX,
+# maxX, -- Minimum and maximum X coordinate minY, maxY -- Minimum and
+# maximum Y coordinate );
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE demo_index USING rtree(
+ id, -- Integer primary key
+ minX, maxX, -- Minimum and maximum X coordinate
+ minY, maxY -- Minimum and maximum Y coordinate
+ );
+ INSERT INTO demo_index VALUES(1,2,3,4,5);
+ INSERT INTO demo_index VALUES(6,7,8,9,10);
+}
+
+# EVIDENCE-OF: R-02287-33529 The shadow tables are ordinary SQLite data
+# tables.
+#
+# Ordinary tables. With ordinary sqlite_schema entries.
+do_execsql_test 2.1 {
+ SELECT type, name, sql FROM sqlite_schema WHERE sql NOT LIKE '%virtual%'
+} {
+ table demo_index_rowid
+ {CREATE TABLE "demo_index_rowid"(rowid INTEGER PRIMARY KEY,nodeno)}
+ table demo_index_node
+ {CREATE TABLE "demo_index_node"(nodeno INTEGER PRIMARY KEY,data)}
+ table demo_index_parent
+ {CREATE TABLE "demo_index_parent"(nodeno INTEGER PRIMARY KEY,parentnode)}
+}
+
+# EVIDENCE-OF: R-10863-13089 You can query them directly if you like,
+# though this unlikely to reveal anything particularly useful.
+#
+# Querying:
+do_execsql_test 2.2 {
+ SELECT count(*) FROM demo_index_node;
+ SELECT count(*) FROM demo_index_rowid;
+ SELECT count(*) FROM demo_index_parent;
+} {1 2 0}
+
+# EVIDENCE-OF: R-05650-46070 And you can UPDATE, DELETE, INSERT or even
+# DROP the shadow tables, though doing so will corrupt your R*Tree
+# index.
+do_execsql_test 2.3 {
+ DELETE FROM demo_index_rowid;
+ INSERT INTO demo_index_parent VALUES(2, 3);
+ UPDATE demo_index_node SET data = 'hello world'
+}
+do_catchsql_test 2.4 {
+ SELECT * FROM demo_index WHERE minX>10 AND maxX<30
+} {1 {database disk image is malformed}}
+do_execsql_test 2.5 {
+ DROP TABLE demo_index_rowid
+}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 3.1.1 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-3
+reset_db
+
+# EVIDENCE-OF: R-44253-50720 In the argments to "rtree" in the CREATE
+# VIRTUAL TABLE statement, the names of the columns are taken from the
+# first token of each argument. All subsequent tokens within each
+# argument are silently ignored.
+#
+foreach {tn cols lCol} {
+ 1 {(id TEXT, x1 TEXT, x2 TEXT, y1 TEXT, y2 TEXT)} {id x1 x2 y1 y2}
+ 2 {(id TEXT, x1 UNIQUE, x2 TEXT, y1 NOT NULL, y2 TEXT)} {id x1 x2 y1 y2}
+ 3 {(id, x1 DEFAULT 4, x2 TEXT, y1 NOT NULL, y2 TEXT)} {id x1 x2 y1 y2}
+} {
+ do_execsql_test 1.$tn.1 " CREATE VIRTUAL TABLE abc USING rtree $cols "
+ do_test 1.$tn.2 { column_name_list db abc } $lCol
+
+# EVIDENCE-OF: R-52032-06717 This means, for example, that if you try to
+# give a column a type affinity or add a constraint such as UNIQUE or
+# NOT NULL or DEFAULT to a column, those extra tokens are accepted as
+# valid, but they do not change the behavior of the rtree.
+
+ # Show there are no UNIQUE constraints
+ do_execsql_test 1.$tn.3 {
+ INSERT INTO abc VALUES(1, 10.0, 20.0, 10.0, 20.0);
+ INSERT INTO abc VALUES(2, 10.0, 20.0, 10.0, 20.0);
+ }
+
+ # Show the default values have not been modified
+ do_execsql_test 1.$tn.4 {
+ INSERT INTO abc DEFAULT VALUES;
+ SELECT * FROM abc WHERE rowid NOT IN (1,2)
+ } {3 0.0 0.0 0.0 0.0}
+
+ # Show that there are no NOT NULL constraints
+ do_execsql_test 1.$tn.5 {
+ INSERT INTO abc VALUES(NULL, NULL, NULL, NULL, NULL);
+ SELECT * FROM abc WHERE rowid NOT IN (1,2,3)
+ } {4 0.0 0.0 0.0 0.0}
+
+# EVIDENCE-OF: R-06893-30579 In an RTREE virtual table, the first column
+# always has a type affinity of INTEGER and all other data columns have
+# a type affinity of REAL.
+ do_execsql_test 1.$tn.5 {
+ INSERT INTO abc VALUES('5', '5', '5', '5', '5');
+ SELECT * FROM abc WHERE rowid NOT IN (1,2,3,4)
+ } {5 5.0 5.0 5.0 5.0}
+ do_execsql_test 1.$tn.6 {
+ SELECT type FROM pragma_table_info('abc') ORDER BY cid
+ } {INT REAL REAL REAL REAL}
+
+ do_execsql_test 1.$tn.7 " CREATE VIRTUAL TABLE abc2 USING rtree_i32 $cols "
+
+# EVIDENCE-OF: R-06224-52418 In an RTREE_I32 virtual table, all columns
+# have type affinity of INTEGER.
+ do_execsql_test 1.$tn.8 {
+ INSERT INTO abc2 VALUES('6.0', '6.0', '6.0', '6.0', '6.0');
+ SELECT * FROM abc2
+ } {6 6 6 6 6}
+ do_execsql_test 1.$tn.9 {
+ SELECT type FROM pragma_table_info('abc2') ORDER BY cid
+ } {INT INT INT INT INT}
+
+
+ do_execsql_test 1.$tn.10 {
+ DROP TABLE abc;
+ DROP TABLE abc2;
+ }
+}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 3.2 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-4
+reset_db
+
+# EVIDENCE-OF: R-36195-31555 The usual INSERT, UPDATE, and DELETE
+# commands work on an R*Tree index just like on regular tables.
+#
+# Create a regular table and an rtree table. Perform INSERT, UPDATE and
+# DELETE operations, then observe that the contents of the two tables
+# are identical.
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2);
+ CREATE TABLE t1(id INTEGER PRIMARY KEY, x1 REAL, x2 REAL);
+}
+foreach {tn sql} {
+ 1 "INSERT INTO %TBL% VALUES(5, 11,12)"
+ 2 "INSERT INTO %TBL% VALUES(11, -11,14.5)"
+ 3 "UPDATE %TBL% SET x1=-99 WHERE id=11"
+ 4 "DELETE FROM %TBL% WHERE x2=14.5"
+ 5 "DELETE FROM %TBL%"
+} {
+ set sql1 [string map {%TBL% rt} $sql]
+ set sql2 [string map {%TBL% t1} $sql]
+ do_execsql_test 1.$tn.0 $sql1
+ do_execsql_test 1.$tn.1 $sql2
+
+ set data1 [execsql {SELECT * FROM rt ORDER BY 1}]
+ set data2 [execsql {SELECT * FROM t1 ORDER BY 1}]
+
+ set res [expr {$data1==$data2}]
+ do_test 1.$tn.2 {set res} 1
+}
+
+# EVIDENCE-OF: R-56987-45305
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE demo_index USING rtree(
+ id, -- Integer primary key
+ minX, maxX, -- Minimum and maximum X coordinate
+ minY, maxY -- Minimum and maximum Y coordinate
+ );
+
+ INSERT INTO demo_index VALUES
+ (28215, -80.781227, -80.604706, 35.208813, 35.297367),
+ (28216, -80.957283, -80.840599, 35.235920, 35.367825),
+ (28217, -80.960869, -80.869431, 35.133682, 35.208233),
+ (28226, -80.878983, -80.778275, 35.060287, 35.154446),
+ (28227, -80.745544, -80.555382, 35.130215, 35.236916),
+ (28244, -80.844208, -80.841988, 35.223728, 35.225471),
+ (28262, -80.809074, -80.682938, 35.276207, 35.377747),
+ (28269, -80.851471, -80.735718, 35.272560, 35.407925),
+ (28270, -80.794983, -80.728966, 35.059872, 35.161823),
+ (28273, -80.994766, -80.875259, 35.074734, 35.172836),
+ (28277, -80.876793, -80.767586, 35.001709, 35.101063),
+ (28278, -81.058029, -80.956375, 35.044701, 35.223812),
+ (28280, -80.844208, -80.841972, 35.225468, 35.227203),
+ (28282, -80.846382, -80.844193, 35.223972, 35.225655);
+}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 3.3 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-5
+
+do_execsql_test 1.0 {
+ INSERT INTO demo_index
+ SELECT NULL, minX, maxX, minY+0.2, maxY+0.2 FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX+0.2, maxX+0.2, minY, maxY FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX, maxX, minY+0.4, maxY+0.4 FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX+0.4, maxX+0.4, minY, maxY FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX, maxX, minY+0.8, maxY+0.8 FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX+0.8, maxX+0.8, minY, maxY FROM demo_index;
+
+ SELECT count(*) FROM demo_index;
+} {896}
+
+proc do_vmstep_test {tn sql expr} {
+ execsql $sql
+ set step [db status vmstep]
+ do_test $tn.$step "expr {[subst $expr]}" 1
+}
+
+# EVIDENCE-OF: R-45880-07724 Any valid query will work against an R*Tree
+# index.
+do_execsql_test 1.1.0 {
+ CREATE TABLE demo_tbl AS SELECT * FROM demo_index;
+}
+foreach {tn sql} {
+ 1 {SELECT * FROM %TBL% ORDER BY 1}
+ 2 {SELECT max(minX) FROM %TBL% ORDER BY 1}
+ 3 {SELECT max(minX) FROM %TBL% GROUP BY round(minY) ORDER BY 1}
+} {
+ set sql1 [string map {%TBL% demo_index} $sql]
+ set sql2 [string map {%TBL% demo_tbl} $sql]
+
+ do_execsql_test 1.1.$tn $sql1 [execsql $sql2]
+}
+
+# EVIDENCE-OF: R-60814-18273 The R*Tree implementation just makes some
+# kinds of queries especially efficient.
+#
+# The second query is more efficient than the first.
+do_vmstep_test 1.2.1 {SELECT * FROM demo_index WHERE +rowid=28269} {$step>2000}
+do_vmstep_test 1.2.2 {SELECT * FROM demo_index WHERE rowid=28269} {$step<100}
+
+# EVIDENCE-OF: R-37800-50174 Queries against the primary key are
+# efficient: SELECT * FROM demo_index WHERE id=28269;
+do_vmstep_test 2.2 { SELECT * FROM demo_index WHERE id=28269 } {$step < 100}
+
+# EVIDENCE-OF: R-35847-18866 The big reason for using an R*Tree is so
+# that you can efficiently do range queries against the coordinate
+# ranges.
+#
+# EVIDENCE-OF: R-49927-54202
+do_vmstep_test 2.3 {
+ SELECT id FROM demo_index
+ WHERE minX<=-80.77470 AND maxX>=-80.77470
+ AND minY<=35.37785 AND maxY>=35.37785;
+} {$step < 100}
+
+# EVIDENCE-OF: R-12823-37176 The query above will quickly locate all
+# zipcodes that contain the SQLite main office in their bounding box,
+# even if the R*Tree contains many entries.
+#
+do_execsql_test 2.4 {
+ SELECT id FROM demo_index
+ WHERE minX<=-80.77470 AND maxX>=-80.77470
+ AND minY<=35.37785 AND maxY>=35.37785;
+} {
+ 28322 28269
+}
+
+# EVIDENCE-OF: R-07351-00257 For example, to find all zipcode bounding
+# boxes that overlap with the 28269 zipcode: SELECT A.id FROM demo_index
+# AS A, demo_index AS B WHERE A.maxX>=B.minX AND A.minX<=B.maxX
+# AND A.maxY>=B.minY AND A.minY<=B.maxY AND B.id=28269;
+#
+# Also check that it is efficient
+#
+# EVIDENCE-OF: R-39094-01937 This second query will find both 28269
+# entry (since every bounding box overlaps with itself) and also other
+# zipcode that is close enough to 28269 that their bounding boxes
+# overlap.
+#
+# 28269 is there in the result.
+#
+do_vmstep_test 2.5.1 {
+ SELECT A.id FROM demo_index AS A, demo_index AS B
+ WHERE A.maxX>=B.minX AND A.minX<=B.maxX
+ AND A.maxY>=B.minY AND A.minY<=B.maxY
+ AND B.id=28269
+} {$step < 100}
+do_execsql_test 2.5.2 {
+ SELECT A.id FROM demo_index AS A, demo_index AS B
+ WHERE A.maxX>=B.minX AND A.minX<=B.maxX
+ AND A.maxY>=B.minY AND A.minY<=B.maxY
+ AND B.id=28269;
+} {
+ 28293 28216 28322 28286 28269
+ 28215 28336 28262 28291 28320
+ 28313 28298 28287
+}
+
+# EVIDENCE-OF: R-02723-34107 Note that it is not necessary for all
+# coordinates in an R*Tree index to be constrained in order for the
+# index search to be efficient.
+#
+# EVIDENCE-OF: R-22490-27246 One might, for example, want to query all
+# objects that overlap with the 35th parallel: SELECT id FROM demo_index
+# WHERE maxY>=35.0 AND minY<=35.0;
+do_vmstep_test 2.6.1 {
+ SELECT id FROM demo_index
+ WHERE maxY>=35.0 AND minY<=35.0;
+} {$step < 100}
+do_execsql_test 2.6.2 {
+ SELECT id FROM demo_index
+ WHERE maxY>=35.0 AND minY<=35.0;
+} {}
+
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 3.4 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-6
+reset_db
+
+# EVIDENCE-OF: R-08327-00674 By default, coordinates are stored in an
+# R*Tree using 32-bit floating point values.
+#
+# EVIDENCE-OF: R-22000-53613 The default virtual table ("rtree") stores
+# coordinates as single-precision (4-byte) floating point numbers.
+#
+# Show this by showing that rounding is consistent with 32-bit float
+# rounding.
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE rt USING rtree(id, a,b);
+}
+do_execsql_test 1.1 {
+ INSERT INTO rt VALUES(14, -1000000000000, 1000000000000);
+ SELECT * FROM rt;
+} {14 -1000000126976.0 1000000126976.0}
+
+# EVIDENCE-OF: R-39127-51288 When a coordinate cannot be exactly
+# represented by a 32-bit floating point number, the lower-bound
+# coordinates are rounded down and the upper-bound coordinates are
+# rounded up.
+foreach {tn val} {
+ 1 100000000000
+ 2 200000000000
+ 3 300000000000
+ 4 400000000000
+
+ 5 -100000000000
+ 6 -200000000000
+ 7 -300000000000
+ 8 -400000000000
+} {
+ set val [expr $val]
+ do_execsql_test 2.$tn.0 {DELETE FROM rt}
+ do_execsql_test 2.$tn.1 {INSERT INTO rt VALUES(23, $val, $val)}
+ do_execsql_test 2.$tn.2 {
+ SELECT $val>=a, $val<=b, a!=b FROM rt
+ } {1 1 1}
+}
+
+do_execsql_test 3.0 {
+ DROP TABLE rt;
+ CREATE VIRTUAL TABLE rt USING rtree(id, x1,x2, y1,y2);
+}
+
+# EVIDENCE-OF: R-45870-62834 Thus, bounding boxes might be slightly
+# larger than specified, but will never be any smaller.
+foreach {tn x1 x2 y1 y2} {
+ 1 100000000000 200000000000 300000000000 400000000000
+} {
+ set val [expr $val]
+ do_execsql_test 3.$tn.0 {DELETE FROM rt}
+ do_execsql_test 3.$tn.1 {INSERT INTO rt VALUES(23, $x1, $x2, $y1, $y2)}
+ do_execsql_test 3.$tn.2 {
+ SELECT (x2-x1)*(y2-y1) >= ($x2-$x1)*($y2-$y1) FROM rt
+ } {1}
+}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 3.5 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-7
+reset_db
+
+# EVIDENCE-OF: R-55979-39402 It is the nature of the Guttman R-Tree
+# algorithm that any write might radically restructure the tree, and in
+# the process change the scan order of the nodes.
+#
+# In the test below, the INSERT marked "THIS INSERT!!" does not affect
+# the results of queries with an ORDER BY, but does affect the results
+# of one without an ORDER BY. Therefore the INSERT changed the scan
+# order.
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE rt USING rtree(id, minX, maxX);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<51
+ )
+ INSERT INTO rt SELECT NULL, i%10, (i%10)+5 FROM s
+}
+do_execsql_test 1.1 { SELECT count(*) FROM rt_node } 1
+do_test 1.2 {
+ set res1 [db eval {SELECT * FROM rt WHERE maxX < 30}]
+ set res1o [db eval {SELECT * FROM rt WHERE maxX < 30 ORDER BY +id}]
+
+ db eval { INSERT INTO rt VALUES(NULL, 50, 50) } ;# THIS INSERT!!
+
+ set res2 [db eval {SELECT * FROM rt WHERE maxX < 30}]
+ set res2o [db eval {SELECT * FROM rt WHERE maxX < 30 ORDER BY +id}]
+ list [expr {$res1==$res2}] [expr {$res1o==$res2o}]
+} {0 1}
+
+do_execsql_test 1.3 { SELECT count(*) FROM rt_node } 3
+
+# EVIDENCE-OF: R-00683-48865 For this reason, it is not generally
+# possible to modify the R-Tree in the middle of a query of the R-Tree.
+# Attempts to do so will fail with a SQLITE_LOCKED "database table is
+# locked" error.
+#
+# SQLITE_LOCKED==6
+#
+do_test 1.4 {
+ set nCnt 3
+ db eval { SELECT * FROM rt WHERE minX>0 AND maxX<12 } {
+ incr nCnt -1
+ if {$nCnt==0} {
+ set rc [catch {db eval {
+ INSERT INTO rt VALUES(NULL, 51, 51);
+ }} msg]
+ set errorcode [db errorcode]
+ break
+ }
+ }
+
+ list $errorcode $rc $msg
+} {6 1 {database table is locked}}
+
+# EVIDENCE-OF: R-19740-29710 So, for example, suppose an application
+# runs one query against an R-Tree like this: SELECT id FROM demo_index
+# WHERE maxY>=35.0 AND minY<=35.0; Then for each "id" value
+# returned, suppose the application creates an UPDATE statement like the
+# following and binds the "id" value returned against the "?1"
+# parameter: UPDATE demo_index SET maxY=maxY+0.5 WHERE id=?1;
+#
+# EVIDENCE-OF: R-52919-32711 Then the UPDATE might fail with an
+# SQLITE_LOCKED error.
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE demo_index USING rtree(
+ id, -- Integer primary key
+ minX, maxX, -- Minimum and maximum X coordinate
+ minY, maxY -- Minimum and maximum Y coordinate
+ );
+ INSERT INTO demo_index VALUES
+ (28215, -80.781227, -80.604706, 35.208813, 35.297367),
+ (28216, -80.957283, -80.840599, 35.235920, 35.367825),
+ (28217, -80.960869, -80.869431, 35.133682, 35.208233),
+ (28226, -80.878983, -80.778275, 35.060287, 35.154446);
+}
+do_test 2.1 {
+ db eval { SELECT id FROM demo_index WHERE maxY>=35.0 AND minY<=35.0 } {
+ set rc [catch {
+ db eval { UPDATE demo_index SET maxY=maxY+0.5 WHERE id=$id }
+ } msg]
+ set errorcode [db errorcode]
+ break
+ }
+ list $errorcode $rc $msg
+} {6 1 {database table is locked}}
+
+# EVIDENCE-OF: R-32604-49843 Ordinary tables in SQLite are able to read
+# and write at the same time.
+#
+do_execsql_test 3.0 {
+ CREATE TABLE x1(a INTEGER PRIMARY KEY, b, c);
+ INSERT INTO x1 VALUES(1, 1, 1);
+ INSERT INTO x1 VALUES(2, 2, 2);
+ INSERT INTO x1 VALUES(3, 3, 3);
+ INSERT INTO x1 VALUES(4, 4, 4);
+}
+do_test 3.1 {
+ unset -nocomplain res
+ set res [list]
+ db eval { SELECT * FROM x1 } {
+ lappend res $a $b $c
+ switch -- $a {
+ 1 {
+ db eval { INSERT INTO x1 VALUES(5, 5, 5) }
+ }
+ 2 {
+ db eval { UPDATE x1 SET c=20 WHERE a=2 }
+ }
+ 3 {
+ db eval { DELETE FROM x1 WHERE c IN (3,4) }
+ }
+ }
+ }
+ set res
+} {1 1 1 2 2 2 3 3 3 5 5 5}
+do_execsql_test 3.2 {
+ SELECT * FROM x1
+} {1 1 1 2 2 20 5 5 5}
+
+# EVIDENCE-OF: R-06177-00576 And R-Tree can appear to read and write at
+# the same time in some circumstances, if it can figure out how to
+# reliably run the query to completion before starting the update.
+#
+# In 8.2, it can, it 8.1, it cannot.
+do_test 8.1 {
+ db eval { SELECT * FROM rt } {
+ set rc [catch { db eval { INSERT INTO rt VALUES(53,53,53) } } msg]
+ break;
+ }
+ list $rc $msg
+} {1 {database table is locked}}
+do_test 8.2 {
+ db eval { SELECT * FROM rt ORDER BY +id } {
+ set rc [catch { db eval { INSERT INTO rt VALUES(53,53,53) } } msg]
+ break
+ }
+ list $rc $msg
+} {0 {}}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 4 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-8
+reset_db
+
+# EVIDENCE-OF: R-21062-30088 For the example above, one might create an
+# auxiliary table as follows: CREATE TABLE demo_data( id INTEGER PRIMARY
+# KEY, -- primary key objname TEXT, -- name of the object objtype TEXT,
+# -- object type boundary BLOB -- detailed boundary of object );
+#
+# One might.
+#
+do_execsql_test 1.0 {
+ CREATE TABLE demo_data(
+ id INTEGER PRIMARY KEY, -- primary key
+ objname TEXT, -- name of the object
+ objtype TEXT, -- object type
+ boundary BLOB -- detailed boundary of object
+ );
+}
+
+do_execsql_test 1.1 {
+ CREATE VIRTUAL TABLE demo_index USING rtree(
+ id, -- Integer primary key
+ minX, maxX, -- Minimum and maximum X coordinate
+ minY, maxY -- Minimum and maximum Y coordinate
+ );
+
+ INSERT INTO demo_index VALUES
+ (28215, -80.781227, -80.604706, 35.208813, 35.297367),
+ (28216, -80.957283, -80.840599, 35.235920, 35.367825),
+ (28217, -80.960869, -80.869431, 35.133682, 35.208233),
+ (28226, -80.878983, -80.778275, 35.060287, 35.154446),
+ (28227, -80.745544, -80.555382, 35.130215, 35.236916),
+ (28244, -80.844208, -80.841988, 35.223728, 35.225471),
+ (28262, -80.809074, -80.682938, 35.276207, 35.377747),
+ (28269, -80.851471, -80.735718, 35.272560, 35.407925),
+ (28270, -80.794983, -80.728966, 35.059872, 35.161823),
+ (28273, -80.994766, -80.875259, 35.074734, 35.172836),
+ (28277, -80.876793, -80.767586, 35.001709, 35.101063),
+ (28278, -81.058029, -80.956375, 35.044701, 35.223812),
+ (28280, -80.844208, -80.841972, 35.225468, 35.227203),
+ (28282, -80.846382, -80.844193, 35.223972, 35.225655);
+
+ INSERT INTO demo_index
+ SELECT NULL, minX, maxX, minY+0.2, maxY+0.2 FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX+0.2, maxX+0.2, minY, maxY FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX, maxX, minY+0.4, maxY+0.4 FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX+0.4, maxX+0.4, minY, maxY FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX, maxX, minY+0.8, maxY+0.8 FROM demo_index;
+ INSERT INTO demo_index
+ SELECT NULL, minX+0.8, maxX+0.8, minY, maxY FROM demo_index;
+
+ INSERT INTO demo_data(id) SELECT id FROM demo_index;
+
+ SELECT count(*) FROM demo_index;
+} {896}
+
+set ::contained_in 0
+proc contained_in {args} {incr ::contained_in ; return 0}
+db func contained_in contained_in
+
+# EVIDENCE-OF: R-32671-43888 Then an efficient way to find the specific
+# ZIP code for the main SQLite office would be to run a query like this:
+# SELECT objname FROM demo_data, demo_index WHERE
+# demo_data.id=demo_index.id AND contained_in(demo_data.boundary,
+# 35.37785, -80.77470) AND minX<=-80.77470 AND maxX>=-80.77470 AND
+# minY<=35.37785 AND maxY>=35.37785;
+do_vmstep_test 1.2 {
+ SELECT objname FROM demo_data, demo_index
+ WHERE demo_data.id=demo_index.id
+ AND contained_in(demo_data.boundary, 35.37785, -80.77470)
+ AND minX<=-80.77470 AND maxX>=-80.77470
+ AND minY<=35.37785 AND maxY>=35.37785;
+} {$step<100}
+set ::contained_in1 $::contained_in
+
+# EVIDENCE-OF: R-32761-23915 One would get the same answer without the
+# use of the R*Tree index using the following simpler query: SELECT
+# objname FROM demo_data WHERE contained_in(demo_data.boundary,
+# 35.37785, -80.77470);
+set ::contained_in 0
+do_vmstep_test 1.3 {
+ SELECT objname FROM demo_data
+ WHERE contained_in(demo_data.boundary, 35.37785, -80.77470);
+} {$step>3200}
+
+# EVIDENCE-OF: R-40261-32799 The problem with this latter query is that
+# it must apply the contained_in() function to all entries in the
+# demo_data table.
+#
+# 896 of them, IIRC.
+do_test 1.4 {
+ set ::contained_in
+} 896
+
+# EVIDENCE-OF: R-24212-52761 The use of the R*Tree in the penultimate
+# query reduces the number of calls to contained_in() function to a
+# small subset of the entire table.
+#
+# 2 is a small subset of 896.
+#
+# EVIDENCE-OF: R-39057-63901 The R*Tree index did not find the exact
+# answer itself, it merely limited the search space.
+#
+# contained_in() filtered out those 2 rows.
+do_test 1.5 {
+ set ::contained_in1
+} {2}
+
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 4.1 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-9
+reset_db
+
+# EVIDENCE-OF: R-46566-43213 Beginning with SQLite version 3.24.0
+# (2018-06-04), r-tree tables can have auxiliary columns that store
+# arbitrary data. Auxiliary columns can be used in place of secondary
+# tables such as "demo_data".
+#
+# EVIDENCE-OF: R-41287-48160 Auxiliary columns are marked with a "+"
+# symbol before the column name.
+#
+# This interface cannot conveniently be used to prove anything about
+# versions of SQLite prior to 3.24.0.
+#
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE rta USING rtree(
+ id, u1,u2, v1,v2, +aux
+ );
+
+ INSERT INTO rta(aux) VALUES(NULL);
+ INSERT INTO rta(aux) VALUES(45);
+ INSERT INTO rta(aux) VALUES(22.3);
+ INSERT INTO rta(aux) VALUES('hello');
+ INSERT INTO rta(aux) VALUES(X'ABCD');
+
+ SELECT typeof(aux), quote(aux) FROM rta;
+} {
+ null NULL
+ integer 45
+ real 22.3
+ text 'hello'
+ blob X'ABCD'
+}
+
+# EVIDENCE-OF: R-30514-26093 Auxiliary columns must come after all of
+# the coordinate boundary columns.
+foreach {tn cols} {
+ 1 "id x1,x2, +extra, y1,y2"
+ 2 "extra, +id x1,x2, y1,y2"
+ 3 "id, x1,+x2, extra, y1,y2"
+} {
+ do_catchsql_test 2.$tn "
+ CREATE VIRTUAL TABLE rrr USING rtree($cols)
+ " {1 {Auxiliary rtree columns must be last}}
+}
+do_catchsql_test 3.0 {
+ CREATE VIRTUAL TABLE rrr USING rtree(+id, extra, x1, x2);
+} {1 {near "+": syntax error}}
+
+# EVIDENCE-OF: R-01280-03635 An RTREE table can have no more than 100
+# columns total. In other words, the count of columns including the
+# integer primary key column, the coordinate boundary columns, and all
+# auxiliary columns must be 100 or less.
+do_catchsql_test 3.1 {
+ CREATE VIRTUAL TABLE r1 USING rtree(intid, u1,u2,
+ +c00, +c01, +c02, +c03, +c04, +c05, +c06, +c07, +c08, +c09,
+ +c10, +c11, +c12, +c13, +c14, +c15, +c16, +c17, +c18, +c19,
+ +c20, +c21, +c22, +c23, +c24, +c25, +c26, +c27, +c28, +c29,
+ +c30, +c31, +c32, +c33, +c34, +c35, +c36, +c37, +c38, +c39,
+ +c40, +c41, +c42, +c43, +c44, +c45, +c46, +c47, +c48, +c49,
+ +c50, +c51, +c52, +c53, +c54, +c55, +c56, +c57, +c58, +c59,
+ +c60, +c61, +c62, +c63, +c64, +c65, +c66, +c67, +c68, +c69,
+ +c70, +c71, +c72, +c73, +c74, +c75, +c76, +c77, +c78, +c79,
+ +c80, +c81, +c82, +c83, +c84, +c85, +c86, +c87, +c88, +c89,
+ +c90, +c91, +c92, +c93, +c94, +c95, +c96
+ );
+} {0 {}}
+do_catchsql_test 3.2 {
+ DROP TABLE r1;
+ CREATE VIRTUAL TABLE r1 USING rtree(intid, u1,u2,
+ +c00, +c01, +c02, +c03, +c04, +c05, +c06, +c07, +c08, +c09,
+ +c10, +c11, +c12, +c13, +c14, +c15, +c16, +c17, +c18, +c19,
+ +c20, +c21, +c22, +c23, +c24, +c25, +c26, +c27, +c28, +c29,
+ +c30, +c31, +c32, +c33, +c34, +c35, +c36, +c37, +c38, +c39,
+ +c40, +c41, +c42, +c43, +c44, +c45, +c46, +c47, +c48, +c49,
+ +c50, +c51, +c52, +c53, +c54, +c55, +c56, +c57, +c58, +c59,
+ +c60, +c61, +c62, +c63, +c64, +c65, +c66, +c67, +c68, +c69,
+ +c70, +c71, +c72, +c73, +c74, +c75, +c76, +c77, +c78, +c79,
+ +c80, +c81, +c82, +c83, +c84, +c85, +c86, +c87, +c88, +c89,
+ +c90, +c91, +c92, +c93, +c94, +c95, +c96, +c97
+ );
+} {1 {Too many columns for an rtree table}}
+do_catchsql_test 3.3 {
+ CREATE VIRTUAL TABLE r1 USING rtree(intid, u1,u2, v1,v2,
+ +c00, +c01, +c02, +c03, +c04, +c05, +c06, +c07, +c08, +c09,
+ +c10, +c11, +c12, +c13, +c14, +c15, +c16, +c17, +c18, +c19,
+ +c20, +c21, +c22, +c23, +c24, +c25, +c26, +c27, +c28, +c29,
+ +c30, +c31, +c32, +c33, +c34, +c35, +c36, +c37, +c38, +c39,
+ +c40, +c41, +c42, +c43, +c44, +c45, +c46, +c47, +c48, +c49,
+ +c50, +c51, +c52, +c53, +c54, +c55, +c56, +c57, +c58, +c59,
+ +c60, +c61, +c62, +c63, +c64, +c65, +c66, +c67, +c68, +c69,
+ +c70, +c71, +c72, +c73, +c74, +c75, +c76, +c77, +c78, +c79,
+ +c80, +c81, +c82, +c83, +c84, +c85, +c86, +c87, +c88, +c89,
+ +c90, +c91, +c92, +c93, +c94,
+ );
+} {0 {}}
+do_catchsql_test 3.4 {
+ DROP TABLE r1;
+ CREATE VIRTUAL TABLE r1 USING rtree(intid, u1,u2, v1,v2,
+ +c00, +c01, +c02, +c03, +c04, +c05, +c06, +c07, +c08, +c09,
+ +c10, +c11, +c12, +c13, +c14, +c15, +c16, +c17, +c18, +c19,
+ +c20, +c21, +c22, +c23, +c24, +c25, +c26, +c27, +c28, +c29,
+ +c30, +c31, +c32, +c33, +c34, +c35, +c36, +c37, +c38, +c39,
+ +c40, +c41, +c42, +c43, +c44, +c45, +c46, +c47, +c48, +c49,
+ +c50, +c51, +c52, +c53, +c54, +c55, +c56, +c57, +c58, +c59,
+ +c60, +c61, +c62, +c63, +c64, +c65, +c66, +c67, +c68, +c69,
+ +c70, +c71, +c72, +c73, +c74, +c75, +c76, +c77, +c78, +c79,
+ +c80, +c81, +c82, +c83, +c84, +c85, +c86, +c87, +c88, +c89,
+ +c90, +c91, +c92, +c93, +c94, +c95,
+ );
+} {1 {Too many columns for an rtree table}}
+
+# EVIDENCE-OF: R-05552-15084
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE demo_index2 USING rtree(
+ id, -- Integer primary key
+ minX, maxX, -- Minimum and maximum X coordinate
+ minY, maxY, -- Minimum and maximum Y coordinate
+ +objname TEXT, -- name of the object
+ +objtype TEXT, -- object type
+ +boundary BLOB -- detailed boundary of object
+ );
+}
+do_execsql_test 4.1 {
+ CREATE VIRTUAL TABLE demo_index USING rtree(
+ id, -- Integer primary key
+ minX, maxX, -- Minimum and maximum X coordinate
+ minY, maxY -- Minimum and maximum Y coordinate
+ );
+ CREATE TABLE demo_data(
+ id INTEGER PRIMARY KEY, -- primary key
+ objname TEXT, -- name of the object
+ objtype TEXT, -- object type
+ boundary BLOB -- detailed boundary of object
+ );
+
+ INSERT INTO demo_index2(id) VALUES(1);
+ INSERT INTO demo_index(id) VALUES(1);
+ INSERT INTO demo_data(id) VALUES(1);
+}
+do_test 4.2 {
+ catch { array unset R }
+ db eval {SELECT * FROM demo_index2} R { set r1 [array names R] }
+ catch { array unset R }
+ db eval {SELECT * FROM demo_index NATURAL JOIN demo_data } R {
+ set r2 [array names R]
+ }
+ expr {$r1==$r2}
+} {1}
+
+# EVIDENCE-OF: R-26099-32169 SELECT objname FROM demo_index2 WHERE
+# contained_in(boundary, 35.37785, -80.77470) AND minX<=-80.77470 AND
+# maxX>=-80.77470 AND minY<=35.37785 AND maxY>=35.37785;
+do_execsql_test 4.3.1 {
+ DELETE FROM demo_index2;
+ INSERT INTO demo_index2(id,minX,maxX,minY,maxY) VALUES
+ (28215, -80.781227, -80.604706, 35.208813, 35.297367),
+ (28216, -80.957283, -80.840599, 35.235920, 35.367825),
+ (28217, -80.960869, -80.869431, 35.133682, 35.208233),
+ (28226, -80.878983, -80.778275, 35.060287, 35.154446),
+ (28227, -80.745544, -80.555382, 35.130215, 35.236916),
+ (28244, -80.844208, -80.841988, 35.223728, 35.225471),
+ (28262, -80.809074, -80.682938, 35.276207, 35.377747),
+ (28269, -80.851471, -80.735718, 35.272560, 35.407925),
+ (28270, -80.794983, -80.728966, 35.059872, 35.161823),
+ (28273, -80.994766, -80.875259, 35.074734, 35.172836),
+ (28277, -80.876793, -80.767586, 35.001709, 35.101063),
+ (28278, -81.058029, -80.956375, 35.044701, 35.223812),
+ (28280, -80.844208, -80.841972, 35.225468, 35.227203),
+ (28282, -80.846382, -80.844193, 35.223972, 35.225655);
+}
+set ::contained_in 0
+proc contained_in {args} {
+ incr ::contained_in
+ return 0
+}
+db func contained_in contained_in
+do_execsql_test 4.3.2 {
+ SELECT objname FROM demo_index2
+ WHERE contained_in(boundary, 35.37785, -80.77470)
+ AND minX<=-80.77470 AND maxX>=-80.77470
+ AND minY<=35.37785 AND maxY>=35.37785;
+}
+do_test 4.3.3 {
+ # Function invoked only once because r-tree filtering happened first.
+ set ::contained_in
+} 1
+set ::contained_in 0
+do_execsql_test 4.3.4 {
+ SELECT objname FROM demo_index2
+ WHERE contained_in(boundary, 35.37785, -80.77470)
+}
+do_test 4.3.3 {
+ # Function invoked 14 times because no r-tree filtering. Inefficient.
+ set ::contained_in
+} 14
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 4.1.1 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-9
+reset_db
+
+# EVIDENCE-OF: R-24021-02490 For auxiliary columns, only the name of the
+# column matters. The type affinity is ignored.
+#
+# EVIDENCE-OF: R-39906-44154 Constraints such as NOT NULL, UNIQUE,
+# REFERENCES, or CHECK are also ignored.
+do_execsql_test 1.0 { PRAGMA foreign_keys = on }
+foreach {tn auxcol nm} {
+ 1 "+extra INTEGER" extra
+ 2 "+extra TEXT" extra
+ 3 "+extra BLOB" extra
+ 4 "+extra REAL" extra
+
+ 5 "+col NOT NULL" col
+ 6 "+col CHECK (col IS NOT NULL)" col
+ 7 "+col REFERENCES tbl(x)" col
+} {
+ do_execsql_test 1.$tn.1 "
+ CREATE VIRTUAL TABLE rt USING rtree_i32(k, a,b, $auxcol)
+ "
+
+ # Check that the aux column has no affinity. Or NOT NULL constraint.
+ # And that the aux column is the child key of an FK constraint.
+ #
+ do_execsql_test 1.$tn.2 "
+ INSERT INTO rt($nm) VALUES(NULL), (45), (-123.2), ('456'), (X'ABCD');
+ SELECT typeof($nm), quote($nm) FROM rt;
+ " {
+ null NULL
+ integer 45
+ real -123.2
+ text '456'
+ blob X'ABCD'
+ }
+
+ # Check that there is no UNIQUE constraint either.
+ #
+ do_execsql_test 1.$tn.3 "
+ INSERT INTO rt($nm) VALUES('xyz'), ('xyz'), ('xyz');
+ "
+
+ do_execsql_test 1.$tn.2 {
+ DROP TABLE rt
+ }
+}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 5 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-10
+
+# EVIDENCE-OF: R-21011-43790 If integer coordinates are desired, declare
+# the table using "rtree_i32" instead: CREATE VIRTUAL TABLE intrtree
+# USING rtree_i32(id,x0,x1,y0,y1,z0,z1);
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE intrtree USING rtree_i32(id,x0,x1,y0,y1,z0,z1);
+ INSERT INTO intrtree DEFAULT VALUES;
+ SELECT typeof(x0) FROM intrtree;
+} {integer}
+
+# EVIDENCE-OF: R-09193-49806 An rtree_i32 stores coordinates as 32-bit
+# signed integers.
+#
+# Show that coordinates are cast in a way consistent with casting to
+# a signed 32-bit integer.
+do_execsql_test 1.1 {
+ DELETE FROM intrtree;
+ INSERT INTO intrtree VALUES(333,
+ 1<<44, (1<<44)+1,
+ 10000000000, 10000000001,
+ -10000000001, -10000000000
+ );
+ SELECT * FROM intrtree;
+} {
+ 333 0 1 1410065408 1410065409 -1410065409 -1410065408
+}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 7.1 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-11
+reset_db
+
+# This command assumes that the argument is a node blob for a 2 dimensional
+# i32 r-tree table. It decodes and returns a list of cells from the node
+# as a list. Each cell is itself a list of the following form:
+#
+# {$rowid $minX $maxX $minY $maxY}
+#
+# For internal (non-leaf) nodes, the rowid is replaced by the child node
+# number.
+#
+proc rnode {aData} {
+ set nDim 2
+
+ set nData [string length $aData]
+ set nBytePerCell [expr (8 + 2*$nDim*4)]
+ binary scan [string range $aData 2 3] S nCell
+
+ set res [list]
+ for {set i 0} {$i < $nCell} {incr i} {
+ set iOff [expr $i*$nBytePerCell+4]
+ set cell [string range $aData $iOff [expr $iOff+$nBytePerCell-1]]
+ binary scan $cell WIIII rowid x1 x2 y1 y2
+ lappend res [list $rowid $x1 $x2 $y1 $y2]
+ }
+
+ return $res
+}
+
+# aData must be a node blob. This command returns true if the node contains
+# rowid $rowid, or false otherwise.
+#
+proc rnode_contains {aData rowid} {
+ set L [rnode $aData]
+ foreach cell $L {
+ set r [lindex $cell 0]
+ if {$r==$rowid} { return 1 }
+ }
+ return 0
+}
+
+proc rnode_replace_cell {aData iCell cell} {
+ set aCell [binary format WIIII {*}$cell]
+ set nDim 2
+ set nBytePerCell [expr (8 + 2*$nDim*4)]
+ set iOff [expr $iCell*$nBytePerCell+4]
+
+ set aNew [binary format a*a*a* \
+ [string range $aData 0 $iOff-1] \
+ $aCell \
+ [string range $aData $iOff+$nBytePerCell end] \
+ ]
+ return $aNew
+}
+
+db function rnode rnode
+db function rnode_contains rnode_contains
+db function rnode_replace_cell rnode_replace_cell
+
+foreach {tn nm} {
+ 1 x1
+ 2 asdfghjkl
+ 3 hello_world
+} {
+ do_execsql_test 1.$tn.1 "
+ CREATE VIRTUAL TABLE $nm USING rtree(a,b,c,d,e);
+ "
+
+ # EVIDENCE-OF: R-33789-46762 The content of an R*Tree index is actually
+ # stored in three ordinary SQLite tables with names derived from the
+ # name of the R*Tree.
+ #
+ # EVIDENCE-OF: R-39849-06566 This is their schema: CREATE TABLE
+ # %_node(nodeno INTEGER PRIMARY KEY, data) CREATE TABLE %_parent(nodeno
+ # INTEGER PRIMARY KEY, parentnode) CREATE TABLE %_rowid(rowid INTEGER
+ # PRIMARY KEY, nodeno)
+ #
+ # EVIDENCE-OF: R-07489-10051 The "%" in the name of each shadow table is
+ # replaced by the name of the R*Tree virtual table. So, if the name of
+ # the R*Tree table is "xyz" then the three shadow tables would be
+ # "xyz_node", "xyz_parent", and "xyz_rowid".
+ do_execsql_test 1.$tn.2 {
+ SELECT sql FROM sqlite_schema WHERE name!=$nm ORDER BY 1
+ } [string map [list % $nm] "
+ {CREATE TABLE \"%_node\"(nodeno INTEGER PRIMARY KEY,data)}
+ {CREATE TABLE \"%_parent\"(nodeno INTEGER PRIMARY KEY,parentnode)}
+ {CREATE TABLE \"%_rowid\"(rowid INTEGER PRIMARY KEY,nodeno)}
+ "]
+
+ do_execsql_test 1.$tn "DROP TABLE $nm"
+}
+
+
+# EVIDENCE-OF: R-51070-59303 There is one entry in the %_node table for
+# each R*Tree node.
+#
+# The following creates a 6 node r-tree structure.
+#
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE r1 USING rtree_i32(i, x1,x2, y1,y2);
+ WITH t(i) AS (
+ VALUES(1) UNION SELECT i+1 FROM t WHERE i<110
+ )
+ INSERT INTO r1 SELECT i, (i%10), (i%10)+2, (i%6), (i%7)+6 FROM t;
+}
+do_execsql_test 2.1 {
+ SELECT count(*) FROM r1_node;
+} 6
+
+# EVIDENCE-OF: R-27261-09153 All nodes other than the root have an entry
+# in the %_parent shadow table that identifies the parent node.
+#
+# In this case nodes 2-6 are the children of node 1.
+#
+do_execsql_test 2.3 {
+ SELECT nodeno, parentnode FROM r1_parent
+} {2 1 3 1 4 1 5 1 6 1}
+
+# EVIDENCE-OF: R-02358-35037 The %_rowid shadow table maps entry rowids
+# to the node that contains that entry.
+#
+do_execsql_test 2.4 {
+ SELECT 'failed' FROM r1_rowid WHERE 0==rnode_contains(
+ (SELECT data FROM r1_node WHERE nodeno=r1_rowid.nodeno), rowid
+ )
+}
+do_test 2.5 {
+ db eval { SELECT nodeno, data FROM r1_node WHERE nodeno!=1 } {
+ set L [rnode $data]
+ foreach cell $L {
+ set rowid [lindex $cell 0]
+ set rowid_nodeno 0
+ db eval {SELECT nodeno AS rowid_nodeno FROM r1_rowid WHERE rowid=$rowid} {
+ break
+ }
+ if {$rowid_nodeno!=$nodeno} { error "data mismatch!" }
+ }
+ }
+} {}
+
+# EVIDENCE-OF: R-65201-22208 Extra columns appended to the %_rowid table
+# hold the content of auxiliary columns.
+#
+# EVIDENCE-OF: R-44161-28345 The names of these extra %_rowid columns
+# are probably not the same as the actual auxiliary column names.
+#
+# In this case, the auxiliary columns are named "e1" and "e2". The
+# extra %_rowid columns are named "a0" and "a1".
+#
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE rtaux USING rtree(id, x1,x2, y1,y2, +e1, +e2);
+ SELECT sql FROM sqlite_schema WHERE name='rtaux_rowid';
+} {
+ {CREATE TABLE "rtaux_rowid"(rowid INTEGER PRIMARY KEY,nodeno,a0,a1)}
+}
+do_execsql_test 3.1 {
+ INSERT INTO rtaux(e1, e2) VALUES('hello', 'world'), (123, 456);
+}
+do_execsql_test 3.2 {
+ SELECT a0, a1 FROM rtaux_rowid;
+} {
+ hello world 123 456
+}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 7.2 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc-12
+reset_db
+forcedelete test.db2
+
+db function rnode rnode
+db function rnode_contains rnode_contains
+db function rnode_replace_cell rnode_replace_cell
+
+# EVIDENCE-OF: R-13571-45795 The scalar SQL function rtreecheck(R) or
+# rtreecheck(S,R) runs an integrity check on the rtree table named R
+# contained within database S.
+#
+# EVIDENCE-OF: R-36011-59963 The function returns a human-language
+# description of any problems found, or the string 'ok' if everything is
+# ok.
+#
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE rt1 USING rtree(id, a, b);
+ WITH s(i) AS (
+ VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<200
+ )
+ INSERT INTO rt1 SELECT i, i, i FROM s;
+
+ ATTACH 'test.db2' AS 'aux';
+ CREATE VIRTUAL TABLE aux.rt1 USING rtree(id, a, b);
+ INSERT INTO aux.rt1 SELECT * FROM rt1;
+}
+
+do_execsql_test 1.1.1 { SELECT rtreecheck('rt1'); } {ok}
+do_execsql_test 1.1.2 { SELECT rtreecheck('main', 'rt1'); } {ok}
+do_execsql_test 1.1.3 { SELECT rtreecheck('aux', 'rt1'); } {ok}
+do_catchsql_test 1.1.4 {
+ SELECT rtreecheck('nosuchdb', 'rt1');
+} {1 {SQL logic error}}
+
+# Corrupt the table in database 'main':
+do_execsql_test 1.2.1 { UPDATE rt1_node SET nodeno=21 WHERE nodeno=3; }
+do_execsql_test 1.2.1 { SELECT rtreecheck('rt1')=='ok'; } {0}
+do_execsql_test 1.2.2 { SELECT rtreecheck('main', 'rt1')=='ok'; } {0}
+do_execsql_test 1.2.3 { SELECT rtreecheck('aux', 'rt1')=='ok'; } {1}
+do_execsql_test 1.2.4 { UPDATE rt1_node SET nodeno=3 WHERE nodeno=21; }
+
+# Corrupt the table in database 'aux':
+do_execsql_test 1.2.1 { UPDATE aux.rt1_node SET nodeno=21 WHERE nodeno=3; }
+do_execsql_test 1.2.1 { SELECT rtreecheck('rt1')=='ok'; } {1}
+do_execsql_test 1.2.2 { SELECT rtreecheck('main', 'rt1')=='ok'; } {1}
+do_execsql_test 1.2.3 { SELECT rtreecheck('aux', 'rt1')=='ok'; } {0}
+do_execsql_test 1.2.4 { UPDATE rt1_node SET nodeno=3 WHERE nodeno=21; }
+
+# EVIDENCE-OF: R-45759-33459 Example: To verify that an R*Tree named
+# "demo_index" is well-formed and internally consistent, run: SELECT
+# rtreecheck('demo_index');
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE demo_index USING rtree(id, x1,x2, y1,y2);
+ INSERT INTO demo_index SELECT id, a, b, a, b FROM rt1;
+}
+do_execsql_test 2.1 { SELECT rtreecheck('demo_index') } {ok}
+do_execsql_test 2.2 {
+ UPDATE demo_index_rowid SET nodeno=44 WHERE rowid=44;
+ SELECT rtreecheck('demo_index');
+} {{Found (44 -> 44) in %_rowid table, expected (44 -> 4)}}
+
+
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE rt2 USING rtree_i32(id, a, b, c, d);
+ WITH s(i) AS (
+ VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<200
+ )
+ INSERT INTO rt2 SELECT i, i, i+2, i, i+2 FROM s;
+}
+
+# EVIDENCE-OF: R-02555-31045 for each dimension, (coord1 <= coord2).
+#
+execsql BEGIN
+do_test 3.1 {
+ set cell [
+ lindex [execsql {SELECT rnode(data) FROM rt2_node WHERE nodeno=3}] 0 3
+ ]
+ set cell [list [lindex $cell 0] \
+ [lindex $cell 2] [lindex $cell 1] \
+ [lindex $cell 3] [lindex $cell 4] \
+ ]
+ execsql {
+ UPDATE rt2_node SET data=rnode_replace_cell(data, 3, $cell) WHERE nodeno=3
+ }
+ execsql { SELECT rtreecheck('rt2') }
+} {{Dimension 0 of cell 3 on node 3 is corrupt}}
+execsql ROLLBACK
+
+# EVIDENCE-OF: R-13844-15873 unless the cell is on the root node, that
+# the cell is bounded by the parent cell on the parent node.
+#
+execsql BEGIN
+do_test 3.2 {
+ set cell [
+ lindex [execsql {SELECT rnode(data) FROM rt2_node WHERE nodeno=3}] 0 3
+ ]
+ lset cell 3 450
+ lset cell 4 451
+ execsql {
+ UPDATE rt2_node SET data=rnode_replace_cell(data, 3, $cell) WHERE nodeno=3
+ }
+ execsql { SELECT rtreecheck('rt2') }
+} {{Dimension 1 of cell 3 on node 3 is corrupt relative to parent}}
+execsql ROLLBACK
+
+# EVIDENCE-OF: R-02505-03621 for leaf nodes, that there is an entry in
+# the %_rowid table corresponding to the cell's rowid value that points
+# to the correct node.
+#
+execsql BEGIN
+do_test 3.3 {
+ execsql {
+ UPDATE rt2_rowid SET rowid=452 WHERE rowid=100
+ }
+ execsql { SELECT rtreecheck('rt2') }
+} {{Mapping (100 -> 6) missing from %_rowid table}}
+execsql ROLLBACK
+
+# EVIDENCE-OF: R-50927-02218 for cells on non-leaf nodes, that there is
+# an entry in the %_parent table mapping from the cell's child node to
+# the node that it resides on.
+#
+execsql BEGIN
+do_test 3.4.1 {
+ execsql {
+ UPDATE rt2_parent SET parentnode=123 WHERE nodeno=3
+ }
+ execsql { SELECT rtreecheck('rt2') }
+} {{Found (3 -> 123) in %_parent table, expected (3 -> 1)}}
+execsql ROLLBACK
+execsql BEGIN
+do_test 3.4.2 {
+ execsql {
+ UPDATE rt2_parent SET nodeno=123 WHERE nodeno=3
+ }
+ execsql { SELECT rtreecheck('rt2') }
+} {{Mapping (3 -> 1) missing from %_parent table}}
+execsql ROLLBACK
+
+# EVIDENCE-OF: R-23235-09153 That there are the same number of entries
+# in the %_rowid table as there are leaf cells in the r-tree structure,
+# and that there is a leaf cell that corresponds to each entry in the
+# %_rowid table.
+execsql BEGIN
+do_test 3.5 {
+ execsql { INSERT INTO rt2_rowid VALUES(1000, 1000) }
+ execsql { SELECT rtreecheck('rt2') }
+} {{Wrong number of entries in %_rowid table - expected 200, actual 201}}
+execsql ROLLBACK
+
+# EVIDENCE-OF: R-62800-43436 That there are the same number of entries
+# in the %_parent table as there are non-leaf cells in the r-tree
+# structure, and that there is a non-leaf cell that corresponds to each
+# entry in the %_parent table.
+execsql BEGIN
+do_test 3.6 {
+ execsql { INSERT INTO rt2_parent VALUES(1000, 1000) }
+ execsql { SELECT rtreecheck('rt2') }
+} {{Wrong number of entries in %_parent table - expected 9, actual 10}}
+execsql ROLLBACK
+
+
+
+finish_test
diff --git a/ext/rtree/rtreedoc2.test b/ext/rtree/rtreedoc2.test
new file mode 100644
index 0000000..ca0c6b3
--- /dev/null
+++ b/ext/rtree/rtreedoc2.test
@@ -0,0 +1,346 @@
+# 2021 September 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the r-tree extension.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+set testprefix rtreedoc2
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 6 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc2-1
+
+# EVIDENCE-OF: R-35254-48865 A call to one of the above APIs creates a
+# new SQL function named by the second parameter (zQueryFunc or zGeom).
+#
+# [register_circle_geom db] registers new geometry callback "Qcircle"
+# and legacy implementation "circle". Test that these do actually appear.
+#
+do_execsql_test 1.1.0 {
+ SELECT * FROM pragma_function_list WHERE name IN('circle', 'qcircle');
+} {
+}
+do_test 1.1 {
+ register_circle_geom db
+} {SQLITE_OK}
+do_execsql_test 1.1.2 {
+ SELECT * FROM pragma_function_list WHERE name = 'circle' AND enc='utf8';
+} {
+ circle 0 s utf8 -1 0
+}
+do_execsql_test 1.1.3 {
+ SELECT * FROM pragma_function_list WHERE name = 'qcircle' AND enc='utf8';
+} {
+ qcircle 0 s utf8 -1 0
+}
+
+do_execsql_test 1.2.0 { SELECT circle(1, 2, 3); } {{}}
+do_execsql_test 1.2.1 { SELECT qcircle(1, 2, 3); } {{}}
+
+# EVIDENCE-OF: R-61427-46983
+do_execsql_test 1.3.0 {
+ CREATE VIRTUAL TABLE demo_index USING rtree(id, x1,x2, y1,y2);
+ INSERT INTO demo_index VALUES(10, 45,45, 24,24);
+ INSERT INTO demo_index VALUES(20, 50,50, 28,28);
+ INSERT INTO demo_index VALUES(30, 43,43, 22,22);
+}
+do_execsql_test 1.3.1 {
+ SELECT id FROM demo_index WHERE id MATCH circle(45.3, 22.9, 5.0)
+} {10 30}
+
+# EVIDENCE-OF: R-16907-50223 The SQL syntax for custom queries is the
+# same regardless of which interface, sqlite3_rtree_geometry_callback()
+# or sqlite3_rtree_query_callback(), is used to register the SQL
+# function.
+do_execsql_test 1.3.2 {
+ SELECT id FROM demo_index WHERE id MATCH qcircle(45.3, 22.9, 5.0, 1)
+} {10 30}
+
+
+# EVIDENCE-OF: R-59634-51678 When that SQL function appears on the
+# right-hand side of the MATCH operator and the left-hand side of the
+# MATCH operator is any column in the R*Tree virtual table, then the
+# callback defined by the third argument (xQueryFunc or xGeom) is
+# invoked to determine if a particular object or subtree overlaps the
+# desired region.
+proc box_geom {args} {
+ lappend ::box_geom [concat [lindex $args 0] [lrange $args 2 end-1]]
+ return ""
+}
+register_box_geom db box_geom
+set box_geom [list]
+do_execsql_test 1.3.2 {
+ SELECT id FROM demo_index WHERE id MATCH box(43,46, 21,25);
+} {10 30}
+do_test 1.3.3 {
+ set ::box_geom
+} [list {*}{
+ {box {43.0 46.0 21.0 25.0} {45.0 45.0 24.0 24.0}}
+ {box {43.0 46.0 21.0 25.0} {50.0 50.0 28.0 28.0}}
+ {box {43.0 46.0 21.0 25.0} {43.0 43.0 22.0 22.0}}
+}]
+
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+# Section 6 of documentation.
+#-------------------------------------------------------------------------
+#-------------------------------------------------------------------------
+set testprefix rtreedoc2-2
+
+# EVIDENCE-OF: R-02424-24769 The second argument is the number of
+# coordinates in each r-tree entry, and is always the same for any given
+# R*Tree.
+#
+# EVIDENCE-OF: R-40260-16838 The number of coordinates is 2 for a
+# 1-dimensional R*Tree, 4 for a 2-dimensional R*Tree, 6 for a
+# 3-dimensional R*Tree, and so forth.
+#
+# The second argument refered to above is the length of the list passed
+# as the 3rd parameter to the Tcl script.
+#
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE rt1 USING rtree(id, x1,x2);
+ CREATE VIRTUAL TABLE rt2 USING rtree(id, x1,x2, y1,y2);
+ CREATE VIRTUAL TABLE rt3 USING rtree(id, x1,x2, y1,y2, z1,z2);
+
+ INSERT INTO rt1 DEFAULT VALUES;
+ INSERT INTO rt2 DEFAULT VALUES;
+ INSERT INTO rt3 DEFAULT VALUES;
+}
+foreach {tn tbl nCoord} {
+ 1 rt1 2
+ 2 rt2 4
+ 3 rt3 6
+} {
+ set ::box_geom [list]
+ do_catchsql_test 1.$tn.1 "
+ SELECT id FROM $tbl WHERE id MATCH box();
+ " {1 {SQL logic error}}
+
+ do_test 1.$tn.2 {
+ llength [lindex $::box_geom 0 2]
+ } $nCoord
+}
+
+# EVIDENCE-OF: R-28051-48608 If xGeom returns anything other than
+# SQLITE_OK, then the r-tree query will abort with an error.
+proc box_geom {args} {
+ error "an error!"
+}
+do_catchsql_test 2.0 {
+ SELECT * FROM rt2 WHERE id MATCH box(22,23, 24,25);
+} {1 {SQL logic error}}
+
+do_execsql_test 3.0 {
+ INSERT INTO rt1 VALUES(10, 10, 10);
+ INSERT INTO rt1 VALUES(11, 11, 11);
+ INSERT INTO rt1 VALUES(12, 12, 12);
+ INSERT INTO rt1 VALUES(13, 13, 13);
+ INSERT INTO rt1 VALUES(14, 14, 14);
+}
+
+# EVIDENCE-OF: R-53759-57366 The exact same sqlite3_rtree_geometry
+# structure is used for every callback for same MATCH operator in the
+# same query.
+proc box_geom {args} {
+ lappend ::ptr_list [lindex $args 4]
+ return 0
+}
+set ::ptr_list [list]
+do_execsql_test 3.1 {
+ SELECT * FROM rt1 WHERE id MATCH box(1,1);
+}
+do_test 3.2 {
+ set val [lindex $::ptr_list 0]
+ foreach p $::ptr_list {
+ if {$p!=$val} {error "pointer mismatch"}
+ }
+} {}
+
+# EVIDENCE-OF: R-60247-35692 The contents of the sqlite3_rtree_geometry
+# structure are initialized by SQLite but are not subsequently modified.
+proc box_geom {args} {
+ lappend ::box_geom [concat [lindex $args 0] [lrange $args 2 end-1]]
+ if {[llength $::box_geom]==3} {
+ return "zero"
+ }
+ return ""
+}
+set ::box_geom [list]
+do_catchsql_test 3.2 {
+ SELECT * FROM rt1 WHERE id MATCH box(1,1);
+} {1 {SQL logic error}}
+do_test 3.3 {
+ set ::box_geom
+} [list {*}{
+ {box {1.0 1.0} {0.0 0.0}}
+ {box {1.0 1.0} {10.0 10.0}}
+ {box {1.0 1.0} {11.0 11.0}}
+ {box 0.0 {12.0 12.0}}
+}]
+
+# EVIDENCE-OF: R-31246-29731 The pContext member of the
+# sqlite3_rtree_geometry structure is always set to a copy of the
+# pContext argument passed to sqlite3_rtree_geometry_callback() when the
+# callback is registered.
+reset_db
+do_execsql_test 4.0 {
+ CREATE VIRTUAL TABLE r1 USING rtree(id, minX,maxX, minY,maxY);
+ WITH s(i) AS (
+ VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<120
+ )
+ INSERT INTO r1 SELECT i,i,i+1, 200,201 FROM s;
+}
+set ctx [register_box_geom db box_geom]
+set ::box_geom [list]
+proc box_geom {args} {
+ lappend ::box_geom [lindex $args 1]
+ return ""
+}
+do_execsql_test 4.1 {
+ SELECT count(*) FROM r1 WHERE id MATCH box(0,150,199,201)
+} 120
+do_test 4.2 {
+ foreach g $::box_geom {
+ if {$g!=$ctx} {error "pointer mismatch"}
+ }
+} {}
+
+# EVIDENCE-OF: R-09904-19077 The aParam[] array (size nParam) contains
+# the parameter values passed to the SQL function on the right-hand side
+# of the MATCH operator.
+proc box_geom {args} {
+ set ::box_geom [lindex $args 2]
+}
+foreach {tn q vals} {
+ 1 "SELECT count(*) FROM r1 WHERE id MATCH box(1,2,3)" {1.0 2.0 3.0}
+ 2 "SELECT count(*) FROM r1 WHERE id MATCH box(10001)" {10001.0}
+ 3 "SELECT count(*) FROM r1 WHERE id MATCH box(-10001)" {-10001.0}
+} {
+ do_catchsql_test 5.$tn.1 $q {1 {SQL logic error}}
+ do_test 5.$tn.2 { set ::box_geom } $vals
+}
+
+do_execsql_test 5.0 {
+ CREATE VIRTUAL TABLE myrtree USING rtree(id, x1,x2);
+ INSERT INTO myrtree VALUES(1, 1, 1);
+ INSERT INTO myrtree VALUES(2, 2, 2);
+ INSERT INTO myrtree VALUES(3, 3, 3);
+}
+
+# EVIDENCE-OF: R-44448-00687 The pUser and xDelUser members of the
+# sqlite3_rtree_geometry structure are initially set to NULL.
+set ::box_geom_calls 0
+proc box_geom {args} {
+ incr ::box_geom_calls
+ return user_is_zero
+}
+do_execsql_test 5.1.1 {
+ SELECT * FROM myrtree WHERE id MATCH box(4, 5);
+}
+do_test 5.1.2 { set ::box_geom_calls } 3
+
+
+# EVIDENCE-OF: R-55837-00155 The pUser variable may be set by the
+# callback implementation to any arbitrary value that may be useful to
+# subsequent invocations of the callback within the same query (for
+# example, a pointer to a complicated data structure used to test for
+# region intersection).
+#
+# EVIDENCE-OF: R-34745-08839 If the xDelUser variable is set to a
+# non-NULL value, then after the query has finished running SQLite
+# automatically invokes it with the value of the pUser variable as the
+# only argument.
+#
+set ::box_geom_calls 0
+proc box_geom {args} {
+ incr ::box_geom_calls
+ switch -- $::box_geom_calls {
+ 1 {
+ return user_is_zero
+ }
+ 2 {
+ return [list user box_geom_finalizer]
+ }
+ }
+ return ""
+}
+proc box_geom_finalizer {} {
+ set ::box_geom_finalizer "::box_geom_calls is $::box_geom_calls"
+}
+do_execsql_test 5.1.1 {
+ SELECT * FROM myrtree WHERE id MATCH box(4, 5);
+}
+do_test 5.1.2 { set ::box_geom_calls } 3
+do_test 5.1.3 {
+ set ::box_geom_finalizer
+} {::box_geom_calls is 3}
+
+
+# EVIDENCE-OF: R-28176-28813 The xGeom callback always does a
+# depth-first search of the r-tree.
+#
+# For a breadth first search, final test case would return "B L" only.
+#
+do_execsql_test 6.0 {
+ CREATE VIRTUAL TABLE xyz USING rtree(x, x1,x2, y1,y2);
+ WITH s(i) AS (
+ VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<15
+ )
+ INSERT INTO xyz SELECT NULL, one.i,one.i+1, two.i,two.i+1 FROM s one, s two;
+}
+do_execsql_test 6.1 {
+ SELECT count(*) FROM xyz_node
+} {10}
+proc box_geom {args} {
+ set coords [lindex $args 3]
+ set area [expr {
+ ([lindex $coords 1]-[lindex $coords 0]) *
+ ([lindex $coords 3]-[lindex $coords 2])
+ }]
+ if {$area==1} {
+ lappend ::box_geom_calls L
+ } else {
+ lappend ::box_geom_calls B
+ }
+}
+set ::box_geom_calls [list]
+do_execsql_test 6.2 {
+ SELECT count(*) FROM xyz WHERE x MATCH box(0,20,0,20)
+} 225
+do_test 6.3 {
+ set prev ""
+ set box_calls [list]
+ foreach c $::box_geom_calls {
+ if {$c!=$prev} {
+ lappend ::box_calls $c
+ set prev $c
+ }
+ }
+ set ::box_calls
+} {B L B L B L B L B L B L B L B L B L}
+
+
+finish_test
+
diff --git a/ext/rtree/rtreedoc3.test b/ext/rtree/rtreedoc3.test
new file mode 100644
index 0000000..0403409
--- /dev/null
+++ b/ext/rtree/rtreedoc3.test
@@ -0,0 +1,292 @@
+# 2021 September 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the r-tree extension.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+set testprefix rtreedoc3
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+
+# This command assumes that the argument is a node blob for a 2 dimensional
+# i32 r-tree table. It decodes and returns a list of cells from the node
+# as a list. Each cell is itself a list of the following form:
+#
+# {$rowid $minX $maxX $minY $maxY}
+#
+# For internal (non-leaf) nodes, the rowid is replaced by the child node
+# number.
+#
+proc rnode_cells {aData} {
+ set nDim 2
+
+ set nData [string length $aData]
+ set nBytePerCell [expr (8 + 2*$nDim*4)]
+ binary scan [string range $aData 2 3] S nCell
+
+ set res [list]
+ for {set i 0} {$i < $nCell} {incr i} {
+ set iOff [expr $i*$nBytePerCell+4]
+ set cell [string range $aData $iOff [expr $iOff+$nBytePerCell-1]]
+ binary scan $cell WIIII rowid x1 x2 y1 y2
+ lappend res [list $rowid $x1 $x2 $y1 $y2]
+ }
+
+ return $res
+}
+
+# Interpret the first two bytes of the blob passed as the only parameter
+# as a 16-bit big-endian integer and return the value. If this blob is
+# the root node of an r-tree, this value is the height of the tree.
+#
+proc rnode_height {aData} {
+ binary scan [string range $aData 0 1] S nHeight
+ return $nHeight
+}
+
+# Return a blob containing node iNode of r-tree "rt".
+#
+proc rt_node_get {iNode} {
+ db one { SELECT data FROM rt_node WHERE nodeno=$iNode }
+}
+
+
+#--------------------------------------------------------------
+# API:
+#
+# pq_init
+# Initialize a new test.
+#
+# pq_test_callback
+# Invoked each time the xQueryCallback function is called. This Tcl
+# command checks that the arguments that SQLite passed to xQueryCallback
+# are as expected.
+#
+# pq_test_row
+# Invoked each time a row is returned. Checks that the row returned
+# was predicted by the documentation.
+#
+# DATA STRUCTURE:
+# The priority queue is stored as a Tcl list. The order of elements in
+# the list is unimportant - it is just used as a set here. Each element
+# in the priority queue is itself a list. The first element is the
+# priority value for the entry (a real). Following this is a list of
+# key-value pairs that make up the entries fields.
+#
+proc pq_init {} {
+ global Q
+ set Q(pri_queue) [list]
+
+ set nHeight [rnode_height [rt_node_get 1]]
+ set nCell [llength [rnode_cells [rt_node_get 1]]]
+
+ # EVIDENCE-OF: R-54708-13595 An R*Tree query is initialized by making
+ # the root node the only entry in a priority queue sorted by rScore.
+ lappend Q(pri_queue) [list 0.0 [list \
+ iLevel [expr $nHeight+1] \
+ iChild 1 \
+ iCurrent 0 \
+ ]]
+}
+
+proc pq_extract {} {
+ global Q
+ if {[llength $Q(pri_queue)]==0} {
+ error "priority queue is empty!"
+ }
+
+ # Find the priority queue entry with the lowest score.
+ #
+ # EVIDENCE-OF: R-47257-47871 Smaller scores are processed first.
+ set iBest 0
+ set rBestScore [lindex $Q(pri_queue) 0 0]
+ for {set ii 1} {$ii < [llength $Q(pri_queue)]} {incr ii} {
+ set rScore [expr [lindex $Q(pri_queue) $ii 0]]
+ if {$rScore<$rBestScore} {
+ set rBestScore $rScore
+ set iBest $ii
+ }
+ }
+
+ # Extract the entry with the lowest score from the queue and return it.
+ #
+ # EVIDENCE-OF: R-60002-49798 The query proceeds by extracting the entry
+ # from the priority queue that has the lowest score.
+ set ret [lindex $Q(pri_queue) $iBest]
+ set Q(pri_queue) [lreplace $Q(pri_queue) $iBest $iBest]
+
+ return $ret
+}
+
+proc pq_new_entry {rScore iLevel cell} {
+ global Q
+
+ set rowid_name "iChild"
+ if {$iLevel==0} { set rowid_name "iRowid" }
+
+ set kv [list]
+ lappend kv aCoord [lrange $cell 1 end]
+ lappend kv iLevel $iLevel
+
+ if {$iLevel==0} {
+ lappend kv iRowid [lindex $cell 0]
+ } else {
+ lappend kv iChild [lindex $cell 0]
+ lappend kv iCurrent 0
+ }
+
+ lappend Q(pri_queue) [list $rScore $kv]
+}
+
+proc pq_test_callback {L res} {
+ #pq_debug "pq_test_callback $L -> $res"
+ global Q
+
+ array set G $L ;# "Got" - as in stuff passed to xQuery
+
+ # EVIDENCE-OF: R-65127-42665 If the extracted priority queue entry is a
+ # node (a subtree), then the next child of that node is passed to the
+ # xQueryFunc callback.
+ #
+ # If it had been a leaf, the row should have been returned, instead of
+ # xQueryCallback being called on a child - as is happening here.
+ foreach {rParentScore parent} [pq_extract] {}
+ array set P $parent ;# "Parent" - as in parent of expected cell
+ if {$P(iLevel)==0} { error "query callback mismatch (1)" }
+ set child_node [rnode_cells [rt_node_get $P(iChild)]]
+ set expected_cell [lindex $child_node $P(iCurrent)]
+ set expected_coords [lrange $expected_cell 1 end]
+ if {[llength $expected_coords] != [llength $G(aCoord)]} {
+ puts [array get P]
+ puts "E: $expected_coords G: $G(aCoord)"
+ error "coordinate mismatch in query callback (1)"
+ }
+ foreach a [lrange $expected_cell 1 end] b $G(aCoord) {
+ if {$a!=$b} { error "coordinate mismatch in query callback (2)" }
+ }
+
+ # Check level is as expected
+ #
+ if {$G(iLevel) != $P(iLevel)-1} {
+ error "iLevel mismatch in query callback (1)"
+ }
+
+ # Unless the callback returned NOT_WITHIN, add the entry to the priority
+ # queue.
+ #
+ # EVIDENCE-OF: R-28754-35153 Those subelements for which the xQueryFunc
+ # callback sets eWithin to PARTLY_WITHIN or FULLY_WITHIN are added to
+ # the priority queue using the score supplied by the callback.
+ #
+ # EVIDENCE-OF: R-08681-45277 Subelements that return NOT_WITHIN are
+ # discarded.
+ set r [lindex $res 0]
+ set rScore [lindex $res 1]
+ if {$r!="fully" && $r!="partly" && $r!="not"} {
+ error "unknown result: $r - expected \"fully\", \"partly\" or \"not\""
+ }
+ if {$r!="not"} {
+ pq_new_entry $rScore [expr $P(iLevel)-1] $expected_cell
+ }
+
+ # EVIDENCE-OF: R-07194-63805 If the node has more children then it is
+ # returned to the priority queue. Otherwise it is discarded.
+ incr P(iCurrent)
+ if {$P(iCurrent)<[llength $child_node]} {
+ lappend Q(pri_queue) [list $rParentScore [array get P]]
+ }
+}
+
+proc pq_test_result {id x1 x2 y1 y2} {
+ #pq_debug "pq_test_result $id $x1 $x2 $y1 $y2"
+ foreach {rScore next} [pq_extract] {}
+
+ # The extracted entry must be a leaf (otherwise, xQueryCallback would
+ # have been called on the extracted entries children instead of just
+ # returning the data).
+ #
+ # EVIDENCE-OF: R-13214-54017 If that entry is a leaf (meaning that it is
+ # an actual R*Tree entry and not a subtree) then that entry is returned
+ # as one row of the query result.
+ array set N $next
+ if {$N(iLevel)!=0} { error "result row mismatch (1)" }
+
+ if {$x1!=[lindex $N(aCoord) 0] || $x2!=[lindex $N(aCoord) 1]
+ || $y1!=[lindex $N(aCoord) 2] || $y2!=[lindex $N(aCoord) 3]
+ } {
+ if {$N(iLevel)!=0} { error "result row mismatch (2)" }
+ }
+
+ if {$id!=$N(iRowid)} { error "result row mismatch (3)" }
+}
+
+proc pq_done {} {
+ global Q
+ # EVIDENCE-OF: R-57438-45968 The query runs until the priority queue is
+ # empty.
+ if {[llength $Q(pri_queue)]>0} {
+ error "priority queue is not empty!"
+ }
+}
+
+proc pq_debug {caption} {
+ global Q
+
+ puts "**** $caption ****"
+ set i 0
+ foreach q [lsort -real -index 0 $Q(pri_queue)] {
+ puts "PQ $i: $q"
+ incr i
+ }
+}
+
+#--------------------------------------------------------------
+
+proc box_query {a} {
+ set res [list fully [expr rand()]]
+ pq_test_callback $a $res
+ return $res
+}
+
+register_box_query db box_query
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE rt USING rtree_i32(id, x1,x2, y1,y2);
+ WITH s(i) AS (
+ SELECT 0 UNION ALL SELECT i+1 FROM s WHERE i<64
+ )
+ INSERT INTO rt SELECT NULL, a.i, a.i+1, b.i, b.i+1 FROM s a, s b;
+}
+
+proc box_query {a} {
+ set res [list fully [expr rand()]]
+ pq_test_callback $a $res
+ return $res
+}
+
+pq_init
+db eval { SELECT id, x1,x2, y1,y2 FROM rt WHERE id MATCH qbox() } {
+ pq_test_result $id $x1 $x2 $y1 $y2
+}
+pq_done
+
+finish_test
+
+
diff --git a/ext/rtree/rtreefuzz001.test b/ext/rtree/rtreefuzz001.test
new file mode 100644
index 0000000..58fd179
--- /dev/null
+++ b/ext/rtree/rtreefuzz001.test
@@ -0,0 +1,1211 @@
+# 2012-12-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.
+#
+#***********************************************************************
+#
+# Test cases for corrupt database files.
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+
+ifcapable !deserialize||!rtree {
+ finish_test
+ return
+}
+database_may_be_corrupt
+
+do_test rtreefuzz001-100 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 24576 pagesize 4096 filename c1b.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 03 00 00 00 06 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 ................
+| 96: 00 2e 30 38 0d 00 00 00 04 0e 9c 00 0f ad 0f 4f ..08...........O
+| 112: 0e fc 0e 9c 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 3728: 00 00 00 00 00 00 00 00 00 00 00 00 5e 04 07 17 ............^...
+| 3744: 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 72 .....tablet1_par
+| 3760: 65 6e 74 74 31 5f 70 61 72 65 6e 74 04 43 52 45 entt1_parent.CRE
+| 3776: 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 61 ATE TABLE .t1_pa
+| 3792: 72 66 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 rfnt.(nodeno INT
+| 3808: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY
+| 3824: 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 03 06 17 ,parentnode)Q...
+| 3840: 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 65 ....tablet1_node
+| 3856: 74 31 5f 6e 6f 64 65 03 43 52 45 41 54 45 20 54 t1_node.CREATE T
+| 3872: 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 6e ABLE .t1_node.(n
+| 3888: 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 52 odeno INTEGER PR
+| 3904: 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 5c IMARY KEY,data).
+| 3920: 02 07 17 1d 1d 01 81 0b 74 61 62 6c 65 74 31 5f ........tablet1_
+| 3936: 72 6f 77 69 64 74 31 5f 72 6f 77 69 64 02 43 52 rowidt1_rowid.CR
+| 3952: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 72 EATE TABLE .t1_r
+| 3968: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE
+| 3984: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY,
+| 4000: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 29 51 01 07 nodeno,a0,a1)Q..
+| 4016: 17 11 11 08 81 0f 74 61 62 6c 65 74 31 74 31 43 ......tablet1t1C
+| 4032: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA
+| 4048: 42 4c 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 BLE t1 USING rtr
+| 4064: 65 65 28 69 64 2c 78 30 2c 78 31 2c 79 30 2c 79 ee(id,x0,x1,y0,y
+| 4080: 31 2c 2b 6c 61 62 65 6c 2c 2b 6f 74 68 65 72 29 1,+label,+other)
+| page 2 offset 4096
+| 0: 0d 0c cd 00 74 08 75 01 0f e8 0c b3 0f d0 0f b7 ....t.u.........
+| 16: 0f 9e 0f 91 0f 81 0f 70 0f 5e 0f 4f 0f 39 0f 29 .......p.^.O.9.)
+| 32: 0f 18 0f 06 0e f7 0c 65 0e 58 0d c2 0d 2c 0c 25 .......e.X...,.%
+| 48: 0b 85 0a e5 0a 45 09 a5 09 05 0c 83 0c 93 0c a3 .....E..........
+| 64: 0f f0 0c 15 0b 75 0a d5 0a 35 09 95 08 f5 0e d8 .....u...5......
+| 80: 0e 42 0d ac 0d 16 0c 05 0b 65 0a c5 0a 25 09 85 .B.......e...%..
+| 96: 08 e5 0e c8 0e 32 0d 9c 0d 06 0b f5 0b 55 0a b5 .....2.......U..
+| 112: 0a 15 09 75 08 d5 0e b8 0e 22 0d 8c 0c f6 0b e5 ...u............
+| 128: 0b 45 0a a5 0a 05 09 65 08 c5 0e a8 0e 12 0d 7c .E.....e.......|
+| 144: 0c e6 0b d5 0b 35 0a 95 09 f5 09 55 08 b5 0e 98 .....5.....U....
+| 160: 0e 02 0d 6c 0c d6 0b c5 0b 25 0a 85 09 e5 09 45 ...l.....%.....E
+| 176: 08 a5 0e 88 0d f2 0d 5c 0c 55 0b b5 0b 15 0a 75 .........U.....u
+| 192: 09 d5 09 35 08 95 0e 78 0d e2 0d 4c 0c 45 0b a5 ...5...x...L.E..
+| 208: 0b 05 0a 65 09 c5 09 25 08 85 0e 68 0d d2 0d 3c ...e...%...h...<
+| 224: 0c 35 0b 95 0a f5 0a 55 09 b5 09 15 08 75 0c 75 .5.....U.....u.u
+| 2160: 00 00 00 00 00 0d 8e 75 05 00 01 1b 00 04 62 6f .......u......bo
+| 2176: 78 2d 39 2c 39 0d 8e 11 05 00 01 1b 00 02 62 6f x-9,9.........bo
+| 2192: 78 2d 39 2c 38 0d 8d 2d 05 00 01 1b 00 02 62 6f x-9,8..-......bo
+| 2208: 78 2d 39 2c 37 0d 8c 49 05 00 01 1b 00 02 62 6f x-9,7..I......bo
+| 2224: 78 2d 39 2c 36 0d 8b 65 05 00 01 1b 00 02 62 6f x-9,6..e......bo
+| 2240: 78 2d 39 2c 35 0d 8b 01 05 00 01 1b 00 02 62 6f x-9,5.........bo
+| 2256: 78 2d 39 2c 34 0d 8a 1d 05 00 01 1b 00 02 62 6f x-9,4.........bo
+| 2272: 78 2d 39 2c 33 0d 89 39 05 00 01 1b 00 02 62 6f x-9,3..9......bo
+| 2288: 78 2d 39 2c 32 0d 88 55 05 00 01 1b 00 02 62 6f x-9,2..U......bo
+| 2304: 78 2d 39 2c 31 0d 87 71 05 00 01 1b 00 02 62 6f x-9,1..q......bo
+| 2320: 78 2d 39 2c 30 0d 8e 74 05 00 01 1b 00 04 62 6f x-9,0..t......bo
+| 2336: 78 2d 38 2c 39 0d 8e 10 05 00 01 1b 00 02 62 6f x-8,9.........bo
+| 2352: 78 2d 38 2c 38 0d 8d 2c 05 00 01 1b 00 02 62 6f x-8,8..,......bo
+| 2368: 78 2d 38 2c 37 0d 8c 48 05 00 01 1b 00 02 62 6f x-8,7..H......bo
+| 2384: 78 2d 38 2c 36 0d 8b 64 05 00 01 1b 00 02 62 6f x-8,6..d......bo
+| 2400: 78 2d 38 2c 35 0d 8b 00 05 00 01 1b 00 02 62 6f x-8,5.........bo
+| 2416: 78 2d 38 2c 34 0d 8a 1c 05 00 01 1b 00 02 62 6f x-8,4.........bo
+| 2432: 78 2d 38 2c 33 0d 89 38 05 00 01 1b 00 02 62 6f x-8,3..8......bo
+| 2448: 78 2d 38 2c 32 0d 88 54 05 00 01 1b 00 02 62 6f x-8,2..T......bo
+| 2464: 78 2d 38 2c 31 0d 87 70 05 00 01 1b 00 02 62 6f x-8,1..p......bo
+| 2480: 78 2d 38 2c 30 0d 8e 73 05 00 01 1b 00 05 62 6f x-8,0..s......bo
+| 2496: 78 2d 37 2c 39 0d 8e 0f 05 00 01 1b 00 05 62 6f x-7,9.........bo
+| 2512: 78 2d 37 2c 38 0d 8d 2b 05 00 01 1b 00 05 62 6f x-7,8..+......bo
+| 2528: 78 2d 37 2c 37 0d 8c 47 05 00 01 1b 00 05 62 6f x-7,7..G......bo
+| 2544: 78 2d 37 2c 36 0d 8b 63 05 00 01 1b 00 05 62 6f x-7,6..c......bo
+| 2560: 78 2d 37 2c 35 0d 8a 7f 05 00 01 1b 00 05 62 6f x-7,5.........bo
+| 2576: 78 2d 37 2c 34 0d 8a 1b 05 00 01 1b 00 05 62 6f x-7,4.........bo
+| 2592: 78 2d 37 2c 33 0d 89 37 05 00 01 1b 00 05 62 6f x-7,3..7......bo
+| 2608: 78 2d 37 2c 32 0d 88 53 05 00 01 1b 00 05 62 6f x-7,2..S......bo
+| 2624: 78 2d 37 2c 31 0d 87 6f 05 00 01 1b 00 05 62 6f x-7,1..o......bo
+| 2640: 78 2d 37 2c 30 0d 8e 72 05 00 01 1b 00 04 62 6f x-7,0..r......bo
+| 2656: 78 2d 36 2c 39 0d 8e 0e 05 00 01 1b 00 05 62 6f x-6,9.........bo
+| 2672: 78 2d 36 2c 38 0d 8d 2a 05 00 01 1b 00 05 62 6f x-6,8..*......bo
+| 2688: 78 2d 36 2c 37 0d 8c 46 05 00 01 1b 00 05 62 6f x-6,7..F......bo
+| 2704: 78 2d 36 2c 36 0d 8b 62 05 00 01 1b 00 05 62 6f x-6,6..b......bo
+| 2720: 78 2d 36 2c 35 0d 8a 7e 05 00 01 1b 00 05 62 6f x-6,5..~......bo
+| 2736: 78 2d 36 2c 34 0d 8a 1a 05 00 01 1b 00 05 62 6f x-6,4.........bo
+| 2752: 78 2d 36 2c 33 0d 89 36 05 00 01 1b 00 05 62 6f x-6,3..6......bo
+| 2768: 78 2d 36 2c 32 0d 88 52 05 00 01 1b 00 05 62 6f x-6,2..R......bo
+| 2784: 78 2d 36 2c 31 0d 87 6e 05 00 01 1b 00 05 62 6f x-6,1..n......bo
+| 2800: 78 2d 36 2c 30 0d 8e 71 05 00 01 1b 00 04 62 6f x-6,0..q......bo
+| 2816: 78 2d 35 2c 39 0d 8e 0d 05 00 01 1b 00 05 62 6f x-5,9.........bo
+| 2832: 78 2d 35 2c 38 0d 8d 29 05 00 01 1b 00 05 62 6f x-5,8..)......bo
+| 2848: 78 2d 35 2c 37 0d 8c 45 05 00 01 1b 00 05 62 6f x-5,7..E......bo
+| 2864: 78 2d 35 2c 36 0d 8b 61 05 00 01 1b 00 05 62 6f x-5,6..a......bo
+| 2880: 78 2d 35 2c 35 0d 8a 7d 05 00 01 1b 00 05 62 6f x-5,5.........bo
+| 2896: 78 2d 35 2c 34 0d 8a 19 05 00 01 1b 00 05 62 6f x-5,4.........bo
+| 2912: 78 2d 35 2c 33 0d 89 35 05 00 01 1b 00 05 62 6f x-5,3..5......bo
+| 2928: 78 2d 35 2c 32 0d 88 51 05 00 01 1b 00 05 62 6f x-5,2..Q......bo
+| 2944: 78 2d 35 2c 31 0d 87 6d 05 00 01 1b 00 05 62 6f x-5,1..m......bo
+| 2960: 78 2d 35 2c 30 0d 8e 70 05 00 01 1b 00 04 62 6f x-5,0..p......bo
+| 2976: 78 2d 34 2c 39 0d 8e 0c 05 00 01 1b 00 04 62 6f x-4,9.........bo
+| 2992: 78 2d 34 2c 38 0d 8d 28 05 00 01 1b 00 04 62 6f x-4,8..(......bo
+| 3008: 78 2d 34 2c 37 0d 8c 44 05 00 01 1b 00 04 62 6f x-4,7..D......bo
+| 3024: 78 2d 34 2c 36 0d 8b 60 05 00 01 1b 00 02 62 6f x-4,6..`......bo
+| 3040: 78 2d 34 2c 35 0d 8a 7c 05 00 01 1b 00 02 62 6f x-4,5..|......bo
+| 3056: 78 2d 34 2c 34 0d 8a 18 05 00 01 1b 00 02 62 6f x-4,4.........bo
+| 3072: 78 2d 34 2c 33 0d 89 34 05 00 01 1b 00 02 62 6f x-4,3..4......bo
+| 3088: 78 2d 34 2c 32 0d 88 50 05 00 01 1b 00 02 62 6f x-4,2..P......bo
+| 3104: 78 2d 34 2c 31 0d 87 6c 05 00 01 1b 00 02 62 6f x-4,1..l......bo
+| 3120: 78 2d 34 2c 30 0d 8e 6f 05 00 01 1b 00 04 62 6f x-4,0..o......bo
+| 3136: 78 2d 33 2c 39 0d 8e 0b 05 00 01 1b 00 04 62 6f x-3,9.........bo
+| 3152: 78 2d 33 2c 38 0d 8d 27 05 00 01 1b 00 04 62 6f x-3,8..'......bo
+| 3168: 78 2d 33 2c 37 0d 87 68 05 00 01 1b 00 03 62 6f x-3,7..h......bo
+| 3184: 78 2d 30 2c 30 06 90 d9 80 80 81 84 4c 05 00 01 x-0,0.......L...
+| 3200: 00 00 03 0d 88 4c 05 00 01 1b 00 02 62 6f 78 2d .....L......box-
+| 3216: 30 2c 31 0d 88 4d 05 00 01 1b 00 02 62 6f 78 2d 0,1..M......box-
+| 3232: 31 2c 31 0d 88 4e 05 00 01 1b 00 02 62 6f 78 2d 1,1..N......box-
+| 3248: 32 2c 31 17 01 05 00 01 2f 00 02 6c 6f 77 65 72 2,1...../..lower
+| 3264: 2d 6c 65 66 74 20 63 6f 72 6e 65 72 0d 0d 26 00 -left corner..&.
+| 3280: 09 00 01 00 00 04 0d 8c 43 05 00 01 1b 00 04 62 ........C......b
+| 3296: 6f 78 2d 33 2c 36 0d 8b 5f 05 00 01 1b 00 02 62 ox-3,6.._......b
+| 3312: 6f 78 2d 33 2c 35 0d 8a 7b 05 00 01 1b 00 02 62 ox-3,5.........b
+| 3328: 6f 78 2d 33 2c 34 0d 8a 17 05 00 01 1b 00 02 62 ox-3,4.........b
+| 3344: 6f 78 2d 33 2c 33 0d 89 33 05 00 01 1b 00 02 62 ox-3,3..3......b
+| 3360: 6f 78 2d 33 2c 32 0d bc 00 06 00 09 0d 87 6b 05 ox-3,2........k.
+| 3376: 00 01 1b 00 03 62 6f 78 2d 33 2c 30 0d 8e 6e 05 .....box-3,0..n.
+| 3392: 00 01 1b 00 04 62 6f 78 2d 32 2c 39 0d 8e 0a 05 .....box-2,9....
+| 3408: 00 01 1b 00 04 62 6f 78 2d 32 2c 38 0d 8d 26 05 .....box-2,8..&.
+| 3424: 00 01 1b 00 04 62 6f 78 2d 32 2c 37 0d 8c 42 05 .....box-2,7..B.
+| 3440: 00 01 1b 00 04 62 6f 78 2d 32 2c 36 0d 8b 5e 05 .....box-2,6..^.
+| 3456: 00 01 1b 00 02 62 6f 78 2d 32 2c 35 0d 8a 7a 05 .....box-2,5..z.
+| 3472: 00 01 1b 00 02 62 6f 78 2d 32 2c 34 0d 8a 16 05 .....box-2,4....
+| 3488: 00 01 1b 00 02 62 6f 78 2d 32 2c 33 0d 89 32 05 .....box-2,3..2.
+| 3504: 00 01 1b 00 02 62 6f 78 2d 32 2c 32 0e 52 00 06 .....box-2,2.R..
+| 3520: 00 09 0d 87 6a 05 00 01 1b 00 03 62 6f 78 2d 32 ....j......box-2
+| 3536: 2c 30 0d 8e 6d 05 00 01 1b 00 04 62 6f 78 2d 31 ,0..m......box-1
+| 3552: 2c 39 0d 8e 09 05 00 01 1b 00 04 62 6f 78 2d 31 ,9.........box-1
+| 3568: 2c 38 0d 8d 25 05 00 01 1b 00 04 62 6f 78 2d 31 ,8..%......box-1
+| 3584: 2c 37 0d 8c 41 05 00 01 1b 00 04 62 6f 78 2d 31 ,7..A......box-1
+| 3600: 2c 36 0d 8b 5d 05 00 01 1b 00 02 62 6f 78 2d 31 ,6..]......box-1
+| 3616: 2c 35 0d 8a 79 05 00 01 1b 00 02 62 6f 78 2d 31 ,5..y......box-1
+| 3632: 2c 34 0d 8a 15 05 00 01 1b 00 02 62 6f 78 2d 31 ,4.........box-1
+| 3648: 2c 33 0d 89 31 05 00 01 1b 00 02 62 6f 78 2d 31 ,3..1......box-1
+| 3664: 2c 32 0e e8 00 06 00 09 0d 87 69 05 00 01 1b 00 ,2........i.....
+| 3680: 03 62 6f 78 2d 31 2c 30 0d 8e 6c 05 00 01 1b 00 .box-1,0..l.....
+| 3696: 04 62 6f 78 2d 30 2c 39 0d 8e 08 05 00 01 1b 00 .box-0,9........
+| 3712: 04 62 6f 78 2d 30 2c 38 0d 8d 24 05 00 01 1b 00 .box-0,8..$.....
+| 3728: 04 62 6f 78 2d 30 2c 37 0d 8c 40 05 00 01 1b 00 .box-0,7..@.....
+| 3744: 04 62 6f 78 2d 30 2c 36 0d 8b 5c 05 00 01 1b 00 .box-0,6........
+| 3760: 02 62 6f 78 2d 30 2c 35 0d 8a 78 05 00 01 1b 00 .box-0,5..x.....
+| 3776: 02 62 6f 78 2d 30 2c 34 0d 8a 14 05 00 01 1b 00 .box-0,4........
+| 3792: 02 62 6f 78 2d 30 2c 33 0d 89 30 05 00 01 1b 00 .box-0,3..0.....
+| 3808: 02 62 6f 78 2d 30 2c 32 00 00 00 0f 00 09 1b 00 .box-0,2........
+| 3824: 62 6f 78 2d 30 2c 30 0d 0e 05 00 09 1d 00 74 6f box-0,0.......to
+| 3840: 70 20 68 61 6c 66 10 0d 05 00 09 23 00 62 6f 74 p half.....#.bot
+| 3856: 74 6f 6d 20 68 61 6c 66 0f 0c 02 05 09 01 00 72 tom half.......r
+| 3872: 69 67 68 74 20 68 61 6c 66 0e 0b 05 00 09 1f 00 ight half.......
+| 3888: 6c 65 66 74 20 68 61 6c 66 14 0a 05 00 09 2b 00 left half.....+.
+| 3904: 74 68 65 20 77 68 6f 6c 65 20 74 68 69 6e 67 0d the whole thing.
+| 3920: 09 05 00 09 1d 00 74 6f 70 20 65 64 67 65 10 08 ......top edge..
+| 3936: 05 00 09 23 00 62 6f 74 74 6f 6d 20 65 64 67 65 ...#.bottom edge
+| 3952: 0f 07 05 00 09 21 00 72 69 67 68 74 20 65 64 67 .....!.right edg
+| 3968: 65 0e 06 05 00 09 1f 00 6c 65 66 74 20 65 64 67 e.......left edg
+| 3984: 65 0b 05 05 00 09 19 00 63 65 6e 74 65 72 17 04 e.......center..
+| 4000: 05 00 09 31 00 75 70 70 65 72 2d 72 69 67 68 74 ...1.upper-right
+| 4016: 20 63 6f 72 6e 65 72 17 03 05 00 09 31 00 6c 6f corner.....1.lo
+| 4032: 77 65 72 2d 72 69 67 68 74 20 63 6f 72 6e 65 72 wer-right corner
+| 4048: 16 02 05 00 09 2f 00 75 70 70 65 72 2d 6c 65 66 ...../.upper-lef
+| 4064: 74 20 63 6f 72 6e 65 72 06 00 05 00 01 00 00 03 t corner........
+| 4080: 0d 88 4f 05 00 01 1b 00 02 62 6f 78 2d 33 2c 31 ..O......box-3,1
+| page 3 offset 8192
+| 0: 05 00 00 00 01 0f fb 00 00 00 00 06 0f fb 00 00 ................
+| 384: 00 00 00 00 00 00 00 89 50 03 04 00 93 24 00 00 ........P....$..
+| 400: 00 11 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 688: 00 00 00 00 42 c8 00 00 42 4c 00 00 42 00 00 00 ....B...BL..B...
+| 720: 03 eb 40 40 00 00 40 80 00 00 00 00 00 00 3f 80 ..@@..@.......?.
+| 736: 00 00 00 00 00 00 00 00 03 ea 40 00 00 00 40 40 ..........@...@@
+| 752: 00 00 00 00 00 00 3f 80 00 00 00 00 00 00 00 00 ......?.........
+| 768: 03 e9 3f 80 00 00 40 00 00 00 00 00 00 00 3f 80 ..?...@.......?.
+| 784: 00 00 00 00 00 00 00 00 03 e8 00 00 00 00 3f 80 ..............?.
+| 800: 00 00 00 00 00 00 3f 80 00 00 00 00 00 00 00 00 ......?.........
+| 1616: 00 00 00 00 00 00 00 00 00 00 89 50 02 04 00 93 ...........P....
+| 1632: 24 00 00 00 33 00 00 00 00 00 00 00 01 00 00 00 $...3...........
+| 1648: 00 41 20 00 00 00 00 00 00 41 0e 00 00 00 00 00 .A ......A......
+| 1664: 00 00 00 04 4f 40 40 00 00 40 80 00 00 3f 80 00 ....O@@..@...?..
+| 1680: 00 40 00 00 00 00 00 00 00 00 00 04 4e 40 00 00 .@..........N@..
+| 1696: 00 40 40 00 00 3f 80 00 00 40 00 00 00 00 00 00 .@@..?...@......
+| 1712: 00 00 00 04 4d 3f 80 00 00 40 00 00 00 3f 80 00 ....M?...@...?..
+| 1728: 00 40 00 00 00 00 00 00 00 00 00 04 4c 00 00 00 .@..........L...
+| 1744: 00 3f 80 00 00 3f 80 00 00 40 00 00 00 00 00 00 .?...?...@......
+| 1760: 00 00 00 04 b3 40 40 00 00 40 80 00 00 40 00 00 .....@@..@...@..
+| 1776: 00 40 40 00 00 00 00 00 00 00 00 04 b2 40 00 00 .@@..........@..
+| 1792: 00 40 40 00 00 40 00 00 00 40 40 00 00 00 00 00 .@@..@...@@.....
+| 1808: 00 00 00 04 b1 3f 80 00 00 40 00 00 00 40 00 00 .....?...@...@..
+| 1824: 00 40 40 00 00 00 00 00 00 00 00 04 b0 00 00 00 .@@.............
+| 1840: 00 3f 80 00 00 40 00 00 00 40 40 00 00 00 00 00 .?...@...@@.....
+| 1856: 00 00 00 05 17 40 40 00 00 40 80 00 00 40 40 00 .....@@..@...@@.
+| 1872: 00 40 80 00 00 00 00 00 00 00 00 05 16 40 00 00 .@...........@..
+| 1888: 00 40 40 00 00 40 40 00 00 40 80 00 00 00 00 00 .@@..@@..@......
+| 1904: 00 00 00 05 15 3f 80 00 00 40 00 00 00 40 40 00 .....?...@...@@.
+| 1920: 00 40 80 00 00 00 00 00 00 00 00 05 14 00 00 00 .@..............
+| 1936: 00 3f 80 00 00 40 40 00 00 40 80 00 00 00 00 00 .?...@@..@......
+| 1952: 00 00 00 05 7b 40 40 00 00 40 80 00 00 40 80 00 .....@@..@...@..
+| 1968: 00 40 a0 00 00 00 00 00 00 00 00 05 7a 40 00 00 .@..........z@..
+| 1984: 00 40 40 00 00 40 80 00 00 40 a0 00 00 00 00 00 .@@..@...@......
+| 2000: 00 00 00 05 79 3f 80 00 00 40 00 00 00 40 80 00 ....y?...@...@..
+| 2016: 00 40 a0 00 00 00 00 00 00 00 00 05 78 00 00 00 .@..........x...
+| 2032: 00 3f 80 00 00 40 80 00 00 40 a0 00 00 00 00 00 .?...@...@......
+| 2048: 00 00 00 05 df 40 40 00 00 40 80 00 00 40 a0 00 .....@@..@...@..
+| 2064: 00 40 c0 00 00 00 00 00 00 00 00 05 de 40 00 00 .@...........@..
+| 2080: 00 40 40 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .@@..@...@......
+| 2096: 00 00 00 05 dd 3f 80 00 00 40 00 00 00 40 a0 00 .....?...@...@..
+| 2112: 00 40 c0 00 00 00 00 00 00 00 00 05 dc 00 00 00 .@..............
+| 2128: 00 3f 80 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .?...@...@......
+| 2144: 00 00 00 06 43 40 40 00 00 40 80 00 00 40 c0 00 ....C@@..@...@..
+| 2160: 00 40 e0 00 00 00 00 00 00 00 00 06 42 40 00 00 .@..........B@..
+| 2176: 00 40 40 00 00 40 c0 00 00 40 e0 00 00 00 00 00 .@@..@...@......
+| 2192: 00 00 00 06 41 3f 80 00 00 40 00 00 00 40 c0 00 ....A?...@...@..
+| 2208: 00 40 e0 00 00 00 00 00 00 00 00 06 40 00 00 00 .@..........@...
+| 2224: 00 3f 80 00 00 40 c0 00 00 40 e0 00 00 00 00 00 .?...@...@......
+| 2240: 00 00 00 06 a7 40 40 00 00 40 80 00 00 40 e0 00 .....@@..@...@..
+| 2256: 00 41 00 00 00 00 00 00 00 00 00 06 a6 40 00 00 .A...........@..
+| 2272: 00 40 40 00 00 40 e0 00 00 41 00 00 00 00 00 00 .@@..@...A......
+| 2288: 00 00 00 06 a5 3f 80 00 00 40 00 00 00 40 e0 00 .....?...@...@..
+| 2304: 00 41 00 00 00 00 00 00 00 00 00 06 a4 00 00 00 .A..............
+| 2320: 00 3f 80 00 00 40 e0 00 00 41 00 00 00 00 00 00 .?...@...A......
+| 2336: 00 00 00 07 0a 40 00 00 00 40 40 00 00 41 00 00 .....@...@@..A..
+| 2352: 00 41 10 00 00 00 00 00 00 00 00 07 09 3f 80 00 .A...........?..
+| 2368: 00 40 00 00 00 41 00 00 00 41 10 00 00 00 00 00 .@...A...A......
+| 2384: 00 00 00 07 08 00 00 00 00 3f 80 00 00 41 00 00 .........?...A..
+| 2400: 00 41 10 00 00 00 00 00 00 00 00 07 6e 40 00 00 .A..........n@..
+| 2416: 00 40 40 00 00 41 10 00 00 41 20 00 00 00 00 00 .@@..A...A .....
+| 2432: 00 00 00 07 6d 3f 80 00 00 40 00 00 00 41 10 00 ....m?...@...A..
+| 2448: 00 41 20 00 00 00 00 00 00 00 00 07 6c 00 00 00 .A .........l...
+| 2464: 00 3f 80 00 00 41 10 00 00 41 20 00 00 00 00 00 .?...A...A .....
+| 2480: 00 00 00 07 0b 40 40 00 00 40 80 00 00 41 00 00 .....@@..@...A..
+| 2496: 00 41 10 00 00 00 00 00 00 00 00 07 6f 40 40 00 .A..........o@@.
+| 2512: 00 40 80 00 00 41 10 00 00 41 20 00 00 00 00 00 .@...A...A .....
+| 2528: 00 00 00 03 ec 40 80 00 00 40 a0 00 00 00 00 00 .....@...@......
+| 2544: 00 3f 80 00 00 00 00 00 00 00 00 04 50 40 80 00 .?..........P@..
+| 2560: 00 40 a0 00 00 3f 80 00 00 40 00 00 00 00 00 00 .@...?...@......
+| 2576: 00 00 00 04 b4 40 80 00 00 40 a0 00 00 40 00 00 .....@...@...@..
+| 2592: 00 40 40 00 00 00 00 00 00 00 00 05 18 40 80 00 .@@..........@..
+| 2608: 00 40 a0 00 00 40 40 00 00 40 80 00 00 00 00 00 .@...@@..@......
+| 2624: 00 00 00 05 7c 40 80 00 00 40 a0 00 00 40 80 00 ....|@...@...@..
+| 2640: 00 40 a0 00 00 00 00 00 00 00 00 05 e0 40 80 00 .@...........@..
+| 2656: 00 40 a0 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .@...@...@......
+| 2672: 00 00 00 06 44 40 80 00 00 40 a0 00 00 40 c0 00 ....D@...@...@..
+| 2688: 00 40 e0 00 00 00 00 00 00 00 00 06 a8 40 80 00 .@...........@..
+| 2704: 00 40 a0 00 00 40 e0 00 00 41 00 00 00 00 00 00 .@...@...A......
+| 2720: 00 00 00 07 0c 40 80 00 00 40 a0 00 00 41 00 00 .....@...@...A..
+| 2736: 00 41 10 00 00 00 00 00 00 00 00 07 70 40 80 00 .A..........p@..
+| 2752: 00 40 a0 00 00 41 10 00 00 41 20 00 00 00 00 00 .@...A...A .....
+| 2768: 00 00 00 03 ed 40 a0 00 00 40 c0 00 00 00 00 00 .....@...@......
+| 2784: 00 3f 80 00 00 00 00 00 00 00 00 04 51 40 a0 00 .?..........Q@..
+| 2800: 00 40 c0 00 00 3f 80 00 00 40 00 00 00 00 00 00 .@...?...@......
+| 2816: 00 00 00 04 b5 40 a0 00 00 40 c0 00 00 40 00 00 .....@...@...@..
+| 2832: 00 40 40 00 00 00 00 00 00 00 00 05 19 40 a0 00 .@@..........@..
+| 2848: 00 40 c0 00 00 40 40 00 00 40 80 00 00 89 50 01 .@...@@..@....P.
+| 2864: 04 00 93 24 00 01 00 02 00 00 00 00 00 00 00 03 ...$............
+| 2880: 00 00 00 00 40 80 00 00 00 00 00 00 3f 80 00 00 ....@.......?...
+| 2896: 00 00 00 00 00 00 00 02 00 00 00 00 41 20 00 00 ............A ..
+| 2912: 00 00 00 00 41 20 00 00 00 00 00 00 00 00 00 00 ....A ..........
+| 4080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 03 ................
+| page 4 offset 12288
+| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 ................
+| page 5 offset 16384
+| 0: 0d 00 00 00 03 01 87 00 0b 2d 06 5a 01 87 00 00 .........-.Z....
+| 384: 00 00 00 00 00 00 00 89 50 03 04 00 93 24 00 00 ........P....$..
+| 400: 00 11 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 688: 00 00 00 00 42 c8 00 00 42 4c 00 00 42 00 00 00 ....B...BL..B...
+| 720: 03 eb 40 40 00 00 40 80 00 00 00 00 00 00 3f 80 ..@@..@.......?.
+| 736: 00 00 00 00 00 00 00 00 03 ea 40 00 00 00 40 40 ..........@...@@
+| 752: 00 00 00 00 00 00 3f 80 00 00 00 00 00 00 00 00 ......?.........
+| 768: 03 e9 3f 80 00 00 40 00 00 00 00 00 00 00 3f 80 ..?...@.......?.
+| 784: 00 00 00 00 00 00 00 00 03 e8 00 00 00 00 3f 80 ..............?.
+| 800: 00 00 00 00 00 00 3f 80 00 00 00 00 00 00 00 00 ......?.........
+| 1616: 00 00 00 00 00 00 00 00 00 00 89 50 02 04 00 93 ...........P....
+| 1632: 24 00 00 00 2d 00 00 00 00 00 00 04 4c 00 00 00 $...-.......L...
+| 1648: 00 3f 80 00 00 3f 80 00 00 40 00 00 00 00 00 00 .?...?...@......
+| 1664: 00 00 00 04 b0 00 00 00 00 3f 80 00 00 40 00 00 .........?...@..
+| 1680: 00 40 40 00 00 00 00 00 00 00 00 05 14 00 00 00 .@@.............
+| 1696: 00 3f 80 00 00 40 40 00 00 40 80 00 00 00 00 00 .?...@@..@......
+| 1712: 00 00 00 05 78 00 00 00 00 3f 80 00 00 40 80 00 ....x....?...@..
+| 1728: 00 40 a0 00 00 00 00 00 00 00 00 05 dc 00 00 00 .@..............
+| 1744: 00 3f 80 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .?...@...@......
+| 1760: 00 00 00 00 01 00 00 00 00 41 20 00 00 00 00 00 .........A .....
+| 1776: 00 41 0e 00 00 00 00 00 00 00 00 04 4d 3f 80 00 .A..........M?..
+| 1792: 00 40 00 00 00 3f 80 00 00 40 00 00 00 00 00 00 .@...?...@......
+| 1808: 00 00 00 04 b1 3f 80 00 00 40 00 00 00 40 00 00 .....?...@...@..
+| 1824: 00 40 40 00 00 00 00 00 00 00 00 05 15 3f 80 00 .@@..........?..
+| 1840: 00 40 00 00 00 40 40 00 00 40 80 00 00 00 00 00 .@...@@..@......
+| 1856: 00 00 00 05 79 3f 80 00 00 40 00 00 00 40 80 00 ....y?...@...@..
+| 1872: 00 40 a0 00 00 00 00 00 00 00 00 05 dd 3f 80 00 .@...........?..
+| 1888: 00 40 00 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .@...@...@......
+| 1904: 00 00 00 04 4e 40 00 00 00 40 40 00 00 3f 80 00 ....N@...@@..?..
+| 1920: 00 40 00 00 00 00 00 00 00 00 00 04 b2 40 00 00 .@...........@..
+| 1936: 00 40 40 00 00 40 00 00 00 40 40 00 00 00 00 00 .@@..@...@@.....
+| 1952: 00 00 00 05 16 40 00 00 00 40 40 00 00 40 40 00 .....@...@@..@@.
+| 1968: 00 40 80 00 00 00 00 00 00 00 00 05 7a 40 00 00 .@..........z@..
+| 1984: 00 40 40 00 00 40 80 00 00 40 a0 00 00 00 00 00 .@@..@...@......
+| 2000: 00 00 00 05 de 40 00 00 00 40 40 00 00 40 a0 00 .....@...@@..@..
+| 2016: 00 40 c0 00 00 00 00 00 00 00 00 04 4f 40 40 00 .@..........O@@.
+| 2032: 00 40 80 00 00 3f 80 00 00 40 00 00 00 00 00 00 .@...?...@......
+| 2048: 00 00 00 04 b3 40 40 00 00 40 80 00 00 40 00 00 .....@@..@...@..
+| 2064: 00 40 40 00 00 00 00 00 00 00 00 05 17 40 40 00 .@@..........@@.
+| 2080: 00 40 80 00 00 40 40 00 00 40 80 00 00 00 00 00 .@...@@..@......
+| 2096: 00 00 00 05 7b 40 40 00 00 40 80 00 00 40 80 00 .....@@..@...@..
+| 2112: 00 40 a0 00 00 00 00 00 00 00 00 05 df 40 40 00 .@...........@@.
+| 2128: 00 40 80 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .@...@...@......
+| 2144: 00 00 00 03 ec 40 80 00 00 40 a0 00 00 00 00 00 .....@...@......
+| 2160: 00 3f 80 00 00 00 00 00 00 00 00 04 50 40 80 00 .?..........P@..
+| 2176: 00 40 a0 00 00 3f 80 00 00 40 00 00 00 00 00 00 .@...?...@......
+| 2192: 00 00 00 04 b4 40 80 00 00 40 a0 00 00 40 00 00 .....@...@...@..
+| 2208: 00 40 40 00 00 00 00 00 00 00 00 05 18 40 80 00 .@@..........@..
+| 2224: 00 40 a0 00 00 40 40 00 00 40 80 00 00 00 00 00 .@...@@..@......
+| 2240: 00 00 00 05 7c 40 80 00 00 40 a0 00 00 40 80 00 ....|@...@...@..
+| 2256: 00 40 a0 00 00 00 00 00 00 00 00 05 e0 40 80 00 .@...........@..
+| 2272: 00 40 a0 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .@...@...@......
+| 2288: 00 00 00 03 f0 41 00 00 00 41 10 00 00 00 00 00 .....A...A......
+| 2304: 00 3f 80 00 00 00 00 00 00 00 00 04 54 41 00 00 .?..........TA..
+| 2320: 00 41 10 00 00 3f 80 00 00 40 00 00 00 00 00 00 .A...?...@......
+| 2336: 00 00 00 04 b8 41 00 00 00 41 10 00 00 40 00 00 .....A...A...@..
+| 2352: 00 40 40 00 00 00 00 00 00 00 00 05 1c 41 00 00 .@@..........A..
+| 2368: 00 41 10 00 00 40 40 00 00 40 80 00 00 00 00 00 .A...@@..@......
+| 2384: 00 00 00 05 80 41 00 00 00 41 10 00 00 40 80 00 .....A...A...@..
+| 2400: 00 40 a0 00 00 00 00 00 00 00 00 05 e4 41 00 00 .@...........A..
+| 2416: 00 41 10 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .A...@...@......
+| 2432: 00 00 00 06 48 41 00 00 00 41 10 00 00 40 c0 00 ....HA...A...@..
+| 2448: 00 40 e0 00 00 00 00 00 00 00 00 06 ac 41 00 00 .@...........A..
+| 2464: 00 41 10 00 00 40 e0 00 00 41 00 00 00 00 00 00 .A...@...A......
+| 2480: 00 00 00 07 10 41 00 00 00 41 10 00 00 41 00 00 .....A...A...A..
+| 2496: 00 41 10 00 00 00 00 00 00 00 00 03 f1 41 10 00 .A...........A..
+| 2512: 00 41 20 00 00 00 00 00 00 3f 80 00 00 00 00 00 .A ......?......
+| 2528: 00 00 00 04 55 41 10 00 00 41 20 00 00 3f 80 00 ....UA...A ..?..
+| 2544: 00 40 00 00 00 00 00 00 00 00 00 04 b9 41 10 00 .@...........A..
+| 2560: 00 41 20 00 00 40 00 00 00 40 40 00 00 00 00 00 .A ..@...@@.....
+| 2576: 00 00 00 05 1d 41 10 00 00 41 20 00 00 40 40 00 .....A...A ..@@.
+| 2592: 00 40 80 00 00 00 00 00 00 00 00 05 81 41 10 00 .@...........A..
+| 2608: 00 41 20 00 00 40 80 00 00 40 a0 00 00 00 00 00 .A ..@...@......
+| 2624: 00 00 00 05 e5 41 10 00 00 41 20 00 00 40 a0 00 .....A...A ..@..
+| 2640: 00 40 c0 00 00 00 00 00 00 00 00 06 49 41 10 00 .@..........IA..
+| 2656: 00 41 20 00 00 40 c0 00 00 40 e0 00 00 00 00 00 .A ..@...@......
+| 2672: 00 00 00 06 ad 41 10 00 00 41 20 00 00 40 e0 00 .....A...A ..@..
+| 2688: 00 41 00 00 00 00 00 00 00 00 00 07 11 41 10 00 .A...........A..
+| 2704: 00 41 20 00 00 41 00 00 00 41 10 00 00 00 00 00 .A ..A...A......
+| 2848: 00 00 00 00 00 00 00 00 00 00 00 00 00 89 50 01 ..............P.
+| 2864: 04 00 93 24 00 01 00 04 00 00 00 00 00 00 00 03 ...$............
+| 2880: 00 00 00 00 40 80 00 00 00 00 00 00 3f 80 00 00 ....@.......?...
+| 2896: 00 00 00 00 00 00 00 02 00 00 00 00 41 20 00 00 ............A ..
+| 2912: 00 00 00 00 41 10 00 00 00 00 00 00 00 00 00 04 ....A...........
+| 2928: 00 00 00 00 41 20 00 00 40 c0 00 00 41 20 00 00 ....A ..@...A ..
+| 2944: 00 00 00 00 00 00 00 05 40 a0 00 00 41 00 00 00 ........@...A...
+| 2960: 00 00 00 00 41 20 00 00 00 00 00 00 00 00 00 00 ....A ..........
+| page 6 offset 20480
+| 0: 0d 00 00 00 02 06 5a 00 0b 2d 06 5a 00 00 00 00 ......Z..-.Z....
+| 1616: 00 00 00 00 00 00 00 00 00 00 89 50 05 04 00 93 ...........P....
+| 1632: 24 00 00 00 1c 00 00 00 00 00 00 03 ed 40 a0 00 $............@..
+| 1648: 00 40 c0 00 00 00 00 00 00 3f 80 00 00 00 00 00 .@.......?......
+| 1664: 00 00 00 04 51 40 a0 00 00 40 c0 00 00 3f 80 00 ....Q@...@...?..
+| 1680: 00 40 00 00 00 00 00 00 00 00 00 04 b5 40 a0 00 .@...........@..
+| 1696: 00 40 c0 00 00 40 00 00 00 40 40 00 00 00 00 00 .@...@...@@.....
+| 1712: 00 00 00 05 19 40 a0 00 00 40 c0 00 00 40 40 00 .....@...@...@@.
+| 1728: 00 40 80 00 00 00 00 00 00 00 00 05 7d 40 a0 00 .@...........@..
+| 1744: 00 40 c0 00 00 40 80 00 00 40 a0 00 00 00 00 00 .@...@...@......
+| 1760: 00 00 00 05 e1 40 a0 00 00 40 c0 00 00 40 a0 00 .....@...@...@..
+| 1776: 00 40 c0 00 00 00 00 00 00 00 00 06 45 40 a0 00 .@..........E@..
+| 1792: 00 40 c0 00 00 40 c0 00 00 40 e0 00 00 00 00 00 .@...@...@......
+| 1808: 00 00 00 06 a9 40 a0 00 00 40 c0 00 00 40 e0 00 .....@...@...@..
+| 1824: 00 41 00 00 00 00 00 00 00 00 00 07 0d 40 a0 00 .A...........@..
+| 1840: 00 40 c0 00 00 41 00 00 00 41 10 00 00 00 00 00 .@...A...A......
+| 1856: 00 00 00 03 ee 40 c0 00 00 40 e0 00 00 00 00 00 .....@...@......
+| 1872: 00 3f 80 00 00 00 00 00 00 00 00 04 52 40 c0 00 .?..........R@..
+| 1888: 00 40 e0 00 00 3f 80 00 00 40 00 00 00 00 00 00 .@...?...@......
+| 1904: 00 00 00 04 b6 40 c0 00 00 40 e0 00 00 40 00 00 .....@...@...@..
+| 1920: 00 40 40 00 00 00 00 00 00 00 00 05 1a 40 c0 00 .@@..........@..
+| 1936: 00 40 e0 00 00 40 40 00 00 40 80 00 00 00 00 00 .@...@@..@......
+| 1952: 00 00 00 05 7e 40 c0 00 00 40 e0 00 00 40 80 00 ....~@...@...@..
+| 1968: 00 40 a0 00 00 00 00 00 00 00 00 05 e2 40 c0 00 .@...........@..
+| 1984: 00 40 e0 00 00 40 a0 00 00 40 c0 00 00 00 00 00 .@...@...@......
+| 2000: 00 00 00 06 46 40 c0 00 00 40 e0 00 00 40 c0 00 ....F@...@...@..
+| 2016: 00 40 e0 00 00 00 00 00 00 00 00 06 aa 40 c0 00 .@...........@..
+| 2032: 00 40 e0 00 00 40 e0 00 00 41 00 00 00 00 00 00 .@...@...A......
+| 2048: 00 00 00 07 0e 40 c0 00 00 40 e0 00 00 41 00 00 .....@...@...A..
+| 2064: 00 41 10 00 00 00 00 00 00 00 00 03 ef 40 e0 00 .A...........@..
+| 2080: 00 41 00 00 00 00 00 00 00 3f 80 00 00 00 00 00 .A.......?......
+| 2096: 00 00 00 04 53 40 e0 00 00 41 00 00 00 3f 80 00 ....S@...A...?..
+| 2112: 00 40 00 00 00 00 00 00 00 00 00 04 b7 40 e0 00 .@...........@..
+| 2128: 00 41 00 00 00 40 00 00 00 40 40 00 00 00 00 00 .A...@...@@.....
+| 2144: 00 00 00 05 1b 40 e0 00 00 41 00 00 00 40 40 00 .....@...A...@@.
+| 2160: 00 40 80 00 00 00 00 00 00 00 00 05 7f 40 e0 00 .@...........@..
+| 2176: 00 41 00 00 00 40 80 00 00 40 a0 00 00 00 00 00 .A...@...@......
+| 2192: 00 00 00 05 e3 40 e0 00 00 41 00 00 00 40 a0 00 .....@...A...@..
+| 2208: 00 40 c0 00 00 00 00 00 00 00 00 06 47 40 e0 00 .@..........G@..
+| 2224: 00 41 00 00 00 40 c0 00 00 40 e0 00 00 00 00 00 .A...@...@......
+| 2240: 00 00 00 06 ab 40 e0 00 00 41 00 00 00 40 e0 00 .....@...A...@..
+| 2256: 00 41 00 00 00 00 00 00 00 00 00 07 0f 40 e0 00 .A...........@..
+| 2272: 00 41 00 00 00 41 00 00 00 41 10 00 00 00 00 00 .A...A...A......
+| 2288: 00 00 00 07 73 40 e0 00 00 41 00 00 00 41 10 00 ....s@...A...A..
+| 2304: 00 41 20 00 00 00 00 00 00 00 00 00 00 00 00 00 .A .............
+| 2848: 00 00 00 00 00 00 00 00 00 00 00 00 00 89 50 04 ..............P.
+| 2864: 04 00 93 24 00 00 00 18 00 00 00 00 00 00 06 43 ...$...........C
+| 2880: 40 40 00 00 40 80 00 00 40 c0 00 00 40 e0 00 00 @@..@...@...@...
+| 2896: 00 00 00 00 00 00 06 42 40 00 00 00 40 40 00 00 .......B@...@@..
+| 2912: 40 c0 00 00 40 e0 00 00 00 00 00 00 00 00 06 41 @...@..........A
+| 2928: 3f 80 00 00 40 00 00 00 40 c0 00 00 40 e0 00 00 ?...@...@...@...
+| 2944: 00 00 00 00 00 00 06 40 00 00 00 00 3f 80 00 00 .......@....?...
+| 2960: 40 c0 00 00 40 e0 00 00 00 00 00 00 00 00 06 44 @...@..........D
+| 2976: 40 80 00 00 40 a0 00 00 40 c0 00 00 40 e0 00 00 @...@...@...@...
+| 2992: 00 00 00 00 00 00 06 a7 40 40 00 00 40 80 00 00 ........@@..@...
+| 3008: 40 e0 00 00 41 00 00 00 00 00 00 00 00 00 06 a6 @...A...........
+| 3024: 40 00 00 00 40 40 00 00 40 e0 00 00 41 00 00 00 @...@@..@...A...
+| 3040: 00 00 00 00 00 00 06 a5 3f 80 00 00 40 00 00 00 ........?...@...
+| 3056: 40 e0 00 00 41 00 00 00 00 00 00 00 00 00 06 a4 @...A...........
+| 3072: 00 00 00 00 3f 80 00 00 40 e0 00 00 41 00 00 00 ....?...@...A...
+| 3088: 00 00 00 00 00 00 06 a8 40 80 00 00 40 a0 00 00 ........@...@...
+| 3104: 40 e0 00 00 41 00 00 00 00 00 00 00 00 00 07 0a @...A...........
+| 3120: 40 00 00 00 40 40 00 00 41 00 00 00 41 10 00 00 @...@@..A...A...
+| 3136: 00 00 00 00 00 00 07 09 3f 80 00 00 40 00 00 00 ........?...@...
+| 3152: 41 00 00 00 41 10 00 00 00 00 00 00 00 00 07 08 A...A...........
+| 3168: 00 00 00 00 3f 80 00 00 41 00 00 00 41 10 00 00 ....?...A...A...
+| 3184: 00 00 00 00 00 00 07 0b 40 40 00 00 40 80 00 00 ........@@..@...
+| 3200: 41 00 00 00 41 10 00 00 00 00 00 00 00 00 07 0c A...A...........
+| 3216: 40 80 00 00 40 a0 00 00 41 00 00 00 41 10 00 00 @...@...A...A...
+| 3232: 00 00 00 00 00 00 07 6e 40 00 00 00 40 40 00 00 .......n@...@@..
+| 3248: 41 10 00 00 41 20 00 00 00 00 00 00 00 00 07 6d A...A .........m
+| 3264: 3f 80 00 00 40 00 00 00 41 10 00 00 41 20 00 00 ?...@...A...A ..
+| 3280: 00 00 00 00 00 00 07 6c 00 00 00 00 3f 80 00 00 .......l....?...
+| 3296: 41 10 00 00 41 20 00 00 00 00 00 00 00 00 07 6f A...A .........o
+| 3312: 40 40 00 00 40 80 00 00 41 10 00 00 41 20 00 00 @@..@...A...A ..
+| 3328: 00 00 00 00 00 00 07 70 40 80 00 00 40 a0 00 00 .......p@...@...
+| 3344: 41 10 00 00 41 20 00 00 00 00 00 00 00 00 07 71 A...A .........q
+| 3360: 40 a0 00 00 40 c0 00 00 41 10 00 00 41 20 00 00 @...@...A...A ..
+| 3376: 00 00 00 00 00 00 07 72 40 c0 00 00 40 e0 00 00 .......r@...@...
+| 3392: 41 10 00 00 41 20 00 00 00 00 00 00 00 00 07 74 A...A .........t
+| 3408: 41 00 00 00 41 10 00 00 41 10 00 00 41 20 00 00 A...A...A...A ..
+| 3424: 00 00 00 00 00 00 07 75 41 10 00 00 41 20 00 00 .......uA...A ..
+| 3440: 41 10 00 00 41 20 00 00 00 00 00 00 00 00 00 00 A...A ..........
+| end c1b.db
+ }]
+ catchsql {
+ PRAGMA writable_schema = 1;
+ SELECT rtreecheck('t1');
+ }
+} {1 {SQL logic error}}
+
+do_test rtreefuzz001-200 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 16384 pagesize 4096 filename c3.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 04 .....@ ........
+| 32: 00 00 00 00 01 00 00 00 00 00 00 04 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 96: 00 00 00 00 0d 00 00 00 04 0e 9c 00 0f ad 0f 4f ...............O
+| 112: 0e fc 0e 9c 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 3728: 00 00 00 00 00 00 00 00 00 00 00 00 5e 04 07 17 ............^...
+| 3744: 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 72 .....tablet1_par
+| 3760: 65 6e 74 74 31 5f 70 61 72 65 6e 74 04 43 52 45 entt1_parent.CRE
+| 3776: 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 61 ATE TABLE .t1_pa
+| 3792: 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 rent.(nodeno INT
+| 3808: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY
+| 3824: 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 03 06 17 ,parentnode)Q...
+| 3840: 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 65 ....tablet1_node
+| 3856: 74 31 5f 6e 6f 64 65 03 43 52 45 41 54 45 20 54 t1_node.CREATE T
+| 3872: 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 6e ABLE .t1_node.(n
+| 3888: 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 52 odeno INTEGER PR
+| 3904: 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 5c IMARY KEY,data).
+| 3920: 02 07 17 1d 1d 01 81 0b 74 61 62 6c 65 74 31 5f ........tablet1_
+| 3936: 72 6f 77 69 64 74 31 5f 72 6f 77 69 64 02 43 52 rowidt1_rowid.CR
+| 3952: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 72 EATE TABLE .t1_r
+| 3968: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE
+| 3984: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY,
+| 4000: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 29 51 01 07 nodeno,a0,a1)Q..
+| 4016: 17 11 11 08 81 0f 74 61 62 6c 65 74 31 74 31 43 ......tablet1t1C
+| 4032: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA
+| 4048: 42 4c 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 BLE t1 USING rtr
+| 4064: 65 65 28 69 64 2c 78 30 2c 78 31 2c 79 30 2c 79 ee(id,x0,x1,y0,y
+| 4080: 31 2c 2b 6c 61 62 65 6c 2c 2b 6f 74 68 65 72 29 1,+label,+other)
+| page 2 offset 4096
+| 0: 0d 00 00 00 0e 0e f7 00 0f e8 0f d0 0f b7 0f 9e ................
+| 16: 0f 91 0f 81 0f 70 0f 5e 0f 4f 0f 39 0f 29 0f 18 .....p.^.O.9.)..
+| 32: 0f 06 0e f7 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 3824: 00 00 00 00 00 00 00 0d 0e 05 00 09 1d 00 74 6f ..............to
+| 3840: 70 20 68 61 6c 66 10 0d 05 00 09 23 00 62 6f 74 p half.....#.bot
+| 3856: 74 6f 6d 20 68 61 6c 66 0f 0c 05 00 09 21 00 72 tom half.....!.r
+| 3872: 69 67 68 74 20 68 61 6c 66 0e 0b 05 00 09 1f 00 ight half.......
+| 3888: 6c 65 66 74 20 68 61 6c 66 14 0a 05 00 09 2b 00 left half.....+.
+| 3904: 74 68 65 20 77 68 6f 6c 65 20 74 68 69 6e 67 0d the whole thing.
+| 3920: 09 05 00 09 1d 00 74 6f 70 20 65 64 67 65 10 08 ......top edge..
+| 3936: 05 00 09 23 00 62 6f 74 74 6f 6d 20 65 64 67 65 ...#.bottom edge
+| 3952: 0f 07 05 00 09 21 00 72 69 67 68 74 20 65 64 67 .....!.right edg
+| 3968: 65 0e 06 05 00 09 1f 00 6c 65 66 74 20 65 64 67 e.......left edg
+| 3984: 65 0b 05 05 00 09 19 00 63 65 6e 74 65 72 17 04 e.......center..
+| 4000: 05 00 09 31 00 75 70 70 65 72 2d 72 69 67 68 74 ...1.upper-right
+| 4016: 20 63 6f 72 6e 65 72 17 03 05 00 09 31 00 6c 6f corner.....1.lo
+| 4032: 77 65 72 2d 72 69 67 68 74 27 60 f6 32 6e 65 72 wer-right'`.2ner
+| 4048: 16 02 05 00 09 2f 00 75 70 70 65 72 2d 6c 65 66 ...../.upper-lef
+| 4064: 74 20 63 6f 72 6e 65 72 16 01 05 00 09 2f 00 6c t corner...../.l
+| 4080: 6f 77 65 72 2d 6c 65 66 74 20 63 6f 72 6e 65 72 ower-left corner
+| page 3 offset 8192
+| 0: 0d 00 00 00 02 0b 2d 00 0b 2d 00 00 00 00 00 00 ......-..-......
+| 2848: 00 00 00 00 00 00 00 00 00 00 00 00 00 89 50 01 ..............P.
+| 2864: 04 00 93 24 00 00 00 0e 00 00 00 00 00 00 00 01 ...$............
+| 2880: 00 00 00 00 41 20 00 00 00 00 00 00 41 20 01 00 ....A ......A ..
+| 2896: 00 00 00 00 00 00 00 02 00 00 00 00 41 00 00 04 ............A...
+| 2912: 2b 40 00 0c 42 c8 00 00 00 00 00 00 00 00 00 03 +@..B...........
+| 2928: 42 b4 00 00 42 c8 00 00 00 00 00 00 41 20 00 00 B...B.......A ..
+| 2944: 00 00 00 00 00 00 00 04 42 b4 00 00 42 c8 00 00 ........B...B...
+| 2960: 42 b4 00 00 42 c8 00 00 00 00 00 00 00 00 00 05 B...B...........
+| 2976: 42 20 00 00 42 70 00 00 42 20 00 00 42 70 00 00 B ..Bp..B ..Bp..
+| 2992: 00 00 00 00 00 00 00 60 00 00 00 04 0a 00 00 00 .......`........
+| 3008: 00 00 00 42 c8 00 00 00 00 00 00 00 00 00 07 42 ...B...........B
+| 3024: be 00 00 42 c8 00 00 00 00 00 00 42 c8 00 00 00 ...B.......B....
+| 3040: 00 00 00 00 00 00 08 00 00 00 00 42 c8 00 00 00 ...........B....
+| 3056: 00 00 00 40 a0 00 00 00 00 00 00 00 00 00 09 00 ...@............
+| 3072: 00 00 00 42 c8 00 00 42 be 00 00 42 c8 00 00 00 ...B...B...B....
+| 3088: 00 00 00 00 00 00 0a 00 00 00 00 42 c8 00 00 00 ...........B....
+| 3104: 00 00 00 42 c8 00 00 00 00 00 00 00 00 00 0b 00 ...B............
+| 3120: 00 00 00 42 48 00 00 00 00 00 04 2c 80 00 00 00 ...BH......,....
+| 3136: 00 00 00 00 00 00 c4 24 c0 00 04 2c 80 00 00 00 .......$...,....
+| 3152: 00 00 04 2c 80 00 00 00 00 00 00 00 00 00 d0 00 ...,............
+| 3168: 00 00 04 2c 80 00 00 00 00 00 04 24 80 00 00 00 ...,.......$....
+| 3184: 00 00 00 00 00 00 e0 00 00 00 04 2c 80 00 04 24 ...........,...$
+| 3200: c0 00 04 2c 00 00 00 00 00 00 00 00 00 00 00 00 ...,............
+| page 4 offset 12288
+| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 ................
+| end c3.db
+ }]
+ catchsql {
+ WITH RECURSIVE
+ c1(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c1 WHERE x<99),
+ c2(y) AS (VALUES(0) UNION ALL SELECT y+1 FROM c2 WHERE y<99)
+ INSERT INTO t1(id, x0,x1,y0,y1,label)
+ SELECT 1000+x+y*100, x, x+1, y, y+1, printf('box-%d,%d',x,y) FROM c1, c2;
+ }
+} {1 {database disk image is malformed}}
+do_test rtreefuzz001-210 {
+ catchsql {
+ SELECT rtreecheck('t1');
+ }
+} {/1 .*corrupt.*/}
+
+do_test rtreefuzz001-300 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 16384 pagesize 4096 filename c4.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 04 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00 04 ................
+| 96: 00 00 00 00 0d 00 00 00 04 0e 9c 00 0f ad 0f 4f ...............O
+| 112: 0e fc 0e 9c 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 3728: 00 00 00 00 00 00 00 00 00 00 00 00 5e 04 07 17 ............^...
+| 3744: 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 72 .....tablet1_par
+| 3760: 65 6e 74 74 31 5f 70 61 72 65 6e 74 04 43 52 45 entt1_parent.CRE
+| 3776: 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 61 ATE TABLE .t1_pa
+| 3792: 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 rent.(nodeno INT
+| 3808: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY
+| 3824: 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 03 06 17 ,parentnode)Q...
+| 3840: 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 65 ....tablet1_node
+| 3856: 74 31 5f 6e 6f 64 65 03 43 52 45 41 54 45 20 54 t1_node.CREATE T
+| 3872: 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 6e ABLE .t1_node.(n
+| 3888: 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 52 odeno INTEGER PR
+| 3904: 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 5c IMARY KEY,data).
+| 3920: 02 07 17 1d 1d 01 81 0b 74 61 62 6c 65 74 31 5f ........tablet1_
+| 3936: 72 6f 77 69 64 74 31 5f 72 6f 77 69 64 02 43 52 rowidt1_rowid.CR
+| 3952: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 72 EATE TABLE .t1_r
+| 3968: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE
+| 3984: 47 45 72 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GEr PRIMARY KEY,
+| 4000: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 29 51 01 07 nodeno,a0,a1)Q..
+| 4016: 17 11 11 08 81 0f 74 61 62 6c 65 74 31 74 31 43 ......tablet1t1C
+| 4032: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA
+| 4048: 42 4c 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 BLE t1 USING rtr
+| 4064: 65 65 28 69 64 2c 78 30 2c 78 31 2c 79 30 2c 79 ee(id,x0,x1,y0,y
+| 4080: 31 2c 2b 6c 61 62 65 6c 2c 2b 6f 74 68 65 72 29 1,+label,+other)
+| page 2 offset 4096
+| 0: 0d 00 00 00 0e 0e f7 00 0f e8 0f 00 fb 70 f9 e0 .............p..
+| 16: f9 10 f8 10 f7 00 f5 e0 f4 f0 f3 90 f2 90 f1 80 ................
+| 32: f0 60 ef 00 00 00 00 00 00 00 00 00 00 00 00 00 .`..............
+| 3824: 00 00 00 00 00 00 00 0d 0e 05 00 09 1d 00 74 6f ..............to
+| 3840: 70 20 68 61 6c 66 10 0d 05 00 09 23 00 62 6f 74 p half.....#.bot
+| 3856: 74 6f 6d 20 68 61 6c 66 0f 0c 05 00 09 21 00 72 tom half.....!.r
+| 3872: 69 67 68 74 20 68 61 6c 66 0e 0b 05 00 09 1f 00 ight half.......
+| 3888: 6c 65 66 74 20 68 61 6c 66 14 0a 05 00 09 2b 00 left half.....+.
+| 3904: 00 03 98 20 49 98 2f 6c 62 05 74 68 69 6e 67 0d ... I./lb.thing.
+| 3920: 09 05 00 09 1d 00 74 6f 70 20 65 64 67 65 10 08 ......top edge..
+| 3936: 05 00 09 23 00 62 6f 74 74 6f 6d 20 65 64 67 65 ...#.bottom edge
+| 3952: 0f 07 05 00 09 21 00 72 69 67 68 74 20 65 64 67 .....!.right edg
+| 3968: 65 0e 06 05 00 09 1f 00 6c 65 66 74 20 65 64 67 e.......left edg
+| 3984: 65 0b 05 05 00 09 19 00 63 65 6e 74 65 72 17 04 e.......center..
+| 4000: 05 00 09 31 00 75 70 70 65 72 2d 72 69 67 68 74 ...1.upper-right
+| 4016: 20 63 6f 72 6e 65 72 17 03 05 00 09 31 00 6c 6f corner.....1.lo
+| 4032: 77 65 72 2d 72 69 67 68 74 20 63 6f 72 6e 65 72 wer-right corner
+| 4048: 16 02 05 00 09 2f 00 75 70 70 65 72 2d 6c 65 66 ...../.upper-lef
+| 4064: 74 20 63 6f 72 6e 65 72 16 01 05 00 09 2f 00 6c t corner...../.l
+| 4080: 6f 77 65 72 2d 6c 65 66 74 20 63 6f 72 6e 65 72 ower-left corner
+| page 3 offset 8192
+| 0: 0d 00 00 00 01 0b 2d 00 0b 2d 00 00 00 00 00 00 ......-..-......
+| 2848: 00 00 00 00 00 00 00 00 00 00 00 00 00 89 50 01 ..............P.
+| 2864: 04 00 93 24 00 00 00 0e 00 00 00 00 00 00 00 01 ...$............
+| 2880: 00 00 00 04 01 20 00 00 00 00 00 04 12 00 00 00 ..... ..........
+| 2896: 00 00 00 00 00 00 00 23 00 00 00 00 41 20 00 00 .......#....A ..
+| 2912: 42 b4 00 00 42 c8 00 00 00 00 00 00 00 00 00 03 B...B...........
+| 2928: 42 b4 00 00 42 c8 00 00 00 00 00 00 41 20 00 00 B...B.......A ..
+| 2944: 00 00 00 00 00 00 00 04 42 b4 00 00 42 c8 00 00 ........B...B...
+| 2960: 42 b4 00 00 42 c8 00 00 00 00 00 00 00 00 00 05 B...B...........
+| 2976: 42 20 00 00 42 70 00 00 42 20 00 00 42 70 00 00 B ..Bp..B ..Bp..
+| 2992: 00 00 00 00 00 00 00 06 00 00 00 00 40 a0 00 00 ............@...
+| 3008: 00 00 00 04 2c 80 00 00 00 00 00 00 00 00 00 74 ....,..........t
+| 3024: 2b e0 00 04 2c 80 00 04 2c 80 00 00 00 00 00 00 +...,...,.......
+| 3040: 00 00 00 80 00 00 00 04 2c 80 00 00 00 00 00 04 ........,.......
+| 3056: 0a 00 00 00 00 00 b0 80 00 00 04 2c 80 00 04 2b ...........,...+
+| 3072: e0 00 04 2c 80 00 00 00 00 00 00 00 00 00 a0 00 ...,............
+| 3088: 00 00 04 2c 80 00 00 00 00 00 04 2c 80 00 00 00 ...,.......,....
+| 3104: 00 00 00 00 00 00 b0 00 00 00 04 24 80 00 00 00 ...........$....
+| 3120: 00 00 04 2c 80 00 00 00 00 00 00 00 50 00 91 f0 ...,........P...
+| 3136: 06 c6 56 67 42 06 86 16 c6 61 40 a0 50 00 92 b0 ..VgB....a@.P...
+| 3152: 07 46 86 52 07 76 86 f6 c6 52 07 46 86 96 e6 70 .F.R.v...R.F...p
+| 3168: d0 90 50 00 91 d0 07 46 f7 02 06 56 46 76 51 00 ..P....F...VFvQ.
+| 3184: 80 50 00 92 30 06 26 f7 47 46 f6 d2 06 56 46 76 .P..0.&.GF...VFv
+| 3200: 50 f0 70 50 00 92 10 07 26 96 76 87 42 06 56 46 P.pP....&.v.B.VF
+| 3216: 76 50 e0 60 50 00 91 f0 06 c6 56 67 42 06 56 46 vP.`P.....VgB.VF
+| 3232: 76 50 b0 50 50 00 91 90 06 36 56 e7 46 57 21 70 vP.PP....6V.FW!p
+| 3248: 40 50 00 93 10 07 57 07 06 57 22 d7 26 96 76 87 @P....W..W..&.v.
+| 3264: 42 06 36 f7 26 e6 57 21 70 30 50 00 93 10 06 c6 B.6.&.W!p0P.....
+| 3280: f7 76 57 22 d7 26 96 76 87 42 06 36 f7 26 e6 57 .vW..&.v.B.6.&.W
+| 3296: 21 60 20 50 00 92 f0 07 57 07 06 57 22 d6 c6 56 !` P....W..W...V
+| 3312: 60 00 00 c4 24 c0 00 04 2c 80 00 00 00 00 00 04 `...$...,.......
+| 3328: 2c 80 00 00 00 00 00 00 00 00 00 d0 00 00 00 04 ,...............
+| 3344: 2c 80 00 00 00 00 00 04 24 80 00 00 00 00 00 00 ,.......$.......
+| 3360: 00 00 00 e0 00 00 00 04 2c 80 00 04 24 c0 00 04 ........,...$...
+| 3376: 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ,...............
+| page 4 offset 12288
+| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 ................
+| end c4.db
+ }]
+ catchsql {
+ UPDATE t1 SET label='x';
+ }
+} {1 {rtree constraint failed: t1.(y0<=y1)}}
+do_test rtreefuzz001-310 {
+ catchsql {
+ SELECT rtreecheck('t1');
+ }
+} {/1 .*corrupt.*/}
+
+do_test rtreefuzz001-400 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 16384 pagesize 4096 filename c7.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 04 .....@ ........
+| 32: 00 00 00 00 01 00 00 00 00 00 00 04 00 00 00 04 ................
+| 48: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
+| 96: 00 00 00 00 0d 00 00 00 04 0e 9c 00 0f ad 0f 4f ...............O
+| 112: 0e fc 0e 9c 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 3728: 00 00 00 00 00 00 00 00 00 00 00 00 5e 04 07 17 ............^...
+| 3744: 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 72 .....tablet1_par
+| 3760: 65 6e 74 74 31 5f 70 61 72 65 6e 74 04 43 52 45 entt1_parent.CRE
+| 3776: 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 61 ATE TABLE .t1_pa
+| 3792: 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e 54 rent.(nodeno INT
+| 3808: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY
+| 3824: 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 03 06 17 ,parentnode)Q...
+| 3840: 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 65 ....tablet1_node
+| 3856: 74 31 5f 6e 6f 64 65 03 43 52 45 41 54 45 20 54 t1_node.CREATE T
+| 3872: 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 6e ABLE .t1_node.(n
+| 3888: 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 52 odeno INTEGER PR
+| 3904: 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 5c IMARY KEY,data).
+| 3920: 02 07 17 1d 1d 01 81 0b 74 61 62 6c 65 74 31 5f ........tablet1_
+| 3936: 72 6f 77 69 64 74 31 5f 72 6f 77 69 64 02 43 52 rowidt1_rowid.CR
+| 3952: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 72 EATE TABLE .t1_r
+| 3968: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE
+| 3984: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY,
+| 4000: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 29 51 01 07 nodeno,a0,a1)Q..
+| 4016: 17 11 11 08 81 0f 74 61 62 6c 65 74 31 74 31 43 ......tablet1t1C
+| 4032: 52 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 REATE VIRTUAL TA
+| 4048: 42 4c 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 BLE t1 USING rtr
+| 4064: 65 65 28 69 64 2c 78 30 2c 78 31 2c 79 30 2c 79 ee(id,x0,x1,y0,y
+| 4080: 31 2c 2b 6c 61 62 65 6c 2c 2b 6f 74 68 65 72 29 1,+label,+other)
+| page 2 offset 4096
+| 0: 0d 00 00 00 0e 0e f7 00 0f e8 0f d0 0f b7 0f 9e ................
+| 16: 0f 91 0f 81 0f 70 0f 5e 0f 4f 0f 39 0f 29 0f 18 .....p.^.O.9.)..
+| 32: 0f 06 0e f7 00 00 00 00 00 00 00 00 00 00 00 00 ................
+| 3824: 00 00 00 00 00 00 00 0d 0e 05 00 09 1d 00 74 6f ..............to
+| 3840: 70 20 68 61 6c 66 10 0d 05 00 09 23 00 62 6f 74 p half.....#.bot
+| 3856: 74 6f 6d 20 68 61 6c 66 0f 0c 05 00 09 21 00 72 tom half.....!.r
+| 3872: 69 67 68 74 20 68 61 6c 66 0e 0b 05 00 09 1f 00 ight half.......
+| 3888: 6c 65 66 74 20 68 61 6c 66 14 0a 05 00 09 2b 00 left half.....+.
+| 3904: 74 68 65 20 77 68 6f 6c 65 20 74 68 69 6e 67 0d the whole thing.
+| 3920: 09 05 00 09 1d 00 74 6f 70 20 65 64 67 65 10 08 ......top edge..
+| 3936: 05 00 09 23 00 62 6f 74 74 6f 6d 20 65 64 67 65 ...#.bottom edge
+| 3952: 0f 07 05 00 09 21 00 72 69 67 68 74 20 65 64 67 .....!.right edg
+| 3968: 65 0e 06 05 00 09 1f 00 6c 65 66 74 20 65 64 67 e.......left edg
+| 3984: 65 0b 05 05 00 09 19 00 23 65 6e 74 65 72 17 04 e.......#enter..
+| 4000: 05 00 09 31 00 75 70 70 65 72 2d 72 69 67 68 74 ...1.upper-right
+| 4016: 20 63 6f 72 6e 65 72 17 03 05 00 09 31 00 6c 6f corner.....1.lo
+| 4032: 77 65 72 2d 72 69 67 68 74 20 63 6f 72 6e 65 72 wer-right corner
+| 4048: 16 02 05 00 09 2f 00 75 70 70 65 72 2d 6c 65 66 ...../.upper-lef
+| 4064: 74 20 63 6f 72 6e 65 72 16 01 05 00 09 2f 00 6c t corner...../.l
+| 4080: 6f 77 65 72 2d 6c 65 66 74 20 63 6f 72 6e 65 72 ower-left corner
+| page 3 offset 8192
+| 0: 0d 00 00 00 02 0b 2d 00 0b 2d 00 00 00 00 00 00 ......-..-......
+| 2848: 00 00 00 00 00 00 00 00 00 00 00 00 00 89 50 01 ..............P.
+| 2864: 04 00 93 24 00 00 00 00 00 00 00 00 08 00 00 00 ...$............
+| 2880: 00 42 c8 00 00 00 00 00 00 40 a0 00 00 00 00 00 .B.......@......
+| 2896: 00 00 00 00 42 c8 00 00 00 00 00 00 00 00 00 07 ....B...........
+| 2912: 42 be 00 00 42 c8 00 00 00 00 00 00 42 c8 00 00 B...B.......B...
+| 2928: 00 00 00 00 00 00 00 08 00 00 00 00 42 c8 00 00 ............B...
+| 2944: 00 00 00 00 40 a0 00 00 00 00 00 00 00 00 00 09 ....@...........
+| 2960: 00 00 00 00 42 c8 00 00 42 be 00 00 42 c8 00 00 ....B...B...B...
+| 2976: 00 00 00 00 00 00 00 0a 00 00 00 00 42 c8 00 00 ............B...
+| 2992: 00 00 00 00 42 c8 00 00 00 00 00 00 00 00 00 0b ....B...........
+| 3008: 00 00 00 00 42 48 00 00 00 00 00 04 2c 80 00 00 ....BH......,...
+| 3024: 00 00 00 00 00 00 00 c4 00 00 00 00 00 42 c8 00 .............B..
+| 3040: 00 00 00 00 00 00 00 00 07 42 be 00 00 42 c8 00 .........B...B..
+| 3056: 00 00 00 00 00 42 c8 00 00 00 00 00 00 00 00 00 .....B..........
+| 3072: 08 00 00 00 00 42 c8 00 00 00 00 00 00 40 a0 00 .....B.......@..
+| 3088: 00 00 00 00 00 00 00 00 09 00 00 00 00 42 c8 00 .............B..
+| 3104: 00 42 be 00 00 42 c8 00 00 00 00 00 00 00 00 00 .B...B..........
+| 3120: 0a 00 00 00 00 42 c8 00 00 00 00 00 00 42 c8 00 .....B.......B..
+| 3136: 00 00 00 00 00 00 00 00 0b 00 00 00 00 42 48 00 .............BH.
+| 3152: 00 00 00 00 04 2c 80 00 00 00 00 00 00 00 00 00 .....,..........
+| 3168: c4 24 c0 00 04 2c 80 00 00 00 00 00 04 2c 80 00 .$...,.......,..
+| 3184: 00 00 00 00 00 00 00 00 d0 00 00 00 04 2c 80 00 .............,..
+| 3200: 00 00 00 00 04 24 80 00 00 00 00 00 00 00 00 00 .....$..........
+| 3216: e0 00 00 00 04 2c 80 00 04 24 c0 00 04 2c 00 00 .....,...$...,..
+| page 4 offset 12288
+| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 0e 00 00 00 ................
+| 16: 00 42 c8 00 00 42 4c 00 00 42 c8 00 00 00 00 00 .B...BL..B......
+| 32: 00 00 00 0a 00 00 00 00 42 c8 00 00 00 00 00 00 ........B.......
+| 48: 42 c8 00 00 00 00 00 00 00 00 00 0b 00 00 00 00 B...............
+| 64: 42 48 00 00 00 00 00 04 2c 80 00 00 00 00 00 00 BH......,.......
+| 80: 00 00 00 c4 24 c0 00 04 2c 80 00 00 00 00 00 04 ....$...,.......
+| 96: 2c 80 00 00 00 00 00 00 00 00 00 d0 00 00 00 04 ,...............
+| 112: 2c 80 00 00 00 00 00 04 24 80 00 00 00 00 00 00 ,.......$.......
+| 128: 00 00 00 e0 00 00 00 04 2c 80 00 04 24 c0 00 04 ........,...$...
+| 144: 2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ,...............
+| end c7.db
+ }]
+ catchsql {
+ WITH RECURSIVE
+ c1(x) AS (VALUES(0) UNION ALL SELECT x+1 FROM c1 WHERE x<8),
+ c2(y) AS (VALUES(0) UNION ALL SELECT y+1 FROM c2 WHERE y<5)
+ INSERT INTO t1(id, x0,x1,y0,y1,label)
+ SELECT 1000+x+y*100, x, x+1, y, y+1, printf('box-%d,%d',x,y) FROM c1, c2;
+ }
+} {1 {database disk image is malformed}}
+
+do_test rtreefuzz001-500 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 16384 pagesize 4096 filename crash-2e81f5dce5cbd4.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 00 .....@ ........
+| 96: 00 00 00 00 0d 00 00 00 05 0e 6d 00 0f c8 0f 7b ..........m.....
+| 112: 0f 20 0e cd 0e 6d 00 00 00 00 00 00 00 00 00 00 . ...m..........
+| 3680: 00 00 00 00 00 00 00 00 00 00 00 00 00 5e 05 07 .............^..
+| 3696: 17 1f 1f 01 81 0b 74 61 62 6c 65 74 31 5f 70 61 ......tablet1_pa
+| 3712: 72 65 6e 74 74 31 5f 70 61 72 65 6e 74 05 43 52 rentt1_parent.CR
+| 3728: 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f 70 EATE TABLE .t1_p
+| 3744: 61 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 20 49 4e arent.(nodeno IN
+| 3760: 54 45 47 45 42 20 50 52 49 4d 41 52 59 20 4b 45 TEGEB PRIMARY KE
+| 3776: 59 2c 70 61 72 65 6e 74 6e 6f 64 65 29 51 04 06 Y,parentnode)Q..
+| 3792: 17 1b 1b 01 7b 74 61 62 6c 65 74 31 5f 6e 6f 64 .....tablet1_nod
+| 3808: 65 74 31 5f 6e 6f 64 65 04 43 52 45 41 54 45 20 et1_node.CREATE
+| 3824: 54 41 42 4c 45 20 22 74 31 5f 6e 6f 64 65 22 28 TABLE .t1_node.(
+| 3840: 6e 6f 64 65 6e 6f 20 49 4e 54 45 47 45 52 20 50 nodeno INTEGER P
+| 3856: 52 49 4d 41 52 59 20 4b 45 59 2c 64 61 74 61 29 RIMARY KEY,data)
+| 3872: 59 03 07 17 1d 1d 01 81 05 74 61 62 6c 65 84 31 Y........table.1
+| 3888: 5f 72 6f 77 69 64 74 31 5f 72 6f 87 69 64 03 43 _rowidt1_ro.id.C
+| 3904: 52 45 41 54 45 20 54 41 42 4c 45 20 22 74 31 5f REATE TABLE .t1_
+| 3920: 72 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 rowid.(rowid INT
+| 3936: 45 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 EGER PRIMARY KEY
+| 3952: 2c 6e f8 64 65 6e 6f 2c 61 30 29 4b 02 07 17 11 ,n.deno,a0)K....
+| 3968: 11 08 81 03 74 22 62 6c 65 74 31 74 31 43 52 45 ....t.blet1t1CRE
+| 3984: 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 4c ATE VIRTUAL TABL
+| 4000: 45 20 74 31 20 55 53 49 4e 47 20 72 74 72 65 65 E t1 USING rtree
+| 4016: 5f 69 33 32 28 69 cc 2c 78 30 2c 78 31 2c 79 30 _i32(i.,x0,x1,y0
+| 4032: 2c 79 31 2c 2b 65 78 29 36 01 06 17 17 17 01 4d ,y1,+ex)6......M
+| 4048: 74 61 62 6c 65 63 6f 6f 72 64 63 6f 6f 72 64 02 tablecoordcoord.
+| 4064: 43 52 45 41 54 45 20 54 41 42 4c 45 20 63 6f 6f CREATE TABLE coo
+| 4080: 71 64 28 76 20 49 4e 54 2c 20 77 20 49 4e 54 29 qd(v INT, w INT)
+| page 2 offset 4096
+| 4016: 00 00 00 00 00 00 00 00 00 00 00 05 0a 03 01 01 ................
+| 4032: 0a 02 05 09 03 01 01 09 02 05 08 03 01 01 08 02 ................
+| 4048: 05 07 03 01 01 07 02 05 06 03 11 01 06 02 05 05 ................
+| 4064: 03 01 01 05 02 05 04 03 01 01 04 02 05 03 03 01 ................
+| 4080: 01 03 02 05 02 03 01 01 02 02 04 01 03 09 01 02 ................
+| page 3 offset 8192
+| 0: 0d 0e 4f 00 64 0b 5a 12 0d bb 0d 84 0f eb 0d c6 ..O.d.Z.........
+| 16: 0f d7 0e cc 0f c1 0f b6 0f ab 0f 9f 0f 94 0d 8f ................
+| 32: 0f 86 0d d1 0f 62 0f 67 0f 5c 0f 51 1f 46 0f 3a .....b.g...Q.F.:
+| 48: 0f 30 0d 9a 0f 21 0d dc 0f 00 00 00 00 00 00 00 .0...!..........
+| 2896: 00 00 00 00 00 00 00 00 00 00 0a ce 1a 04 00 01 ................
+| 2912: 17 03 31 30 78 31 30 0a 4e 19 03 ff f1 15 03 31 ..10x10.N......1
+| 2928: 30 78 39 09 ce 18 04 00 01 15 03 31 30 78 38 09 0x9........10x8.
+| 2944: ce 17 04 00 01 15 03 31 30 78 37 09 ce 16 04 00 .......10x7.....
+| 2960: 12 15 03 31 30 78 36 09 ce 15 04 00 01 15 03 31 ...10x6........1
+| 2976: 30 78 35 09 ce 14 04 00 01 15 0d a1 30 78 34 09 0x5.........0x4.
+| 2992: ce 13 04 00 01 15 03 31 30 78 33 09 ce 12 04 00 .......10x3.....
+| 3008: 01 15 03 31 40 78 32 09 ce 11 04 00 01 15 03 31 ...1@x2........1
+| 3024: 30 78 31 09 c6 32 04 00 01 15 03 39 78 31 30 08 0x1..2.....9x10.
+| 3040: c6 31 04 00 01 13 03 39 78 39 08 c6 30 04 00 01 .1.....9x9..0...
+| 3056: 13 03 39 78 38 08 c6 2f 04 00 01 14 03 39 78 37 ..9x8../.....9x7
+| 3072: 08 c6 2e 04 00 01 13 03 39 78 36 08 c6 2d 04 00 ........9x6..-..
+| 3088: 01 13 03 39 78 34 f8 c6 2c 04 00 01 13 03 39 78 ...9x4..,.....9x
+| 3104: 34 08 c6 2b 04 00 60 13 03 39 79 13 08 c6 2a 04 4..+..`..9y...*.
+| 3120: 00 11 13 03 39 78 32 08 c6 29 04 00 01 13 03 39 ....9x2..).....9
+| 3136: 78 31 09 be 4a 04 00 01 15 03 38 78 31 30 08 be x1..J.....8x10..
+| 3152: 49 04 00 01 13 03 38 78 39 08 be 48 04 00 01 13 I.....8x9..H....
+| 3168: 03 38 77 98 08 be 47 04 00 01 14 23 38 78 37 08 .8w...G....#8x7.
+| 3184: be 46 04 00 01 13 03 38 78 36 08 be 45 04 00 01 .F.....8x6..E...
+| 3200: 13 03 38 78 35 08 be 44 04 00 01 13 03 38 78 34 ..8x5..D.....8x4
+| 3216: 08 be 43 04 00 01 13 03 38 78 33 08 be 42 04 00 ..C.....8x3..B..
+| 3232: 01 13 03 38 78 32 08 be 41 04 00 01 13 03 38 78 ...8x2..A.....8x
+| 3248: 31 09 b6 62 04 00 01 15 03 37 68 31 30 08 b6 61 1..b.....7h10..a
+| 3264: 04 00 01 13 03 37 79 39 08 b6 60 04 00 01 12 f3 .....7y9..`.....
+| 3280: 37 78 38 08 b6 5e 04 00 01 13 03 37 78 37 08 b6 7x8..^.....7x7..
+| 3296: 5e 04 00 01 13 03 37 78 36 08 b6 5d 04 00 01 13 ^.....7x6..]....
+| 3312: 03 37 78 35 08 b6 5c 04 00 00 13 03 37 78 34 08 .7x5........7x4.
+| 3328: b6 5b 04 00 01 13 03 37 78 33 08 b6 5a 04 00 01 .[.....7x3..Z...
+| 3344: 13 03 37 78 32 08 b6 59 04 00 01 13 03 37 78 31 ..7x2..Y.....7x1
+| 3360: 09 ae 7a 04 00 01 15 03 36 78 31 30 08 ae 79 04 ..z.....6x10..y.
+| 3376: 00 01 e2 03 36 78 39 08 ae 78 04 00 01 13 03 36 ....6x9..x.....6
+| 3392: 78 38 08 ae 77 04 00 01 13 03 36 78 37 08 ae 76 x8..w.....6x7..v
+| 3408: 04 00 01 13 03 36 78 36 08 ae 85 04 00 01 13 03 .....6x6........
+| 3424: 36 78 35 08 ae 73 f4 00 01 13 03 36 78 34 08 ae 6x5..s.....6x4..
+| 3440: 73 04 00 01 13 03 36 78 33 08 ae 72 04 00 01 13 s.....6x3..r....
+| 3456: 03 36 78 32 08 87 6a 04 00 01 13 02 3d e8 32 08 .6x2..j.....=.2.
+| 3472: 8f 52 04 00 01 13 02 32 78 32 08 97 3b 04 00 01 .R.....2x2..;...
+| 3488: 13 02 33 78 32 08 9f 22 04 00 01 13 02 34 78 32 ..3x2........4x2
+| 3504: 08 a7 0a 04 00 01 13 02 35 78 32 08 87 69 04 00 ........5x2..i..
+| 3520: 01 13 02 31 78 31 08 87 6c 04 00 01 13 02 31 78 ...1x1..l.....1x
+| 3536: 34 08 8f 54 04 00 01 13 02 32 78 34 08 97 3c 04 4..T.....2x4..<.
+| 3552: 00 01 12 f2 33 78 34 08 9f 24 04 00 01 13 02 34 ....3x4..$.....4
+| 3568: 78 34 08 a7 0c 04 00 01 13 02 35 78 34 0e 6c 00 x4........5x4.l.
+| 3584: 08 ae 71 04 00 01 13 03 36 78 31 09 a7 12 04 00 ..q.....6x1.....
+| 3600: 01 15 02 35 78 31 30 08 a7 11 04 00 01 13 02 35 ...5x10........5
+| 3616: 78 39 08 a7 10 04 00 01 13 02 35 78 38 08 a7 0f x9........5x8...
+| 3632: 04 00 01 14 02 35 78 37 08 a7 0e 04 00 01 13 02 .....5x7........
+| 3648: 35 78 36 08 a7 0d 04 00 01 13 02 35 78 35 0e 0e 5x6........5x5..
+| 3664: b3 00 08 00 01 00 03 08 a7 0b 04 00 01 13 02 35 ...............5
+| 3680: 78 33 0e d1 00 08 a7 09 04 00 01 13 02 35 78 31 x3...........5x1
+| 3696: 09 9f 2a 04 00 01 15 02 34 78 31 30 03 cf 29 04 ..*.....4x10..).
+| 3712: 00 01 13 02 34 78 39 08 9f 28 04 00 01 13 02 34 ....4x9..(.....4
+| 3728: 78 38 09 9f 27 04 00 01 13 02 34 78 37 08 9f 26 x8..'.....4x7..&
+| 3744: 04 00 01 13 0e a4 78 36 08 9f 25 04 00 01 13 02 ......x6..%.....
+| 3760: 34 78 35 0f 18 00 09 00 09 13 34 78 08 9f 23 04 4x5.......4x..#.
+| 3776: 00 01 13 02 34 78 33 0f 36 00 08 9f 21 04 00 01 ....4x3.6...!...
+| 3792: 13 02 34 78 31 09 97 42 04 00 01 15 02 33 78 31 ..4x1..B.....3x1
+| 3808: 30 08 97 41 04 00 01 13 02 33 78 39 08 97 40 04 0..A.....3x9..@.
+| 3824: 00 01 13 02 33 78 38 18 97 3f 04 00 01 13 02 33 ....3x8..?.....3
+| 3840: 78 37 08 97 3e 04 00 01 13 02 33 78 36 08 97 3d x7..>.....3x6..=
+| 3856: 04 00 01 13 02 33 78 35 1f 7d 00 09 00 09 13 33 .....3x5.......3
+| 3872: 78 07 97 3b 04 00 01 13 02 33 78 33 0f 9b 00 08 x..;.....3x3....
+| 3888: 97 39 04 00 01 13 02 33 78 31 09 8f 5a 04 00 01 .9.....3x1..Z...
+| 3904: 15 02 32 79 31 30 08 8f 59 04 00 01 13 fa 32 78 ..2y10..Y.....2x
+| 3920: 39 08 8f 58 04 00 01 13 02 32 78 38 08 8f 57 04 9..X.....2x8..W.
+| 3936: 00 01 13 02 32 78 37 08 8f 56 04 00 01 13 02 32 ....2x7..V.....2
+| 3952: 78 36 08 8f 55 04 00 01 13 02 32 78 35 0f e2 00 x6..U.....2x5...
+| 3968: 09 00 09 13 32 78 08 8f 53 04 00 01 13 02 32 78 ....2x..S.....2x
+| 3984: 33 00 00 00 08 8f 51 04 00 01 13 02 aa 78 31 09 3.....Q......x1.
+| 4000: 87 72 04 00 01 15 02 31 78 31 30 08 87 71 04 00 .r.....1x10..q..
+| 4016: 01 13 03 31 78 39 08 87 70 04 00 01 13 02 31 78 ...1x9..p.....1x
+| 4032: 38 08 87 6f 04 00 01 13 02 31 78 37 08 87 6e 04 8..o.....1x7..n.
+| 4048: 00 01 13 02 31 78 36 08 87 6d 04 00 01 13 02 31 ....1x6..m.....1
+| 4064: 7d 25 0f f9 00 08 ff f9 13 31 78 08 87 6b 04 00 .%.......1x..k..
+| 4080: 01 13 02 31 78 33 00 00 00 00 00 08 00 01 00 03 ...1x3..........
+| page 4 offset 12288
+| 0: 0d 00 00 00 03 01 87 00 0b 2d 06 5a 01 87 00 00 .........-.Z....
+| 384: 00 00 00 00 00 00 00 89 50 01 54 00 93 24 00 00 ........P.T..$..
+| 400: 00 32 00 00 00 00 00 00 23 2f 00 00 00 09 00 00 .2......#/......
+| 416: 00 0b 00 00 00 07 00 00 00 09 00 00 00 00 00 00 ................
+| 432: 23 2e 00 00 10 09 00 00 00 0b 00 00 00 06 00 00 #...............
+| 448: 00 08 00 00 00 00 00 00 23 2d 00 00 00 09 00 00 ........#-......
+| 464: 00 0b 00 00 00 05 00 00 00 07 00 00 00 00 00 00 ................
+| 480: 23 2c 00 00 00 09 00 00 00 0b 00 00 00 04 00 00 #,..............
+| 496: 00 06 00 00 00 00 00 00 23 2b 00 00 00 09 00 00 ........#+......
+| 512: 00 0b 00 00 00 03 00 00 00 05 00 00 00 00 00 00 ................
+| 528: 23 2a 00 00 00 09 00 00 00 0b 00 00 00 02 00 00 #*..............
+| 544: 00 04 00 00 00 00 00 00 23 29 00 00 00 09 00 00 ........#)......
+| 560: 00 0b 00 00 00 01 00 00 00 03 00 00 00 00 00 00 ................
+| 576: 1f 4a 00 00 00 08 00 00 00 0a 00 00 00 0a 00 00 .J..............
+| 592: 00 0c 00 00 00 00 00 00 0f 49 00 00 00 08 00 00 .........I......
+| 608: 00 0a 00 00 00 09 00 00 00 0b 00 00 00 00 00 00 ................
+| 624: 1f 48 00 00 00 08 00 00 00 0a 00 00 00 08 00 06 .H..............
+| 640: 00 0a 00 00 00 00 00 00 1f 47 00 00 00 08 00 00 .........G......
+| 656: 00 0a 00 00 00 07 00 00 00 09 00 00 00 00 00 00 ................
+| 672: 15 d6 00 00 00 08 00 00 00 0a 00 00 00 06 00 00 ................
+| 688: 00 08 00 00 00 00 00 00 1f 45 00 00 00 08 00 00 .........E......
+| 704: 00 0a 00 00 00 05 00 00 00 07 00 00 00 00 00 00 ................
+| 720: 1f 44 00 00 00 08 00 00 00 0a 00 00 00 04 00 00 .D..............
+| 736: 00 06 00 00 00 00 00 00 1f 43 00 00 00 07 ff ff .........C......
+| 752: f0 0a 00 00 00 03 00 00 00 05 00 00 00 00 00 00 ................
+| 768: 1f 42 00 00 00 08 00 00 00 0a 00 00 00 01 ff f0 .B..............
+| 784: 00 03 ff ff ff ff ff ff 1f 41 00 00 00 08 00 00 .........A......
+| 800: 00 0a 00 00 00 01 00 00 00 03 00 00 00 00 00 00 ................
+| 816: 1b 62 00 00 00 07 00 00 00 09 00 00 00 0a 00 00 .b..............
+| 832: 00 0c 05 00 00 00 00 00 1b 64 10 00 00 07 00 00 .........d......
+| 848: 00 09 00 00 00 09 00 00 00 0b 00 00 00 00 00 00 ................
+| 864: 1b 60 00 00 00 07 00 00 00 09 00 00 00 08 00 00 .`..............
+| 880: 00 0a 00 00 00 00 00 00 1b 5f 00 00 00 07 00 00 ........._......
+| 896: 00 09 00 00 00 07 00 00 00 09 00 00 00 00 00 00 ................
+| 912: 1b 5e 00 00 00 07 00 00 00 09 00 00 00 06 00 00 .^..............
+| 928: 00 08 00 00 00 00 00 00 1b 5d 00 00 00 08 00 00 .........]......
+| 944: 00 09 00 00 00 05 00 00 00 07 00 00 00 00 00 00 ................
+| 960: 1b 5c 00 00 00 07 00 00 00 09 00 00 00 04 00 00 ................
+| 976: 06 46 00 00 00 00 00 00 1b 5b 00 00 00 07 00 00 .F.......[......
+| 992: 00 09 00 00 00 03 00 00 00 04 ff f0 00 00 00 00 ................
+| 1008: 1b 5a 00 00 00 07 00 00 00 19 00 00 00 02 00 00 .Z..............
+| 1024: 00 04 00 00 00 00 00 00 1b 59 00 00 00 07 00 00 .........Y......
+| 1040: 00 09 00 00 00 01 00 00 00 03 00 00 00 00 ff f0 ................
+| 1056: 17 7a 00 00 00 06 00 00 00 08 00 00 00 0a 00 00 .z..............
+| 1072: 00 0c 00 00 00 00 00 00 17 79 00 00 00 06 00 00 .........y......
+| 1088: 00 08 00 00 00 09 00 00 00 0b 00 00 00 00 00 00 ................
+| 1104: 17 78 00 00 00 06 00 00 00 08 00 00 00 08 00 00 .x..............
+| 1120: 00 0a 00 00 00 00 00 00 17 77 00 00 00 06 10 00 .........w......
+| 1136: 00 08 00 00 00 07 00 09 c0 09 00 00 00 00 00 00 ................
+| 1152: 17 76 00 00 00 06 00 00 00 08 00 00 00 06 00 00 .v..............
+| 1168: 00 08 00 00 00 00 00 00 17 75 00 00 00 06 00 00 .........u......
+| 1184: 00 08 00 00 00 05 00 00 00 07 00 00 00 00 00 00 ................
+| 1200: 17 74 00 00 00 06 00 00 00 08 00 00 00 03 ff ff .t..............
+| 1216: f0 06 00 00 00 83 00 00 17 73 00 00 00 06 00 00 .........s......
+| 1232: 00 08 00 00 00 03 00 00 00 05 00 00 00 00 00 00 ................
+| 1248: 17 71 ff 00 00 06 00 00 10 08 00 00 00 02 00 00 .q..............
+| 1264: 00 04 00 00 c0 00 00 00 17 0d 00 00 00 06 00 00 ................
+| 1280: 00 08 00 00 e7 01 00 00 00 03 00 00 09 e0 00 00 ................
+| 1296: 23 30 00 00 00 09 00 00 00 0a 00 00 00 08 00 00 #0..............
+| 1312: 00 0a 00 00 00 00 bb 00 23 31 00 00 00 09 00 00 ........#1......
+| 1328: 00 0b 00 00 00 09 00 00 00 0b 00 00 00 00 00 00 ................
+| 1344: 23 32 00 00 00 09 00 00 00 0b 00 00 00 0a 00 00 #2..............
+| 1360: 00 0c 00 00 00 00 00 00 27 11 00 00 00 0a 00 00 ........'.......
+| 1376: 00 0c 00 00 00 01 00 08 c0 03 00 00 00 00 00 00 ................
+| 1392: 27 12 00 00 00 0a 00 00 00 0c 51 00 00 02 00 00 '.........Q.....
+| 1408: 00 04 6f 00 00 00 00 00 27 13 00 00 00 09 ff ff ..o.....'.......
+| 1424: 00 0c 00 00 00 03 00 00 00 05 00 00 00 00 00 00 ................
+| 1440: 27 14 00 00 00 0a 00 00 00 00 00 00 00 00 00 00 '...............
+| 1616: 00 00 00 00 00 00 00 00 00 00 89 50 02 04 00 93 ...........P....
+| 1632: 24 00 00 00 32 00 00 00 00 00 00 23 8c 00 00 00 $...2......#....
+| 1648: 05 00 00 00 07 00 00 00 04 00 00 00 06 00 00 00 ................
+| 1664: 00 00 00 0f a4 00 00 00 04 00 00 00 06 00 00 00 ................
+| 1680: 04 00 00 00 06 00 00 00 00 00 00 0b bc 00 00 00 ................
+| 1696: 03 00 00 00 05 00 00 00 04 00 00 00 06 00 00 00 ................
+| 1712: 00 00 00 07 d4 00 00 00 02 00 00 00 04 00 00 00 ................
+| 1728: 04 00 00 00 06 00 00 00 10 00 00 03 ec 00 00 00 ................
+| 1744: 01 00 00 00 03 00 00 00 04 00 00 00 06 00 00 00 ................
+| 1760: 00 00 00 13 8d 00 00 00 05 00 00 00 07 00 00 00 ................
+| 1776: 05 00 00 00 07 00 00 00 00 00 00 0f a5 00 00 00 ................
+| 1792: 04 00 00 00 06 00 00 00 05 00 00 00 07 00 00 00 ................
+| 1808: 00 00 00 0b bd 00 00 00 03 00 00 00 05 00 00 00 ................
+| 1824: 05 00 00 00 07 00 00 00 00 00 00 07 d5 00 00 00 ................
+| 1840: 02 00 00 00 05 00 00 00 05 00 00 00 07 00 00 00 ................
+| 1856: 00 00 00 03 ed 00 00 00 01 00 00 00 03 00 00 00 ................
+| 1872: 05 00 00 00 07 00 00 00 00 00 00 13 8e 00 00 00 ................
+| 1888: 05 00 00 00 07 00 00 00 06 00 00 00 08 00 00 00 ................
+| 1904: 00 00 00 0f a6 00 00 00 04 00 00 00 06 00 00 00 ................
+| 1920: 06 00 00 00 07 ff ff 00 00 00 00 0b be 00 00 00 ................
+| 1936: 0b 40 00 00 05 00 00 00 06 00 00 00 08 00 00 00 .@..............
+| 1952: 00 00 00 07 d6 00 00 00 02 00 00 00 04 00 00 00 ................
+| 1968: 05 00 00 00 08 00 00 00 00 00 00 03 ee 00 00 00 ................
+| 1984: 01 00 00 00 02 ff ff 00 06 00 00 00 08 00 00 00 ................
+| 2000: 00 00 00 13 8f 00 00 00 05 00 00 00 07 00 00 00 ................
+| 2016: 07 00 00 00 09 00 00 00 00 00 00 0f a7 00 00 00 ................
+| 2032: 04 00 00 00 06 00 00 00 07 00 00 00 09 00 00 08 ................
+| 2048: 30 00 00 0b bf 00 00 00 03 00 00 00 05 00 00 00 0...............
+| 2064: 07 00 00 00 09 00 00 00 00 00 00 07 d7 00 00 00 ................
+| 2080: 02 00 00 00 04 00 00 00 07 00 00 00 09 00 00 00 ................
+| 2096: 00 00 00 03 ef 00 00 00 01 00 00 00 03 00 00 00 ................
+| 2112: 07 00 00 00 09 00 00 00 00 00 00 13 90 00 00 00 ................
+| 2128: 05 00 01 00 07 00 00 00 08 00 00 00 0a 00 00 00 ................
+| 2144: 00 00 00 0f a8 00 00 00 04 00 00 00 06 00 00 00 ................
+| 2160: 08 00 00 00 0a 00 00 00 00 00 00 0b f2 00 00 00 ................
+| 2176: 03 00 00 00 05 00 00 00 08 00 00 00 0a 00 00 01 ................
+| 2192: 00 00 00 07 d8 00 00 00 02 00 00 00 04 00 00 00 ................
+| 2208: 08 00 00 00 0a 00 00 00 00 00 00 03 f0 00 00 00 ................
+| 2224: 01 00 00 00 03 00 00 00 08 00 00 00 09 ff 00 00 ................
+| 2240: 00 00 00 13 91 00 00 00 05 00 00 00 07 00 00 00 ................
+| 2256: 09 00 00 00 0b 00 00 00 00 00 00 0f a9 00 00 00 ................
+| 2272: 04 00 00 00 06 00 00 00 09 00 00 00 0b 00 00 00 ................
+| 2288: 00 00 00 0b c1 00 00 00 03 00 00 00 05 00 00 00 ................
+| 2304: 09 00 00 00 0b 00 00 00 00 00 00 07 d9 00 00 00 ................
+| 2320: 02 00 00 00 04 00 00 00 09 00 00 00 0b 00 00 01 ................
+| 2336: 00 00 00 03 f0 ff ff 00 01 00 00 00 03 00 00 00 ................
+| 2352: 09 00 00 00 0b 00 00 00 00 00 00 13 92 00 00 00 ................
+| 2368: 05 00 00 00 07 00 00 00 0a 00 00 00 0c 00 00 00 ................
+| 2384: 00 00 00 0f aa 00 00 00 04 00 00 00 06 00 00 00 ................
+| 2400: 0a 00 00 00 0c 00 00 00 00 00 00 0b c2 00 00 00 ................
+| 2416: 03 00 00 00 05 00 00 00 0a 00 00 00 0c 00 00 00 ................
+| 2432: 00 00 00 07 da 00 00 00 02 00 00 00 04 00 00 00 ................
+| 2448: 0a 00 00 00 0c 00 00 00 00 00 00 03 f2 00 00 00 ................
+| 2464: 01 00 00 10 03 00 00 00 0a 00 00 00 0c 00 00 00 ................
+| 2480: 00 00 00 03 eb 00 00 00 01 00 00 00 03 00 00 00 ................
+| 2496: 03 00 00 00 05 00 00 00 00 00 00 07 d3 00 00 00 ................
+| 2512: 02 00 00 00 04 00 00 00 03 00 00 00 05 00 00 00 ................
+| 2528: 00 00 00 0b bb 00 00 00 03 00 00 00 05 00 00 00 ................
+| 2544: 03 00 00 00 05 00 00 00 00 00 00 0f a3 00 00 00 ................
+| 2560: 04 00 00 00 06 00 00 00 03 00 00 00 05 00 00 00 ................
+| 2576: 00 00 00 13 8b 00 00 00 05 00 00 00 07 00 00 00 ................
+| 2592: 03 00 00 00 05 00 00 00 00 00 00 03 ea 00 00 00 ................
+| 2608: 01 00 00 00 03 00 00 00 02 00 00 00 04 00 00 00 ................
+| 2624: 00 00 00 07 d2 00 00 00 02 00 00 00 04 00 00 00 ................
+| 2640: 02 00 00 00 04 00 00 00 00 00 00 0b ba 00 00 00 ................
+| 2656: 03 00 00 00 05 00 00 00 02 00 00 00 04 00 00 00 ................
+| 2672: 00 00 00 0f a1 ff ff ff 04 00 00 00 06 00 00 00 ................
+| 2688: 02 00 00 00 04 00 00 00 00 00 00 13 8a 00 00 00 ................
+| 2704: 05 00 00 00 06 ff ff ff f2 00 00 00 04 00 00 00 ................
+| 2720: 00 00 00 03 e9 00 00 00 01 00 00 00 03 00 00 00 ................
+| 2736: 01 00 00 00 03 00 00 00 00 00 00 07 d1 00 00 00 ................
+| 2848: 00 00 00 00 00 00 00 00 00 00 00 00 00 89 50 01 ..............P.
+| 2864: 04 00 93 24 00 01 00 02 00 00 00 00 00 00 00 02 ...$............
+| 2880: ff ff ff 06 00 00 00 0c 00 00 00 01 00 00 00 0b ................
+| 2896: 00 00 00 00 00 00 00 02 40 00 00 00 00 00 00 00 ........@.......
+| end crash-2e81f5dce5cbd4.db}]
+ execsql { PRAGMA writable_schema = 1;}
+ catchsql {UPDATE t1 SET ex= ex ISNULL}
+} {1 {database disk image is malformed}}
+
+do_test rtreefuzz001-600 {
+ sqlite3 db {}
+ db deserialize [decode_hexdb {
+| size 20480 pagesize 4096 filename crash-7b37d80f000235.db
+| page 1 offset 0
+| 0: 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 SQLite format 3.
+| 16: 10 00 01 01 00 40 20 20 00 00 00 00 00 00 00 05 .....@ ........
+| 32: 00 00 00 00 00 00 00 00 00 00 10 06 00 00 00 04 ................
+| 96: 00 00 00 00 0d 00 00 00 05 0e 49 00 0f 99 0f 40 ..........I....@
+| 112: 0e da 0e 8f 0e 49 00 00 00 00 00 00 00 00 00 00 .....I..........
+| 3648: 00 00 00 00 00 00 00 00 00 44 05 06 17 15 15 08 .........D......
+| 3664: 6f 74 61 62 6c 65 67 65 6f 31 67 65 6f 31 43 52 otablegeo1geo1CR
+| 3680: 45 41 54 45 20 56 49 52 54 55 41 4c 20 54 41 42 EATE VIRTUAL TAB
+| 3696: 4c 45 20 67 65 6f 31 20 55 53 49 4e 47 20 67 65 LE geo1 USING ge
+| 3712: 6f 70 6f 6c 79 28 74 79 70 65 2c 63 6c 72 29 49 opoly(type,clr)I
+| 3728: 04 06 17 1f 1f 01 63 74 61 62 6c 65 71 75 65 72 ......ctablequer
+| 3744: 79 70 6f 6c 79 71 75 65 72 79 70 6f 6c 79 05 43 ypolyquerypoly.C
+| 3760: 52 45 41 54 45 20 54 41 42 4c 45 20 71 75 65 72 REATE TABLE quer
+| 3776: 79 70 6f 6c 79 28 70 6f 6c 79 20 4a 53 4f 4e 2c ypoly(poly JSON,
+| 3792: 20 63 6c 72 20 54 45 58 54 29 64 03 07 17 23 23 clr TEXT)d...##
+| 3808: 01 81 0f 74 61 62 6c 65 67 65 6f 31 5f 70 61 72 ...tablegeo1_par
+| 3824: 65 6e 74 67 65 6f 31 5f 70 61 72 65 6e 74 04 43 entgeo1_parent.C
+| 3840: 52 45 41 54 45 20 54 41 42 4c 45 20 22 67 65 6f REATE TABLE .geo
+| 3856: 31 5f 70 61 72 65 6e 74 22 28 6e 6f 64 65 6e 6f 1_parent.(nodeno
+| 3872: 20 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 INTEGER PRIMARY
+| 3888: 20 4b 45 59 2c 70 61 72 65 6e 74 6e 6f 64 85 29 KEY,parentnod.)
+| 3904: 57 02 06 17 1f 1f 01 7f 74 61 62 6c 65 67 65 6f W.......tablegeo
+| 3920: 31 5f 6e 6f 64 65 67 65 6f 31 5f 6e 6f 64 65 03 1_nodegeo1_node.
+| 3936: 43 52 45 41 54 45 20 54 41 42 4c 45 20 22 67 65 CREATE TABLE .ge
+| 3952: 6f 31 5f 6e 6f 64 65 22 28 6e 6f 64 65 6e 6f 20 o1_node.(nodeno
+| 3968: 49 4e 54 45 47 45 52 20 50 52 49 4d 41 52 59 20 INTEGER PRIMARY
+| 3984: 4b 45 59 2c 64 61 74 61 29 65 01 07 17 21 21 01 KEY,data)e...!!.
+| 4000: 81 15 74 61 62 6c 65 67 65 6f 31 5f 72 6f 77 69 ..tablegeo1_rowi
+| 4016: 64 67 65 6f 31 5f 72 6f 77 69 64 02 43 52 45 41 dgeo1_rowid.CREA
+| 4032: 54 45 20 54 41 42 4c 45 20 22 67 65 6f 31 5f 72 TE TABLE .geo1_r
+| 4048: 6f 77 69 64 22 28 72 6f 77 69 64 20 49 4e 54 45 owid.(rowid INTE
+| 4064: 47 45 52 20 50 52 49 4d 41 52 59 20 4b 45 59 2c GER PRIMARY KEY,
+| 4080: 6e 6f 64 65 6e 6f 2c 61 30 2c 61 31 2c 61 32 29 nodeno,a0,a1,a2)
+| page 2 offset 4096
+| 0: 0d 00 00 00 0a 0d ab 00 0f c9 0f 88 0f 48 0f 00 .............H..
+| 3488: 00 00 00 00 00 00 00 00 00 00 00 45 82 0a 06 00 ...........E....
+| 3504: 09 74 1d 13 01 00 00 06 00 80 b5 43 00 80 ac 43 .t.........C...C
+| 3520: 00 00 bd 43 8f 82 9f 43 71 fd c9 43 8f 02 a7 43 ...C...Cq..C...C
+| 3536: 71 fd c8 43 e4 bd a8 43 64 bb bd 43 f4 3d a2 43 q..C...Cd..C.=.C
+| 3552: 64 3b b7 43 00 80 ad 43 61 6e 67 6c 65 2d 33 30 d;.C...Cangle-30
+| 3568: 72 65 64 32 81 4e 06 00 09 44 23 17 01 00 00 03 red2.N...D#.....
+| 3584: 00 40 3f 44 00 c0 20 44 00 c0 46 44 00 c0 20 44 .@?D.. D..FD.. D
+| 3600: 00 00 43 44 00 40 28 44 74 72 69 61 6e 67 6c 65 ..CD.@(Dtriangle
+| 3616: 2d 33 30 62 6c 61 63 6b 35 82 3e 06 00 09 54 1d -30black5.>...T.
+| 3632: 13 01 00 00 04 00 40 54 44 00 80 1d 44 9a c9 5c ......@TD...D...
+| 3648: 44 66 36 1b 44 33 13 5f 44 00 c0 23 44 9a 89 5b Df6.D3._D..#D..[
+| 3664: 44 a4 60 1d 44 61 72 72 6f 77 2d 35 30 72 65 64 D.`.Darrow-50red
+| 3680: 36 74 06 00 09 54 1b 17 01 00 00 04 00 80 0d 44 6t...T.........D
+| 3696: 00 00 f2 42 0a d7 04 44 00 00 ca 42 0a 77 05 44 ...B...D...B.w.D
+| 3712: 0a 57 c1 42 00 20 0e 44 0a 57 e9 42 6c 69 6e 65 .W.B. .D.W.Bline
+| 3728: 2d 34 30 67 72 65 65 6e 36 72 06 00 09 54 1b 17 -40green6r...T..
+| 3744: 01 00 00 04 00 00 7b 43 00 00 ea 42 29 5c 58 43 .......C...B).XC
+| 3760: 00 00 c2 42 29 dc 5a 43 0a 57 b9 42 00 80 7d 43 ...B).ZC.W.B...C
+| 3776: 0a 57 e1 42 6c 69 6e 65 2d 34 30 67 72 65 65 6e .W.Bline-40green
+| 3792: 36 54 06 00 09 54 1b 17 01 00 00 04 00 00 a2 43 6T...T.........C
+| 3808: 00 00 24 44 00 00 b6 43 00 00 24 44 00 00 b6 43 ..$D...C..$D...C
+| 3824: 00 40 25 44 00 00 a2 43 00 40 25 44 6c 69 6e 65 .@%D...C.@%Dline
+| 3840: 2d 34 30 62 6c 61 63 6b 3e 37 06 00 09 64 1d 15 -40black>7...d..
+| 3856: 01 00 00 05 00 80 f0 43 00 00 54 43 66 16 01 44 .......C..TCf..D
+| 3872: 66 a6 30 43 cd ec 09 44 00 00 54 43 8f 0a 09 44 f.0C...D..TC...D
+| 3888: a4 d0 73 43 66 16 01 44 9a 59 77 43 68 6f 75 73 ..sCf..D.YwChous
+| 3904: 65 2d 37 30 62 6c 75 65 3e 35 06 00 09 64 1d 15 e-70blue>5...d..
+| 3920: 01 00 00 05 00 00 a2 43 00 00 5a 43 cd ac b3 43 .......C..ZC...C
+| 3936: 66 a6 36 43 9a 59 c5 43 00 00 5a 43 1f 95 c3 43 f.6C.Y.C..ZC...C
+| 3952: a4 d0 79 43 cd ac b3 43 9a 59 7d 43 68 6f 75 73 ..yC...C.Y.Chous
+| 3968: 65 2d 37 30 62 6c 75 65 3f 2c 06 00 09 64 1d 17 e-70blue?,...d..
+| 3984: 01 00 00 05 00 00 f5 43 00 00 2f 43 00 00 07 44 .......C../C...D
+| 4000: 00 00 2f 43 00 00 07 44 00 00 61 43 00 c0 00 44 ../C...D..aC...D
+| 4016: 00 00 75 43 00 00 f5 43 00 00 61 43 68 6f 75 73 ..uC...C..aChous
+| 4032: 65 2d 37 30 62 6c 61 63 6b 35 1f 06 10 09 54 19 e-70black5....T.
+| 4048: 17 01 00 00 04 00 00 9b 43 00 00 67 43 0a 57 92 ........C..gC.W.
+| 4064: 43 00 00 5d 43 0a 57 97 43 14 ae 4b 42 ff ff a0 C..]C.W.C..KB...
+| 4080: 43 14 ae 55 43 62 6f 78 2d 32 30 67 72 65 65 6e C..UCbox-20green
+| page 3 offset 8192
+| 0: 0d 00 00 00 01 0b 2d 00 0b 2e 00 00 00 00 00 00 ......-.........
+| 2848: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 89 50 ...............P
+| 2864: 01 04 00 93 24 00 00 00 0a 00 00 00 00 00 00 01 ....$...........
+| 2880: 0a 43 b5 80 00 43 c9 fd 71 43 9f 82 8f 43 ad 80 .C...C..qC...C..
+| 2896: 00 00 00 00 00 00 00 00 72 43 58 5c 29 43 7d 80 ........rCX.)C..
+| 2912: 00 42 b9 57 0a 42 ea 00 00 00 00 00 00 00 00 00 .B.W.B..........
+| 2928: 35 43 a2 00 00 43 c5 59 9a 43 36 a6 66 43 7d 59 5C...C.Y.C6.fC.Y
+| 2944: 9a 00 00 00 00 00 00 00 1f 43 92 57 0a 43 a0 00 .........C.W.C..
+| 2960: 00 43 4b ae 14 43 67 00 00 00 00 00 00 00 00 00 .CK..Cg.........
+| 2976: 37 43 f0 80 00 44 09 ec cd 43 30 a6 66 43 77 59 7C...D...C0.fCwY
+| 2992: 9a 00 00 00 00 00 00 00 2c 43 f5 00 00 44 07 00 ........,C...D..
+| 3008: 00 43 2f 00 00 43 75 00 00 00 00 00 00 00 00 00 .C/..Cu.........
+| 3024: 74 44 04 d7 0a 44 0e 20 00 42 c1 57 0a 42 f2 00 tD...D. .B.W.B..
+| 3040: 00 00 00 00 00 00 00 00 ce 44 3f 40 00 44 46 c0 .........D?@.DF.
+| 3056: 00 44 20 c0 00 44 28 40 00 00 00 00 00 00 00 00 .D ..D(@........
+| 3072: be 44 54 40 00 44 5f 13 33 44 1b 36 66 44 23 c0 .DT@.D_.3D.6fD#.
+| 3088: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C..
+| 3104: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........
+| 3120: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@
+| 3136: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C..
+| 3152: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........
+| 3168: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@
+| 3184: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C..
+| 3200: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........
+| 3216: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@
+| 3232: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C..
+| 3248: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........
+| 3264: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@
+| 3280: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C..
+| 3296: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 00 .D$..D%@........
+| 3312: 54 43 a2 00 00 43 b6 00 00 44 24 00 00 44 25 40 TC...C...D$..D%@
+| 3328: 00 00 00 00 00 00 00 00 54 43 a2 00 00 43 b6 00 ........TC...C..
+| 3344: 00 44 24 00 00 44 25 40 00 00 00 00 00 00 00 01 .D$..D%@........
+| 3360: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C..
+| 3376: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV.
+| 3392: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C..........
+| 3408: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C..
+| 3424: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV.
+| 3440: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C..........
+| 3456: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C..
+| 3472: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV.
+| 3488: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C..........
+| 3504: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C..
+| 3520: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV.
+| 3536: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C..........
+| 3552: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C..
+| 3568: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV.
+| 3584: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C..........
+| 3600: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C..
+| 3616: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV.
+| 3632: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C..........
+| 3648: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C..
+| 3664: 00 00 00 00 00 00 00 01 36 44 53 e0 00 44 56 bb ........6DS..DV.
+| 3680: 64 43 71 34 bc 43 7d 00 00 00 00 00 00 00 00 01 dCq4.C..........
+| 3696: 36 44 53 e0 00 44 56 bb 64 43 71 34 bc 43 7d 00 6DS..DV.dCq4.C..
+| page 4 offset 12288
+| 0: 0d 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 ................
+| page 5 offset 16384
+| 0: 0d 00 00 00 01 0f 8f 00 00 00 00 00 00 00 00 00 ................
+| 3968: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6f ...............o
+| 3984: 01 04 81 57 19 5b 5b 33 30 30 2c 33 30 30 5d 2c ...W.[[300,300],
+| 4000: 5b 34 30 30 2c 33 35 30 5d 2c 5b 35 30 30 2c 32 [400,350],[500,2
+| 4016: 35 30 5d 2c 5b 34 38 30 2c 35 30 30 5d 2c 5b 34 50],[480,500],[4
+| 4032: 30 30 2c 34 38 30 5d 2c 5c 33 30 30 2c 35 35 30 00,480],.300,550
+| 4048: 5d 2c 5b 32 38 30 2c 34 35 30 5d 2c 5b 33 32 30 ],[280,450],[320
+| 4064: 2c 34 30 30 5d 2c 5b 32 38 30 2c 33 35 30 5d 2c ,400],[280,350],
+| 4080: 5b 33 30 30 2c 33 30 00 00 00 00 00 00 00 00 00 [300,30.........
+| end crash-7b37d80f000235.db
+}]} {}
+
+ifcapable geopoly {
+
+do_catchsql_test rtreefuzz001-601 {
+ SAVEPOINT one;
+ UPDATE geo1 SET clr=CASE WHEN rowid IN ( SELECT geo1.rowid FROM geo1, querypoly ) THEN 'e' ELSE 'blue' END;
+} {1 {database disk image is malformed}}
+
+do_catchsql_test rtreefuzz001-602 {
+ SELECT geopoly_svg(_shape, printf('j',geo1.clr))
+ FROM geo1, querypoly WHERE geopoly_overlap(_shape, poly);
+} {1 {database disk image is malformed}}
+
+} ;# ifcapable geopoly
+
+finish_test
diff --git a/ext/rtree/sqlite3rtree.h b/ext/rtree/sqlite3rtree.h
new file mode 100644
index 0000000..9976c2d
--- /dev/null
+++ b/ext/rtree/sqlite3rtree.h
@@ -0,0 +1,117 @@
+/*
+** 2010 August 30
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+*************************************************************************
+*/
+
+#ifndef _SQLITE3RTREE_H_
+#define _SQLITE3RTREE_H_
+
+#include <sqlite3.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry;
+typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info;
+
+/* The double-precision datatype used by RTree depends on the
+** SQLITE_RTREE_INT_ONLY compile-time option.
+*/
+#ifdef SQLITE_RTREE_INT_ONLY
+ typedef sqlite3_int64 sqlite3_rtree_dbl;
+#else
+ typedef double sqlite3_rtree_dbl;
+#endif
+
+/*
+** Register a geometry callback named zGeom that can be used as part of an
+** R-Tree geometry query as follows:
+**
+** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zGeom(... params ...)
+*/
+int sqlite3_rtree_geometry_callback(
+ sqlite3 *db,
+ const char *zGeom,
+ int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*),
+ void *pContext
+);
+
+
+/*
+** A pointer to a structure of the following type is passed as the first
+** argument to callbacks registered using rtree_geometry_callback().
+*/
+struct sqlite3_rtree_geometry {
+ void *pContext; /* Copy of pContext passed to s_r_g_c() */
+ int nParam; /* Size of array aParam[] */
+ sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */
+ void *pUser; /* Callback implementation user data */
+ void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */
+};
+
+/*
+** Register a 2nd-generation geometry callback named zScore that can be
+** used as part of an R-Tree geometry query as follows:
+**
+** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zQueryFunc(... params ...)
+*/
+int sqlite3_rtree_query_callback(
+ sqlite3 *db,
+ const char *zQueryFunc,
+ int (*xQueryFunc)(sqlite3_rtree_query_info*),
+ void *pContext,
+ void (*xDestructor)(void*)
+);
+
+
+/*
+** A pointer to a structure of the following type is passed as the
+** argument to scored geometry callback registered using
+** sqlite3_rtree_query_callback().
+**
+** Note that the first 5 fields of this structure are identical to
+** sqlite3_rtree_geometry. This structure is a subclass of
+** sqlite3_rtree_geometry.
+*/
+struct sqlite3_rtree_query_info {
+ void *pContext; /* pContext from when function registered */
+ int nParam; /* Number of function parameters */
+ sqlite3_rtree_dbl *aParam; /* value of function parameters */
+ void *pUser; /* callback can use this, if desired */
+ void (*xDelUser)(void*); /* function to free pUser */
+ sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */
+ unsigned int *anQueue; /* Number of pending entries in the queue */
+ int nCoord; /* Number of coordinates */
+ int iLevel; /* Level of current node or entry */
+ int mxLevel; /* The largest iLevel value in the tree */
+ sqlite3_int64 iRowid; /* Rowid for current entry */
+ sqlite3_rtree_dbl rParentScore; /* Score of parent node */
+ int eParentWithin; /* Visibility of parent node */
+ int eWithin; /* OUT: Visibility */
+ sqlite3_rtree_dbl rScore; /* OUT: Write the score here */
+ /* The following fields are only available in 3.8.11 and later */
+ sqlite3_value **apSqlParam; /* Original SQL values of parameters */
+};
+
+/*
+** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin.
+*/
+#define NOT_WITHIN 0 /* Object completely outside of query region */
+#define PARTLY_WITHIN 1 /* Object partially overlaps query region */
+#define FULLY_WITHIN 2 /* Object fully contained within query region */
+
+
+#ifdef __cplusplus
+} /* end of the 'extern "C"' block */
+#endif
+
+#endif /* ifndef _SQLITE3RTREE_H_ */
diff --git a/ext/rtree/test_rtreedoc.c b/ext/rtree/test_rtreedoc.c
new file mode 100644
index 0000000..cdbcb2e
--- /dev/null
+++ b/ext/rtree/test_rtreedoc.c
@@ -0,0 +1,348 @@
+/*
+** 2010 August 28
+**
+** 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.
+**
+*************************************************************************
+** Code for testing all sorts of SQLite interfaces. This code
+** is not included in the SQLite library.
+*/
+
+#include "sqlite3.h"
+#if defined(INCLUDE_SQLITE_TCL_H)
+# include "sqlite_tcl.h"
+#else
+# include "tcl.h"
+#endif
+
+/* Solely for the UNUSED_PARAMETER() macro. */
+#include "sqliteInt.h"
+
+#ifdef SQLITE_ENABLE_RTREE
+
+typedef struct BoxGeomCtx BoxGeomCtx;
+struct BoxGeomCtx {
+ Tcl_Interp *interp;
+ Tcl_Obj *pScript;
+};
+
+typedef struct BoxQueryCtx BoxQueryCtx;
+struct BoxQueryCtx {
+ Tcl_Interp *interp;
+ Tcl_Obj *pScript;
+};
+
+static void testDelUser(void *pCtx){
+ BoxGeomCtx *p = (BoxGeomCtx*)pCtx;
+ Tcl_EvalObjEx(p->interp, p->pScript, 0);
+ Tcl_DecrRefCount(p->pScript);
+ sqlite3_free(p);
+}
+
+static int invokeTclGeomCb(
+ const char *zName,
+ sqlite3_rtree_geometry *p,
+ int nCoord,
+ sqlite3_rtree_dbl *aCoord
+){
+ int rc = SQLITE_OK;
+ if( p->pContext ){
+ char aPtr[64];
+ BoxGeomCtx *pCtx = (BoxGeomCtx*)p->pContext;
+ Tcl_Interp *interp = pCtx->interp;
+ Tcl_Obj *pScript = 0;
+ Tcl_Obj *pParam = 0;
+ Tcl_Obj *pCoord = 0;
+ int ii;
+ Tcl_Obj *pRes;
+
+
+ pScript = Tcl_DuplicateObj(pCtx->pScript);
+ Tcl_IncrRefCount(pScript);
+ Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(zName,-1));
+
+ sqlite3_snprintf(sizeof(aPtr)-1, aPtr, "%p", (void*)p->pContext);
+ Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(aPtr,-1));
+
+ pParam = Tcl_NewObj();
+ for(ii=0; ii<p->nParam; ii++){
+ Tcl_ListObjAppendElement(
+ interp, pParam, Tcl_NewDoubleObj(p->aParam[ii])
+ );
+ }
+ Tcl_ListObjAppendElement(interp, pScript, pParam);
+
+ pCoord = Tcl_NewObj();
+ for(ii=0; ii<nCoord; ii++){
+ Tcl_ListObjAppendElement(interp, pCoord, Tcl_NewDoubleObj(aCoord[ii]));
+ }
+ Tcl_ListObjAppendElement(interp, pScript, pCoord);
+
+ sqlite3_snprintf(sizeof(aPtr)-1, aPtr, "%p", (void*)p);
+ Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(aPtr,-1));
+
+ rc = Tcl_EvalObjEx(interp, pScript, 0);
+ if( rc!=TCL_OK ){
+ rc = SQLITE_ERROR;
+ }else{
+ int nObj = 0;
+ Tcl_Obj **aObj = 0;
+
+ pRes = Tcl_GetObjResult(interp);
+ if( Tcl_ListObjGetElements(interp, pRes, &nObj, &aObj) ) return TCL_ERROR;
+ if( nObj>0 ){
+ const char *zCmd = Tcl_GetString(aObj[0]);
+ if( 0==sqlite3_stricmp(zCmd, "zero") ){
+ p->aParam[0] = 0.0;
+ p->nParam = 1;
+ }
+ else if( 0==sqlite3_stricmp(zCmd, "user") ){
+ if( p->pUser || p->xDelUser ){
+ rc = SQLITE_ERROR;
+ }else{
+ BoxGeomCtx *pBGCtx = sqlite3_malloc(sizeof(BoxGeomCtx));
+ if( pBGCtx==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pBGCtx->interp = interp;
+ pBGCtx->pScript = Tcl_DuplicateObj(pRes);
+ Tcl_IncrRefCount(pBGCtx->pScript);
+ Tcl_ListObjReplace(interp, pBGCtx->pScript, 0, 1, 0, 0);
+ p->pUser = (void*)pBGCtx;
+ p->xDelUser = testDelUser;
+ }
+ }
+ }
+ else if( 0==sqlite3_stricmp(zCmd, "user_is_zero") ){
+ if( p->pUser || p->xDelUser ) rc = SQLITE_ERROR;
+ }
+ }
+ }
+ }
+ return rc;
+}
+
+/*
+# EVIDENCE-OF: R-00693-36727 The legacy xGeom callback is invoked with
+# four arguments.
+
+# EVIDENCE-OF: R-50437-53270 The first argument is a pointer to an
+# sqlite3_rtree_geometry structure which provides information about how
+# the SQL function was invoked.
+
+# EVIDENCE-OF: R-00090-24248 The third argument, aCoord[], is an array
+# of nCoord coordinates that defines a bounding box to be tested.
+
+# EVIDENCE-OF: R-28207-40885 The last argument is a pointer into which
+# the callback result should be written.
+
+*/
+static int box_geom(
+ sqlite3_rtree_geometry *p, /* R-50437-53270 */
+ int nCoord, /* R-02424-24769 */
+ sqlite3_rtree_dbl *aCoord, /* R-00090-24248 */
+ int *pRes /* R-28207-40885 */
+){
+ int ii;
+
+ if( p->nParam!=nCoord ){
+ invokeTclGeomCb("box", p, nCoord, aCoord);
+ return SQLITE_ERROR;
+ }
+ if( invokeTclGeomCb("box", p, nCoord, aCoord) ) return SQLITE_ERROR;
+
+ for(ii=0; ii<nCoord; ii+=2){
+ if( aCoord[ii]>p->aParam[ii+1] || aCoord[ii+1]<p->aParam[ii] ){
+ /* R-28207-40885 */
+ *pRes = 0;
+ return SQLITE_OK;
+ }
+ }
+
+ /* R-28207-40885 */
+ *pRes = 1;
+
+ return SQLITE_OK;
+}
+
+static int SQLITE_TCLAPI register_box_geom(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
+ extern const char *sqlite3ErrName(int);
+ sqlite3 *db;
+ BoxGeomCtx *pCtx;
+ char aPtr[64];
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB SCRIPT");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+ pCtx = (BoxGeomCtx*)ckalloc(sizeof(BoxGeomCtx*));
+ pCtx->interp = interp;
+ pCtx->pScript = Tcl_DuplicateObj(objv[2]);
+ Tcl_IncrRefCount(pCtx->pScript);
+
+ sqlite3_rtree_geometry_callback(db, "box", box_geom, (void*)pCtx);
+
+ sqlite3_snprintf(64, aPtr, "%p", (void*)pCtx);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(aPtr, -1));
+ return TCL_OK;
+}
+
+static int box_query(sqlite3_rtree_query_info *pInfo){
+ const char *azParentWithin[] = {"not", "partly", "fully", 0};
+ BoxQueryCtx *pCtx = (BoxQueryCtx*)pInfo->pContext;
+ Tcl_Interp *interp = pCtx->interp;
+ Tcl_Obj *pEval;
+ Tcl_Obj *pArg;
+ Tcl_Obj *pTmp = 0;
+ int rc;
+ int ii;
+
+ pEval = Tcl_DuplicateObj(pCtx->pScript);
+ Tcl_IncrRefCount(pEval);
+ pArg = Tcl_NewObj();
+ Tcl_IncrRefCount(pArg);
+
+ /* aParam[] */
+ pTmp = Tcl_NewObj();
+ Tcl_IncrRefCount(pTmp);
+ for(ii=0; ii<pInfo->nParam; ii++){
+ Tcl_Obj *p = Tcl_NewDoubleObj(pInfo->aParam[ii]);
+ Tcl_ListObjAppendElement(interp, pTmp, p);
+ }
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("aParam", -1));
+ Tcl_ListObjAppendElement(interp, pArg, pTmp);
+ Tcl_DecrRefCount(pTmp);
+
+ /* aCoord[] */
+ pTmp = Tcl_NewObj();
+ Tcl_IncrRefCount(pTmp);
+ for(ii=0; ii<pInfo->nCoord; ii++){
+ Tcl_Obj *p = Tcl_NewDoubleObj(pInfo->aCoord[ii]);
+ Tcl_ListObjAppendElement(interp, pTmp, p);
+ }
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("aCoord", -1));
+ Tcl_ListObjAppendElement(interp, pArg, pTmp);
+ Tcl_DecrRefCount(pTmp);
+
+ /* anQueue[] */
+ pTmp = Tcl_NewObj();
+ Tcl_IncrRefCount(pTmp);
+ for(ii=0; ii<=pInfo->mxLevel; ii++){
+ Tcl_Obj *p = Tcl_NewIntObj((int)pInfo->anQueue[ii]);
+ Tcl_ListObjAppendElement(interp, pTmp, p);
+ }
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("anQueue", -1));
+ Tcl_ListObjAppendElement(interp, pArg, pTmp);
+ Tcl_DecrRefCount(pTmp);
+
+ /* iLevel */
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("iLevel", -1));
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewIntObj(pInfo->iLevel));
+
+ /* mxLevel */
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("mxLevel", -1));
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewIntObj(pInfo->mxLevel));
+
+ /* iRowid */
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("iRowid", -1));
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewWideIntObj(pInfo->iRowid));
+
+ /* rParentScore */
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("rParentScore", -1));
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewDoubleObj(pInfo->rParentScore));
+
+ /* eParentWithin */
+ assert( pInfo->eParentWithin==0
+ || pInfo->eParentWithin==1
+ || pInfo->eParentWithin==2
+ );
+ Tcl_ListObjAppendElement(interp, pArg, Tcl_NewStringObj("eParentWithin", -1));
+ Tcl_ListObjAppendElement(interp, pArg,
+ Tcl_NewStringObj(azParentWithin[pInfo->eParentWithin], -1)
+ );
+
+ Tcl_ListObjAppendElement(interp, pEval, pArg);
+ rc = Tcl_EvalObjEx(interp, pEval, 0) ? SQLITE_ERROR : SQLITE_OK;
+
+ if( rc==SQLITE_OK ){
+ double rScore = 0.0;
+ int nObj = 0;
+ int eP = 0;
+ Tcl_Obj **aObj = 0;
+ Tcl_Obj *pRes = Tcl_GetObjResult(interp);
+
+ if( Tcl_ListObjGetElements(interp, pRes, &nObj, &aObj)
+ || nObj!=2
+ || Tcl_GetDoubleFromObj(interp, aObj[1], &rScore)
+ || Tcl_GetIndexFromObj(interp, aObj[0], azParentWithin, "value", 0, &eP)
+ ){
+ rc = SQLITE_ERROR;
+ }else{
+ pInfo->rScore = rScore;
+ pInfo->eParentWithin = eP;
+ }
+ }
+
+ Tcl_DecrRefCount(pArg);
+ Tcl_DecrRefCount(pEval);
+ return rc;
+}
+
+static void box_query_destroy(void *p){
+ BoxQueryCtx *pCtx = (BoxQueryCtx*)p;
+ Tcl_DecrRefCount(pCtx->pScript);
+ ckfree((char*)pCtx);
+}
+
+static int SQLITE_TCLAPI register_box_query(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
+ extern const char *sqlite3ErrName(int);
+ sqlite3 *db;
+ BoxQueryCtx *pCtx;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB SCRIPT");
+ return TCL_ERROR;
+ }
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+ pCtx = (BoxQueryCtx*)ckalloc(sizeof(BoxQueryCtx));
+ pCtx->interp = interp;
+ pCtx->pScript = Tcl_DuplicateObj(objv[2]);
+ Tcl_IncrRefCount(pCtx->pScript);
+
+ sqlite3_rtree_query_callback(
+ db, "qbox", box_query, (void*)pCtx, box_query_destroy
+ );
+
+ Tcl_ResetResult(interp);
+ return TCL_OK;
+}
+#endif /* SQLITE_ENABLE_RTREE */
+
+
+int Sqlitetestrtreedoc_Init(Tcl_Interp *interp){
+#ifdef SQLITE_ENABLE_RTREE
+ Tcl_CreateObjCommand(interp, "register_box_geom", register_box_geom, 0, 0);
+ Tcl_CreateObjCommand(interp, "register_box_query", register_box_query, 0, 0);
+#endif /* SQLITE_ENABLE_RTREE */
+ return TCL_OK;
+}
diff --git a/ext/rtree/tkt3363.test b/ext/rtree/tkt3363.test
new file mode 100644
index 0000000..db05ed5
--- /dev/null
+++ b/ext/rtree/tkt3363.test
@@ -0,0 +1,50 @@
+# 2008 Sep 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing that ticket #3363 is fixed.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source [file join [file dirname [info script]] rtree_util.tcl]
+source $testdir/tester.tcl
+
+ifcapable !rtree {
+ finish_test
+ return
+}
+
+do_test tkt3363.1.1 {
+ execsql { CREATE VIRTUAL TABLE t1 USING rtree(ii, x1, x2, y1, y2) }
+} {}
+
+do_test tkt3363.1.2 {
+ for {set ii 1} {$ii < 50} {incr ii} {
+ set x 1000000
+ set y [expr 4000000 + $ii*10]
+ execsql { INSERT INTO t1 VALUES($ii, $x, $x, $y, $y) }
+ }
+} {}
+
+do_test tkt3363.1.3 {
+ execsql {
+ SELECT count(*) FROM t1 WHERE +y2>4000425.0;
+ }
+} {7}
+
+do_test tkt3363.1.4 {
+ execsql {
+ SELECT count(*) FROM t1 WHERE y2>4000425.0;
+ }
+} {7}
+
+finish_test
diff --git a/ext/rtree/util/randomshape.tcl b/ext/rtree/util/randomshape.tcl
new file mode 100644
index 0000000..98725bc
--- /dev/null
+++ b/ext/rtree/util/randomshape.tcl
@@ -0,0 +1,87 @@
+#!/usr/bin/tclsh
+#
+# This script generates a cluster of random polygons that are useful
+# for testing the geopoly extension.
+#
+# Usage:
+#
+# tclsh randomshape.tcl | tee x.sql | sqlite3 >x.html
+#
+# The output files are x.sql and x.html. Run the above multiple times
+# until an interesting "x.html" file is found, then use the "x.sql" inputs
+# to construct test cases.
+#
+proc randomenclosure {cx cy p1 p2 p3 p4} {
+ set r 0
+ set pi 3.145926
+ set pi2 [expr {$pi*2}]
+ set x0 [expr {$cx + rand()*$p3 + $p4}]
+ set ans "\[\[$x0,$cy\]"
+ while {1} {
+ set r [expr {$r+$p1+$p2*rand()}]
+ if {$r>=$pi2} break
+ set m [expr {rand()*$p3 + $p4}]
+ set x [expr {$cx+$m*cos($r)}]
+ set y [expr {$cy+$m*sin($r)}]
+ append ans ",\[$x,$y\]"
+ }
+ append ans ",\[$x0,$cy\]\]"
+ return $ans
+}
+proc randomshape1 {} {
+ set cx [expr {100+int(rand()*800)}]
+ set cy [expr {100+int(rand()*600)}]
+ set p1 [expr {rand()*0.1}]
+ set p2 [expr {rand()*0.5+0.5}]
+ set p3 [expr {rand()*100+25}]
+ set p4 [expr {rand()*25}]
+ return [randomenclosure $cx $cy $p1 $p2 $p3 $p4]
+}
+proc randomshape1_sm {} {
+ set cx [expr {100+int(rand()*800)}]
+ set cy [expr {100+int(rand()*600)}]
+ set p1 [expr {rand()*0.1}]
+ set p2 [expr {rand()*0.5+0.5}]
+ set p3 [expr {rand()*10+25}]
+ set p4 [expr {rand()*5}]
+ return [randomenclosure $cx $cy $p1 $p2 $p3 $p4]
+}
+proc randomshape2 {} {
+ set cx [expr {400+int(rand()*200)}]
+ set cy [expr {300+int(rand()*200)}]
+ set p1 [expr {rand()*0.05}]
+ set p2 [expr {rand()*0.5+0.5}]
+ set p3 [expr {rand()*50+200}]
+ set p4 [expr {rand()*50+100}]
+ return [randomenclosure $cx $cy $p1 $p2 $p3 $p4]
+}
+proc randomcolor {} {
+ set n [expr {int(rand()*5)}]
+ return [lindex {red orange green blue purple} $n]
+}
+
+puts {.print '<html>'}
+puts {.print '<svg width="1000" height="800" style="border:1px solid black">'}
+puts {CREATE TABLE t1(poly,clr);}
+puts {CREATE TABLE t2(poly,clr);}
+for {set i 0} {$i<30} {incr i} {
+ puts "INSERT INTO t1(rowid,poly,clr)"
+ puts " VALUES($i,'[randomshape1]','[randomcolor]');"
+}
+for {set i 30} {$i<80} {incr i} {
+ puts "INSERT INTO t1(rowid,poly,clr)"
+ puts " VALUES($i,'[randomshape1_sm]','[randomcolor]');"
+}
+for {set i 100} {$i<105} {incr i} {
+ puts "INSERT INTO t2(rowid,poly,clr)"
+ puts " VALUES($i,'[randomshape2]','[randomcolor]');"
+}
+
+puts {DELETE FROM t1 WHERE geopoly_json(poly) IS NULL;}
+puts {SELECT geopoly_svg(poly,
+ printf('style="fill:none;stroke:%s;stroke-width:1;"',clr))
+ FROM t1;}
+puts {SELECT geopoly_svg(poly,
+ printf('style="fill:none;stroke:%s;stroke-width:2;"',clr))
+ FROM t2;}
+puts {.print '<svg>'}
diff --git a/ext/rtree/viewrtree.tcl b/ext/rtree/viewrtree.tcl
new file mode 100644
index 0000000..794677f
--- /dev/null
+++ b/ext/rtree/viewrtree.tcl
@@ -0,0 +1,188 @@
+
+load ./libsqlite3.dylib
+#package require sqlite3
+source [file join [file dirname $argv0] rtree_util.tcl]
+
+wm title . "SQLite r-tree viewer"
+
+if {[llength $argv]!=1} {
+ puts stderr "Usage: $argv0 <database-file>"
+ puts stderr ""
+ exit
+}
+sqlite3 db [lindex $argv 0]
+
+canvas .c -background white -width 400 -height 300 -highlightthickness 0
+
+button .b -text "Parent Node" -command {
+ set sql "SELECT parentnode FROM $::O(zTab)_parent WHERE nodeno = $::O(iNode)"
+ set ::O(iNode) [db one $sql]
+ if {$::O(iNode) eq ""} {set ::O(iNode) 1}
+ view_node
+}
+
+set O(iNode) 1
+set O(zTab) ""
+set O(listbox_captions) [list]
+set O(listbox_itemmap) [list]
+set O(listbox_highlight) -1
+
+listbox .l -listvariable ::O(listbox_captions) -yscrollcommand {.ls set}
+scrollbar .ls -command {.l yview}
+label .status -font courier -anchor w
+label .title -anchor w -text "Node 1:" -background white -borderwidth 0
+
+
+set rtree_tables [list]
+db eval {
+ SELECT name
+ FROM sqlite_master
+ WHERE type='table' AND sql LIKE '%virtual%table%using%rtree%'
+} {
+ set nCol [expr [llength [db eval "pragma table_info($name)"]]/6]
+ if {$nCol != 5} {
+ puts stderr "Not viewing $name - is not 2-dimensional"
+ } else {
+ lappend rtree_tables [list Table $name]
+ }
+}
+if {$rtree_tables eq ""} {
+ puts stderr "Cannot find an r-tree table in database [lindex $argv 0]"
+ puts stderr ""
+ exit
+}
+eval tk_optionMenu .select option_var $rtree_tables
+trace add variable option_var write set_option_var
+proc set_option_var {args} {
+ set ::O(zTab) [lindex $::option_var 1]
+ set ::O(iNode) 1
+ view_node
+}
+set ::O(zTab) [lindex $::rtree_tables 0 1]
+
+bind .l <1> {listbox_click [.l nearest %y]}
+bind .l <Motion> {listbox_mouseover [.l nearest %y]}
+bind .l <Leave> {listbox_mouseover -1}
+
+proc listbox_click {sel} {
+ if {$sel ne ""} {
+ set ::O(iNode) [lindex $::O(listbox_captions) $sel 1]
+ view_node
+ }
+}
+proc listbox_mouseover {i} {
+ set oldid [lindex $::O(listbox_itemmap) $::O(listbox_highlight)]
+ .c itemconfigure $oldid -fill ""
+
+ .l selection clear 0 end
+ .status configure -text ""
+ if {$i>=0} {
+ set id [lindex $::O(listbox_itemmap) $i]
+ .c itemconfigure $id -fill grey
+ .c lower $id
+ set ::O(listbox_highlight) $i
+ .l selection set $i
+ .status configure -text [cell_report db $::O(zTab) $::O(iNode) $i]
+ }
+}
+
+grid configure .select -row 0 -column 0 -columnspan 2 -sticky nsew
+grid configure .b -row 1 -column 0 -columnspan 2 -sticky nsew
+grid configure .l -row 2 -column 0 -sticky nsew
+grid configure .status -row 3 -column 0 -columnspan 3 -sticky nsew
+
+grid configure .title -row 0 -column 2 -sticky nsew
+grid configure .c -row 1 -column 2 -rowspan 2 -sticky nsew
+grid configure .ls -row 2 -column 1 -sticky nsew
+
+grid columnconfigure . 2 -weight 1
+grid rowconfigure . 2 -weight 1
+
+proc node_bbox {data} {
+ set xmin 0
+ set xmax 0
+ set ymin 0
+ set ymax 0
+ foreach {rowid xmin xmax ymin ymax} [lindex $data 0] break
+ foreach cell [lrange $data 1 end] {
+ foreach {rowid x1 x2 y1 y2} $cell break
+ if {$x1 < $xmin} {set xmin $x1}
+ if {$x2 > $xmax} {set xmax $x2}
+ if {$y1 < $ymin} {set ymin $y1}
+ if {$y2 > $ymax} {set ymax $y2}
+ }
+ list $xmin $xmax $ymin $ymax
+}
+
+proc view_node {} {
+ set iNode $::O(iNode)
+ set zTab $::O(zTab)
+
+ set data [rtree_node db $zTab $iNode 12]
+ set depth [rtree_nodedepth db $zTab $iNode]
+
+ .c delete all
+ set ::O(listbox_captions) [list]
+ set ::O(listbox_itemmap) [list]
+ set $::O(listbox_highlight) -1
+
+ .b configure -state normal
+ if {$iNode == 1} {.b configure -state disabled}
+ .title configure -text "Node $iNode: [cell_report db $zTab $iNode -1]"
+
+ foreach {xmin xmax ymin ymax} [node_bbox $data] break
+ set total_area 0.0
+
+ set xscale [expr {double([winfo width .c]-20)/($xmax-$xmin)}]
+ set yscale [expr {double([winfo height .c]-20)/($ymax-$ymin)}]
+
+ set xoff [expr {10.0 - $xmin*$xscale}]
+ set yoff [expr {10.0 - $ymin*$yscale}]
+
+ foreach cell $data {
+ foreach {rowid x1 x2 y1 y2} $cell break
+ set total_area [expr {$total_area + ($x2-$x1)*($y2-$y1)}]
+ set x1 [expr {$x1*$xscale + $xoff}]
+ set x2 [expr {$x2*$xscale + $xoff}]
+ set y1 [expr {$y1*$yscale + $yoff}]
+ set y2 [expr {$y2*$yscale + $yoff}]
+
+ set id [.c create rectangle $x1 $y1 $x2 $y2]
+ if {$depth>0} {
+ lappend ::O(listbox_captions) "Node $rowid"
+ lappend ::O(listbox_itemmap) $id
+ }
+ }
+}
+
+proc cell_report {db zTab iParent iCell} {
+ set data [rtree_node db $zTab $iParent 12]
+ set cell [lindex $data $iCell]
+
+ foreach {xmin xmax ymin ymax} [node_bbox $data] break
+ set total_area [expr ($xmax-$xmin)*($ymax-$ymin)]
+
+ if {$cell eq ""} {
+ set cell_area 0.0
+ foreach cell $data {
+ foreach {rowid x1 x2 y1 y2} $cell break
+ set cell_area [expr $cell_area+($x2-$x1)*($y2-$y1)]
+ }
+ set cell_area [expr $cell_area/[llength $data]]
+ set zReport [format "Size = %.1f x %.1f Average child area = %.1f%%" \
+ [expr $xmax-$xmin] [expr $ymax-$ymin] [expr 100.0*$cell_area/$total_area]\
+ ]
+ append zReport " Sub-tree height: [rtree_nodedepth db $zTab $iParent]"
+ } else {
+ foreach {rowid x1 x2 y1 y2} $cell break
+ set cell_area [expr ($x2-$x1)*($y2-$y1)]
+ set zReport [format "Size = %.1f x %.1f Area = %.1f%%" \
+ [expr $x2-$x1] [expr $y2-$y1] [expr 100.0*$cell_area/$total_area]
+ ]
+ }
+
+ return $zReport
+}
+
+view_node
+bind .c <Configure> view_node
diff --git a/ext/rtree/visual01.txt b/ext/rtree/visual01.txt
new file mode 100644
index 0000000..281d610
--- /dev/null
+++ b/ext/rtree/visual01.txt
@@ -0,0 +1,602 @@
+#!sqlite3
+#
+# This is a visual test case for the geopoly virtual table.
+#
+# Run this script in the sqlite3 CLI, and redirect output into an
+# HTML file. This display the HTML in a webbrowser.
+#
+
+/* Test data.
+** Lots of shapes to be displayed over a 1000x800 canvas.
+*/
+CREATE TEMP TABLE basis(name TEXT, jshape TEXT);
+INSERT INTO basis(name,jshape) VALUES
+ ('box-20','[[0,0],[20,0],[20,20],[0,20],[0,0]]'),
+ ('house-70','[[0,0],[50,0],[50,50],[25,70],[0,50],[0,0]]'),
+ ('line-40','[[0,0],[40,0],[40,5],[0,5],[0,0]]'),
+ ('line-80','[[0,0],[80,0],[80,7],[0,7],[0,0]]'),
+ ('arrow-50','[[0,0],[25,25],[0,50],[15,25],[0,0]]'),
+ ('triangle-30','[[0,0],[30,0],[15,30],[0,0]]'),
+ ('angle-30','[[0,0],[30,0],[30,30],[26,30],[26,4],[0,4],[0,0]]'),
+ ('star-10','[[1,0],[5,2],[9,0],[7,4],[10,8],[7,7],[5,10],[3,7],[0,8],[3,4],[1,0]]');
+CREATE TEMP TABLE xform(A,B,C,D,clr);
+INSERT INTO xform(A,B,clr) VALUES
+ (1,0,'black'),
+ (0.707,0.707,'blue'),
+ (0.5,0.866,'red'),
+ (-0.866,0.5,'green');
+CREATE TEMP TABLE xyoff(id1,id2,xoff,yoff,PRIMARY KEY(id1,id2,xoff,yoff))
+ WITHOUT ROWID;
+INSERT INTO xyoff VALUES(1,1,811,659);
+INSERT INTO xyoff VALUES(1,1,235,550);
+INSERT INTO xyoff VALUES(1,1,481,620);
+INSERT INTO xyoff VALUES(1,1,106,494);
+INSERT INTO xyoff VALUES(1,1,487,106);
+INSERT INTO xyoff VALUES(1,1,817,595);
+INSERT INTO xyoff VALUES(1,1,240,504);
+INSERT INTO xyoff VALUES(1,1,806,457);
+INSERT INTO xyoff VALUES(1,1,608,107);
+INSERT INTO xyoff VALUES(1,1,768,662);
+INSERT INTO xyoff VALUES(1,2,808,528);
+INSERT INTO xyoff VALUES(1,2,768,528);
+INSERT INTO xyoff VALUES(1,2,771,171);
+INSERT INTO xyoff VALUES(1,2,275,671);
+INSERT INTO xyoff VALUES(1,2,326,336);
+INSERT INTO xyoff VALUES(1,2,690,688);
+INSERT INTO xyoff VALUES(1,2,597,239);
+INSERT INTO xyoff VALUES(1,2,317,528);
+INSERT INTO xyoff VALUES(1,2,366,223);
+INSERT INTO xyoff VALUES(1,2,621,154);
+INSERT INTO xyoff VALUES(1,3,829,469);
+INSERT INTO xyoff VALUES(1,3,794,322);
+INSERT INTO xyoff VALUES(1,3,358,387);
+INSERT INTO xyoff VALUES(1,3,184,444);
+INSERT INTO xyoff VALUES(1,3,729,500);
+INSERT INTO xyoff VALUES(1,3,333,523);
+INSERT INTO xyoff VALUES(1,3,117,595);
+INSERT INTO xyoff VALUES(1,3,496,201);
+INSERT INTO xyoff VALUES(1,3,818,601);
+INSERT INTO xyoff VALUES(1,3,541,343);
+INSERT INTO xyoff VALUES(1,4,603,248);
+INSERT INTO xyoff VALUES(1,4,761,649);
+INSERT INTO xyoff VALUES(1,4,611,181);
+INSERT INTO xyoff VALUES(1,4,607,233);
+INSERT INTO xyoff VALUES(1,4,860,206);
+INSERT INTO xyoff VALUES(1,4,310,231);
+INSERT INTO xyoff VALUES(1,4,727,539);
+INSERT INTO xyoff VALUES(1,4,660,661);
+INSERT INTO xyoff VALUES(1,4,403,133);
+INSERT INTO xyoff VALUES(1,4,619,331);
+INSERT INTO xyoff VALUES(2,1,712,578);
+INSERT INTO xyoff VALUES(2,1,567,313);
+INSERT INTO xyoff VALUES(2,1,231,423);
+INSERT INTO xyoff VALUES(2,1,490,175);
+INSERT INTO xyoff VALUES(2,1,898,353);
+INSERT INTO xyoff VALUES(2,1,589,483);
+INSERT INTO xyoff VALUES(2,1,188,462);
+INSERT INTO xyoff VALUES(2,1,720,106);
+INSERT INTO xyoff VALUES(2,1,793,380);
+INSERT INTO xyoff VALUES(2,1,154,396);
+INSERT INTO xyoff VALUES(2,2,324,218);
+INSERT INTO xyoff VALUES(2,2,120,327);
+INSERT INTO xyoff VALUES(2,2,655,133);
+INSERT INTO xyoff VALUES(2,2,516,603);
+INSERT INTO xyoff VALUES(2,2,529,572);
+INSERT INTO xyoff VALUES(2,2,481,212);
+INSERT INTO xyoff VALUES(2,2,802,107);
+INSERT INTO xyoff VALUES(2,2,234,509);
+INSERT INTO xyoff VALUES(2,2,501,269);
+INSERT INTO xyoff VALUES(2,2,349,553);
+INSERT INTO xyoff VALUES(2,3,495,685);
+INSERT INTO xyoff VALUES(2,3,897,372);
+INSERT INTO xyoff VALUES(2,3,350,681);
+INSERT INTO xyoff VALUES(2,3,832,257);
+INSERT INTO xyoff VALUES(2,3,778,149);
+INSERT INTO xyoff VALUES(2,3,683,426);
+INSERT INTO xyoff VALUES(2,3,693,217);
+INSERT INTO xyoff VALUES(2,3,746,317);
+INSERT INTO xyoff VALUES(2,3,805,369);
+INSERT INTO xyoff VALUES(2,3,336,585);
+INSERT INTO xyoff VALUES(2,4,890,255);
+INSERT INTO xyoff VALUES(2,4,556,565);
+INSERT INTO xyoff VALUES(2,4,865,555);
+INSERT INTO xyoff VALUES(2,4,230,293);
+INSERT INTO xyoff VALUES(2,4,247,251);
+INSERT INTO xyoff VALUES(2,4,730,563);
+INSERT INTO xyoff VALUES(2,4,318,282);
+INSERT INTO xyoff VALUES(2,4,220,431);
+INSERT INTO xyoff VALUES(2,4,828,336);
+INSERT INTO xyoff VALUES(2,4,278,525);
+INSERT INTO xyoff VALUES(3,1,324,656);
+INSERT INTO xyoff VALUES(3,1,625,362);
+INSERT INTO xyoff VALUES(3,1,155,570);
+INSERT INTO xyoff VALUES(3,1,267,433);
+INSERT INTO xyoff VALUES(3,1,599,121);
+INSERT INTO xyoff VALUES(3,1,873,498);
+INSERT INTO xyoff VALUES(3,1,789,520);
+INSERT INTO xyoff VALUES(3,1,656,378);
+INSERT INTO xyoff VALUES(3,1,831,601);
+INSERT INTO xyoff VALUES(3,1,256,471);
+INSERT INTO xyoff VALUES(3,2,332,258);
+INSERT INTO xyoff VALUES(3,2,305,463);
+INSERT INTO xyoff VALUES(3,2,796,341);
+INSERT INTO xyoff VALUES(3,2,830,229);
+INSERT INTO xyoff VALUES(3,2,413,271);
+INSERT INTO xyoff VALUES(3,2,269,140);
+INSERT INTO xyoff VALUES(3,2,628,441);
+INSERT INTO xyoff VALUES(3,2,747,643);
+INSERT INTO xyoff VALUES(3,2,584,435);
+INSERT INTO xyoff VALUES(3,2,784,314);
+INSERT INTO xyoff VALUES(3,3,722,233);
+INSERT INTO xyoff VALUES(3,3,815,421);
+INSERT INTO xyoff VALUES(3,3,401,267);
+INSERT INTO xyoff VALUES(3,3,451,650);
+INSERT INTO xyoff VALUES(3,3,329,485);
+INSERT INTO xyoff VALUES(3,3,878,370);
+INSERT INTO xyoff VALUES(3,3,162,616);
+INSERT INTO xyoff VALUES(3,3,844,183);
+INSERT INTO xyoff VALUES(3,3,161,216);
+INSERT INTO xyoff VALUES(3,3,176,676);
+INSERT INTO xyoff VALUES(3,4,780,128);
+INSERT INTO xyoff VALUES(3,4,566,121);
+INSERT INTO xyoff VALUES(3,4,646,120);
+INSERT INTO xyoff VALUES(3,4,223,557);
+INSERT INTO xyoff VALUES(3,4,251,117);
+INSERT INTO xyoff VALUES(3,4,139,209);
+INSERT INTO xyoff VALUES(3,4,813,597);
+INSERT INTO xyoff VALUES(3,4,454,538);
+INSERT INTO xyoff VALUES(3,4,616,198);
+INSERT INTO xyoff VALUES(3,4,210,159);
+INSERT INTO xyoff VALUES(4,1,208,415);
+INSERT INTO xyoff VALUES(4,1,326,665);
+INSERT INTO xyoff VALUES(4,1,612,133);
+INSERT INTO xyoff VALUES(4,1,537,513);
+INSERT INTO xyoff VALUES(4,1,638,438);
+INSERT INTO xyoff VALUES(4,1,808,269);
+INSERT INTO xyoff VALUES(4,1,552,121);
+INSERT INTO xyoff VALUES(4,1,100,189);
+INSERT INTO xyoff VALUES(4,1,643,664);
+INSERT INTO xyoff VALUES(4,1,726,378);
+INSERT INTO xyoff VALUES(4,2,478,409);
+INSERT INTO xyoff VALUES(4,2,497,507);
+INSERT INTO xyoff VALUES(4,2,233,148);
+INSERT INTO xyoff VALUES(4,2,587,237);
+INSERT INTO xyoff VALUES(4,2,604,166);
+INSERT INTO xyoff VALUES(4,2,165,455);
+INSERT INTO xyoff VALUES(4,2,320,258);
+INSERT INTO xyoff VALUES(4,2,353,496);
+INSERT INTO xyoff VALUES(4,2,347,495);
+INSERT INTO xyoff VALUES(4,2,166,622);
+INSERT INTO xyoff VALUES(4,3,461,332);
+INSERT INTO xyoff VALUES(4,3,685,278);
+INSERT INTO xyoff VALUES(4,3,427,594);
+INSERT INTO xyoff VALUES(4,3,467,346);
+INSERT INTO xyoff VALUES(4,3,125,548);
+INSERT INTO xyoff VALUES(4,3,597,680);
+INSERT INTO xyoff VALUES(4,3,820,445);
+INSERT INTO xyoff VALUES(4,3,144,330);
+INSERT INTO xyoff VALUES(4,3,557,434);
+INSERT INTO xyoff VALUES(4,3,254,315);
+INSERT INTO xyoff VALUES(4,4,157,339);
+INSERT INTO xyoff VALUES(4,4,249,220);
+INSERT INTO xyoff VALUES(4,4,391,323);
+INSERT INTO xyoff VALUES(4,4,589,429);
+INSERT INTO xyoff VALUES(4,4,859,592);
+INSERT INTO xyoff VALUES(4,4,337,680);
+INSERT INTO xyoff VALUES(4,4,410,288);
+INSERT INTO xyoff VALUES(4,4,636,596);
+INSERT INTO xyoff VALUES(4,4,734,433);
+INSERT INTO xyoff VALUES(4,4,559,549);
+INSERT INTO xyoff VALUES(5,1,549,607);
+INSERT INTO xyoff VALUES(5,1,584,498);
+INSERT INTO xyoff VALUES(5,1,699,116);
+INSERT INTO xyoff VALUES(5,1,525,524);
+INSERT INTO xyoff VALUES(5,1,304,667);
+INSERT INTO xyoff VALUES(5,1,302,232);
+INSERT INTO xyoff VALUES(5,1,403,149);
+INSERT INTO xyoff VALUES(5,1,824,403);
+INSERT INTO xyoff VALUES(5,1,697,203);
+INSERT INTO xyoff VALUES(5,1,293,689);
+INSERT INTO xyoff VALUES(5,2,199,275);
+INSERT INTO xyoff VALUES(5,2,395,393);
+INSERT INTO xyoff VALUES(5,2,657,642);
+INSERT INTO xyoff VALUES(5,2,200,655);
+INSERT INTO xyoff VALUES(5,2,882,234);
+INSERT INTO xyoff VALUES(5,2,483,565);
+INSERT INTO xyoff VALUES(5,2,755,640);
+INSERT INTO xyoff VALUES(5,2,810,305);
+INSERT INTO xyoff VALUES(5,2,731,655);
+INSERT INTO xyoff VALUES(5,2,466,690);
+INSERT INTO xyoff VALUES(5,3,563,584);
+INSERT INTO xyoff VALUES(5,3,491,117);
+INSERT INTO xyoff VALUES(5,3,779,292);
+INSERT INTO xyoff VALUES(5,3,375,637);
+INSERT INTO xyoff VALUES(5,3,253,553);
+INSERT INTO xyoff VALUES(5,3,797,514);
+INSERT INTO xyoff VALUES(5,3,229,480);
+INSERT INTO xyoff VALUES(5,3,257,194);
+INSERT INTO xyoff VALUES(5,3,449,555);
+INSERT INTO xyoff VALUES(5,3,849,630);
+INSERT INTO xyoff VALUES(5,4,329,286);
+INSERT INTO xyoff VALUES(5,4,640,197);
+INSERT INTO xyoff VALUES(5,4,104,150);
+INSERT INTO xyoff VALUES(5,4,438,272);
+INSERT INTO xyoff VALUES(5,4,773,226);
+INSERT INTO xyoff VALUES(5,4,441,650);
+INSERT INTO xyoff VALUES(5,4,242,340);
+INSERT INTO xyoff VALUES(5,4,301,435);
+INSERT INTO xyoff VALUES(5,4,171,397);
+INSERT INTO xyoff VALUES(5,4,541,619);
+INSERT INTO xyoff VALUES(6,1,651,301);
+INSERT INTO xyoff VALUES(6,1,637,137);
+INSERT INTO xyoff VALUES(6,1,765,643);
+INSERT INTO xyoff VALUES(6,1,173,296);
+INSERT INTO xyoff VALUES(6,1,263,192);
+INSERT INTO xyoff VALUES(6,1,791,302);
+INSERT INTO xyoff VALUES(6,1,860,601);
+INSERT INTO xyoff VALUES(6,1,780,445);
+INSERT INTO xyoff VALUES(6,1,462,214);
+INSERT INTO xyoff VALUES(6,1,802,207);
+INSERT INTO xyoff VALUES(6,2,811,685);
+INSERT INTO xyoff VALUES(6,2,533,531);
+INSERT INTO xyoff VALUES(6,2,390,614);
+INSERT INTO xyoff VALUES(6,2,260,580);
+INSERT INTO xyoff VALUES(6,2,116,377);
+INSERT INTO xyoff VALUES(6,2,860,458);
+INSERT INTO xyoff VALUES(6,2,438,590);
+INSERT INTO xyoff VALUES(6,2,604,562);
+INSERT INTO xyoff VALUES(6,2,241,242);
+INSERT INTO xyoff VALUES(6,2,667,298);
+INSERT INTO xyoff VALUES(6,3,787,698);
+INSERT INTO xyoff VALUES(6,3,868,521);
+INSERT INTO xyoff VALUES(6,3,412,587);
+INSERT INTO xyoff VALUES(6,3,640,131);
+INSERT INTO xyoff VALUES(6,3,748,410);
+INSERT INTO xyoff VALUES(6,3,257,244);
+INSERT INTO xyoff VALUES(6,3,411,195);
+INSERT INTO xyoff VALUES(6,3,464,356);
+INSERT INTO xyoff VALUES(6,3,157,339);
+INSERT INTO xyoff VALUES(6,3,434,505);
+INSERT INTO xyoff VALUES(6,4,480,671);
+INSERT INTO xyoff VALUES(6,4,519,228);
+INSERT INTO xyoff VALUES(6,4,404,513);
+INSERT INTO xyoff VALUES(6,4,120,538);
+INSERT INTO xyoff VALUES(6,4,403,663);
+INSERT INTO xyoff VALUES(6,4,477,677);
+INSERT INTO xyoff VALUES(6,4,690,154);
+INSERT INTO xyoff VALUES(6,4,606,498);
+INSERT INTO xyoff VALUES(6,4,430,665);
+INSERT INTO xyoff VALUES(6,4,499,273);
+INSERT INTO xyoff VALUES(7,1,118,526);
+INSERT INTO xyoff VALUES(7,1,817,522);
+INSERT INTO xyoff VALUES(7,1,388,638);
+INSERT INTO xyoff VALUES(7,1,181,265);
+INSERT INTO xyoff VALUES(7,1,442,332);
+INSERT INTO xyoff VALUES(7,1,475,282);
+INSERT INTO xyoff VALUES(7,1,722,633);
+INSERT INTO xyoff VALUES(7,1,104,394);
+INSERT INTO xyoff VALUES(7,1,631,262);
+INSERT INTO xyoff VALUES(7,1,372,392);
+INSERT INTO xyoff VALUES(7,2,600,413);
+INSERT INTO xyoff VALUES(7,2,386,223);
+INSERT INTO xyoff VALUES(7,2,839,174);
+INSERT INTO xyoff VALUES(7,2,293,410);
+INSERT INTO xyoff VALUES(7,2,281,391);
+INSERT INTO xyoff VALUES(7,2,859,387);
+INSERT INTO xyoff VALUES(7,2,478,347);
+INSERT INTO xyoff VALUES(7,2,646,690);
+INSERT INTO xyoff VALUES(7,2,713,234);
+INSERT INTO xyoff VALUES(7,2,199,588);
+INSERT INTO xyoff VALUES(7,3,389,256);
+INSERT INTO xyoff VALUES(7,3,349,542);
+INSERT INTO xyoff VALUES(7,3,363,345);
+INSERT INTO xyoff VALUES(7,3,751,302);
+INSERT INTO xyoff VALUES(7,3,423,386);
+INSERT INTO xyoff VALUES(7,3,267,444);
+INSERT INTO xyoff VALUES(7,3,243,182);
+INSERT INTO xyoff VALUES(7,3,453,658);
+INSERT INTO xyoff VALUES(7,3,126,345);
+INSERT INTO xyoff VALUES(7,3,120,472);
+INSERT INTO xyoff VALUES(7,4,359,654);
+INSERT INTO xyoff VALUES(7,4,339,516);
+INSERT INTO xyoff VALUES(7,4,710,452);
+INSERT INTO xyoff VALUES(7,4,810,560);
+INSERT INTO xyoff VALUES(7,4,644,692);
+INSERT INTO xyoff VALUES(7,4,826,327);
+INSERT INTO xyoff VALUES(7,4,465,462);
+INSERT INTO xyoff VALUES(7,4,310,456);
+INSERT INTO xyoff VALUES(7,4,577,613);
+INSERT INTO xyoff VALUES(7,4,502,555);
+INSERT INTO xyoff VALUES(8,1,601,620);
+INSERT INTO xyoff VALUES(8,1,372,683);
+INSERT INTO xyoff VALUES(8,1,758,399);
+INSERT INTO xyoff VALUES(8,1,485,552);
+INSERT INTO xyoff VALUES(8,1,159,563);
+INSERT INTO xyoff VALUES(8,1,536,303);
+INSERT INTO xyoff VALUES(8,1,122,263);
+INSERT INTO xyoff VALUES(8,1,836,435);
+INSERT INTO xyoff VALUES(8,1,544,146);
+INSERT INTO xyoff VALUES(8,1,270,277);
+INSERT INTO xyoff VALUES(8,2,849,281);
+INSERT INTO xyoff VALUES(8,2,563,242);
+INSERT INTO xyoff VALUES(8,2,704,463);
+INSERT INTO xyoff VALUES(8,2,102,165);
+INSERT INTO xyoff VALUES(8,2,797,524);
+INSERT INTO xyoff VALUES(8,2,612,426);
+INSERT INTO xyoff VALUES(8,2,345,372);
+INSERT INTO xyoff VALUES(8,2,820,376);
+INSERT INTO xyoff VALUES(8,2,789,156);
+INSERT INTO xyoff VALUES(8,2,321,466);
+INSERT INTO xyoff VALUES(8,3,150,332);
+INSERT INTO xyoff VALUES(8,3,136,152);
+INSERT INTO xyoff VALUES(8,3,468,528);
+INSERT INTO xyoff VALUES(8,3,409,192);
+INSERT INTO xyoff VALUES(8,3,820,216);
+INSERT INTO xyoff VALUES(8,3,847,249);
+INSERT INTO xyoff VALUES(8,3,801,267);
+INSERT INTO xyoff VALUES(8,3,181,670);
+INSERT INTO xyoff VALUES(8,3,398,563);
+INSERT INTO xyoff VALUES(8,3,439,576);
+INSERT INTO xyoff VALUES(8,4,123,309);
+INSERT INTO xyoff VALUES(8,4,190,496);
+INSERT INTO xyoff VALUES(8,4,571,531);
+INSERT INTO xyoff VALUES(8,4,290,255);
+INSERT INTO xyoff VALUES(8,4,244,412);
+INSERT INTO xyoff VALUES(8,4,264,596);
+INSERT INTO xyoff VALUES(8,4,253,420);
+INSERT INTO xyoff VALUES(8,4,847,536);
+INSERT INTO xyoff VALUES(8,4,120,288);
+INSERT INTO xyoff VALUES(8,4,331,639);
+
+/* Create the geopoly object from test data above */
+CREATE VIRTUAL TABLE geo1 USING geopoly(type,clr);
+INSERT INTO geo1(_shape,type,clr)
+ SELECT geopoly_xform(jshape,A,B,-B,A,xoff,yoff), basis.name, xform.clr
+ FROM basis, xform, xyoff
+ WHERE xyoff.id1=basis.rowid AND xyoff.id2=xform.rowid;
+
+
+/* Query polygon */
+CREATE TEMP TABLE querypoly(poly JSON, clr TEXT);
+INSERT INTO querypoly(clr, poly) VALUES
+ ('orange', '[[300,300],[400,350],[500,250],[480,500],[400,480],[300,550],[280,450],[320,400],[280,350],[300,300]]');
+
+/* Generate the HTML */
+.print '<html>'
+.print '<h1>Everything</h1>'
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',clr)
+ )
+ FROM geo1;
+SELECT geopoly_svg(poly,
+ printf('style="fill:%s;fill-opacity:0.5;"',clr)
+ )
+ FROM querypoly;
+.print '</svg>'
+
+.print '<h1>Overlap Query</h1>'
+.print '<pre>'
+.print 'SELECT *'
+.print ' FROM geo1, querypoly'
+.print ' WHERE geopoly_overlap(_shape, poly);'
+.print
+EXPLAIN QUERY PLAN
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE geopoly_overlap(_shape, poly);
+.print '</pre>'
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE geopoly_overlap(_shape, poly);
+SELECT geopoly_svg(poly,
+ printf('style="fill:%s;fill-opacity:0.5;"',clr)
+ )
+ FROM querypoly;
+.print '</svg>'
+
+.print '<h1>Overlap Query And Result Bounding Box</h1>'
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE geopoly_overlap(_shape, poly);
+SELECT geopoly_svg(geopoly_bbox(poly),
+ 'style="fill:none;stroke:black;stroke-width:3"'
+ )
+ FROM querypoly;
+SELECT geopoly_svg(poly,
+ printf('style="fill:%s;fill-opacity:0.5;"',clr)
+ )
+ FROM querypoly;
+SELECT geopoly_svg(geopoly_group_bbox(_shape),
+ 'style="fill:none;stroke:red;stroke-width:3"'
+ )
+ FROM geo1, querypoly
+ WHERE geopoly_overlap(_shape, poly);
+.print '</svg>'
+
+.print '<h1>Bounding-Box Overlap Query</h1>'
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ ),
+ geopoly_svg(geopoly_bbox(_shape),
+ 'style="fill:none;stroke:black;stroke-width:1"'
+ )
+ FROM geo1, querypoly
+ WHERE geopoly_overlap(geopoly_bbox(_shape), geopoly_bbox(poly));
+SELECT geopoly_svg(poly,
+ printf('style="fill:%s;fill-opacity:0.5;"',clr)
+ )
+ FROM querypoly;
+SELECT geopoly_svg(geopoly_bbox(poly),
+ 'style="fill:none;stroke:black;stroke-width:3"'
+ )
+ FROM querypoly;
+.print '</svg>'
+
+.print '<h1>Within Query</h1>'
+.print '<pre>'
+.print 'SELECT *'
+.print ' FROM geo1, querypoly'
+.print ' WHERE geopoly_within(_shape, poly);'
+.print
+EXPLAIN QUERY PLAN
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE geopoly_within(_shape, poly);
+.print '</pre>'
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE geopoly_within(_shape, poly);
+SELECT geopoly_svg(poly,
+ printf('style="fill:%s;fill-opacity:0.5;"',clr)
+ )
+ FROM querypoly;
+.print '</svg>'
+
+.print '<h1>Bounding-Box WITHIN Query</h1>'
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ ),
+ geopoly_svg(geopoly_bbox(_shape),
+ 'style="fill:none;stroke:black;stroke-width:1"'
+ )
+ FROM geo1, querypoly
+ WHERE geopoly_within(geopoly_bbox(_shape), geopoly_bbox(poly));
+SELECT geopoly_svg(poly,
+ printf('style="fill:%s;fill-opacity:0.5;"',clr)
+ )
+ FROM querypoly;
+SELECT geopoly_svg(geopoly_bbox(poly),
+ 'style="fill:none;stroke:black;stroke-width:3"'
+ )
+ FROM querypoly;
+.print '</svg>'
+
+.print '<h1>Not Overlap Query</h1>'
+.print '<pre>'
+.print 'SELECT *'
+.print ' FROM geo1, querypoly'
+.print ' WHERE NOT geopoly_overlap(_shape, poly);'
+.print
+EXPLAIN QUERY PLAN
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE NOT geopoly_overlap(_shape, poly);
+.print '</pre>'
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE NOT geopoly_overlap(_shape, poly);
+SELECT geopoly_svg(poly,
+ printf('style="fill:%s;fill-opacity:0.5;"',clr)
+ )
+ FROM querypoly;
+.print '</svg>'
+
+.print '<h1>Not Within Query</h1>'
+.print '<pre>'
+.print 'SELECT *'
+.print ' FROM geo1, querypoly'
+.print ' WHERE NOT geopoly_within(_shape, poly);'
+.print
+EXPLAIN QUERY PLAN
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE NOT geopoly_within(_shape, poly);
+.print '</pre>'
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1, querypoly
+ WHERE NOT geopoly_within(_shape, poly);
+SELECT geopoly_svg(poly,
+ printf('style="fill:%s;fill-opacity:0.5;"',clr)
+ )
+ FROM querypoly;
+.print '</svg>'
+
+.print '<h1>Color-Change For Overlapping Elements</h1>'
+BEGIN;
+UPDATE geo1
+ SET clr=CASE WHEN rowid IN (SELECT geo1.rowid FROM geo1, querypoly
+ WHERE geopoly_overlap(_shape,poly))
+ THEN 'red' ELSE 'blue' END;
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1;
+SELECT geopoly_svg(poly,'style="fill:none;stroke:black;stroke-width:2"')
+ FROM querypoly;
+ROLLBACK;
+.print '</svg>'
+
+.print '<h1>Color-Change And Move Overlapping Elements</h1>'
+BEGIN;
+UPDATE geo1
+ SET clr=CASE WHEN rowid IN (SELECT geo1.rowid FROM geo1, querypoly
+ WHERE geopoly_overlap(_shape,poly))
+ THEN 'red' ELSE '#76ccff' END;
+UPDATE geo1
+ SET _shape=geopoly_xform(_shape,1,0,0,1,300,0)
+ WHERE geopoly_overlap(_shape,(SELECT poly FROM querypoly));
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1;
+SELECT geopoly_svg(poly,'style="fill:none;stroke:black;stroke-width:2"')
+ FROM querypoly;
+--ROLLBACK;
+.print '</svg>'
+
+
+.print '<h1>Overlap With Translated Query Polygon</h1>'
+UPDATE querypoly SET poly=geopoly_xform(poly,1,0,0,1,300,0);
+.print '<svg width="1000" height="800" style="border:1px solid black">'
+SELECT geopoly_svg(_shape,
+ printf('style="fill:none;stroke:%s;stroke-width:1"',geo1.clr)
+ )
+ FROM geo1
+ WHERE geopoly_overlap(_shape,(SELECT poly FROM querypoly));
+SELECT geopoly_svg(poly,'style="fill:none;stroke:black;stroke-width:2"')
+ FROM querypoly;
+ROLLBACK;
+.print '</svg>'
+
+.print '<h1>Regular Polygons</h1>'
+.print '<svg width="1000" height="200" style="border:1px solid black">'
+SELECT geopoly_svg(geopoly_regular(100,100,40,3),'style="fill:none;stroke:red;stroke-width:1"');
+SELECT geopoly_svg(geopoly_regular(200,100,40,4),'style="fill:none;stroke:orange;stroke-width:1"');
+SELECT geopoly_svg(geopoly_regular(300,100,40,5),'style="fill:none;stroke:green;stroke-width:1"');
+SELECT geopoly_svg(geopoly_regular(400,100,40,6),'style="fill:none;stroke:blue;stroke-width:1"');
+SELECT geopoly_svg(geopoly_regular(500,100,40,7),'style="fill:none;stroke:purple;stroke-width:1"');
+SELECT geopoly_svg(geopoly_regular(600,100,40,8),'style="fill:none;stroke:red;stroke-width:1"');
+SELECT geopoly_svg(geopoly_regular(700,100,40,10),'style="fill:none;stroke:orange;stroke-width:1"');
+SELECT geopoly_svg(geopoly_regular(800,100,40,20),'style="fill:none;stroke:green;stroke-width:1"');
+SELECT geopoly_svg(geopoly_regular(900,100,40,30),'style="fill:none;stroke:blue;stroke-width:1"');
+.print '</svg>'
+
+.print '</html>'