Add new file rtreeA.test, to test that the r-tree extension doesn't crash if it encounters a corrupt or inconsistent database.
FossilOrigin-Name: 68a305fd5ac917317fee2ef6670ac389a120e502
This commit is contained in:
parent
90b1b3a346
commit
bd188afd4c
@ -13,6 +13,45 @@
|
||||
** 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
|
||||
|
207
ext/rtree/rtreeA.test
Normal file
207
ext/rtree/rtreeA.test
Normal file
@ -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
|
13
manifest
13
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
|
||||
|
@ -1 +1 @@
|
||||
14e8659e576258b64d67cb3f1222f173089d5127
|
||||
68a305fd5ac917317fee2ef6670ac389a120e502
|
Loading…
Reference in New Issue
Block a user