diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:28:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:28:19 +0000 |
commit | 18657a960e125336f704ea058e25c27bd3900dcb (patch) | |
tree | 17b438b680ed45a996d7b59951e6aa34023783f2 /ext/rtree | |
parent | Initial commit. (diff) | |
download | sqlite3-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 '')
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*)ⅈ + 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>' |