1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Test cases and bug fixes for the sqlite3_rtree_query_callback()

mechanism.

FossilOrigin-Name: 1ccaaed6b516ec2ce953c1b31025a82ba76d00e7
This commit is contained in:
drh
2014-04-17 14:52:20 +00:00
parent 78b486ea12
commit b640595f97
5 changed files with 168 additions and 70 deletions

View File

@ -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; i<pCsr->nConstraint; 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 || *prScore<RTREE_ZERO ){
*prScore = pInfo->rScore;
}
}
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->iCell<nCell ){
sqlite3_rtree_dbl rScore = (sqlite3_rtree_dbl)0;
sqlite3_rtree_dbl rScore = (sqlite3_rtree_dbl)-1;
u8 *pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell);
eWithin = FULLY_WITHIN;
for(ii=0; ii<nConstraint; ii++){
@ -1319,6 +1330,7 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){
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 = 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; ii<nCell; ii++){
int jj;
RtreeDValue o = (RtreeDValue)1;
@ -1799,8 +1818,8 @@ static int ChooseLeaf(
int iCell;
sqlite3_int64 iBest = 0;
RtreeDValue fMinGrowth = 0.0;
RtreeDValue fMinArea = 0.0;
RtreeDValue fMinGrowth = RTREE_ZERO;
RtreeDValue fMinArea = RTREE_ZERO;
int nCell = NCELL(pNode);
RtreeCell cell;
@ -2050,7 +2069,7 @@ static int splitNodeStartree(
int iBestDim = 0;
int iBestSplit = 0;
RtreeDValue fBestMargin = 0.0;
RtreeDValue fBestMargin = RTREE_ZERO;
int nByte = (pRtree->nDim+1)*(sizeof(int*)+nCell*sizeof(int));
@ -2071,9 +2090,9 @@ static int splitNodeStartree(
}
for(ii=0; ii<pRtree->nDim; 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; ii<nCell; ii++){
aDistance[ii] = 0.0;
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]));
@ -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);
}

72
ext/rtree/rtreeE.test Normal file
View File

@ -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

View File

@ -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

View File

@ -1 +1 @@
32a13870175a1dd1d33af3572dde09ff607a04b6
1ccaaed6b516ec2ce953c1b31025a82ba76d00e7

View File

@ -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 ){