** 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.
+**
+** 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)
/*
#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
+** 2^40 is greater than 2^64, an r-tree structure always has a depth of
+** 40 or less.
+*/
+#define RTREE_MAX_DEPTH 40
+
/*
** An rtree cursor object.
*/
/*
** An rtree structure node.
-**
-** Data format (RtreeNode.zData):
-**
-** 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.
*/
struct RtreeNode {
RtreeNode *pParent; /* Parent node */
*/
static RtreeNode *nodeHashLookup(Rtree *pRtree, i64 iNode){
RtreeNode *p;
- assert( iNode!=0 );
for(p=pRtree->aHash[nodeHash(iNode)]; p && p->iNode!=iNode; p=p->pNext);
return p;
}
return SQLITE_OK;
}
- pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode) + pRtree->iNodeSize);
- if( !pNode ){
- *ppNode = 0;
- return SQLITE_NOMEM;
- }
- pNode->pParent = pParent;
- pNode->zData = (u8 *)&pNode[1];
- pNode->nRef = 1;
- pNode->iNode = iNode;
- pNode->isDirty = 0;
- pNode->pNext = 0;
-
sqlite3_bind_int64(pRtree->pReadNode, 1, iNode);
rc = sqlite3_step(pRtree->pReadNode);
if( rc==SQLITE_ROW ){
const u8 *zBlob = sqlite3_column_blob(pRtree->pReadNode, 0);
- assert( sqlite3_column_bytes(pRtree->pReadNode, 0)==pRtree->iNodeSize );
- memcpy(pNode->zData, zBlob, pRtree->iNodeSize);
- nodeReference(pParent);
- }else{
- sqlite3_free(pNode);
- pNode = 0;
+ if( pRtree->iNodeSize==sqlite3_column_bytes(pRtree->pReadNode, 0) ){
+ pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode)+pRtree->iNodeSize);
+ if( !pNode ){
+ rc = SQLITE_NOMEM;
+ }else{
+ pNode->pParent = pParent;
+ pNode->zData = (u8 *)&pNode[1];
+ pNode->nRef = 1;
+ pNode->iNode = iNode;
+ pNode->isDirty = 0;
+ pNode->pNext = 0;
+ memcpy(pNode->zData, zBlob, pRtree->iNodeSize);
+ nodeReference(pParent);
+ }
+ }
}
-
- *ppNode = pNode;
rc = sqlite3_reset(pRtree->pReadNode);
+ /* 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( pNode && iNode==1 ){
pRtree->iDepth = readInt16(pNode->zData);
+ if( pRtree->iDepth>RTREE_MAX_DEPTH ){
+ rc = SQLITE_CORRUPT;
+ }
}
- if( pNode!=0 ){
- nodeHashInsert(pRtree, pNode);
- }else if( rc==SQLITE_OK ){
- rc = SQLITE_CORRUPT;
+ /* 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.
+ */
+ if( pNode && rc==SQLITE_OK ){
+ if( NCELL(pNode)>((pRtree->iNodeSize-4)/pRtree->nBytesPerCell) ){
+ rc = SQLITE_CORRUPT;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ if( pNode!=0 ){
+ nodeHashInsert(pRtree, pNode);
+ }else{
+ rc = SQLITE_CORRUPT;
+ }
+ *ppNode = pNode;
+ }else{
+ sqlite3_free(pNode);
+ *ppNode = 0;
}
return rc;
nMaxCell = (pRtree->iNodeSize-4)/pRtree->nBytesPerCell;
nCell = NCELL(pNode);
- assert(nCell<=nMaxCell);
-
+ assert( nCell<=nMaxCell );
if( nCell<nMaxCell ){
nodeOverwriteCell(pRtree, pNode, pCell, nCell);
writeInt16(&pNode->zData[2], nCell+1);
rtreeReference(pRtree);
assert(nData>=1);
-#if 0
- assert(hashIsEmpty(pRtree));
-#endif
/* If azData[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
--- /dev/null
+# 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 {
+ 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
+}
+
+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.2.0 { DROP TABLE t1_node } {}
+do_corruption_tests rtreeA-1.2 -error "SQL logic error or missing database" {
+ 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_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)"
+}
+
+#-------------------------------------------------------------------------
+# 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}
+breakpoint
+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"
+}
+
+finish_test
-C Add\snew\sfile\se_delete.test.
-D 2010-09-21T19:00:46
+C Add\snew\sfile\srtreeA.test,\sto\stest\sthat\sthe\sr-tree\sextension\sdoesn't\scrash\sif\sit\sencounters\sa\scorrupt\sor\sinconsistent\sdatabase.
+D 2010-09-22T14:19:53
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in c599a15d268b1db2aeadea19df2adc3bf2eb6bee
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F ext/icu/icu.c 850e9a36567bbcce6bd85a4b68243cad8e3c2de2
F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
-F ext/rtree/rtree.c a1a1202ac87be4e17969f1b26ac1bd26a804205f
+F ext/rtree/rtree.c 3733ff121035e83b598089d398293015c4102e6d
F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
F ext/rtree/rtree1.test dbd4250ac0ad367a262eb9676f7e3080b0368206
F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba
F ext/rtree/rtree7.test bcb647b42920b3b5d025846689147778485cc318
F ext/rtree/rtree8.test 9772e16da71e17e02bdebf0a5188590f289ab37d
F ext/rtree/rtree9.test df9843d1a9195249c8d3b4ea6aedda2d5c73e9c2
+F ext/rtree/rtreeA.test 2c6c7742957a9970a625762fee625444412b0a89
F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195
F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 528f71e29c5422af778dbae2c1dce3b0ee289750
-R d64850457ce5c89ffc8e7b8d8754b194
+P 14e8659e576258b64d67cb3f1222f173089d5127
+R f765e03b8b7edfbba31db768fa2c7c43
U dan
-Z 698ce76efbb1df140642fe36b4a58abb
+Z 161dee882fd7434c2ba579743fed4212
-14e8659e576258b64d67cb3f1222f173089d5127
\ No newline at end of file
+68a305fd5ac917317fee2ef6670ac389a120e502
\ No newline at end of file