Index: configure ================================================================== --- configure +++ configure @@ -1,8 +1,8 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.62 for sqlite 3.7.11. +# Generated by GNU Autoconf 2.62 for sqlite 3.7.12. # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. @@ -741,12 +741,12 @@ SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME='sqlite' PACKAGE_TARNAME='sqlite' -PACKAGE_VERSION='3.7.11' -PACKAGE_STRING='sqlite 3.7.11' +PACKAGE_VERSION='3.7.12' +PACKAGE_STRING='sqlite 3.7.12' PACKAGE_BUGREPORT='' # Factoring default headers for most tests. ac_includes_default="\ #include @@ -1483,11 +1483,11 @@ # if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures sqlite 3.7.11 to adapt to many kinds of systems. +\`configure' configures sqlite 3.7.12 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. @@ -1548,11 +1548,11 @@ _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sqlite 3.7.11:";; + short | recursive ) echo "Configuration of sqlite 3.7.12:";; esac cat <<\_ACEOF Optional Features: --disable-option-checking ignore unrecognized --enable/--with options @@ -1664,11 +1664,11 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sqlite configure 3.7.11 +sqlite configure 3.7.12 generated by GNU Autoconf 2.62 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation @@ -1678,11 +1678,11 @@ fi cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by sqlite $as_me 3.7.11, which was +It was created by sqlite $as_me 3.7.12, which was generated by GNU Autoconf 2.62. Invocation command line was $ $0 $@ _ACEOF @@ -14030,11 +14030,11 @@ # Save the log message, to keep $[0] and so on meaningful, and to # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by sqlite $as_me 3.7.11, which was +This file was extended by sqlite $as_me 3.7.12, which was generated by GNU Autoconf 2.62. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS @@ -14083,11 +14083,11 @@ Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_version="\\ -sqlite config.status 3.7.11 +sqlite config.status 3.7.12 configured by $0, generated by GNU Autoconf 2.62, with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" Copyright (C) 2008 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation Index: ext/fts3/fts3_icu.c ================================================================== --- ext/fts3/fts3_icu.c +++ ext/fts3/fts3_icu.c @@ -108,11 +108,14 @@ int iInput = 0; int iOut = 0; *ppCursor = 0; - if( nInput<0 ){ + if( zInput==0 ){ + nInput = 0; + zInput = ""; + }else if( nInput<0 ){ nInput = strlen(zInput); } nChar = nInput+1; pCsr = (IcuCursor *)sqlite3_malloc( sizeof(IcuCursor) + /* IcuCursor */ Index: ext/fts3/fts3_test.c ================================================================== --- ext/fts3/fts3_test.c +++ ext/fts3/fts3_test.c @@ -20,11 +20,12 @@ #include #include #include -#ifdef SQLITE_TEST +#if defined(SQLITE_TEST) +#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) /* Required so that the "ifdef SQLITE_ENABLE_FTS3" below works */ #include "fts3Int.h" #define NM_MAX_TOKEN 12 @@ -528,6 +529,7 @@ Tcl_CreateObjCommand( interp, "fts3_test_tokenizer", fts3_test_tokenizer_cmd, 0, 0 ); return TCL_OK; } +#endif /* SQLITE_ENABLE_FTS3 || SQLITE_ENABLE_FTS4 */ #endif /* ifdef SQLITE_TEST */ Index: ext/fts3/fts3_write.c ================================================================== --- ext/fts3/fts3_write.c +++ ext/fts3/fts3_write.c @@ -3737,11 +3737,10 @@ /* Advance to the next output block */ pLeaf->iBlock++; pLeaf->key.n = 0; pLeaf->block.n = 0; - nPrefix = 0; nSuffix = nTerm; nSpace = 1; nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist; } Index: ext/rtree/rtree.c ================================================================== --- ext/rtree/rtree.c +++ ext/rtree/rtree.c @@ -180,10 +180,23 @@ /* Possible values for eCoordType: */ #define RTREE_COORD_REAL32 0 #define RTREE_COORD_INT32 1 +/* +** 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 + /* ** The minimum number of cells allowed for a node is a third of the ** maximum. In Gutman's notation: ** ** m = M/3 @@ -215,33 +228,38 @@ int nConstraint; /* Number of entries in aConstraint */ RtreeConstraint *aConstraint; /* Search constraints. */ }; union RtreeCoord { - float f; + RtreeValue f; int i; }; /* ** The argument is an RtreeCoord. Return the value stored within the RtreeCoord -** formatted as a double. This macro assumes that local variable pRtree points -** to the Rtree structure associated with the RtreeCoord. +** formatted as a RtreeDValue (double or int64). This macro assumes that local +** variable pRtree points to the Rtree structure associated with the +** RtreeCoord. */ -#define DCOORD(coord) ( \ - (pRtree->eCoordType==RTREE_COORD_REAL32) ? \ - ((double)coord.f) : \ - ((double)coord.i) \ -) +#ifdef SQLITE_RTREE_INT_ONLY +# define DCOORD(coord) ((RtreeDValue)coord.i) +#else +# define DCOORD(coord) ( \ + (pRtree->eCoordType==RTREE_COORD_REAL32) ? \ + ((double)coord.f) : \ + ((double)coord.i) \ + ) +#endif /* ** A search constraint. */ struct RtreeConstraint { int iCoord; /* Index of constrained coordinate */ int op; /* Constraining operation */ - double rValue; /* Constraint value. */ - int (*xGeom)(sqlite3_rtree_geometry *, int, double *, int *); + RtreeDValue rValue; /* Constraint value. */ + int (*xGeom)(sqlite3_rtree_geometry*, int, RtreeDValue*, int*); sqlite3_rtree_geometry *pGeom; /* Constraint callback argument for a MATCH */ }; /* Possible values for RtreeConstraint.op */ #define RTREE_EQ 0x41 @@ -285,14 +303,14 @@ ** the right-hand-side of an SQL MATCH operator used to constrain an ** r-tree query. */ struct RtreeMatchArg { u32 magic; /* Always RTREE_GEOMETRY_MAGIC */ - int (*xGeom)(sqlite3_rtree_geometry *, int, double *, int *); + int (*xGeom)(sqlite3_rtree_geometry *, int, RtreeDValue*, int *); void *pContext; int nParam; - double aParam[1]; + RtreeDValue aParam[1]; }; /* ** When a geometry callback is created (see sqlite3_rtree_geometry_callback), ** a single instance of the following structure is allocated. It is used @@ -300,11 +318,11 @@ ** is eventually deleted by the destructor mechanism provided by ** sqlite3_create_function_v2() (which is called by s_r_g_c() to create ** the geometry callback function). */ struct RtreeGeomCallback { - int (*xGeom)(sqlite3_rtree_geometry *, int, double *, int *); + int (*xGeom)(sqlite3_rtree_geometry*, int, RtreeDValue*, int*); void *pContext; }; #ifndef MAX # define MAX(x,y) ((x) < (y) ? (y) : (x)) @@ -866,11 +884,11 @@ RtreeConstraint *pConstraint, /* MATCH constraint to test */ RtreeCell *pCell, /* Cell to test */ int *pbRes /* OUT: Test result */ ){ int i; - double aCoord[RTREE_MAX_DIMENSIONS*2]; + RtreeDValue aCoord[RTREE_MAX_DIMENSIONS*2]; int nCoord = pRtree->nDim*2; assert( pConstraint->op==RTREE_MATCH ); assert( pConstraint->pGeom ); @@ -896,12 +914,12 @@ int rc = SQLITE_OK; nodeGetCell(pRtree, pCursor->pNode, pCursor->iCell, &cell); for(ii=0; bRes==0 && iinConstraint; ii++){ RtreeConstraint *p = &pCursor->aConstraint[ii]; - double cell_min = DCOORD(cell.aCoord[(p->iCoord>>1)*2]); - double cell_max = DCOORD(cell.aCoord[(p->iCoord>>1)*2+1]); + RtreeDValue cell_min = DCOORD(cell.aCoord[(p->iCoord>>1)*2]); + RtreeDValue cell_max = DCOORD(cell.aCoord[(p->iCoord>>1)*2+1]); assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_MATCH ); @@ -949,11 +967,11 @@ *pbEof = 0; nodeGetCell(pRtree, pCursor->pNode, pCursor->iCell, &cell); for(ii=0; iinConstraint; ii++){ RtreeConstraint *p = &pCursor->aConstraint[ii]; - double coord = DCOORD(cell.aCoord[p->iCoord]); + RtreeDValue coord = DCOORD(cell.aCoord[p->iCoord]); int res; assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_MATCH ); switch( p->op ){ @@ -1147,13 +1165,16 @@ i64 iRowid = nodeGetRowid(pRtree, pCsr->pNode, pCsr->iCell); sqlite3_result_int64(ctx, iRowid); }else{ RtreeCoord c; nodeGetCoord(pRtree, pCsr->pNode, pCsr->iCell, i-1, &c); +#ifndef SQLITE_RTREE_INT_ONLY if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ sqlite3_result_double(ctx, c.f); - }else{ + }else +#endif + { assert( pRtree->eCoordType==RTREE_COORD_INT32 ); sqlite3_result_int(ctx, c.i); } } @@ -1196,11 +1217,11 @@ 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(double))!=0 + || ((nBlob-sizeof(RtreeMatchArg))%sizeof(RtreeDValue))!=0 ){ return SQLITE_ERROR; } pGeom = (sqlite3_rtree_geometry *)sqlite3_malloc( @@ -1210,11 +1231,11 @@ memset(pGeom, 0, sizeof(sqlite3_rtree_geometry)); p = (RtreeMatchArg *)&pGeom[1]; memcpy(p, sqlite3_value_blob(pValue), nBlob); if( p->magic!=RTREE_GEOMETRY_MAGIC - || nBlob!=(int)(sizeof(RtreeMatchArg) + (p->nParam-1)*sizeof(double)) + || nBlob!=(int)(sizeof(RtreeMatchArg) + (p->nParam-1)*sizeof(RtreeDValue)) ){ sqlite3_free(pGeom); return SQLITE_ERROR; } @@ -1282,11 +1303,15 @@ rc = deserializeGeometry(argv[ii], p); if( rc!=SQLITE_OK ){ break; } }else{ +#ifdef SQLITE_RTREE_INT_ONLY + p->rValue = sqlite3_value_int64(argv[ii]); +#else p->rValue = sqlite3_value_double(argv[ii]); +#endif } } } } @@ -1416,28 +1441,28 @@ } /* ** Return the N-dimensional volumn of the cell stored in *p. */ -static float cellArea(Rtree *pRtree, RtreeCell *p){ - float area = 1.0; +static RtreeDValue cellArea(Rtree *pRtree, RtreeCell *p){ + RtreeDValue area = (RtreeDValue)1; int ii; for(ii=0; ii<(pRtree->nDim*2); ii+=2){ - area = (float)(area * (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii]))); + area = (area * (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii]))); } return area; } /* ** Return the margin length of cell p. The margin length is the sum ** of the objects size in each dimension. */ -static float cellMargin(Rtree *pRtree, RtreeCell *p){ - float margin = 0.0; +static RtreeDValue cellMargin(Rtree *pRtree, RtreeCell *p){ + RtreeDValue margin = (RtreeDValue)0; int ii; for(ii=0; ii<(pRtree->nDim*2); ii+=2){ - margin += (float)(DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii])); + margin += (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii])); } return margin; } /* @@ -1478,51 +1503,50 @@ } /* ** Return the amount cell p would grow by if it were unioned with pCell. */ -static float cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){ - float area; +static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){ + RtreeDValue area; RtreeCell cell; memcpy(&cell, p, sizeof(RtreeCell)); area = cellArea(pRtree, &cell); cellUnion(pRtree, &cell, pCell); return (cellArea(pRtree, &cell)-area); } #if VARIANT_RSTARTREE_CHOOSESUBTREE || VARIANT_RSTARTREE_SPLIT -static float cellOverlap( +static RtreeDValue cellOverlap( Rtree *pRtree, RtreeCell *p, RtreeCell *aCell, int nCell, int iExclude ){ int ii; - float overlap = 0.0; + RtreeDValue overlap = 0.0; for(ii=0; iinDim*2); jj+=2){ - double x1; - double x2; + 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])); if( x2iDepth-iHeight); ii++){ int iCell; sqlite3_int64 iBest = 0; - float fMinGrowth = 0.0; - float fMinArea = 0.0; + RtreeDValue fMinGrowth = 0.0; + RtreeDValue fMinArea = 0.0; #if VARIANT_RSTARTREE_CHOOSESUBTREE - float fMinOverlap = 0.0; - float overlap; + RtreeDValue fMinOverlap = 0.0; + RtreeDValue overlap; #endif int nCell = NCELL(pNode); RtreeCell cell; RtreeNode *pChild; @@ -1601,12 +1624,12 @@ ** is inserted into it. Resolve ties by choosing the entry with ** the smallest area. */ for(iCell=0; iCellnDim; i++){ - float x1 = DCOORD(aCell[0].aCoord[i*2]); - float x2 = DCOORD(aCell[0].aCoord[i*2+1]); - float x3 = x1; - float x4 = x2; + RtreeDValue x1 = DCOORD(aCell[0].aCoord[i*2]); + RtreeDValue x2 = DCOORD(aCell[0].aCoord[i*2+1]); + RtreeDValue x3 = x1; + RtreeDValue x4 = x2; int jj; int iCellLeft = 0; int iCellRight = 0; for(jj=1; jjx4 ) x4 = right; if( left>x3 ){ x3 = left; @@ -1763,11 +1786,11 @@ iCellLeft = jj; } } if( x4!=x1 ){ - float normalwidth = (x3 - x2) / (x4 - x1); + RtreeDValue normalwidth = (x3 - x2) / (x4 - x1); if( normalwidth>maxNormalInnerWidth ){ iLeftSeed = iCellLeft; iRightSeed = iCellRight; } } @@ -1792,17 +1815,17 @@ int *aiUsed ){ #define FABS(a) ((a)<0.0?-1.0*(a):(a)) int iSelect = -1; - float fDiff; + RtreeDValue fDiff; int ii; for(ii=0; iifDiff ){ fDiff = diff; iSelect = ii; } } @@ -1825,17 +1848,17 @@ int ii; int jj; int iLeftSeed = 0; int iRightSeed = 1; - float fWaste = 0.0; + RtreeDValue fWaste = 0.0; for(ii=0; iifWaste ){ iLeftSeed = ii; iRightSeed = jj; fWaste = waste; @@ -1866,11 +1889,11 @@ ** sorting algorithm. */ static void SortByDistance( int *aIdx, int nIdx, - float *aDistance, + RtreeDValue *aDistance, int *aSpare ){ if( nIdx>1 ){ int iLeft = 0; int iRight = 0; @@ -1892,12 +1915,12 @@ iRight++; }else if( iRight==nRight ){ aIdx[iLeft+iRight] = aLeft[iLeft]; iLeft++; }else{ - float fLeft = aDistance[aLeft[iLeft]]; - float fRight = aDistance[aRight[iRight]]; + RtreeDValue fLeft = aDistance[aLeft[iLeft]]; + RtreeDValue fRight = aDistance[aRight[iRight]]; if( fLeftnDim+1)*(sizeof(int*)+nCell*sizeof(int)); aaSorted = (int **)sqlite3_malloc(nByte); if( !aaSorted ){ @@ -2025,13 +2048,13 @@ } SortByDimension(pRtree, aaSorted[ii], nCell, ii, aCell, aSpare); } for(ii=0; iinDim; ii++){ - float margin = 0.0; - float fBestOverlap = 0.0; - float fBestArea = 0.0; + RtreeDValue margin = 0.0; + RtreeDValue fBestOverlap = 0.0; + RtreeDValue fBestArea = 0.0; int iBestLeft = 0; int nLeft; for( nLeft=RTREE_MINCELLS(pRtree); @@ -2039,12 +2062,12 @@ nLeft++ ){ RtreeCell left; RtreeCell right; int kk; - float overlap; - float area; + RtreeDValue overlap; + RtreeDValue area; memcpy(&left, &aCell[aaSorted[ii][0]], sizeof(RtreeCell)); memcpy(&right, &aCell[aaSorted[ii][nCell-1]], sizeof(RtreeCell)); for(kk=1; kk<(nCell-1); kk++){ if( kk0; i--){ RtreeCell *pNext; pNext = PickNext(pRtree, aCell, nCell, pBboxLeft, pBboxRight, aiUsed); - float diff = + RtreeDValue diff = cellGrowth(pRtree, pBboxLeft, pNext) - cellGrowth(pRtree, pBboxRight, pNext) ; if( (RTREE_MINCELLS(pRtree)-NCELL(pRight)==i) || (diff>0.0 && (RTREE_MINCELLS(pRtree)-NCELL(pLeft)!=i)) @@ -2456,58 +2479,60 @@ int iHeight ){ int *aOrder; int *aSpare; RtreeCell *aCell; - float *aDistance; + RtreeDValue *aDistance; int nCell; - float aCenterCoord[RTREE_MAX_DIMENSIONS]; + RtreeDValue aCenterCoord[RTREE_MAX_DIMENSIONS]; int iDim; int ii; int rc = SQLITE_OK; + int n; - memset(aCenterCoord, 0, sizeof(float)*RTREE_MAX_DIMENSIONS); + memset(aCenterCoord, 0, sizeof(RtreeDValue)*RTREE_MAX_DIMENSIONS); nCell = NCELL(pNode)+1; + n = (nCell+1)&(~1); /* Allocate the buffers used by this operation. The allocation is ** relinquished before this function returns. */ - aCell = (RtreeCell *)sqlite3_malloc(nCell * ( - sizeof(RtreeCell) + /* aCell array */ - sizeof(int) + /* aOrder array */ - sizeof(int) + /* aSpare array */ - sizeof(float) /* aDistance array */ + aCell = (RtreeCell *)sqlite3_malloc(n * ( + sizeof(RtreeCell) + /* aCell array */ + sizeof(int) + /* aOrder array */ + sizeof(int) + /* aSpare array */ + sizeof(RtreeDValue) /* aDistance array */ )); if( !aCell ){ return SQLITE_NOMEM; } - aOrder = (int *)&aCell[nCell]; - aSpare = (int *)&aOrder[nCell]; - aDistance = (float *)&aSpare[nCell]; + aOrder = (int *)&aCell[n]; + aSpare = (int *)&aOrder[n]; + aDistance = (RtreeDValue *)&aSpare[n]; for(ii=0; iinDim; iDim++){ - aCenterCoord[iDim] += (float)DCOORD(aCell[ii].aCoord[iDim*2]); - aCenterCoord[iDim] += (float)DCOORD(aCell[ii].aCoord[iDim*2+1]); + aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]); + aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]); } } for(iDim=0; iDimnDim; iDim++){ - aCenterCoord[iDim] = (float)(aCenterCoord[iDim]/((float)nCell*2.0)); + aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2)); } for(ii=0; iinDim; iDim++){ - float coord = (float)(DCOORD(aCell[ii].aCoord[iDim*2+1]) - - DCOORD(aCell[ii].aCoord[iDim*2])); + RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) - + DCOORD(aCell[ii].aCoord[iDim*2])); aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]); } } SortByDistance(aOrder, nCell, aDistance, aSpare); @@ -2745,20 +2770,23 @@ if( nData>1 ){ int ii; /* Populate the cell.aCoord[] array. The first coordinate is azData[3]. */ assert( nData==(pRtree->nDim*2 + 3) ); +#ifndef SQLITE_RTREE_INT_ONLY if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ for(ii=0; ii<(pRtree->nDim*2); ii+=2){ - cell.aCoord[ii].f = (float)sqlite3_value_double(azData[ii+3]); - cell.aCoord[ii+1].f = (float)sqlite3_value_double(azData[ii+4]); + cell.aCoord[ii].f = (RtreeValue)sqlite3_value_double(azData[ii+3]); + cell.aCoord[ii+1].f = (RtreeValue)sqlite3_value_double(azData[ii+4]); if( cell.aCoord[ii].f>cell.aCoord[ii+1].f ){ rc = SQLITE_CONSTRAINT; goto constraint; } } - }else{ + }else +#endif + { for(ii=0; ii<(pRtree->nDim*2); ii+=2){ cell.aCoord[ii].i = sqlite3_value_int(azData[ii+3]); cell.aCoord[ii+1].i = sqlite3_value_int(azData[ii+4]); if( cell.aCoord[ii].i>cell.aCoord[ii+1].i ){ rc = SQLITE_CONSTRAINT; @@ -3152,11 +3180,17 @@ nodeGetCell(&tree, &node, ii, &cell); sqlite3_snprintf(512-nCell,&zCell[nCell],"%lld", cell.iRowid); nCell = (int)strlen(zCell); for(jj=0; jjmagic = RTREE_GEOMETRY_MAGIC; pBlob->xGeom = pGeomCtx->xGeom; pBlob->pContext = pGeomCtx->pContext; pBlob->nParam = nArg; for(i=0; iaParam[i] = sqlite3_value_int64(aArg[i]); +#else pBlob->aParam[i] = sqlite3_value_double(aArg[i]); +#endif } sqlite3_result_blob(ctx, pBlob, nBlob, doSqlite3Free); } } @@ -3251,11 +3293,11 @@ ** Register a new geometry function for use with the r-tree MATCH operator. */ int sqlite3_rtree_geometry_callback( sqlite3 *db, const char *zGeom, - int (*xGeom)(sqlite3_rtree_geometry *, int, double *, int *), + int (*xGeom)(sqlite3_rtree_geometry *, int, RtreeDValue *, int *), void *pContext ){ RtreeGeomCallback *pGeomCtx; /* Context object for new user-function */ /* Allocate and populate the context object. */ Index: ext/rtree/rtree1.test ================================================================== --- ext/rtree/rtree1.test +++ ext/rtree/rtree1.test @@ -101,10 +101,22 @@ " } $X catchsql { DROP TABLE t1 } } + +# Like execsql except display output as integer where that can be +# done without loss of information. +# +proc execsql_intout {sql} { + set out {} + foreach term [execsql $sql] { + regsub {\.0$} $term {} term + lappend out $term + } + return $out +} # Test that it is possible to open an existing database that contains # r-tree tables. # do_test rtree-1.4.1 { @@ -115,26 +127,26 @@ } } {} do_test rtree-1.4.2 { db close sqlite3 db test.db - execsql { SELECT * FROM t1 ORDER BY ii } -} {1 5.0 10.0 2 15.0 20.0} + execsql_intout { SELECT * FROM t1 ORDER BY ii } +} {1 5 10 2 15 20} do_test rtree-1.4.3 { execsql { DROP TABLE t1 } } {} # Test that it is possible to create an r-tree table with ridiculous # column names. # do_test rtree-1.5.1 { - execsql { + execsql_intout { CREATE VIRTUAL TABLE t1 USING rtree("the key", "x dim.", "x2'dim"); INSERT INTO t1 VALUES(1, 2, 3); SELECT "the key", "x dim.", "x2'dim" FROM t1; } -} {1 2.0 3.0} +} {1 2 3} do_test rtree-1.5.1 { execsql { DROP TABLE t1 } } {} # Force the r-tree constructor to fail. @@ -159,12 +171,12 @@ } } {} do_test rtree-2.1.2 { execsql { INSERT INTO t1 VALUES(NULL, 1, 3, 2, 4) } - execsql { SELECT * FROM t1 } -} {1 1.0 3.0 2.0 4.0} + execsql_intout { SELECT * FROM t1 } +} {1 1 3 2 4} do_test rtree-2.1.3 { execsql { INSERT INTO t1 VALUES(NULL, 1, 3, 2, 4) } execsql { SELECT rowid FROM t1 ORDER BY rowid } } {1 2} do_test rtree-2.1.3 { @@ -199,21 +211,21 @@ CREATE VIRTUAL TABLE t1 USING rtree(ii, x1, x2, y1, y2); SELECT * FROM t1; } } {} do_test rtree-3.1.2 { - execsql { + execsql_intout { INSERT INTO t1 VALUES(5, 1, 3, 2, 4); SELECT * FROM t1; } -} {5 1.0 3.0 2.0 4.0} +} {5 1 3 2 4} do_test rtree-3.1.3 { - execsql { + execsql_intout { INSERT INTO t1 VALUES(6, 2, 6, 4, 8); SELECT * FROM t1; } -} {5 1.0 3.0 2.0 4.0 6 2.0 6.0 4.0 8.0} +} {5 1 3 2 4 6 2 6 4 8} # Test the constraint on the coordinates (c[i]<=c[i+1] where (i%2==0)): do_test rtree-3.2.1 { catchsql { INSERT INTO t1 VALUES(7, 2, 6, 4, 3) } } {1 {constraint failed}} @@ -226,29 +238,29 @@ # do_test rtree-5.1.1 { execsql { CREATE VIRTUAL TABLE t2 USING rtree(ii, x1, x2) } } {} do_test rtree-5.1.2 { - execsql { + execsql_intout { INSERT INTO t2 VALUES(1, 10, 20); INSERT INTO t2 VALUES(2, 30, 40); INSERT INTO t2 VALUES(3, 50, 60); SELECT * FROM t2 ORDER BY ii; } -} {1 10.0 20.0 2 30.0 40.0 3 50.0 60.0} +} {1 10 20 2 30 40 3 50 60} do_test rtree-5.1.3 { - execsql { + execsql_intout { DELETE FROM t2 WHERE ii=2; SELECT * FROM t2 ORDER BY ii; } -} {1 10.0 20.0 3 50.0 60.0} +} {1 10 20 3 50 60} do_test rtree-5.1.4 { - execsql { + execsql_intout { DELETE FROM t2 WHERE ii=1; SELECT * FROM t2 ORDER BY ii; } -} {3 50.0 60.0} +} {3 50 60} do_test rtree-5.1.5 { execsql { DELETE FROM t2 WHERE ii=3; SELECT * FROM t2 ORDER BY ii; } @@ -262,20 +274,20 @@ # do_test rtree-6.1.1 { execsql { CREATE VIRTUAL TABLE t3 USING rtree(ii, x1, x2, y1, y2) } } {} do_test rtree-6.1.2 { - execsql { + execsql_intout { INSERT INTO t3 VALUES(1, 2, 3, 4, 5); UPDATE t3 SET x2=5; SELECT * FROM t3; } -} {1 2.0 5.0 4.0 5.0} +} {1 2 5 4 5} do_test rtree-6.1.3 { execsql { UPDATE t3 SET ii = 2 } - execsql { SELECT * FROM t3 } -} {2 2.0 5.0 4.0 5.0} + execsql_intout { SELECT * FROM t3 } +} {2 2 5 4 5} #---------------------------------------------------------------------------- # Test cases rtree-7.* test rename operations. # do_test rtree-7.1.1 { @@ -284,44 +296,44 @@ INSERT INTO t4 VALUES(1, 2, 3, 4, 5, 6, 7); } } {} do_test rtree-7.1.2 { execsql { ALTER TABLE t4 RENAME TO t5 } - execsql { SELECT * FROM t5 } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM t5 } +} {1 2 3 4 5 6 7} do_test rtree-7.1.3 { db close sqlite3 db test.db - execsql { SELECT * FROM t5 } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM t5 } +} {1 2 3 4 5 6 7} do_test rtree-7.1.4 { execsql { ALTER TABLE t5 RENAME TO 'raisara "one"'''} - execsql { SELECT * FROM "raisara ""one""'" } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM "raisara ""one""'" } +} {1 2 3 4 5 6 7} do_test rtree-7.1.5 { - execsql { SELECT * FROM 'raisara "one"''' } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM 'raisara "one"''' } +} {1 2 3 4 5 6 7} do_test rtree-7.1.6 { execsql { ALTER TABLE "raisara ""one""'" RENAME TO "abc 123" } - execsql { SELECT * FROM "abc 123" } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM "abc 123" } +} {1 2 3 4 5 6 7} do_test rtree-7.1.7 { db close sqlite3 db test.db - execsql { SELECT * FROM "abc 123" } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM "abc 123" } +} {1 2 3 4 5 6 7} # An error midway through a rename operation. do_test rtree-7.2.1 { execsql { CREATE TABLE t4_node(a); } catchsql { ALTER TABLE "abc 123" RENAME TO t4 } } {1 {SQL logic error or missing database}} do_test rtree-7.2.2 { - execsql { SELECT * FROM "abc 123" } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM "abc 123" } +} {1 2 3 4 5 6 7} do_test rtree-7.2.3 { execsql { DROP TABLE t4_node; CREATE TABLE t4_rowid(a); } @@ -328,17 +340,17 @@ catchsql { ALTER TABLE "abc 123" RENAME TO t4 } } {1 {SQL logic error or missing database}} do_test rtree-7.2.4 { db close sqlite3 db test.db - execsql { SELECT * FROM "abc 123" } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM "abc 123" } +} {1 2 3 4 5 6 7} do_test rtree-7.2.5 { execsql { DROP TABLE t4_rowid } execsql { ALTER TABLE "abc 123" RENAME TO t4 } - execsql { SELECT * FROM t4 } -} {1 2.0 3.0 4.0 5.0 6.0 7.0} + execsql_intout { SELECT * FROM t4 } +} {1 2 3 4 5 6 7} #---------------------------------------------------------------------------- # Test cases rtree-8.* # Index: ext/rtree/rtree4.test ================================================================== --- ext/rtree/rtree4.test +++ ext/rtree/rtree4.test @@ -25,25 +25,42 @@ set ::NROW 2500 if {[info exists G(isquick)] && $G(isquick)} { set ::NROW 250 } -# Return a floating point number between -X and X. -# -proc rand {X} { - return [expr {int((rand()-0.5)*1024.0*$X)/512.0}] -} - -# Return a positive floating point number less than or equal to X -# -proc randincr {X} { - while 1 { - set r [expr {int(rand()*$X*32.0)/32.0}] - if {$r>0.0} {return $r} - } -} - +ifcapable !rtree_int_only { + # Return a floating point number between -X and X. + # + proc rand {X} { + return [expr {int((rand()-0.5)*1024.0*$X)/512.0}] + } + + # Return a positive floating point number less than or equal to X + # + proc randincr {X} { + while 1 { + set r [expr {int(rand()*$X*32.0)/32.0}] + if {$r>0.0} {return $r} + } + } +} else { + # For rtree_int_only, return an number between -X and X. + # + proc rand {X} { + return [expr {int((rand()-0.5)*2*$X)}] + } + + # Return a positive integer less than or equal to X + # + proc randincr {X} { + while 1 { + set r [expr {int(rand()*$X)+1}] + if {$r>0} {return $r} + } + } +} + # Scramble the $inlist into a random order. # proc scramble {inlist} { set y {} foreach x $inlist { Index: ext/rtree/rtree5.test ================================================================== --- ext/rtree/rtree5.test +++ ext/rtree/rtree5.test @@ -47,13 +47,15 @@ } {1} do_test rtree5-1.7 { execsql { SELECT count(*) FROM t1 WHERE x1==5 } } {1} -do_test rtree5-1.8 { - execsql { SELECT count(*) FROM t1 WHERE x1==5.2 } -} {0} +ifcapable !rtree_int_only { + do_test rtree5-1.8 { + execsql { SELECT count(*) FROM t1 WHERE x1==5.2 } + } {0} +} do_test rtree5-1.9 { execsql { SELECT count(*) FROM t1 WHERE x1==5.0 } } {1} do_test rtree5-1.10 { Index: ext/rtree/rtree6.test ================================================================== --- ext/rtree/rtree6.test +++ ext/rtree/rtree6.test @@ -14,11 +14,11 @@ if {![info exists testdir]} { set testdir [file join [file dirname [info script]] .. .. test] } source $testdir/tester.tcl -ifcapable !rtree { +ifcapable {!rtree || rtree_int_only} { finish_test return } # Operator Byte Value Index: ext/rtree/rtree7.test ================================================================== --- ext/rtree/rtree7.test +++ ext/rtree/rtree7.test @@ -21,38 +21,50 @@ ifcapable !rtree||!vacuum { finish_test return } + +# Like execsql except display output as integer where that can be +# done without loss of information. +# +proc execsql_intout {sql} { + set out {} + foreach term [execsql $sql] { + regsub {\.0$} $term {} term + lappend out $term + } + return $out +} do_test rtree7-1.1 { execsql { PRAGMA page_size = 1024; CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, y1, y2); INSERT INTO rt VALUES(1, 1, 2, 3, 4); } } {} do_test rtree7-1.2 { - execsql { SELECT * FROM rt } -} {1 1.0 2.0 3.0 4.0} + execsql_intout { SELECT * FROM rt } +} {1 1 2 3 4} do_test rtree7-1.3 { - execsql { + execsql_intout { PRAGMA page_size = 2048; VACUUM; SELECT * FROM rt; } -} {1 1.0 2.0 3.0 4.0} +} {1 1 2 3 4} do_test rtree7-1.4 { for {set i 2} {$i <= 51} {incr i} { execsql { INSERT INTO rt VALUES($i, 1, 2, 3, 4) } } - execsql { SELECT sum(x1), sum(x2), sum(y1), sum(y2) FROM rt } -} {51.0 102.0 153.0 204.0} + execsql_intout { SELECT sum(x1), sum(x2), sum(y1), sum(y2) FROM rt } +} {51 102 153 204} do_test rtree7-1.5 { - execsql { + execsql_intout { PRAGMA page_size = 512; VACUUM; SELECT sum(x1), sum(x2), sum(y1), sum(y2) FROM rt } -} {51.0 102.0 153.0 204.0} +} {51 102 153 204} finish_test Index: ext/rtree/rtree9.test ================================================================== --- ext/rtree/rtree9.test +++ ext/rtree/rtree9.test @@ -15,10 +15,11 @@ 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 } register_cube_geom db do_execsql_test rtree9-1.1 { CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2, y1, y2, z1, z2); Index: ext/rtree/rtreeB.test ================================================================== --- ext/rtree/rtreeB.test +++ ext/rtree/rtreeB.test @@ -16,19 +16,32 @@ set testdir [file join [file dirname [info script]] .. .. test] } source $testdir/tester.tcl ifcapable !rtree { finish_test ; return } -do_test rtreeB-1.1 { - db eval { - CREATE VIRTUAL TABLE t1 USING rtree(ii, x0, y0, x1, y1); - INSERT INTO t1 VALUES(1073741824, 0.0, 0.0, 100.0, 100.0); - INSERT INTO t1 VALUES(2147483646, 0.0, 0.0, 200.0, 200.0); - INSERT INTO t1 VALUES(4294967296, 0.0, 0.0, 300.0, 300.0); - INSERT INTO t1 VALUES(8589934592, 20.0, 20.0, 150.0, 150.0); - INSERT INTO t1 VALUES(9223372036854775807, 150, 150, 400, 400); - SELECT rtreenode(2, data) FROM t1_node; - } -} {{{1073741824 0.000000 0.000000 100.000000 100.000000} {2147483646 0.000000 0.000000 200.000000 200.000000} {4294967296 0.000000 0.000000 300.000000 300.000000} {8589934592 20.000000 20.000000 150.000000 150.000000} {9223372036854775807 150.000000 150.000000 400.000000 400.000000}}} - +ifcapable rtree_int_only { + do_test rtreeB-1.1-intonly { + db eval { + CREATE VIRTUAL TABLE t1 USING rtree(ii, x0, y0, x1, y1); + INSERT INTO t1 VALUES(1073741824, 0.0, 0.0, 100.0, 100.0); + INSERT INTO t1 VALUES(2147483646, 0.0, 0.0, 200.0, 200.0); + INSERT INTO t1 VALUES(4294967296, 0.0, 0.0, 300.0, 300.0); + INSERT INTO t1 VALUES(8589934592, 20.0, 20.0, 150.0, 150.0); + INSERT INTO t1 VALUES(9223372036854775807, 150, 150, 400, 400); + SELECT rtreenode(2, data) FROM t1_node; + } + } {{{1073741824 0 0 100 100} {2147483646 0 0 200 200} {4294967296 0 0 300 300} {8589934592 20 20 150 150} {9223372036854775807 150 150 400 400}}} +} else { + do_test rtreeB-1.1 { + db eval { + CREATE VIRTUAL TABLE t1 USING rtree(ii, x0, y0, x1, y1); + INSERT INTO t1 VALUES(1073741824, 0.0, 0.0, 100.0, 100.0); + INSERT INTO t1 VALUES(2147483646, 0.0, 0.0, 200.0, 200.0); + INSERT INTO t1 VALUES(4294967296, 0.0, 0.0, 300.0, 300.0); + INSERT INTO t1 VALUES(8589934592, 20.0, 20.0, 150.0, 150.0); + INSERT INTO t1 VALUES(9223372036854775807, 150, 150, 400, 400); + SELECT rtreenode(2, data) FROM t1_node; + } + } {{{1073741824 0.000000 0.000000 100.000000 100.000000} {2147483646 0.000000 0.000000 200.000000 200.000000} {4294967296 0.000000 0.000000 300.000000 300.000000} {8589934592 20.000000 20.000000 150.000000 150.000000} {9223372036854775807 150.000000 150.000000 400.000000 400.000000}}} +} finish_test Index: ext/rtree/sqlite3rtree.h ================================================================== --- ext/rtree/sqlite3rtree.h +++ ext/rtree/sqlite3rtree.h @@ -29,11 +29,15 @@ ** SELECT ... FROM WHERE MATCH $zGeom(... params ...) */ int sqlite3_rtree_geometry_callback( sqlite3 *db, const char *zGeom, - int (*xGeom)(sqlite3_rtree_geometry *, int nCoord, double *aCoord, int *pRes), +#ifdef SQLITE_RTREE_INT_ONLY + int (*xGeom)(sqlite3_rtree_geometry*, int n, sqlite3_int64 *a, int *pRes), +#else + int (*xGeom)(sqlite3_rtree_geometry*, int n, double *a, int *pRes), +#endif void *pContext ); /* Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -7552,10 +7552,29 @@ } } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ #ifndef SQLITE_OMIT_INTEGRITY_CHECK + +/* +** Return non-zero if the bit in the IntegrityCk.aPgRef[] array that +** corresponds to page iPg is already set. +*/ +static int getPageReferenced(IntegrityCk *pCheck, Pgno iPg){ + assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 ); + return (pCheck->aPgRef[iPg/8] & (1 << (iPg & 0x07))); +} + +/* +** Set the bit in the IntegrityCk.aPgRef[] array that corresponds to page iPg. +*/ +static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){ + assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 ); + pCheck->aPgRef[iPg/8] |= (1 << (iPg & 0x07)); +} + + /* ** Add 1 to the reference count for page iPage. If this is the second ** reference to the page, add an error message to pCheck->zErrMsg. ** Return 1 if there are 2 ore more references to the page and 0 if ** if this is the first reference to the page. @@ -7566,15 +7585,16 @@ if( iPage==0 ) return 1; if( iPage>pCheck->nPage ){ checkAppendMsg(pCheck, zContext, "invalid page number %d", iPage); return 1; } - if( pCheck->anRef[iPage]==1 ){ + if( getPageReferenced(pCheck, iPage) ){ checkAppendMsg(pCheck, zContext, "2nd reference to page %d", iPage); return 1; } - return (pCheck->anRef[iPage]++)>1; + setPageReferenced(pCheck, iPage); + return 0; } #ifndef SQLITE_OMIT_AUTOVACUUM /* ** Check that the entry in the pointer-map for page iChild maps to @@ -7946,21 +7966,19 @@ *pnErr = 0; if( sCheck.nPage==0 ){ sqlite3BtreeLeave(p); return 0; } - sCheck.anRef = sqlite3Malloc( (sCheck.nPage+1)*sizeof(sCheck.anRef[0]) ); - if( !sCheck.anRef ){ + + sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1); + if( !sCheck.aPgRef ){ *pnErr = 1; sqlite3BtreeLeave(p); return 0; } - for(i=0; i<=sCheck.nPage; i++){ sCheck.anRef[i] = 0; } i = PENDING_BYTE_PAGE(pBt); - if( i<=sCheck.nPage ){ - sCheck.anRef[i] = 1; - } + if( i<=sCheck.nPage ) setPageReferenced(&sCheck, i); sqlite3StrAccumInit(&sCheck.errMsg, zErr, sizeof(zErr), 20000); sCheck.errMsg.useMalloc = 2; /* Check the integrity of the freelist */ @@ -7981,22 +7999,22 @@ /* Make sure every page in the file is referenced */ for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ #ifdef SQLITE_OMIT_AUTOVACUUM - if( sCheck.anRef[i]==0 ){ + if( getPageReferenced(&sCheck, i)==0 ){ checkAppendMsg(&sCheck, 0, "Page %d is never used", i); } #else /* If the database supports auto-vacuum, make sure no tables contain ** references to pointer-map pages. */ - if( sCheck.anRef[i]==0 && + if( getPageReferenced(&sCheck, i)==0 && (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ checkAppendMsg(&sCheck, 0, "Page %d is never used", i); } - if( sCheck.anRef[i]!=0 && + if( getPageReferenced(&sCheck, i)!=0 && (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ checkAppendMsg(&sCheck, 0, "Pointer map page %d is referenced", i); } #endif } @@ -8013,11 +8031,11 @@ } /* Clean up and report errors. */ sqlite3BtreeLeave(p); - sqlite3_free(sCheck.anRef); + sqlite3_free(sCheck.aPgRef); if( sCheck.mallocFailed ){ sqlite3StrAccumReset(&sCheck.errMsg); *pnErr = sCheck.nErr+1; return 0; } Index: src/btreeInt.h ================================================================== --- src/btreeInt.h +++ src/btreeInt.h @@ -629,16 +629,22 @@ /* ** This structure is passed around through all the sanity checking routines ** in order to keep track of some global state information. +** +** The aRef[] array is allocated so that there is 1 bit for each page in +** the database. As the integrity-check proceeds, for each page used in +** the database the corresponding bit is set. This allows integrity-check to +** detect pages that are used twice and orphaned pages (both of which +** indicate corruption). */ typedef struct IntegrityCk IntegrityCk; struct IntegrityCk { BtShared *pBt; /* The tree being checked out */ Pager *pPager; /* The associated pager. Also accessible by pBt->pPager */ - int *anRef; /* Number of times each page is referenced */ + u8 *aPgRef; /* 1 bit per page in the db (see above) */ Pgno nPage; /* Number of pages in the database */ int mxErr; /* Stop accumulating errors when this reaches zero */ int nErr; /* Number of messages written to zErrMsg so far */ int mallocFailed; /* A memory allocation error has occurred */ StrAccum errMsg; /* Accumulate the error message text here */ Index: src/callback.c ================================================================== --- src/callback.c +++ src/callback.c @@ -220,43 +220,62 @@ /* During the search for the best function definition, this procedure ** is called to test how well the function passed as the first argument ** matches the request for a function with nArg arguments in a system ** that uses encoding enc. The value returned indicates how well the ** request is matched. A higher value indicates a better match. +** +** If nArg is -1 that means to only return a match (non-zero) if p->nArg +** is also -1. In other words, we are searching for a function that +** takes a variable number of arguments. +** +** If nArg is -2 that means that we are searching for any function +** regardless of the number of arguments it uses, so return a positive +** match score for any ** ** The returned value is always between 0 and 6, as follows: ** -** 0: Not a match, or if nArg<0 and the function is has no implementation. -** 1: A variable arguments function that prefers UTF-8 when a UTF-16 -** encoding is requested, or vice versa. -** 2: A variable arguments function that uses UTF-16BE when UTF-16LE is -** requested, or vice versa. -** 3: A variable arguments function using the same text encoding. -** 4: A function with the exact number of arguments requested that -** prefers UTF-8 when a UTF-16 encoding is requested, or vice versa. -** 5: A function with the exact number of arguments requested that -** prefers UTF-16LE when UTF-16BE is requested, or vice versa. -** 6: An exact match. +** 0: Not a match. +** 1: UTF8/16 conversion required and function takes any number of arguments. +** 2: UTF16 byte order change required and function takes any number of args. +** 3: encoding matches and function takes any number of arguments +** 4: UTF8/16 conversion required - argument count matches exactly +** 5: UTF16 byte order conversion required - argument count matches exactly +** 6: Perfect match: encoding and argument count match exactly. ** +** If nArg==(-2) then any function with a non-null xStep or xFunc is +** a perfect match and any function with both xStep and xFunc NULL is +** a non-match. */ -static int matchQuality(FuncDef *p, int nArg, u8 enc){ - int match = 0; - if( p->nArg==-1 || p->nArg==nArg - || (nArg==-1 && (p->xFunc!=0 || p->xStep!=0)) - ){ +#define FUNC_PERFECT_MATCH 6 /* The score for a perfect match */ +static int matchQuality( + FuncDef *p, /* The function we are evaluating for match quality */ + int nArg, /* Desired number of arguments. (-1)==any */ + u8 enc /* Desired text encoding */ +){ + int match; + + /* nArg of -2 is a special case */ + if( nArg==(-2) ) return (p->xFunc==0 && p->xStep==0) ? 0 : FUNC_PERFECT_MATCH; + + /* Wrong number of arguments means "no match" */ + if( p->nArg!=nArg && p->nArg>=0 ) return 0; + + /* Give a better score to a function with a specific number of arguments + ** than to function that accepts any number of arguments. */ + if( p->nArg==nArg ){ + match = 4; + }else{ match = 1; - if( p->nArg==nArg || nArg==-1 ){ - match = 4; - } - if( enc==p->iPrefEnc ){ - match += 2; - } - else if( (enc==SQLITE_UTF16LE && p->iPrefEnc==SQLITE_UTF16BE) || - (enc==SQLITE_UTF16BE && p->iPrefEnc==SQLITE_UTF16LE) ){ - match += 1; - } - } + } + + /* Bonus points if the text encoding matches */ + if( enc==p->iPrefEnc ){ + match += 2; /* Exact encoding match */ + }else if( (enc & p->iPrefEnc & 2)!=0 ){ + match += 1; /* Both are UTF16, but with different byte orders */ + } + return match; } /* ** Search a FuncDefHash for a function with the given name. Return @@ -308,17 +327,16 @@ ** pointer to the FuncDef structure that defines that function, or return ** NULL if the function does not exist. ** ** If the createFlag argument is true, then a new (blank) FuncDef ** structure is created and liked into the "db" structure if a -** no matching function previously existed. When createFlag is true -** and the nArg parameter is -1, then only a function that accepts -** any number of arguments will be returned. +** no matching function previously existed. ** -** If createFlag is false and nArg is -1, then the first valid -** function found is returned. A function is valid if either xFunc -** or xStep is non-zero. +** If nArg is -2, then the first valid function found is returned. A +** function is valid if either xFunc or xStep is non-zero. The nArg==(-2) +** case is used to see if zName is a valid function name for some number +** of arguments. If nArg is -2, then createFlag must be 0. ** ** If createFlag is false, then a function with the required name and ** number of arguments may be returned even if the eTextRep flag does not ** match that requested. */ @@ -326,18 +344,19 @@ sqlite3 *db, /* An open database */ const char *zName, /* Name of the function. Not null-terminated */ int nName, /* Number of characters in the name */ int nArg, /* Number of arguments. -1 means any number */ u8 enc, /* Preferred text encoding */ - int createFlag /* Create new entry if true and does not otherwise exist */ + u8 createFlag /* Create new entry if true and does not otherwise exist */ ){ FuncDef *p; /* Iterator variable */ FuncDef *pBest = 0; /* Best match found so far */ int bestScore = 0; /* Score of best match */ int h; /* Hash value */ - + assert( nArg>=(-2) ); + assert( nArg>=(-1) || createFlag==0 ); assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); h = (sqlite3UpperToLower[(u8)zName[0]] + nName) % ArraySize(db->aFunc.a); /* First search for a match amongst the application-defined functions. */ @@ -379,11 +398,11 @@ /* If the createFlag parameter is true and the search did not reveal an ** exact match for the name, number of arguments and encoding, then add a ** new entry to the hash table and return it. */ - if( createFlag && (bestScore<6 || pBest->nArg!=nArg) && + if( createFlag && bestScorezName = (char *)&pBest[1]; pBest->nArg = (u16)nArg; pBest->iPrefEnc = enc; memcpy(pBest->zName, zName, nName); Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -482,27 +482,59 @@ int op, /* Expression opcode */ Expr *pLeft, /* Left operand */ Expr *pRight, /* Right operand */ const Token *pToken /* Argument token */ ){ - Expr *p = sqlite3ExprAlloc(pParse->db, op, pToken, 1); - sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); + Expr *p; + if( op==TK_AND && pLeft && pRight ){ + /* Take advantage of short-circuit false optimization for AND */ + p = sqlite3ExprAnd(pParse->db, pLeft, pRight); + }else{ + p = sqlite3ExprAlloc(pParse->db, op, pToken, 1); + sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); + } if( p ) { sqlite3ExprCheckHeight(pParse, p->nHeight); } return p; } + +/* +** Return 1 if an expression must be FALSE in all cases and 0 if the +** expression might be true. This is an optimization. If is OK to +** return 0 here even if the expression really is always false (a +** false negative). But it is a bug to return 1 if the expression +** might be true in some rare circumstances (a false positive.) +** +** Note that if the expression is part of conditional for a +** LEFT JOIN, then we cannot determine at compile-time whether or not +** is it true or false, so always return 0. +*/ +static int exprAlwaysFalse(Expr *p){ + int v = 0; + if( ExprHasProperty(p, EP_FromJoin) ) return 0; + if( !sqlite3ExprIsInteger(p, &v) ) return 0; + return v==0; +} /* ** Join two expressions using an AND operator. If either expression is ** NULL, then just return the other expression. +** +** If one side or the other of the AND is known to be false, then instead +** of returning an AND expression, just return a constant expression with +** a value of false. */ Expr *sqlite3ExprAnd(sqlite3 *db, Expr *pLeft, Expr *pRight){ if( pLeft==0 ){ return pRight; }else if( pRight==0 ){ return pLeft; + }else if( exprAlwaysFalse(pLeft) || exprAlwaysFalse(pRight) ){ + sqlite3ExprDelete(db, pLeft); + sqlite3ExprDelete(db, pRight); + return sqlite3ExprAlloc(db, TK_INTEGER, &sqlite3IntTokens[0], 0); }else{ Expr *pNew = sqlite3ExprAlloc(db, TK_AND, 0, 0); sqlite3ExprAttachSubtrees(db, pNew, pLeft, pRight); return pNew; } @@ -3744,11 +3776,11 @@ if( pA->iTable!=pB->iTable || pA->iColumn!=pB->iColumn ) return 2; if( ExprHasProperty(pA, EP_IntValue) ){ if( !ExprHasProperty(pB, EP_IntValue) || pA->u.iValue!=pB->u.iValue ){ return 2; } - }else if( pA->op!=TK_COLUMN && pA->u.zToken ){ + }else if( pA->op!=TK_COLUMN && pA->op!=TK_AGG_COLUMN && pA->u.zToken ){ if( ExprHasProperty(pB, EP_IntValue) || NEVER(pB->u.zToken==0) ) return 2; if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){ return 2; } } @@ -3780,10 +3812,45 @@ if( pA->a[i].sortOrder!=pB->a[i].sortOrder ) return 1; if( sqlite3ExprCompare(pExprA, pExprB) ) return 1; } return 0; } + +/* +** This is the expression callback for sqlite3FunctionUsesOtherSrc(). +** +** Determine if an expression references any table other than one of the +** tables in pWalker->u.pSrcList and abort if it does. +*/ +static int exprUsesOtherSrc(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN ){ + int i; + SrcList *pSrc = pWalker->u.pSrcList; + for(i=0; inSrc; i++){ + if( pExpr->iTable==pSrc->a[i].iCursor ) return WRC_Continue; + } + return WRC_Abort; + }else{ + return WRC_Continue; + } +} + +/* +** Determine if any of the arguments to the pExpr Function references +** any SrcList other than pSrcList. Return true if they do. Return +** false if pExpr has no argument or has only constant arguments or +** only references tables named in pSrcList. +*/ +static int sqlite3FunctionUsesOtherSrc(Expr *pExpr, SrcList *pSrcList){ + Walker w; + assert( pExpr->op==TK_AGG_FUNCTION ); + memset(&w, 0, sizeof(w)); + w.xExprCallback = exprUsesOtherSrc; + w.u.pSrcList = pSrcList; + if( sqlite3WalkExprList(&w, pExpr->x.pList)!=WRC_Continue ) return 1; + return 0; +} /* ** Add a new element to the pAggInfo->aCol[] array. Return the index of ** the new element. Return a negative number if malloc fails. */ @@ -3896,13 +3963,11 @@ } /* end loop over pSrcList */ } return WRC_Prune; } case TK_AGG_FUNCTION: { - /* The pNC->nDepth==0 test causes aggregate functions in subqueries - ** to be ignored */ - if( pNC->nDepth==0 ){ + if( !sqlite3FunctionUsesOtherSrc(pExpr, pSrcList) ){ /* Check to see if pExpr is a duplicate of another aggregate ** function that is already in the pAggInfo structure */ struct AggInfo_func *pItem = pAggInfo->aFunc; for(i=0; inFunc; i++, pItem++){ @@ -3942,19 +4007,11 @@ } } return WRC_Continue; } static int analyzeAggregatesInSelect(Walker *pWalker, Select *pSelect){ - NameContext *pNC = pWalker->u.pNC; - if( pNC->nDepth==0 ){ - pNC->nDepth++; - sqlite3WalkSelect(pWalker, pSelect); - pNC->nDepth--; - return WRC_Prune; - }else{ - return WRC_Continue; - } + return WRC_Continue; } /* ** Analyze the given expression looking for aggregate functions and ** for variables that need to be added to the pParse->aAgg[] array. @@ -3963,10 +4020,11 @@ ** This routine should only be called after the expression has been ** analyzed by sqlite3ResolveExprNames(). */ void sqlite3ExprAnalyzeAggregates(NameContext *pNC, Expr *pExpr){ Walker w; + memset(&w, 0, sizeof(w)); w.xExprCallback = analyzeAggregate; w.xSelectCallback = analyzeAggregatesInSelect; w.u.pNC = pNC; assert( pNC->pSrcList!=0 ); sqlite3WalkExpr(&w, pExpr); Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -1216,11 +1216,10 @@ /* Test all CHECK constraints */ #ifndef SQLITE_OMIT_CHECK if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ ExprList *pCheck = pTab->pCheck; - int i; pParse->ckBase = regData; onError = overrideError!=OE_Default ? overrideError : OE_Abort; for(i=0; inExpr; i++){ int allOk = sqlite3VdbeMakeLabel(v); sqlite3ExprIfTrue(pParse, pCheck->a[i].pExpr, allOk, SQLITE_JUMPIFNULL); Index: src/os_unix.c ================================================================== --- src/os_unix.c +++ src/os_unix.c @@ -163,12 +163,12 @@ #ifndef SQLITE_DEFAULT_FILE_PERMISSIONS # define SQLITE_DEFAULT_FILE_PERMISSIONS 0644 #endif /* - ** Default permissions when creating auto proxy dir - */ +** Default permissions when creating auto proxy dir +*/ #ifndef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS # define SQLITE_DEFAULT_PROXYDIR_PERMISSIONS 0755 #endif /* @@ -510,11 +510,11 @@ return 0; } /* ** Invoke open(). Do so multiple times, until it either succeeds or -** files for some reason other than EINTR. +** fails for some reason other than EINTR. ** ** If the file creation mode "m" is 0 then set it to the default for ** SQLite. The default is SQLITE_DEFAULT_FILE_PERMISSIONS (normally ** 0644) as modified by the system umask. If m is not 0, then ** make the file creation mode be exactly m ignoring the umask. @@ -526,24 +526,33 @@ ** transaction crashes and leaves behind hot journals, then any ** process that is able to write to the database will also be able to ** recover the hot journals. */ static int robust_open(const char *z, int f, mode_t m){ - int rc; + int fd; mode_t m2; mode_t origM = 0; if( m==0 ){ m2 = SQLITE_DEFAULT_FILE_PERMISSIONS; }else{ m2 = m; origM = osUmask(0); } - do{ rc = osOpen(z,f,m2); }while( rc<0 && errno==EINTR ); + do{ +#if defined(O_CLOEXEC) + fd = osOpen(z,f|O_CLOEXEC,m2); +#else + fd = osOpen(z,f,m2); +#endif + }while( fd<0 && errno==EINTR ); if( m ){ osUmask(origM); } - return rc; +#if defined(FD_CLOEXEC) && (!defined(O_CLOEXEC) || O_CLOEXEC==0) + if( fd>=0 ) osFcntl(fd, F_SETFD, osFcntl(fd, F_GETFD, 0) | FD_CLOEXEC); +#endif + return fd; } /* ** Helper functions to obtain and relinquish the global mutex. The ** global mutex is used to protect the unixInodeInfo and @@ -3334,13 +3343,10 @@ for(ii=(int)strlen(zDirname); ii>1 && zDirname[ii]!='/'; ii--); if( ii>0 ){ zDirname[ii] = '\0'; fd = robust_open(zDirname, O_RDONLY|O_BINARY, 0); if( fd>=0 ){ -#ifdef FD_CLOEXEC - osFcntl(fd, F_SETFD, osFcntl(fd, F_GETFD, 0) | FD_CLOEXEC); -#endif OSTRACE(("OPENDIR %-3d %s\n", fd, zDirname)); } } *pFd = fd; return (fd>=0?SQLITE_OK:unixLogError(SQLITE_CANTOPEN_BKPT, "open", zDirname)); @@ -3419,11 +3425,11 @@ /* If the user has configured a chunk-size for this file, truncate the ** file so that it consists of an integer number of chunks (i.e. the ** actual file size after the operation may be larger than the requested ** size). */ - if( pFile->szChunk ){ + if( pFile->szChunk>0 ){ nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk; } rc = robust_ftruncate(pFile->h, (off_t)nByte); if( rc ){ @@ -5181,14 +5187,10 @@ else{ p->openFlags = openFlags; } #endif -#ifdef FD_CLOEXEC - osFcntl(fd, F_SETFD, osFcntl(fd, F_GETFD, 0) | FD_CLOEXEC); -#endif - noLock = eType!=SQLITE_OPEN_MAIN_DB; #if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE if( fstatfs(fd, &fsInfo) == -1 ){ Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -531,11 +531,11 @@ assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); zId = pExpr->u.zToken; nId = sqlite3Strlen30(zId); pDef = sqlite3FindFunction(pParse->db, zId, nId, n, enc, 0); if( pDef==0 ){ - pDef = sqlite3FindFunction(pParse->db, zId, nId, -1, enc, 0); + pDef = sqlite3FindFunction(pParse->db, zId, nId, -2, enc, 0); if( pDef==0 ){ no_such_func = 1; }else{ wrong_num_args = 1; } Index: src/rowset.c ================================================================== --- src/rowset.c +++ src/rowset.c @@ -74,10 +74,15 @@ #define ROWSET_ENTRY_PER_CHUNK \ ((ROWSET_ALLOCATION_SIZE-8)/sizeof(struct RowSetEntry)) /* ** Each entry in a RowSet is an instance of the following object. +** +** This same object is reused to store a linked list of trees of RowSetEntry +** objects. In that alternative use, pRight points to the next entry +** in the list, pLeft points to the tree, and v is unused. The +** RowSet.pForest value points to the head of this forest list. */ struct RowSetEntry { i64 v; /* ROWID value for this entry */ struct RowSetEntry *pRight; /* Right subtree (larger entries) or list */ struct RowSetEntry *pLeft; /* Left subtree (smaller entries) */ @@ -103,16 +108,22 @@ struct RowSetChunk *pChunk; /* List of all chunk allocations */ sqlite3 *db; /* The database connection */ struct RowSetEntry *pEntry; /* List of entries using pRight */ struct RowSetEntry *pLast; /* Last entry on the pEntry list */ struct RowSetEntry *pFresh; /* Source of new entry objects */ - struct RowSetEntry *pTree; /* Binary tree of entries */ + struct RowSetEntry *pForest; /* List of binary trees of entries */ u16 nFresh; /* Number of objects on pFresh */ - u8 isSorted; /* True if pEntry is sorted */ + u8 rsFlags; /* Various flags */ u8 iBatch; /* Current insert batch */ }; +/* +** Allowed values for RowSet.rsFlags +*/ +#define ROWSET_SORTED 0x01 /* True if RowSet.pEntry is sorted */ +#define ROWSET_NEXT 0x02 /* True if sqlite3RowSetNext() has been called */ + /* ** Turn bulk memory into a RowSet object. N bytes of memory ** are available at pSpace. The db pointer is used as a memory context ** for any subsequent allocations that need to occur. ** Return a pointer to the new RowSet object. @@ -129,14 +140,14 @@ p = pSpace; p->pChunk = 0; p->db = db; p->pEntry = 0; p->pLast = 0; - p->pTree = 0; + p->pForest = 0; p->pFresh = (struct RowSetEntry*)(ROUND8(sizeof(*p)) + (char*)p); p->nFresh = (u16)((N - ROUND8(sizeof(*p)))/sizeof(struct RowSetEntry)); - p->isSorted = 1; + p->rsFlags = ROWSET_SORTED; p->iBatch = 0; return p; } /* @@ -152,12 +163,37 @@ } p->pChunk = 0; p->nFresh = 0; p->pEntry = 0; p->pLast = 0; - p->pTree = 0; - p->isSorted = 1; + p->pForest = 0; + p->rsFlags = ROWSET_SORTED; +} + +/* +** Allocate a new RowSetEntry object that is associated with the +** given RowSet. Return a pointer to the new and completely uninitialized +** objected. +** +** In an OOM situation, the RowSet.db->mallocFailed flag is set and this +** routine returns NULL. +*/ +static struct RowSetEntry *rowSetEntryAlloc(RowSet *p){ + assert( p!=0 ); + if( p->nFresh==0 ){ + struct RowSetChunk *pNew; + pNew = sqlite3DbMallocRaw(p->db, sizeof(*pNew)); + if( pNew==0 ){ + return 0; + } + pNew->pNextChunk = p->pChunk; + p->pChunk = pNew; + p->pFresh = pNew->aEntry; + p->nFresh = ROWSET_ENTRY_PER_CHUNK; + } + p->nFresh--; + return p->pFresh++; } /* ** Insert a new value into a RowSet. ** @@ -165,34 +201,25 @@ ** memory allocation fails. */ void sqlite3RowSetInsert(RowSet *p, i64 rowid){ struct RowSetEntry *pEntry; /* The new entry */ struct RowSetEntry *pLast; /* The last prior entry */ - assert( p!=0 ); - if( p->nFresh==0 ){ - struct RowSetChunk *pNew; - pNew = sqlite3DbMallocRaw(p->db, sizeof(*pNew)); - if( pNew==0 ){ - return; - } - pNew->pNextChunk = p->pChunk; - p->pChunk = pNew; - p->pFresh = pNew->aEntry; - p->nFresh = ROWSET_ENTRY_PER_CHUNK; - } - pEntry = p->pFresh++; - p->nFresh--; + + /* This routine is never called after sqlite3RowSetNext() */ + assert( p!=0 && (p->rsFlags & ROWSET_NEXT)==0 ); + + pEntry = rowSetEntryAlloc(p); + if( pEntry==0 ) return; pEntry->v = rowid; pEntry->pRight = 0; pLast = p->pLast; if( pLast ){ - if( p->isSorted && rowid<=pLast->v ){ - p->isSorted = 0; + if( (p->rsFlags & ROWSET_SORTED)!=0 && rowid<=pLast->v ){ + p->rsFlags &= ~ROWSET_SORTED; } pLast->pRight = pEntry; }else{ - assert( p->pEntry==0 ); /* Fires if INSERT after SMALLEST */ p->pEntry = pEntry; } p->pLast = pEntry; } @@ -200,11 +227,11 @@ ** Merge two lists of RowSetEntry objects. Remove duplicates. ** ** The input lists are connected via pRight pointers and are ** assumed to each already be in sorted order. */ -static struct RowSetEntry *rowSetMerge( +static struct RowSetEntry *rowSetEntryMerge( struct RowSetEntry *pA, /* First sorted list to be merged */ struct RowSetEntry *pB /* Second sorted list to be merged */ ){ struct RowSetEntry head; struct RowSetEntry *pTail; @@ -234,36 +261,33 @@ } return head.pRight; } /* -** Sort all elements on the pEntry list of the RowSet into ascending order. +** Sort all elements on the list of RowSetEntry objects into order of +** increasing v. */ -static void rowSetSort(RowSet *p){ +static struct RowSetEntry *rowSetEntrySort(struct RowSetEntry *pIn){ unsigned int i; - struct RowSetEntry *pEntry; - struct RowSetEntry *aBucket[40]; + struct RowSetEntry *pNext, *aBucket[40]; - assert( p->isSorted==0 ); memset(aBucket, 0, sizeof(aBucket)); - while( p->pEntry ){ - pEntry = p->pEntry; - p->pEntry = pEntry->pRight; - pEntry->pRight = 0; + while( pIn ){ + pNext = pIn->pRight; + pIn->pRight = 0; for(i=0; aBucket[i]; i++){ - pEntry = rowSetMerge(aBucket[i], pEntry); + pIn = rowSetEntryMerge(aBucket[i], pIn); aBucket[i] = 0; } - aBucket[i] = pEntry; + aBucket[i] = pIn; + pIn = pNext; } - pEntry = 0; + pIn = 0; for(i=0; ipEntry = pEntry; - p->pLast = 0; - p->isSorted = 1; + return pIn; } /* ** The input, pIn, is a binary tree (or subtree) of RowSetEntry objects. @@ -353,24 +377,41 @@ } return p; } /* -** Convert the list in p->pEntry into a sorted list if it is not -** sorted already. If there is a binary tree on p->pTree, then -** convert it into a list too and merge it into the p->pEntry list. +** Take all the entries on p->pEntry and on the trees in p->pForest and +** sort them all together into one big ordered list on p->pEntry. +** +** This routine should only be called once in the life of a RowSet. */ static void rowSetToList(RowSet *p){ - if( !p->isSorted ){ - rowSetSort(p); - } - if( p->pTree ){ - struct RowSetEntry *pHead, *pTail; - rowSetTreeToList(p->pTree, &pHead, &pTail); - p->pTree = 0; - p->pEntry = rowSetMerge(p->pEntry, pHead); - } + + /* This routine is called only once */ + assert( p!=0 && (p->rsFlags & ROWSET_NEXT)==0 ); + + if( (p->rsFlags & ROWSET_SORTED)==0 ){ + p->pEntry = rowSetEntrySort(p->pEntry); + } + + /* While this module could theoretically support it, sqlite3RowSetNext() + ** is never called after sqlite3RowSetText() for the same RowSet. So + ** there is never a forest to deal with. Should this change, simply + ** remove the assert() and the #if 0. */ + assert( p->pForest==0 ); +#if 0 + while( p->pForest ){ + struct RowSetEntry *pTree = p->pForest->pLeft; + if( pTree ){ + struct RowSetEntry *pHead, *pTail; + rowSetTreeToList(pTree, &pHead, &pTail); + p->pEntry = rowSetEntryMerge(p->pEntry, pHead); + } + p->pForest = p->pForest->pRight; + } +#endif + p->rsFlags |= ROWSET_NEXT; /* Verify this routine is never called again */ } /* ** Extract the smallest element from the RowSet. ** Write the element into *pRowid. Return 1 on success. Return @@ -378,11 +419,16 @@ ** ** After this routine has been called, the sqlite3RowSetInsert() ** routine may not be called again. */ int sqlite3RowSetNext(RowSet *p, i64 *pRowid){ - rowSetToList(p); + assert( p!=0 ); + + /* Merge the forest into a single sorted list on first call */ + if( (p->rsFlags & ROWSET_NEXT)==0 ) rowSetToList(p); + + /* Return the next entry on the list */ if( p->pEntry ){ *pRowid = p->pEntry->v; p->pEntry = p->pEntry->pRight; if( p->pEntry==0 ){ sqlite3RowSetClear(p); @@ -394,29 +440,69 @@ } /* ** Check to see if element iRowid was inserted into the the rowset as ** part of any insert batch prior to iBatch. Return 1 or 0. +** +** If this is the first test of a new batch and if there exist entires +** on pRowSet->pEntry, then sort those entires into the forest at +** pRowSet->pForest so that they can be tested. */ int sqlite3RowSetTest(RowSet *pRowSet, u8 iBatch, sqlite3_int64 iRowid){ - struct RowSetEntry *p; + struct RowSetEntry *p, *pTree; + + /* This routine is never called after sqlite3RowSetNext() */ + assert( pRowSet!=0 && (pRowSet->rsFlags & ROWSET_NEXT)==0 ); + + /* Sort entries into the forest on the first test of a new batch + */ if( iBatch!=pRowSet->iBatch ){ - if( pRowSet->pEntry ){ - rowSetToList(pRowSet); - pRowSet->pTree = rowSetListToTree(pRowSet->pEntry); + p = pRowSet->pEntry; + if( p ){ + struct RowSetEntry **ppPrevTree = &pRowSet->pForest; + if( (pRowSet->rsFlags & ROWSET_SORTED)==0 ){ + p = rowSetEntrySort(p); + } + for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){ + ppPrevTree = &pTree->pRight; + if( pTree->pLeft==0 ){ + pTree->pLeft = rowSetListToTree(p); + break; + }else{ + struct RowSetEntry *pAux, *pTail; + rowSetTreeToList(pTree->pLeft, &pAux, &pTail); + pTree->pLeft = 0; + p = rowSetEntryMerge(pAux, p); + } + } + if( pTree==0 ){ + *ppPrevTree = pTree = rowSetEntryAlloc(pRowSet); + if( pTree ){ + pTree->v = 0; + pTree->pRight = 0; + pTree->pLeft = rowSetListToTree(p); + } + } pRowSet->pEntry = 0; pRowSet->pLast = 0; + pRowSet->rsFlags |= ROWSET_SORTED; } pRowSet->iBatch = iBatch; } - p = pRowSet->pTree; - while( p ){ - if( p->vpRight; - }else if( p->v>iRowid ){ - p = p->pLeft; - }else{ - return 1; + + /* Test to see if the iRowid value appears anywhere in the forest. + ** Return 1 if it does and 0 if not. + */ + for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){ + p = pTree->pLeft; + while( p ){ + if( p->vpRight; + }else if( p->v>iRowid ){ + p = p->pLeft; + }else{ + return 1; + } } } return 0; } Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -1256,13 +1256,21 @@ int nCol; /* Number of columns in the result set */ Expr *p; /* Expression for a single result column */ char *zName; /* Column name */ int nName; /* Size of name in zName[] */ - *pnCol = nCol = pEList ? pEList->nExpr : 0; - aCol = *paCol = sqlite3DbMallocZero(db, sizeof(aCol[0])*nCol); - if( aCol==0 ) return SQLITE_NOMEM; + if( pEList ){ + nCol = pEList->nExpr; + aCol = sqlite3DbMallocZero(db, sizeof(aCol[0])*nCol); + testcase( aCol==0 ); + }else{ + nCol = 0; + aCol = 0; + } + *pnCol = nCol; + *paCol = aCol; + for(i=0, pCol=aCol; ia[i].pExpr; assert( p->pRight==0 || ExprHasProperty(p->pRight, EP_IntValue) @@ -2841,11 +2849,12 @@ /***** If we reach this point, flattening is permitted. *****/ /* Authorize the subquery */ pParse->zAuthContext = pSubitem->zName; - sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0); + TESTONLY(i =) sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0); + testcase( i==SQLITE_DENY ); pParse->zAuthContext = zSavedAuthContext; /* If the sub-query is a compound SELECT statement, then (by restrictions ** 17 and 18 above) it must be a UNION ALL and the parent query must ** be of the form: Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -419,10 +419,11 @@ sqlite3 *db; /* The database */ int echoOn; /* True to echo input commands */ int statsOn; /* True to display memory stats before each finalize */ int cnt; /* Number of records displayed so far */ FILE *out; /* Write results here */ + FILE *traceOut; /* Output for sqlite3_trace() */ int nErr; /* Number of errors seen */ int mode; /* An output mode setting */ int writableSchema; /* True if PRAGMA writable_schema=ON */ int showHeader; /* True to show column names in List or Column mode */ char *zDestTable; /* Name of destination table when MODE_Insert */ @@ -1307,10 +1308,11 @@ /* Always quote the table name, even if it appears to be pure ascii, ** in case it is a keyword. Ex: INSERT INTO "table" ... */ zTmp = appendText(zTmp, zTable, '"'); if( zTmp ){ zSelect = appendText(zSelect, zTmp, '\''); + free(zTmp); } zSelect = appendText(zSelect, " || ' VALUES(' || ", 0); rc = sqlite3_step(pTableInfo); while( rc==SQLITE_ROW ){ const char *zText = (const char *)sqlite3_column_text(pTableInfo, 1); @@ -1335,11 +1337,11 @@ rc = run_table_dump_query(p, zSelect, zPrepStmt); if( rc==SQLITE_CORRUPT ){ zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0); run_table_dump_query(p, zSelect, 0); } - if( zSelect ) free(zSelect); + free(zSelect); } return 0; } /* @@ -1365,11 +1367,11 @@ sqlite3_free(zErr); zErr = 0; } zQ2 = malloc( len+100 ); if( zQ2==0 ) return rc; - sqlite3_snprintf(sizeof(zQ2), zQ2, "%s ORDER BY rowid DESC", zQuery); + sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery); rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); if( rc ){ fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); }else{ rc = SQLITE_CORRUPT; @@ -1431,10 +1433,11 @@ ".stats ON|OFF Turn stats on or off\n" ".tables ?TABLE? List names of tables\n" " If TABLE specified, only list tables matching\n" " LIKE pattern TABLE.\n" ".timeout MS Try opening locked tables for MS milliseconds\n" + ".trace FILE|off Output each SQL statement as it is run\n" ".vfsname ?AUX? Print the name of the VFS stack\n" ".width NUM1 NUM2 ... Set column widths for \"column\" mode\n" ; static char zTimerHelp[] = @@ -1519,10 +1522,56 @@ }else if( strcmp(zArg,"yes")==0 ){ val = 1; } return val; } + +/* +** Close an output file, assuming it is not stderr or stdout +*/ +static void output_file_close(FILE *f){ + if( f && f!=stdout && f!=stderr ) fclose(f); +} + +/* +** Try to open an output file. The names "stdout" and "stderr" are +** recognized and do the right thing. NULL is returned if the output +** filename is "off". +*/ +static FILE *output_file_open(const char *zFile){ + FILE *f; + if( strcmp(zFile,"stdout")==0 ){ + f = stdout; + }else if( strcmp(zFile, "stderr")==0 ){ + f = stderr; + }else if( strcmp(zFile, "off")==0 ){ + f = 0; + }else{ + f = fopen(zFile, "wb"); + if( f==0 ){ + fprintf(stderr, "Error: cannot open \"%s\"\n", zFile); + } + } + return f; +} + +/* +** A routine for handling output from sqlite3_trace(). +*/ +static void sql_trace_callback(void *pArg, const char *z){ + FILE *f = (FILE*)pArg; + if( f ) fprintf(f, "%s\n", z); +} + +/* +** A no-op routine that runs with the ".breakpoint" doc-command. This is +** a useful spot to set a debugger breakpoint. +*/ +static void test_breakpoint(void){ + static int nCall = 0; + nCall++; +} /* ** If an input line begins with "." then invoke this routine to ** process that line. ** @@ -1598,10 +1647,17 @@ }else if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 && nArg>1 && nArg<3 ){ bail_on_error = booleanValue(azArg[1]); }else + + /* The undocumented ".breakpoint" command causes a call to the no-op + ** routine named test_breakpoint(). + */ + if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ + test_breakpoint(); + }else if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 && nArg==1 ){ struct callback_data data; char *zErrMsg = 0; open_db(p); @@ -1930,26 +1986,12 @@ }else #endif if( c=='l' && strncmp(azArg[0], "log", n)==0 && nArg>=2 ){ const char *zFile = azArg[1]; - if( p->pLog && p->pLog!=stdout && p->pLog!=stderr ){ - fclose(p->pLog); - p->pLog = 0; - } - if( strcmp(zFile,"stdout")==0 ){ - p->pLog = stdout; - }else if( strcmp(zFile, "stderr")==0 ){ - p->pLog = stderr; - }else if( strcmp(zFile, "off")==0 ){ - p->pLog = 0; - }else{ - p->pLog = fopen(zFile, "w"); - if( p->pLog==0 ){ - fprintf(stderr, "Error: cannot open \"%s\"\n", zFile); - } - } + output_file_close(p->pLog); + p->pLog = output_file_open(zFile); }else if( c=='m' && strncmp(azArg[0], "mode", n)==0 && nArg==2 ){ int n2 = strlen30(azArg[1]); if( (n2==4 && strncmp(azArg[1],"line",n2)==0) @@ -1998,37 +2040,35 @@ sqlite3_snprintf(sizeof(p->nullvalue), p->nullvalue, "%.*s", (int)ArraySize(p->nullvalue)-1, azArg[1]); }else if( c=='o' && strncmp(azArg[0], "output", n)==0 && nArg==2 ){ - if( p->out!=stdout ){ - if( p->outfile[0]=='|' ){ - pclose(p->out); - }else{ - fclose(p->out); - } - } - if( strcmp(azArg[1],"stdout")==0 ){ - p->out = stdout; - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "stdout"); - }else if( azArg[1][0]=='|' ){ + if( p->outfile[0]=='|' ){ + pclose(p->out); + }else{ + output_file_close(p->out); + } + p->outfile[0] = 0; + if( azArg[1][0]=='|' ){ p->out = popen(&azArg[1][1], "w"); if( p->out==0 ){ fprintf(stderr,"Error: cannot open pipe \"%s\"\n", &azArg[1][1]); p->out = stdout; rc = 1; }else{ sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", azArg[1]); } }else{ - p->out = fopen(azArg[1], "wb"); + p->out = output_file_open(azArg[1]); if( p->out==0 ){ - fprintf(stderr,"Error: cannot write to \"%s\"\n", azArg[1]); + if( strcmp(azArg[1],"off")!=0 ){ + fprintf(stderr,"Error: cannot write to \"%s\"\n", azArg[1]); + } p->out = stdout; rc = 1; } else { - sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", azArg[1]); + sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", azArg[1]); } } }else if( c=='p' && strncmp(azArg[0], "prompt", n)==0 && (nArg==2 || nArg==3)){ @@ -2394,10 +2434,22 @@ && nArg==2 ){ enableTimer = booleanValue(azArg[1]); }else + if( c=='t' && strncmp(azArg[0], "trace", n)==0 && nArg>1 ){ + output_file_close(p->traceOut); + p->traceOut = output_file_open(azArg[1]); +#ifndef SQLITE_OMIT_TRACE + if( p->traceOut==0 ){ + sqlite3_trace(p->db, 0, 0); + }else{ + sqlite3_trace(p->db, sql_trace_callback, p->traceOut); + } +#endif + }else + if( c=='v' && strncmp(azArg[0], "version", n)==0 ){ printf("SQLite %s %s\n" /*extra-version-info*/, sqlite3_libversion(), sqlite3_sourceid()); }else @@ -2605,16 +2657,15 @@ return errCnt; } /* ** Return a pathname which is the user's home directory. A -** 0 return indicates an error of some kind. Space to hold the -** resulting string is obtained from malloc(). The calling -** function should free the result. +** 0 return indicates an error of some kind. */ static char *find_home_dir(void){ - char *home_dir = NULL; + static char *home_dir = NULL; + if( home_dir ) return home_dir; #if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(_WIN32_WCE) && !defined(__RTP__) && !defined(_WRS_KERNEL) struct passwd *pwent; uid_t uid = getuid(); if( (pwent=getpwuid(uid)) != NULL) { @@ -2623,11 +2674,11 @@ #endif #if defined(_WIN32_WCE) /* Windows CE (arm-wince-mingw32ce-gcc) does not provide getenv() */ - home_dir = strdup("/"); + home_dir = "/"; #else #if defined(_WIN32) || defined(WIN32) || defined(__OS2__) if (!home_dir) { home_dir = getenv("USERPROFILE"); @@ -2679,11 +2730,10 @@ ){ char *home_dir = NULL; const char *sqliterc = sqliterc_override; char *zBuf = 0; FILE *in = NULL; - int nBuf; int rc = 0; if (sqliterc == NULL) { home_dir = find_home_dir(); if( home_dir==0 ){ @@ -2690,29 +2740,22 @@ #if !defined(__RTP__) && !defined(_WRS_KERNEL) fprintf(stderr,"%s: Error: cannot locate your home directory\n", Argv0); #endif return 1; } - nBuf = strlen30(home_dir) + 16; - zBuf = malloc( nBuf ); - if( zBuf==0 ){ - fprintf(stderr,"%s: Error: out of memory\n",Argv0); - return 1; - } - sqlite3_snprintf(nBuf, zBuf,"%s/.sqliterc",home_dir); - free(home_dir); - sqliterc = (const char*)zBuf; + zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir); + sqliterc = zBuf; } in = fopen(sqliterc,"rb"); if( in ){ if( stdin_is_interactive ){ fprintf(stderr,"-- Loading resources from %s\n",sqliterc); } rc = process_input(p,in); fclose(in); } - free(zBuf); + sqlite3_free(zBuf); return rc; } /* ** Show available command line options @@ -3049,11 +3092,10 @@ if( zHistory ){ stifle_history(100); write_history(zHistory); free(zHistory); } - free(zHome); }else{ rc = process_input(&data, stdin); } } set_table_name(&data, 0); Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -1540,11 +1540,11 @@ ** database connection is opened. By default, URI handling is globally ** disabled. The default value may be changed by compiling with the ** [SQLITE_USE_URI] symbol defined. ** ** [[SQLITE_CONFIG_PCACHE]] [[SQLITE_CONFIG_GETPCACHE]] -**
SQLITE_CONFIG_PCACHE and SQLITE_CONFNIG_GETPCACHE +**
SQLITE_CONFIG_PCACHE and SQLITE_CONFIG_GETPCACHE **
These options are obsolete and should not be used by new code. ** They are retained for backwards compatibility but are now no-ops. ** */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -2017,11 +2017,10 @@ int nRef; /* Number of names resolved by this context */ int nErr; /* Number of errors encountered while resolving names */ u8 allowAgg; /* Aggregate functions allowed here */ u8 hasAgg; /* True if aggregates are seen */ u8 isCheck; /* True if resolving names in a CHECK constraint */ - int nDepth; /* Depth of subquery recursion. 1 for no recursion */ AggInfo *pAggInfo; /* Information about aggregates at this level */ NameContext *pNext; /* Next outer name context. NULL for outermost */ }; /* @@ -2487,10 +2486,11 @@ int (*xSelectCallback)(Walker*,Select*); /* Callback for SELECTs */ Parse *pParse; /* Parser context. */ union { /* Extra data for callback */ NameContext *pNC; /* Naming context */ int i; /* Integer value */ + SrcList *pSrcList; /* FROM clause */ } u; }; /* Forward declarations */ int sqlite3WalkExpr(Walker*, Expr*); @@ -2855,11 +2855,11 @@ ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int); SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int); IdList *sqlite3IdListDup(sqlite3*,IdList*); Select *sqlite3SelectDup(sqlite3*,Select*,int); void sqlite3FuncDefInsert(FuncDefHash*, FuncDef*); -FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,int,u8,int); +FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,int,u8,u8); void sqlite3RegisterBuiltinFunctions(sqlite3*); void sqlite3RegisterDateTimeFunctions(void); void sqlite3RegisterGlobalFunctions(void); int sqlite3SafetyCheckOk(sqlite3*); int sqlite3SafetyCheckSickOrOk(sqlite3*); Index: src/test_config.c ================================================================== --- src/test_config.c +++ src/test_config.c @@ -423,10 +423,16 @@ #ifdef SQLITE_ENABLE_RTREE Tcl_SetVar2(interp, "sqlite_options", "rtree", "1", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "rtree", "0", TCL_GLOBAL_ONLY); #endif + +#ifdef SQLITE_RTREE_INT_ONLY + Tcl_SetVar2(interp, "sqlite_options", "rtree_int_only", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "rtree_int_only", "0", TCL_GLOBAL_ONLY); +#endif #ifdef SQLITE_OMIT_SCHEMA_PRAGMAS Tcl_SetVar2(interp, "sqlite_options", "schema_pragmas", "0", TCL_GLOBAL_ONLY); #else Tcl_SetVar2(interp, "sqlite_options", "schema_pragmas", "1", TCL_GLOBAL_ONLY); Index: src/test_fuzzer.c ================================================================== --- src/test_fuzzer.c +++ src/test_fuzzer.c @@ -1125,12 +1125,11 @@ return SQLITE_OK; } /* -** A virtual table module that provides read-only access to a -** Tcl global variable namespace. +** A virtual table module that implements the "fuzzer". */ static sqlite3_module fuzzerModule = { 0, /* iVersion */ fuzzerConnect, fuzzerConnect, Index: src/test_multiplex.c ================================================================== --- src/test_multiplex.c +++ src/test_multiplex.c @@ -327,10 +327,11 @@ /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are ** part of a database journal are named db.401, db.402, and so on. A ** database may therefore not grow to larger than 400 chunks. Attempting ** to open chunk 401 indicates the database is full. */ if( iChunk>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){ + sqlite3_log(SQLITE_FULL, "multiplexed chunk overflow: %s", pGroup->zName); *rc = SQLITE_FULL; return 0; } #endif @@ -345,11 +346,17 @@ }else if( pGroup->aReal[iChunk].z==0 ){ return 0; }else{ *rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[iChunk].z, SQLITE_ACCESS_EXISTS, &bExists); - if( *rc || !bExists ) return 0; + if( *rc || !bExists ){ + if( *rc ){ + sqlite3_log(*rc, "multiplexor.xAccess failure on %s", + pGroup->aReal[iChunk].z); + } + return 0; + } flags &= ~SQLITE_OPEN_CREATE; } pSubOpen = sqlite3_malloc( pOrigVfs->szOsFile ); if( pSubOpen==0 ){ *rc = SQLITE_IOERR_NOMEM; @@ -357,10 +364,12 @@ } pGroup->aReal[iChunk].p = pSubOpen; *rc = pOrigVfs->xOpen(pOrigVfs, pGroup->aReal[iChunk].z, pSubOpen, flags, pOutFlags); if( (*rc)!=SQLITE_OK ){ + sqlite3_log(*rc, "multiplexor.xOpen failure on %s", + pGroup->aReal[iChunk].z); sqlite3_free(pSubOpen); pGroup->aReal[iChunk].p = 0; return 0; } } Index: src/test_quota.c ================================================================== --- src/test_quota.c +++ src/test_quota.c @@ -118,10 +118,13 @@ */ struct quota_FILE { FILE *f; /* Open stdio file pointer */ sqlite3_int64 iOfst; /* Current offset into the file */ quotaFile *pFile; /* The file record in the quota system */ +#if SQLITE_OS_WIN + char *zMbcsName; /* Full MBCS pathname of the file */ +#endif }; /************************* Global Variables **********************************/ /* @@ -977,11 +980,11 @@ ** Open a potentially quotaed file for I/O. */ quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){ quota_FILE *p = 0; char *zFull = 0; - char *zFullTranslated; + char *zFullTranslated = 0; int rc; quotaGroup *pGroup; quotaFile *pFile; zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); @@ -993,11 +996,10 @@ if( p==0 ) goto quota_fopen_error; memset(p, 0, sizeof(*p)); zFullTranslated = quota_utf8_to_mbcs(zFull); if( zFullTranslated==0 ) goto quota_fopen_error; p->f = fopen(zFullTranslated, zMode); - quota_mbcs_free(zFullTranslated); if( p->f==0 ) goto quota_fopen_error; quotaEnter(); pGroup = quotaGroupFind(zFull); if( pGroup ){ pFile = quotaFindFile(pGroup, zFull, 1); @@ -1008,13 +1010,17 @@ pFile->nRef++; p->pFile = pFile; } quotaLeave(); sqlite3_free(zFull); +#if SQLITE_OS_WIN + p->zMbcsName = zFullTranslated; +#endif return p; quota_fopen_error: + quota_mbcs_free(zFullTranslated); sqlite3_free(zFull); if( p && p->f ) fclose(p->f); sqlite3_free(p); return 0; } @@ -1043,10 +1049,11 @@ ){ sqlite3_int64 iOfst; sqlite3_int64 iEnd; sqlite3_int64 szNew; quotaFile *pFile; + size_t rc; iOfst = ftell(p->f); iEnd = iOfst + size*nmemb; pFile = p->pFile; if( pFile && pFile->iSizeiSize = szNew; pFile->iSize = iEnd; quotaLeave(); + }else{ + pFile = 0; + } + rc = fwrite(pBuf, size, nmemb, p->f); + + /* If the write was incomplete, adjust the file size and group size + ** downward */ + if( rc=0 ? rc : 0; + sqlite3_int64 iNewEnd = iOfst + size*nWritten; + if( iNewEndpGroup->iSize += iNewEnd - pFile->iSize; + pFile->iSize = iNewEnd; + quotaLeave(); } - return fwrite(pBuf, size, nmemb, p->f); + return rc; } /* ** Close an open quota_FILE stream. */ @@ -1091,10 +1113,13 @@ } quotaGroupDeref(pGroup); } quotaLeave(); } +#if SQLITE_OS_WIN + quota_mbcs_free(p->zMbcsName); +#endif sqlite3_free(p); return rc; } /* @@ -1132,10 +1157,87 @@ ** Tell the current location of a quota_FILE stream. */ long sqlite3_quota_ftell(quota_FILE *p){ return ftell(p->f); } + +/* +** Truncate a file to szNew bytes. +*/ +int sqlite3_quota_ftruncate(quota_FILE *p, sqlite3_int64 szNew){ + quotaFile *pFile = p->pFile; + int rc; + if( (pFile = p->pFile)!=0 && pFile->iSizeiSizepGroup; + quotaEnter(); + pGroup->iSize += szNew - pFile->iSize; + quotaLeave(); + } +#if SQLITE_OS_UNIX + rc = ftruncate(fileno(p->f), szNew); +#endif +#if SQLITE_OS_WIN + rc = _chsize_s(_fileno(p->f), szNew); +#endif + if( pFile && rc==0 ){ + quotaGroup *pGroup = pFile->pGroup; + quotaEnter(); + pGroup->iSize += szNew - pFile->iSize; + pFile->iSize = szNew; + quotaLeave(); + } + return rc; +} + +/* +** Determine the time that the given file was last modified, in +** seconds size 1970. Write the result into *pTime. Return 0 on +** success and non-zero on any kind of error. +*/ +int sqlite3_quota_file_mtime(quota_FILE *p, time_t *pTime){ + int rc; +#if SQLITE_OS_UNIX + struct stat buf; + rc = fstat(fileno(p->f), &buf); +#endif +#if SQLITE_OS_WIN + struct _stati64 buf; + rc = _stati64(p->zMbcsName, &buf); +#endif + if( rc==0 ) *pTime = buf.st_mtime; + return rc; +} + +/* +** Return the true size of the file, as reported by the operating +** system. +*/ +sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE *p){ + int rc; +#if SQLITE_OS_UNIX + struct stat buf; + rc = fstat(fileno(p->f), &buf); +#endif +#if SQLITE_OS_WIN + struct _stati64 buf; + rc = _stati64(p->zMbcsName, &buf); +#endif + return rc==0 ? buf.st_size : -1; +} + +/* +** Return the size of the file, as it is known to the quota subsystem. +*/ +sqlite3_int64 sqlite3_quota_file_size(quota_FILE *p){ + return p->pFile ? p->pFile->iSize : -1; +} /* ** Remove a managed file. Update quotas accordingly. */ int sqlite3_quota_remove(const char *zFilename){ @@ -1653,10 +1755,100 @@ p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); x = sqlite3_quota_ftell(p); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); return TCL_OK; } + +/* +** tclcmd: sqlite3_quota_ftruncate HANDLE SIZE +*/ +static int test_quota_ftruncate( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + sqlite3_int64 x; + Tcl_WideInt w; + int rc; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + if( Tcl_GetWideIntFromObj(interp, objv[2], &w) ) return TCL_ERROR; + x = (sqlite3_int64)w; + rc = sqlite3_quota_ftruncate(p, x); + Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_file_size HANDLE +*/ +static int test_quota_file_size( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + sqlite3_int64 x; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + x = sqlite3_quota_file_size(p); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_file_truesize HANDLE +*/ +static int test_quota_file_truesize( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + sqlite3_int64 x; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + x = sqlite3_quota_file_truesize(p); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_file_mtime HANDLE +*/ +static int test_quota_file_mtime( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + time_t t; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + t = 0; + sqlite3_quota_file_mtime(p, &t); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(t)); + return TCL_OK; +} + /* ** tclcmd: sqlite3_quota_remove FILENAME */ static int test_quota_remove( @@ -1711,25 +1903,29 @@ int Sqlitequota_Init(Tcl_Interp *interp){ static struct { char *zName; Tcl_ObjCmdProc *xProc; } aCmd[] = { - { "sqlite3_quota_initialize", test_quota_initialize }, - { "sqlite3_quota_shutdown", test_quota_shutdown }, - { "sqlite3_quota_set", test_quota_set }, - { "sqlite3_quota_file", test_quota_file }, - { "sqlite3_quota_dump", test_quota_dump }, - { "sqlite3_quota_fopen", test_quota_fopen }, - { "sqlite3_quota_fread", test_quota_fread }, - { "sqlite3_quota_fwrite", test_quota_fwrite }, - { "sqlite3_quota_fclose", test_quota_fclose }, - { "sqlite3_quota_fflush", test_quota_fflush }, - { "sqlite3_quota_fseek", test_quota_fseek }, - { "sqlite3_quota_rewind", test_quota_rewind }, - { "sqlite3_quota_ftell", test_quota_ftell }, - { "sqlite3_quota_remove", test_quota_remove }, - { "sqlite3_quota_glob", test_quota_glob }, + { "sqlite3_quota_initialize", test_quota_initialize }, + { "sqlite3_quota_shutdown", test_quota_shutdown }, + { "sqlite3_quota_set", test_quota_set }, + { "sqlite3_quota_file", test_quota_file }, + { "sqlite3_quota_dump", test_quota_dump }, + { "sqlite3_quota_fopen", test_quota_fopen }, + { "sqlite3_quota_fread", test_quota_fread }, + { "sqlite3_quota_fwrite", test_quota_fwrite }, + { "sqlite3_quota_fclose", test_quota_fclose }, + { "sqlite3_quota_fflush", test_quota_fflush }, + { "sqlite3_quota_fseek", test_quota_fseek }, + { "sqlite3_quota_rewind", test_quota_rewind }, + { "sqlite3_quota_ftell", test_quota_ftell }, + { "sqlite3_quota_ftruncate", test_quota_ftruncate }, + { "sqlite3_quota_file_size", test_quota_file_size }, + { "sqlite3_quota_file_truesize", test_quota_file_truesize }, + { "sqlite3_quota_file_mtime", test_quota_file_mtime }, + { "sqlite3_quota_remove", test_quota_remove }, + { "sqlite3_quota_glob", test_quota_glob }, }; int i; for(i=0; i +#include +#include +#if SQLITE_OS_UNIX +# include +#endif +#if SQLITE_OS_WIN +# include +#endif /* Make this callable from C++ */ #ifdef __cplusplus extern "C" { #endif @@ -180,10 +188,52 @@ */ int sqlite3_quota_fseek(quota_FILE*, long, int); void sqlite3_quota_rewind(quota_FILE*); long sqlite3_quota_ftell(quota_FILE*); +/* +** Truncate a file previously opened by sqlite3_quota_fopen(). Return +** zero on success and non-zero on any kind of failure. +** +** The newSize argument must be less than or equal to the current file size. +** Any attempt to "truncate" a file to a larger size results in +** undefined behavior. +*/ +int sqlite3_quota_ftrunate(quota_FILE*, sqlite3_int64 newSize); + +/* +** Return the last modification time of the opened file, in seconds +** since 1970. +*/ +int sqlite3_quota_file_mtime(quota_FILE*, time_t *pTime); + +/* +** Return the size of the file as it is known to the quota system. +** +** This size might be different from the true size of the file on +** disk if some outside process has modified the file without using the +** quota mechanism, or if calls to sqlite3_quota_fwrite() have occurred +** which have increased the file size, but those writes have not yet been +** forced to disk using sqlite3_quota_fflush(). +** +** Return -1 if the file is not participating in quota management. +*/ +sqlite3_int64 sqlite3_quota_file_size(quota_FILE*); + +/* +** Return the true size of the file. +** +** The true size should be the same as the size of the file as known +** to the quota system, however the sizes might be different if the +** file has been extended or truncated via some outside process or if +** pending writes have not yet been flushed to disk. +** +** Return -1 if the file does not exist or if the size of the file +** cannot be determined for some reason. +*/ +sqlite3_int64 sqlite3_quota_file_truesize(quota_FILE*); + /* ** Delete a file from the disk, if that file is under quota management. ** Adjust quotas accordingly. ** ** If zFilename is the name of a directory that matches one of the Index: src/test_rtree.c ================================================================== --- src/test_rtree.c +++ src/test_rtree.c @@ -47,11 +47,15 @@ ** Implementation of "circle" r-tree geometry callback. */ static int circle_geom( sqlite3_rtree_geometry *p, int nCoord, +#ifdef SQLITE_RTREE_INT_ONLY + sqlite3_int64 *aCoord, +#else double *aCoord, +#endif int *pRes ){ int i; /* Iterator variable */ Circle *pCircle; /* Structure defining circular region */ double xmin, xmax; /* X dimensions of box being tested */ @@ -186,12 +190,16 @@ ** ** The width, height and depth parameters must all be greater than zero. */ static int cube_geom( sqlite3_rtree_geometry *p, - int nCoord, + int nCoord, +#ifdef SQLITE_RTREE_INT_ONLY + sqlite3_int64 *aCoord, +#else double *aCoord, +#endif int *piRes ){ Cube *pCube = (Cube *)p->pUser; assert( p->pContext==(void *)&gHere ); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -2742,12 +2742,14 @@ } db->isTransactionSavepoint = 0; rc = p->rc; }else{ iSavepoint = db->nSavepoint - iSavepoint - 1; - for(ii=0; iinDb; ii++){ - sqlite3BtreeTripAllCursors(db->aDb[ii].pBt, SQLITE_ABORT); + if( p1==SAVEPOINT_ROLLBACK ){ + for(ii=0; iinDb; ii++){ + sqlite3BtreeTripAllCursors(db->aDb[ii].pBt, SQLITE_ABORT); + } } for(ii=0; iinDb; ii++){ rc = sqlite3BtreeSavepoint(db->aDb[ii].pBt, p1, iSavepoint); if( rc!=SQLITE_OK ){ goto abort_due_to_error; Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -1238,11 +1238,11 @@ int nByte = (nSub+1)*sizeof(SubProgram*); int j; for(j=0; jp4.pProgram ) break; } - if( j==nSub && SQLITE_OK==sqlite3VdbeMemGrow(pSub, nByte, 1) ){ + if( j==nSub && SQLITE_OK==sqlite3VdbeMemGrow(pSub, nByte, nSub!=0) ){ apSub = (SubProgram **)pSub->z; apSub[nSub++] = pOp->p4.pProgram; pSub->flags |= MEM_Blob; pSub->n = nSub*sizeof(SubProgram*); } Index: src/vdbemem.c ================================================================== --- src/vdbemem.c +++ src/vdbemem.c @@ -57,14 +57,14 @@ /* ** Make sure pMem->z points to a writable allocation of at least ** n bytes. ** -** If the memory cell currently contains string or blob data -** and the third argument passed to this function is true, the -** current content of the cell is preserved. Otherwise, it may -** be discarded. +** If the third argument passed to this function is true, then memory +** cell pMem must contain a string or blob. In this case the content is +** preserved. Otherwise, if the third parameter to this function is false, +** any current string or blob value may be discarded. ** ** This function sets the MEM_Dyn flag and clears any xDel callback. ** It also clears MEM_Ephem and MEM_Static. If the preserve flag is ** not set, Mem.n is zeroed. */ @@ -74,10 +74,14 @@ (((pMem->flags&MEM_Dyn)&&pMem->xDel) ? 1 : 0) + ((pMem->flags&MEM_Ephem) ? 1 : 0) + ((pMem->flags&MEM_Static) ? 1 : 0) ); assert( (pMem->flags&MEM_RowSet)==0 ); + + /* If the preserve flag is set to true, then the memory cell must already + ** contain a valid string or blob value. */ + assert( preserve==0 || pMem->flags&(MEM_Blob|MEM_Str) ); if( n<32 ) n = 32; if( sqlite3DbMallocSize(pMem->db, pMem->zMalloc)z==pMem->zMalloc ){ pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); Index: test/capi3.test ================================================================== --- test/capi3.test +++ test/capi3.test @@ -899,10 +899,16 @@ sqlite3_get_autocommit $DB } 1 do_test capi3-11.10 { sqlite3_step $STMT } {SQLITE_ERROR} +ifcapable !autoreset { + # If SQLITE_OMIT_AUTORESET is defined, then the statement must be + # reset() before it can be passed to step() again. + do_test capi3-11.11a { sqlite3_step $STMT } {SQLITE_MISUSE} + do_test capi3-11.11b { sqlite3_reset $STMT } {SQLITE_ABORT} +} do_test capi3-11.11 { sqlite3_step $STMT } {SQLITE_ROW} do_test capi3-11.12 { sqlite3_step $STMT Index: test/capi3c.test ================================================================== --- test/capi3c.test +++ test/capi3c.test @@ -854,10 +854,16 @@ sqlite3_get_autocommit $DB } 1 do_test capi3c-11.10 { sqlite3_step $STMT } {SQLITE_ABORT} +ifcapable !autoreset { + # If SQLITE_OMIT_AUTORESET is defined, then the statement must be + # reset() before it can be passed to step() again. + do_test capi3-11.11a { sqlite3_step $STMT } {SQLITE_MISUSE} + do_test capi3-11.11b { sqlite3_reset $STMT } {SQLITE_ABORT} +} do_test capi3c-11.11 { sqlite3_step $STMT } {SQLITE_ROW} do_test capi3c-11.12 { sqlite3_step $STMT Index: test/fts3defer.test ================================================================== --- test/fts3defer.test +++ test/fts3defer.test @@ -487,7 +487,41 @@ } do_execsql_test 4.2 { SELECT * FROM x2 WHERE x2 MATCH 'a b c d e f g h i j k l m n o p q r s'; } {{a b c d e f g h i j k l m n o p q r s t u v w x y m}} +set tokenizers {1 simple} +ifcapable icu { lappend tokenizers 2 {icu en_US} } +foreach {tn tokenizer} $tokenizers { + do_execsql_test 5.$tn.1 " + CREATE VIRTUAL TABLE x3 USING FTS4(a, b, TOKENIZE $tokenizer) + " + do_execsql_test 5.$tn.2 { + BEGIN; + INSERT INTO x3 VALUES('b b b b b b b b b b b', 'b b b b b b b b b b b b b'); + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 SELECT * FROM x3; + INSERT INTO x3 VALUES('a b c', NULL); + INSERT INTO x3 VALUES('a x c', NULL); + COMMIT; + + SELECT * FROM x3 WHERE x3 MATCH 'a b'; + } {{a b c} {}} + + do_execsql_test 5.$tn.3 { DROP TABLE x3 } +} finish_test Index: test/fts4merge3.test ================================================================== --- test/fts4merge3.test +++ test/fts4merge3.test @@ -17,10 +17,15 @@ source $testdir/fts3_common.tcl source $testdir/lock_common.tcl source $testdir/bc_common.tcl set ::testprefix fts4merge3 + +ifcapable !fts3 { + finish_test + return +} if {"" == [bc_find_binaries backcompat.test]} { finish_test return } Index: test/in.test ================================================================== --- test/in.test +++ test/in.test @@ -256,20 +256,32 @@ do_test in-7.5 { execsql { SELECT a FROM t1 WHERE a IN (5) AND b NOT IN (); } } {5} -do_test in-7.6 { +do_test in-7.6.1 { execsql { SELECT a FROM ta WHERE a IN (); } } {} +do_test in-7.6.2 { + db status step +} {0} do_test in-7.7 { execsql { SELECT a FROM ta WHERE a NOT IN (); } } {1 2 3 4 6 8 10} + +do_test in-7.8.1 { + execsql { + SELECT * FROM ta LEFT JOIN tb ON (ta.b=tb.b) WHERE ta.a IN (); + } +} {} +do_test in-7.8.2 { + db status step +} {0} do_test in-8.1 { execsql { SELECT b FROM t1 WHERE a IN ('hello','there') } Index: test/quota2.test ================================================================== --- test/quota2.test +++ test/quota2.test @@ -74,10 +74,17 @@ sqlite3_quota_fwrite $::h1 1 7000 $bigtext } {4000} do_test quota2-1.2 { set ::quota } {PWD/quota2a/xyz.txt 4000 7000} +do_test quota2-1.2.1 { + sqlite3_quota_file_size $::h1 +} {4000} +do_test quota2-1.2.2 { + sqlite3_quota_fflush $::h1 1 + sqlite3_quota_file_truesize $::h1 +} {4000} do_test quota2-1.3 { sqlite3_quota_rewind $::h1 set ::x [sqlite3_quota_fread $::h1 1001 7] string length $::x } {3003} @@ -110,17 +117,45 @@ } {0} do_test quota2-1.11 { standard_path [sqlite3_quota_dump] } {{*/quota2b/* 5000 0} {*/quota2a/* 4000 4000 {PWD/quota2a/xyz.txt 4000 1 0}}} do_test quota2-1.12 { + sqlite3_quota_ftruncate $::h1 3500 + sqlite3_quota_file_size $::h1 +} {3500} +do_test quota2-1.13 { + sqlite3_quota_file_truesize $::h1 +} {3500} +do_test quota2-1.14 { + standard_path [sqlite3_quota_dump] +} {{*/quota2b/* 5000 0} {*/quota2a/* 4000 3500 {PWD/quota2a/xyz.txt 3500 1 0}}} +do_test quota2-1.15 { + sqlite3_quota_fseek $::h1 0 SEEK_END + sqlite3_quota_ftell $::h1 +} {3500} +do_test quota2-1.16 { + sqlite3_quota_fwrite $::h1 1 7000 $bigtext +} {500} +do_test quota2-1.17 { + sqlite3_quota_ftell $::h1 +} {4000} +do_test quota2-1.18 { + sqlite3_quota_file_size $::h1 +} {4000} +do_test quota2-1.19 { + sqlite3_quota_fflush $::h1 1 + sqlite3_quota_file_truesize $::h1 +} {4000} +do_test quota2-1.20 { sqlite3_quota_fclose $::h1 standard_path [sqlite3_quota_dump] } {{*/quota2b/* 5000 0} {*/quota2a/* 4000 4000 {PWD/quota2a/xyz.txt 4000 0 0}}} -do_test quota2-1.13 { +do_test quota2-1.21 { sqlite3_quota_remove quota2a/xyz.txt standard_path [sqlite3_quota_dump] } {{*/quota2b/* 5000 0} {*/quota2a/* 4000 0}} + set quota {} do_test quota2-2.1 { set ::h1 [sqlite3_quota_fopen quota2c/xyz.txt w+b] ADDED test/savepoint7.test Index: test/savepoint7.test ================================================================== --- /dev/null +++ test/savepoint7.test @@ -0,0 +1,96 @@ +# 2012 March 31 +# +# 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. +# +#*********************************************************************** +# +# Focus on the interaction between RELEASE and ROLLBACK TO with +# pending query aborts. See ticket [27ca74af3c083f787a1c44b11fbb7c53bdbbcf1e]. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# The RELEASE of an inner savepoint should not effect pending queries. +# +do_test savepoint7-1.1 { + db eval { + CREATE TABLE t1(a,b,c); + CREATE TABLE t2(x,y,z); + INSERT INTO t1 VALUES(1,2,3); + INSERT INTO t1 VALUES(4,5,6); + INSERT INTO t1 VALUES(7,8,9); + SAVEPOINT x1; + } + db eval {SELECT * FROM t1} { + db eval { + SAVEPOINT x2; + INSERT INTO t2 VALUES($a,$b,$c); + RELEASE x2; + } + } + db eval {SELECT * FROM t2; RELEASE x1} +} {1 2 3 4 5 6 7 8 9} + +do_test savepoint7-1.2 { + db eval {DELETE FROM t2;} + db eval {SELECT * FROM t1} { + db eval { + SAVEPOINT x2; + INSERT INTO t2 VALUES($a,$b,$c); + RELEASE x2; + } + } + db eval {SELECT * FROM t2} +} {1 2 3 4 5 6 7 8 9} + +do_test savepoint7-1.3 { + db eval {DELETE FROM t2; BEGIN;} + db eval {SELECT * FROM t1} { + db eval { + SAVEPOINT x2; + INSERT INTO t2 VALUES($a,$b,$c); + RELEASE x2; + } + } + db eval {SELECT * FROM t2; ROLLBACK;} +} {1 2 3 4 5 6 7 8 9} + +# However, a ROLLBACK of an inner savepoint will abort all queries, including +# queries in outer contexts. +# +do_test savepoint7-2.1 { + db eval {DELETE FROM t2; SAVEPOINT x1;} + set rc [catch { + db eval {SELECT * FROM t1} { + db eval { + SAVEPOINT x2; + INSERT INTO t2 VALUES($a,$b,$c); + ROLLBACK TO x2; + } + } + } msg] + db eval {RELEASE x1} + list $rc $msg [db eval {SELECT * FROM t2}] +} {1 {callback requested query abort} {}} + +do_test savepoint7-2.2 { + db eval {DELETE FROM t2;} + set rc [catch { + db eval {SELECT * FROM t1} { + db eval { + SAVEPOINT x2; + INSERT INTO t2 VALUES($a,$b,$c); + ROLLBACK TO x2; + } + } + } msg] + list $rc $msg [db eval {SELECT * FROM t2}] +} {1 {callback requested query abort} {}} + +finish_test Index: test/subquery.test ================================================================== --- test/subquery.test +++ test/subquery.test @@ -328,10 +328,57 @@ do_test subquery-3.3.5 { execsql { SELECT a, (SELECT count(*) FROM t2 WHERE a=c) FROM t1; } } {1 1 2 1} + +# The following tests check for aggregate subqueries in an aggregate +# query. +# +do_test subquery-3.4.1 { + execsql { + CREATE TABLE t34(x,y); + INSERT INTO t34 VALUES(106,4), (107,3), (106,5), (107,5); + SELECT a.x, avg(a.y) + FROM t34 AS a + GROUP BY a.x + HAVING NOT EXISTS( SELECT b.x, avg(b.y) + FROM t34 AS b + GROUP BY b.x + HAVING avg(a.y) > avg(b.y)); + } +} {107 4.0} +do_test subquery-3.4.2 { + execsql { + SELECT a.x, avg(a.y) AS avg1 + FROM t34 AS a + GROUP BY a.x + HAVING NOT EXISTS( SELECT b.x, avg(b.y) AS avg2 + FROM t34 AS b + GROUP BY b.x + HAVING avg1 > avg2); + } +} {107 4.0} +do_test subquery-3.4.3 { + execsql { + SELECT + a.x, + avg(a.y), + NOT EXISTS ( SELECT b.x, avg(b.y) + FROM t34 AS b + GROUP BY b.x + HAVING avg(a.y) > avg(b.y)), + EXISTS ( SELECT c.x, avg(c.y) + FROM t34 AS c + GROUP BY c.x + HAVING avg(a.y) > avg(c.y)) + FROM t34 AS a + GROUP BY a.x + ORDER BY a.x; + } +} {106 4.5 0 1 107 4.0 1 0} + #------------------------------------------------------------------ # These tests - subquery-4.* - use the TCL statement cache to try # and expose bugs to do with re-using statements that have been # passed to sqlite3_reset(). Index: tool/build-shell.sh ================================================================== --- tool/build-shell.sh +++ tool/build-shell.sh @@ -10,12 +10,12 @@ # make sqlite3.c gcc -o sqlite3 -g -Os -I. \ -DSQLITE_THREADSAFE=0 \ -DSQLITE_ENABLE_VFSTRACE \ - -DSQLITE_ENABLE_STAT2 \ - -DSQLITE_ENABLE_FTS3 \ + -DSQLITE_ENABLE_STAT3 \ + -DSQLITE_ENABLE_FTS4 \ -DSQLITE_ENABLE_RTREE \ -DHAVE_READLINE \ -DHAVE_USLEEP=1 \ ../sqlite/src/shell.c ../sqlite/src/test_vfstrace.c \ sqlite3.c -ldl -lreadline -lncurses Index: tool/showdb.c ================================================================== --- tool/showdb.c +++ tool/showdb.c @@ -7,10 +7,11 @@ #include #include #include #include #include +#include "sqlite3.h" static int pagesize = 1024; /* Size of a database page */ static int db = -1; /* File descriptor for reading the DB */ static int mxPage = 0; /* Last page number */ @@ -447,19 +448,238 @@ pgno = (int)decodeInt32(&a[0]); } free(a); } } + +/* +** A short text comment on the use of each page. +*/ +static char **zPageUse; + +/* +** Add a comment on the use of a page. +*/ +static void page_usage_msg(int pgno, const char *zFormat, ...){ + va_list ap; + char *zMsg; + + va_start(ap, zFormat); + zMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + if( pgno<=0 || pgno>mxPage ){ + printf("ERROR: page %d out of bounds. Range=1..%d. Msg: %s\n", + pgno, mxPage, zMsg); + sqlite3_free(zMsg); + return; + } + if( zPageUse[pgno]!=0 ){ + printf("ERROR: page %d used multiple times:\n", pgno); + printf("ERROR: previous: %s\n", zPageUse[pgno]); + printf("ERROR: current: %s\n", zPageUse[pgno]); + sqlite3_free(zPageUse[pgno]); + } + zPageUse[pgno] = zMsg; +} + +/* +** Find overflow pages of a cell and describe their usage. +*/ +static void page_usage_cell( + unsigned char cType, /* Page type */ + unsigned char *a, /* Cell content */ + int pgno, /* page containing the cell */ + int cellno /* Index of the cell on the page */ +){ + int i; + int nDesc = 0; + int n = 0; + i64 nPayload; + i64 rowid; + int nLocal; + i = 0; + if( cType<=5 ){ + a += 4; + n += 4; + } + if( cType!=5 ){ + i = decodeVarint(a, &nPayload); + a += i; + n += i; + nLocal = localPayload(nPayload, cType); + }else{ + nPayload = nLocal = 0; + } + if( cType==5 || cType==13 ){ + i = decodeVarint(a, &rowid); + a += i; + n += i; + } + if( nLocalmxPage ) return; + a = getContent((pgno-1)*pagesize, pagesize); + switch( a[hdr] ){ + case 2: zType = "interior node of index"; break; + case 5: zType = "interior node of table"; break; + case 10: zType = "leaf of index"; break; + case 13: zType = "leaf of table"; break; + } + if( parent ){ + page_usage_msg(pgno, "%s [%s], child %d of page %d", + zType, zName, idx, parent); + }else{ + page_usage_msg(pgno, "root %s [%s]", zType, zName); + } + nCell = a[hdr+3]*256 + a[hdr+4]; + if( a[hdr]==2 || a[hdr]==5 ){ + int cellstart = hdr+12; + unsigned int child; + for(i=0; i0 && pgno<=mxPage && (cnt++)=2 && [string length $extension]<=4} { set pattern [file rootname $file_to_analyze] - append pattern [string range $extension 0 1] - append pattern {[0-9][0-9]} + append pattern {.[0-3][0-9][0-9]} foreach f [glob -nocomplain $pattern] { incr true_file_size [file size $f] } } Index: tool/warnings-clang.sh ================================================================== --- tool/warnings-clang.sh +++ tool/warnings-clang.sh @@ -7,7 +7,8 @@ make sqlite3.c echo '************* FTS4 and RTREE ****************' scan-build gcc -c -DHAVE_STDINT_H -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_RTREE \ -DSQLITE_DEBUG sqlite3.c 2>&1 | grep -v 'ANALYZE:' echo '********** ENABLE_STAT3. THREADSAFE=0 *******' -scan-build gcc -c -DSQLITE_ENABLE_STAT3 -DSQLITE_THREADSAFE=0 \ - -DSQLITE_DEBUG sqlite3.c 2>&1 | grep -v 'ANALYZE:' +scan-build gcc -c -I. -DSQLITE_ENABLE_STAT3 -DSQLITE_THREADSAFE=0 \ + -DSQLITE_DEBUG \ + sqlite3.c ../sqlite/src/shell.c -ldl 2>&1 | grep -v 'ANALYZE:'