Index: ext/fts3/fts3.c ================================================================== --- ext/fts3/fts3.c +++ ext/fts3/fts3.c @@ -436,11 +436,11 @@ char **pp, char *pStart, sqlite3_int64 *pVal ){ sqlite3_int64 iVal; - char *p = *pp; + char *p; /* Pointer p now points at the first byte past the varint we are ** interested in. So, unless the doclist is corrupt, the 0x80 bit is ** clear on character p[-1]. */ for(p = (*pp)-2; p>=pStart && *p&0x80; p--); @@ -466,10 +466,11 @@ sqlite3_finalize(p->aStmt[i]); } sqlite3_free(p->zSegmentsTbl); sqlite3_free(p->zReadExprlist); sqlite3_free(p->zWriteExprlist); + sqlite3_free(p->zContentTbl); /* Invoke the tokenizer destructor to free the tokenizer. */ p->pTokenizer->pModule->xDestroy(p->pTokenizer); sqlite3_free(p); @@ -505,20 +506,23 @@ /* ** The xDestroy() virtual table method. */ static int fts3DestroyMethod(sqlite3_vtab *pVtab){ - int rc = SQLITE_OK; /* Return code */ Fts3Table *p = (Fts3Table *)pVtab; - sqlite3 *db = p->db; + int rc = SQLITE_OK; /* Return code */ + const char *zDb = p->zDb; /* Name of database (e.g. "main", "temp") */ + sqlite3 *db = p->db; /* Database handle */ /* Drop the shadow tables */ - fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", p->zDb, p->zName); - fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", p->zDb,p->zName); - fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", p->zDb, p->zName); - fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", p->zDb, p->zName); - fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", p->zDb, p->zName); + if( p->zContentTbl==0 ){ + fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", zDb, p->zName); + } + fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", zDb,p->zName); + fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", zDb, p->zName); + fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", zDb, p->zName); + fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", zDb, p->zName); /* If everything has worked, invoke fts3DisconnectMethod() to free the ** memory associated with the Fts3Table structure and return SQLITE_OK. ** Otherwise, return an SQLite error code. */ @@ -576,27 +580,31 @@ ** %_stat tables required by FTS4. */ static int fts3CreateTables(Fts3Table *p){ int rc = SQLITE_OK; /* Return code */ int i; /* Iterator variable */ - char *zContentCols; /* Columns of %_content table */ sqlite3 *db = p->db; /* The database connection */ - /* Create a list of user columns for the content table */ - zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY"); - for(i=0; zContentCols && inColumn; i++){ - char *z = p->azColumn[i]; - zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z); - } - if( zContentCols==0 ) rc = SQLITE_NOMEM; - - /* Create the content table */ - fts3DbExec(&rc, db, - "CREATE TABLE %Q.'%q_content'(%s)", - p->zDb, p->zName, zContentCols - ); - sqlite3_free(zContentCols); + if( p->zContentTbl==0 ){ + char *zContentCols; /* Columns of %_content table */ + + /* Create a list of user columns for the content table */ + zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY"); + for(i=0; zContentCols && inColumn; i++){ + char *z = p->azColumn[i]; + zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z); + } + if( zContentCols==0 ) rc = SQLITE_NOMEM; + + /* Create the content table */ + fts3DbExec(&rc, db, + "CREATE TABLE %Q.'%q_content'(%s)", + p->zDb, p->zName, zContentCols + ); + sqlite3_free(zContentCols); + } + /* Create other tables */ fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);", p->zDb, p->zName ); @@ -743,12 +751,12 @@ } return zRet; } /* -** Return a list of comma separated SQL expressions that could be used -** in a SELECT statement such as the following: +** Return a list of comma separated SQL expressions and a FROM clause that +** could be used in a SELECT statement such as the following: ** ** SELECT FROM %_content AS x ... ** ** to return the docid, followed by each column of text data in order ** from left to write. If parameter zFunc is not NULL, then instead of @@ -755,11 +763,11 @@ ** being returned directly each column of text data is passed to an SQL ** function named zFunc first. For example, if zFunc is "unzip" and the ** table has the three user-defined columns "a", "b", and "c", the following ** string is returned: ** -** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c')" +** "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c') FROM %_content AS x" ** ** The pointer returned points to a buffer allocated by sqlite3_malloc(). It ** is the responsibility of the caller to eventually free it. ** ** If *pRc is not SQLITE_OK when this function is called, it is a no-op (and @@ -771,20 +779,32 @@ char *zRet = 0; char *zFree = 0; char *zFunction; int i; - if( !zFunc ){ - zFunction = ""; + if( p->zContentTbl==0 ){ + if( !zFunc ){ + zFunction = ""; + }else{ + zFree = zFunction = fts3QuoteId(zFunc); + } + fts3Appendf(pRc, &zRet, "docid"); + for(i=0; inColumn; i++){ + fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]); + } + sqlite3_free(zFree); }else{ - zFree = zFunction = fts3QuoteId(zFunc); + fts3Appendf(pRc, &zRet, "rowid"); + for(i=0; inColumn; i++){ + fts3Appendf(pRc, &zRet, ", x.'%q'", p->azColumn[i]); + } } - fts3Appendf(pRc, &zRet, "docid"); - for(i=0; inColumn; i++){ - fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]); - } - sqlite3_free(zFree); + fts3Appendf(pRc, &zRet, "FROM '%q'.'%q%s' AS x", + p->zDb, + (p->zContentTbl ? p->zContentTbl : p->zName), + (p->zContentTbl ? "" : "_content") + ); return zRet; } /* ** Return a list of N comma separated question marks, where N is the number @@ -837,11 +857,11 @@ ** the output value undefined. Otherwise SQLITE_OK is returned. ** ** This function is used when parsing the "prefix=" FTS4 parameter. */ static int fts3GobbleInt(const char **pp, int *pnOut){ - const char *p = *pp; /* Iterator pointer */ + const char *p; /* Iterator pointer */ int nInt = 0; /* Output value */ for(p=*pp; p[0]>='0' && p[0]<='9'; p++){ nInt = nInt * 10 + (p[0] - '0'); } @@ -903,10 +923,95 @@ } } return SQLITE_OK; } + +/* +** This function is called when initializing an FTS4 table that uses the +** content=xxx option. It determines the number of and names of the columns +** of the new FTS4 table. +** +** The third argument passed to this function is the value passed to the +** config=xxx option (i.e. "xxx"). This function queries the database for +** a table of that name. If found, the output variables are populated +** as follows: +** +** *pnCol: Set to the number of columns table xxx has, +** +** *pnStr: Set to the total amount of space required to store a copy +** of each columns name, including the nul-terminator. +** +** *pazCol: Set to point to an array of *pnCol strings. Each string is +** the name of the corresponding column in table xxx. The array +** and its contents are allocated using a single allocation. It +** is the responsibility of the caller to free this allocation +** by eventually passing the *pazCol value to sqlite3_free(). +** +** If the table cannot be found, an error code is returned and the output +** variables are undefined. Or, if an OOM is encountered, SQLITE_NOMEM is +** returned (and the output variables are undefined). +*/ +static int fts3ContentColumns( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of db (i.e. "main", "temp" etc.) */ + const char *zTbl, /* Name of content table */ + const char ***pazCol, /* OUT: Malloc'd array of column names */ + int *pnCol, /* OUT: Size of array *pazCol */ + int *pnStr /* OUT: Bytes of string content */ +){ + int rc = SQLITE_OK; /* Return code */ + char *zSql; /* "SELECT *" statement on zTbl */ + sqlite3_stmt *pStmt = 0; /* Compiled version of zSql */ + + zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + } + sqlite3_free(zSql); + + if( rc==SQLITE_OK ){ + const char **azCol; /* Output array */ + int nStr = 0; /* Size of all column names (incl. 0x00) */ + int nCol; /* Number of table columns */ + int i; /* Used to iterate through columns */ + + /* Loop through the returned columns. Set nStr to the number of bytes of + ** space required to store a copy of each column name, including the + ** nul-terminator byte. */ + nCol = sqlite3_column_count(pStmt); + for(i=0; i MATCHINFO */ - { "prefix", 6, 0 }, /* 1 -> PREFIX */ - { "compress", 8, 0 }, /* 2 -> COMPRESS */ - { "uncompress", 10, 0 }, /* 3 -> UNCOMPRESS */ - { "order", 5, 0 } /* 4 -> ORDER */ + { "matchinfo", 9 }, /* 0 -> MATCHINFO */ + { "prefix", 6 }, /* 1 -> PREFIX */ + { "compress", 8 }, /* 2 -> COMPRESS */ + { "uncompress", 10 }, /* 3 -> UNCOMPRESS */ + { "order", 5 }, /* 4 -> ORDER */ + { "content", 7 } /* 5 -> CONTENT */ }; int iOpt; if( !zVal ){ rc = SQLITE_NOMEM; @@ -1043,17 +1149,24 @@ zVal = 0; break; case 4: /* ORDER */ if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3)) - && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 3)) + && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4)) ){ *pzErr = sqlite3_mprintf("unrecognized order: %s", zVal); rc = SQLITE_ERROR; } bDescIdx = (zVal[0]=='d' || zVal[0]=='D'); break; + + default: /* CONTENT */ + assert( iOpt==5 ); + sqlite3_free(zUncompress); + zContent = zVal; + zVal = 0; + break; } } sqlite3_free(zVal); } } @@ -1062,10 +1175,30 @@ else { nString += (int)(strlen(z) + 1); aCol[nCol++] = z; } } + + /* If a content=xxx option was specified, the following: + ** + ** 1. Ignore any compress= and uncompress= options. + ** + ** 2. If no column names were specified as part of the CREATE VIRTUAL + ** TABLE statement, use all columns from the content table. + */ + if( rc==SQLITE_OK && zContent ){ + sqlite3_free(zCompress); + sqlite3_free(zUncompress); + zCompress = 0; + zUncompress = 0; + if( nCol==0 ){ + sqlite3_free((void*)aCol); + aCol = 0; + rc = fts3ContentColumns(db, argv[1], zContent, &aCol, &nCol, &nString); + } + assert( rc!=SQLITE_OK || nCol>0 ); + } if( rc!=SQLITE_OK ) goto fts3_init_out; if( nCol==0 ){ assert( nString==0 ); aCol[0] = "content"; @@ -1106,10 +1239,12 @@ p->pTokenizer = pTokenizer; p->nMaxPendingData = FTS3_MAX_PENDING_DATA; p->bHasDocsize = (isFts4 && bNoDocsize==0); p->bHasStat = isFts4; p->bDescIdx = bDescIdx; + p->zContentTbl = zContent; + zContent = 0; TESTONLY( p->inTransaction = -1 ); TESTONLY( p->mxSavepoint = -1 ); p->aIndex = (struct Fts3Index *)&p->azColumn[nCol]; memcpy(p->aIndex, aIndex, sizeof(struct Fts3Index) * nIndex); @@ -1167,10 +1302,11 @@ fts3_init_out: sqlite3_free(zPrefix); sqlite3_free(aIndex); sqlite3_free(zCompress); sqlite3_free(zUncompress); + sqlite3_free(zContent); sqlite3_free((void *)aCol); if( rc!=SQLITE_OK ){ if( p ){ fts3DisconnectMethod((sqlite3_vtab *)p); }else if( pTokenizer ){ @@ -1317,40 +1453,69 @@ sqlite3_free(pCsr->aMatchinfo); assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); sqlite3_free(pCsr); return SQLITE_OK; } + +/* +** If pCsr->pStmt has not been prepared (i.e. if pCsr->pStmt==0), then +** compose and prepare an SQL statement of the form: +** +** "SELECT FROM %_content WHERE rowid = ?" +** +** (or the equivalent for a content=xxx table) and set pCsr->pStmt to +** it. If an error occurs, return an SQLite error code. +** +** Otherwise, set *ppStmt to point to pCsr->pStmt and return SQLITE_OK. +*/ +static int fts3CursorSeekStmt(Fts3Cursor *pCsr, sqlite3_stmt **ppStmt){ + int rc = SQLITE_OK; + if( pCsr->pStmt==0 ){ + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + char *zSql; + zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); + if( !zSql ) return SQLITE_NOMEM; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); + sqlite3_free(zSql); + } + *ppStmt = pCsr->pStmt; + return rc; +} /* ** Position the pCsr->pStmt statement so that it is on the row ** of the %_content table that contains the last match. Return ** SQLITE_OK on success. */ static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){ + int rc = SQLITE_OK; if( pCsr->isRequireSeek ){ - sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId); - pCsr->isRequireSeek = 0; - if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){ - return SQLITE_OK; - }else{ - int rc = sqlite3_reset(pCsr->pStmt); - if( rc==SQLITE_OK ){ - /* If no row was found and no error has occured, then the %_content - ** table is missing a row that is present in the full-text index. - ** The data structures are corrupt. - */ - rc = SQLITE_CORRUPT_VTAB; - } - pCsr->isEof = 1; - if( pContext ){ - sqlite3_result_error_code(pContext, rc); - } - return rc; - } - }else{ - return SQLITE_OK; - } + sqlite3_stmt *pStmt = 0; + + rc = fts3CursorSeekStmt(pCsr, &pStmt); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId); + pCsr->isRequireSeek = 0; + if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){ + return SQLITE_OK; + }else{ + rc = sqlite3_reset(pCsr->pStmt); + if( rc==SQLITE_OK && ((Fts3Table *)pCsr->base.pVtab)->zContentTbl==0 ){ + /* If no row was found and no error has occured, then the %_content + ** table is missing a row that is present in the full-text index. + ** The data structures are corrupt. */ + rc = FTS_CORRUPT_VTAB; + pCsr->isEof = 1; + } + } + } + } + + if( rc!=SQLITE_OK && pContext ){ + sqlite3_result_error_code(pContext, rc); + } + return rc; } /* ** This function is used to process a single interior node when searching ** a b-tree for a term or term prefix. The node data is passed to this @@ -1396,11 +1561,11 @@ ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details). */ zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); if( zCsr>zEnd ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } while( zCsrzEnd ){ - rc = SQLITE_CORRUPT_VTAB; + rc = FTS_CORRUPT_VTAB; goto finish_scan; } if( nPrefix+nSuffix>nAlloc ){ char *zNew; nAlloc = (nPrefix+nSuffix) * 2; @@ -1427,10 +1592,11 @@ rc = SQLITE_NOMEM; goto finish_scan; } zBuffer = zNew; } + assert( zBuffer ); memcpy(&zBuffer[nPrefix], zCsr, nSuffix); nBuffer = nPrefix + nSuffix; zCsr += nSuffix; /* Compare the term we are searching for with the term just loaded from @@ -1785,20 +1951,20 @@ int isSaveLeft, /* Save the left position */ int isExact, /* If *pp1 is exactly nTokens before *pp2 */ char **pp1, /* IN/OUT: Left input list */ char **pp2 /* IN/OUT: Right input list */ ){ - char *p = (pp ? *pp : 0); + char *p = *pp; char *p1 = *pp1; char *p2 = *pp2; int iCol1 = 0; int iCol2 = 0; /* Never set both isSaveLeft and isExact for the same invocation. */ assert( isSaveLeft==0 || isExact==0 ); - assert( *p1!=0 && *p2!=0 ); + assert( p!=0 && *p1!=0 && *p2!=0 ); if( *p1==POS_COLUMN ){ p1++; p1 += sqlite3Fts3GetVarint32(p1, &iCol1); } if( *p2==POS_COLUMN ){ @@ -1811,11 +1977,11 @@ char *pSave = p; sqlite3_int64 iPrev = 0; sqlite3_int64 iPos1 = 0; sqlite3_int64 iPos2 = 0; - if( pp && iCol1 ){ + if( iCol1 ){ *p++ = POS_COLUMN; p += sqlite3Fts3PutVarint(p, iCol1); } assert( *p1!=POS_END && *p1!=POS_COLUMN ); @@ -1826,20 +1992,14 @@ while( 1 ){ if( iPos2==iPos1+nToken || (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken) ){ sqlite3_int64 iSave; - if( !pp ){ - fts3PoslistCopy(0, &p2); - fts3PoslistCopy(0, &p1); - *pp1 = p1; - *pp2 = p2; - return 1; - } iSave = isSaveLeft ? iPos1 : iPos2; fts3PutDeltaVarint(&p, &iPrev, iSave+2); iPrev -= 2; pSave = 0; + assert( p ); } if( (!isSaveLeft && iPos2<=(iPos1+nToken)) || iPos2<=iPos1 ){ if( (*p2&0xFE)==0 ) break; fts3GetDeltaVarint(&p2, &iPos2); iPos2 -= 2; }else{ @@ -1884,11 +2044,11 @@ fts3PoslistCopy(0, &p2); fts3PoslistCopy(0, &p1); *pp1 = p1; *pp2 = p2; - if( !pp || *pp==p ){ + if( *pp==p ){ return 0; } *p++ = 0x00; *pp = p; return 1; @@ -2186,10 +2346,60 @@ } } *pnRight = p - aOut; } + +/* +** Argument pList points to a position list nList bytes in size. This +** function checks to see if the position list contains any entries for +** a token in position 0 (of any column). If so, it writes argument iDelta +** to the output buffer pOut, followed by a position list consisting only +** of the entries from pList at position 0, and terminated by an 0x00 byte. +** The value returned is the number of bytes written to pOut (if any). +*/ +int sqlite3Fts3FirstFilter( + sqlite3_int64 iDelta, /* Varint that may be written to pOut */ + char *pList, /* Position list (no 0x00 term) */ + int nList, /* Size of pList in bytes */ + char *pOut /* Write output here */ +){ + int nOut = 0; + int bWritten = 0; /* True once iDelta has been written */ + char *p = pList; + char *pEnd = &pList[nList]; + + if( *p!=0x01 ){ + if( *p==0x02 ){ + nOut += sqlite3Fts3PutVarint(&pOut[nOut], iDelta); + pOut[nOut++] = 0x02; + bWritten = 1; + } + fts3ColumnlistCopy(0, &p); + } + + while( ppSegcsr; memset(&tsc, 0, sizeof(TermSelect)); filter.flags = FTS3_SEGMENT_IGNORE_EMPTY | FTS3_SEGMENT_REQUIRE_POS | (pTok->isPrefix ? FTS3_SEGMENT_PREFIX : 0) + | (pTok->bFirst ? FTS3_SEGMENT_FIRST : 0) | (iColumnnColumn ? FTS3_SEGMENT_COLUMN_FILTER : 0); filter.iCol = iColumn; filter.zTerm = pTok->z; filter.nTerm = pTok->n; @@ -2683,12 +2894,12 @@ if( zQuery==0 && sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ return SQLITE_NOMEM; } - rc = sqlite3Fts3ExprParse(p->pTokenizer, p->azColumn, p->nColumn, - iCol, zQuery, -1, &pCsr->pExpr + rc = sqlite3Fts3ExprParse(p->pTokenizer, p->azColumn, p->bHasStat, + p->nColumn, iCol, zQuery, -1, &pCsr->pExpr ); if( rc!=SQLITE_OK ){ if( rc==SQLITE_ERROR ){ static const char *zErr = "malformed MATCH expression: [%s]"; p->base.zErrMsg = sqlite3_mprintf(zErr, zQuery); @@ -2711,26 +2922,27 @@ ** statement loops through all rows of the %_content table. For a ** full-text query or docid lookup, the statement retrieves a single ** row by docid. */ if( idxNum==FTS3_FULLSCAN_SEARCH ){ - const char *zSort = (pCsr->bDesc ? "DESC" : "ASC"); - const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x ORDER BY docid %s"; - zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName, zSort); - }else{ - const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?"; - zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName); - } - if( !zSql ) return SQLITE_NOMEM; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); - sqlite3_free(zSql); - if( rc!=SQLITE_OK ) return rc; - - if( idxNum==FTS3_DOCID_SEARCH ){ - rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); - if( rc!=SQLITE_OK ) return rc; - } + zSql = sqlite3_mprintf( + "SELECT %s ORDER BY rowid %s", + p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC") + ); + if( zSql ){ + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); + sqlite3_free(zSql); + }else{ + rc = SQLITE_NOMEM; + } + }else if( idxNum==FTS3_DOCID_SEARCH ){ + rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt); + if( rc==SQLITE_OK ){ + rc = sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); + } + } + if( rc!=SQLITE_OK ) return rc; return fts3NextMethod(pCursor); } /* @@ -2779,11 +2991,11 @@ ** Return a blob which is a pointer to the cursor. */ sqlite3_result_blob(pContext, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT); }else{ rc = fts3CursorSeek(0, pCsr); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && sqlite3_data_count(pCsr->pStmt)>(iCol+1) ){ sqlite3_result_value(pContext, sqlite3_column_value(pCsr->pStmt, iCol+1)); } } assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); @@ -2863,11 +3075,11 @@ ** moves *ppPoslist so that it instead points to the first byte of the ** same position list. */ static void fts3ReversePoslist(char *pStart, char **ppPoslist){ char *p = &(*ppPoslist)[-2]; - char c; + char c = 0; while( p>pStart && (c=*p--)==0 ); while( p>pStart && (*p & 0x80) | c ){ c = *p--; } @@ -3072,19 +3284,26 @@ ){ Fts3Table *p = (Fts3Table *)pVtab; sqlite3 *db = p->db; /* Database connection */ int rc; /* Return Code */ + /* As it happens, the pending terms table is always empty here. This is + ** because an "ALTER TABLE RENAME TABLE" statement inside a transaction + ** always opens a savepoint transaction. And the xSavepoint() method + ** flushes the pending terms table. But leave the (no-op) call to + ** PendingTermsFlush() in in case that changes. + */ + assert( p->nPendingData==0 ); rc = sqlite3Fts3PendingTermsFlush(p); - if( rc!=SQLITE_OK ){ - return rc; + + if( p->zContentTbl==0 ){ + fts3DbExec(&rc, db, + "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';", + p->zDb, p->zName, zName + ); } - fts3DbExec(&rc, db, - "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';", - p->zDb, p->zName, zName - ); if( p->bHasDocsize ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_docsize' RENAME TO '%q_docsize';", p->zDb, p->zName, zName ); @@ -3439,25 +3658,24 @@ ** ** SQLITE_OK is returned if no error occurs, otherwise an SQLite error code. */ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ int iToken; /* Used to iterate through phrase tokens */ - int rc = SQLITE_OK; /* Return code */ char *aPoslist = 0; /* Position list for deferred tokens */ int nPoslist = 0; /* Number of bytes in aPoslist */ int iPrev = -1; /* Token number of previous deferred token */ assert( pPhrase->doclist.bFreeList==0 ); - for(iToken=0; rc==SQLITE_OK && iTokennToken; iToken++){ + for(iToken=0; iTokennToken; iToken++){ Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; Fts3DeferredToken *pDeferred = pToken->pDeferred; if( pDeferred ){ char *pList; int nList; - rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList); + int rc = sqlite3Fts3DeferredTokenList(pDeferred, &pList, &nList); if( rc!=SQLITE_OK ) return rc; if( pList==0 ){ sqlite3_free(aPoslist); pPhrase->doclist.pList = 0; @@ -3554,10 +3772,11 @@ if( pCsr->bDesc==pTab->bDescIdx && bOptOk==1 && p->nToken==1 && pFirst->pSegcsr && pFirst->pSegcsr->bLookup + && pFirst->bFirst==0 ){ /* Use the incremental approach. */ int iCol = (p->iColumn >= pTab->nColumn ? -1 : p->iColumn); rc = sqlite3Fts3MsrIncrStart( pTab, pFirst->pSegcsr, iCol, pFirst->z, pFirst->n); @@ -3783,11 +4002,11 @@ Fts3Expr *pExpr, /* Expression to consider */ Fts3TokenAndCost **ppTC, /* Write new entries to *(*ppTC)++ */ Fts3Expr ***ppOr, /* Write new OR root to *(*ppOr)++ */ int *pRc /* IN/OUT: Error code */ ){ - if( *pRc==SQLITE_OK && pExpr ){ + if( *pRc==SQLITE_OK ){ if( pExpr->eType==FTSQUERY_PHRASE ){ Fts3Phrase *pPhrase = pExpr->pPhrase; int i; for(i=0; *pRc==SQLITE_OK && inToken; i++){ Fts3TokenAndCost *pTC = (*ppTC)++; @@ -3797,10 +4016,15 @@ pTC->pToken = &pPhrase->aToken[i]; pTC->iCol = pPhrase->iColumn; *pRc = sqlite3Fts3MsrOvfl(pCsr, pTC->pToken->pSegcsr, &pTC->nOvfl); } }else if( pExpr->eType!=FTSQUERY_NOT ){ + assert( pExpr->eType==FTSQUERY_OR + || pExpr->eType==FTSQUERY_AND + || pExpr->eType==FTSQUERY_NEAR + ); + assert( pExpr->pLeft && pExpr->pRight ); if( pExpr->eType==FTSQUERY_OR ){ pRoot = pExpr->pLeft; **ppOr = pRoot; (*ppOr)++; } @@ -3857,11 +4081,11 @@ while( anDoc = nDoc; pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz); assert( pCsr->nRowAvg>0 ); @@ -3900,10 +4124,19 @@ int nOvfl = 0; /* Total overflow pages used by doclists */ int nToken = 0; /* Total number of tokens in cluster */ int nMinEst = 0; /* The minimum count for any phrase so far. */ int nLoad4 = 1; /* (Phrases that will be loaded)^4. */ + + /* Tokens are never deferred for FTS tables created using the content=xxx + ** option. The reason being that it is not guaranteed that the content + ** table actually contains the same data as the index. To prevent this from + ** causing any problems, the deferred token optimization is completely + ** disabled for content=xxx tables. */ + if( pTab->zContentTbl ){ + return SQLITE_OK; + } /* Count the tokens in this AND/NEAR cluster. If none of the doclists ** associated with the tokens spill onto overflow pages, or if there is ** only 1 token, exit early. No tokens to defer in this case. */ for(ii=0; iipToken; rc = sqlite3Fts3DeferToken(pCsr, pToken, pTC->iCol); fts3SegReaderCursorFree(pToken->pSegcsr); pToken->pSegcsr = 0; }else{ - nLoad4 = nLoad4*4; + /* Set nLoad4 to the value of (4^nOther) for the next iteration of the + ** for-loop. Except, limit the value to 2^24 to prevent it from + ** overflowing the 32-bit integer it is stored in. */ + if( ii<12 ) nLoad4 = nLoad4*4; + if( ii==0 || pTC->pPhrase->nToken>1 ){ /* Either this is the cheapest token in the entire query, or it is ** part of a multi-token phrase. Either way, the entire doclist will ** (eventually) be loaded into memory. It may as well be now. */ Fts3PhraseToken *pToken = pTC->pToken; @@ -4333,12 +4570,15 @@ } aPoslist = pExpr->pRight->pPhrase->doclist.pList; nToken = pExpr->pRight->pPhrase->nToken; for(p=pExpr->pLeft; p && res; p=p->pLeft){ - int nNear = p->pParent->nNear; - Fts3Phrase *pPhrase = ( + int nNear; + Fts3Phrase *pPhrase; + assert( p->pParent && p->pParent->pLeft==p ); + nNear = p->pParent->nNear; + pPhrase = ( p->eType==FTSQUERY_NEAR ? p->pRight->pPhrase : p->pPhrase ); res = fts3EvalNearTrim(nNear, aTmp, &aPoslist, &nToken, pPhrase); } } @@ -4824,10 +5064,19 @@ pPhrase->aToken[i].pSegcsr = 0; } } } +/* +** Return SQLITE_CORRUPT_VTAB. +*/ +#ifdef SQLITE_DEBUG +int sqlite3Fts3Corrupt(){ + return SQLITE_CORRUPT_VTAB; +} +#endif + #if !SQLITE_CORE /* ** Initialize API pointer table, if required. */ int sqlite3_extension_init( Index: ext/fts3/fts3Int.h ================================================================== --- ext/fts3/fts3Int.h +++ ext/fts3/fts3Int.h @@ -154,10 +154,17 @@ #else # define TESTONLY(X) #endif #endif /* SQLITE_AMALGAMATION */ + +#ifdef SQLITE_DEBUG +int sqlite3Fts3Corrupt(void); +# define FTS_CORRUPT_VTAB sqlite3Fts3Corrupt() +#else +# define FTS_CORRUPT_VTAB SQLITE_CORRUPT_VTAB +#endif typedef struct Fts3Table Fts3Table; typedef struct Fts3Cursor Fts3Cursor; typedef struct Fts3Expr Fts3Expr; typedef struct Fts3Phrase Fts3Phrase; @@ -182,10 +189,11 @@ const char *zDb; /* logical database name */ const char *zName; /* virtual table name */ int nColumn; /* number of named columns in virtual table */ char **azColumn; /* column names. malloced */ sqlite3_tokenizer *pTokenizer; /* tokenizer for inserts and queries */ + char *zContentTbl; /* content=xxx option, or NULL */ /* Precompiled statements used by the implementation. Each of these ** statements is run and reset within a single virtual table API call. */ sqlite3_stmt *aStmt[27]; @@ -222,11 +230,11 @@ } *aIndex; int nMaxPendingData; /* Max pending data before flush to disk */ int nPendingData; /* Current bytes of pending data */ sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */ -#if defined(SQLITE_DEBUG) +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) /* State variables used for validating that the transaction control ** methods of the virtual table are called at appropriate times. These ** values do not contribution to the FTS computation; they are used for ** verifying the SQLite core. */ @@ -307,10 +315,11 @@ */ struct Fts3PhraseToken { char *z; /* Text of the token */ int n; /* Number of bytes in buffer z */ int isPrefix; /* True if token ends with a "*" character */ + int bFirst; /* True if token must appear at position 0 */ /* Variables above this point are populated when the expression is ** parsed (by code in fts3_expr.c). Below this point the variables are ** used when evaluating the expression. */ Fts3DeferredToken *pDeferred; /* Deferred token object for this token */ @@ -425,10 +434,11 @@ #define FTS3_SEGMENT_REQUIRE_POS 0x00000001 #define FTS3_SEGMENT_IGNORE_EMPTY 0x00000002 #define FTS3_SEGMENT_COLUMN_FILTER 0x00000004 #define FTS3_SEGMENT_PREFIX 0x00000008 #define FTS3_SEGMENT_SCAN 0x00000010 +#define FTS3_SEGMENT_FIRST 0x00000020 /* Type passed as 4th argument to SegmentReaderIterate() */ struct Fts3SegFilter { const char *zTerm; int nTerm; @@ -464,12 +474,12 @@ int sqlite3Fts3GetVarint(const char *, sqlite_int64 *); int sqlite3Fts3GetVarint32(const char *, int *); int sqlite3Fts3VarintLen(sqlite3_uint64); void sqlite3Fts3Dequote(char *); void sqlite3Fts3DoclistPrev(int,char*,int,char**,sqlite3_int64*,int*,u8*); - int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *); +int sqlite3Fts3FirstFilter(sqlite3_int64, char *, int, char *); /* fts3_tokenizer.c */ const char *sqlite3Fts3NextToken(const char *, int *); int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *); int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *, @@ -484,11 +494,11 @@ ); void sqlite3Fts3Matchinfo(sqlite3_context *, Fts3Cursor *, const char *); /* fts3_expr.c */ int sqlite3Fts3ExprParse(sqlite3_tokenizer *, - char **, int, int, const char *, int, Fts3Expr ** + char **, int, int, int, const char *, int, Fts3Expr ** ); void sqlite3Fts3ExprFree(Fts3Expr *); #ifdef SQLITE_TEST int sqlite3Fts3ExprInitTestInterface(sqlite3 *db); int sqlite3Fts3InitTerm(sqlite3 *db); Index: ext/fts3/fts3_expr.c ================================================================== --- ext/fts3/fts3_expr.c +++ ext/fts3/fts3_expr.c @@ -91,10 +91,11 @@ */ typedef struct ParseContext ParseContext; struct ParseContext { sqlite3_tokenizer *pTokenizer; /* Tokenizer module */ const char **azCol; /* Array of column names for fts3 table */ + int bFts4; /* True to allow FTS4-only syntax */ int nCol; /* Number of entries in azCol[] */ int iDefaultCol; /* Default column to query */ int isNot; /* True if getNextNode() sees a unary - */ sqlite3_context *pCtx; /* Write error message here */ int nNest; /* Number of nested brackets */ @@ -178,13 +179,25 @@ if( iEndpPhrase->aToken[0].isPrefix = 1; iEnd++; } - if( !sqlite3_fts3_enable_parentheses && iStart>0 && z[iStart-1]=='-' ){ - pParse->isNot = 1; + + while( 1 ){ + if( !sqlite3_fts3_enable_parentheses + && iStart>0 && z[iStart-1]=='-' + ){ + pParse->isNot = 1; + iStart--; + }else if( pParse->bFts4 && iStart>0 && z[iStart-1]=='^' ){ + pRet->pPhrase->aToken[0].bFirst = 1; + iStart--; + }else{ + break; + } } + } nConsumed = iEnd; } pModule->xClose(pCursor); @@ -279,10 +292,11 @@ memcpy(&zTemp[nTemp], zByte, nByte); nTemp += nByte; pToken->n = nByte; pToken->isPrefix = (iEndbFirst = (iBegin>0 && zInput[iBegin-1]=='^'); nToken = ii+1; } } pModule->xClose(pCursor); @@ -300,12 +314,16 @@ p->pPhrase = (Fts3Phrase *)&p[1]; p->pPhrase->iColumn = pParse->iDefaultCol; p->pPhrase->nToken = nToken; zBuf = (char *)&p->pPhrase->aToken[nToken]; - memcpy(zBuf, zTemp, nTemp); - sqlite3_free(zTemp); + if( zTemp ){ + memcpy(zBuf, zTemp, nTemp); + sqlite3_free(zTemp); + }else{ + assert( nTemp==0 ); + } for(jj=0; jjpPhrase->nToken; jj++){ p->pPhrase->aToken[jj].z = zBuf; zBuf += p->pPhrase->aToken[jj].n; } @@ -726,10 +744,11 @@ ** match any table column. */ int sqlite3Fts3ExprParse( sqlite3_tokenizer *pTokenizer, /* Tokenizer module */ char **azCol, /* Array of column names for fts3 table */ + int bFts4, /* True to allow FTS4-only syntax */ int nCol, /* Number of entries in azCol[] */ int iDefaultCol, /* Default column to query */ const char *z, int n, /* Text of MATCH query */ Fts3Expr **ppExpr /* OUT: Parsed query structure */ ){ @@ -739,10 +758,11 @@ sParse.pTokenizer = pTokenizer; sParse.azCol = (const char **)azCol; sParse.nCol = nCol; sParse.iDefaultCol = iDefaultCol; sParse.nNest = 0; + sParse.bFts4 = bFts4; if( z==0 ){ *ppExpr = 0; return SQLITE_OK; } if( n<0 ){ @@ -928,11 +948,11 @@ for(ii=0; iipCsr, pExpr, p->iCol); if( pCsr ){ int iFirst = 0; pPhrase->pList = pCsr; fts3GetDeltaPosition(&pCsr, &iFirst); + assert( iFirst>=0 ); pPhrase->pHead = pCsr; pPhrase->pTail = pCsr; pPhrase->iHead = iFirst; pPhrase->iTail = iFirst; }else{ @@ -846,11 +847,11 @@ pStmt = *ppStmt; assert( sqlite3_data_count(pStmt)==1 ); a = sqlite3_column_blob(pStmt, 0); a += sqlite3Fts3GetVarint(a, &nDoc); - if( nDoc==0 ) return SQLITE_CORRUPT_VTAB; + if( nDoc==0 ) return FTS_CORRUPT_VTAB; *pnDoc = (u32)nDoc; if( paLen ) *paLen = a; return SQLITE_OK; } @@ -1407,11 +1408,11 @@ } } if( !pTerm ){ /* All offsets for this column have been gathered. */ - break; + rc = SQLITE_DONE; }else{ assert( iCurrent<=iMinPos ); if( 0==(0xFE&*pTerm->pList) ){ pTerm->pList = 0; }else{ @@ -1424,12 +1425,12 @@ char aBuffer[64]; sqlite3_snprintf(sizeof(aBuffer), aBuffer, "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart ); rc = fts3StringAppend(&res, aBuffer, -1); - }else if( rc==SQLITE_DONE ){ - rc = SQLITE_CORRUPT_VTAB; + }else if( rc==SQLITE_DONE && pTab->zContentTbl==0 ){ + rc = FTS_CORRUPT_VTAB; } } } if( rc==SQLITE_DONE ){ rc = SQLITE_OK; Index: ext/fts3/fts3_write.c ================================================================== --- ext/fts3/fts3_write.c +++ ext/fts3/fts3_write.c @@ -254,11 +254,11 @@ /* 2 */ "DELETE FROM %Q.'%q_content'", /* 3 */ "DELETE FROM %Q.'%q_segments'", /* 4 */ "DELETE FROM %Q.'%q_segdir'", /* 5 */ "DELETE FROM %Q.'%q_docsize'", /* 6 */ "DELETE FROM %Q.'%q_stat'", -/* 7 */ "SELECT %s FROM %Q.'%q_content' AS x WHERE rowid=?", +/* 7 */ "SELECT %s WHERE rowid=?", /* 8 */ "SELECT (SELECT max(idx) FROM %Q.'%q_segdir' WHERE level = ?) + 1", /* 9 */ "INSERT INTO %Q.'%q_segments'(blockid, block) VALUES(?, ?)", /* 10 */ "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)", /* 11 */ "INSERT INTO %Q.'%q_segdir' VALUES(?,?,?,?,?,?)", @@ -296,11 +296,11 @@ if( !pStmt ){ char *zSql; if( eStmt==SQL_CONTENT_INSERT ){ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist); }else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){ - zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist, p->zDb, p->zName); + zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist); }else{ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName); } if( !zSql ){ rc = SQLITE_NOMEM; @@ -339,11 +339,11 @@ sqlite3_bind_int64(pStmt, 1, iDocid); } rc = sqlite3_step(pStmt); if( rc!=SQLITE_ROW || sqlite3_column_type(pStmt, 0)!=SQLITE_BLOB ){ rc = sqlite3_reset(pStmt); - if( rc==SQLITE_OK ) rc = SQLITE_CORRUPT_VTAB; + if( rc==SQLITE_OK ) rc = FTS_CORRUPT_VTAB; pStmt = 0; }else{ rc = SQLITE_OK; } } @@ -407,21 +407,28 @@ ** We try to avoid this because if FTS3 returns any error when committing ** a transaction, the whole transaction will be rolled back. And this is ** not what users expect when they get SQLITE_LOCKED_SHAREDCACHE. It can ** still happen if the user reads data directly from the %_segments or ** %_segdir tables instead of going through FTS3 though. +** +** This reasoning does not apply to a content=xxx table. */ int sqlite3Fts3ReadLock(Fts3Table *p){ int rc; /* Return code */ sqlite3_stmt *pStmt; /* Statement used to obtain lock */ - rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pStmt, 0); - if( rc==SQLITE_OK ){ - sqlite3_bind_null(pStmt, 1); - sqlite3_step(pStmt); - rc = sqlite3_reset(pStmt); + if( p->zContentTbl==0 ){ + rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_null(pStmt, 1); + sqlite3_step(pStmt); + rc = sqlite3_reset(pStmt); + } + }else{ + rc = SQLITE_OK; } + return rc; } /* ** Set *ppStmt to a statement handle that may be used to iterate through @@ -777,10 +784,22 @@ sqlite3_value **apVal, /* Array of values to insert */ sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */ ){ int rc; /* Return code */ sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */ + + if( p->zContentTbl ){ + sqlite3_value *pRowid = apVal[p->nColumn+3]; + if( sqlite3_value_type(pRowid)==SQLITE_NULL ){ + pRowid = apVal[1]; + } + if( sqlite3_value_type(pRowid)!=SQLITE_INTEGER ){ + return SQLITE_CONSTRAINT; + } + *piDocid = sqlite3_value_int64(pRowid); + return SQLITE_OK; + } /* Locate the statement handle used to insert data into the %_content ** table. The SQL for this statement is: ** ** INSERT INTO %_content VALUES(?, ?, ?, ...) @@ -828,18 +847,20 @@ /* ** Remove all data from the FTS3 table. Clear the hash table containing ** pending terms. */ -static int fts3DeleteAll(Fts3Table *p){ +static int fts3DeleteAll(Fts3Table *p, int bContent){ int rc = SQLITE_OK; /* Return code */ /* Discard the contents of the pending-terms hash table. */ sqlite3Fts3PendingTermsClear(p); - /* Delete everything from the %_content, %_segments and %_segdir tables. */ - fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0); + /* Delete everything from the shadow tables. Except, leave %_content as + ** is if bContent is false. */ + assert( p->zContentTbl==0 || bContent==0 ); + if( bContent ) fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0); fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGMENTS, 0); fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0); if( p->bHasDocsize ){ fts3SqlExec(&rc, p, SQL_DELETE_ALL_DOCSIZE, 0); } @@ -1143,11 +1164,11 @@ pNext += sqlite3Fts3GetVarint32(pNext, &nPrefix); pNext += sqlite3Fts3GetVarint32(pNext, &nSuffix); if( nPrefix<0 || nSuffix<=0 || &pNext[nSuffix]>&pReader->aNode[pReader->nNode] ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } if( nPrefix+nSuffix>pReader->nTermAlloc ){ int nNew = (nPrefix+nSuffix)*2; char *zNew = sqlite3_realloc(pReader->zTerm, nNew); @@ -1173,11 +1194,11 @@ ** of these statements is untrue, then the data structure is corrupt. */ if( &pReader->aDoclist[pReader->nDoclist]>&pReader->aNode[pReader->nNode] || (pReader->nPopulate==0 && pReader->aDoclist[pReader->nDoclist-1]) ){ - return SQLITE_CORRUPT_VTAB; + return FTS_CORRUPT_VTAB; } return SQLITE_OK; } /* @@ -2123,16 +2144,22 @@ ** error occurs, an SQLite error code is returned. */ static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){ sqlite3_stmt *pStmt; int rc; - rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid); - if( rc==SQLITE_OK ){ - if( SQLITE_ROW==sqlite3_step(pStmt) ){ - *pisEmpty = sqlite3_column_int(pStmt, 0); + if( p->zContentTbl ){ + /* If using the content=xxx option, assume the table is never empty */ + *pisEmpty = 0; + rc = SQLITE_OK; + }else{ + rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid); + if( rc==SQLITE_OK ){ + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + *pisEmpty = sqlite3_column_int(pStmt, 0); + } + rc = sqlite3_reset(pStmt); } - rc = sqlite3_reset(pStmt); } return rc; } /* @@ -2480,10 +2507,11 @@ int isIgnoreEmpty = (pCsr->pFilter->flags & FTS3_SEGMENT_IGNORE_EMPTY); int isRequirePos = (pCsr->pFilter->flags & FTS3_SEGMENT_REQUIRE_POS); int isColFilter = (pCsr->pFilter->flags & FTS3_SEGMENT_COLUMN_FILTER); int isPrefix = (pCsr->pFilter->flags & FTS3_SEGMENT_PREFIX); int isScan = (pCsr->pFilter->flags & FTS3_SEGMENT_SCAN); + int isFirst = (pCsr->pFilter->flags & FTS3_SEGMENT_FIRST); Fts3SegReader **apSegment = pCsr->apSegment; int nSegment = pCsr->nSegment; Fts3SegFilter *pFilter = pCsr->pFilter; int (*xCmp)(Fts3SegReader *, Fts3SegReader *) = ( @@ -2539,10 +2567,11 @@ } assert( isIgnoreEmpty || (isRequirePos && !isColFilter) ); if( nMerge==1 && !isIgnoreEmpty + && !isFirst && (p->bDescIdx==0 || fts3SegReaderIsPending(apSegment[0])==0) ){ pCsr->nDoclist = apSegment[0]->nDoclist; if( fts3SegReaderIsPending(apSegment[0]) ){ rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist); @@ -2604,16 +2633,28 @@ if( !aNew ){ return SQLITE_NOMEM; } pCsr->aBuffer = aNew; } - nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta); - iPrev = iDocid; - if( isRequirePos ){ - memcpy(&pCsr->aBuffer[nDoclist], pList, nList); - nDoclist += nList; - pCsr->aBuffer[nDoclist++] = '\0'; + + if( isFirst ){ + char *a = &pCsr->aBuffer[nDoclist]; + int nWrite; + + nWrite = sqlite3Fts3FirstFilter(iDelta, pList, nList, a); + if( nWrite ){ + iPrev = iDocid; + nDoclist += nWrite; + } + }else{ + nDoclist += sqlite3Fts3PutVarint(&pCsr->aBuffer[nDoclist], iDelta); + iPrev = iDocid; + if( isRequirePos ){ + memcpy(&pCsr->aBuffer[nDoclist], pList, nList); + nDoclist += nList; + pCsr->aBuffer[nDoclist++] = '\0'; + } } } fts3SegReaderSort(apSegment, nMerge, j, xCmp); } @@ -2785,13 +2826,13 @@ ** Insert the sizes (in tokens) for each column of the document ** with docid equal to p->iPrevDocid. The sizes are encoded as ** a blob of varints. */ static void fts3InsertDocsize( - int *pRC, /* Result code */ - Fts3Table *p, /* Table into which to insert */ - u32 *aSz /* Sizes of each column */ + int *pRC, /* Result code */ + Fts3Table *p, /* Table into which to insert */ + u32 *aSz /* Sizes of each column, in tokens */ ){ char *pBlob; /* The BLOB encoding of the document size */ int nBlob; /* Number of bytes in the BLOB */ sqlite3_stmt *pStmt; /* Statement used to insert the encoding */ int rc; /* Result code from subfunctions */ @@ -2908,10 +2949,90 @@ sqlite3Fts3SegmentsClose(p); sqlite3Fts3PendingTermsClear(p); return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc; } + +/* +** This function is called when the user executes the following statement: +** +** INSERT INTO () VALUES('rebuild'); +** +** The entire FTS index is discarded and rebuilt. If the table is one +** created using the content=xxx option, then the new index is based on +** the current contents of the xxx table. Otherwise, it is rebuilt based +** on the contents of the %_content table. +*/ +static int fts3DoRebuild(Fts3Table *p){ + int rc; /* Return Code */ + + rc = fts3DeleteAll(p, 0); + if( rc==SQLITE_OK ){ + u32 *aSz = 0; + u32 *aSzIns = 0; + u32 *aSzDel = 0; + sqlite3_stmt *pStmt = 0; + int nEntry = 0; + + /* Compose and prepare an SQL statement to loop through the content table */ + char *zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist); + if( !zSql ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + } + + if( rc==SQLITE_OK ){ + int nByte = sizeof(u32) * (p->nColumn+1)*3; + aSz = (u32 *)sqlite3_malloc(nByte); + if( aSz==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(aSz, 0, nByte); + aSzIns = &aSz[p->nColumn+1]; + aSzDel = &aSzIns[p->nColumn+1]; + } + } + + while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + int iCol; + rc = fts3PendingTermsDocid(p, sqlite3_column_int64(pStmt, 0)); + aSz[p->nColumn] = 0; + for(iCol=0; rc==SQLITE_OK && iColnColumn; iCol++){ + const char *z = (const char *) sqlite3_column_text(pStmt, iCol+1); + rc = fts3PendingTermsAdd(p, z, iCol, &aSz[iCol]); + aSz[p->nColumn] += sqlite3_column_bytes(pStmt, iCol+1); + } + if( p->bHasDocsize ){ + fts3InsertDocsize(&rc, p, aSz); + } + if( rc!=SQLITE_OK ){ + sqlite3_finalize(pStmt); + pStmt = 0; + }else{ + nEntry++; + for(iCol=0; iCol<=p->nColumn; iCol++){ + aSzIns[iCol] += aSz[iCol]; + } + } + } + if( p->bHasStat ){ + fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nEntry); + } + sqlite3_free(aSz); + + if( pStmt ){ + int rc2 = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ){ + rc = rc2; + } + } + } + + return rc; +} /* ** Handle a 'special' INSERT of the form: ** ** "INSERT INTO tbl(tbl) VALUES()" @@ -2926,10 +3047,12 @@ if( !zVal ){ return SQLITE_NOMEM; }else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){ rc = fts3DoOptimize(p, 0); + }else if( nVal==7 && 0==sqlite3_strnicmp(zVal, "rebuild", 7) ){ + rc = fts3DoRebuild(p); #ifdef SQLITE_TEST }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){ p->nNodeSize = atoi(&zVal[9]); rc = SQLITE_OK; }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){ @@ -3006,10 +3129,11 @@ pTC->pTokenizer = pT; rc = pModule->xNext(pTC, &zToken, &nToken, &iDum1, &iDum2, &iPos); for(pDef=pCsr->pDeferred; pDef && rc==SQLITE_OK; pDef=pDef->pNext){ Fts3PhraseToken *pPT = pDef->pToken; if( (pDef->iCol>=p->nColumn || pDef->iCol==i) + && (pPT->bFirst==0 || iPos==0) && (pPT->n==nToken || (pPT->isPrefix && pPT->nz, pPT->n)) ){ fts3PendingListAppend(&pDef->pList, iDocid, i, iPos, &rc); } @@ -3097,18 +3221,22 @@ if( rc==SQLITE_OK ){ if( isEmpty ){ /* Deleting this row means the whole table is empty. In this case ** delete the contents of all three tables and throw away any ** data in the pendingTerms hash table. */ - rc = fts3DeleteAll(p); + rc = fts3DeleteAll(p, 1); *pnDoc = *pnDoc - 1; }else{ sqlite3_int64 iRemove = sqlite3_value_int64(pRowid); rc = fts3PendingTermsDocid(p, iRemove); fts3DeleteTerms(&rc, p, pRowid, aSzDel); - fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid); - if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1; + if( p->zContentTbl==0 ){ + fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid); + if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1; + }else{ + *pnDoc = *pnDoc - 1; + } if( p->bHasDocsize ){ fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid); } } } @@ -3127,11 +3255,10 @@ sqlite_int64 *pRowid /* OUT: The affected (or effected) rowid */ ){ Fts3Table *p = (Fts3Table *)pVtab; int rc = SQLITE_OK; /* Return Code */ int isRemove = 0; /* True for an UPDATE or DELETE */ - sqlite3_int64 iRemove = 0; /* Rowid removed by UPDATE or DELETE */ u32 *aSzIns = 0; /* Sizes of inserted documents */ u32 *aSzDel; /* Sizes of deleted documents */ int nChng = 0; /* Net change in number of documents */ int bInsertDone = 0; @@ -3165,11 +3292,11 @@ ** should be deleted from the database before inserting the new row. Or, ** if the on-conflict mode is other than REPLACE, then this method must ** detect the conflict and return SQLITE_CONSTRAINT before beginning to ** modify the database file. */ - if( nArg>1 ){ + if( nArg>1 && p->zContentTbl==0 ){ /* Find the value object that holds the new rowid value. */ sqlite3_value *pNewRowid = apVal[3+p->nColumn]; if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){ pNewRowid = apVal[1]; } @@ -3210,23 +3337,25 @@ /* If this is a DELETE or UPDATE operation, remove the old record. */ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){ assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ); rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel); isRemove = 1; - iRemove = sqlite3_value_int64(apVal[0]); } /* If this is an INSERT or UPDATE operation, insert the new record. */ if( nArg>1 && rc==SQLITE_OK ){ if( bInsertDone==0 ){ rc = fts3InsertData(p, apVal, pRowid); - if( rc==SQLITE_CONSTRAINT ) rc = SQLITE_CORRUPT_VTAB; + if( rc==SQLITE_CONSTRAINT && p->zContentTbl==0 ){ + rc = FTS_CORRUPT_VTAB; + } } - if( rc==SQLITE_OK && (!isRemove || *pRowid!=iRemove) ){ + if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){ rc = fts3PendingTermsDocid(p, *pRowid); } if( rc==SQLITE_OK ){ + assert( p->iPrevDocid==*pRowid ); rc = fts3InsertTerms(p, apVal, aSzIns); } if( p->bHasDocsize ){ fts3InsertDocsize(&rc, p, aSzIns); } Index: ext/rtree/rtree.c ================================================================== --- ext/rtree/rtree.c +++ ext/rtree/rtree.c @@ -1266,11 +1266,12 @@ pCsr->nConstraint = argc; if( !pCsr->aConstraint ){ rc = SQLITE_NOMEM; }else{ memset(pCsr->aConstraint, 0, sizeof(RtreeConstraint)*argc); - assert( (idxStr==0 && argc==0) || (int)strlen(idxStr)==argc*2 ); + assert( (idxStr==0 && argc==0) + || (idxStr && (int)strlen(idxStr)==argc*2) ); for(ii=0; iiaConstraint[ii]; p->op = idxStr[ii*2]; p->iCoord = idxStr[ii*2+1]-'a'; if( p->op==RTREE_MATCH ){ @@ -1567,11 +1568,14 @@ int iCell; sqlite3_int64 iBest = 0; float fMinGrowth = 0.0; float fMinArea = 0.0; +#if VARIANT_RSTARTREE_CHOOSESUBTREE float fMinOverlap = 0.0; + float overlap; +#endif int nCell = NCELL(pNode); RtreeCell cell; RtreeNode *pChild; @@ -1599,33 +1603,34 @@ */ for(iCell=0; iCelliDepth-1) ){ overlap = cellOverlapEnlargement(pRtree,&cell,pCell,aCell,nCell,iCell); + }else{ + overlap = 0.0; } if( (iCell==0) || (overlappSrcDb->mutex); sqlite3BtreeEnter(p->pSrc); - mutex = p->pSrcDb->mutex; + MUTEX_LOGIC( mutex = p->pSrcDb->mutex; ) if( p->pDestDb ){ sqlite3_mutex_enter(p->pDestDb->mutex); } /* Detach this backup from the source pager. */ @@ -702,13 +702,15 @@ sqlite3_backup_step(&b, 0x7FFFFFFF); assert( b.rc!=SQLITE_OK ); rc = sqlite3_backup_finish(&b); if( rc==SQLITE_OK ){ pTo->pBt->pageSizeFixed = 0; + }else{ + sqlite3PagerClearCache(sqlite3BtreePager(b.pDest)); } assert( sqlite3BtreeIsInTrans(pTo)==0 ); sqlite3BtreeLeave(pFrom); sqlite3BtreeLeave(pTo); return rc; } #endif /* SQLITE_OMIT_VACUUM */ Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -1764,21 +1764,23 @@ */ if( isMemdb==0 && isTempDb==0 ){ if( vfsFlags & SQLITE_OPEN_SHAREDCACHE ){ int nFullPathname = pVfs->mxPathname+1; char *zFullPathname = sqlite3Malloc(nFullPathname); - sqlite3_mutex *mutexShared; + MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) p->sharable = 1; if( !zFullPathname ){ sqlite3_free(p); return SQLITE_NOMEM; } sqlite3OsFullPathname(pVfs, zFilename, nFullPathname, zFullPathname); +#if SQLITE_THREADSAFE mutexOpen = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN); sqlite3_mutex_enter(mutexOpen); mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); sqlite3_mutex_enter(mutexShared); +#endif for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){ assert( pBt->nRef>0 ); if( 0==strcmp(zFullPathname, sqlite3PagerFilename(pBt->pPager)) && sqlite3PagerVfs(pBt->pPager)==pVfs ){ int iDb; @@ -1880,13 +1882,13 @@ #if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO) /* Add the new BtShared object to the linked list sharable BtShareds. */ if( p->sharable ){ - sqlite3_mutex *mutexShared; + MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) pBt->nRef = 1; - mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);) if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){ pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST); if( pBt->mutex==0 ){ rc = SQLITE_NOMEM; db->mallocFailed = 0; @@ -1964,16 +1966,16 @@ ** true if the BtShared.nRef counter reaches zero and return ** false if it is still positive. */ static int removeFromSharingList(BtShared *pBt){ #ifndef SQLITE_OMIT_SHARED_CACHE - sqlite3_mutex *pMaster; + MUTEX_LOGIC( sqlite3_mutex *pMaster; ) BtShared *pList; int removed = 0; assert( sqlite3_mutex_notheld(pBt->mutex) ); - pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(pMaster); pBt->nRef--; if( pBt->nRef<=0 ){ if( GLOBAL(BtShared*,sqlite3SharedCacheList)==pBt ){ GLOBAL(BtShared*,sqlite3SharedCacheList) = pBt->pNext; @@ -4583,11 +4585,10 @@ } } if( c==0 ){ if( pPage->intKey && !pPage->leaf ){ lwr = idx; - upr = lwr - 1; break; }else{ *pRes = 0; rc = SQLITE_OK; goto moveto_finish; @@ -4601,11 +4602,11 @@ if( lwr>upr ){ break; } pCur->aiIdx[pCur->iPage] = (u16)(idx = (lwr+upr)/2); } - assert( lwr==upr+1 ); + assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); assert( pPage->isInit ); if( pPage->leaf ){ chldPg = 0; }else if( lwr>=pPage->nCell ){ chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); @@ -4866,10 +4867,12 @@ } if( rc ){ pTrunk = 0; goto end_allocate_page; } + assert( pTrunk!=0 ); + assert( pTrunk->aData!=0 ); k = get4byte(&pTrunk->aData[4]); /* # of leaves on this trunk page */ if( k==0 && !searchList ){ /* The trunk has no leaves and the list is not being searched. ** So extract the trunk page itself and use it as the newly @@ -5993,17 +5996,19 @@ ** This is safe because dropping a cell only overwrites the first ** four bytes of it, and this function does not need the first ** four bytes of the divider cell. So the pointer is safe to use ** later on. ** - ** Unless SQLite is compiled in secure-delete mode. In this case, + ** But not if we are in secure-delete mode. In secure-delete mode, ** the dropCell() routine will overwrite the entire cell with zeroes. ** In this case, temporarily copy the cell into the aOvflSpace[] ** buffer. It will be copied out again as soon as the aSpace[] buffer ** is allocated. */ if( pBt->secureDelete ){ - int iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); + int iOff; + + iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); if( (iOff+szNew[i])>(int)pBt->usableSize ){ rc = SQLITE_CORRUPT_BKPT; memset(apOld, 0, (i+1)*sizeof(MemPage*)); goto balance_cleanup; }else{ @@ -6419,10 +6424,11 @@ int isDivider = 0; while( i==iNextOld ){ /* Cell i is the cell immediately following the last cell on old ** sibling page j. If the siblings are not leaf pages of an ** intkey b-tree, then cell i was a divider cell. */ + assert( j+1 < ArraySize(apCopy) ); pOld = apCopy[++j]; iNextOld = i + !leafData + pOld->nCell + pOld->nOverflow; if( pOld->nOverflow ){ nOverflow = pOld->nOverflow; iOverflow = i + !leafData + pOld->aOvfl[0].idx; Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -2340,17 +2340,19 @@ */ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ Table *pTab = pIndex->pTable; /* The table that is indexed */ int iTab = pParse->nTab++; /* Btree cursor used for pTab */ int iIdx = pParse->nTab++; /* Btree cursor used for pIndex */ - int iSorter = iTab; /* Cursor opened by OpenSorter (if in use) */ + int iSorter; /* Cursor opened by OpenSorter (if in use) */ int addr1; /* Address of top of loop */ int addr2; /* Address to jump to for next iteration */ int tnum; /* Root page of index */ Vdbe *v; /* Generate code into this virtual machine */ KeyInfo *pKey; /* KeyInfo for index */ +#ifdef SQLITE_OMIT_MERGE_SORT int regIdxKey; /* Registers containing the index key */ +#endif int regRecord; /* Register holding assemblied index record */ sqlite3 *db = pParse->db; /* The database connection */ int iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); #ifndef SQLITE_OMIT_AUTHORIZATION @@ -2380,21 +2382,22 @@ #ifndef SQLITE_OMIT_MERGE_SORT /* Open the sorter cursor if we are to use one. */ iSorter = pParse->nTab++; sqlite3VdbeAddOp4(v, OP_SorterOpen, iSorter, 0, 0, (char*)pKey, P4_KEYINFO); +#else + iSorter = iTab; #endif /* Open the table. Loop through all rows of the table, inserting index ** records into the sorter. */ sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iTab, 0); - addr2 = addr1 + 1; regRecord = sqlite3GetTempReg(pParse); - regIdxKey = sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); #ifndef SQLITE_OMIT_MERGE_SORT + sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); sqlite3VdbeAddOp2(v, OP_SorterInsert, iSorter, regRecord); sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); sqlite3VdbeJumpHere(v, addr1); addr1 = sqlite3VdbeAddOp2(v, OP_SorterSort, iSorter, 0); if( pIndex->onError!=OE_None ){ @@ -2410,10 +2413,12 @@ } sqlite3VdbeAddOp2(v, OP_SorterData, iSorter, regRecord); sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 1); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); #else + regIdxKey = sqlite3GenerateIndexKey(pParse, pIndex, iTab, regRecord, 1); + addr2 = addr1 + 1; if( pIndex->onError!=OE_None ){ const int regRowid = regIdxKey + pIndex->nColumn; const int j2 = sqlite3VdbeCurrentAddr(v) + 2; void * const pRegKey = SQLITE_INT_TO_PTR(regIdxKey); @@ -2507,10 +2512,11 @@ ** before looking up the table. */ assert( pName1 && pName2 ); iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); if( iDb<0 ) goto exit_create_index; + assert( pName && pName->z ); #ifndef SQLITE_OMIT_TEMPDB /* If the index name was unqualified, check if the the table ** is a temp table. If so, set the database to 1. Do not do this ** if initialising a database schema. @@ -2534,10 +2540,11 @@ pTblName->a[0].zDatabase); if( !pTab || db->mallocFailed ) goto exit_create_index; assert( db->aDb[iDb].pSchema==pTab->pSchema ); }else{ assert( pName==0 ); + assert( pStart==0 ); pTab = pParse->pNewTable; if( !pTab ) goto exit_create_index; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); } pDb = &db->aDb[iDb]; @@ -2576,10 +2583,11 @@ ** own name. */ if( pName ){ zName = sqlite3NameFromToken(db, pName); if( zName==0 ) goto exit_create_index; + assert( pName->z!=0 ); if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto exit_create_index; } if( !db->init.busy ){ if( sqlite3FindTable(db, zName, 0)!=0 ){ @@ -3431,17 +3439,14 @@ /* ** Commit a transaction */ void sqlite3CommitTransaction(Parse *pParse){ - sqlite3 *db; Vdbe *v; assert( pParse!=0 ); - db = pParse->db; - assert( db!=0 ); -/* if( db->aDb[0].pBt==0 ) return; */ + assert( pParse->db!=0 ); if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "COMMIT", 0, 0) ){ return; } v = sqlite3GetVdbe(pParse); if( v ){ @@ -3451,17 +3456,14 @@ /* ** Rollback a transaction */ void sqlite3RollbackTransaction(Parse *pParse){ - sqlite3 *db; Vdbe *v; assert( pParse!=0 ); - db = pParse->db; - assert( db!=0 ); -/* if( db->aDb[0].pBt==0 ) return; */ + assert( pParse->db!=0 ); if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, "ROLLBACK", 0, 0) ){ return; } v = sqlite3GetVdbe(pParse); if( v ){ Index: src/date.c ================================================================== --- src/date.c +++ src/date.c @@ -287,16 +287,22 @@ } return 0; } /* -** Set the time to the current time reported by the VFS +** Set the time to the current time reported by the VFS. +** +** Return the number of errors. */ -static void setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ +static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ sqlite3 *db = sqlite3_context_db_handle(context); - sqlite3OsCurrentTimeInt64(db->pVfs, &p->iJD); - p->validJD = 1; + if( sqlite3OsCurrentTimeInt64(db->pVfs, &p->iJD)==SQLITE_OK ){ + p->validJD = 1; + return 0; + }else{ + return 1; + } } /* ** Attempt to parse the given string into a Julian Day Number. Return ** the number of errors. @@ -322,12 +328,11 @@ if( parseYyyyMmDd(zDate,p)==0 ){ return 0; }else if( parseHhMmSs(zDate, p)==0 ){ return 0; }else if( sqlite3StrICmp(zDate,"now")==0){ - setDateTimeToCurrent(context, p); - return 0; + return setDateTimeToCurrent(context, p); }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){ p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); p->validJD = 1; return 0; } @@ -750,12 +755,13 @@ int i; const unsigned char *z; int eType; memset(p, 0, sizeof(*p)); if( argc==0 ){ - setDateTimeToCurrent(context, p); - }else if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT + return setDateTimeToCurrent(context, p); + } + if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT || eType==SQLITE_INTEGER ){ p->iJD = (sqlite3_int64)(sqlite3_value_double(argv[0])*86400000.0 + 0.5); p->validJD = 1; }else{ z = sqlite3_value_text(argv[0]); @@ -1063,35 +1069,32 @@ ){ time_t t; char *zFormat = (char *)sqlite3_user_data(context); sqlite3 *db; sqlite3_int64 iT; + struct tm *pTm; + struct tm sNow; char zBuf[20]; UNUSED_PARAMETER(argc); UNUSED_PARAMETER(argv); db = sqlite3_context_db_handle(context); - sqlite3OsCurrentTimeInt64(db->pVfs, &iT); + if( sqlite3OsCurrentTimeInt64(db->pVfs, &iT) ) return; t = iT/1000 - 10000*(sqlite3_int64)21086676; #ifdef HAVE_GMTIME_R - { - struct tm sNow; - gmtime_r(&t, &sNow); - strftime(zBuf, 20, zFormat, &sNow); - } + pTm = gmtime_r(&t, &sNow); #else - { - struct tm *pTm; - sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); - pTm = gmtime(&t); - strftime(zBuf, 20, zFormat, pTm); - sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); - } + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); + pTm = gmtime(&t); + if( pTm ) memcpy(&sNow, pTm, sizeof(sNow)); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); #endif - - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + if( pTm ){ + strftime(zBuf, 20, zFormat, &sNow); + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + } } #endif /* ** This function registered all of the above C functions as SQL Index: src/expr.c ================================================================== --- src/expr.c +++ src/expr.c @@ -401,11 +401,12 @@ pNew->flags |= EP_IntValue; pNew->u.iValue = iValue; }else{ int c; pNew->u.zToken = (char*)&pNew[1]; - memcpy(pNew->u.zToken, pToken->z, pToken->n); + assert( pToken->z!=0 || pToken->n==0 ); + if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); pNew->u.zToken[pToken->n] = 0; if( dequote && nExtra>=3 && ((c = pToken->z[0])=='\'' || c=='"' || c=='[' || c=='`') ){ sqlite3Dequote(pNew->u.zToken); if( c=='"' ) pNew->flags |= EP_DblQuoted; @@ -1440,15 +1441,23 @@ ** ephemeral table. */ p = (ExprHasProperty(pX, EP_xIsSelect) ? pX->x.pSelect : 0); if( ALWAYS(pParse->nErr==0) && isCandidateForInOpt(p) ){ sqlite3 *db = pParse->db; /* Database connection */ - Expr *pExpr = p->pEList->a[0].pExpr; /* Expression */ - int iCol = pExpr->iColumn; /* Index of column */ Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ - Table *pTab = p->pSrc->a[0].pTab; /* Table . */ + Table *pTab; /* Table
. */ + Expr *pExpr; /* Expression */ + int iCol; /* Index of column */ int iDb; /* Database idx for pTab */ + + assert( p ); /* Because of isCandidateForInOpt(p) */ + assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ + assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ + assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ + pTab = p->pSrc->a[0].pTab; + pExpr = p->pEList->a[0].pExpr; + iCol = pExpr->iColumn; /* Code an OP_VerifyCookie and OP_TableLock for
. */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); @@ -3451,11 +3460,11 @@ if( !ExprHasProperty(pB, EP_IntValue) || pA->u.iValue!=pB->u.iValue ){ return 2; } }else if( pA->op!=TK_COLUMN && pA->u.zToken ){ if( ExprHasProperty(pB, EP_IntValue) || NEVER(pB->u.zToken==0) ) return 2; - if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ){ + if( strcmp(pA->u.zToken,pB->u.zToken)!=0 ){ return 2; } } if( (pA->flags & EP_ExpCollate)!=(pB->flags & EP_ExpCollate) ) return 1; if( (pA->flags & EP_ExpCollate)!=0 && pA->pColl!=pB->pColl ) return 2; Index: src/fkey.c ================================================================== --- src/fkey.c +++ src/fkey.c @@ -1122,10 +1122,11 @@ sqlite3SelectDelete(db, pSelect); if( db->mallocFailed==1 ){ fkTriggerDelete(db, pTrigger); return 0; } + assert( pStep!=0 ); switch( action ){ case OE_Restrict: pStep->op = TK_SELECT; break; Index: src/func.c ================================================================== --- src/func.c +++ src/func.c @@ -330,20 +330,19 @@ /* Verify that the call to _bytes() does not invalidate the _text() pointer */ assert( z2==(char*)sqlite3_value_text(argv[0]) ); if( z2 ){ z1 = contextMalloc(context, ((i64)n)+1); if( z1 ){ - memcpy(z1, z2, n+1); - for(i=0; z1[i]; i++){ - z1[i] = (char)sqlite3Toupper(z1[i]); + for(i=0; iyyerrcnt = -1; yypParser->yystack[0].stateno = 0; yypParser->yystack[0].major = 0; } yyminorunion.yy0 = yyminor; +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) yyendofinput = (yymajor==0); +#endif ParseARG_STORE; #ifndef NDEBUG if( yyTraceFILE ){ fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]); @@ -749,11 +753,10 @@ #endif do{ yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); if( yyactyyerrcnt--; yymajor = YYNOCODE; }else if( yyact < YYNSTATE + YYNRULE ){ yy_reduce(yypParser,yyact-YYNSTATE); Index: src/loadext.c ================================================================== --- src/loadext.c +++ src/loadext.c @@ -401,11 +401,11 @@ sqlite3_vfs *pVfs = db->pVfs; void *handle; int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*); char *zErrmsg = 0; void **aHandle; - const int nMsg = 300; + int nMsg = 300 + sqlite3Strlen30(zFile); if( pzErrMsg ) *pzErrMsg = 0; /* Ticket #1863. To avoid a creating security problems for older ** applications that relink against newer versions of SQLite, the @@ -438,10 +438,11 @@ } xInit = (int(*)(sqlite3*,char**,const sqlite3_api_routines*)) sqlite3OsDlSym(pVfs, handle, zProc); if( xInit==0 ){ if( pzErrMsg ){ + nMsg += sqlite3Strlen30(zProc); *pzErrMsg = zErrmsg = sqlite3_malloc(nMsg); if( zErrmsg ){ sqlite3_snprintf(nMsg, zErrmsg, "no entry point [%s] in shared library [%s]", zProc,zFile); sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -104,11 +104,11 @@ ** ** * Recursive calls to this routine from thread X return immediately ** without blocking. */ int sqlite3_initialize(void){ - sqlite3_mutex *pMaster; /* The main static mutex */ + MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ int rc; /* Result code */ #ifdef SQLITE_OMIT_WSD rc = sqlite3_wsd_init(4096, 24); if( rc!=SQLITE_OK ){ @@ -138,11 +138,11 @@ ** This operation is protected by the STATIC_MASTER mutex. Note that ** MutexAlloc() is called for a static mutex prior to initializing the ** malloc subsystem - this implies that the allocation of a static ** mutex must not require support from the malloc subsystem. */ - pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(pMaster); sqlite3GlobalConfig.isMutexInit = 1; if( !sqlite3GlobalConfig.isMallocInit ){ rc = sqlite3MallocInit(); } @@ -1212,17 +1212,17 @@ sqlite3 *db, const char *zName, int nArg ){ int nName = sqlite3Strlen30(zName); - int rc; + int rc = SQLITE_OK; sqlite3_mutex_enter(db->mutex); if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){ - sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, - 0, sqlite3InvalidFunction, 0, 0, 0); + rc = sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, + 0, sqlite3InvalidFunction, 0, 0, 0); } - rc = sqlite3ApiExit(db, SQLITE_OK); + rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; } #ifndef SQLITE_OMIT_TRACE @@ -2301,10 +2301,11 @@ if( db ){ assert( db->mutex!=0 || isThreadsafe==0 || sqlite3GlobalConfig.bFullMutex==0 ); sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); + assert( db!=0 || rc==SQLITE_NOMEM ); if( rc==SQLITE_NOMEM ){ sqlite3_close(db); db = 0; }else if( rc!=SQLITE_OK ){ db->magic = SQLITE_MAGIC_SICK; Index: src/mutex.h ================================================================== --- src/mutex.h +++ src/mutex.h @@ -58,14 +58,17 @@ /* ** If this is a no-op implementation, implement everything as macros. */ #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) #define sqlite3_mutex_free(X) -#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_enter(X) #define sqlite3_mutex_try(X) SQLITE_OK -#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_leave(X) #define sqlite3_mutex_held(X) ((void)(X),1) #define sqlite3_mutex_notheld(X) ((void)(X),1) #define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) #define sqlite3MutexInit() SQLITE_OK #define sqlite3MutexEnd() +#define MUTEX_LOGIC(X) +#else +#define MUTEX_LOGIC(X) X #endif /* defined(SQLITE_MUTEX_OMIT) */ Index: src/os.c ================================================================== --- src/os.c +++ src/os.c @@ -295,16 +295,16 @@ ** Register a VFS with the system. It is harmless to register the same ** VFS multiple times. The new VFS becomes the default if makeDflt is ** true. */ int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){ - sqlite3_mutex *mutex = 0; + MUTEX_LOGIC(sqlite3_mutex *mutex;) #ifndef SQLITE_OMIT_AUTOINIT int rc = sqlite3_initialize(); if( rc ) return rc; #endif - mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(mutex); vfsUnlink(pVfs); if( makeDflt || vfsList==0 ){ pVfs->pNext = vfsList; vfsList = pVfs; Index: src/os_unix.c ================================================================== --- src/os_unix.c +++ src/os_unix.c @@ -526,11 +526,11 @@ return sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); } #endif -#ifdef SQLITE_DEBUG +#if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) /* ** Helper function for printing out trace information from debugging ** binaries. This returns the string represetation of the supplied ** integer lock-type. */ @@ -1361,18 +1361,18 @@ ** locking a random byte from a range, concurrent SHARED locks may exist ** even if the locking primitive used is always a write-lock. */ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; - unixInodeInfo *pInode = pFile->pInode; + unixInodeInfo *pInode; struct flock lock; int tErrno = 0; assert( pFile ); OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (unix)\n", pFile->h, azFileLock(eFileLock), azFileLock(pFile->eFileLock), - azFileLock(pInode->eFileLock), pInode->nShared , getpid())); + azFileLock(pFile->pInode->eFileLock), pFile->pInode->nShared , getpid())); /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the end_lock: exit path, as ** unixEnterMutex() hasn't been called yet. */ @@ -1572,11 +1572,10 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ unixFile *pFile = (unixFile*)id; unixInodeInfo *pInode; struct flock lock; int rc = SQLITE_OK; - int h; assert( pFile ); OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (unix)\n", pFile->h, eFileLock, pFile->eFileLock, pFile->pInode->eFileLock, pFile->pInode->nShared, getpid())); @@ -1584,18 +1583,14 @@ assert( eFileLock<=SHARED_LOCK ); if( pFile->eFileLock<=eFileLock ){ return SQLITE_OK; } unixEnterMutex(); - h = pFile->h; pInode = pFile->pInode; assert( pInode->nShared!=0 ); if( pFile->eFileLock>SHARED_LOCK ){ assert( pInode->eFileLock==pFile->eFileLock ); - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); #ifndef NDEBUG /* When reducing a lock such that other processes can start ** reading the database file again, make sure that the ** transaction counter was updated if any part of the database @@ -1602,15 +1597,10 @@ ** file changed. If the transaction counter is not updated, ** other connections to the same file might not realize that ** the file has changed and hence might not know to flush their ** cache. The use of a stale cache can lead to database corruption. */ -#if 0 - assert( pFile->inNormalWrite==0 - || pFile->dbUpdate==0 - || pFile->transCntrChng==1 ); -#endif pFile->inNormalWrite = 0; #endif /* downgrading to a shared lock on NFS involves clearing the write lock ** before establishing the readlock - to avoid a race condition we downgrade @@ -1708,13 +1698,10 @@ pInode->nShared--; if( pInode->nShared==0 ){ lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = lock.l_len = 0L; - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); if( unixFileLock(pFile, &lock)==0 ){ pInode->eFileLock = NO_LOCK; }else{ rc = SQLITE_IOERR_UNLOCK; pFile->lastErrno = errno; @@ -3852,20 +3839,19 @@ rc = SQLITE_NOMEM; goto shm_open_err; } if( pInode->bProcessLock==0 ){ - pShmNode->h = robust_open(zShmFilename, O_RDWR|O_CREAT, - (sStat.st_mode & 0777)); + const char *zRO; + int openFlags = O_RDWR | O_CREAT; + zRO = sqlite3_uri_parameter(pDbFd->zPath, "readonly_shm"); + if( zRO && sqlite3GetBoolean(zRO) ){ + openFlags = O_RDONLY; + pShmNode->isReadonly = 1; + } + pShmNode->h = robust_open(zShmFilename, openFlags, (sStat.st_mode&0777)); if( pShmNode->h<0 ){ - const char *zRO; - zRO = sqlite3_uri_parameter(pDbFd->zPath, "readonly_shm"); - if( zRO && sqlite3GetBoolean(zRO) ){ - pShmNode->h = robust_open(zShmFilename, O_RDONLY, - (sStat.st_mode & 0777)); - pShmNode->isReadonly = 1; - } if( pShmNode->h<0 ){ rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShmFilename); goto shm_open_err; } } @@ -4546,10 +4532,13 @@ assert( zFilename==0 || zFilename[0]=='/' || pVfs->pAppData==(void*)&autolockIoFinder ); #else assert( zFilename==0 || zFilename[0]=='/' ); #endif + + /* No locking occurs in temporary files */ + assert( zFilename!=0 || noLock ); OSTRACE(("OPEN %-3d %s\n", h, zFilename)); pNew->h = h; pNew->zPath = zFilename; if( memcmp(pVfs->zName,"unix-excl",10)==0 ){ @@ -4648,10 +4637,11 @@ /* Dotfile locking uses the file path so it needs to be included in ** the dotlockLockingContext */ char *zLockFile; int nFilename; + assert( zFilename!=0 ); nFilename = (int)strlen(zFilename) + 6; zLockFile = (char *)sqlite3_malloc(nFilename); if( zLockFile==0 ){ rc = SQLITE_NOMEM; }else{ @@ -4882,17 +4872,17 @@ ** "-journal" ** "-wal" ** "-journalNN" ** "-walNN" ** - ** where NN is a 4 digit decimal number. The NN naming schemes are + ** where NN is a decimal number. The NN naming schemes are ** used by the test_multiplex.c module. */ nDb = sqlite3Strlen30(zPath) - 1; #ifdef SQLITE_ENABLE_8_3_NAMES - while( nDb>0 && zPath[nDb]!='-' && zPath[nDb]!='/' ) nDb--; - if( nDb==0 || zPath[nDb]=='/' ) return SQLITE_OK; + while( nDb>0 && !sqlite3Isalnum(zPath[nDb]) ) nDb--; + if( nDb==0 || zPath[nDb]!='-' ) return SQLITE_OK; #else while( zPath[nDb]!='-' ){ assert( nDb>0 ); assert( zPath[nDb]!='\n' ); nDb--; @@ -5427,14 +5417,16 @@ ** the current time and date as a Julian Day number times 86_400_000. In ** other words, write into *piNow the number of milliseconds since the Julian ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return 0. Return 1 if the time and date cannot be found. +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** cannot be found. */ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; + int rc = SQLITE_OK; #if defined(NO_GETTOD) time_t t; time(&t); *piNow = ((sqlite3_int64)t)*1000 + unixEpoch; #elif OS_VXWORKS @@ -5441,34 +5433,38 @@ struct timespec sNow; clock_gettime(CLOCK_REALTIME, &sNow); *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_nsec/1000000; #else struct timeval sNow; - gettimeofday(&sNow, 0); - *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + if( gettimeofday(&sNow, 0)==0 ){ + *piNow = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + }else{ + rc = SQLITE_ERROR; + } #endif #ifdef SQLITE_TEST if( sqlite3_current_time ){ *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; } #endif UNUSED_PARAMETER(NotUsed); - return 0; + return rc; } /* ** Find the current time (in Universal Coordinated Time). Write the ** current time and date as a Julian Day number into *prNow and ** return 0. Return 1 if the time and date cannot be found. */ static int unixCurrentTime(sqlite3_vfs *NotUsed, double *prNow){ - sqlite3_int64 i; + sqlite3_int64 i = 0; + int rc; UNUSED_PARAMETER(NotUsed); - unixCurrentTimeInt64(0, &i); + rc = unixCurrentTimeInt64(0, &i); *prNow = i/86400000.0; - return 0; + return rc; } /* ** We added the xGetLastError() method with the intention of providing ** better low-level error messages when operating-system problems come up Index: src/os_win.c ================================================================== --- src/os_win.c +++ src/os_win.c @@ -2979,11 +2979,11 @@ } static void winDlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){ UNUSED_PARAMETER(pVfs); getLastErrorMsg(nBuf, zBufOut); } -void (*winDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol))(void){ +static void (*winDlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol))(void){ UNUSED_PARAMETER(pVfs); #if SQLITE_OS_WINCE /* The GetProcAddressA() routine is only available on wince. */ return (void(*)(void))GetProcAddressA((HANDLE)pHandle, zSymbol); #else @@ -2990,11 +2990,11 @@ /* All other windows platforms expect GetProcAddress() to take ** an Ansi string regardless of the _UNICODE setting */ return (void(*)(void))GetProcAddress((HANDLE)pHandle, zSymbol); #endif } -void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ +static void winDlClose(sqlite3_vfs *pVfs, void *pHandle){ UNUSED_PARAMETER(pVfs); FreeLibrary((HANDLE)pHandle); } #else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */ #define winDlOpen 0 @@ -3064,11 +3064,12 @@ ** the current time and date as a Julian Day number times 86_400_000. In ** other words, write into *piNow the number of milliseconds since the Julian ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return 0. Return 1 if the time and date cannot be found. +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** cannot be found. */ static int winCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *piNow){ /* FILETIME structure is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (= JD 2305813.5). */ @@ -3084,11 +3085,11 @@ #if SQLITE_OS_WINCE SYSTEMTIME time; GetSystemTime(&time); /* if SystemTimeToFileTime() fails, it returns zero. */ if (!SystemTimeToFileTime(&time,&ft)){ - return 1; + return SQLITE_ERROR; } #else GetSystemTimeAsFileTime( &ft ); #endif @@ -3100,19 +3101,19 @@ if( sqlite3_current_time ){ *piNow = 1000*(sqlite3_int64)sqlite3_current_time + unixEpoch; } #endif UNUSED_PARAMETER(pVfs); - return 0; + return SQLITE_OK; } /* ** Find the current time (in Universal Coordinated Time). Write the ** current time and date as a Julian Day number into *prNow and ** return 0. Return 1 if the time and date cannot be found. */ -int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){ +static int winCurrentTime(sqlite3_vfs *pVfs, double *prNow){ int rc; sqlite3_int64 i; rc = winCurrentTimeInt64(pVfs, &i); if( !rc ){ *prNow = i/86400000.0; Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -2701,11 +2701,10 @@ needPagerReset = 0; } rc = pager_playback_one_page(pPager,&pPager->journalOff,0,1,0); if( rc!=SQLITE_OK ){ if( rc==SQLITE_DONE ){ - rc = SQLITE_OK; pPager->journalOff = szJ; break; }else if( rc==SQLITE_IOERR_SHORT_READ ){ /* If the journal has been truncated, simply stop reading and ** processing the journal. This might happen if the journal was @@ -2963,10 +2962,11 @@ #if defined(SQLITE_DEBUG) || defined(SQLITE_CHECK_PAGES) PgHdr *p; /* For looping over pages */ #endif assert( pPager->pWal ); + assert( pList ); #ifdef SQLITE_DEBUG /* Verify that the page list is in accending order */ for(p=pList; p && p->pDirty; p=p->pDirty){ assert( p->pgno < p->pDirty->pgno ); } @@ -6833,10 +6833,17 @@ pPager->pWal = 0; } } return rc; } + +/* +** Unless this is an in-memory or temporary database, clear the pager cache. +*/ +void sqlite3PagerClearCache(Pager *pPager){ + if( !MEMDB && pPager->tempFile==0 ) pager_reset(pPager); +} #ifdef SQLITE_HAS_CODEC /* ** This function is called by the wal module when writing page content ** into the log file. Index: src/pager.h ================================================================== --- src/pager.h +++ src/pager.h @@ -154,10 +154,11 @@ const char *sqlite3PagerJournalname(Pager*); int sqlite3PagerNosync(Pager*); void *sqlite3PagerTempSpace(Pager*); int sqlite3PagerIsMemdb(Pager*); void sqlite3PagerCacheStat(Pager *, int, int, int *); +void sqlite3PagerClearCache(Pager *); /* Functions used to truncate the database file. */ void sqlite3PagerTruncateImage(Pager*,Pgno); #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -465,11 +465,11 @@ ){ int iReg; if( sqlite3ReadSchema(pParse) ) goto pragma_out; sqlite3CodeVerifySchema(pParse, iDb); iReg = ++pParse->nMem; - if( zLeft[0]=='p' ){ + if( sqlite3Tolower(zLeft[0])=='p' ){ sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg); }else{ sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg, sqlite3Atoi(zRight)); } sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1); @@ -1078,11 +1078,11 @@ { OP_IfNeg, 1, 0, 0}, /* 1 */ { OP_String8, 0, 3, 0}, /* 2 */ { OP_ResultRow, 3, 1, 0}, }; - int isQuick = (zLeft[0]=='q'); + int isQuick = (sqlite3Tolower(zLeft[0])=='q'); /* Initialize the VDBE program */ if( sqlite3ReadSchema(pParse) ) goto pragma_out; pParse->nMem = 6; sqlite3VdbeSetNumCols(v, 1); Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -5,52 +5,14 @@ ** an historical reference. Most of the "enhancements" have been backed ** out so that the functionality is now the same as standard printf(). ** ************************************************************************** ** -** The following modules is an enhanced replacement for the "printf" subroutines -** found in the standard C library. The following enhancements are -** supported: -** -** + Additional functions. The standard set of "printf" functions -** includes printf, fprintf, sprintf, vprintf, vfprintf, and -** vsprintf. This module adds the following: -** -** * snprintf -- Works like sprintf, but has an extra argument -** which is the size of the buffer written to. -** -** * mprintf -- Similar to sprintf. Writes output to memory -** obtained from malloc. -** -** * xprintf -- Calls a function to dispose of output. -** -** * nprintf -- No output, but returns the number of characters -** that would have been output by printf. -** -** * A v- version (ex: vsnprintf) of every function is also -** supplied. -** -** + A few extensions to the formatting notation are supported: -** -** * The "=" flag (similar to "-") causes the output to be -** be centered in the appropriately sized field. -** -** * The %b field outputs an integer in binary notation. -** -** * The %c field now accepts a precision. The character output -** is repeated by the number of times the precision specifies. -** -** * The %' field works like %c, but takes as its character the -** next character of the format string, instead of the next -** argument. For example, printf("%.78'-") prints 78 minus -** signs, the same as printf("%.78c",'-'). -** -** + When compiled using GCC on a SPARC, this version of printf is -** faster than the library printf for SUN OS 4.1. -** -** + All functions are fully reentrant. -** +** This file contains code for a set of "printf"-like routines. These +** routines format strings much like the printf() from the standard C +** library, though the implementation here has enhancements to support +** SQLlite. */ #include "sqliteInt.h" /* ** Conversion types fall into various categories as defined by the @@ -185,47 +147,19 @@ } } /* ** On machines with a small stack size, you can redefine the -** SQLITE_PRINT_BUF_SIZE to be less than 350. +** SQLITE_PRINT_BUF_SIZE to be something smaller, if desired. */ #ifndef SQLITE_PRINT_BUF_SIZE -# if defined(SQLITE_SMALL_STACK) -# define SQLITE_PRINT_BUF_SIZE 50 -# else -# define SQLITE_PRINT_BUF_SIZE 350 -# endif +# define SQLITE_PRINT_BUF_SIZE 70 #endif #define etBUFSIZE SQLITE_PRINT_BUF_SIZE /* Size of the output buffer */ /* -** The root program. All variations call this core. -** -** INPUTS: -** func This is a pointer to a function taking three arguments -** 1. A pointer to anything. Same as the "arg" parameter. -** 2. A pointer to the list of characters to be output -** (Note, this list is NOT null terminated.) -** 3. An integer number of characters to be output. -** (Note: This number might be zero.) -** -** arg This is the pointer to anything which will be passed as the -** first argument to "func". Use it for whatever you like. -** -** fmt This is the format string, as in the usual print. -** -** ap This is a pointer to a list of arguments. Same as in -** vfprint. -** -** OUTPUTS: -** The return value is the total number of characters sent to -** the function "func". Returns -1 on a error. -** -** Note that the order in which automatic variables are declared below -** seems to make a big difference in determining how fast this beast -** will run. +** Render a string given by "fmt" into the StrAccum object. */ void sqlite3VXPrintf( StrAccum *pAccum, /* Accumulate results here */ int useExtended, /* Allow extended %-conversions */ const char *fmt, /* Format string */ @@ -244,27 +178,27 @@ etByte flag_altform2; /* True if "!" flag is present */ etByte flag_zeropad; /* True if field width constant starts with zero */ etByte flag_long; /* True if "l" flag is present */ etByte flag_longlong; /* True if the "ll" flag is present */ etByte done; /* Loop termination flag */ + etByte xtype = 0; /* Conversion paradigm */ + char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ LONGDOUBLE_TYPE realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ - char buf[etBUFSIZE]; /* Conversion buffer */ - char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ - etByte xtype = 0; /* Conversion paradigm */ - char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ + char *zOut; /* Rendering buffer */ + int nOut; /* Size of the rendering buffer */ + char *zExtra; /* Malloced memory used by some conversion */ #ifndef SQLITE_OMIT_FLOATING_POINT int exp, e2; /* exponent of real numbers */ + int nsd; /* Number of significant digits returned */ double rounder; /* Used for rounding floating point values */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ - etByte flag_exp; /* True to force display of the exponent */ - int nsd; /* Number of significant digits returned */ #endif + char buf[etBUFSIZE]; /* Conversion buffer */ - length = 0; bufpt = 0; for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ int amt; bufpt = (char *)fmt; @@ -305,13 +239,10 @@ while( c>='0' && c<='9' ){ width = width*10 + c - '0'; c = *++fmt; } } - if( width > etBUFSIZE-10 ){ - width = etBUFSIZE-10; - } /* Get the precision */ if( c=='.' ){ precision = 0; c = *++fmt; if( c=='*' ){ @@ -354,16 +285,10 @@ break; } } zExtra = 0; - - /* Limit the precision to prevent overflowing buf[] during conversion */ - if( precision>etBUFSIZE-40 && (infop->flags & FLAG_STRING)==0 ){ - precision = etBUFSIZE-40; - } - /* ** At this point, variables are initialized as follows: ** ** flag_alternateform TRUE if a '#' is present. ** flag_altform2 TRUE if a '!' is present. @@ -424,20 +349,30 @@ } if( longvalue==0 ) flag_alternateform = 0; if( flag_zeropad && precisionmallocFailed = 1; + return; + } + } + bufpt = &zOut[nOut-1]; if( xtype==etORDINAL ){ static const char zOrd[] = "thstndrd"; int x = (int)(longvalue % 10); if( x>=4 || (longvalue/10)%10==1 ){ x = 0; } - buf[etBUFSIZE-3] = zOrd[x*2]; - buf[etBUFSIZE-2] = zOrd[x*2+1]; - bufpt -= 2; + *(--bufpt) = zOrd[x*2+1]; + *(--bufpt) = zOrd[x*2]; } { register const char *cset; /* Use registers for speed */ register int base; cset = &aDigits[infop->charset]; @@ -445,11 +380,11 @@ do{ /* Convert to ascii */ *(--bufpt) = cset[longvalue%base]; longvalue = longvalue/base; }while( longvalue>0 ); } - length = (int)(&buf[etBUFSIZE-1]-bufpt); + length = (int)(&zOut[nOut-1]-bufpt); for(idx=precision-length; idx>0; idx--){ *(--bufpt) = '0'; /* Zero pad */ } if( prefix ) *(--bufpt) = prefix; /* Add sign */ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ @@ -456,21 +391,20 @@ const char *pre; char x; pre = &aPrefix[infop->prefix]; for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; } - length = (int)(&buf[etBUFSIZE-1]-bufpt); + length = (int)(&zOut[nOut-1]-bufpt); break; case etFLOAT: case etEXP: case etGENERIC: realvalue = va_arg(ap,double); #ifdef SQLITE_OMIT_FLOATING_POINT length = 0; #else if( precision<0 ) precision = 6; /* Set default precision */ - if( precision>etBUFSIZE/2-10 ) precision = etBUFSIZE/2-10; if( realvalue<0.0 ){ realvalue = -realvalue; prefix = '-'; }else{ if( flag_plussign ) prefix = '+'; @@ -514,11 +448,10 @@ bufpt = buf; /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ - flag_exp = xtype==etEXP; if( xtype!=etFLOAT ){ realvalue += rounder; if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } } if( xtype==etGENERIC ){ @@ -535,10 +468,18 @@ if( xtype==etEXP ){ e2 = 0; }else{ e2 = exp; } + if( e2+precision+width > etBUFSIZE - 15 ){ + bufpt = zExtra = sqlite3Malloc( e2+precision+width+15 ); + if( bufpt==0 ){ + pAccum->mallocFailed = 1; + return; + } + } + zOut = bufpt; nsd = 0; flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2; /* The sign in front of the number */ if( prefix ){ *(bufpt++) = prefix; @@ -566,21 +507,21 @@ *(bufpt++) = et_getdigit(&realvalue,&nsd); } /* Remove trailing zeros and the "." if no digits follow the "." */ if( flag_rtz && flag_dp ){ while( bufpt[-1]=='0' ) *(--bufpt) = 0; - assert( bufpt>buf ); + assert( bufpt>zOut ); if( bufpt[-1]=='.' ){ if( flag_altform2 ){ *(bufpt++) = '0'; }else{ *(--bufpt) = 0; } } } /* Add the "eNNN" suffix */ - if( flag_exp || xtype==etEXP ){ + if( xtype==etEXP ){ *(bufpt++) = aDigits[infop->charset]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; }else{ *(bufpt++) = '+'; @@ -595,12 +536,12 @@ *bufpt = 0; /* The converted number is in buf[] and zero terminated. Output it. ** Note that the number is in the usual order, not reversed as with ** integer conversions. */ - length = (int)(bufpt-buf); - bufpt = buf; + length = (int)(bufpt-zOut); + bufpt = zOut; /* Special case: Add leading zeros if the flag_zeropad flag is ** set and we are not left justified */ if( flag_zeropad && !flag_leftjustify && length < width){ int i; @@ -734,13 +675,11 @@ nspace = width-length; if( nspace>0 ){ appendSpace(pAccum, nspace); } } - if( zExtra ){ - sqlite3_free(zExtra); - } + sqlite3_free(zExtra); }/* End for loop over the format string */ } /* End of function */ /* ** Append N bytes of text from z to the StrAccum object. @@ -750,10 +689,11 @@ if( p->tooBig | p->mallocFailed ){ testcase(p->tooBig); testcase(p->mallocFailed); return; } + assert( p->zText!=0 || p->nChar==0 ); if( N<0 ){ N = sqlite3Strlen30(z); } if( N==0 || NEVER(z==0) ){ return; @@ -781,19 +721,20 @@ zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc); }else{ zNew = sqlite3_realloc(zOld, p->nAlloc); } if( zNew ){ - if( zOld==0 ) memcpy(zNew, p->zText, p->nChar); + if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); p->zText = zNew; }else{ p->mallocFailed = 1; sqlite3StrAccumReset(p); return; } } } + assert( p->zText ); memcpy(&p->zText[p->nChar], z, N); p->nChar += N; } /* Index: src/resolve.c ================================================================== --- src/resolve.c +++ src/resolve.c @@ -95,10 +95,28 @@ ExprSetProperty(pExpr, EP_Static); sqlite3ExprDelete(db, pExpr); memcpy(pExpr, pDup, sizeof(*pExpr)); sqlite3DbFree(db, pDup); } + + +/* +** Return TRUE if the name zCol occurs anywhere in the USING clause. +** +** Return FALSE if the USING clause is NULL or if it does not contain +** zCol. +*/ +static int nameInUsingClause(IdList *pUsing, const char *zCol){ + if( pUsing ){ + int k; + for(k=0; knId; k++){ + if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1; + } + } + return 0; +} + /* ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up ** that name in the set of source tables in pSrcList and make the pExpr ** expression node refer back to that source column. The following changes @@ -187,38 +205,25 @@ pSchema = pTab->pSchema; pMatch = pItem; } for(j=0, pCol=pTab->aCol; jnCol; j++, pCol++){ if( sqlite3StrICmp(pCol->zName, zCol)==0 ){ - IdList *pUsing; + /* If there has been exactly one prior match and this match + ** is for the right-hand table of a NATURAL JOIN or is in a + ** USING clause, then skip this match. + */ + if( cnt==1 ){ + if( pItem->jointype & JT_NATURAL ) continue; + if( nameInUsingClause(pItem->pUsing, zCol) ) continue; + } cnt++; pExpr->iTable = pItem->iCursor; pExpr->pTab = pTab; pMatch = pItem; pSchema = pTab->pSchema; /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j; - if( inSrc-1 ){ - if( pItem[1].jointype & JT_NATURAL ){ - /* If this match occurred in the left table of a natural join, - ** then skip the right table to avoid a duplicate match */ - pItem++; - i++; - }else if( (pUsing = pItem[1].pUsing)!=0 ){ - /* If this match occurs on a column that is in the USING clause - ** of a join, skip the search of the right table of the join - ** to avoid a duplicate match there. */ - int k; - for(k=0; knId; k++){ - if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ){ - pItem++; - i++; - break; - } - } - } - } break; } } } } Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -63,10 +63,11 @@ Select standin; sqlite3 *db = pParse->db; pNew = sqlite3DbMallocZero(db, sizeof(*pNew) ); assert( db->mallocFailed || !pOffset || pLimit ); /* OFFSET implies LIMIT */ if( pNew==0 ){ + assert( db->mallocFailed ); pNew = &standin; memset(pNew, 0, sizeof(*pNew)); } if( pEList==0 ){ pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db,TK_ALL,0)); @@ -90,10 +91,11 @@ if( pNew!=&standin ) sqlite3DbFree(db, pNew); pNew = 0; }else{ assert( pNew->pSrc!=0 || pParse->nErr>0 ); } + assert( pNew!=&standin ); return pNew; } /* ** Delete the given Select structure and all of its substructures. @@ -1268,11 +1270,14 @@ /* If the column contains an "AS " phrase, use as the name */ zName = sqlite3DbStrDup(db, zName); }else{ Expr *pColExpr = p; /* The expression that is the result column name */ Table *pTab; /* Table associated with this expression */ - while( pColExpr->op==TK_DOT ) pColExpr = pColExpr->pRight; + while( pColExpr->op==TK_DOT ){ + pColExpr = pColExpr->pRight; + assert( pColExpr!=0 ); + } if( pColExpr->op==TK_COLUMN && ALWAYS(pColExpr->pTab!=0) ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; pTab = pColExpr->pTab; if( iCol<0 ) iCol = pTab->iPKey; Index: src/shell.c ================================================================== --- src/shell.c +++ src/shell.c @@ -15,10 +15,21 @@ #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) /* This needs to come before any includes for MSVC compiler */ #define _CRT_SECURE_NO_WARNINGS #endif +/* +** Enable large-file support for fopen() and friends on unix. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + #include #include #include #include #include "sqlite3.h" @@ -58,11 +69,11 @@ #define isatty(h) _isatty(h) #define access(f,m) _access((f),(m)) #else /* Make sure isatty() has a prototype. */ -extern int isatty(); +extern int isatty(int); #endif #if defined(_WIN32_WCE) /* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty() * thus we always assume that we have a console. That can be @@ -71,10 +82,15 @@ #define isatty(x) 1 #endif /* True if the timer is enabled */ static int enableTimer = 0; + +/* ctype macros that work with signed characters */ +#define IsSpace(X) isspace((unsigned char)X) +#define IsDigit(X) isdigit((unsigned char)X) +#define ToLower(X) (char)tolower((unsigned char)X) #if !defined(_WIN32) && !defined(WIN32) && !defined(__OS2__) && !defined(__RTP__) && !defined(_WRS_KERNEL) #include #include @@ -263,27 +279,27 @@ /* ** Determines if a string is a number of not. */ static int isNumber(const char *z, int *realnum){ if( *z=='-' || *z=='+' ) z++; - if( !isdigit(*z) ){ + if( !IsDigit(*z) ){ return 0; } z++; if( realnum ) *realnum = 0; - while( isdigit(*z) ){ z++; } + while( IsDigit(*z) ){ z++; } if( *z=='.' ){ z++; - if( !isdigit(*z) ) return 0; - while( isdigit(*z) ){ z++; } + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } if( realnum ) *realnum = 1; } if( *z=='e' || *z=='E' ){ z++; if( *z=='+' || *z=='-' ) z++; - if( !isdigit(*z) ) return 0; - while( isdigit(*z) ){ z++; } + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } if( realnum ) *realnum = 1; } return *z==0; } @@ -320,22 +336,20 @@ */ static char *local_getline(char *zPrompt, FILE *in){ char *zLine; int nLine; int n; - int eol; if( zPrompt && *zPrompt ){ printf("%s",zPrompt); fflush(stdout); } nLine = 100; zLine = malloc( nLine ); if( zLine==0 ) return 0; n = 0; - eol = 0; - while( !eol ){ + while( 1 ){ if( n+100>nLine ){ nLine = nLine*2 + 100; zLine = realloc(zLine, nLine); if( zLine==0 ) return 0; } @@ -343,19 +357,18 @@ if( n==0 ){ free(zLine); return 0; } zLine[n] = 0; - eol = 1; break; } while( zLine[n] ){ n++; } if( n>0 && zLine[n-1]=='\n' ){ n--; if( n>0 && zLine[n-1]=='\r' ) n--; zLine[n] = 0; - eol = 1; + break; } } zLine = realloc( zLine, n+1 ); return zLine; } @@ -400,10 +413,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 */ + 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 */ char separator[20]; /* Separator character for MODE_List */ @@ -925,31 +939,37 @@ ** ** This is used, for example, to show the schema of the database by ** querying the SQLITE_MASTER table. */ static int run_table_dump_query( - FILE *out, /* Send output here */ - sqlite3 *db, /* Database to query */ - const char *zSelect, /* SELECT statement to extract content */ - const char *zFirstRow /* Print before first row, if not NULL */ + struct callback_data *p, /* Query context */ + const char *zSelect, /* SELECT statement to extract content */ + const char *zFirstRow /* Print before first row, if not NULL */ ){ sqlite3_stmt *pSelect; int rc; - rc = sqlite3_prepare(db, zSelect, -1, &pSelect, 0); + rc = sqlite3_prepare(p->db, zSelect, -1, &pSelect, 0); if( rc!=SQLITE_OK || !pSelect ){ + fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); + p->nErr++; return rc; } rc = sqlite3_step(pSelect); while( rc==SQLITE_ROW ){ if( zFirstRow ){ - fprintf(out, "%s", zFirstRow); + fprintf(p->out, "%s", zFirstRow); zFirstRow = 0; } - fprintf(out, "%s;\n", sqlite3_column_text(pSelect, 0)); + fprintf(p->out, "%s;\n", sqlite3_column_text(pSelect, 0)); rc = sqlite3_step(pSelect); } - return sqlite3_finalize(pSelect); + rc = sqlite3_finalize(pSelect); + if( rc!=SQLITE_OK ){ + fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db)); + p->nErr++; + } + return rc; } /* ** Allocate space and save off current error string. */ @@ -1072,10 +1092,11 @@ struct callback_data *pArg, /* Pointer to struct callback_data */ char **pzErrMsg /* Error msg written here */ ){ sqlite3_stmt *pStmt = NULL; /* Statement to execute. */ int rc = SQLITE_OK; /* Return Code */ + int rc2; const char *zLeftover; /* Tail of unprocessed SQL */ if( pzErrMsg ){ *pzErrMsg = NULL; } @@ -1088,11 +1109,11 @@ } }else{ if( !pStmt ){ /* this happens for a comment or white-space */ zSql = zLeftover; - while( isspace(zSql[0]) ) zSql++; + while( IsSpace(zSql[0]) ) zSql++; continue; } /* save off the prepared statment handle and reset row count */ if( pArg ){ @@ -1165,14 +1186,15 @@ } /* Finalize the statement just executed. If this fails, save a ** copy of the error message. Otherwise, set zSql to point to the ** next statement to execute. */ - rc = sqlite3_finalize(pStmt); + rc2 = sqlite3_finalize(pStmt); + if( rc!=SQLITE_NOMEM ) rc = rc2; if( rc==SQLITE_OK ){ zSql = zLeftover; - while( isspace(zSql[0]) ) zSql++; + while( IsSpace(zSql[0]) ) zSql++; }else if( pzErrMsg ){ *pzErrMsg = save_err_msg(db); } /* clear saved stmt handle */ @@ -1271,14 +1293,14 @@ return 1; } zSelect = appendText(zSelect, "|| ')' FROM ", 0); zSelect = appendText(zSelect, zTable, '"'); - rc = run_table_dump_query(p->out, p->db, zSelect, zPrepStmt); + rc = run_table_dump_query(p, zSelect, zPrepStmt); if( rc==SQLITE_CORRUPT ){ zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0); - rc = run_table_dump_query(p->out, p->db, zSelect, 0); + run_table_dump_query(p, zSelect, 0); } if( zSelect ) free(zSelect); } return 0; } @@ -1290,23 +1312,34 @@ ** If we get a SQLITE_CORRUPT error, rerun the query after appending ** "ORDER BY rowid DESC" to the end. */ static int run_schema_dump_query( struct callback_data *p, - const char *zQuery, - char **pzErrMsg + const char *zQuery ){ int rc; - rc = sqlite3_exec(p->db, zQuery, dump_callback, p, pzErrMsg); + char *zErr = 0; + rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr); if( rc==SQLITE_CORRUPT ){ char *zQ2; int len = strlen30(zQuery); - if( pzErrMsg ) sqlite3_free(*pzErrMsg); + fprintf(p->out, "/****** CORRUPTION ERROR *******/\n"); + if( zErr ){ + fprintf(p->out, "/****** %s ******/\n", zErr); + 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); - rc = sqlite3_exec(p->db, zQ2, dump_callback, p, pzErrMsg); + rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr); + if( rc ){ + fprintf(p->out, "/****** ERROR: %s ******/\n", zErr); + }else{ + rc = SQLITE_CORRUPT; + } + sqlite3_free(zErr); free(zQ2); } return rc; } @@ -1439,11 +1472,11 @@ */ static int booleanValue(char *zArg){ int val = atoi(zArg); int j; for(j=0; zArg[j]; j++){ - zArg[j] = (char)tolower(zArg[j]); + zArg[j] = ToLower(zArg[j]); } if( strcmp(zArg,"on")==0 ){ val = 1; }else if( strcmp(zArg,"yes")==0 ){ val = 1; @@ -1465,11 +1498,11 @@ char *azArg[50]; /* Parse the input line into tokens. */ while( zLine[i] && nArgout, "PRAGMA foreign_keys=OFF;\n"); fprintf(p->out, "BEGIN TRANSACTION;\n"); p->writableSchema = 0; - sqlite3_exec(p->db, "PRAGMA writable_schema=ON", 0, 0, 0); + sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); + p->nErr = 0; if( nArg==1 ){ run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " - "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'", 0 + "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'" ); run_schema_dump_query(p, "SELECT name, type, sql FROM sqlite_master " - "WHERE name=='sqlite_sequence'", 0 + "WHERE name=='sqlite_sequence'" ); - run_table_dump_query(p->out, p->db, + run_table_dump_query(p, "SELECT sql FROM sqlite_master " "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0 ); }else{ int i; @@ -1577,12 +1610,12 @@ for(i=1; iout, p->db, + " AND sql NOT NULL"); + run_table_dump_query(p, "SELECT sql FROM sqlite_master " "WHERE sql NOT NULL" " AND type IN ('index','trigger','view')" " AND tbl_name LIKE shellstatic()", 0 ); @@ -1591,17 +1624,13 @@ } if( p->writableSchema ){ fprintf(p->out, "PRAGMA writable_schema=OFF;\n"); p->writableSchema = 0; } - sqlite3_exec(p->db, "PRAGMA writable_schema=OFF", 0, 0, 0); - if( zErrMsg ){ - fprintf(stderr,"Error: %s\n", zErrMsg); - sqlite3_free(zErrMsg); - }else{ - fprintf(p->out, "COMMIT;\n"); - } + sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); + sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); + fprintf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n"); }else if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 && nArg<3 ){ p->echoOn = booleanValue(azArg[1]); }else @@ -1730,11 +1759,10 @@ } sqlite3_exec(p->db, "BEGIN", 0, 0, 0); zCommit = "COMMIT"; while( (zLine = local_getline(0, in))!=0 ){ char *z; - i = 0; lineno++; azCol[0] = zLine; for(i=0, z=zLine; *z && *z!='\n' && *z!='\r'; z++){ if( *z==p->separator[0] && strncmp(z, p->separator, nSep)==0 ){ *z = 0; @@ -2019,11 +2047,11 @@ memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_Semi; if( nArg>1 ){ int i; - for(i=0; azArg[1][i]; i++) azArg[1][i] = (char)tolower(azArg[1][i]); + for(i=0; azArg[1][i]; i++) azArg[1][i] = ToLower(azArg[1][i]); if( strcmp(azArg[1],"sqlite_master")==0 ){ char *new_argv[2], *new_colv[2]; new_argv[0] = "CREATE TABLE sqlite_master (\n" " type text,\n" " name text,\n" @@ -2205,11 +2233,11 @@ for(i=0; i<(int)(sizeof(aCtrl)/sizeof(aCtrl[0])); i++){ if( strncmp(azArg[1], aCtrl[i].zCtrlName, n)==0 ){ if( testctrl<0 ){ testctrl = aCtrl[i].ctrlCode; }else{ - fprintf(stderr, "ambiguous option name: \"%s\"\n", azArg[i]); + fprintf(stderr, "ambiguous option name: \"%s\"\n", azArg[1]); testctrl = -1; break; } } } @@ -2342,11 +2370,11 @@ /* ** Test to see if a line consists entirely of whitespace. */ static int _all_whitespace(const char *z){ for(; *z; z++){ - if( isspace(*(unsigned char*)z) ) continue; + if( IsSpace(z[0]) ) continue; if( *z=='/' && z[1]=='*' ){ z += 2; while( *z && (*z!='*' || z[1]!='/') ){ z++; } if( *z==0 ) return 0; z++; @@ -2367,15 +2395,15 @@ ** Return TRUE if the line typed in is an SQL command terminator other ** than a semi-colon. The SQL Server style "go" command is understood ** as is the Oracle "/". */ static int _is_command_terminator(const char *zLine){ - while( isspace(*(unsigned char*)zLine) ){ zLine++; }; + while( IsSpace(zLine[0]) ){ zLine++; }; if( zLine[0]=='/' && _all_whitespace(&zLine[1]) ){ return 1; /* Oracle */ } - if( tolower(zLine[0])=='g' && tolower(zLine[1])=='o' + if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' && _all_whitespace(&zLine[2]) ){ return 1; /* SQL Server */ } return 0; } @@ -2441,11 +2469,11 @@ memcpy(zLine,";",2); } nSqlPrior = nSql; if( zSql==0 ){ int i; - for(i=0; zLine[i] && isspace((unsigned char)zLine[i]); i++){} + for(i=0; zLine[i] && IsSpace(zLine[i]); i++){} if( zLine[i]!=0 ){ nSql = strlen30(zLine); zSql = malloc( nSql+3 ); if( zSql==0 ){ fprintf(stderr, "Error: out of memory\n"); @@ -2713,10 +2741,11 @@ ** we do the actual processing of arguments later in a second pass. */ }else if( strcmp(argv[i],"-batch")==0 ){ stdin_is_interactive = 0; }else if( strcmp(argv[i],"-heap")==0 ){ +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) int j, c; const char *zSize; sqlite3_int64 szHeap; zSize = argv[++i]; @@ -2725,11 +2754,10 @@ if( c=='M' ){ szHeap *= 1000000; break; } if( c=='K' ){ szHeap *= 1000; break; } if( c=='G' ){ szHeap *= 1000000000; break; } } if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000; -#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64); #endif #ifdef SQLITE_ENABLE_VFSTRACE }else if( strcmp(argv[i],"-vfstrace")==0 ){ extern int vfstrace_register( Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -1397,12 +1397,12 @@ ** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or ** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory ** allocator is engaged to handle all of SQLites memory allocation needs. ** The first pointer (the memory pointer) must be aligned to an 8-byte ** boundary or subsequent behavior of SQLite will be undefined. -** The minimum allocation size is capped at 2^12. Reasonable values -** for the minimum allocation size are 2^5 through 2^8. +** The minimum allocation size is capped at 2**12. Reasonable values +** for the minimum allocation size are 2**5 through 2**8. ** ** [[SQLITE_CONFIG_MUTEX]]
SQLITE_CONFIG_MUTEX
**
^(This option takes a single argument which is a pointer to an ** instance of the [sqlite3_mutex_methods] structure. The argument specifies ** alternative low-level mutex routines to be used in place @@ -2797,11 +2797,12 @@ ** zSql string ends at either the first '\000' or '\u0000' character or ** the nByte-th byte, whichever comes first. If the caller knows ** that the supplied string is nul-terminated, then there is a small ** performance advantage to be gained by passing an nByte parameter that ** is equal to the number of bytes in the input string including -** the nul-terminator bytes. +** the nul-terminator bytes as this saves SQLite from having to +** make a copy of the input string. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only ** compile the first statement in zSql, so *pzTail is left pointing to ** what remains uncompiled. @@ -3018,10 +3019,17 @@ ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the ** number of bytes in the value, not the number of characters.)^ ** ^If the fourth parameter is negative, the length of the string is ** the number of bytes up to the first zero terminator. +** If a non-negative fourth parameter is provided to sqlite3_bind_text() +** or sqlite3_bind_text16() then that parameter must be the byte offset +** where the NUL terminator would occur assuming the string were NUL +** terminated. If any NUL characters occur at byte offsets less than +** the value of the fourth parameter then the resulting string value will +** contain embedded NULs. The result of expressions involving strings +** with embedded NULs is undefined. ** ** ^The fifth argument to sqlite3_bind_blob(), sqlite3_bind_text(), and ** sqlite3_bind_text16() is a destructor used to dispose of the BLOB or ** string after SQLite has finished with it. ^The destructor is called ** to dispose of the BLOB or string even if the call to sqlite3_bind_blob(), @@ -4036,11 +4044,16 @@ ** is negative, then SQLite takes result text from the 2nd parameter ** through the first zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined -** function result. +** function result. If the 3rd parameter is non-negative, then it +** must be the byte offset into the string where the NUL terminator would +** appear if the string where NUL terminated. If any NUL characters occur +** in the string at a byte offset that is less than the value of the 3rd +** parameter, then the resulting string will contain embedded NULs and the +** result of expressions operating on strings with embedded NULs is undefined. ** ^If the 4th parameter to the sqlite3_result_text* interfaces ** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that ** function as the destructor on the text or BLOB result when it has ** finished using that result. ** ^If the 4th parameter to the sqlite3_result_text* interfaces or to Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -3102,19 +3102,21 @@ # define sqlite3VtabInSync(db) 0 # define sqlite3VtabLock(X) # define sqlite3VtabUnlock(X) # define sqlite3VtabUnlockList(X) # define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK +# define sqlite3GetVTable(X,Y) ((VTable*)0) #else void sqlite3VtabClear(sqlite3 *db, Table*); int sqlite3VtabSync(sqlite3 *db, char **); int sqlite3VtabRollback(sqlite3 *db); int sqlite3VtabCommit(sqlite3 *db); void sqlite3VtabLock(VTable *); void sqlite3VtabUnlock(VTable *); void sqlite3VtabUnlockList(sqlite3*); int sqlite3VtabSavepoint(sqlite3 *, int, int); + VTable *sqlite3GetVTable(sqlite3*, Table*); # define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0) #endif void sqlite3VtabMakeWritable(Parse*,Table*); void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*); void sqlite3VtabFinishParse(Parse*, Token*); @@ -3130,11 +3132,10 @@ int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); int sqlite3Reprepare(Vdbe*); void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*); CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *); int sqlite3TempInMemory(const sqlite3*); -VTable *sqlite3GetVTable(sqlite3*, Table*); const char *sqlite3JournalModename(int); int sqlite3Checkpoint(sqlite3*, int, int, int*, int*); int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); /* Declarations for functions in fkey.c. All of these are replaced by Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -961,11 +961,11 @@ Tcl_DStringAppendElement(&str, zArg2 ? zArg2 : ""); Tcl_DStringAppendElement(&str, zArg3 ? zArg3 : ""); Tcl_DStringAppendElement(&str, zArg4 ? zArg4 : ""); rc = Tcl_GlobalEval(pDb->interp, Tcl_DStringValue(&str)); Tcl_DStringFree(&str); - zReply = Tcl_GetStringResult(pDb->interp); + zReply = rc==TCL_OK ? Tcl_GetStringResult(pDb->interp) : "SQLITE_DENY"; if( strcmp(zReply,"SQLITE_OK")==0 ){ rc = SQLITE_OK; }else if( strcmp(zReply,"SQLITE_DENY")==0 ){ rc = SQLITE_DENY; }else if( strcmp(zReply,"SQLITE_IGNORE")==0 ){ @@ -1010,18 +1010,16 @@ */ static char *local_getline(char *zPrompt, FILE *in){ char *zLine; int nLine; int n; - int eol; nLine = 100; zLine = malloc( nLine ); if( zLine==0 ) return 0; n = 0; - eol = 0; - while( !eol ){ + while( 1 ){ if( n+100>nLine ){ nLine = nLine*2 + 100; zLine = realloc(zLine, nLine); if( zLine==0 ) return 0; } @@ -1029,18 +1027,17 @@ if( n==0 ){ free(zLine); return 0; } zLine[n] = 0; - eol = 1; break; } while( zLine[n] ){ n++; } if( n>0 && zLine[n-1]=='\n' ){ n--; zLine[n] = 0; - eol = 1; + break; } } zLine = realloc( zLine, n+1 ); return zLine; } @@ -2204,11 +2201,10 @@ } (void)sqlite3_exec(pDb->db, "BEGIN", 0, 0, 0); zCommit = "COMMIT"; while( (zLine = local_getline(0, in))!=0 ){ char *z; - i = 0; lineno++; azCol[0] = zLine; for(i=0, z=zLine; *z; z++){ if( *z==zSep[0] && strncmp(z, zSep, nSep)==0 ){ *z = 0; @@ -2633,18 +2629,20 @@ ** $db rekey KEY ** ** Change the encryption key on the currently open database. */ case DB_REKEY: { +#ifdef SQLITE_HAS_CODEC int nKey; void *pKey; +#endif if( objc!=3 ){ Tcl_WrongNumArgs(interp, 2, objv, "KEY"); return TCL_ERROR; } - pKey = Tcl_GetByteArrayFromObj(objv[2], &nKey); #ifdef SQLITE_HAS_CODEC + pKey = Tcl_GetByteArrayFromObj(objv[2], &nKey); rc = sqlite3_rekey(pDb->db, pKey, nKey); if( rc ){ Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0); rc = TCL_ERROR; } @@ -3064,19 +3062,21 @@ ** The second argument is the name of the database file. ** */ static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ SqliteDb *p; - void *pKey = 0; - int nKey = 0; const char *zArg; char *zErrMsg; int i; const char *zFile; const char *zVfs = 0; int flags; Tcl_DString translatedFilename; +#ifdef SQLITE_HAS_CODEC + void *pKey = 0; + int nKey = 0; +#endif /* In normal use, each TCL interpreter runs in a single thread. So ** by default, we can turn of mutexing on SQLite database connections. ** However, for testing purposes it is useful to have mutexes turned ** on. So, by default, mutexes default off. But if compiled with @@ -3104,11 +3104,13 @@ } } for(i=3; i+1pIndex; pIdx; pIdx=pIdx->pNext, i++){ + assert( aRegIdx ); if( openAll || aRegIdx[i]>0 ){ KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx); sqlite3VdbeAddOp4(v, OP_OpenWrite, iCur+i+1, pIdx->tnum, iDb, (char*)pKey, P4_KEYINFO_HANDOFF); assert( pParse->nTab>iCur+i+1 ); @@ -540,10 +541,11 @@ sqlite3VdbeAddOp2(v, OP_Goto, 0, addr); sqlite3VdbeJumpHere(v, addr); /* Close all tables */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + assert( aRegIdx ); if( openAll || aRegIdx[i]>0 ){ sqlite3VdbeAddOp2(v, OP_Close, iCur+i+1, 0); } } sqlite3VdbeAddOp2(v, OP_Close, iCur, 0); Index: src/util.c ================================================================== --- src/util.c +++ src/util.c @@ -329,11 +329,11 @@ }else if( *z=='+' ){ z+=incr; } /* copy digits to exponent */ while( z=342 ){ + if( esign<0 ){ + result = 0.0*s; + }else{ + result = 1e308*1e308*s; /* Infinity */ + } }else{ /* 1.0e+22 is the largest power of 10 than can be ** represented exactly. */ while( e%22 ) { scale *= 1.0e+1; e -= 1; } while( e>0 ) { scale *= 1.0e+22; e -= 22; } Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -2186,20 +2186,20 @@ }else if( pC->cacheStatus==p->cacheCtr ){ payloadSize = pC->payloadSize; zRec = (char*)pC->aRow; }else if( pC->isIndex ){ assert( sqlite3BtreeCursorIsValid(pCrsr) ); - rc = sqlite3BtreeKeySize(pCrsr, &payloadSize64); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCrsr, &payloadSize64); assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ /* sqlite3BtreeParseCellPtr() uses getVarint32() to extract the ** payload size, so it is impossible for payloadSize64 to be ** larger than 32 bits. */ assert( (payloadSize64 & SQLITE_MAX_U32)==(u64)payloadSize64 ); payloadSize = (u32)payloadSize64; }else{ assert( sqlite3BtreeCursorIsValid(pCrsr) ); - rc = sqlite3BtreeDataSize(pCrsr, &payloadSize); + VVA_ONLY(rc =) sqlite3BtreeDataSize(pCrsr, &payloadSize); assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ } }else if( ALWAYS(pC->pseudoTableReg>0) ){ pReg = &aMem[pC->pseudoTableReg]; assert( pReg->flags & MEM_Blob ); @@ -4241,18 +4241,18 @@ rc = sqlite3VdbeCursorMoveto(pC); if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error; if( pC->isIndex ){ assert( !pC->isTable ); - rc = sqlite3BtreeKeySize(pCrsr, &n64); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCrsr, &n64); assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */ if( n64>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } n = (u32)n64; }else{ - rc = sqlite3BtreeDataSize(pCrsr, &n); + VVA_ONLY(rc =) sqlite3BtreeDataSize(pCrsr, &n); assert( rc==SQLITE_OK ); /* DataSize() cannot fail */ if( n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } } @@ -5517,11 +5517,11 @@ /* Do not allow a transition to journal_mode=WAL for a database ** in temporary storage or if the VFS does not support shared memory */ if( eNew==PAGER_JOURNALMODE_WAL - && (zFilename[0]==0 /* Temp file */ + && (sqlite3Strlen30(zFilename)==0 /* Temp file */ || !sqlite3PagerWalSupported(pPager)) /* No shared-memory support */ ){ eNew = eOld; } @@ -5938,14 +5938,19 @@ pName = &aMem[pOp->p1]; assert( pVtab->pModule->xRename ); assert( memIsValid(pName) ); REGISTER_TRACE(pOp->p1, pName); assert( pName->flags & MEM_Str ); - rc = pVtab->pModule->xRename(pVtab, pName->z); - importVtabErrMsg(p, pVtab); - p->expired = 0; - + testcase( pName->enc==SQLITE_UTF8 ); + testcase( pName->enc==SQLITE_UTF16BE ); + testcase( pName->enc==SQLITE_UTF16LE ); + rc = sqlite3VdbeChangeEncoding(pName, SQLITE_UTF8); + if( rc==SQLITE_OK ){ + rc = pVtab->pModule->xRename(pVtab, pName->z); + importVtabErrMsg(p, pVtab); + p->expired = 0; + } break; } #endif #ifndef SQLITE_OMIT_VIRTUALTABLE Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -780,34 +780,33 @@ ** Change the comment on the the most recently coded instruction. Or ** insert a No-op and add the comment to that new instruction. This ** makes the code easier to read during debugging. None of this happens ** in a production build. */ -void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){ - va_list ap; - if( !p ) return; +static void vdbeVComment(Vdbe *p, const char *zFormat, va_list ap){ assert( p->nOp>0 || p->aOp==0 ); assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); if( p->nOp ){ - char **pz = &p->aOp[p->nOp-1].zComment; + assert( p->aOp ); + sqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment); + p->aOp[p->nOp-1].zComment = sqlite3VMPrintf(p->db, zFormat, ap); + } +} +void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){ + va_list ap; + if( p ){ va_start(ap, zFormat); - sqlite3DbFree(p->db, *pz); - *pz = sqlite3VMPrintf(p->db, zFormat, ap); + vdbeVComment(p, zFormat, ap); va_end(ap); } } void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){ va_list ap; - if( !p ) return; - sqlite3VdbeAddOp0(p, OP_Noop); - assert( p->nOp>0 || p->aOp==0 ); - assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); - if( p->nOp ){ - char **pz = &p->aOp[p->nOp-1].zComment; + if( p ){ + sqlite3VdbeAddOp0(p, OP_Noop); va_start(ap, zFormat); - sqlite3DbFree(p->db, *pz); - *pz = sqlite3VMPrintf(p->db, zFormat, ap); + vdbeVComment(p, zFormat, ap); va_end(ap); } } #endif /* NDEBUG */ @@ -3064,11 +3063,11 @@ ** than 2GiB are support - anything large must be database corruption. ** Any corruption is detected in sqlite3BtreeParseCellPtr(), though, so ** this code can safely assume that nCellKey is 32-bits */ assert( sqlite3BtreeCursorIsValid(pCur) ); - rc = sqlite3BtreeKeySize(pCur, &nCellKey); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ assert( (nCellKey & SQLITE_MAX_U32)==(u64)nCellKey ); /* Read in the complete content of the index entry */ memset(&m, 0, sizeof(m)); @@ -3139,11 +3138,11 @@ int rc; BtCursor *pCur = pC->pCursor; Mem m; assert( sqlite3BtreeCursorIsValid(pCur) ); - rc = sqlite3BtreeKeySize(pCur, &nCellKey); + VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey); assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */ /* nCellKey will always be between 0 and 0xffffffff because of the say ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */ if( nCellKey<=0 || nCellKey>0x7fffffff ){ *res = 0; Index: src/wal.c ================================================================== --- src/wal.c +++ src/wal.c @@ -2341,11 +2341,11 @@ */ if( iRead ){ int sz; i64 iOffset; sz = pWal->hdr.szPage; - sz = (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); + sz = (sz&0xfe00) + ((sz&0x0001)<<16); testcase( sz<=32768 ); testcase( sz>=65536 ); iOffset = walFrameOffset(iRead, sz) + WAL_FRAME_HDRSIZE; *pInWal = 1; /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL */ Index: src/where.c ================================================================== --- src/where.c +++ src/where.c @@ -703,11 +703,11 @@ int iCol = pRight->iColumn; pVal = sqlite3VdbeGetValue(pReprepare, iCol, SQLITE_AFF_NONE); if( pVal && sqlite3_value_type(pVal)==SQLITE_TEXT ){ z = (char *)sqlite3_value_text(pVal); } - sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); /* IMP: R-31526-56213 */ + sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER ); }else if( op==TK_STRING ){ z = pRight->u.zToken; } if( z ){ @@ -721,11 +721,11 @@ pPrefix = sqlite3Expr(db, TK_STRING, z); if( pPrefix ) pPrefix->u.zToken[cnt] = 0; *ppPrefix = pPrefix; if( op==TK_VARIABLE ){ Vdbe *v = pParse->pVdbe; - sqlite3VdbeSetVarmask(v, pRight->iColumn); /* IMP: R-31526-56213 */ + sqlite3VdbeSetVarmask(v, pRight->iColumn); if( *pisComplete && pRight->u.zToken[1] ){ /* If the rhs of the LIKE expression is a variable, and the current ** value of the variable means there is no need to invoke the LIKE ** function, then no OP_Variable will be added to the program. ** This causes problems for the sqlite3_bind_parameter_name() @@ -1853,10 +1853,11 @@ tempWC.pParse = pWC->pParse; tempWC.pMaskSet = pWC->pMaskSet; tempWC.pOuter = pWC; tempWC.op = TK_AND; tempWC.a = pOrTerm; + tempWC.wctrlFlags = 0; tempWC.nTerm = 1; bestIndex(pParse, &tempWC, pSrc, notReady, notValid, 0, &sTermCost); }else{ continue; } @@ -2474,11 +2475,10 @@ assert( roundUp==0 || roundUp==1 ); assert( pIdx->nSample>0 ); if( pVal==0 ) return SQLITE_ERROR; n = pIdx->aiRowEst[0]; aSample = pIdx->aSample; - i = 0; eType = sqlite3_value_type(pVal); if( eType==SQLITE_INTEGER ){ v = sqlite3_value_int64(pVal); r = (i64)v; @@ -2635,11 +2635,11 @@ ){ if( pExpr->op==TK_VARIABLE || (pExpr->op==TK_REGISTER && pExpr->op2==TK_VARIABLE) ){ int iVar = pExpr->iColumn; - sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); /* IMP: R-31526-56213 */ + sqlite3VdbeSetVarmask(pParse->pVdbe, iVar); *pp = sqlite3VdbeGetValue(pParse->pReprepare, iVar, aff); return SQLITE_OK; } return sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, aff, pp); } @@ -4894,11 +4894,12 @@ assert( bestJ>=0 ); assert( notReady & getMask(pMaskSet, pTabList->a[bestJ].iCursor) ); WHERETRACE(("*** Optimizer selects table %d for loop %d" " with cost=%g and nRow=%g\n", bestJ, pLevel-pWInfo->a, bestPlan.rCost, bestPlan.plan.nRow)); - if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 ){ + /* The ALWAYS() that follows was added to hush up clang scan-build */ + if( (bestPlan.plan.wsFlags & WHERE_ORDERBY)!=0 && ALWAYS(ppOrderBy) ){ *ppOrderBy = 0; } if( (bestPlan.plan.wsFlags & WHERE_DISTINCT)!=0 ){ assert( pWInfo->eDistinct==0 ); pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; ADDED test/fts-9fd058691.test Index: test/fts-9fd058691.test ================================================================== --- /dev/null +++ test/fts-9fd058691.test @@ -0,0 +1,59 @@ +# 2011 October 13 +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file implements regression tests for the FTS SQLite module. +# +# This file implements tests to verify that ticket [9fd058691] has been +# fixed. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# If SQLITE_ENABLE_FTS3 is defined, omit this file. +ifcapable !fts3 { + finish_test + return +} + +set ::testprefix fts3-9fd058691 + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE fts USING fts3( tags TEXT); + INSERT INTO fts (tags) VALUES ('tag1'); + SELECT * FROM fts WHERE tags MATCH 'tag1'; +} {tag1} + +do_test 1.1 { + db close + sqlite3 db test.db + execsql { + UPDATE fts SET tags = 'tag1' WHERE rowid = 1; + SELECT * FROM fts WHERE tags MATCH 'tag1'; + } +} {tag1} + +db close +forcedelete test.db +sqlite3 db test.db + +do_execsql_test 2.0 { + CREATE VIRTUAL TABLE fts USING fts3(tags TEXT); + INSERT INTO fts (docid, tags) VALUES (1, 'tag1'); + INSERT INTO fts (docid, tags) VALUES (2, NULL); + INSERT INTO fts (docid, tags) VALUES (3, 'three'); +} {} + +do_test 2.1 { + execsql { + UPDATE fts SET tags = 'two' WHERE rowid = 2; + SELECT * FROM fts WHERE tags MATCH 'two'; + } +} {two} + +finish_test Index: test/fts3ao.test ================================================================== --- test/fts3ao.test +++ test/fts3ao.test @@ -198,10 +198,13 @@ INSERT INTO t6 VALUES('Down came the troopers, one, two, three'); ROLLBACK; SELECT * FROM t5; } } {{the quick brown fox} {jumped over the} {lazy dog}} +do_execsql_test fts3ao-4.8 { + SELECT snippet(t5, '[', ']') FROM t5 WHERE t5 MATCH 'the' +} {{[the] quick brown fox} {jumped over [the]}} # Test that it is possible to rename an FTS4 table. Renaming an FTS4 table # involves renaming the extra %_docsize and %_stat tables. # do_execsql_test 5.1 { Index: test/fts3d.test ================================================================== --- test/fts3d.test +++ test/fts3d.test @@ -302,6 +302,59 @@ SELECT OPTIMIZE(t1) FROM t1 LIMIT 1; SELECT level, idx FROM t1_segdir ORDER BY level, idx; } } {{Index already optimal} 2 0} + +# ALTER TABLE RENAME should work regardless of the database encoding. +# +do_test fts3d-6.0 { + db close + forcedelete test.db + sqlite3 db test.db + db eval { + PRAGMA encoding=UTF8; + CREATE VIRTUAL TABLE fts USING fts3(a,b,c); + SELECT name FROM sqlite_master WHERE name GLOB '???_*' ORDER BY 1; + } +} {fts_content fts_segdir fts_segments} +do_test fts3d-6.1 { + db eval { + ALTER TABLE fts RENAME TO xyz; + SELECT name FROM sqlite_master WHERE name GLOB '???_*' ORDER BY 1; + } +} {xyz_content xyz_segdir xyz_segments} +do_test fts3d-6.2 { + db close + forcedelete test.db + sqlite3 db test.db + db eval { + PRAGMA encoding=UTF16le; + CREATE VIRTUAL TABLE fts USING fts3(a,b,c); + SELECT name FROM sqlite_master WHERE name GLOB '???_*' ORDER BY 1; + } +} {fts_content fts_segdir fts_segments} +do_test fts3d-6.3 { + db eval { + ALTER TABLE fts RENAME TO xyz; + SELECT name FROM sqlite_master WHERE name GLOB '???_*' ORDER BY 1; + } +} {xyz_content xyz_segdir xyz_segments} +do_test fts3d-6.4 { + db close + forcedelete test.db + sqlite3 db test.db + db eval { + PRAGMA encoding=UTF16be; + CREATE VIRTUAL TABLE fts USING fts3(a,b,c); + SELECT name FROM sqlite_master WHERE name GLOB '???_*' ORDER BY 1; + } +} {fts_content fts_segdir fts_segments} +do_test fts3d-6.5 { + db eval { + ALTER TABLE fts RENAME TO xyz; + SELECT name FROM sqlite_master WHERE name GLOB '???_*' ORDER BY 1; + } +} {xyz_content xyz_segdir xyz_segments} + + finish_test Index: test/fts3defer.test ================================================================== --- test/fts3defer.test +++ test/fts3defer.test @@ -424,10 +424,22 @@ if {$fts3_simple_deferred_tokens_only==0} { do_select_test 6.2.3 { SELECT rowid FROM t1 WHERE t1 MATCH '"jk xduvfhk" OR "zm azavwm"' } {8 15 26 92 96} } + + if {$tn>1} { + # These tests will not work with $tn==1, as in this case table t1 is + # created using FTS3. The ^ syntax is only available with FTS4 tables. + # + do_select_test 7.1 { + SELECT rowid FROM t1 WHERE t1 MATCH '^zm mjpavjuhw' + } {56 62} + do_select_test 7.2 { + SELECT rowid FROM t1 WHERE t1 MATCH '^azavwm zm' + } {43} + } } set testprefix fts3defer do_execsql_test 3.1 { @@ -446,8 +458,36 @@ " do_execsql_test 3.3 { SELECT count(*) FROM x1 WHERE x1 MATCH '"d e f"' } {16} + +# At one point the following was causing a floating-point exception. +# +do_execsql_test 4.1 { + CREATE VIRTUAL TABLE x2 USING FTS4(x); + BEGIN; + INSERT INTO x2 VALUES('m m m m m m m m m m m m m m m m m m m m m m m m m m'); + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 SELECT * FROM x2; + INSERT INTO x2 VALUES('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'); + COMMIT; +} +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}} finish_test Index: test/fts3fault2.test ================================================================== --- test/fts3fault2.test +++ test/fts3fault2.test @@ -79,7 +79,56 @@ } -body { execsql "SELECT * FROM terms2" } -test { faultsim_test_result {0 {a * 1 1 a 0 1 1 b * 1 1 b 0 1 1 c * 1 1 c 0 1 1 x * 1 1 x 1 1 1 y * 1 1 y 1 1 1 z * 1 1 z 1 1 1}} } + +do_faultsim_test 3.0 -faults oom* -prep { + faultsim_delete_and_reopen + db eval { CREATE TABLE 'xx yy'(a, b); } +} -body { + execsql { + CREATE VIRTUAL TABLE tt USING fts4(content="xx yy"); + } +} -test { + faultsim_test_result {0 {}} +} + +do_faultsim_test 3.1 -faults oom* -prep { + faultsim_delete_and_reopen + db func zip zip + db func unzip unzip +} -body { + execsql { + CREATE VIRTUAL TABLE tt USING fts4(compress=zip, uncompress=unzip); + } +} -test { + faultsim_test_result {0 {}} +} + +do_test 4.0 { + faultsim_delete_and_reopen + execsql { + CREATE VIRTUAL TABLE ft USING fts4(a, b); + INSERT INTO ft VALUES('U U T C O', 'F N D E S'); + INSERT INTO ft VALUES('P H X G B', 'I D M R U'); + INSERT INTO ft VALUES('P P X D M', 'Y V N T C'); + INSERT INTO ft VALUES('Z L Q O W', 'D F U N Q'); + INSERT INTO ft VALUES('A J D U P', 'C H M Q E'); + INSERT INTO ft VALUES('P S A O H', 'S Z C W D'); + INSERT INTO ft VALUES('T B N L W', 'C A K T I'); + INSERT INTO ft VALUES('K E Z L O', 'L L Y C E'); + INSERT INTO ft VALUES('C R E S V', 'Q V F W P'); + INSERT INTO ft VALUES('S K H G W', 'R W Q F G'); + } + faultsim_save_and_close +} {} +do_faultsim_test 4.1 -prep { + faultsim_restore_and_reopen + db eval {SELECT * FROM sqlite_master} +} -body { + execsql { INSERT INTO ft(ft) VALUES('rebuild') } +} -test { + faultsim_test_result {0 {}} +} finish_test ADDED test/fts3first.test Index: test/fts3first.test ================================================================== --- /dev/null +++ test/fts3first.test @@ -0,0 +1,163 @@ +# 2011 October 18 +# +# 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. +# +#*********************************************************************** + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl + +ifcapable !fts3 { + finish_test + return +} + +set testprefix fts3first + +proc lreverse {L} { + set res [list] + for {set ii [expr [llength $L]-1]} {$ii>=0} {incr ii -1} { + lappend res [lindex $L $ii] + } + set res +} + +proc mit {blob} { + set scan(littleEndian) i* + set scan(bigEndian) I* + binary scan $blob $scan($::tcl_platform(byteOrder)) r + return $r +} +db func mit mit + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE x1 USING FTS4(a, b, c); + INSERT INTO x1(docid,a,b,c) VALUES(0, 'K H D S T', 'V M N Y K', 'S Z N Q S'); + INSERT INTO x1(docid,a,b,c) VALUES(1, 'K N J L W', 'S Z W J Q', 'D U W S E'); + INSERT INTO x1(docid,a,b,c) VALUES(2, 'B P M O I', 'R P H W S', 'R J L L E'); + INSERT INTO x1(docid,a,b,c) VALUES(3, 'U R Q M L', 'M J K A V', 'Q W J T J'); + INSERT INTO x1(docid,a,b,c) VALUES(4, 'N J C Y N', 'R U D X V', 'B O U A Q'); + INSERT INTO x1(docid,a,b,c) VALUES(5, 'Q L X L U', 'I F N X S', 'U Q A N Y'); + INSERT INTO x1(docid,a,b,c) VALUES(6, 'M R G U T', 'U V I Q P', 'X Y D L S'); + INSERT INTO x1(docid,a,b,c) VALUES(7, 'D Y P O I', 'X J P K R', 'V O T H V'); + INSERT INTO x1(docid,a,b,c) VALUES(8, 'R Y D L R', 'U U E S J', 'N W L M R'); + INSERT INTO x1(docid,a,b,c) VALUES(9, 'Z P F N P', 'W A X D U', 'V A E Q A'); + INSERT INTO x1(docid,a,b,c) VALUES(10, 'Q I A Q M', 'N D K H C', 'A H T Q Z'); + INSERT INTO x1(docid,a,b,c) VALUES(11, 'T E R Q B', 'C I B C B', 'F Z U W R'); + INSERT INTO x1(docid,a,b,c) VALUES(12, 'E S V U W', 'T P F W H', 'A M D J Q'); + INSERT INTO x1(docid,a,b,c) VALUES(13, 'X S B X Y', 'U D N D P', 'X Z Y G F'); + INSERT INTO x1(docid,a,b,c) VALUES(14, 'K H A B L', 'S R C C Z', 'D W E H J'); + INSERT INTO x1(docid,a,b,c) VALUES(15, 'C E U C C', 'W F M N M', 'T Z U X T'); + INSERT INTO x1(docid,a,b,c) VALUES(16, 'Q G C G H', 'H N N B H', 'B Q I H Y'); + INSERT INTO x1(docid,a,b,c) VALUES(17, 'Q T S K B', 'W B D Y N', 'V J P E C'); + INSERT INTO x1(docid,a,b,c) VALUES(18, 'A J M O Q', 'L G Y Y A', 'G N M R N'); + INSERT INTO x1(docid,a,b,c) VALUES(19, 'T R Y P Y', 'N V Y B X', 'L Z T N T'); + + CREATE VIRTUAL TABLE x2 USING FTS4(a, b, c, order=DESC); + INSERT INTO x2(docid, a, b, c) SELECT docid, a, b, c FROM x1; +} + + +# Test queries. +# +foreach x {1 2} { + foreach {tn match res} { + 1 "^K" {0 1 14} + 2 "^S" {0 1 14} + 3 "^W" {9 15 17} + 4 "^J" {} + 5 "^E" {12} + 6 "V ^-E" {0 3 4 6 7 9 17 19} + 7 "V -^E" {0 3 4 6 7 9 17 19} + 8 "^-E V" {0 3 4 6 7 9 17 19} + 9 "-^E V" {0 3 4 6 7 9 17 19} + 10 "V" {0 3 4 6 7 9 12 17 19} + + 11 {"^K H"} {0 14} + 12 {"K H"} {0 10 14} + 13 {"K ^H"} {} + } { + set rev [lreverse $res] + do_execsql_test 1.$x.$tn.1 {SELECT docid FROM x1 WHERE x1 MATCH $match} $res + do_execsql_test 1.$x.$tn.2 {SELECT docid FROM x2 WHERE x2 MATCH $match} $rev + } + + do_execsql_test 1.$x.[expr $tn+1] { + INSERT INTO x1(x1) VALUES('optimize'); + INSERT INTO x2(x2) VALUES('optimize'); + } {} +} + +# Test the snippet() function. +# +foreach {tn match res} { + 1 {^K} {{[K] H D S T} {[K] N J L W} {[K] H A B L}} + 2 {^X} {{[X] Y D L S} {[X] J P K R} {[X] S B X Y}} + 3 {^X Y} {{[X] [Y] D L S} {D [Y] P O I...[X] J P K R} {[X] S B X [Y]}} +} { + set rev [lreverse $res] + + do_execsql_test 1.3.$tn.1 { + SELECT snippet(x1, '[', ']', '...') FROM x1 WHERE x1 MATCH $match + } $res + + do_execsql_test 1.3.$tn.2 { + SELECT snippet(x2, '[', ']', '...') FROM x2 WHERE x2 MATCH $match + } $rev +} + +# Test matchinfo(). +# +foreach {tn match res} { + 1 {^K} { + {1 3 3 0 0 0 0 0 0} + {1 3 3 0 0 0 0 0 0} + {1 3 3 0 0 0 0 0 0} + } + 2 {^X} { + {0 1 1 0 1 1 1 2 2} + {0 1 1 1 1 1 0 2 2} + {1 1 1 0 1 1 1 2 2} + } + 3 {^X Y} { + {0 1 1 0 1 1 1 2 2 0 6 5 0 5 4 1 4 4} + {0 1 1 1 1 1 0 2 2 1 6 5 0 5 4 0 4 4} + {1 1 1 0 1 1 1 2 2 1 6 5 0 5 4 1 4 4} + } +} { + set rev [lreverse $res] + + do_execsql_test 1.3.$tn.1 { + SELECT mit(matchinfo(x1, 'x')) FROM x1 WHERE x1 MATCH $match + } $res + do_execsql_test 1.3.$tn.2 { + SELECT mit(matchinfo(x2, 'x')) FROM x2 WHERE x2 MATCH $match + } $rev +} + +# Test that ^ is ignored for FTS3 tables. +# +do_execsql_test 2.1 { + CREATE VIRTUAL TABLE x3 USING fts3; + INSERT INTO x3 VALUES('A B C'); + INSERT INTO x3 VALUES('B A C'); + + CREATE VIRTUAL TABLE x4 USING fts4; + INSERT INTO x4 VALUES('A B C'); + INSERT INTO x4 VALUES('B A C'); +} + +do_execsql_test 2.2.1 { + SELECT * FROM x3 WHERE x3 MATCH '^A'; +} {{A B C} {B A C}} +do_execsql_test 2.2.2 { + SELECT * FROM x4 WHERE x4 MATCH '^A'; +} {{A B C}} + +finish_test Index: test/fts3malloc.test ================================================================== --- test/fts3malloc.test +++ test/fts3malloc.test @@ -292,10 +292,11 @@ INSERT INTO ft VALUES('short alongertoken reallyquitealotlongerimeanit andthistokenisjustsolongthatonemightbeforgivenforimaginingthatitwasmerelyacontrivedexampleandnotarealtoken', 'cynics!') } do_test fts3_malloc-5.2 { execsql { CREATE VIRTUAL TABLE ft8 USING fts3(x, tokenize porter) } } {} + do_write_test fts3_malloc-5.3 ft_content { INSERT INTO ft8 VALUES('short alongertoken reallyquitealotlongerimeanit andthistokenisjustsolongthatonemightbeforgivenforimaginingthatitwasmerelyacontrivedexampleandnotarealtoken') } Index: test/fts3matchinfo.test ================================================================== --- test/fts3matchinfo.test +++ test/fts3matchinfo.test @@ -17,10 +17,11 @@ # If SQLITE_ENABLE_FTS3 is not defined, omit this file. ifcapable !fts3 { finish_test ; return } set testprefix fts3matchinfo +set sqlite_fts3_enable_parentheses 0 proc mit {blob} { set scan(littleEndian) i* set scan(bigEndian) I* binary scan $blob $scan($::tcl_platform(byteOrder)) r @@ -55,10 +56,13 @@ CREATE VIRTUAL TABLE x1 USING fts4(matchinfo=fs3); } {1 {unrecognized matchinfo: fs3}} do_catchsql_test 2.1 { CREATE VIRTUAL TABLE x2 USING fts4(mtchinfo=fts3); } {1 {unrecognized parameter: mtchinfo=fts3}} +do_catchsql_test 2.2 { + CREATE VIRTUAL TABLE x2 USING fts4(matchinfo=fts5); +} {1 {unrecognized matchinfo: fts5}} # Check that with fts3, the "=" character is permitted in column definitions. # do_execsql_test 3.1 { CREATE VIRTUAL TABLE t3 USING fts3(mtchinfo=fts3); @@ -222,10 +226,22 @@ do_matchinfo_test 4.1.3 t4 {t4 MATCH 'a b'} { s {{2 0} {0 2}} } do_matchinfo_test 4.1.4 t4 {t4 MATCH '"a b" c'} { s {{2 0} {0 2}} } do_matchinfo_test 4.1.5 t4 {t4 MATCH 'a "b c"'} { s {{2 0} {0 2}} } do_matchinfo_test 4.1.6 t4 {t4 MATCH 'd d'} { s {{1 0} {0 1}} } +do_matchinfo_test 4.1.7 t4 {t4 MATCH 'f OR abcd'} { + x { + {0 1 1 1 1 1 0 0 0 0 0 0} + {1 1 1 0 1 1 0 0 0 0 0 0} + } +} +do_matchinfo_test 4.1.8 t4 {t4 MATCH 'f -abcd'} { + x { + {0 1 1 1 1 1} + {1 1 1 0 1 1} + } +} do_execsql_test 4.2.0 { CREATE VIRTUAL TABLE t5 USING fts4; INSERT INTO t5 VALUES('a a a a a'); INSERT INTO t5 VALUES('a b a b a'); Index: test/fts3prefix.test ================================================================== --- test/fts3prefix.test +++ test/fts3prefix.test @@ -197,7 +197,17 @@ SELECT * FROM t3 WHERE t3 MATCH 'sev*' } {{seven eight nine}} do_execsql_test 4.6 { SELECT * FROM t3 WHERE t3 MATCH 'one*' } {{one two three}} + +#------------------------------------------------------------------------- +# Syntax tests. +# +do_catchsql_test 5.1 { + CREATE VIRTUAL TABLE t4 USING fts4(prefix="abc"); +} {1 {error parsing prefix parameter: abc}} +do_catchsql_test 5.2 { + CREATE VIRTUAL TABLE t4 USING fts4(prefix=""); +} {0 {}} finish_test Index: test/fts3sort.test ================================================================== --- test/fts3sort.test +++ test/fts3sort.test @@ -136,10 +136,12 @@ 1 "order=asc" {0 {}} 2 "order=desc" {0 {}} 3 "order=dec" {1 {unrecognized order: dec}} 4 "order=xxx, order=asc" {1 {unrecognized order: xxx}} 5 "order=desc, order=asc" {0 {}} + 6 "order=xxxx, order=asc" {1 {unrecognized order: xxxx}} + 7 "order=desk" {1 {unrecognized order: desk}} } { execsql { DROP TABLE IF EXISTS t1 } do_catchsql_test 2.1.$tn " CREATE VIRTUAL TABLE t1 USING fts4(a, b, $param) " $res @@ -155,10 +157,13 @@ END; } {3 1} do_execsql_test 2.3 { SELECT docid FROM t2 WHERE t2 MATCH 'aa'; } {3 1} +do_execsql_test 2.4 { + SELECT docid FROM t2 WHERE t2 MATCH 'aa' ORDER BY content; +} {1 3} #------------------------------------------------------------------------- # Test that ticket [56be976859] has been fixed. # do_execsql_test 3.1 { ADDED test/fts4content.test Index: test/fts4content.test ================================================================== --- /dev/null +++ test/fts4content.test @@ -0,0 +1,478 @@ +# 2011 October 03 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#************************************************************************* +# This file implements regression tests for SQLite library. The +# focus of this script is testing the content=xxx FTS4 option. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix fts4content + +# If SQLITE_ENABLE_FTS3 is defined, omit this file. +ifcapable !fts3 { + finish_test + return +} + +#------------------------------------------------------------------------- +# Test organization: +# +# 1.* - Warm-body tests. +# +# 2.* - Querying a content=xxx FTS table. +# +# 3.* - Writing to a content=xxx FTS table. +# +# 4.* - The "INSERT INTO fts(fts) VALUES('rebuild')" command. +# +# 5.* - Check that CREATE TABLE, DROP TABLE and ALTER TABLE correctly +# ignore any %_content table when used with the content=xxx option. +# +# 6.* - Test the effects of messing with the schema of table xxx after +# creating a content=xxx FTS index. +# + +do_execsql_test 1.1.1 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES('w x', 'x y', 'y z'); + CREATE VIRTUAL TABLE ft1 USING fts4(content=t1); +} + +do_execsql_test 1.1.2 { + PRAGMA table_info(ft1); +} { + 0 a {} 0 {} 0 + 1 b {} 0 {} 0 + 2 c {} 0 {} 0 +} + +do_execsql_test 1.1.3 { SELECT *, rowid FROM ft1 } {{w x} {x y} {y z} 1} +do_execsql_test 1.1.4 { SELECT a, c FROM ft1 WHERE rowid=1 } {{w x} {y z}} + +do_execsql_test 1.1.5 { INSERT INTO ft1(ft1) VALUES('rebuild') } {} +do_execsql_test 1.1.6 { SELECT rowid FROM ft1 WHERE ft1 MATCH 'x' } {1} +do_execsql_test 1.1.7 { SELECT rowid FROM ft1 WHERE ft1 MATCH 'a' } {} + +do_execsql_test 1.2.1 { + DROP TABLE ft1; + CREATE VIRTUAL TABLE ft1 USING fts4(content=t1, b); + PRAGMA table_info(ft1); +} { + 0 b {} 0 {} 0 +} +do_execsql_test 1.2.2 { + SELECT *, rowid FROM ft1 +} {{x y} 1} + +#------------------------------------------------------------------------- +# The following block of tests - 2.* - test that a content=xxx FTS table +# can be queried. Also tested are cases where rows identified in the FTS +# are missing from the content table, and cases where the index is +# inconsistent with the content table. +# +do_execsql_test 2.0 { + CREATE TABLE t2(x); + INSERT INTO t2 VALUES('O S W W F U C R Q I C N P Z Y Y E Y Y E'); -- 1 + INSERT INTO t2 VALUES('Y X U V L B E H Y J C Y A I A P V F V K'); -- 2 + INSERT INTO t2 VALUES('P W I N J H I I N I F B K D U Q B Z S F'); -- 3 + INSERT INTO t2 VALUES('N R O R H J R H G M D I U U B O M P A U'); -- 4 + INSERT INTO t2 VALUES('Y O V O G T P N G T N F I V B U M J M G'); -- 5 + INSERT INTO t2 VALUES('J O B N K N E C H Z R K J O U G M K L S'); -- 6 + INSERT INTO t2 VALUES('S Z S R I Q U A P W R X H K C Z U L S P'); -- 7 + INSERT INTO t2 VALUES('J C H N R C K R V N M O F Z M Z A I H W'); -- 8 + INSERT INTO t2 VALUES('O Y G I S J U U W O D Z F J K N R P R L'); -- 9 + INSERT INTO t2 VALUES('B G L K U R U P V X Z I H V R W C Q A S'); -- 10 + INSERT INTO t2 VALUES('T F T J F F Y V F W N X K Q A Y L X W G'); -- 11 + INSERT INTO t2 VALUES('C J U H B Q X L C M M Y E G V F W V Z C'); -- 12 + INSERT INTO t2 VALUES('B W L T F S G X D P H N G M R I O A X I'); -- 13 + INSERT INTO t2 VALUES('N G Y O K Q K Z N M H U J E D H U W R K'); -- 14 + INSERT INTO t2 VALUES('U D T R U Y F J D S J X E H Q G V A S Z'); -- 15 + INSERT INTO t2 VALUES('M I W P J S H R J D Q I C G P C T P H R'); -- 16 + INSERT INTO t2 VALUES('J M N I S L X Q C A B F C B Y D H V R J'); -- 17 + INSERT INTO t2 VALUES('F V Z W J Q L P X Y E W B U Q N H X K T'); -- 18 + INSERT INTO t2 VALUES('R F S R Y O F Q E I E G H C B H R X Y N'); -- 19 + INSERT INTO t2 VALUES('U Q Q Q T E P D M F X P J G H X C Q D L'); -- 20 +} + +do_execsql_test 2.1 { + CREATE VIRTUAL TABLE ft2 USING fts4(content=t2); + INSERT INTO ft2(ft2) VALUES('rebuild'); + + -- Modify the backing table a bit: Row 17 is missing and the contents + -- of row 20 do not match the FTS index contents. + DELETE FROM t2 WHERE rowid = 17; + UPDATE t2 SET x = 'a b c d e f g h i j' WHERE rowid = 20; +} + +foreach {tn match rowidlist} { + 1 {S} {1 3 6 7 9 10 13 15 16 17 19} + 2 {"S R"} {7 19} + 3 {"N K N"} {6} + 4 {"Q Q"} {20} + 5 {"B Y D"} {17} +} { + do_execsql_test 2.2.1.$tn { + SELECT rowid FROM ft2 WHERE ft2 MATCH $match + } $rowidlist + + do_execsql_test 2.2.2.$tn { + SELECT docid FROM ft2 WHERE ft2 MATCH $match + } $rowidlist +} + +foreach {tn match result} { + 1 {"N K N"} {{J O B N K N E C H Z R K J O U G M K L S}} + 2 {"Q Q"} {{a b c d e f g h i j}} + 3 {"B Y D"} {{}} +} { + do_execsql_test 2.3.$tn { + SELECT * FROM ft2 WHERE ft2 MATCH $match + } $result +} + +foreach {tn match result} { + 1 {"N K N"} {{..O B [N] [K] [N] E..}} + 2 {"B Y D"} {{}} + 3 {"Q Q"} {{a [b] [c] [d] e f..}} +} { + do_execsql_test 2.4.$tn { + SELECT snippet(ft2, '[', ']', '..', -1, 6) FROM ft2 WHERE ft2 MATCH $match + } $result +} + +foreach {tn match result} { + 1 {"N K N"} {{0 0 6 1 0 1 8 1 0 2 10 1}} + 2 {"B Y D"} {{}} + 3 {"Q Q"} {{0 0 2 1 0 0 4 1 0 1 4 1 0 1 6 1}} + 4 {"Q D L"} {{}} +} { + do_execsql_test 2.5.$tn { + SELECT offsets(ft2) FROM ft2 WHERE ft2 MATCH $match + } $result +} + +#------------------------------------------------------------------------- +# The following block of tests - 3.* - test that the FTS index can be +# modified by writing to the table. But that this has no effect on the +# content table. +# + +do_execsql_test 3.1 { + CREATE TABLE t3(x, y); + CREATE VIRTUAL TABLE ft3 USING fts4(content=t3); +} + +do_catchsql_test 3.1.1 { + INSERT INTO ft3 VALUES('a b c', 'd e f'); +} {1 {constraint failed}} +do_execsql_test 3.1.2 { + INSERT INTO ft3(docid, x, y) VALUES(21, 'a b c', 'd e f'); + SELECT rowid FROM ft3 WHERE ft3 MATCH '"a b c"'; +} {21} +do_execsql_test 3.1.3 { SELECT * FROM t3 } {} + +# This DELETE does not work, since there is no row in [t3] to base the +# DELETE on. So the SELECT on [ft3] still returns rowid 21. +do_execsql_test 3.1.4 { + DELETE FROM ft3; + SELECT rowid FROM ft3 WHERE ft3 MATCH '"a b c"'; +} {21} + +# If the row is added to [t3] before the DELETE on [ft3], it works. +do_execsql_test 3.1.5 { + INSERT INTO t3(rowid, x, y) VALUES(21, 'a b c', 'd e f'); + DELETE FROM ft3; + SELECT rowid FROM ft3 WHERE ft3 MATCH '"a b c"'; +} {} +do_execsql_test 3.1.6 { SELECT rowid FROM t3 } {21} + +do_execsql_test 3.2.1 { + INSERT INTO ft3(rowid, x, y) VALUES(0, 'R T M S M', 'A F O K H'); + INSERT INTO ft3(rowid, x, y) VALUES(1, 'C Z J O X', 'U S Q D K'); + INSERT INTO ft3(rowid, x, y) VALUES(2, 'N G H P O', 'N O P O C'); + INSERT INTO ft3(rowid, x, y) VALUES(3, 'V H S D R', 'K N G E C'); + INSERT INTO ft3(rowid, x, y) VALUES(4, 'J T R V U', 'U X S L C'); + INSERT INTO ft3(rowid, x, y) VALUES(5, 'N A Y N G', 'X D G P Y'); + INSERT INTO ft3(rowid, x, y) VALUES(6, 'I Q I S P', 'D R O Q B'); + INSERT INTO ft3(rowid, x, y) VALUES(7, 'T K T Z J', 'B W D G O'); + INSERT INTO ft3(rowid, x, y) VALUES(8, 'Y K F X T', 'D F G V G'); + INSERT INTO ft3(rowid, x, y) VALUES(9, 'E L E T L', 'P W N F Z'); + INSERT INTO ft3(rowid, x, y) VALUES(10, 'O G J G X', 'G J F E P'); + INSERT INTO ft3(rowid, x, y) VALUES(11, 'O L N N Z', 'K E Z F D'); + INSERT INTO ft3(rowid, x, y) VALUES(12, 'R Z M R J', 'X G I M Z'); + INSERT INTO ft3(rowid, x, y) VALUES(13, 'L X N N X', 'R R N S T'); + INSERT INTO ft3(rowid, x, y) VALUES(14, 'F L B J H', 'K W F L C'); + INSERT INTO ft3(rowid, x, y) VALUES(15, 'P E B M V', 'E A A B U'); + INSERT INTO ft3(rowid, x, y) VALUES(16, 'V E C F P', 'L U T V K'); + INSERT INTO ft3(rowid, x, y) VALUES(17, 'T N O Z N', 'T P Q X N'); + INSERT INTO ft3(rowid, x, y) VALUES(18, 'V W U W R', 'H O A A V'); + INSERT INTO ft3(rowid, x, y) VALUES(19, 'A H N L F', 'I G H B O'); +} + +foreach {tn match rowidlist} { + 1 "N A" {5 19} + 2 "x:O" {1 2 10 11 17} + 3 "y:O" {0 2 6 7 18 19} +} { + set res [list] + foreach rowid $rowidlist { lappend res $rowid {} {} } + + do_execsql_test 3.2.2.$tn { + SELECT rowid, * FROM ft3 WHERE ft3 MATCH $match + } $res + do_execsql_test 3.2.3.$tn { + SELECT docid, * FROM ft3 WHERE ft3 MATCH $match + } $res +} + +do_execsql_test 3.3.1 { + INSERT INTO t3(rowid, x, y) VALUES(0, 'R T M S M', 'A F O K H'); + INSERT INTO t3(rowid, x, y) VALUES(1, 'C Z J O X', 'U S Q D K'); + INSERT INTO t3(rowid, x, y) VALUES(2, 'N G H P O', 'N O P O C'); + INSERT INTO t3(rowid, x, y) VALUES(3, 'V H S D R', 'K N G E C'); + INSERT INTO t3(rowid, x, y) VALUES(4, 'J T R V U', 'U X S L C'); + INSERT INTO t3(rowid, x, y) VALUES(5, 'N A Y N G', 'X D G P Y'); + UPDATE ft3 SET x = y, y = x; + DELETE FROM t3; +} + +foreach {tn match rowidlist} { + 1 "N A" {5 19} + 2 "x:O" {0 2 10 11 17} + 3 "y:O" {1 2 6 7 18 19} +} { + set res [list] + foreach rowid $rowidlist { lappend res $rowid {} {} } + + do_execsql_test 3.3.2.$tn { + SELECT rowid, * FROM ft3 WHERE ft3 MATCH $match + } $res + do_execsql_test 3.3.3.$tn { + SELECT docid, * FROM ft3 WHERE ft3 MATCH $match + } $res +} + +do_execsql_test 3.3.1 { + INSERT INTO t3(rowid, x, y) VALUES(15, 'P E B M V', 'E A A B U'); + INSERT INTO t3(rowid, x, y) VALUES(16, 'V E C F P', 'L U T V K'); + INSERT INTO t3(rowid, x, y) VALUES(17, 'T N O Z N', 'T P Q X N'); + INSERT INTO t3(rowid, x, y) VALUES(18, 'V W U W R', 'H O A A V'); + INSERT INTO t3(rowid, x, y) VALUES(19, 'A H N L F', 'I G H B O'); + DELETE FROM ft3; +} + +foreach {tn match rowidlist} { + 1 "N A" {5} + 2 "x:O" {0 2 10 11} + 3 "y:O" {1 2 6 7} +} { + set res [list] + foreach rowid $rowidlist { lappend res $rowid {} {} } + + do_execsql_test 3.3.2.$tn { + SELECT rowid, * FROM ft3 WHERE ft3 MATCH $match + } $res + do_execsql_test 3.3.3.$tn { + SELECT docid, * FROM ft3 WHERE ft3 MATCH $match + } $res +} + + +#------------------------------------------------------------------------- +# Test cases 4.* test the 'rebuild' command. On content=xxx and regular +# FTS tables. +# +do_execsql_test 4.0 { + CREATE TABLE t4(x); + CREATE VIRTUAL TABLE ft4 USING fts4(content=t4); + CREATE VIRTUAL TABLE ft4x USING fts4(x); +} + +do_execsql_test 4.1.1 { + INSERT INTO ft4x(ft4x) VALUES('rebuild'); + INSERT INTO ft4(ft4) VALUES('rebuild'); +} {} +do_execsql_test 4.1.2 { + SELECT id, quote(value) FROM ft4_stat +} {0 X'000000'} +do_execsql_test 4.1.3 { + SELECT id, quote(value) FROM ft4x_stat +} {0 X'000000'} + +do_execsql_test 4.2.1 { + INSERT INTO ft4x VALUES('M G M F T'); + INSERT INTO ft4x VALUES('Z Q C A U'); + INSERT INTO ft4x VALUES('N L L V'); + INSERT INTO ft4x VALUES('T F D X D'); + INSERT INTO ft4x VALUES('Z H I S D'); + + SELECT id, quote(value) FROM ft4x_stat +} {0 X'05182B'} + +do_execsql_test 4.2.2 { + INSERT INTO ft4(rowid, x) SELECT rowid, * FROM ft4x; + SELECT id, quote(value) FROM ft4_stat +} {0 X'05182B'} + +do_execsql_test 4.2.3 { + SELECT docid, quote(size) FROM ft4_docsize +} {1 X'05' 2 X'05' 3 X'04' 4 X'05' 5 X'05'} + +do_execsql_test 4.2.4 { + INSERT INTO ft4x(ft4x) VALUES('rebuild'); + SELECT id, quote(value) FROM ft4x_stat; + SELECT docid, quote(size) FROM ft4x_docsize +} {0 X'05182B' 1 X'05' 2 X'05' 3 X'04' 4 X'05' 5 X'05'} + +do_execsql_test 4.2.5 { + INSERT INTO ft4(ft4) VALUES('rebuild'); + SELECT id, quote(value) FROM ft4_stat; + SELECT docid, quote(size) FROM ft4_docsize +} {0 X'000000'} + +do_execsql_test 4.2.6 { + INSERT INTO t4(rowid, x) SELECT rowid, x FROM ft4x; + INSERT INTO ft4(ft4) VALUES('rebuild'); + SELECT id, quote(value) FROM ft4_stat; + SELECT docid, quote(size) FROM ft4_docsize +} {0 X'05182B' 1 X'05' 2 X'05' 3 X'04' 4 X'05' 5 X'05'} + + +#------------------------------------------------------------------------- +# Test cases 5.* test that the following commands do not create/move or +# delete a %_content table when used with a content=xxx FTS table. +# +do_execsql_test 5.1.1 { + CREATE TABLE t5(a, b, c, d); + CREATE VIRTUAL TABLE ft5 USING fts4(content=t5); + SELECT name FROM sqlite_master WHERE name LIKE '%t5%'; +} { + t5 ft5 ft5_segments ft5_segdir + sqlite_autoindex_ft5_segdir_1 ft5_docsize ft5_stat +} +do_execsql_test 5.1.2 { + ALTER TABLE ft5 RENAME TO ft6; + SELECT name FROM sqlite_master WHERE name LIKE '%t5%'; +} { + t5 +} +do_execsql_test 5.1.3 { + SELECT name FROM sqlite_master WHERE name LIKE '%t6%'; +} { + ft6 ft6_segments ft6_segdir + sqlite_autoindex_ft6_segdir_1 ft6_docsize ft6_stat +} +do_execsql_test 5.1.4 { + INSERT INTO t5 VALUES('a', 'b', 'c', 'd'); + INSERT INTO ft6(ft6) VALUES('rebuild'); + SELECT rowid FROM ft6 WHERE ft6 MATCH 'b'; +} {1} +do_execsql_test 5.1.5 { + DROP TABLE ft6; + SELECT * FROM t5; +} {a b c d} +do_execsql_test 5.1.6 { + SELECT name FROM sqlite_master WHERE name LIKE '%t6%'; +} { +} +do_execsql_test 5.1.7 { + CREATE VIRTUAL TABLE ft5 USING fts4(content=t5); + CREATE TABLE t5_content(a, b); + DROP TABLE ft5; + SELECT name FROM sqlite_master WHERE name LIKE '%t5%'; +} { + t5 t5_content +} + +#------------------------------------------------------------------------- +# Test cases 6.* test +# +do_catchsql_test 6.1.1 { + CREATE VIRTUAL TABLE ft7 USING fts4(content=t7); +} {1 {vtable constructor failed: ft7}} + +do_execsql_test 6.2.1 { + CREATE TABLE t7(one, two); + CREATE VIRTUAL TABLE ft7 USING fts4(content=t7); + INSERT INTO t7 VALUES('A B', 'B A'); + INSERT INTO t7 VALUES('C D', 'A A'); + SELECT * FROM ft7; +} { + {A B} {B A} {C D} {A A} +} + +do_catchsql_test 6.2.2 { + DROP TABLE t7; + SELECT * FROM ft7; +} {1 {SQL logic error or missing database}} + +db close +sqlite3 db test.db +do_execsql_test 6.2.3 { + SELECT name FROM sqlite_master WHERE name LIKE '%t7%' +} { + ft7 ft7_segments ft7_segdir sqlite_autoindex_ft7_segdir_1 + ft7_docsize ft7_stat +} +do_catchsql_test 6.2.4 { + SELECT * FROM ft7; +} {1 {vtable constructor failed: ft7}} +do_execsql_test 6.2.5 { + CREATE TABLE t7(x, y); + INSERT INTO t7 VALUES('A B', 'B A'); + INSERT INTO t7 VALUES('C D', 'A A'); + SELECT * FROM ft7; +} { + {A B} {B A} {C D} {A A} +} + +do_execsql_test 6.2.6 { + INSERT INTO ft7(ft7) VALUES('rebuild'); + SELECT rowid FROM ft7 WHERE ft7 MATCH '"A A"'; +} {2} + +do_execsql_test 6.2.7 { + DROP TABLE t7; + CREATE TABLE t7(x); +} +do_catchsql_test 6.2.8 { + SELECT * FROM ft7 WHERE ft7 MATCH '"A A"'; +} {1 {SQL logic error or missing database}} +do_catchsql_test 6.2.9 { + SELECT * FROM ft7 WHERE ft7 MATCH '"A A"'; +} {1 {SQL logic error or missing database}} + +db close +sqlite3 db test.db +do_catchsql_test 6.2.10 { + SELECT rowid FROM ft7 WHERE ft7 MATCH '"A A"'; +} {0 2} +do_catchsql_test 6.2.11 { + SELECT rowid, * FROM ft7 WHERE ft7 MATCH '"A A"'; +} {0 {2 {}}} + +#------------------------------------------------------------------------- +# Test cases 7.* +# +do_execsql_test 7.1.1 { + CREATE VIRTUAL TABLE ft8 USING fts4(content=nosuchtable, x); + INSERT INTO ft8(docid, x) VALUES(13, 'U O N X G'); + INSERT INTO ft8(docid, x) VALUES(14, 'C J J U B'); + INSERT INTO ft8(docid, x) VALUES(15, 'N J Y G X'); + INSERT INTO ft8(docid, x) VALUES(16, 'R Y D O R'); + INSERT INTO ft8(docid, x) VALUES(17, 'I Y T Q O'); +} + +do_execsql_test 7.1.2 { + SELECT docid FROM ft8 WHERE ft8 MATCH 'N'; +} {13 15} + +finish_test Index: test/nan.test ================================================================== --- test/nan.test +++ test/nan.test @@ -317,9 +317,55 @@ db eval {DELETE FROM t1} set big [string repeat 9 10000].0e-9000 db eval "INSERT INTO t1 VALUES($big)" db eval {SELECT x, typeof(x) FROM t1} } {inf real} + +do_realnum_test nan-4.30 { + db eval { + DELETE FROM t1; + INSERT INTO t1 VALUES('2.5e+9999'); + SELECT x, typeof(x) FROM t1; + } +} {inf real} +do_realnum_test nan-4.31 { + db eval { + DELETE FROM t1; + INSERT INTO t1 VALUES('2.5e+10000'); + SELECT x, typeof(x) FROM t1; + } +} {inf real} + +do_realnum_test nan-4.32 { + db eval { + DELETE FROM t1; + INSERT INTO t1 VALUES('2.5e-9999'); + SELECT x, typeof(x) FROM t1; + } +} {0.0 real} +do_realnum_test nan-4.33 { + db eval { + DELETE FROM t1; + INSERT INTO t1 VALUES('2.5e-10000'); + SELECT x, typeof(x) FROM t1; + } +} {0.0 real} +do_realnum_test nan-4.34 { + db eval { + DELETE FROM t1; + INSERT INTO t1 VALUES('2.5e2147483650'); + SELECT x, typeof(x) FROM t1; + } +} {inf real} +do_realnum_test nan-4.35 { + db eval { + DELETE FROM t1; + INSERT INTO t1 VALUES('2.5e-2147483650'); + SELECT x, typeof(x) FROM t1; + } +} {0.0 real} + + finish_test Index: test/permutations.test ================================================================== --- test/permutations.test +++ test/permutations.test @@ -181,14 +181,16 @@ fts3ak.test fts3al.test fts3am.test fts3an.test fts3ao.test fts3atoken.test fts3b.test fts3c.test fts3cov.test fts3d.test fts3defer.test fts3defer2.test fts3e.test fts3expr.test fts3expr2.test fts3near.test fts3query.test fts3shared.test fts3snippet.test fts3sort.test - fts3fault.test fts3malloc.test fts3matchinfo.test - fts3aux1.test fts3comp1.test fts3auto.test + fts4aa.test fts4content.test + fts3conf.test fts3prefix.test fts3fault2.test fts3corrupt.test + fts3corrupt2.test + fts3first.test } lappend ::testsuitelist xxx #------------------------------------------------------------------------- Index: test/pragma.test ================================================================== --- test/pragma.test +++ test/pragma.test @@ -327,10 +327,13 @@ execsql {PRAGMA integrity_check} } {ok} do_test pragma-3.8.1 { execsql {PRAGMA quick_check} } {ok} + do_test pragma-3.8.2 { + execsql {PRAGMA QUICK_CHECK} + } {ok} do_test pragma-3.9 { execsql { ATTACH 'testerr.db' AS t2; PRAGMA integrity_check } @@ -1217,18 +1220,24 @@ execsql { CREATE TABLE abc(a, b, c); PRAGMA page_count; } } {2} + do_test pragma-14.2uc { + execsql {pragma PAGE_COUNT} + } {2} do_test pragma-14.3 { execsql { BEGIN; CREATE TABLE def(a, b, c); PRAGMA page_count; } } {3} + do_test pragma-14.3uc { + execsql {pragma PAGE_COUNT} + } {3} do_test pragma-14.4 { set page_size [db one {pragma page_size}] expr [file size test.db] / $page_size } {2} @@ -1254,10 +1263,13 @@ execsql { ATTACH 'test2.db' AS aux; PRAGMA aux.page_count; } } {5} + do_test pragma-14.6uc { + execsql {pragma AUX.PAGE_COUNT} + } {5} } # Test that the value set using the cache_size pragma is not reset when the # schema is reloaded. # Index: test/printf.test ================================================================== --- test/printf.test +++ test/printf.test @@ -3545,11 +3545,11 @@ do_test printf-5.1 { set x [sqlite3_mprintf_str {%d %d %100000s} 0 0 {Hello}] string length $x -} {344} +} {100004} do_test printf-5.2 { sqlite3_mprintf_str {%d %d (%-10.10s) %} -9 -10 {HelloHelloHello} } {-9 -10 (HelloHello) %} do_test printf-6.1 { ADDED test/tkt-fa7bf5ec.test Index: test/tkt-fa7bf5ec.test ================================================================== --- /dev/null +++ test/tkt-fa7bf5ec.test @@ -0,0 +1,39 @@ +# 2011 October 13 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for SQLite library. Specifically, +# it tests that ticket [fa7bf5ec94801e7e2030e41eefe5d9dd96eaacfd] has +# been resolved. +# +# The problem described by this ticket was that the sqlite3ExprCompare() +# function was saying that expressions (x='a') and (x='A') were identical +# because it was using sqlite3StrICmp() instead of strcmp() to compare string +# literals. That was causing the query optimizer for aggregate queries to +# believe that both count() operations were identical, and thus only +# computing the first count() and making a copy of the result for the +# second count(). +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test tkt-fa7bf5ec-1 { + execsql { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES ('a'); + INSERT INTO t1 VALUES ('A'); + INSERT INTO t1 VALUES ('A'); + SELECT count(CASE WHEN x='a' THEN 1 END), + count(CASE WHEN x='A' THEN 1 END) + FROM t1; + } +} {1 2} + +finish_test Index: test/tkt3793.test ================================================================== --- test/tkt3793.test +++ test/tkt3793.test @@ -98,20 +98,22 @@ # associated with [db1]. # # Note: Before the bug was fixed, if [db2] was opened with the "-fullmutex 1" # option, then this test case would cause an assert() to fail. # -set ::busyconnection db1 -db1 eval {SELECT * FROM t2 ORDER BY a LIMIT 20} { - do_test tkt3793-2.[incr x] { set ::busyconnection } db1 - set ::busyconnection db2 - - db2 eval { SELECT count(*) FROM t2 } - do_test tkt3793-2.[incr x] { set ::busyconnection } db2 - set ::busyconnection db1 -} - +ifcapable threadsafe { + set ::busyconnection db1 + db1 eval {SELECT * FROM t2 ORDER BY a LIMIT 20} { + do_test tkt3793-2.[incr x] { set ::busyconnection } db1 + set ::busyconnection db2 + + db2 eval { SELECT count(*) FROM t2 } + do_test tkt3793-2.[incr x] { set ::busyconnection } db2 + set ::busyconnection db1 + } +} + do_test tkt3793-3 { db1 close db2 close } {} Index: test/walro.test ================================================================== --- test/walro.test +++ test/walro.test @@ -141,20 +141,20 @@ code1 { sqlite3 db test.db } csql1 { SELECT * FROM t1 } } {1 {unable to open database file}} # Also test that if the -shm file can be opened for read/write access, - # it is, even if readonly_shm=1 is present in the URI. + # it is not if readonly_shm=1 is present in the URI. do_test 1.3.2.1 { code1 { db close } code2 { db2 close } file exists test.db-shm } {0} do_test 1.3.2.2 { code1 { sqlite3 db file:test.db?readonly_shm=1 } - sql1 { SELECT * FROM t1 } - } {a b c d e f g h i j k l} + csql1 { SELECT * FROM sqlite_master } + } {1 {unable to open database file}} do_test 1.3.2.3 { code1 { db close } close [open test.db-shm w] file attributes test.db-shm -permissions r--r--r-- code1 { sqlite3 db file:test.db?readonly_shm=1 } Index: test/where3.test ================================================================== --- test/where3.test +++ test/where3.test @@ -339,7 +339,90 @@ } { 0 0 1 {SEARCH TABLE aaa USING INDEX aaa_333 (fk=?) (~10 rows)} 0 1 0 {SEARCH TABLE aaa AS bbb USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)} 0 0 0 {USE TEMP B-TREE FOR ORDER BY} } + +# Name resolution with NATURAL JOIN and USING +# +do_test where3-6.setup { + db eval { + CREATE TABLE t6w(a, w); + INSERT INTO t6w VALUES(1, 'w-one'); + INSERT INTO t6w VALUES(2, 'w-two'); + INSERT INTO t6w VALUES(9, 'w-nine'); + CREATE TABLE t6x(a, x); + INSERT INTO t6x VALUES(1, 'x-one'); + INSERT INTO t6x VALUES(3, 'x-three'); + INSERT INTO t6x VALUES(9, 'x-nine'); + CREATE TABLE t6y(a, y); + INSERT INTO t6y VALUES(1, 'y-one'); + INSERT INTO t6y VALUES(4, 'y-four'); + INSERT INTO t6y VALUES(9, 'y-nine'); + CREATE TABLE t6z(a, z); + INSERT INTO t6z VALUES(1, 'z-one'); + INSERT INTO t6z VALUES(5, 'z-five'); + INSERT INTO t6z VALUES(9, 'z-nine'); + } +} {} +set cnt 0 +foreach predicate { + {} + {ORDER BY a} + {ORDER BY t6w.a} + {WHERE a>0} + {WHERE t6y.a>0} + {WHERE a>0 ORDER BY a} +} { + incr cnt + do_test where3-6.$cnt.1 { + set sql "SELECT * FROM t6w NATURAL JOIN t6x NATURAL JOIN t6y" + append sql " NATURAL JOIN t6z " + append sql $::predicate + db eval $sql + } {1 w-one x-one y-one z-one 9 w-nine x-nine y-nine z-nine} + do_test where3-6.$cnt.2 { + set sql "SELECT * FROM t6w JOIN t6x USING(a) JOIN t6y USING(a)" + append sql " JOIN t6z USING(a) " + append sql $::predicate + db eval $sql + } {1 w-one x-one y-one z-one 9 w-nine x-nine y-nine z-nine} + do_test where3-6.$cnt.3 { + set sql "SELECT * FROM t6w NATURAL JOIN t6x JOIN t6y USING(a)" + append sql " JOIN t6z USING(a) " + append sql $::predicate + db eval $sql + } {1 w-one x-one y-one z-one 9 w-nine x-nine y-nine z-nine} + do_test where3-6.$cnt.4 { + set sql "SELECT * FROM t6w JOIN t6x USING(a) NATURAL JOIN t6y" + append sql " JOIN t6z USING(a) " + append sql $::predicate + db eval $sql + } {1 w-one x-one y-one z-one 9 w-nine x-nine y-nine z-nine} + do_test where3-6.$cnt.5 { + set sql "SELECT * FROM t6w JOIN t6x USING(a) JOIN t6y USING(a)" + append sql " NATURAL JOIN t6z " + append sql $::predicate + db eval $sql + } {1 w-one x-one y-one z-one 9 w-nine x-nine y-nine z-nine} + do_test where3-6.$cnt.6 { + set sql "SELECT * FROM t6w JOIN t6x USING(a) NATURAL JOIN t6y" + append sql " NATURAL JOIN t6z " + append sql $::predicate + db eval $sql + } {1 w-one x-one y-one z-one 9 w-nine x-nine y-nine z-nine} + do_test where3-6.$cnt.7 { + set sql "SELECT * FROM t6w NATURAL JOIN t6x JOIN t6y USING(a)" + append sql " NATURAL JOIN t6z " + append sql $::predicate + db eval $sql + } {1 w-one x-one y-one z-one 9 w-nine x-nine y-nine z-nine} + do_test where3-6.$cnt.8 { + set sql "SELECT * FROM t6w NATURAL JOIN t6x NATURAL JOIN t6y" + append sql " JOIN t6z USING(a) " + append sql $::predicate + db eval $sql + } {1 w-one x-one y-one z-one 9 w-nine x-nine y-nine z-nine} +} + finish_test Index: tool/omittest.tcl ================================================================== --- tool/omittest.tcl +++ tool/omittest.tcl @@ -46,36 +46,43 @@ # # run_quick_test /tmp/testdir {SQLITE_OMIT_TRIGGER SQLITE_OMIT_VIEW} # # proc run_quick_test {dir omit_symbol_list} { - set target "testfixture" # Compile the value of the OPTS Makefile variable. - set opts "-DSQLITE_MEMDEBUG -DSQLITE_DEBUG -DSQLITE_NO_SYNC" + set opts "" if {$::tcl_platform(platform)=="windows"} { - append opts " -DSQLITE_OS_WIN=1" + append opts "OPTS += -DSQLITE_OS_WIN=1\n" set target "testfixture.exe" } elseif {$::tcl_platform(platform)=="os2"} { - append opts " -DSQLITE_OS_OS2=1" + append opts "OPTS += -DSQLITE_OS_OS2=1\n" } else { - append opts " -DSQLITE_OS_UNIX=1" + append opts "OPTS += -DSQLITE_OS_UNIX=1\n" } foreach sym $omit_symbol_list { - append opts " -D${sym}=1" + append opts "OPTS += -D${sym}=1\n" } # Create the directory and do the build. If an error occurs return # early without attempting to run the test suite. file mkdir $dir puts -nonewline "Building $dir..." flush stdout -catch { - file copy -force ./config.h $dir - file copy -force ./libtool $dir -} + catch { + file copy -force ./config.h $dir + file copy -force ./libtool $dir + } + set fd [open $::MAKEFILE] + set mkfile [read $fd] + close $fd + regsub {\ninclude} $mkfile "\n$opts\ninclude" mkfile + set fd [open $dir/makefile w] + puts $fd $mkfile + close $fd + set rc [catch { - exec $::MAKEBIN -C $dir -f $::MAKEFILE clean $target OPTS=$opts >& $dir/build.log + exec $::MAKEBIN -C $dir -f makefile clean $::TARGET >& $dir/build.log }] if {$rc} { puts "No good. See $dir/build.log." return } else { @@ -100,11 +107,11 @@ } else { # Run the test suite. puts -nonewline "Testing $dir..." flush stdout set rc [catch { - exec $::MAKEBIN -C $dir -f $::MAKEFILE test OPTS=$opts >& $dir/test.log + exec $::MAKEBIN -C $dir -f makefile test >& $dir/test.log }] if {$rc} { puts "No good. See $dir/test.log." } else { puts "Ok" @@ -124,10 +131,11 @@ set ::MAKEFILE ./Makefile ;# Default value on Windows and OS2 } else { set ::MAKEFILE ./Makefile.linux-gcc ;# Default value } set ::SKIP_RUN 0 ;# Default to attempt test + set ::TARGET testfixture ;# Default thing to build for {set i 0} {$i < [llength $argv]} {incr i} { switch -- [lindex $argv $i] { -makefile { incr i @@ -136,10 +144,15 @@ -nmake { set ::MAKEBIN nmake set ::MAKEFILE ./Makefile.msc } + + -target { + incr i + set ::TARGET [lindex $argv $i] + } -skip_run { set ::SKIP_RUN 1 } @@ -180,11 +193,10 @@ SQLITE_OMIT_COMPLETE \ SQLITE_OMIT_COMPOUND_SELECT \ SQLITE_OMIT_DATETIME_FUNCS \ SQLITE_OMIT_DECLTYPE \ SQLITE_OMIT_DEPRECATED \ - xxxSQLITE_OMIT_DISKIO \ SQLITE_OMIT_EXPLAIN \ SQLITE_OMIT_FLAG_PRAGMAS \ SQLITE_OMIT_FLOATING_POINT \ SQLITE_OMIT_FOREIGN_KEY \ SQLITE_OMIT_GET_TABLE \ @@ -222,28 +234,24 @@ set ::ENABLE_SYMBOLS [list \ SQLITE_DISABLE_DIRSYNC \ SQLITE_DISABLE_LFS \ SQLITE_ENABLE_ATOMIC_WRITE \ - xxxSQLITE_ENABLE_CEROD \ SQLITE_ENABLE_COLUMN_METADATA \ SQLITE_ENABLE_EXPENSIVE_ASSERT \ - xxxSQLITE_ENABLE_FTS1 \ - xxxSQLITE_ENABLE_FTS2 \ SQLITE_ENABLE_FTS3 \ SQLITE_ENABLE_FTS3_PARENTHESIS \ SQLITE_ENABLE_FTS4 \ - xxxSQLITE_ENABLE_ICU \ SQLITE_ENABLE_IOTRACE \ SQLITE_ENABLE_LOAD_EXTENSION \ SQLITE_ENABLE_LOCKING_STYLE \ SQLITE_ENABLE_MEMORY_MANAGEMENT \ SQLITE_ENABLE_MEMSYS3 \ SQLITE_ENABLE_MEMSYS5 \ SQLITE_ENABLE_OVERSIZE_CELL_CHECK \ SQLITE_ENABLE_RTREE \ - SQLITE_ENABLE_STAT2 \ + SQLITE_ENABLE_STAT3 \ SQLITE_ENABLE_UNLOCK_NOTIFY \ SQLITE_ENABLE_UPDATE_DELETE_LIMIT \ ] # Process any command line options. ADDED tool/symbols-mingw.sh Index: tool/symbols-mingw.sh ================================================================== --- /dev/null +++ tool/symbols-mingw.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# +# Run this script in a directory that contains a valid SQLite makefile in +# order to verify that unintentionally exported symbols. +# +make sqlite3.c + +echo '****** Exported symbols from a build including RTREE && FTS4 ******' +gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \ + -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \ + -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \ + sqlite3.c +nm sqlite3.o | grep " [TD] " + +echo '****** Surplus symbols from a build including RTREE & FTS4 ******' +nm sqlite3.o | grep " [TD] " | grep -v " .*sqlite3_" + +echo '****** Dependencies of the core. No extensions. No OS interface *******' +gcc -c -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \ + -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \ + -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \ + -DSQLITE_OS_OTHER -DSQLITE_THREADSAFE=0 \ + sqlite3.c +nm sqlite3.o | grep " U " + +echo '****** Dependencies including RTREE & FTS4 *******' +gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \ + -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \ + -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \ + sqlite3.c +nm sqlite3.o | grep " U " Index: tool/symbols.sh ================================================================== --- tool/symbols.sh +++ tool/symbols.sh @@ -5,11 +5,11 @@ # make sqlite3.c echo '****** Exported symbols from a build including RTREE, FTS4 & ICU ******' gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \ - -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT2 \ + -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \ -DSQLITE_ENABLE_ICU \ sqlite3.c nm sqlite3.o | grep ' [TD] ' | sort -k 3 @@ -16,19 +16,19 @@ echo '****** Surplus symbols from a build including RTREE, FTS4 & ICU ******' nm sqlite3.o | grep ' [TD] ' | grep -v ' .*sqlite3_' echo '****** Dependencies of the core. No extensions. No OS interface *******' -gcc -c -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT2 \ +gcc -c -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \ -DSQLITE_OS_OTHER -DSQLITE_THREADSAFE=0 \ sqlite3.c nm sqlite3.o | grep ' U ' | sort -k 3 echo '****** Dependencies including RTREE & FTS4 *******' gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \ - -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT2 \ + -DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \ -DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \ -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \ sqlite3.c nm sqlite3.o | grep ' U ' | sort -k 3 ADDED tool/warnings-clang.sh Index: tool/warnings-clang.sh ================================================================== --- /dev/null +++ tool/warnings-clang.sh @@ -0,0 +1,13 @@ +#/bin/sh +# +# Run this script in a directory with a working makefile to check for +# compiler warnings in SQLite. +# +rm -f sqlite3.c +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:'