]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add new file rtreeA.test, to test that the r-tree extension doesn't crash if it encou...
authordan <dan@noemail.net>
Wed, 22 Sep 2010 14:19:53 +0000 (14:19 +0000)
committerdan <dan@noemail.net>
Wed, 22 Sep 2010 14:19:53 +0000 (14:19 +0000)
FossilOrigin-Name: 68a305fd5ac917317fee2ef6670ac389a120e502

ext/rtree/rtree.c
ext/rtree/rtreeA.test [new file with mode: 0644]
manifest
manifest.uuid

index 444e23547f9f0e6c901a519f443f61390c7fa6b7..e860e2db9dbb0a53d55188eac266f7006003eb84 100644 (file)
 ** 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)
 
 /*
@@ -150,6 +189,15 @@ struct 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.
 */
@@ -199,21 +247,6 @@ struct RtreeConstraint {
 
 /* 
 ** 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 */
@@ -370,7 +403,6 @@ static int nodeHash(i64 iNode){
 */
 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;
 }
@@ -446,41 +478,61 @@ nodeAcquire(
     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;
@@ -534,8 +586,7 @@ nodeInsertCell(
   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);
@@ -2576,9 +2627,6 @@ static int rtreeUpdate(
   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
diff --git a/ext/rtree/rtreeA.test b/ext/rtree/rtreeA.test
new file mode 100644 (file)
index 0000000..936b8fc
--- /dev/null
@@ -0,0 +1,207 @@
+# 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
index d43de1fd269e9305b38015b9f8be53e12d4e8034..209eb10b07aa1840ef65ebbbfe20fd4667516ffb 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-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
@@ -79,7 +79,7 @@ F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
 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
@@ -90,6 +90,7 @@ F ext/rtree/rtree6.test 1ebe0d632a7501cc80ba5a225f028fd4f0fdda08
 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
@@ -862,7 +863,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 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
index 78db6c19a7b56a799f5b27b0d5623eacad6830ba..378d2937d68e835d39e1d7ab271f000bebde508d 100644 (file)
@@ -1 +1 @@
-14e8659e576258b64d67cb3f1222f173089d5127
\ No newline at end of file
+68a305fd5ac917317fee2ef6670ac389a120e502
\ No newline at end of file