Fix various issues in sqlite3intck.c.

FossilOrigin-Name: 8a7bfa74525a495f45b1ea212b1718633b637295090d514dd777f9263477d514
This commit is contained in:
dan
2024-02-21 20:58:48 +00:00
parent 6a00f707e8
commit 639db50a02
3 changed files with 145 additions and 117 deletions
+138 -110
View File
@@ -14,12 +14,25 @@
#include "sqlite3intck.h"
#include <string.h>
#include <assert.h>
#include <stdio.h>
/*
** nKeyVal:
** The number of values that make up the 'key' for the current pCheck
** statement.
**
** rc:
** Error code returned by most recent sqlite3_intck_step() or
** sqlite3_intck_unlock() call. This is set to SQLITE_DONE when
** the integrity-check operation is finished.
**
** zErr:
** If the object has entered the error state, this is the error message.
** Is freed using sqlite3_free() when the object is deleted.
**
** zTestSql:
** The value returned by the most recent call to sqlite3_intck_testsql().
** Each call to testsql() frees the previous zTestSql value (using
** sqlite3_free()) and replaces it with the new value it will return.
*/
struct sqlite3_intck {
sqlite3 *db;
@@ -52,40 +65,56 @@ static void intckSaveErrmsg(sqlite3_intck *p){
}
}
static sqlite3_stmt *intckPrepare(sqlite3_intck *p, const char *zFmt, ...){
/*
** If the handle passed as the first argument is already in the error state,
** then this function is a no-op (returns NULL immediately). Otherwise, if an
** error occurs within this function, it leaves an error in said handle.
**
** Otherwise, this function attempts to prepare SQL statement zSql and
** return the resulting statement handle to the user.
*/
static sqlite3_stmt *intckPrepare(sqlite3_intck *p, const char *zSql){
sqlite3_stmt *pRet = 0;
if( p->rc==SQLITE_OK ){
p->rc = sqlite3_prepare_v2(p->db, zSql, -1, &pRet, 0);
if( p->rc!=SQLITE_OK ){
intckSaveErrmsg(p);
assert( pRet==0 );
}
}
return pRet;
}
/*
** If the handle passed as the first argument is already in the error state,
** then this function is a no-op (returns NULL immediately). Otherwise, if an
** error occurs within this function, it leaves an error in said handle.
**
** Otherwise, this function treats argument zFmt as a printf() style format
** string. It formats it according to the trailing arguments and then
** attempts to prepare the results and return the resulting prepared
** statement.
*/
static sqlite3_stmt *intckPrepareFmt(sqlite3_intck *p, const char *zFmt, ...){
sqlite3_stmt *pRet = 0;
va_list ap;
char *zSql = 0;
va_start(ap, zFmt);
zSql = sqlite3_vmprintf(zFmt, ap);
if( p->rc==SQLITE_OK ){
if( zSql==0 ){
p->rc = SQLITE_NOMEM;
}else{
p->rc = sqlite3_prepare_v2(p->db, zSql, -1, &pRet, 0);
fflush(stdout);
if( p->rc!=SQLITE_OK ){
#if 0
printf("ERROR: %s\n", zSql);
printf("MSG: %s\n", sqlite3_errmsg(p->db));
#endif
if( sqlite3_error_offset(p->db)>=0 ){
#if 0
int iOff = sqlite3_error_offset(p->db);
printf("AT: %.40s\n", &zSql[iOff]);
#endif
}
fflush(stdout);
intckSaveErrmsg(p);
assert( pRet==0 );
}
}
if( p->rc==SQLITE_OK && zSql==0 ){
p->rc = SQLITE_NOMEM;
}
pRet = intckPrepare(p, zSql);
sqlite3_free(zSql);
va_end(ap);
return pRet;
}
/*
** Finalize SQL statement pStmt. If an error occurs and the handle passed
** as the first argument does not already contain an error, store the
** error in the handle.
*/
static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){
int rc = sqlite3_finalize(pStmt);
if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){
@@ -93,6 +122,18 @@ static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){
}
}
/*
** Execute SQL statement zSql. There is no way to obtain any results
** returned by the statement. This function uses the sqlite3_intck error
** code convention.
*/
static void intckExec(sqlite3_intck *p, const char *zSql){
sqlite3_stmt *pStmt = 0;
pStmt = intckPrepare(p, zSql);
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) );
intckFinalize(p, pStmt);
}
/*
** Wrapper around sqlite3_malloc64() that uses the sqlite3_intck error
** code convention.
@@ -110,15 +151,8 @@ static void *intckMalloc(sqlite3_intck *p, sqlite3_int64 nByte){
}
/*
** If p->rc is other than SQLITE_OK when this function is called, it
** immediately returns NULL. Otherwise, it attempts to create a copy of
** nul-terminated string zIn in a buffer obtained from sqlite3_malloc().
** If successful, a pointer to this buffer is returned and it becomes
** the responsibility of the caller to release it using sqlite3_free()
** at some point in the future.
**
** Or, if an allocation fails within this function, p->rc is set to
** SQLITE_NOMEM and NULL is returned.
** Like strdup(), but uses the sqlite3_intck error code convention. Any
** returned buffer should eventually be freed using sqlite3_free().
*/
static char *intckStrdup(sqlite3_intck *p, const char *zIn){
char *zOut = 0;
@@ -131,10 +165,8 @@ static char *intckStrdup(sqlite3_intck *p, const char *zIn){
}
/*
** A wrapper around sqlite3_mprintf() that:
**
** + Always returns 0 if p->rc is other than SQLITE_OK when it is called, and
** + Sets p->rc to SQLITE_NOMEM if an allocation fails.
** A wrapper around sqlite3_mprintf() that uses the sqlite3_intck error
** code convention.
*/
static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){
va_list ap;
@@ -153,25 +185,11 @@ static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){
}
/*
** Free the sqlite3_intck.apKeyVal, if it is allocated and populated.
** This is used by sqlite3_intck_unlock() to save the vector key value
** required to restart the current pCheck query as a nul-terminated string
** in p->zKey.
*/
static void intckSavedKeyClear(sqlite3_intck *p){
sqlite3_free(p->zKey);
p->zKey = 0;
}
/*
** If the apKeyVal array is currently allocated and populated, return
** a pointer to a buffer containing a nul-terminated string representing
** the values as an SQL vector. e.g.
**
** "('abc', NULL, 2)"
**
** If apKeyVal is not allocated, return NULL. Or, if an error (e.g. OOM)
** occurs within this function, set sqlite3_intck.rc before returning
** and return NULL.
*/
static void intckSavedKeySave(sqlite3_intck *p){
static void intckSaveKey(sqlite3_intck *p){
int ii;
const char *zSep = "SELECT '(' || ";
char *zSql = 0;
@@ -180,14 +198,13 @@ static void intckSavedKeySave(sqlite3_intck *p){
assert( p->pCheck );
assert( p->zKey==0 );
for(ii=0; ii<p->nKeyVal; ii++){
zSql = intckMprintf(p, "%z%squote(?)", zSql, zSep);
zSep = " || ', ' || ";
}
zSql = intckMprintf(p, "%z || ')'", zSql);
pStmt = intckPrepare(p, "%s", zSql);
pStmt = intckPrepare(p, zSql);
if( p->rc==SQLITE_OK ){
for(ii=0; ii<p->nKeyVal; ii++){
sqlite3_bind_value(pStmt, ii+1, sqlite3_column_value(p->pCheck, ii+1));
@@ -213,7 +230,7 @@ static void intckFindObject(sqlite3_intck *p){
assert( p->rc==SQLITE_OK );
assert( p->pCheck==0 );
pStmt = intckPrepare(p,
pStmt = intckPrepareFmt(p,
"WITH tables(table_name) AS ("
" SELECT name"
" FROM %Q.sqlite_schema WHERE (type='table' OR type='index') AND rootpage"
@@ -236,7 +253,8 @@ static void intckFindObject(sqlite3_intck *p){
/* If this is a new object, ensure the previous key value is cleared. */
if( sqlite3_stricmp(p->zObj, zPrev) ){
intckSavedKeyClear(p);
sqlite3_free(p->zKey);
p->zKey = 0;
}
sqlite3_free(zPrev);
@@ -274,22 +292,13 @@ static int intckGetToken(const char *z){
return iRet;
}
/*
** Return true if argument c is an ascii whitespace character.
*/
static int intckIsSpace(char c){
return (c==' ' || c=='\t' || c=='\n' || c=='\r');
}
static int intckTokenMatch(
const char *zToken,
int nToken,
const char *z1,
const char *z2
){
return (
(strlen(z1)==nToken && 0==sqlite3_strnicmp(zToken, z1, nToken))
|| (z2 && strlen(z2)==nToken && 0==sqlite3_strnicmp(zToken, z2, nToken))
);
}
/*
** Argument z points to the text of a CREATE INDEX statement. This function
** identifies the part of the text that contains either the index WHERE
@@ -350,7 +359,9 @@ static const char *intckParseCreateIndex(const char *z, int iCol, int *pnByte){
if( z[iOff]==')' ) nOpen--;
nToken = intckGetToken(zToken);
if( intckTokenMatch(zToken, nToken, "ASC", "DESC") ){
if( (nToken==3 && 0==sqlite3_strnicmp(zToken, "ASC", nToken))
|| (nToken==4 && 0==sqlite3_strnicmp(zToken, "DESC", nToken))
){
iEndOfCol = iOff;
}else if( 0==intckIsSpace(zToken[0]) ){
iEndOfCol = 0;
@@ -383,7 +394,12 @@ static const char *intckParseCreateIndex(const char *z, int iCol, int *pnByte){
return zRet;
}
static void parseCreateIndexFunc(
/*
** User-defined SQL function wrapper for intckParseCreateIndex():
**
** SELECT parse_create_index(<sql>, <icol>);
*/
static void intckParseCreateIndexFunc(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
@@ -421,7 +437,7 @@ static int intckGetAutoIndex(sqlite3_intck *p){
static int intckIsIndex(sqlite3_intck *p, const char *zObj){
int bRet = 0;
sqlite3_stmt *pStmt = 0;
pStmt = intckPrepare(p,
pStmt = intckPrepareFmt(p,
"SELECT 1 FROM %Q.sqlite_schema WHERE name=%Q AND type='index'",
p->zDb, zObj
);
@@ -432,17 +448,20 @@ static int intckIsIndex(sqlite3_intck *p, const char *zObj){
return bRet;
}
static void intckExec(sqlite3_intck *p, const char *zSql){
sqlite3_stmt *pStmt = 0;
pStmt = intckPrepare(p, "%s", zSql);
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) );
intckFinalize(p, pStmt);
}
/*
** Return a pointer to a nul-terminated buffer containing the SQL statement
** used to check database object zObj (a table or index) for corruption.
** If parameter zPrev is not NULL, then it must be a string containing the
** vector key required to restart the check where it left off last time.
** If pnKeyVal is not NULL, then (*pnKeyVal) is set to the number of
** columns in the vector key value for the specified object.
**
** This function uses the sqlite3_intck error code convention.
*/
static char *intckCheckObjectSql(
sqlite3_intck *p,
const char *zObj,
const char *zPrev,
sqlite3_intck *p, /* Integrity check object */
const char *zObj, /* Object (table or index) to scan */
const char *zPrev, /* Restart key vector, if any */
int *pnKeyVal /* OUT: Number of key-values for this scan */
){
char *zRet = 0;
@@ -563,7 +582,7 @@ static char *intckCheckObjectSql(
bIsIndex = intckIsIndex(p, zObj);
if( bIsIndex ){
pStmt = intckPrepare(p,
pStmt = intckPrepareFmt(p,
/* Table idxname contains a single row. The first column, "db", contains
** the name of the db containing the table (e.g. "main") and the second,
** "tab", the name of the table itself. */
@@ -616,7 +635,7 @@ static char *intckCheckObjectSql(
, zPrev, zCommon
);
}else{
pStmt = intckPrepare(p,
pStmt = intckPrepareFmt(p,
/* Table tabname contains a single row. The first column, "db", contains
** the name of the db containing the table (e.g. "main") and the second,
** "tab", the name of the table itself. */
@@ -697,22 +716,10 @@ static char *intckCheckObjectSql(
}
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
#if 0
int nField = sqlite3_column_count(pStmt);
int ii;
for(ii=0; ii<nField; ii++){
const char *zName = sqlite3_column_name(pStmt, ii);
const char *zVal = (const char*)sqlite3_column_text(pStmt, ii);
printf("FIELD %s = %s\n", zName, zVal ? zVal : "(null)");
}
printf("\n");
fflush(stdout);
#else
zRet = intckStrdup(p, (const char*)sqlite3_column_text(pStmt, 0));
if( pnKeyVal ){
*pnKeyVal = sqlite3_column_int(pStmt, 1);
}
#endif
}
intckFinalize(p, pStmt);
@@ -720,14 +727,9 @@ static char *intckCheckObjectSql(
return zRet;
}
static void intckCheckObject(sqlite3_intck *p){
char *zSql = 0;
zSql = intckCheckObjectSql(p, p->zObj, p->zKey, &p->nKeyVal);
p->pCheck = intckPrepare(p, "%s", zSql);
sqlite3_free(zSql);
intckSavedKeyClear(p);
}
/*
** Open a new integrity-check object.
*/
int sqlite3_intck_open(
sqlite3 *db, /* Database handle to operate on */
const char *zDbArg, /* "main", "temp" etc. */
@@ -743,7 +745,7 @@ int sqlite3_intck_open(
rc = SQLITE_NOMEM;
}else{
sqlite3_create_function(db, "parse_create_index",
2, SQLITE_UTF8, 0, parseCreateIndexFunc, 0, 0
2, SQLITE_UTF8, 0, intckParseCreateIndexFunc, 0, 0
);
memset(pNew, 0, sizeof(*pNew));
pNew->db = db;
@@ -755,6 +757,9 @@ int sqlite3_intck_open(
return rc;
}
/*
** Free the integrity-check object.
*/
void sqlite3_intck_close(sqlite3_intck *p){
if( p ){
if( p->db ){
@@ -763,13 +768,16 @@ void sqlite3_intck_close(sqlite3_intck *p){
);
}
sqlite3_free(p->zObj);
intckSavedKeyClear(p);
sqlite3_free(p->zKey);
sqlite3_free(p->zTestSql);
sqlite3_free(p->zErr);
sqlite3_free(p);
}
}
/*
** Step the integrity-check object.
*/
int sqlite3_intck_step(sqlite3_intck *p){
if( p->rc==SQLITE_OK ){
@@ -785,7 +793,12 @@ int sqlite3_intck_step(sqlite3_intck *p){
intckFindObject(p);
if( p->rc==SQLITE_OK ){
if( p->zObj ){
intckCheckObject(p);
char *zSql = 0;
zSql = intckCheckObjectSql(p, p->zObj, p->zKey, &p->nKeyVal);
p->pCheck = intckPrepare(p, zSql);
sqlite3_free(zSql);
sqlite3_free(p->zKey);
p->zKey = 0;
}else{
p->rc = SQLITE_DONE;
}
@@ -819,6 +832,10 @@ int sqlite3_intck_step(sqlite3_intck *p){
return p->rc;
}
/*
** Return a message describing the corruption encountered by the most recent
** call to sqlite3_intck_step(), or NULL if no corruption was encountered.
*/
const char *sqlite3_intck_message(sqlite3_intck *p){
assert( p->pCheck==0 || p->zMessage==0 );
if( p->zMessage ){
@@ -830,21 +847,32 @@ const char *sqlite3_intck_message(sqlite3_intck *p){
return 0;
}
/*
** Return the error code and message.
*/
int sqlite3_intck_error(sqlite3_intck *p, const char **pzErr){
if( pzErr ) *pzErr = p->zErr;
return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc);
}
/*
** Close any read transaction the integrity-check object is holding open
** on the database.
*/
int sqlite3_intck_unlock(sqlite3_intck *p){
if( p->pCheck && p->rc==SQLITE_OK ){
assert( p->zKey==0 && p->nKeyVal>0 );
intckSavedKeySave(p);
intckSaveKey(p);
intckFinalize(p, p->pCheck);
p->pCheck = 0;
}
return p->rc;
}
/*
** Return the SQL statement used to check object zObj. Or, if zObj is
** NULL, the current SQL statement.
*/
const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){
sqlite3_free(p->zTestSql);
if( zObj ){
+6 -6
View File
@@ -1,5 +1,5 @@
C Simplify\sthe\sway\sthe\srestart\skey\sis\ssaved\sinternally\sby\sthe\sintck\sextension.
D 2024-02-21T19:31:00.293
C Fix\svarious\sissues\sin\ssqlite3intck.c.
D 2024-02-21T20:58:48.924
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -252,7 +252,7 @@ F ext/intck/intck1.test 15e94ee868e939c6d3aef6884c5652d969b28d3eac940a15585511cf
F ext/intck/intck2.test b65d7f627342f767e1d2c447d25619666cec36f516786dd56568bd741e5d7e67
F ext/intck/intck_common.tcl b8a63ae820dbcdf50ddaba474f130b43c26ba81f4b7720ff1d8b9a6592bdd420
F ext/intck/intckcorrupt.test 3211ef68ac53e83951b6c8f6a8d2396506d123fe5898f97f848a25837744ec56
F ext/intck/sqlite3intck.c c437aaa1f4af77621ba984718a62c57722ad9d071151be79335fd87f2c2c2dde
F ext/intck/sqlite3intck.c 3fa96647dde4c719a477f7dbdd1edf76b2f245549b1a4de7f7d60447b24a14df
F ext/intck/sqlite3intck.h 2b40c38e7063ab822c974c0bd4aed97dabb579ccfe2e180a4639bb3bbef0f1c9
F ext/intck/test_intck.c dec07fc82e2626a1450e58be474e351643627b04ee08ce8fae6495a533b87e85
F ext/jni/GNUmakefile 59eb05f2a363bdfac8d15d66bed624bfe1ff289229184f3861b95f98a19cf4b2
@@ -2169,8 +2169,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 4cc19bd74f05fe92658cc392a1d1afa173d93181a77303af6bc5684436ae832e
R 735ea6d5058bc579028d0f5e808c7418
P 0e39962baae8a82a3021077676b792ac30c79426bcd8c075b5e92bee55e8c3a5
R 2d481f6c889817bf845056ed7696633c
U dan
Z b84fa961179c349189e714cd49d2cd34
Z 13c318cbe203f9a86dd5c12df0ee017f
# Remove this line to create a well-formed Fossil manifest.
+1 -1
View File
@@ -1 +1 @@
0e39962baae8a82a3021077676b792ac30c79426bcd8c075b5e92bee55e8c3a5
8a7bfa74525a495f45b1ea212b1718633b637295090d514dd777f9263477d514