sqlite3 *db;
const char *zDb; /* Copy of zDb parameter to _open() */
- sqlite3_stmt *pListTables;
+ char *zObj; /* Current object. Or NULL. */
+ char *zKey; /* Key saved by _suspect() call. */
sqlite3_stmt *pCheck;
- int nCheck;
int rc; /* SQLite error code */
char *zErr; /* Error message */
p->rc = sqlite3_prepare_v2(p->db, zSql, -1, &pRet, 0);
fflush(stdout);
if( p->rc!=SQLITE_OK ){
+#if 1
printf("ERROR: %s\n", zSql);
printf("MSG: %s\n", sqlite3_errmsg(p->db));
+#endif
if( sqlite3_error_offset(p->db)>=0 ){
+#if 1
int iOff = sqlite3_error_offset(p->db);
printf("AT: %.40s\n", &zSql[iOff]);
+#endif
}
fflush(stdout);
intckSaveErrmsg(p);
}
}
-/*
-** Return an SQL statement that will itself return a single row for each
-** table in the target schema. The row contains two columns:
-**
-** 0: table_name - name of table
-** 1: without_rowid - true for WITHOUT ROWID tables, false otherwise.
-**
-*/
-static sqlite3_stmt *intckListTables(sqlite3_intck *p){
- return intckPrepare(p,
- "WITH tables(table_name) AS ("
- " SELECT name"
- " FROM %Q.sqlite_schema WHERE type='table' OR type='index'"
- " UNION ALL "
- " SELECT 'sqlite_schema'"
- ")"
- "SELECT * FROM tables"
- , p->zDb
- );
-}
-
static char *intckStrdup(sqlite3_intck *p, const char *zIn){
char *zOut = 0;
if( p->rc==SQLITE_OK ){
return zOut;
}
+static void intckFindObject(sqlite3_intck *p){
+ sqlite3_stmt *pStmt = 0;
+ char *zPrev = p->zObj;
+ p->zObj = 0;
+
+ assert( p->rc==SQLITE_OK );
+ pStmt = intckPrepare(p,
+ "WITH tables(table_name) AS ("
+ " SELECT name"
+ " FROM %Q.sqlite_schema WHERE type='table' OR type='index'"
+ " UNION ALL "
+ " SELECT 'sqlite_schema'"
+ ")"
+ "SELECT table_name FROM tables "
+ "WHERE ?1 IS NULL OR table_name%s?1 "
+ "ORDER BY 1"
+ , p->zDb, (p->zKey ? ">=" : ">")
+ );
+
+ if( p->rc==SQLITE_OK ){
+ sqlite3_bind_text(pStmt, 1, zPrev, -1, SQLITE_TRANSIENT);
+ if( sqlite3_step(pStmt)==SQLITE_ROW ){
+ p->zObj = intckStrdup(p, (const char*)sqlite3_column_text(pStmt, 0));
+ }
+ }
+ intckFinalize(p, pStmt);
+
+ /* 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;
+ }
+
+ sqlite3_free(zPrev);
+}
+
/*
** Return the size in bytes of the first token in nul-terminated buffer z.
** For the purposes of this call, a token is either:
intckFinalize(p, pStmt);
}
-static char *intckCheckObjectSql(sqlite3_intck *p, const char *zObj){
+static char *intckCheckObjectSql(
+ sqlite3_intck *p,
+ const char *zObj,
+ const char *zPrev
+){
char *zRet = 0;
sqlite3_stmt *pStmt = 0;
int bAutoIndex = 0;
/* 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. */
- "WITH tabname(db, tab, idx) AS ("
- " SELECT %Q, (SELECT tbl_name FROM %Q.sqlite_schema WHERE name=%Q), %Q "
+ "WITH tabname(db, tab, idx, prev) AS ("
+ " SELECT %Q, (SELECT tbl_name FROM %Q.sqlite_schema WHERE name=%Q), "
+ " %Q, %Q "
")"
"%s" /* zCommon */
""
", case_statement(c) AS ("
" SELECT "
- " 'CASE WHEN (' || group_concat(col_alias, ', ') || ') IS (\n ' "
+ " 'CASE WHEN (' || group_concat(col_alias, ', ') || ') IS (\n ' "
" || 'SELECT ' || group_concat(col_expr, ', ') || ' FROM '"
- " || format('%%Q.%%Q NOT INDEXED WHERE %%s\n)', t.db, t.tab, p.eq_pk)"
- " || '\nTHEN NULL\n'"
+ " || format('%%Q.%%Q NOT INDEXED WHERE %%s\n', t.db, t.tab, p.eq_pk)"
+ " || ' )\n THEN NULL\n '"
" || 'ELSE format(''surplus entry ('"
" || group_concat('%%s', ',') || ',' || p.ps_pk"
" || ') in index ' || t.idx || ''', ' "
" || group_concat('quote('||i.col_alias||')', ', ') || ', ' || p.pk_pk"
" || ')'"
- " || '\nEND'"
+ " || '\nEND AS error_message'"
" 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"
+ ")"
+ ""
+ ", whereclause(w_c) AS ("
+ " SELECT CASE WHEN prev!='' THEN "
+ " '\nWHERE (' || group_concat(i.col_alias, ',') || ',' "
+ " || o_pk || ') > ' || prev"
+ " ELSE ''"
+ " END"
+ " FROM tabpk, tabname, idx_cols i WHERE i.idx_name=tabpk.idx"
")"
""
", main_select(m) AS ("
" SELECT format("
- " 'WITH %%s\nSELECT %%s\nFROM intck_wrapper AS o',"
- " ww.s, c"
+ " 'WITH %%s\nSELECT %%s,\n%%s\nFROM intck_wrapper AS o%%s',"
+ " ww.s, c, t.k, whereclause.w_c"
" )"
- " FROM case_statement, wrapper_with ww"
+ " FROM case_statement, wrapper_with ww, thiskey t, whereclause"
")"
"SELECT m FROM main_select"
, p->zDb, p->zDb, zObj, zObj
- , zCommon
+ , zPrev, zCommon
);
}else{
pStmt = intckPrepare(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. */
- "WITH tabname(db, tab, idx) AS (SELECT %Q, %Q, NULL)"
+ "WITH tabname(db, tab, idx, prev) AS (SELECT %Q, %Q, NULL, %Q)"
""
"%s" /* zCommon */
" '\nEND AS error_message'"
" FROM numbered"
")"
+ ""
+ /* This table contains a single row consisting of a single value -
+ ** the text of an SQL expression that may be used by the main SQL
+ ** statement to output an SQL literal that can be used to resume
+ ** the scan if it is suspended. e.g. for a rowid table, an expression
+ ** like:
+ **
+ ** format('(%d,%d)', _rowid_, n.ii)
+ */
+ ", thiskey(k) AS ("
+ " SELECT 'format(''(' || ps_pk || ',%%d)'', ' || pk_pk || ', n.ii)'"
+ " FROM tabpk"
+ ")"
+ ""
+ ", whereclause(w_c) AS ("
+ " SELECT CASE WHEN prev!='' THEN "
+ " '\nWHERE (' || o_pk ||', n.ii) > ' || prev"
+ " ELSE ''"
+ " END"
+ " FROM tabpk, tabname"
+ ")"
+ ""
", main_select(m) AS ("
" SELECT format("
- " '%%s, %%s\nSELECT %%s\nFROM intck_wrapper AS o"
- ", intck_counter AS n ORDER BY %%s', "
- " w, ww.s, c, t.o_pk"
+ " '%%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"
" )"
- " FROM case_statement, tabpk t, counter_with, wrapper_with ww"
+ " FROM case_statement, tabpk t, counter_with, "
+ " wrapper_with ww, thiskey, whereclause"
")"
"SELECT m FROM main_select",
- p->zDb, zObj, zCommon
+ p->zDb, zObj, zPrev, zCommon
);
}
}
static void intckCheckObject(sqlite3_intck *p){
- const char *zTab = (const char*)sqlite3_column_text(p->pListTables, 0);
- char *zSql = intckCheckObjectSql(p, zTab);
+ char *zSql = intckCheckObjectSql(p, p->zObj, p->zKey);
p->pCheck = intckPrepare(p, "%s", zSql);
sqlite3_free(zSql);
+ sqlite3_free(p->zKey);
+ p->zKey = 0;
}
int sqlite3_intck_open(
p->db, "parse_create_index", 1, SQLITE_UTF8, 0, 0, 0, 0
);
}
+ sqlite3_free(p->zObj);
+ sqlite3_free(p->zKey);
sqlite3_free(p->zTestSql);
sqlite3_free(p->zErr);
sqlite3_free(p);
int sqlite3_intck_step(sqlite3_intck *p){
if( p->rc==SQLITE_OK ){
- if( p->pListTables==0 ){
- p->pListTables = intckListTables(p);
- }
- assert( p->pListTables || p->rc!=SQLITE_OK );
-
- if( p->rc==SQLITE_OK && p->pCheck==0 ){
- if( sqlite3_step(p->pListTables)==SQLITE_ROW ){
- intckCheckObject(p);
- }else{
- int rc = sqlite3_finalize(p->pListTables);
- if( rc==SQLITE_OK ){
- p->rc = SQLITE_DONE;
+ if( p->pCheck==0 ){
+ intckFindObject(p);
+ if( p->rc==SQLITE_OK ){
+ if( p->zObj ){
+ intckCheckObject(p);
}else{
- intckSaveErrmsg(p);
+ p->rc = SQLITE_DONE;
}
- p->pListTables = 0;
}
}
if( p->rc==SQLITE_OK ){
+ assert( p->pCheck );
if( sqlite3_step(p->pCheck)==SQLITE_ROW ){
/* Fine, whatever... */
}else{
return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc);
}
-int sqlite3_intck_suspend(sqlite3_intck *pCk){
- return SQLITE_OK;
+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));
+ intckFinalize(p, p->pCheck);
+ p->pCheck = 0;
+ }
+ return p->rc;
}
const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){
sqlite3_free(p->zTestSql);
- p->zTestSql = intckCheckObjectSql(p, zObj);
+ if( zObj ){
+ p->zTestSql = intckCheckObjectSql(p, zObj, 0);
+ }else{
+ if( p->zObj ){
+ p->zTestSql = intckCheckObjectSql(p, p->zObj, p->zKey);
+ }else{
+ sqlite3_free(p->zTestSql);
+ p->zTestSql = 0;
+ }
+ }
return p->zTestSql;
}
-C Add\sstart\sof\sextension\sfor\sincremental\sintegrity-checks\sto\sext/intck/.
-D 2024-02-17T20:55:01.343
+C Add\simplementation\sof\ssqlite3_intck_suspend().
+D 2024-02-19T18:03:53.963
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
-F ext/intck/intck1.test 0fcf3696b59aff6c344553647d612921dd529600796ff7172c02679955cecdcf
-F ext/intck/intck2.test dd06719eca145b317ae380c81f04cd8a096a7cfdb71074cc6b6e7f195058b0d0
-F ext/intck/intck_common.tcl 1f2599d50033d21d5df89f5ed54cc29af472d86e3927e116db50c5ba94d903b9
-F ext/intck/sqlite3intck.c 14300998e91cd8788f483d97e53be9406f2c0be8af1867f399b80fef5e3721fb
+F ext/intck/intck1.test c831bc6ff67da3c5b6c9568640f87ad0442d4fc98ef97bc837133b94bcc645b3
+F ext/intck/intck2.test b65d7f627342f767e1d2c447d25619666cec36f516786dd56568bd741e5d7e67
+F ext/intck/intck_common.tcl 2895854e7aaf5e199a15f6f82538a00999fd8fc55553bc1f04619af7aa86c0d0
+F ext/intck/sqlite3intck.c 703ff16bc936192cff20d06b015d66279f4594e88371c00b18d17fec4f01ff5c
F ext/intck/sqlite3intck.h 342ee2e2c7636b4daf29fa195d0a3a658272b76b283d586fba50f6bc80fc143d
-F ext/intck/test_intck.c 3f9a950978842340df7492f0a4190022979f23ff904e90873a5e262adf30b78c
+F ext/intck/test_intck.c eb596269c4a690a9b8ee689b1e52ff6e3306013ec706e319d5b97af05a08f0b9
F ext/jni/GNUmakefile 59eb05f2a363bdfac8d15d66bed624bfe1ff289229184f3861b95f98a19cf4b2
F ext/jni/README.md d899789a9082a07b99bf30b1bbb6204ae57c060efcaa634536fa669323918f42
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 7fca1bc482fc2456d75392eb42f768fda72631c9070de46b8123b1126e78306f
-R f8dccfe308ed2507020014beca90ae47
-T *branch * incr-integrity-check
-T *sym-incr-integrity-check *
-T -sym-trunk *
+P 444e3c9210026da7eae1ed98850722e002433aa2cc77dbc6b6f80327a6b7a390
+R 6b69910b5d388b58f98078e173b37781
U dan
-Z 8b8b6eda2ec7249d41ba58db982258fb
+Z 3045521be4122627d743cbd6c4ab9b9c
# Remove this line to create a well-formed Fossil manifest.