Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Convert fts2 to use sqlite3_prepare_v2() to prevent certain logic
errors around SQLITE_SCHEMA handling. This also allows
sql_step_statement() and sql_step_leaf_statement() to be replaced with
sqlite3_step().
Also fix a logic error in flushPendingTerms() which was clearing the term table in case of error. This was wrong in the face of SQLITE_SCHEMA. Even though the change to sqlite3_prepare_v2() should cause us not to see SQLITE_SCHEMA any longer, it was still a logic error... (CVS 4205) |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
16730cb137eaf576b87cdc17913564c9 |
User & Date: | shess 2007-08-10 23:47:04.000 |
Context
2007-08-10
| ||
23:54 | Fix the corruption problem of ticket #2565 as demonstrated by the test added in (4204). There may yet be other instances of similar problems lurking in the code. (CVS 4206) (check-in: 7ed2f59e70 user: drh tags: trunk) | |
23:47 |
Convert fts2 to use sqlite3_prepare_v2() to prevent certain logic
errors around SQLITE_SCHEMA handling. This also allows
sql_step_statement() and sql_step_leaf_statement() to be replaced with
sqlite3_step().
Also fix a logic error in flushPendingTerms() which was clearing the term table in case of error. This was wrong in the face of SQLITE_SCHEMA. Even though the change to sqlite3_prepare_v2() should cause us not to see SQLITE_SCHEMA any longer, it was still a logic error... (CVS 4205) (check-in: 16730cb137 user: shess tags: trunk) | |
19:46 | Add a test case to reproduce the database corruption problem reported by ticket #2565. (CVS 4204) (check-in: f267ce8094 user: drh tags: trunk) | |
Changes
Changes to ext/fts2/fts2.c.
︙ | ︙ | |||
1645 1646 1647 1648 1649 1650 1651 | } static int sql_prepare(sqlite3 *db, const char *zDb, const char *zName, sqlite3_stmt **ppStmt, const char *zFormat){ char *zCommand = string_format(zFormat, zDb, zName); int rc; TRACE(("FTS2 prepare: %s\n", zCommand)); | | | 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 | } static int sql_prepare(sqlite3 *db, const char *zDb, const char *zName, sqlite3_stmt **ppStmt, const char *zFormat){ char *zCommand = string_format(zFormat, zDb, zName); int rc; TRACE(("FTS2 prepare: %s\n", zCommand)); rc = sqlite3_prepare_v2(db, zCommand, -1, ppStmt, NULL); free(zCommand); return rc; } /* end utility functions */ /* Forward reference */ |
︙ | ︙ | |||
1921 1922 1923 1924 1925 1926 1927 | if( rc!=SQLITE_OK ) return rc; } *ppStmt = v->pFulltextStatements[iStmt]; return SQLITE_OK; } | < < < < < < < < < < < < | < | < < < < < < < < < < < < < < < < < | | < | < | 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 | if( rc!=SQLITE_OK ) return rc; } *ppStmt = v->pFulltextStatements[iStmt]; return SQLITE_OK; } /* Like sqlite3_step(), but convert SQLITE_DONE to SQLITE_OK and ** SQLITE_ROW to SQLITE_ERROR. Useful for statements like UPDATE, ** where we expect no results. */ static int sql_single_step(sqlite3_stmt *s){ int rc = sqlite3_step(s); return (rc==SQLITE_DONE) ? SQLITE_OK : rc; } /* Like sql_get_statement(), but for special replicated LEAF_SELECT ** statements. */ /* TODO(shess) Write version for generic statements and then share |
︙ | ︙ | |||
1984 1985 1986 1987 1988 1989 1990 | if( rc!=SQLITE_OK ) return rc; } *ppStmt = v->pLeafSelectStmts[idx]; return SQLITE_OK; } | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | | | 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 | if( rc!=SQLITE_OK ) return rc; } *ppStmt = v->pLeafSelectStmts[idx]; return SQLITE_OK; } /* insert into %_content (rowid, ...) values ([rowid], [pValues]) */ static int content_insert(fulltext_vtab *v, sqlite3_value *rowid, sqlite3_value **pValues){ sqlite3_stmt *s; int i; int rc = sql_get_statement(v, CONTENT_INSERT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_value(s, 1, rowid); if( rc!=SQLITE_OK ) return rc; for(i=0; i<v->nColumn; ++i){ rc = sqlite3_bind_value(s, 2+i, pValues[i]); if( rc!=SQLITE_OK ) return rc; } return sql_single_step(s); } /* update %_content set col0 = pValues[0], col1 = pValues[1], ... * where rowid = [iRowid] */ static int content_update(fulltext_vtab *v, sqlite3_value **pValues, sqlite_int64 iRowid){ sqlite3_stmt *s; int i; int rc = sql_get_statement(v, CONTENT_UPDATE_STMT, &s); if( rc!=SQLITE_OK ) return rc; for(i=0; i<v->nColumn; ++i){ rc = sqlite3_bind_value(s, 1+i, pValues[i]); if( rc!=SQLITE_OK ) return rc; } rc = sqlite3_bind_int64(s, 1+v->nColumn, iRowid); if( rc!=SQLITE_OK ) return rc; return sql_single_step(s); } static void freeStringArray(int nString, const char **pString){ int i; for (i=0 ; i < nString ; ++i) { if( pString[i]!=NULL ) free((void *) pString[i]); |
︙ | ︙ | |||
2083 2084 2085 2086 2087 2088 2089 | rc = sql_get_statement(v, CONTENT_SELECT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iRow); if( rc!=SQLITE_OK ) return rc; | | | 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 | rc = sql_get_statement(v, CONTENT_SELECT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iRow); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_step(s); if( rc!=SQLITE_ROW ) return rc; values = (const char **) malloc(v->nColumn * sizeof(const char *)); for(i=0; i<v->nColumn; ++i){ if( sqlite3_column_type(s, i)==SQLITE_NULL ){ values[i] = NULL; }else{ |
︙ | ︙ | |||
2116 2117 2118 2119 2120 2121 2122 | sqlite3_stmt *s; int rc = sql_get_statement(v, CONTENT_DELETE_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iRow); if( rc!=SQLITE_OK ) return rc; | | | | 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 | sqlite3_stmt *s; int rc = sql_get_statement(v, CONTENT_DELETE_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iRow); if( rc!=SQLITE_OK ) return rc; return sql_single_step(s); } /* insert into %_segments values ([pData]) ** returns assigned rowid in *piBlockid */ static int block_insert(fulltext_vtab *v, const char *pData, int nData, sqlite_int64 *piBlockid){ sqlite3_stmt *s; int rc = sql_get_statement(v, BLOCK_INSERT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_blob(s, 1, pData, nData, SQLITE_STATIC); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_step(s); if( rc==SQLITE_ROW ) return SQLITE_ERROR; if( rc!=SQLITE_DONE ) return rc; *piBlockid = sqlite3_last_insert_rowid(v->db); return SQLITE_OK; } |
︙ | ︙ | |||
2157 2158 2159 2160 2161 2162 2163 | rc = sqlite3_bind_int64(s, 1, iStartBlockid); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 2, iEndBlockid); if( rc!=SQLITE_OK ) return rc; | | | | 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 | rc = sqlite3_bind_int64(s, 1, iStartBlockid); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 2, iEndBlockid); if( rc!=SQLITE_OK ) return rc; return sql_single_step(s); } /* Returns SQLITE_ROW with *pidx set to the maximum segment idx found ** at iLevel. Returns SQLITE_DONE if there are no segments at ** iLevel. Otherwise returns an error. */ static int segdir_max_index(fulltext_vtab *v, int iLevel, int *pidx){ sqlite3_stmt *s; int rc = sql_get_statement(v, SEGDIR_MAX_INDEX_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int(s, 1, iLevel); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_step(s); /* Should always get at least one row due to how max() works. */ if( rc==SQLITE_DONE ) return SQLITE_DONE; if( rc!=SQLITE_ROW ) return rc; /* NULL means that there were no inputs to max(). */ if( SQLITE_NULL==sqlite3_column_type(s, 0) ){ rc = sqlite3_step(s); |
︙ | ︙ | |||
2227 2228 2229 2230 2231 2232 2233 | rc = sqlite3_bind_int64(s, 5, iEndBlockid); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_blob(s, 6, pRootData, nRootData, SQLITE_STATIC); if( rc!=SQLITE_OK ) return rc; | | | | 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 | rc = sqlite3_bind_int64(s, 5, iEndBlockid); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_blob(s, 6, pRootData, nRootData, SQLITE_STATIC); if( rc!=SQLITE_OK ) return rc; return sql_single_step(s); } /* Queries %_segdir for the block span of the segments in level ** iLevel. Returns SQLITE_DONE if there are no blocks for iLevel, ** SQLITE_ROW if there are blocks, else an error. */ static int segdir_span(fulltext_vtab *v, int iLevel, sqlite_int64 *piStartBlockid, sqlite_int64 *piEndBlockid){ sqlite3_stmt *s; int rc = sql_get_statement(v, SEGDIR_SPAN_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int(s, 1, iLevel); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_step(s); if( rc==SQLITE_DONE ) return SQLITE_DONE; /* Should never happen */ if( rc!=SQLITE_ROW ) return rc; /* This happens if all segments at this level are entirely inline. */ if( SQLITE_NULL==sqlite3_column_type(s, 0) ){ /* We expect only one row. We must execute another sqlite3_step() * to complete the iteration; otherwise the table will remain locked. */ |
︙ | ︙ | |||
2289 2290 2291 2292 2293 2294 2295 | /* Delete the segment directory itself. */ rc = sql_get_statement(v, SEGDIR_DELETE_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iLevel); if( rc!=SQLITE_OK ) return rc; | | | 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 | /* Delete the segment directory itself. */ rc = sql_get_statement(v, SEGDIR_DELETE_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iLevel); if( rc!=SQLITE_OK ) return rc; return sql_single_step(s); } /* TODO(shess) clearPendingTerms() is far down the file because ** writeZeroSegment() is far down the file because LeafWriter is far ** down the file. Consider refactoring the code to move the non-vtab ** code above the vtab code so that we don't need this forward ** reference. |
︙ | ︙ | |||
5009 5010 5011 5012 5013 5014 5015 | rc = sqlite3_bind_int64(s, 1, iStartBlockid); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 2, iEndBlockid); if( rc!=SQLITE_OK ) return rc; | | | 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 | rc = sqlite3_bind_int64(s, 1, iStartBlockid); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 2, iEndBlockid); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_step(s); if( rc==SQLITE_DONE ){ pReader->eof = 1; return SQLITE_OK; } if( rc!=SQLITE_ROW ) return rc; pReader->pStmt = s; |
︙ | ︙ | |||
5037 5038 5039 5040 5041 5042 5043 | if( leafReaderAtEnd(&pReader->leafReader) ){ int rc; if( pReader->rootData.pData ){ pReader->eof = 1; return SQLITE_OK; } | | | 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 | if( leafReaderAtEnd(&pReader->leafReader) ){ int rc; if( pReader->rootData.pData ){ pReader->eof = 1; return SQLITE_OK; } rc = sqlite3_step(pReader->pStmt); if( rc!=SQLITE_ROW ){ pReader->eof = 1; return rc==SQLITE_DONE ? SQLITE_OK : rc; } leafReaderDestroy(&pReader->leafReader); leafReaderInit(sqlite3_column_blob(pReader->pStmt, 0), sqlite3_column_bytes(pReader->pStmt, 0), |
︙ | ︙ | |||
5101 5102 5103 5104 5105 5106 5107 | int i, rc = sql_get_statement(v, SEGDIR_SELECT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int(s, 1, iLevel); if( rc!=SQLITE_OK ) return rc; i = 0; | | | 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 | int i, rc = sql_get_statement(v, SEGDIR_SELECT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int(s, 1, iLevel); if( rc!=SQLITE_OK ) return rc; i = 0; while( (rc = sqlite3_step(s))==SQLITE_ROW ){ sqlite_int64 iStart = sqlite3_column_int64(s, 0); sqlite_int64 iEnd = sqlite3_column_int64(s, 1); const char *pRootData = sqlite3_column_blob(s, 2); int nRootData = sqlite3_column_bytes(s, 2); assert( i<MERGE_COUNT ); rc = leavesReaderInit(v, i, iStart, iEnd, pRootData, nRootData, |
︙ | ︙ | |||
5396 5397 5398 5399 5400 5401 5402 | rc = sql_get_statement(v, BLOCK_SELECT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iBlockid); if( rc!=SQLITE_OK ) return rc; | | | 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 | rc = sql_get_statement(v, BLOCK_SELECT_STMT, &s); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_bind_int64(s, 1, iBlockid); if( rc!=SQLITE_OK ) return rc; rc = sqlite3_step(s); if( rc==SQLITE_DONE ) return SQLITE_ERROR; if( rc!=SQLITE_ROW ) return rc; getChildrenContaining(sqlite3_column_blob(s, 0), sqlite3_column_bytes(s, 0), pTerm, nTerm, isPrefix, piStartChild, piEndChild); /* We expect only one row. We must execute another sqlite3_step() |
︙ | ︙ | |||
5534 5535 5536 5537 5538 5539 5540 | assert( v->nPendingData<0 ); dataBufferInit(&doclist, 0); /* Traverse the segments from oldest to newest so that newer doclist ** elements for given docids overwrite older elements. */ | | | 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 | assert( v->nPendingData<0 ); dataBufferInit(&doclist, 0); /* Traverse the segments from oldest to newest so that newer doclist ** elements for given docids overwrite older elements. */ while( (rc = sqlite3_step(s))==SQLITE_ROW ){ const char *pData = sqlite3_column_blob(s, 0); const int nData = sqlite3_column_bytes(s, 0); const sqlite_int64 iLeavesEnd = sqlite3_column_int64(s, 1); rc = loadSegment(v, pData, nData, iLeavesEnd, pTerm, nTerm, isPrefix, &doclist); if( rc!=SQLITE_OK ) goto err; } |
︙ | ︙ | |||
5651 5652 5653 5654 5655 5656 5657 | /* If pendingTerms has data, flush it to a level-zero segment, and ** free it. */ static int flushPendingTerms(fulltext_vtab *v){ if( v->nPendingData>=0 ){ int rc = writeZeroSegment(v, &v->pendingTerms); | | | 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 | /* If pendingTerms has data, flush it to a level-zero segment, and ** free it. */ static int flushPendingTerms(fulltext_vtab *v){ if( v->nPendingData>=0 ){ int rc = writeZeroSegment(v, &v->pendingTerms); if( rc==SQLITE_OK ) clearPendingTerms(v); return rc; } return SQLITE_OK; } /* If pendingTerms is "too big", or docid is out of order, flush it. ** Regardless, be certain that pendingTerms is initialized for use. |
︙ | ︙ |
Changes to test/fts2k.test.
1 2 3 4 5 6 | # 2007 March 9 # # The author disclaims copyright to this source code. # #************************************************************************* # This file implements regression tests for SQLite library. These | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # 2007 March 9 # # The author disclaims copyright to this source code. # #************************************************************************* # This file implements regression tests for SQLite library. These # make sure that fts2 insertion buffering is fully transparent when # using transactions. # # $Id: fts2k.test,v 1.2 2007/08/10 23:47:04 shess Exp $ # set testdir [file dirname $argv0] source $testdir/tester.tcl # If SQLITE_ENABLE_FTS2 is defined, omit this file. ifcapable !fts2 { |
︙ | ︙ | |||
75 76 77 78 79 80 81 82 83 | BEGIN TRANSACTION; INSERT INTO t1 (rowid, content) VALUES(10, "second world"); INSERT INTO t1 (rowid, content) VALUES(11, "second sight"); ROLLBACK TRANSACTION; SELECT rowid FROM t1 WHERE t1 MATCH 'world'; } } {1 3 4 6} finish_test | > > > > > > > > > > > > > > > > > > > > > > | 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | BEGIN TRANSACTION; INSERT INTO t1 (rowid, content) VALUES(10, "second world"); INSERT INTO t1 (rowid, content) VALUES(11, "second sight"); ROLLBACK TRANSACTION; SELECT rowid FROM t1 WHERE t1 MATCH 'world'; } } {1 3 4 6} # Test that the obvious case works. do_test fts2k-1.6 { execsql { BEGIN; INSERT INTO t1 (rowid, content) VALUES(12, "third world"); COMMIT; SELECT rowid FROM t1 WHERE t1 MATCH 'third'; } } {12} # This is exactly the same as the previous test, except that older # code loses the INSERT due to an SQLITE_SCHEMA error. do_test fts2k-1.7 { execsql { BEGIN; INSERT INTO t1 (rowid, content) VALUES(13, "third dimension"); CREATE TABLE x (c); COMMIT; SELECT rowid FROM t1 WHERE t1 MATCH 'dimension'; } } {13} finish_test |