Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Test cases and bug fixes for the sqlite3_rtree_query_callback() mechanism. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | rtree-enhancements |
Files: | files | file ages | folders |
SHA1: |
1ccaaed6b516ec2ce953c1b31025a82b |
User & Date: | drh 2014-04-17 14:52:20.025 |
Context
2014-04-17
| ||
15:34 | More test cases with very long priority queues. (check-in: 71692aa97c user: drh tags: rtree-enhancements) | |
14:52 | Test cases and bug fixes for the sqlite3_rtree_query_callback() mechanism. (check-in: 1ccaaed6b5 user: drh tags: rtree-enhancements) | |
13:15 | Refactor the constraint checking logic in RTree. The new-style constraint callbacks created by sqlite3_rtree_query_callback() are now hooked up from end to end, though still untested. (check-in: 32a1387017 user: drh tags: rtree-enhancements) | |
Changes
Changes to ext/rtree/rtree.c.
︙ | ︙ | |||
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | ** If SQLITE_RTREE_INT_ONLY is defined, then this virtual table will ** only deal with integer coordinates. No floating point operations ** will be done. */ #ifdef SQLITE_RTREE_INT_ONLY typedef sqlite3_int64 RtreeDValue; /* High accuracy coordinate */ typedef int RtreeValue; /* Low accuracy coordinate */ #else typedef double RtreeDValue; /* High accuracy coordinate */ typedef float RtreeValue; /* Low accuracy coordinate */ #endif /* ** When doing a search of an r-tree, instances of the following structure ** record intermediate results from the tree walk. ** ** The id is always a node-id. For iLevel>=1 the id is the node-id of | > > | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | ** If SQLITE_RTREE_INT_ONLY is defined, then this virtual table will ** only deal with integer coordinates. No floating point operations ** will be done. */ #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 /* ** When doing a search of an r-tree, instances of the following structure ** record intermediate results from the tree walk. ** ** The id is always a node-id. For iLevel>=1 the id is the node-id of |
︙ | ︙ | |||
266 267 268 269 270 271 272 | int iCoord; /* Index of constrained coordinate */ int op; /* Constraining operation */ union { RtreeDValue rValue; /* Constraint value. */ int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*); int (*xQueryFunc)(sqlite3_rtree_query_info*); } u; | | | 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | int iCoord; /* Index of constrained coordinate */ int op; /* Constraining operation */ union { RtreeDValue rValue; /* Constraint value. */ int (*xGeom)(sqlite3_rtree_geometry*,int,RtreeDValue*,int*); int (*xQueryFunc)(sqlite3_rtree_query_info*); } u; sqlite3_rtree_query_info *pInfo; /* xGeom and xQueryFunc argument */ }; /* Possible values for RtreeConstraint.op */ #define RTREE_EQ 0x41 /* A */ #define RTREE_LE 0x42 /* B */ #define RTREE_LT 0x43 /* C */ #define RTREE_GE 0x44 /* D */ |
︙ | ︙ | |||
859 860 861 862 863 864 865 | /* ** Free the RtreeCursor.aConstraint[] array and its contents. */ static void freeCursorConstraints(RtreeCursor *pCsr){ if( pCsr->aConstraint ){ int i; /* Used to iterate through constraint array */ for(i=0; i<pCsr->nConstraint; i++){ | | | | | | 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 | /* ** Free the RtreeCursor.aConstraint[] array and its contents. */ 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 *pInfo = pCsr->aConstraint[i].pInfo; if( pInfo ){ if( pInfo->xDelUser ) pInfo->xDelUser(pInfo->pUser); sqlite3_free(pInfo); } } sqlite3_free(pCsr->aConstraint); pCsr->aConstraint = 0; } } |
︙ | ︙ | |||
924 925 926 927 928 929 930 | int eInt, /* True if RTree holding integer coordinates */ u8 *pCellData, /* Raw cell content */ RtreeSearchPoint *pSearch, /* Container of this cell */ sqlite3_rtree_dbl *prScore, /* OUT: score for the cell */ int *peWithin /* OUT: visibility of the cell */ ){ int i; /* Loop counter */ | | | | > | | | | | | | > > | 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 | int eInt, /* True if RTree holding integer coordinates */ u8 *pCellData, /* Raw cell content */ RtreeSearchPoint *pSearch, /* Container of this cell */ sqlite3_rtree_dbl *prScore, /* OUT: score for the cell */ int *peWithin /* OUT: visibility of the cell */ ){ int i; /* Loop counter */ 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 */ assert( pConstraint->op==RTREE_MATCH || pConstraint->op==RTREE_QUERY ); assert( nCoord==2 || nCoord==4 || nCoord==6 || nCoord==8 || nCoord==10 ); pCellData += 8; for(i=0; i<nCoord; i++, pCellData += 4){ RTREE_DECODE_COORD(eInt, pCellData, aCoord[i]); } if( pConstraint->op==RTREE_MATCH ){ rc = pConstraint->u.xGeom((sqlite3_rtree_geometry*)pInfo, nCoord, aCoord, &i); if( i==0 ) *peWithin = NOT_WITHIN; *prScore = RTREE_ZERO; }else{ 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; } /* ** Check the internal RTree node given by pCellData against constraint p. ** If this constraint cannot be satisfied by any child within the node, |
︙ | ︙ | |||
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 | *piIndex = -1; return SQLITE_OK; } /* ** Compare two search points. Return negative, zero, or positive if the first ** is less than, equal to, or greater than the second. */ static int rtreeSearchPointCompare( const RtreeSearchPoint *pA, const RtreeSearchPoint *pB ){ if( pA->rScore<pB->rScore ) return -1; if( pA->rScore>pB->rScore ) return +1; | > > > > > > | 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 | *piIndex = -1; return SQLITE_OK; } /* ** 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, const RtreeSearchPoint *pB ){ if( pA->rScore<pB->rScore ) return -1; if( pA->rScore>pB->rScore ) return +1; |
︙ | ︙ | |||
1285 1286 1287 1288 1289 1290 1291 | eInt = pRtree->eCoordType==RTREE_COORD_INT32; while( (p = rtreeSearchPointFirst(pCur))!=0 && p->iLevel>0 ){ pNode = rtreeNodeOfFirstSearchPoint(pCur, &rc); if( rc ) return rc; nCell = NCELL(pNode); assert( nCell<200 ); while( p->iCell<nCell ){ | | | 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 | eInt = pRtree->eCoordType==RTREE_COORD_INT32; while( (p = rtreeSearchPointFirst(pCur))!=0 && p->iLevel>0 ){ pNode = rtreeNodeOfFirstSearchPoint(pCur, &rc); if( rc ) return rc; nCell = NCELL(pNode); assert( nCell<200 ); while( p->iCell<nCell ){ 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++){ RtreeConstraint *pConstraint = pCur->aConstraint + ii; if( pConstraint->op>=RTREE_MATCH ){ rc = rtreeCallbackConstraint(pConstraint, eInt, pCellData, p, &rScore, &eWithin); |
︙ | ︙ | |||
1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 | x.id = p->id; x.iCell = p->iCell - 1; } if( p->iCell>=nCell ){ RTREE_QUEUE_TRACE(pCur, "POP-S:"); rtreeSearchPointPop(pCur); } p = rtreeSearchPointNew(pCur, rScore, x.iLevel); if( p==0 ) return SQLITE_NOMEM; p->eWithin = eWithin; p->id = x.id; p->iCell = x.iCell; RTREE_QUEUE_TRACE(pCur, "PUSH-S:"); break; | > | 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 | x.id = p->id; x.iCell = p->iCell - 1; } if( p->iCell>=nCell ){ 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; p->id = x.id; p->iCell = x.iCell; RTREE_QUEUE_TRACE(pCur, "PUSH-S:"); break; |
︙ | ︙ | |||
1425 1426 1427 1428 1429 1430 1431 | /* ** This function is called to configure the RtreeConstraint object passed ** as the second argument for a MATCH constraint. The value passed as the ** first argument to this function is the right-hand operand to the MATCH ** operator. */ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ | | | | > | | | | | | | | | | | | > | > > > > | | 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 | /* ** This function is called to configure the RtreeConstraint object passed ** as the second argument for a MATCH constraint. The value passed as the ** first argument to this function is the right-hand operand to the MATCH ** operator. */ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ 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; /* Check that the blob is roughly the right size. */ nBlob = sqlite3_value_bytes(pValue); if( nBlob<(int)sizeof(RtreeMatchArg) || ((nBlob-sizeof(RtreeMatchArg))%sizeof(RtreeDValue))!=0 ){ return SQLITE_ERROR; } 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(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; } pInfo->pContext = pBlob->cb.pContext; pInfo->nParam = pBlob->nParam; pInfo->aParam = pBlob->aParam; 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; } /* ** Rtree virtual table module xFilter method. */ static int rtreeFilter( |
︙ | ︙ | |||
1489 1490 1491 1492 1493 1494 1495 | /* Special case - lookup by rowid. */ RtreeNode *pLeaf; /* Leaf on which the required cell resides */ RtreeSearchPoint *p; /* Search point for the the leaf */ i64 iRowid = sqlite3_value_int64(argv[0]); i64 iNode = 0; rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); if( rc==SQLITE_OK && pLeaf!=0 ){ | | | 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 | /* Special case - lookup by rowid. */ RtreeNode *pLeaf; /* Leaf on which the required cell resides */ RtreeSearchPoint *p; /* Search point for the the leaf */ i64 iRowid = sqlite3_value_int64(argv[0]); i64 iNode = 0; rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); if( rc==SQLITE_OK && pLeaf!=0 ){ p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0); assert( p!=0 ); /* Always returns pCsr->sPoint */ pCsr->aNode[0] = pLeaf; p->id = iNode; p->eWithin = PARTLY_WITHIN; rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell); p->iCell = iCell; RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:"); |
︙ | ︙ | |||
1517 1518 1519 1520 1521 1522 1523 | memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc); assert( (idxStr==0 && argc==0) || (idxStr && (int)strlen(idxStr)==argc*2) ); for(ii=0; ii<argc; ii++){ RtreeConstraint *p = &pCsr->aConstraint[ii]; p->op = idxStr[ii*2]; p->iCoord = idxStr[ii*2+1]-'0'; | | | | > | 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 | memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc); assert( (idxStr==0 && argc==0) || (idxStr && (int)strlen(idxStr)==argc*2) ); for(ii=0; ii<argc; ii++){ RtreeConstraint *p = &pCsr->aConstraint[ii]; p->op = idxStr[ii*2]; p->iCoord = idxStr[ii*2+1]-'0'; 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. */ rc = deserializeGeometry(argv[ii], p); if( rc!=SQLITE_OK ){ break; } p->pInfo->nCoord = pRtree->nDim*2; }else{ #ifdef SQLITE_RTREE_INT_ONLY p->u.rValue = sqlite3_value_int64(argv[ii]); #else p->u.rValue = sqlite3_value_double(argv[ii]); #endif } } } } if( rc==SQLITE_OK ){ rc = nodeAcquire(pRtree, 1, 0, &pRoot); } if( rc==SQLITE_OK ){ RtreeSearchPoint *pNew; pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, pRtree->iDepth+1); if( pNew==0 ) return SQLITE_NOMEM; pNew->id = 1; pNew->iCell = 0; pNew->eWithin = PARTLY_WITHIN; assert( pCsr->bPoint==1 ); pCsr->aNode[0] = pRoot; RTREE_QUEUE_TRACE(pCsr, "PUSH-Fm:"); |
︙ | ︙ | |||
1755 1756 1757 1758 1759 1760 1761 | static RtreeDValue cellOverlap( Rtree *pRtree, RtreeCell *p, RtreeCell *aCell, int nCell ){ int ii; | | | 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 | static RtreeDValue cellOverlap( Rtree *pRtree, RtreeCell *p, RtreeCell *aCell, int nCell ){ int ii; RtreeDValue overlap = RTREE_ZERO; for(ii=0; ii<nCell; ii++){ int jj; RtreeDValue o = (RtreeDValue)1; for(jj=0; jj<(pRtree->nDim*2); jj+=2){ RtreeDValue x1, x2; x1 = MAX(DCOORD(p->aCoord[jj]), DCOORD(aCell[ii].aCoord[jj])); x2 = MIN(DCOORD(p->aCoord[jj+1]), DCOORD(aCell[ii].aCoord[jj+1])); |
︙ | ︙ | |||
1795 1796 1797 1798 1799 1800 1801 | RtreeNode *pNode; rc = nodeAcquire(pRtree, 1, 0, &pNode); for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){ int iCell; sqlite3_int64 iBest = 0; | | | | 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 | RtreeNode *pNode; rc = nodeAcquire(pRtree, 1, 0, &pNode); for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){ int iCell; sqlite3_int64 iBest = 0; RtreeDValue fMinGrowth = RTREE_ZERO; RtreeDValue fMinArea = RTREE_ZERO; int nCell = NCELL(pNode); RtreeCell cell; RtreeNode *pChild; RtreeCell *aCell = 0; |
︙ | ︙ | |||
2046 2047 2048 2049 2050 2051 2052 | ){ int **aaSorted; int *aSpare; int ii; int iBestDim = 0; int iBestSplit = 0; | | | | | | 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 | ){ int **aaSorted; int *aSpare; int ii; int iBestDim = 0; int iBestSplit = 0; RtreeDValue fBestMargin = RTREE_ZERO; int nByte = (pRtree->nDim+1)*(sizeof(int*)+nCell*sizeof(int)); aaSorted = (int **)sqlite3_malloc(nByte); if( !aaSorted ){ return SQLITE_NOMEM; } aSpare = &((int *)&aaSorted[pRtree->nDim])[pRtree->nDim*nCell]; memset(aaSorted, 0, nByte); for(ii=0; ii<pRtree->nDim; ii++){ int jj; aaSorted[ii] = &((int *)&aaSorted[pRtree->nDim])[ii*nCell]; for(jj=0; jj<nCell; jj++){ aaSorted[ii][jj] = jj; } SortByDimension(pRtree, aaSorted[ii], nCell, ii, aCell, aSpare); } for(ii=0; ii<pRtree->nDim; ii++){ RtreeDValue margin = RTREE_ZERO; RtreeDValue fBestOverlap = RTREE_ZERO; RtreeDValue fBestArea = RTREE_ZERO; int iBestLeft = 0; int nLeft; for( nLeft=RTREE_MINCELLS(pRtree); nLeft<=(nCell-RTREE_MINCELLS(pRtree)); nLeft++ |
︙ | ︙ | |||
2488 2489 2490 2491 2492 2493 2494 | } } for(iDim=0; iDim<pRtree->nDim; iDim++){ aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2)); } for(ii=0; ii<nCell; ii++){ | | | 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 | } } for(iDim=0; iDim<pRtree->nDim; iDim++){ aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2)); } for(ii=0; ii<nCell; ii++){ 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])); aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]); } } |
︙ | ︙ | |||
3295 3296 3297 3298 3299 3300 3301 | ** This routine deletes the RtreeGeomCallback object that was attached ** one of the SQL functions create by sqlite3_rtree_geometry_callback() ** or sqlite3_rtree_query_callback(). In other words, this routine is the ** destructor for an RtreeGeomCallback objecct. This routine is called when ** the corresponding SQL function is deleted. */ static void rtreeFreeCallback(void *p){ | | | | 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 | ** This routine deletes the RtreeGeomCallback object that was attached ** one of the SQL functions create by sqlite3_rtree_geometry_callback() ** or sqlite3_rtree_query_callback(). In other words, this routine is the ** destructor for an RtreeGeomCallback objecct. This routine is called when ** the corresponding SQL function is deleted. */ static void rtreeFreeCallback(void *p){ RtreeGeomCallback *pInfo = (RtreeGeomCallback*)p; if( pInfo->xDestructor ) pInfo->xDestructor(pInfo->pContext); sqlite3_free(p); } /* ** Each call to sqlite3_rtree_geometry_callback() or ** sqlite3_rtree_query_callback() creates an ordinary SQLite ** scalar function that is implemented by this routine. |
︙ | ︙ |
Added ext/rtree/rtreeE.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 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 |
Changes to src/test_rtree.c.
︙ | ︙ | |||
31 32 33 34 35 36 37 38 39 40 41 42 43 44 | double xmax; double ymin; double ymax; } aBox[2]; double centerx; double centery; double radius; }; /* ** Destructor function for Circle objects allocated by circle_geom(). */ static void circle_del(void *p){ sqlite3_free(p); | > | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | double xmax; double ymin; double ymax; } aBox[2]; double centerx; double centery; double radius; double mxArea; }; /* ** Destructor function for Circle objects allocated by circle_geom(). */ static void circle_del(void *p){ sqlite3_free(p); |
︙ | ︙ | |||
54 55 56 57 58 59 60 | int *pRes ){ int i; /* Iterator variable */ Circle *pCircle; /* Structure defining circular region */ double xmin, xmax; /* X dimensions of box being tested */ double ymin, ymax; /* X dimensions of box being tested */ | > > > > | > | 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | int *pRes ){ int i; /* Iterator variable */ Circle *pCircle; /* Structure defining circular region */ double xmin, xmax; /* X dimensions of box being tested */ double ymin, ymax; /* X dimensions of box being tested */ 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. */ /* This geometry callback is for use with a 2-dimensional r-tree table. ** Return an error if the table does not have exactly 2 dimensions. */ if( nCoord!=4 ) return SQLITE_ERROR; |
︙ | ︙ | |||
100 101 102 103 104 105 106 107 108 | pCircle->aBox[0].xmax = pCircle->centerx; pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius; pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius; pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius; pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius; pCircle->aBox[1].ymin = pCircle->centery; pCircle->aBox[1].ymax = pCircle->centery; } | > < < < < < < | 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | pCircle->aBox[0].xmax = pCircle->centerx; pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius; pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius; pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius; 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; } /* 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 ** return SQLITE_OK in this case. */ for(i=0; i<4; i++){ double x = (i&0x01) ? xmax : xmin; double y = (i&0x02) ? ymax : ymin; |
︙ | ︙ | |||
157 158 159 160 161 162 163 | static int circle_query_func(sqlite3_rtree_query_info *p){ int i; /* Iterator variable */ Circle *pCircle; /* Structure defining circular region */ double xmin, xmax; /* X dimensions of box being tested */ double ymin, ymax; /* X dimensions of box being tested */ int nWithin = 0; /* Number of corners inside the circle */ | > > > > | > | 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | static int circle_query_func(sqlite3_rtree_query_info *p){ int i; /* Iterator variable */ Circle *pCircle; /* Structure defining circular region */ double xmin, xmax; /* X dimensions of box being tested */ double ymin, ymax; /* X dimensions of box being tested */ int nWithin = 0; /* Number of corners inside the circle */ 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. */ /* This geometry callback is for use with a 2-dimensional r-tree table. ** Return an error if the table does not have exactly 2 dimensions. */ if( p->nCoord!=4 ) return SQLITE_ERROR; |
︙ | ︙ | |||
203 204 205 206 207 208 209 210 211 | pCircle->aBox[0].xmax = pCircle->centerx; pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius; pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius; pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius; pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius; pCircle->aBox[1].ymin = pCircle->centery; pCircle->aBox[1].ymax = pCircle->centery; } | > < < < < < < | 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | pCircle->aBox[0].xmax = pCircle->centerx; pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius; pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius; pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius; 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; } /* 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 ** return SQLITE_OK in this case. */ for(i=0; i<4; i++){ double x = (i&0x01) ? xmax : xmin; double y = (i&0x02) ? ymax : ymin; |
︙ | ︙ | |||
242 243 244 245 246 247 248 | ){ nWithin = 1; break; } } } | > > > > | > | 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | ){ nWithin = 1; break; } } } 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 ){ p->eWithin = FULLY_WITHIN; }else{ p->eWithin = PARTLY_WITHIN; } |
︙ | ︙ |