diff --git a/ext/intck/sqlite3intck.c b/ext/intck/sqlite3intck.c index 05eaa81a93..6d54526928 100644 --- a/ext/intck/sqlite3intck.c +++ b/ext/intck/sqlite3intck.c @@ -16,12 +16,25 @@ #include #include +/* +** apKeyVal: +** If sqlite3_intck_suspend() is called when there is a running pCheck +** statement, this array is allocated and populated with the key values +** required to restart the check. If the intck object has not been +** suspended, this is set to NULL. +** +** nKeyVal: +** The size of the apKeyVal[] array, if it is allocated. +*/ struct sqlite3_intck { sqlite3 *db; const char *zDb; /* Copy of zDb parameter to _open() */ char *zObj; /* Current object. Or NULL. */ - char *zKey; /* Key saved by _intck_suspend() call. */ + sqlite3_stmt *pCheck; /* Current check statement */ + int nKeyVal; + sqlite3_value **apKeyVal; + int rc; /* Error code */ char *zErr; /* Error message */ char *zTestSql; /* Returned by sqlite3_intck_test_sql() */ @@ -82,20 +95,119 @@ static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){ } } -static char *intckStrdup(sqlite3_intck *p, const char *zIn){ - char *zOut = 0; +/* +** Wrapper around sqlite3_malloc64() that uses the sqlite3_intck error +** code convention. +*/ +static void *intckMalloc(sqlite3_intck *p, sqlite3_int64 nByte){ + void *pRet = 0; + assert( nByte>0 ); if( p->rc==SQLITE_OK ){ - int nIn = strlen(zIn); - zOut = sqlite3_malloc(nIn+1); - if( zOut==0 ){ + pRet = sqlite3_malloc64(nByte); + if( pRet==0 ){ p->rc = SQLITE_NOMEM; - }else{ - memcpy(zOut, zIn, nIn+1); } } + return pRet; +} + +/* +** 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. +*/ +static char *intckStrdup(sqlite3_intck *p, const char *zIn){ + char *zOut = 0; + int nIn = strlen(zIn); + zOut = (char*)intckMalloc(p, nIn+1); + if( zOut ){ + memcpy(zOut, zIn, nIn+1); + } return zOut; } +/* +** 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. +*/ +static char *intckMprintf(sqlite3_intck *p, const char *zFmt, ...){ + va_list ap; + char *zRet = 0; + va_start(ap, zFmt); + zRet = sqlite3_vmprintf(zFmt, ap); + if( p->rc==SQLITE_OK ){ + if( zRet==0 ){ + p->rc = SQLITE_NOMEM; + } + }else{ + sqlite3_free(zRet); + zRet = 0; + } + return zRet; +} + +/* +** Free the sqlite3_intck.apKeyVal, if it is allocated and populated. +*/ +static void intckSavedKeyClear(sqlite3_intck *p){ + if( p->apKeyVal ){ + int ii; + for(ii=0; iinKeyVal; ii++){ + sqlite3_value_free(p->apKeyVal[ii]); + } + sqlite3_free(p->apKeyVal); + p->apKeyVal = 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 char *intckSavedKeyToText(sqlite3_intck *p){ + char *zRet = 0; + if( p->apKeyVal ){ + int ii; + const char *zSep = "SELECT '(' || "; + char *zSql = 0; + sqlite3_stmt *pStmt = 0; + + for(ii=0; iinKeyVal; ii++){ + zSql = intckMprintf(p, "%z%squote(?)", zSql, zSep); + zSep = " || ', ' || "; + } + zSql = intckMprintf(p, "%z || ')'", zSql); + + pStmt = intckPrepare(p, "%s", zSql); + if( p->rc==SQLITE_OK ){ + for(ii=0; iinKeyVal; ii++){ + sqlite3_bind_value(pStmt, ii+1, p->apKeyVal[ii]); + } + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + zRet = intckStrdup(p, (const char*)sqlite3_column_text(pStmt, 0)); + } + intckFinalize(p, pStmt); + } + sqlite3_free(zSql); + } + return zRet; +} + static void intckFindObject(sqlite3_intck *p){ sqlite3_stmt *pStmt = 0; char *zPrev = p->zObj; @@ -112,7 +224,7 @@ static void intckFindObject(sqlite3_intck *p){ "SELECT table_name FROM tables " "WHERE ?1 IS NULL OR table_name%s?1 " "ORDER BY 1" - , p->zDb, (p->zKey ? ">=" : ">") + , p->zDb, (p->apKeyVal ? ">=" : ">") ); if( p->rc==SQLITE_OK ){ @@ -125,8 +237,7 @@ 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) ){ - sqlite3_free(p->zKey); - p->zKey = 0; + intckSavedKeyClear(p); } sqlite3_free(zPrev); @@ -332,7 +443,8 @@ static void intckExec(sqlite3_intck *p, const char *zSql){ static char *intckCheckObjectSql( sqlite3_intck *p, const char *zObj, - const char *zPrev + const char *zPrev, + int *pnKeyVal /* OUT: Number of key-values for this scan */ ){ char *zRet = 0; sqlite3_stmt *pStmt = 0; @@ -377,7 +489,16 @@ static char *intckCheckObjectSql( ")" "" "" - ", tabpk(db, tab, idx, o_pk, i_pk, q_pk, eq_pk, ps_pk, pk_pk) AS (" + /* + ** For a PK declared as "PRIMARY KEY(a, b) ... WITHOUT ROWID", where + ** the intck_wrapper aliases of "a" and "b" are "c1" and "c2": + ** + ** o_pk: "o.c1, o.c2" + ** i_pk: "i.'a', i.'b'" + ** ... + ** n_pk: 2 + */ + ", tabpk(db, tab, idx, o_pk, i_pk, q_pk, eq_pk, ps_pk, pk_pk, n_pk) AS (" " WITH pkfields(f, a) AS (" " SELECT i.col_name, i.col_alias FROM idx_cols i WHERE i.idx_ispk" " )" @@ -390,7 +511,8 @@ static char *intckCheckObjectSql( " group_concat(format('\"%w\"', f), ', ')" " )," " group_concat('%s', ',')," - " group_concat('quote('||a||')', ', ') " + " group_concat('quote('||a||')', ', '), " + " count(*)" " FROM tabname t, pkfields" ")" "" @@ -467,11 +589,10 @@ static char *intckCheckObjectSql( " FROM tabname t, tabpk p, idx_cols i WHERE i.idx_name=t.idx" ")" "" - ", thiskey(k) AS (" - " SELECT format('format(''(%%s,%%s)'', %%s, %%s) AS thiskey', " - " group_concat('%%s', ','), p.ps_pk, " - " group_concat('quote('||i.col_alias||')',', '), p.pk_pk" - " ) FROM tabpk p, idx_cols i WHERE i.idx_name=p.idx" + ", thiskey(k, n) AS (" + " SELECT group_concat(i.col_alias, ', ') || ', ' || p.o_pk, " + " count(*) + p.n_pk " + " FROM tabpk p, idx_cols i WHERE i.idx_name=p.idx" ")" "" ", whereclause(w_c) AS (" @@ -483,15 +604,15 @@ static char *intckCheckObjectSql( " FROM tabpk, tabname, idx_cols i WHERE i.idx_name=tabpk.idx" ")" "" - ", main_select(m) AS (" + ", main_select(m, n) AS (" " SELECT format(" " 'WITH %%s\nSELECT %%s,\n%%s\nFROM intck_wrapper AS o%%s'," " ww.s, c, t.k, whereclause.w_c" - " )" + " ), t.n" " FROM case_statement, wrapper_with ww, thiskey t, whereclause" ")" - "SELECT m FROM main_select" + "SELECT m, n FROM main_select" , p->zDb, p->zDb, zObj, zObj , zPrev, zCommon ); @@ -549,9 +670,8 @@ static char *intckCheckObjectSql( ** ** format('(%d,%d)', _rowid_, n.ii) */ - ", thiskey(k) AS (" - " SELECT 'format(''(' || ps_pk || ',%%d)'', ' || pk_pk || ', n.ii)'" - " FROM tabpk" + ", thiskey(k, n) AS (" + " SELECT o_pk || ', n.ii', n_pk+1 FROM tabpk" ")" "" ", whereclause(w_c) AS (" @@ -562,17 +682,17 @@ static char *intckCheckObjectSql( " FROM tabpk, tabname" ")" "" - ", main_select(m) AS (" + ", main_select(m, n) AS (" " SELECT format(" " '%%s, %%s\nSELECT %%s,\n%%s AS thiskey\nFROM intck_wrapper AS o" ", intck_counter AS n%%s\nORDER BY %%s', " " w, ww.s, c, thiskey.k, whereclause.w_c, t.o_pk" - " )" + " ), thiskey.n" " FROM case_statement, tabpk t, counter_with, " " wrapper_with ww, thiskey, whereclause" ")" - "SELECT m FROM main_select", + "SELECT m, n FROM main_select", p->zDb, zObj, zPrev, zCommon ); } @@ -590,6 +710,9 @@ static char *intckCheckObjectSql( 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); @@ -599,11 +722,14 @@ static char *intckCheckObjectSql( } static void intckCheckObject(sqlite3_intck *p){ - char *zSql = intckCheckObjectSql(p, p->zObj, p->zKey); + char *zSql = 0; + char *zKey = 0; + zKey = intckSavedKeyToText(p); + zSql = intckCheckObjectSql(p, p->zObj, zKey, &p->nKeyVal); p->pCheck = intckPrepare(p, "%s", zSql); sqlite3_free(zSql); - sqlite3_free(p->zKey); - p->zKey = 0; + sqlite3_free(zKey); + intckSavedKeyClear(p); } int sqlite3_intck_open( @@ -644,7 +770,7 @@ int sqlite3_intck_close(sqlite3_intck *p){ ); } sqlite3_free(p->zObj); - sqlite3_free(p->zKey); + intckSavedKeyClear(p); sqlite3_free(p->zTestSql); sqlite3_free(p->zErr); sqlite3_free(p); @@ -668,12 +794,13 @@ int sqlite3_intck_step(sqlite3_intck *p){ if( p->rc==SQLITE_OK ){ assert( p->pCheck ); if( sqlite3_step(p->pCheck)==SQLITE_ROW ){ - /* Fine, whatever... */ + /* Normal case, do nothing. */ }else{ if( sqlite3_finalize(p->pCheck)!=SQLITE_OK ){ intckSaveErrmsg(p); } p->pCheck = 0; + p->nKeyVal = 0; } } } @@ -693,10 +820,27 @@ int sqlite3_intck_error(sqlite3_intck *p, const char **pzErr){ return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc); } + +static sqlite3_value *intckValueDup(sqlite3_intck *p, sqlite3_value *pIn){ + sqlite3_value *pRet = 0; + if( p->rc==SQLITE_OK ){ + pRet = sqlite3_value_dup(pIn); + if( pRet==0 ){ + p->rc = SQLITE_NOMEM; + } + } + return pRet; +} + int sqlite3_intck_suspend(sqlite3_intck *p){ if( p->pCheck && p->rc==SQLITE_OK ){ - assert( p->zKey==0 ); - p->zKey = intckStrdup(p, (const char*)sqlite3_column_text(p->pCheck, 1)); + const int nByte = sizeof(sqlite3_value*) * p->nKeyVal; + int ii; + assert( p->apKeyVal==0 && p->nKeyVal>0 ); + p->apKeyVal = (sqlite3_value**)intckMalloc(p, nByte); + for(ii=0; p->rc==SQLITE_OK && iinKeyVal; ii++){ + p->apKeyVal[ii] = intckValueDup(p, sqlite3_column_value(p->pCheck, ii+1)); + } intckFinalize(p, p->pCheck); p->pCheck = 0; } @@ -706,10 +850,12 @@ int sqlite3_intck_suspend(sqlite3_intck *p){ const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){ sqlite3_free(p->zTestSql); if( zObj ){ - p->zTestSql = intckCheckObjectSql(p, zObj, 0); + p->zTestSql = intckCheckObjectSql(p, zObj, 0, 0); }else{ if( p->zObj ){ - p->zTestSql = intckCheckObjectSql(p, p->zObj, p->zKey); + char *zKey = intckSavedKeyToText(p); + p->zTestSql = intckCheckObjectSql(p, p->zObj, zKey, 0); + sqlite3_free(zKey); }else{ sqlite3_free(p->zTestSql); p->zTestSql = 0; diff --git a/manifest b/manifest index e254a597a2..39fc17cfc5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Consider\susing\s"="\sand\sIS\soperators\swith\seven\slow-quality\sindexes\sin\scases\swhere\sthey\sare\sselected\sexplicitly\susing\san\sINDEXED\sBY\sclause. -D 2024-02-20T16:04:27.694 +C Use\sfewer\scycles\sto\sgenerate\sthe\s"next\skey"\svalue\sused\sby\ssqlite3_intck_suspend()\sfunction\sin\sthe\sintck\sextension. +D 2024-02-20T18:17:06.096 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -251,7 +251,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/intck/intck1.test 5b3c9800e119b4dd50a381974f34cee6cfd5b7434286fb8da83b7c8ff1d6bb3c F ext/intck/intck2.test b65d7f627342f767e1d2c447d25619666cec36f516786dd56568bd741e5d7e67 F ext/intck/intck_common.tcl 2895854e7aaf5e199a15f6f82538a00999fd8fc55553bc1f04619af7aa86c0d0 -F ext/intck/sqlite3intck.c 5f319b7c72b0c01cfa28bb5fdd19be5781eb11f5a5216af2a0870dc7e001414d +F ext/intck/sqlite3intck.c 7a795f23424a29f656f3d4c7b83d23484746b57cdc25d3fb98ec805d017fc935 F ext/intck/sqlite3intck.h d9501ea480b7c41c0555f39f4f1b7c3e8d54fc1ea6d115de5e1211e0bc11d3e7 F ext/intck/test_intck.c 06206b35f1428961015c060dd35201246c849625cfdff461e0eeaaf76bda545c F ext/jni/GNUmakefile 59eb05f2a363bdfac8d15d66bed624bfe1ff289229184f3861b95f98a19cf4b2 @@ -2168,8 +2168,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 c01e008c28895e50b14531b2a1f3f1110aab3b54df41ebdbd416fbac7b1bba94 -R 8db173421c176ccb487011bd9440e123 +P 43cbbea82132db2d0ddb4f34cc2b6910b3a1243ae6d4e837b1b27bfe91b84834 +R 9dcf98c64d0302d62dce4c3c1b529641 U dan -Z d4e70ffeb295fbc1a103b9adca306ecc +Z c2dbf2f2090bfeaba66446a197f3ad62 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4565b3d528..69d33b6938 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -43cbbea82132db2d0ddb4f34cc2b6910b3a1243ae6d4e837b1b27bfe91b84834 \ No newline at end of file +95f01426f948cf435d0b400dc7ae06fa699eee32cff498fe77e74a1257a4e09b \ No newline at end of file