From: drh Date: Thu, 17 Apr 2014 14:52:20 +0000 (+0000) Subject: Test cases and bug fixes for the sqlite3_rtree_query_callback() X-Git-Tag: version-3.13.0~148^2~145^2~8 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b640595f97b7f55e619fd895becb7bf7ad5d6cf3;p=thirdparty%2Fsqlite.git Test cases and bug fixes for the sqlite3_rtree_query_callback() mechanism. FossilOrigin-Name: 1ccaaed6b516ec2ce953c1b31025a82ba76d00e7 --- diff --git a/ext/rtree/rtree.c b/ext/rtree/rtree.c index 3c9a8daba9..53414af7ee 100644 --- a/ext/rtree/rtree.c +++ b/ext/rtree/rtree.c @@ -162,9 +162,11 @@ struct Rtree { #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 /* @@ -270,7 +272,7 @@ struct RtreeConstraint { int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*); int (*xQueryFunc)(sqlite3_rtree_query_info*); } u; - sqlite3_rtree_query_info *pGeom; /* xGeom and xQueryFunc argument */ + sqlite3_rtree_query_info *pInfo; /* xGeom and xQueryFunc argument */ }; /* Possible values for RtreeConstraint.op */ @@ -863,10 +865,10 @@ static void freeCursorConstraints(RtreeCursor *pCsr){ if( pCsr->aConstraint ){ int i; /* Used to iterate through constraint array */ for(i=0; inConstraint; i++){ - sqlite3_rtree_query_info *pGeom = pCsr->aConstraint[i].pGeom; - if( pGeom ){ - if( pGeom->xDelUser ) pGeom->xDelUser(pGeom->pUser); - sqlite3_free(pGeom); + 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); @@ -928,8 +930,8 @@ static int rtreeCallbackConstraint( int *peWithin /* OUT: visibility of the cell */ ){ int i; /* Loop counter */ - sqlite3_rtree_query_info *pGeom = pConstraint->pGeom; /* Callback info */ - int nCoord = pGeom->nCoord; /* No. of coordinates */ + sqlite3_rtree_query_info *pInfo = pConstraint->pInfo; /* Callback info */ + int nCoord = pInfo->nCoord; /* No. of coordinates */ int rc; /* Callback return code */ sqlite3_rtree_dbl aCoord[RTREE_MAX_DIMENSIONS*2]; /* Decoded coordinates */ @@ -941,17 +943,20 @@ static int rtreeCallbackConstraint( RTREE_DECODE_COORD(eInt, pCellData, aCoord[i]); } if( pConstraint->op==RTREE_MATCH ){ - rc = pConstraint->u.xGeom((sqlite3_rtree_geometry*)pGeom, + rc = pConstraint->u.xGeom((sqlite3_rtree_geometry*)pInfo, nCoord, aCoord, &i); if( i==0 ) *peWithin = NOT_WITHIN; + *prScore = RTREE_ZERO; }else{ - pGeom->aCoord = aCoord; - pGeom->iLevel = pSearch->iLevel; - pGeom->rScore = pGeom->rParentScore = pSearch->rScore; - pGeom->eWithin = pGeom->eParentWithin = pSearch->eWithin; - rc = pConstraint->u.xQueryFunc(pGeom); - if( pGeom->eWithin<*peWithin ) *peWithin = pGeom->eWithin; - if( pGeom->rScore<*prScore ) *prScore = pGeom->rScore; + pInfo->aCoord = aCoord; + pInfo->iLevel = pSearch->iLevel; + 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 || *prScorerScore; + } } return rc; } @@ -1065,6 +1070,12 @@ static int nodeParentIndex(Rtree *pRtree, RtreeNode *pNode, int *piIndex){ /* ** 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, @@ -1289,7 +1300,7 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){ nCell = NCELL(pNode); assert( nCell<200 ); while( p->iCellzData + (4+pRtree->nBytesPerCell*p->iCell); eWithin = FULLY_WITHIN; for(ii=0; iieWithin = eWithin; @@ -1429,9 +1441,10 @@ static int findLeafNode( ** operator. */ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ - RtreeMatchArg *p; - sqlite3_rtree_query_info *pGeom; - int nBlob; + RtreeMatchArg *pBlob; /* BLOB returned by geometry function */ + sqlite3_rtree_query_info *pInfo; /* Callback information */ + int nBlob; /* Size of the geometry function blob */ + int nExpected; /* Expected size of the BLOB */ /* Check that value is actually a blob. */ if( sqlite3_value_type(pValue)!=SQLITE_BLOB ) return SQLITE_ERROR; @@ -1444,24 +1457,29 @@ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ return SQLITE_ERROR; } - pGeom = (sqlite3_rtree_query_info*)sqlite3_malloc( sizeof(*pGeom)+nBlob ); - if( !pGeom ) return SQLITE_NOMEM; - memset(pGeom, 0, sizeof(*pGeom)); - p = (RtreeMatchArg*)&pGeom[1]; + pInfo = (sqlite3_rtree_query_info*)sqlite3_malloc( sizeof(*pInfo)+nBlob ); + if( !pInfo ) return SQLITE_NOMEM; + memset(pInfo, 0, sizeof(*pInfo)); + pBlob = (RtreeMatchArg*)&pInfo[1]; - memcpy(p, sqlite3_value_blob(pValue), nBlob); - if( p->magic!=RTREE_GEOMETRY_MAGIC - || nBlob!=(int)(sizeof(RtreeMatchArg) + (p->nParam-1)*sizeof(RtreeDValue)) - ){ - sqlite3_free(pGeom); + memcpy(pBlob, sqlite3_value_blob(pValue), nBlob); + nExpected = (int)(sizeof(RtreeMatchArg) + + (pBlob->nParam-1)*sizeof(RtreeDValue)); + if( pBlob->magic!=RTREE_GEOMETRY_MAGIC || nBlob!=nExpected ){ + sqlite3_free(pInfo); return SQLITE_ERROR; } - pGeom->pContext = p->cb.pContext; - pGeom->nParam = p->nParam; - pGeom->aParam = p->aParam; + pInfo->pContext = pBlob->cb.pContext; + pInfo->nParam = pBlob->nParam; + pInfo->aParam = pBlob->aParam; - pCons->u.xGeom = p->cb.xGeom; - pCons->pGeom = pGeom; + 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; } @@ -1493,7 +1511,7 @@ static int rtreeFilter( i64 iNode = 0; rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); if( rc==SQLITE_OK && pLeaf!=0 ){ - p = rtreeSearchPointNew(pCsr, 0.0, 0); + p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0); assert( p!=0 ); /* Always returns pCsr->sPoint */ pCsr->aNode[0] = pLeaf; p->id = iNode; @@ -1521,7 +1539,7 @@ static int rtreeFilter( RtreeConstraint *p = &pCsr->aConstraint[ii]; p->op = idxStr[ii*2]; p->iCoord = idxStr[ii*2+1]-'0'; - if( p->op==RTREE_MATCH ){ + 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. @@ -1530,7 +1548,7 @@ static int rtreeFilter( if( rc!=SQLITE_OK ){ break; } - p->pGeom->nCoord = pRtree->nDim*2; + p->pInfo->nCoord = pRtree->nDim*2; }else{ #ifdef SQLITE_RTREE_INT_ONLY p->u.rValue = sqlite3_value_int64(argv[ii]); @@ -1546,7 +1564,8 @@ static int rtreeFilter( rc = nodeAcquire(pRtree, 1, 0, &pRoot); } if( rc==SQLITE_OK ){ - RtreeSearchPoint *pNew = rtreeSearchPointNew(pCsr, 0.0, pRtree->iDepth+1); + RtreeSearchPoint *pNew; + pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, pRtree->iDepth+1); if( pNew==0 ) return SQLITE_NOMEM; pNew->id = 1; pNew->iCell = 0; @@ -1759,7 +1778,7 @@ static RtreeDValue cellOverlap( int nCell ){ int ii; - RtreeDValue overlap = 0.0; + RtreeDValue overlap = RTREE_ZERO; for(ii=0; iinDim+1)*(sizeof(int*)+nCell*sizeof(int)); @@ -2071,9 +2090,9 @@ static int splitNodeStartree( } for(ii=0; iinDim; ii++){ - RtreeDValue margin = 0.0; - RtreeDValue fBestOverlap = 0.0; - RtreeDValue fBestArea = 0.0; + RtreeDValue margin = RTREE_ZERO; + RtreeDValue fBestOverlap = RTREE_ZERO; + RtreeDValue fBestArea = RTREE_ZERO; int iBestLeft = 0; int nLeft; @@ -2492,7 +2511,7 @@ static int Reinsert( } for(ii=0; iinDim; iDim++){ RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) - DCOORD(aCell[ii].aCoord[iDim*2])); @@ -3299,8 +3318,8 @@ int sqlite3RtreeInit(sqlite3 *db){ ** the corresponding SQL function is deleted. */ static void rtreeFreeCallback(void *p){ - RtreeGeomCallback *pGeom = (RtreeGeomCallback*)p; - if( pGeom->xDestructor ) pGeom->xDestructor(pGeom->pContext); + RtreeGeomCallback *pInfo = (RtreeGeomCallback*)p; + if( pInfo->xDestructor ) pInfo->xDestructor(pInfo->pContext); sqlite3_free(p); } diff --git a/ext/rtree/rtreeE.test b/ext/rtree/rtreeE.test new file mode 100644 index 0000000000..14f5c332ca --- /dev/null +++ b/ext/rtree/rtreeE.test @@ -0,0 +1,72 @@ +# 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 $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.1 { + PRAGMA page_size=512; + CREATE VIRTUAL TABLE rt2 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 rt2 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 rt2 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 rt2 SELECT 200+x+5*y, x*7, x*7+15, y*7+200, y*7+215 FROM x, y; +} {} + +if 0 { +# Queries against each of the three clusters */ +do_execsql_test rtreeE-1.1 { + SELECT id FROM rt2 WHERE id MATCH Qcircle(0.0, 0.0, 50.0) 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 rt2 WHERE id MATCH Qcircle(100.0, 0.0, 50.0) 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 rt2 WHERE id MATCH Qcircle(0.0, 200.0, 50.0) 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 rt2 WHERE id MATCH Qcircle(0,0,1000) AND id%100==0 +} {200 100 0} + +finish_test diff --git a/manifest b/manifest index d10804afcb..63a2d6b9ce 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Refactor\sthe\sconstraint\schecking\slogic\sin\sRTree.\s\sThe\snew-style\sconstraint\ncallbacks\screated\sby\ssqlite3_rtree_query_callback()\sare\snow\shooked\sup\sfrom\nend\sto\send,\sthough\sstill\suntested. -D 2014-04-17T13:15:33.281 +C Test\scases\sand\sbug\sfixes\sfor\sthe\ssqlite3_rtree_query_callback()\nmechanism. +D 2014-04-17T14:52:20.025 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in e4ee6d36cdf6136aee0158675a3b24dd3bf31a5a F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -120,7 +120,7 @@ F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95 F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 -F ext/rtree/rtree.c 117aaebed87a7c1e5d3e03afbad83c3700aa5ab8 +F ext/rtree/rtree.c 6a47918e44697dd32f5bba8a79d3490e56bd76c9 F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e F ext/rtree/rtree1.test cf679265ecafff494a768ac9c2f43a70915a6290 F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba @@ -135,6 +135,7 @@ F ext/rtree/rtreeA.test ace05e729a36e342d40cf94e9efc7b4723d9dcdf F ext/rtree/rtreeB.test c85f9ce78766c4e68b8b89fbf2979ee9cfa82b4e F ext/rtree/rtreeC.test df158dcc81f1a43ce7eef361af03c48ec91f1e06 F ext/rtree/rtreeD.test 636630357638f5983701550b37f0f5867130d2ca +F ext/rtree/rtreeE.test c8c951df54fd556d30eb621ecc2acd8771970a5e F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195 F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea F ext/rtree/sqlite3rtree.h 488cf8834d2012b913d33683157d3cf5f1327a69 @@ -274,7 +275,7 @@ F src/test_osinst.c 90a845c8183013d80eccb1f29e8805608516edba F src/test_pcache.c a5cd24730cb43c5b18629043314548c9169abb00 F src/test_quota.c 30c64f0ef84734f2231a686df41ed882b0c59bc0 F src/test_quota.h 8761e463b25e75ebc078bd67d70e39b9c817a0cb -F src/test_rtree.c cd35d54c0b847c0c373d66f91616c49697ab4edf +F src/test_rtree.c 636d2a5bc9ded2fa1df4e7d4c575eb0d3f13b334 F src/test_schema.c cd12a2223c3a394f4d07bb93bdf6d344c5c121b6 F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe F src/test_sqllog.c c1c1bbedbcaf82b93d83e4f9dd990e62476a680e @@ -1175,7 +1176,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh d1a6de74685f360ab718efda6265994b99bbea01 F tool/win/sqlite.vsix 030f3eeaf2cb811a3692ab9c14d021a75ce41fff -P 5d20ff9ec837ad35bc44d6c25d13764b350e81dd -R 9ddd572c495f4e9fca0e8e969ae19d88 +P 32a13870175a1dd1d33af3572dde09ff607a04b6 +R 1f38906450e19fda5c27a4543b70f18c U drh -Z 43698a2882be63e69b530e3c48875378 +Z 26a5b86beaa061e155488cf6e80f8d30 diff --git a/manifest.uuid b/manifest.uuid index 5fbbb6cae9..07ccb10268 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -32a13870175a1dd1d33af3572dde09ff607a04b6 \ No newline at end of file +1ccaaed6b516ec2ce953c1b31025a82ba76d00e7 \ No newline at end of file diff --git a/src/test_rtree.c b/src/test_rtree.c index 3b4c205879..c1951a896b 100644 --- a/src/test_rtree.c +++ b/src/test_rtree.c @@ -35,6 +35,7 @@ struct Circle { double centerx; double centery; double radius; + double mxArea; }; /* @@ -58,7 +59,12 @@ static int circle_geom( double xmin, xmax; /* X dimensions of box being tested */ double ymin, ymax; /* X dimensions of box being tested */ - if( p->pUser==0 ){ + xmin = aCoord[0]; + xmax = aCoord[1]; + ymin = aCoord[2]; + ymax = aCoord[3]; + pCircle = (Circle *)p->pUser; + if( pCircle==0 ){ /* If pUser is still 0, then the parameter values have not been tested ** for correctness or stored into a Circle structure yet. Do this now. */ @@ -104,14 +110,9 @@ static int circle_geom( pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius; pCircle->aBox[1].ymin = pCircle->centery; pCircle->aBox[1].ymax = pCircle->centery; + pCircle->mxArea = (xmax - xmin)*(ymax - ymin) + 1.0; } - pCircle = (Circle *)p->pUser; - xmin = aCoord[0]; - xmax = aCoord[1]; - ymin = aCoord[2]; - ymax = aCoord[3]; - /* Check if any of the 4 corners of the bounding-box being tested lie ** inside the circular region. If they do, then the bounding-box does ** intersect the region of interest. Set the output variable to true and @@ -161,7 +162,12 @@ static int circle_query_func(sqlite3_rtree_query_info *p){ double ymin, ymax; /* X dimensions of box being tested */ int nWithin = 0; /* Number of corners inside the circle */ - if( p->pUser==0 ){ + xmin = p->aCoord[0]; + xmax = p->aCoord[1]; + ymin = p->aCoord[2]; + ymax = p->aCoord[3]; + pCircle = (Circle *)p->pUser; + if( pCircle==0 ){ /* If pUser is still 0, then the parameter values have not been tested ** for correctness or stored into a Circle structure yet. Do this now. */ @@ -207,14 +213,9 @@ static int circle_query_func(sqlite3_rtree_query_info *p){ pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius; pCircle->aBox[1].ymin = pCircle->centery; pCircle->aBox[1].ymax = pCircle->centery; + pCircle->mxArea = 200.0*200.0; } - pCircle = (Circle *)p->pUser; - xmin = p->aCoord[0]; - xmax = p->aCoord[1]; - ymin = p->aCoord[2]; - ymax = p->aCoord[3]; - /* Check if any of the 4 corners of the bounding-box being tested lie ** inside the circular region. If they do, then the bounding-box does ** intersect the region of interest. Set the output variable to true and @@ -246,7 +247,12 @@ static int circle_query_func(sqlite3_rtree_query_info *p){ } } - p->rScore = p->iLevel; + if( p->iLevel==2 ){ + p->rScore = 1.0 - (xmax-xmin)*(ymax-ymin)/pCircle->mxArea; + if( p->rScore<0.01 ) p->rScore = 0.01; + }else{ + p->rScore = 0.0; + } if( nWithin==0 ){ p->eWithin = NOT_WITHIN; }else if( nWithin>=4 ){