]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add start of extension for incremental integrity-checks to ext/intck/.
authordan <Dan Kennedy>
Sat, 17 Feb 2024 20:55:01 +0000 (20:55 +0000)
committerdan <Dan Kennedy>
Sat, 17 Feb 2024 20:55:01 +0000 (20:55 +0000)
FossilOrigin-Name: 444e3c9210026da7eae1ed98850722e002433aa2cc77dbc6b6f80327a6b7a390

12 files changed:
Makefile.in
Makefile.msc
ext/intck/intck1.test [new file with mode: 0644]
ext/intck/intck2.test [new file with mode: 0644]
ext/intck/intck_common.tcl [new file with mode: 0644]
ext/intck/sqlite3intck.c [new file with mode: 0644]
ext/intck/sqlite3intck.h [new file with mode: 0644]
ext/intck/test_intck.c [new file with mode: 0644]
main.mk
manifest
manifest.uuid
src/test_tclsh.c

index cb894666d981fb18484bda991e93f66f46853d8f..509ad4884ffad35f0b06918ea3daf036362c3a93 100644 (file)
@@ -418,6 +418,8 @@ TESTSRC = \
   $(TOP)/ext/recover/sqlite3recover.c \
   $(TOP)/ext/recover/dbdata.c \
   $(TOP)/ext/recover/test_recover.c \
+  $(TOP)/ext/intck/test_intck.c  \
+  $(TOP)/ext/intck/sqlite3intck.c \
   $(TOP)/ext/rbu/test_rbu.c
 
 # Statically linked extensions
index 19bfe2f3866b371045e5c6ea07f5e0dcb7ac1c50..7d9dbd2c2b2fdfdb85a78581ecf2b6acd64eb381 100644 (file)
@@ -1595,6 +1595,8 @@ TESTEXT = \
   $(TOP)\ext\rtree\test_rtreedoc.c \
   $(TOP)\ext\recover\sqlite3recover.c \
   $(TOP)\ext\recover\test_recover.c \
+  $(TOP)\ext\intck\test_intck.c  \
+  $(TOP)\ext\intck\sqlite3intck.c \
   $(TOP)\ext\recover\dbdata.c 
 
 # If use of zlib is enabled, add the "zipfile.c" source file.
diff --git a/ext/intck/intck1.test b/ext/intck/intck1.test
new file mode 100644 (file)
index 0000000..c46a7d6
--- /dev/null
@@ -0,0 +1,192 @@
+# 2008 Feb 19
+#
+# 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the r-tree extension.
+#
+
+source [file join [file dirname [info script]] intck_common.tcl]
+set testprefix intck1
+
+foreach {tn sql} {
+  1 "CREATE TABLE t1(a PRIMARY KEY, b)"
+  2 "CREATE TABLE t2(a PRIMARY KEY, b) WITHOUT   ROWID  "
+  3 "CREATE TABLE t3(a PRIMARY KEY, b) WITHOUT   rowID;"
+  4 "CREATE TABLE t4(a PRIMARY KEY, ROWID)"
+  5 {CREATE TABLE t5(a PRIMARY KEY, ROWID) WITHOUT ROWID
+    }
+} {
+  do_test 1.1.$tn {
+    db eval $sql
+    set {} {}
+  } {}
+}
+
+set space " \n\v\t\r\f"
+
+do_execsql_test 1.2 {
+  SELECT name, (rtrim(sql, $space) LIKE '%rowid') 
+  FROM sqlite_schema WHERE type='table'
+  ORDER BY 1
+} {
+  t1 0
+  t2 1
+  t3 1
+  t4 0
+  t5 1
+}
+
+do_execsql_test 1.3 {
+  CREATE TABLE x1(a COLLATE nocase, b INTEGER, c BLOB);
+  INSERT INTO x1 VALUES('lEtTeRs', 1234, 1234);
+}
+do_execsql_test 1.3.1 {
+  WITH wrapper(c1, c2, c3) AS (
+    SELECT a, b, c FROM x1
+  )
+  SELECT * FROM wrapper WHERE c1='letters';
+} {lEtTeRs 1234 1234}
+do_execsql_test 1.3.2 {
+  WITH wrapper(c1, c2, c3) AS (
+    SELECT a, b, c FROM x1
+  )
+  SELECT * FROM wrapper WHERE c2='1234';
+} {lEtTeRs 1234 1234}
+do_execsql_test 1.3.2 {
+  WITH wrapper(c1, c2, c3) AS (
+    SELECT a, b, c FROM x1
+  )
+  SELECT * FROM wrapper WHERE c3='1234';
+} {}
+
+do_execsql_test 1.4 {
+  CREATE TABLE z1(a, b);
+  CREATE INDEX z1ab ON z1(a+b COLLATE nocase);
+}
+do_execsql_test 1.4.1 {
+  SELECT * FROM z1 INDEXED BY z1ab 
+}
+
+do_catchsql_test 1.5.1 {
+  CREATE INDEX z1b ON z1(b ASC NULLS LAST);
+} {1 {unsupported use of NULLS LAST}}
+do_catchsql_test 1.5.2 {
+  CREATE INDEX z1b ON z1(b DESC NULLS LAST);
+} {1 {unsupported use of NULLS LAST}}
+do_catchsql_test 1.5.3 {
+  CREATE INDEX z1b ON z1(b ASC NULLS FIRST);
+} {1 {unsupported use of NULLS FIRST}}
+do_catchsql_test 1.5.4 {
+  CREATE INDEX z1b ON z1(b DESC NULLS FIRST);
+} {1 {unsupported use of NULLS FIRST}}
+
+
+#-------------------------------------------------------------------------
+reset_db
+
+do_test 2.0 {
+  set ic [sqlite3_intck db main ""]
+  $ic close
+} {}
+
+do_execsql_test 2.1 {
+  CREATE TABLE t1(a, b);
+  INSERT INTO t1 VALUES(1, 2);
+  INSERT INTO t1 VALUES(3, 4);
+
+  CREATE INDEX i1 ON t1(a COLLATE nocase);
+  CREATE INDEX i2 ON t1(b, a);
+  CREATE INDEX i3 ON t1(b + a COLLATE nocase) WHERE a!=1;
+}
+
+do_intck_test 2.2 {
+}
+
+# Delete a row from each of the i1 and i2 indexes using the imposter
+# table interface.
+#
+do_test 2.3 {
+  db eval {SELECT name, rootpage FROM sqlite_schema} {
+    set R($name) $rootpage
+  }
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i1)
+  db eval { CREATE TABLE imp1(a PRIMARY KEY, rowid) WITHOUT ROWID; }
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(i2)
+  db eval { CREATE TABLE imp2(b, a, rowid, PRIMARY KEY(b, a)) WITHOUT ROWID; }
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
+
+  db eval {
+    DELETE FROM imp1 WHERE rowid=1;
+    DELETE FROM imp2 WHERE rowid=2;
+  }
+
+  db close
+  sqlite3 db test.db
+} {}
+
+do_intck_test 2.4 {
+  {entry (1,1) missing from index i1} 
+  {entry (4,3,2) missing from index i2}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 3.0 {
+  CREATE TABLE x1(a, b, c, PRIMARY KEY(c, b)) WITHOUT ROWID;
+  CREATE INDEX x1a ON x1(a COLLATE nocase);
+
+  INSERT INTO x1 VALUES(1, 2, 'three');
+  INSERT INTO x1 VALUES(4, 5, 'six');
+  INSERT INTO x1 VALUES(7, 8, 'nine');
+}
+
+do_intck_test 3.1 { }
+
+do_test 3.2 {
+  db eval {SELECT name, rootpage FROM sqlite_schema} {
+    set R($name) $rootpage
+  }
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $R(x1a)
+  db eval { CREATE TABLE imp1(c, b, a, PRIMARY KEY(c, b)) WITHOUT ROWID }
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
+
+  db eval {
+    DELETE FROM imp1 WHERE a=5;
+  }
+  execsql_pp {
+  }
+
+  db close
+  sqlite3 db test.db
+} {}
+
+puts "[intck_sql db x1a]"
+execsql_pp "EXPLAIN QUERY PLAN [intck_sql db x1a]"
+do_intck_test 3.3 { 
+  {entry (4,'six',5) missing from index x1a}
+}
+
+#explain_i [intck_sql db x1]
+#puts [intck_sql db x1]
+#puts [intck_sql db x1a]
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+  CREATE TABLE www(x, y, z);
+  CREATE INDEX w1 ON www( (x+1), z );
+  INSERT INTO www VALUES(1, 1, 1), (2, 2, 2);
+}
+
+do_intck_test 4.1 { }
+
+finish_test
+
+
diff --git a/ext/intck/intck2.test b/ext/intck/intck2.test
new file mode 100644 (file)
index 0000000..bc9aebe
--- /dev/null
@@ -0,0 +1,70 @@
+# 2008 Feb 19
+#
+# 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.
+#
+#***********************************************************************
+#
+# The focus of this file is testing the r-tree extension.
+#
+
+source [file join [file dirname [info script]] intck_common.tcl]
+set testprefix intck2
+
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
+  INSERT INTO t1 VALUES(1, 'one');
+  INSERT INTO t1 VALUES(2, 'two');
+  INSERT INTO t1 VALUES(3, 'three');
+  CREATE INDEX i1 ON t1(b);
+}
+
+proc imposter_edit {obj create sql} {
+  sqlite3 xdb test.db
+  set pgno [xdb one {SELECT rootpage FROM sqlite_schema WHERE name=$obj}]
+
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 1 $pgno
+  xdb eval $create
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER xdb main 0 0
+  xdb eval $sql
+  xdb close
+}
+
+imposter_edit i1 {
+  CREATE TABLE imp(b, a, PRIMARY KEY(b)) WITHOUT ROWID;
+} {
+  DELETE FROM imp WHERE b='two';
+  INSERT INTO imp(b, a) VALUES('four', 4);
+}
+
+do_intck_test 1.1 {
+  {entry ('two',2) missing from index i1}
+  {surplus entry ('four',4) in index i1}
+}
+
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 2.0 {
+  CREATE TABLE x1(a, b, "c d");
+  CREATE INDEX x1a ON x1(a COLLATE nocase DESC , b ASC);
+  CREATE INDEX x1b ON x1( a || b || ' "''" ' COLLATE binary ASC );
+  CREATE INDEX x1c ON x1( format('%s', a)ASC, format('%d', "c d" ) );
+  INSERT INTO x1 VALUES('one', 2, 3);
+  INSERT INTO x1 VALUES('One', 4, 5);
+  INSERT INTO x1 VALUES('ONE', 6, 7);
+  INSERT INTO x1 VALUES(NULL, NULL, NULL);
+}
+
+do_intck_test 2.1 {}
+puts [intck_sql db x1]
+
+
+
+finish_test
+
+
diff --git a/ext/intck/intck_common.tcl b/ext/intck/intck_common.tcl
new file mode 100644 (file)
index 0000000..0763d13
--- /dev/null
@@ -0,0 +1,49 @@
+# 2024 Feb 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.
+#
+#***********************************************************************
+#
+
+if {![info exists testdir]} {
+  set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+
+proc do_intck {db} {
+  set ic [sqlite3_intck $db main ""]
+
+  set ret [list]
+  while {"SQLITE_OK"==[$ic step]} {
+    set msg [$ic message]
+    if {$msg!=""} {
+      lappend ret $msg
+    }
+  }
+
+  set err [$ic error]
+  if {[lindex $err 0]!="SQLITE_OK"} {
+    error $err
+  }
+  $ic close
+
+  return $ret
+}
+
+proc intck_sql {db tbl} {
+  set ic [sqlite3_intck $db main ""]
+  set sql [$ic test_sql $tbl]
+  $ic close
+  return $sql
+}
+
+proc do_intck_test {tn expect} {
+  uplevel [list do_test $tn [list do_intck db] [list {*}$expect]]
+}
+
+
diff --git a/ext/intck/sqlite3intck.c b/ext/intck/sqlite3intck.c
new file mode 100644 (file)
index 0000000..7610d44
--- /dev/null
@@ -0,0 +1,647 @@
+/*
+** 2024-02-08
+**
+** 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.
+**
+*************************************************************************
+*/
+
+#include "sqlite3intck.h"
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+struct sqlite3_intck {
+  sqlite3 *db;
+  const char *zDb;                /* Copy of zDb parameter to _open() */
+
+  sqlite3_stmt *pListTables;
+
+  sqlite3_stmt *pCheck;
+  int nCheck;
+
+  int rc;                         /* SQLite error code */
+  char *zErr;                     /* Error message */
+
+  char *zTestSql;                 /* Returned by sqlite3_intck_test_sql() */
+};
+
+
+/*
+** Some error has occurred while using database p->db. Save the error message
+** and error code currently held by the database handle in p->rc and p->zErr.
+*/
+static void intckSaveErrmsg(sqlite3_intck *p){
+  const char *zDberr = sqlite3_errmsg(p->db);
+  p->rc = sqlite3_errcode(p->db);
+  if( zDberr ){
+    sqlite3_free(p->zErr);
+    p->zErr = sqlite3_mprintf("%s", zDberr);
+  }
+}
+
+static sqlite3_stmt *intckPrepare(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 ){
+      printf("ERROR: %s\n", zSql);
+      printf("MSG: %s\n", sqlite3_errmsg(p->db));
+      if( sqlite3_error_offset(p->db)>=0 ){
+        int iOff = sqlite3_error_offset(p->db);
+        printf("AT: %.40s\n", &zSql[iOff]);
+      }
+      fflush(stdout);
+        intckSaveErrmsg(p);
+        assert( pRet==0 );
+      }
+    }
+  }
+  sqlite3_free(zSql);
+  va_end(ap);
+  return pRet;
+}
+
+static void intckFinalize(sqlite3_intck *p, sqlite3_stmt *pStmt){
+  int rc = sqlite3_finalize(pStmt);
+  if( p->rc==SQLITE_OK && rc!=SQLITE_OK ){
+    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 ){
+    int nIn = strlen(zIn);
+    zOut = sqlite3_malloc(nIn+1);
+    if( zOut==0 ){
+      p->rc = SQLITE_NOMEM;
+    }else{
+      memcpy(zOut, zIn, nIn+1);
+    }
+  }
+  return zOut;
+}
+
+/*
+** Return the size in bytes of the first token in nul-terminated buffer z.
+** For the purposes of this call, a token is either:
+**
+**   *  a quoted SQL string,
+*    *  a contiguous series of ascii alphabet characters, or
+*    *  any other single byte.
+*/
+static int intckGetToken(const char *z){
+  char c = z[0];
+  int iRet = 1;
+  if( c=='\'' || c=='"' || c=='`' ){
+    while( 1 ){
+      if( z[iRet]==c ){
+        iRet++;
+        if( z[iRet+1]!=c ) break;
+      }
+      iRet++;
+    }
+  }
+  else if( c=='[' ){
+    while( z[iRet++]!=']' && z[iRet] );
+  }
+  else if( (c>='A' && c<='Z') || (c>='a' && c<='z') ){
+    while( (z[iRet]>='A' && z[iRet]<='Z') || (z[iRet]>='a' && z[iRet]<='z') ){
+      iRet++;
+    }
+  }
+
+  return iRet;
+}
+
+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 
+** clause (if iCol<0) or the iCol'th column of the index.
+**
+** If (iCol<0), the identified fragment does not include the "WHERE" keyword,
+** only the expression that follows it. If (iCol>=0) then the identified
+** fragment does not include any trailing sort-order keywords - "ASC" or 
+** "DESC".
+**
+** If the CREATE INDEX statement does not contain the requested field or
+** clause, NULL is returned and (*pnByte) is set to 0. Otherwise, a pointer to
+** the identified fragment is returned and output parameter (*pnByte) set
+** to its size in bytes.
+*/
+static const char *intckParseCreateIndex(const char *z, int iCol, int *pnByte){
+  int iOff = 0;
+  int iThisCol = 0;
+  int iStart = 0;
+  int nOpen = 0;
+
+  const char *zRet = 0;
+  int nRet = 0;
+
+  int iEndOfCol = 0;
+
+  /* Skip forward until the first "(" token */
+  while( z[iOff]!='(' ){
+    iOff += intckGetToken(&z[iOff]);
+    if( z[iOff]=='\0' ) return 0;
+  }
+  assert( z[iOff]=='(' );
+
+  nOpen = 1;
+  iOff++;
+  iStart = iOff;
+  while( z[iOff] ){
+    const char *zToken = &z[iOff];
+    int nToken = 0;
+
+    /* Check if this is the end of the current column - either a "," or ")"
+    ** when nOpen==1.  */
+    if( nOpen==1 ){
+      if( z[iOff]==',' || z[iOff]==')' ){
+        if( iCol==iThisCol ){
+          int iEnd = iEndOfCol ? iEndOfCol : iOff;
+          nRet = (iEnd - iStart);
+          zRet = &z[iStart];
+          break;
+        }
+        iStart = iOff+1;
+        while( intckIsSpace(z[iStart]) ) iStart++;
+        iThisCol++;
+      }
+      if( z[iOff]==')' ) break;
+    }
+    if( z[iOff]=='(' ) nOpen++;
+    if( z[iOff]==')' ) nOpen--;
+    nToken = intckGetToken(zToken);
+
+    if( intckTokenMatch(zToken, nToken, "ASC", "DESC") ){
+      iEndOfCol = iOff;
+    }else if( 0==intckIsSpace(zToken[0]) ){
+      iEndOfCol = 0;
+    }
+
+    iOff += nToken;
+  }
+
+  /* iStart is now the byte offset of 1 byte passed the final ')' in the
+  ** CREATE INDEX statement. Try to find a WHERE clause to return.  */
+  while( zRet==0 && z[iOff] ){
+    int n = intckGetToken(&z[iOff]);
+    if( n==5 && 0==sqlite3_strnicmp(&z[iOff], "where", 5) ){
+      zRet = &z[iOff+5];
+      nRet = strlen(zRet);
+    }
+    iOff += n;
+  }
+
+  /* Trim any whitespace from the start and end of the returned string. */
+  if( zRet ){
+    while( intckIsSpace(zRet[0]) ){
+      nRet--;
+      zRet++;
+    }
+    while( nRet>0 && intckIsSpace(zRet[nRet-1]) ) nRet--;
+  }
+
+  *pnByte = nRet;
+  return zRet;
+}
+
+static void parseCreateIndexFunc(
+  sqlite3_context *pCtx, 
+  int nVal, 
+  sqlite3_value **apVal
+){
+  const char *zSql = (const char*)sqlite3_value_text(apVal[0]);
+  int idx = sqlite3_value_int(apVal[1]);
+  const char *zRes = 0;
+  int nRes = 0;
+
+  assert( nVal==2 );
+  if( zSql ){
+    zRes = intckParseCreateIndex(zSql, idx, &nRes);
+  }
+  sqlite3_result_text(pCtx, zRes, nRes, SQLITE_TRANSIENT);
+}
+
+/*
+** Return true if sqlite3_intck.db has automatic indexes enabled, false
+** otherwise.
+*/
+static int intckGetAutoIndex(sqlite3_intck *p){
+  int bRet = 0;
+  sqlite3_stmt *pStmt = 0;
+  pStmt = intckPrepare(p, "PRAGMA automatic_index");
+  if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+    bRet = sqlite3_column_int(pStmt, 0);
+  }
+  intckFinalize(p, pStmt);
+  return bRet;
+}
+
+/*
+** Return true if zObj is an index, or false otherwise.
+*/
+static int intckIsIndex(sqlite3_intck *p, const char *zObj){
+  int bRet = 0;
+  sqlite3_stmt *pStmt = 0;
+  pStmt = intckPrepare(p, 
+      "SELECT 1 FROM %Q.sqlite_schema WHERE name=%Q AND type='index'",
+      p->zDb, zObj
+  );
+  if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+    bRet = 1;
+  }
+  intckFinalize(p, pStmt);
+  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);
+}
+
+static char *intckCheckObjectSql(sqlite3_intck *p, const char *zObj){
+  char *zRet = 0;
+  sqlite3_stmt *pStmt = 0;
+  int bAutoIndex = 0;
+  int bIsIndex = 0;
+
+  const char *zCommon = 
+      /* Relation without_rowid also contains just one row. Column "b" is
+      ** set to true if the table being examined is a WITHOUT ROWID table,
+      ** or false otherwise.  */
+      ", without_rowid(b) AS ("
+      "  SELECT EXISTS ("
+      "    SELECT 1 FROM tabname, pragma_index_list(tab, db) AS l"
+      "      WHERE origin='pk' "
+      "      AND NOT EXISTS (SELECT 1 FROM sqlite_schema WHERE name=l.name)"
+      "  )"
+      ")"
+      ""
+      /* Table idx_cols contains 1 row for each column in each index on the
+      ** table being checked. Columns are:
+      **
+      **   idx_name: Name of the index.
+      **   idx_ispk: True if this index is the PK of a WITHOUT ROWID table.
+      **   col_name: Name of indexed column, or NULL for index on expression.
+      **   col_expr: Indexed expression, including COLLATE clause.
+      **   col_alias: Alias used for column in 'intck_wrapper' table.
+      */
+      ", idx_cols(idx_name, idx_ispk, col_name, col_expr, col_alias) AS ("
+      "  SELECT l.name, (l.origin=='pk' AND w.b), i.name, COALESCE(("
+      "    SELECT parse_create_index(sql, i.seqno) FROM "
+      "    sqlite_schema WHERE name = l.name"
+      "  ), format('\"%w\"', i.name) || ' COLLATE ' || quote(i.coll)),"
+      "  'c' || row_number() OVER ()"
+      "  FROM "
+      "      tabname t,"
+      "      without_rowid w,"
+      "      pragma_index_list(t.tab, t.db) l,"
+      "      pragma_index_xinfo(l.name) i"
+      "      WHERE i.key"
+      "  UNION ALL"
+      "  SELECT '', 1, '_rowid_', '_rowid_', 'r1' FROM without_rowid WHERE b=0"
+      ")"
+      ""
+      ""
+      ", tabpk(db, tab, idx, o_pk, i_pk, q_pk, eq_pk, ps_pk, pk_pk) AS ("
+      "    WITH pkfields(f, a) AS ("
+      "      SELECT i.col_name, i.col_alias FROM idx_cols i WHERE i.idx_ispk"
+      "    )"
+      "    SELECT t.db, t.tab, t.idx, "
+      "           group_concat('o.'||a, ', '), "
+      "           group_concat('i.'||quote(f), ', '), "
+      "           group_concat('quote(o.'||a||')', ' || '','' || '),  "
+      "           format('(%s)==(%s)',"
+      "               group_concat('o.'||a, ', '), "
+      "               group_concat(format('\"%w\"', f), ', ')"
+      "           ),"
+      "           group_concat('%s', ','),"
+      "           group_concat('quote('||a||')', ', ')  "
+      "    FROM tabname t, pkfields"
+      ")"
+      ""
+      ", idx(name, match_expr, partial, partial_alias, idx_ps, idx_idx) AS ("
+      "  SELECT idx_name,"
+      "    format('(%s) IS (%s)', "
+      "           group_concat(i.col_expr, ', '),"
+      "           group_concat('o.'||i.col_alias, ', ')"
+      "    ), "
+      "    parse_create_index("
+      "        (SELECT sql FROM sqlite_schema WHERE name=idx_name), -1"
+      "    ),"
+      "    'cond' || row_number() OVER ()"
+      "    , group_concat('%s', ',')"
+      "    , group_concat('quote('||i.col_alias||')', ', ')"
+      "  FROM tabpk t, "
+      "       without_rowid w,"
+      "       idx_cols i"
+      "  WHERE i.idx_ispk==0 "
+      "  GROUP BY idx_name"
+      ")"
+      ""
+      ", wrapper_with(s) AS ("
+      "  SELECT 'intck_wrapper AS (\n  SELECT\n    ' || ("
+      "      WITH f(a, b) AS ("
+      "        SELECT col_expr, col_alias FROM idx_cols"
+      "          UNION ALL "
+      "        SELECT partial, partial_alias FROM idx WHERE partial IS NOT NULL"
+      "      )"
+      "      SELECT group_concat(format('%s AS %s', a, b), ',\n    ') FROM f"
+      "    )"
+      "    || format('\n  FROM %Q.%Q ', t.db, t.tab)"
+           /* If the object being checked is a table, append "NOT INDEXED".
+           ** Otherwise, append "INDEXED BY <index>", and then, if the index 
+           ** is a partial index " WHERE <condition>".  */
+      "    || CASE WHEN t.idx IS NULL THEN "
+      "        'NOT INDEXED'"
+      "       ELSE"
+      "        format('INDEXED BY %Q%s', t.idx, ' WHERE '||i.partial)"
+      "       END"
+      "    || '\n)'"
+      "    FROM tabname t LEFT JOIN idx i ON (i.name=t.idx)"
+      ")"
+      ""
+  ;
+
+  bAutoIndex = intckGetAutoIndex(p);
+  if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 0");
+
+  bIsIndex = intckIsIndex(p, zObj);
+  if( bIsIndex ){
+    pStmt = intckPrepare(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.  */
+      "WITH tabname(db, tab, idx) AS ("
+      "  SELECT %Q, (SELECT tbl_name FROM %Q.sqlite_schema WHERE name=%Q), %Q "
+      ")"
+      "%s" /* zCommon */
+      ""
+      ", case_statement(c) AS ("
+      "  SELECT "
+      "    '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'"
+      "    || 'ELSE format(''surplus entry ('"
+      "    ||   group_concat('%%s', ',') || ',' || p.ps_pk"
+      "    || ') in index ' || t.idx || ''', ' "
+      "    ||   group_concat('quote('||i.col_alias||')', ', ') || ', ' || p.pk_pk"
+      "    || ')'"
+      "    || '\nEND'"
+      "  FROM tabname t, tabpk p, idx_cols i WHERE i.idx_name=t.idx"
+      ""
+      ")"
+      ""
+      ", main_select(m) AS ("
+      "  SELECT format("
+      "      'WITH %%s\nSELECT %%s\nFROM intck_wrapper AS o',"
+      "      ww.s, c"
+      "  )"
+      "  FROM case_statement, wrapper_with ww"
+      ")"
+
+      "SELECT m FROM main_select"
+      , p->zDb, p->zDb, zObj, zObj
+      , 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)"
+      ""
+      "%s" /* zCommon */
+
+      /* expr(e) contains one row for each index on table zObj. Value e
+      ** is set to an expression that evaluates to NULL if the required
+      ** entry is present in the index, or an error message otherwise.  */
+      ", expr(e, p) AS ("
+      "  SELECT format('CASE WHEN (%%s) IN\n"
+      "    (SELECT %%s FROM %%Q.%%Q AS i INDEXED BY %%Q WHERE %%s%%s)\n"
+      "    THEN NULL\n"
+      "    ELSE format(''entry (%%s,%%s) missing from index %%s'', %%s, %%s)\n"
+      "  END\n'"
+      "    , t.o_pk, t.i_pk, t.db, t.tab, i.name, i.match_expr, "
+      "    ' AND (' || partial || ')',"
+      "      i.idx_ps, t.ps_pk, i.name, i.idx_idx, t.pk_pk),"
+      "    CASE WHEN partial IS NULL THEN NULL ELSE i.partial_alias END"
+      "  FROM tabpk t, idx i"
+      ")"
+
+      ", numbered(ii, cond, e) AS ("
+      "  SELECT 0, 'n.ii=0', 'NULL'"
+      "    UNION ALL "
+      "  SELECT row_number() OVER (),"
+      "      '(n.ii='||row_number() OVER ()||COALESCE(' AND '||p||')', ')'), e"
+      "  FROM expr"
+      ")"
+
+      ", counter_with(w) AS ("
+      "    SELECT 'WITH intck_counter(ii) AS (\n  ' || "
+      "       group_concat('SELECT '||ii, ' UNION ALL\n  ') "
+      "    || '\n)' FROM numbered"
+      ")"
+      ""
+      ", case_statement(c) AS ("
+      "    SELECT 'CASE ' || "
+      "    group_concat(format('\n  WHEN %%s THEN (%%s)', cond, e), '') ||"
+      "    '\nEND AS error_message'"
+      "    FROM numbered"
+      ")"
+
+      ", 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"
+      "  )"
+      "  FROM case_statement, tabpk t, counter_with, wrapper_with ww"
+      ")"
+
+      "SELECT m FROM main_select",
+      p->zDb, zObj, zCommon
+    );
+  }
+
+  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));
+#endif
+  }
+  intckFinalize(p, pStmt);
+
+  if( bAutoIndex ) intckExec(p, "PRAGMA automatic_index = 1");
+  return zRet;
+}
+
+static void intckCheckObject(sqlite3_intck *p){
+  const char *zTab = (const char*)sqlite3_column_text(p->pListTables, 0);
+  char *zSql = intckCheckObjectSql(p, zTab);
+  p->pCheck = intckPrepare(p, "%s", zSql);
+  sqlite3_free(zSql);
+}
+
+int sqlite3_intck_open(
+  sqlite3 *db,                    /* Database handle to operate on */
+  const char *zDbArg,             /* "main", "temp" etc. */
+  const char *zFile,              /* Path to save-state db on disk (or NULL) */
+  sqlite3_intck **ppOut           /* OUT: New integrity-check handle */
+){
+  sqlite3_intck *pNew = 0;
+  int rc = SQLITE_OK;
+  const char *zDb = zDbArg ? zDbArg : "main";
+  int nDb = strlen(zDb);
+
+  pNew = (sqlite3_intck*)sqlite3_malloc(sizeof(*pNew) + nDb + 1);
+  if( pNew==0 ){
+    rc = SQLITE_NOMEM;
+  }else{
+    memset(pNew, 0, sizeof(*pNew));
+    pNew->db = db;
+    pNew->zDb = (const char*)&pNew[1];
+    memcpy(&pNew[1], zDb, nDb+1);
+    sqlite3_create_function(db, "parse_create_index", 
+        2, SQLITE_UTF8, 0, parseCreateIndexFunc, 0, 0
+    );
+  }
+
+  *ppOut = pNew;
+  return rc;
+}
+
+void sqlite3_intck_close(sqlite3_intck *p){
+  if( p && p->db ){
+    sqlite3_create_function(
+        p->db, "parse_create_index", 1, SQLITE_UTF8, 0, 0, 0, 0
+    );
+  }
+  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;
+        }else{
+          intckSaveErrmsg(p);
+        }
+        p->pListTables = 0;
+      }
+    }
+
+    if( p->rc==SQLITE_OK ){
+      if( sqlite3_step(p->pCheck)==SQLITE_ROW ){
+        /* Fine, whatever... */
+      }else{
+        if( sqlite3_finalize(p->pCheck)!=SQLITE_OK ){
+          intckSaveErrmsg(p);
+        }
+        p->pCheck = 0;
+      }
+    }
+  }
+
+  return p->rc;
+}
+
+const char *sqlite3_intck_message(sqlite3_intck *p){
+  if( p->pCheck ){
+    return (const char*)sqlite3_column_text(p->pCheck, 0);
+  }
+  return 0;
+}
+
+int sqlite3_intck_error(sqlite3_intck *p, const char **pzErr){
+  *pzErr = p->zErr;
+  return (p->rc==SQLITE_DONE ? SQLITE_OK : p->rc);
+}
+
+int sqlite3_intck_suspend(sqlite3_intck *pCk){
+  return SQLITE_OK;
+}
+
+const char *sqlite3_intck_test_sql(sqlite3_intck *p, const char *zObj){
+  sqlite3_free(p->zTestSql);
+  p->zTestSql = intckCheckObjectSql(p, zObj);
+  return p->zTestSql;
+}
+
diff --git a/ext/intck/sqlite3intck.h b/ext/intck/sqlite3intck.h
new file mode 100644 (file)
index 0000000..8846812
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+** 2024-02-08
+**
+** 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.
+**
+*************************************************************************
+*/
+
+#ifndef _SQLITE_INTCK_H
+#define _SQLITE_INTCK_H
+
+#include "sqlite3.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct sqlite3_intck sqlite3_intck;
+
+int sqlite3_intck_open(
+  sqlite3 *db, 
+  const char *zDb, 
+  const char *zFile, 
+  sqlite3_intck **ppOut
+);
+
+void sqlite3_intck_close(sqlite3_intck*);
+
+int sqlite3_intck_step(sqlite3_intck *pCk);
+
+const char *sqlite3_intck_message(sqlite3_intck *pCk);
+
+int sqlite3_intck_error(sqlite3_intck *pCk, const char **pzErr);
+
+int sqlite3_intck_suspend(sqlite3_intck *pCk);
+
+/*
+** This API is used for testing only. It returns the full-text of an SQL
+** statement used to test object zObj, which may be a table or index.
+** The returned buffer is valid until the next call to either this function
+** or sqlite3_intck_close() on the same sqlite3_intck handle.
+*/
+const char *sqlite3_intck_test_sql(sqlite3_intck *pCk, const char *zObj);
+
+
+#ifdef __cplusplus
+}  /* end of the 'extern "C"' block */
+#endif
+
+#endif /* ifndef _SQLITE_INTCK_H */
diff --git a/ext/intck/test_intck.c b/ext/intck/test_intck.c
new file mode 100644 (file)
index 0000000..33129ec
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+** 2010 August 28
+**
+** 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.
+**
+*************************************************************************
+** Code for testing all sorts of SQLite interfaces. This code
+** is not included in the SQLite library. 
+*/
+
+#include "sqlite3.h"
+#include "sqlite3intck.h"
+
+#if defined(INCLUDE_SQLITE_TCL_H)
+#  include "sqlite_tcl.h"
+#else
+#  include "tcl.h"
+#endif
+
+#include <string.h>
+#include <assert.h>
+
+/* In test1.c */
+int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb);
+const char *sqlite3ErrName(int);
+
+typedef struct TestIntck TestIntck;
+struct TestIntck {
+  sqlite3_intck *intck;
+};
+
+static int testIntckCmd(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  struct Subcmd {
+    const char *zName;
+    int nArg;
+    const char *zExpect;
+  } aCmd[] = {
+    {"close", 0, ""},        /* 0 */
+    {"step", 0, ""},         /* 1 */
+    {"message", 0, ""},      /* 2 */
+    {"error", 0, ""},        /* 3 */
+    {"suspend", 0, ""},      /* 4 */
+    {"test_sql", 1, ""},     /* 5 */
+    {0 , 0}
+  };
+  int rc = TCL_OK;
+  int iIdx = -1;
+  TestIntck *p = (TestIntck*)clientData;
+
+  if( objc<2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND ...");
+    return TCL_ERROR;
+  }
+
+  rc = Tcl_GetIndexFromObjStruct(
+      interp, objv[1], aCmd, sizeof(aCmd[0]), "SUB-COMMAND", 0, &iIdx
+  );
+  if( rc ) return rc;
+
+  if( objc!=2+aCmd[iIdx].nArg ){
+    Tcl_WrongNumArgs(interp, 2, objv, aCmd[iIdx].zExpect);
+    return TCL_ERROR;
+  }
+
+  switch( iIdx ){
+    case 0: assert( 0==strcmp("close", aCmd[iIdx].zName) ); {
+      Tcl_DeleteCommand(interp, Tcl_GetStringFromObj(objv[0], 0));
+      break;
+    }
+
+    case 1: assert( 0==strcmp("step", aCmd[iIdx].zName) ); {
+      int rc = sqlite3_intck_step(p->intck);
+      Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+      break;
+    }
+
+    case 2: assert( 0==strcmp("message", aCmd[iIdx].zName) ); {
+      const char *z = sqlite3_intck_message(p->intck);
+      Tcl_SetObjResult(interp, Tcl_NewStringObj(z ? z : "", -1));
+      break;
+    }
+
+    case 3: assert( 0==strcmp("error", aCmd[iIdx].zName) ); {
+      const char *zErr = 0;
+      int rc = sqlite3_intck_error(p->intck, &zErr);
+      Tcl_Obj *pRes = Tcl_NewObj();
+
+      Tcl_ListObjAppendElement(
+          interp, pRes, Tcl_NewStringObj(sqlite3ErrName(rc), -1)
+      );
+      Tcl_ListObjAppendElement(
+          interp, pRes, Tcl_NewStringObj(zErr ? zErr : 0, -1)
+      );
+      Tcl_SetObjResult(interp, pRes);
+      break;
+    }
+
+    case 4: assert( 0==strcmp("suspend", aCmd[iIdx].zName) ); {
+      int rc = sqlite3_intck_suspend(p->intck);
+      Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+      break;
+    }
+
+    case 5: assert( 0==strcmp("test_sql", aCmd[iIdx].zName) ); {
+      const char *zObj = Tcl_GetString(objv[2]);
+      const char *zSql = sqlite3_intck_test_sql(p->intck, zObj);
+      Tcl_SetObjResult(interp, Tcl_NewStringObj(zSql, -1));
+      break;
+    }
+  }
+
+  return TCL_OK;
+}
+
+/*
+** Destructor for commands created by test_sqlite3_intck().
+*/
+static void testIntckFree(void *clientData){
+  TestIntck *p = (TestIntck*)clientData;
+  sqlite3_intck_close(p->intck);
+  ckfree(p);
+}
+
+/*
+** tclcmd: sqlite3_intck DB DBNAME PATH
+*/
+static int test_sqlite3_intck(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  char zName[64];
+  int iName = 0;
+  Tcl_CmdInfo info;
+  TestIntck *p = 0;
+  sqlite3 *db = 0;
+  const char *zDb = 0;
+  const char *zFile = 0;
+  int rc = SQLITE_OK;
+
+  if( objc!=4 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME PATH");
+    return TCL_ERROR;
+  }
+
+  p = (TestIntck*)ckalloc(sizeof(TestIntck));
+  memset(p, 0, sizeof(TestIntck));
+
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+    return TCL_ERROR;
+  }
+  zDb = Tcl_GetString(objv[2]);
+  zFile = Tcl_GetString(objv[3]);
+
+  rc = sqlite3_intck_open(db, zDb, zFile, &p->intck);
+  if( rc!=SQLITE_OK ){
+    ckfree(p);
+    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_errstr(rc), -1));
+    return TCL_ERROR;
+  }
+
+  do {
+    sprintf(zName, "intck%d", iName);
+  }while( Tcl_GetCommandInfo(interp, zName, &info)!=0 );
+  Tcl_CreateObjCommand(interp, zName, testIntckCmd, (void*)p, testIntckFree);
+  Tcl_SetObjResult(interp, Tcl_NewStringObj(zName, -1));
+
+  return TCL_OK;
+}
+
+int Sqlitetestintck_Init(Tcl_Interp *interp){
+  Tcl_CreateObjCommand(interp, "sqlite3_intck", test_sqlite3_intck, 0, 0);
+  return TCL_OK;
+}
diff --git a/main.mk b/main.mk
index 081e0cd3b5b4e83d37a8aab0ec6bd3f318223dab..0a0af725d0158cee064f751f15ffb7ed66dd91de 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -375,7 +375,9 @@ TESTSRC += \
   $(TOP)/ext/rtree/test_rtreedoc.c \
   $(TOP)/ext/recover/sqlite3recover.c \
   $(TOP)/ext/recover/dbdata.c \
-  $(TOP)/ext/recover/test_recover.c
+  $(TOP)/ext/recover/test_recover.c \
+  $(TOP)/ext/intck/test_intck.c  \
+  $(TOP)/ext/intck/sqlite3intck.c 
 
 
 #TESTSRC += $(TOP)/ext/fts3/fts3_tokenizer.c
index ab0f22bde5de1224c66615c1d909b2e0de7dcdc9..a378ab14eb7144eb3d775aad15bb81559e979c1c 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,11 +1,11 @@
-C Fix\srounding\sin\szero-precision\s%f\sand\s%g\sprintf\sconversions.\n[forum:/info/393708f4a8|Forum\spost\s393708f4a8].\s\sThis\sbug\swas\nintroduced\sby\scheck-in\s[32befb224b254639]\sand\sfirst\sappeared\sin\sversion\s3.43.0.
-D 2024-02-17T03:32:31.878
+C Add\sstart\sof\sextension\sfor\sincremental\sintegrity-checks\sto\sext/intck/.
+D 2024-02-17T20:55:01.343
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in 24be65ae641c5727bbc247d60286a15ecc24fb80f14f8fb2d32533bf0ec96e79
+F Makefile.in 216eea0cc5a9613d9f4f21402a4b759c2fce2a0cb9567513933562b65e30670b
 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6
-F Makefile.msc df56c06ef05add5ebcdcc9d4823fb2ec66ac16b06acb6971a7420859025886fa
+F Makefile.msc a496ca640052c1e102daaa6e2d2216ae482f22995498c7c9492fd7f841481400
 F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3
 F VERSION c84541c6a9e8426462176fbb1f9ecb5cfd7d1bb56228053ff7eeba8841673eb6
 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@@ -248,6 +248,12 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
 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/sqlite3intck.h 342ee2e2c7636b4daf29fa195d0a3a658272b76b283d586fba50f6bc80fc143d
+F ext/intck/test_intck.c 3f9a950978842340df7492f0a4190022979f23ff904e90873a5e262adf30b78c
 F ext/jni/GNUmakefile 59eb05f2a363bdfac8d15d66bed624bfe1ff289229184f3861b95f98a19cf4b2
 F ext/jni/README.md d899789a9082a07b99bf30b1bbb6204ae57c060efcaa634536fa669323918f42
 F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
@@ -659,7 +665,7 @@ F ext/wasm/wasmfs.make 8a4955882aaa0783b3f60a9484a1f0f3d8b6f775c0fcd17c082f31966
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
-F main.mk ef8d6b0e76b27d2b32d7b71ea30a2b2626b668f998a4f32f6171c9623a310a53
+F main.mk 678f023e03c5dca755570ed964a8355e44a6435f679e3763a6f9fe3d309f9986
 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
 F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421
@@ -790,7 +796,7 @@ F src/test_schema.c cbfd7a9a9b6b40d4377d0c76a6c5b2a58387385977f26edab4e77eb5f90a
 F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
 F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
 F src/test_syscall.c 9fdb13b1df05e639808d44fcb8f6064aaded32b6565c00b215cfd05a060d1aca
-F src/test_tclsh.c 3ff5d188a72f00807425954ea3b493dfd3a4b890ecc6700ea83bad2fd1332ecf
+F src/test_tclsh.c aaf0d1de4a518a8db5ad38e5262be3e48b4a74ad1909f2dba753cecb30979d5d
 F src/test_tclvar.c 3273f9d59395b336e381b53cfc68ec6ebdaada4e93106a2e976ffb0550504e1c
 F src/test_thread.c 7ddcf0c8b79fa3c1d172f82f322302c963d923cdb503c6171f3c8081586d0b01
 F src/test_vdbecov.c f60c6f135ec42c0de013a1d5136777aa328a776d33277f92abac648930453d43
@@ -2162,8 +2168,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 1c33c5db2e05019d1a375109f79ad8588a3c17f81e4f4b8d66c880c3c860e87e
-R e3e534a124d08ab0760f858683268942
-U drh
-Z 25ef9b1be0189ee473aa53bd8732a56c
+P 7fca1bc482fc2456d75392eb42f768fda72631c9070de46b8123b1126e78306f
+R f8dccfe308ed2507020014beca90ae47
+T *branch * incr-integrity-check
+T *sym-incr-integrity-check *
+T -sym-trunk *
+U dan
+Z 8b8b6eda2ec7249d41ba58db982258fb
 # Remove this line to create a well-formed Fossil manifest.
index 1e28ec46dd347b4034622371899917864a71b9f8..7ea8d90d425f491ae9fdccb2e9ee57080d207457 100644 (file)
@@ -1 +1 @@
-7fca1bc482fc2456d75392eb42f768fda72631c9070de46b8123b1126e78306f
\ No newline at end of file
+444e3c9210026da7eae1ed98850722e002433aa2cc77dbc6b6f80327a6b7a390
\ No newline at end of file
index 32aee426753b76bc8e393a11b762c07ab4572cd9..4697c3b856e34728d53919b69f970a9bdc473ce8 100644 (file)
@@ -108,6 +108,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){
   extern int Sqlitetest_window_Init(Tcl_Interp *);
   extern int Sqlitetestvdbecov_Init(Tcl_Interp *);
   extern int TestRecover_Init(Tcl_Interp*);
+  extern int Sqlitetestintck_Init(Tcl_Interp*);
 
   Tcl_CmdInfo cmdInfo;
 
@@ -175,6 +176,7 @@ const char *sqlite3TestInit(Tcl_Interp *interp){
   Sqlitetest_window_Init(interp);
   Sqlitetestvdbecov_Init(interp);
   TestRecover_Init(interp);
+  Sqlitetestintck_Init(interp);
 
   Tcl_CreateObjCommand(
       interp, "load_testfixture_extensions", load_testfixture_extensions,0,0