]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the start of the "incremental_index_check" virtual table in
authordan <dan@noemail.net>
Fri, 27 Oct 2017 20:53:16 +0000 (20:53 +0000)
committerdan <dan@noemail.net>
Fri, 27 Oct 2017 20:53:16 +0000 (20:53 +0000)
ext/repair/checkindex.c. For incremental verification of index contents.

FossilOrigin-Name: d5b9dada471358a2864727759648b763bf6890fc2521fac53c0d8216017d39b7

ext/repair/checkindex.c [new file with mode: 0644]
manifest
manifest.uuid
test/checkindex.test [new file with mode: 0644]

diff --git a/ext/repair/checkindex.c b/ext/repair/checkindex.c
new file mode 100644 (file)
index 0000000..a74d1d4
--- /dev/null
@@ -0,0 +1,442 @@
+/*
+** 2017 October 27
+**
+** 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 "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+
+#ifndef SQLITE_AMALGAMATION
+# include <string.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <assert.h>
+# define ALWAYS(X)  1
+# define NEVER(X)   0
+  typedef unsigned char u8;
+  typedef unsigned short u16;
+  typedef unsigned int u32;
+#define get4byte(x) (        \
+    ((u32)((x)[0])<<24) +    \
+    ((u32)((x)[1])<<16) +    \
+    ((u32)((x)[2])<<8) +     \
+    ((u32)((x)[3]))          \
+)
+#endif
+
+typedef struct CidxTable CidxTable;
+typedef struct CidxCursor CidxCursor;
+
+struct CidxTable {
+  sqlite3_vtab base;              /* Base class.  Must be first */
+  sqlite3 *db;
+};
+
+struct CidxCursor {
+  sqlite3_vtab_cursor base;       /* Base class.  Must be first */
+  sqlite3_stmt *pStmt;
+};
+
+static void *cidxMalloc(int *pRc, int n){
+  void *pRet = 0;
+  assert( n!=0 );
+  if( *pRc==SQLITE_OK ){
+    pRet = sqlite3_malloc(n);
+    if( pRet ){
+      memset(pRet, 0, n);
+    }else{
+      *pRc = SQLITE_NOMEM;
+    }
+  }
+  return pRet;
+}
+
+static void cidxCursorError(CidxCursor *pCsr, const char *zFmt, ...){
+  va_list ap;
+  va_start(ap, zFmt);
+  assert( pCsr->base.pVtab->zErrMsg==0 );
+  pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
+  va_end(ap);
+}
+
+/*
+** Connect to then incremental_index_check virtual table.
+*/
+static int cidxConnect(
+  sqlite3 *db,
+  void *pAux,
+  int argc, const char *const*argv,
+  sqlite3_vtab **ppVtab,
+  char **pzErr
+){
+  int rc = SQLITE_OK;
+  CidxTable *pRet;
+
+  rc = sqlite3_declare_vtab(db,
+      "CREATE TABLE xyz("
+      " errmsg TEXT, current_key TEXT,"
+      " index_name HIDDEN, after_key HIDDEN"
+      ")"
+  );
+  pRet = cidxMalloc(&rc, sizeof(CidxTable));
+  if( pRet ){
+    pRet->db = db;
+  }
+
+  *ppVtab = (sqlite3_vtab*)pRet;
+  return rc;
+}
+
+/*
+** Disconnect from or destroy an incremental_index_check virtual table.
+*/
+static int cidxDisconnect(sqlite3_vtab *pVtab){
+  CidxTable *pTab = (CidxTable*)pVtab;
+  sqlite3_free(pTab);
+  return SQLITE_OK;
+}
+
+/*
+** xBestIndex method.
+*/
+static int cidxBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pInfo){
+  int iIdxName = -1;
+  int iAfterKey = -1;
+  int i;
+
+  for(i=0; i<pInfo->nConstraint; i++){
+    struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
+    if( p->usable==0 ) continue;
+    if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
+
+    if( p->iColumn==2 ){
+      iIdxName = i;
+    }
+    if( p->iColumn==3 ){
+      iAfterKey = i;
+    }
+  }
+
+  if( iIdxName<0 ){
+    pInfo->estimatedCost = 1000000000.0;
+  }else{
+    pInfo->aConstraintUsage[iIdxName].argvIndex = 1;
+    pInfo->aConstraintUsage[iIdxName].omit = 1;
+    if( iAfterKey<0 ){
+      pInfo->estimatedCost = 1000000.0;
+    }else{
+      pInfo->aConstraintUsage[iAfterKey].argvIndex = 2;
+      pInfo->aConstraintUsage[iAfterKey].omit = 1;
+      pInfo->estimatedCost = 1000.0;
+    }
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+** Open a new btreeinfo cursor.
+*/
+static int cidxOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
+  CidxCursor *pRet;
+  int rc = SQLITE_OK;
+
+  pRet = cidxMalloc(&rc, sizeof(CidxCursor));
+
+  *ppCursor = (sqlite3_vtab_cursor*)pRet;
+  return rc;
+}
+
+/*
+** Close a btreeinfo cursor.
+*/
+static int cidxClose(sqlite3_vtab_cursor *pCursor){
+  CidxCursor *pCsr = (CidxCursor*)pCursor;
+  sqlite3_finalize(pCsr->pStmt);
+  pCsr->pStmt = 0;
+  sqlite3_free(pCsr);
+  return SQLITE_OK;
+}
+
+/*
+** Move a btreeinfo cursor to the next entry in the file.
+*/
+static int cidxNext(sqlite3_vtab_cursor *pCursor){
+  CidxCursor *pCsr = (CidxCursor*)pCursor;
+  int rc = sqlite3_step(pCsr->pStmt);
+  if( rc!=SQLITE_ROW ){
+    rc = sqlite3_finalize(pCsr->pStmt);
+    pCsr->pStmt = 0;
+    if( rc!=SQLITE_OK ){
+      sqlite3 *db = ((CidxTable*)pCsr->base.pVtab)->db;
+      cidxCursorError(pCsr, "Cursor error: %s", sqlite3_errmsg(db));
+    }
+  }else{
+    rc = SQLITE_OK;
+  }
+  return rc;
+}
+
+/* We have reached EOF if previous sqlite3_step() returned
+** anything other than SQLITE_ROW;
+*/
+static int cidxEof(sqlite3_vtab_cursor *pCursor){
+  CidxCursor *pCsr = (CidxCursor*)pCursor;
+  return pCsr->pStmt==0;
+}
+
+static sqlite3_stmt *cidxPrepare(
+  int *pRc, CidxCursor *pCsr, const char *zFmt, ...
+){
+  sqlite3_stmt *pRet = 0;
+  char *zSql;
+  va_list ap;                     /* ... printf arguments */
+  va_start(ap, zFmt);
+
+  zSql = sqlite3_vmprintf(zFmt, ap);
+  if( *pRc==SQLITE_OK ){
+    if( zSql==0 ){
+      *pRc = SQLITE_NOMEM;
+    }else{
+      sqlite3 *db = ((CidxTable*)pCsr->base.pVtab)->db;
+      *pRc = sqlite3_prepare_v2(db, zSql, -1, &pRet, 0);
+      if( *pRc!=SQLITE_OK ){
+        cidxCursorError(pCsr, "SQL error: %s", sqlite3_errmsg(db));
+      }
+    }
+  }
+  sqlite3_free(zSql);
+  va_end(ap);
+
+  return pRet;
+}
+
+static void cidxFinalize(int *pRc, sqlite3_stmt *pStmt){
+  int rc = sqlite3_finalize(pStmt);
+  if( *pRc==SQLITE_OK ) *pRc = rc;
+}
+
+char *cidxStrdup(int *pRc, const char *zStr){
+  char *zRet = 0;
+  if( *pRc==SQLITE_OK ){
+    int n = strlen(zStr);
+    zRet = cidxMalloc(pRc, n+1);
+    if( zRet ) memcpy(zRet, zStr, n+1);
+  }
+  return zRet;
+}
+
+static int cidxLookupIndex(
+  CidxCursor *pCsr,               /* Cursor object */
+  const char *zIdx,               /* Name of index to look up */
+  char **pzTab,                   /* OUT: Table name */
+  char **pzCurrentKey,            /* OUT: Expression for current_key */
+  char **pzOrderBy,               /* OUT: ORDER BY expression list */
+  char **pzSubWhere,              /* OUT: sub-query WHERE clause */
+  char **pzSubExpr                /* OUT: sub-query WHERE clause */
+){
+  int rc = SQLITE_OK;
+  char *zTab = 0;
+  char *zCurrentKey = 0;
+  char *zOrderBy = 0;
+  char *zSubWhere = 0;
+  char *zSubExpr = 0;
+
+  sqlite3_stmt *pFindTab = 0;
+  sqlite3_stmt *pGroup = 0;
+    
+  /* Find the table */
+  pFindTab = cidxPrepare(&rc, pCsr, 
+      "SELECT tbl_name FROM sqlite_master WHERE name=%Q AND type='index'",
+      zIdx
+  );
+  if( rc==SQLITE_OK && sqlite3_step(pFindTab)==SQLITE_ROW ){
+    zTab = cidxStrdup(&rc, (const char*)sqlite3_column_text(pFindTab, 0));
+  }
+  cidxFinalize(&rc, pFindTab);
+  if( rc==SQLITE_OK && zTab==0 ){
+    rc = SQLITE_ERROR;
+  }
+
+  pGroup = cidxPrepare(&rc, pCsr,
+      "SELECT group_concat("
+      "  coalesce(name, 'rowid'), '|| '','' ||'"
+      ") AS zCurrentKey,"
+      "       group_concat("
+      "  coalesce(name, 'rowid') || CASE WHEN desc THEN ' DESC' ELSE '' END,"
+      "  ', '"
+      ") AS zOrderBy,"
+      "       group_concat("
+      "         CASE WHEN key==1 THEN NULL ELSE "
+      "  coalesce(name, 'rowid') || ' IS \"%w\".' || coalesce(name, 'rowid') "
+      "         END,"
+      "  'AND '"
+      ") AS zSubWhere,"
+      "       group_concat("
+      "         CASE WHEN key==0 THEN NULL ELSE "
+      "  coalesce(name, 'rowid') || ' IS \"%w\".' || coalesce(name, 'rowid') "
+      "         END,"
+      "  'AND '"
+      ") AS zSubExpr "
+      " FROM pragma_index_xinfo(%Q);"
+      , zIdx, zIdx, zIdx
+  );
+  if( rc==SQLITE_OK && sqlite3_step(pGroup)==SQLITE_ROW ){
+    zCurrentKey = cidxStrdup(&rc, (const char*)sqlite3_column_text(pGroup, 0));
+    zOrderBy = cidxStrdup(&rc, (const char*)sqlite3_column_text(pGroup, 1));
+    zSubWhere = cidxStrdup(&rc, (const char*)sqlite3_column_text(pGroup, 2));
+    zSubExpr = cidxStrdup(&rc, (const char*)sqlite3_column_text(pGroup, 3));
+  }
+  cidxFinalize(&rc, pGroup);
+  
+  if( rc!=SQLITE_OK ){
+    sqlite3_free(zTab);
+    sqlite3_free(zCurrentKey);
+    sqlite3_free(zOrderBy);
+    sqlite3_free(zSubWhere);
+    sqlite3_free(zSubExpr);
+  }else{
+    *pzTab = zTab;
+    *pzCurrentKey = zCurrentKey;
+    *pzOrderBy = zOrderBy;
+    *pzSubWhere = zSubWhere;
+    *pzSubExpr = zSubExpr;
+  }
+
+  return rc;
+}
+
+/* 
+** Position a cursor back to the beginning.
+*/
+static int cidxFilter(
+  sqlite3_vtab_cursor *pCursor, 
+  int idxNum, const char *idxStr,
+  int argc, sqlite3_value **argv
+){
+  int rc = SQLITE_OK;
+  CidxCursor *pCsr = (CidxCursor*)pCursor;
+  const char *zIdxName = 0;
+  const char *zAfterKey = 0;
+
+  if( argc>0 ){
+    zIdxName = (const char*)sqlite3_value_text(argv[0]);
+    if( argc>1 ){
+      zAfterKey = (const char*)sqlite3_value_text(argv[1]);
+    }
+  }
+
+  if( zIdxName ){
+    char *zTab = 0;
+    char *zCurrentKey = 0;
+    char *zOrderBy = 0;
+    char *zSubWhere = 0;
+    char *zSubExpr = 0;
+
+    rc = cidxLookupIndex(pCsr, zIdxName, 
+        &zTab, &zCurrentKey, &zOrderBy, &zSubWhere, &zSubExpr
+    );
+    pCsr->pStmt = cidxPrepare(&rc, pCsr, 
+        "SELECT (SELECT %s FROM %Q WHERE %s), %s FROM %Q AS %Q ORDER BY %s",
+        zSubExpr, zTab, zSubWhere, zCurrentKey, zTab, zIdxName, zOrderBy
+    );
+
+    sqlite3_free(zTab);
+    sqlite3_free(zCurrentKey);
+    sqlite3_free(zOrderBy);
+    sqlite3_free(zSubWhere);
+    sqlite3_free(zSubExpr);
+  }
+
+  if( pCsr->pStmt ){
+    assert( rc==SQLITE_OK );
+    rc = cidxNext(pCursor);
+  }
+  return rc;
+}
+
+/* Return a column for the sqlite_btreeinfo table */
+static int cidxColumn(
+  sqlite3_vtab_cursor *pCursor, 
+  sqlite3_context *ctx, 
+  int iCol
+){
+  CidxCursor *pCsr = (CidxCursor*)pCursor;
+  assert( iCol==0 || iCol==1 );
+  if( iCol==0 ){
+    const char *zVal = 0;
+    if( sqlite3_column_type(pCsr->pStmt, 0)==SQLITE_INTEGER ){
+      if( sqlite3_column_int(pCsr->pStmt, 0)==0 ){
+        zVal = "row data mismatch";
+      }
+    }else{
+      zVal = "row missing";
+    }
+    sqlite3_result_text(ctx, zVal, -1, SQLITE_STATIC);
+  }else{
+    sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, 1));
+  }
+  return SQLITE_OK;
+}
+
+/* Return the ROWID for the sqlite_btreeinfo table */
+static int cidxRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){
+  *pRowid = 0;
+  return SQLITE_OK;
+}
+
+/*
+** Register the virtual table modules with the database handle passed
+** as the only argument.
+*/
+static int ciInit(sqlite3 *db){
+  static sqlite3_module cidx_module = {
+    0,                            /* iVersion */
+    0,                            /* xCreate */
+    cidxConnect,                  /* xConnect */
+    cidxBestIndex,                /* xBestIndex */
+    cidxDisconnect,               /* xDisconnect */
+    0,                            /* xDestroy */
+    cidxOpen,                     /* xOpen - open a cursor */
+    cidxClose,                    /* xClose - close a cursor */
+    cidxFilter,                   /* xFilter - configure scan constraints */
+    cidxNext,                     /* xNext - advance a cursor */
+    cidxEof,                      /* xEof - check for end of scan */
+    cidxColumn,                   /* xColumn - read data */
+    cidxRowid,                    /* xRowid - read data */
+    0,                            /* xUpdate */
+    0,                            /* xBegin */
+    0,                            /* xSync */
+    0,                            /* xCommit */
+    0,                            /* xRollback */
+    0,                            /* xFindMethod */
+    0,                            /* xRename */
+    0,                            /* xSavepoint */
+    0,                            /* xRelease */
+    0,                            /* xRollbackTo */
+  };
+  return sqlite3_create_module(db, "incremental_index_check", &cidx_module, 0);
+}
+
+/*
+** Extension load function.
+*/
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_checkindex_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  SQLITE_EXTENSION_INIT2(pApi);
+  return ciInit(db);
+}
index 2d13f91b8db006694b13ac976d6182cf0cf2f60c..e0740cdd7b96068f3bae64052e9ee23559724e2f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Handle\sthe\sCtrl-C\sevent\sfor\sthe\sshell\son\sWin32.
-D 2017-10-27T17:09:44.546
+C Add\sthe\sstart\sof\sthe\s"incremental_index_check"\svirtual\stable\sin\next/repair/checkindex.c.\sFor\sincremental\sverification\sof\sindex\scontents.
+D 2017-10-27T20:53:16.451
 F Makefile.in e016061b23e60ac9ec27c65cb577292b6bde0307ca55abd874ab3487b3b1beb2
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc 37740aba9c4bb359c627eadccf1cfd7be4f5f847078723777ea7763969e533b1
@@ -328,6 +328,7 @@ F ext/rbu/sqlite3rbu.h b42bcd4d8357268c6c39ab2a60b29c091e89328fa8cc49c8fac5ab8d0
 F ext/rbu/test_rbu.c 7073979b9cc80912bb03599ac8d85ab5d3bf03cfacd3463f2dcdd7822997533a
 F ext/repair/README.md 92f5e8aae749a4dae14f02eea8e1bb42d4db2b6ce5e83dbcdd6b1446997e0c15
 F ext/repair/checkfreelist.c 0abb84b4545016d57ba1a2aa8884c72c73ed838968909858c03bc1f38fb6b054
+F ext/repair/checkindex.c 924432d01fabff8df8a758ef29d7124483653cd7874787564664e0eea8e267b1
 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
 F ext/rtree/rtree.c cc91b6905bf55512c6ebc7dfdd37ac81c86f1753db8cfa6d62f0ee864464044f
 F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
@@ -653,6 +654,7 @@ F test/cast.test 4c275cbdc8202d6f9c54a3596701719868ac7dc3
 F test/cffault.test 9d6b20606afe712374952eec4f8fd74b1a8097ef
 F test/check.test 33a698e8c63613449d85d624a38ef669bf20331daabebe3891c9405dd6df463a
 F test/checkfreelist.test 100283a3e6b8a3018c7fab7cfdaf03d1d6540fc66453114e248cf82b25784d3b
+F test/checkindex.test 2dc7bd4c0de8ba7a8af0b6d3beaa6759d57b88c62e10ae4d158e9f544982d5d4
 F test/close.test 799ea4599d2f5704b0a30f477d17c2c760d8523fa5d0c8be4a7df2a8cad787d8
 F test/closure01.test b1703ba40639cfc9b295cf478d70739415eec6a4
 F test/coalesce.test cee0dccb9fbd2d494b77234bccf9dc6c6786eb91
@@ -1666,7 +1668,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 8d3cc928a8f0c7b2616c5c75af9d3a50bd4f0fe8e4ccab545ab8648cbfbb4b7f
-R b04098ed5c4b7451669ce8c4fddf24e2
-U mistachkin
-Z 04b0a33e7f8b0430861d645ce26e5f19
+P c8aaf37117ed4a23bbd15dc481788735efcb77bff98be423eca3521c0a9270de
+R 38fb5f31777282a52c65b4e266007341
+T *branch * checkindex
+T *sym-checkindex *
+T -sym-trunk *
+U dan
+Z 12607f1e180da6aa96255bb6397017d4
index 478c761165084ca754ef536ae5e5a5b0724efbbe..9ee1c84b8f3f1b11763c721a133784616852b879 100644 (file)
@@ -1 +1 @@
-c8aaf37117ed4a23bbd15dc481788735efcb77bff98be423eca3521c0a9270de
\ No newline at end of file
+d5b9dada471358a2864727759648b763bf6890fc2521fac53c0d8216017d39b7
\ No newline at end of file
diff --git a/test/checkindex.test b/test/checkindex.test
new file mode 100644 (file)
index 0000000..841dca7
--- /dev/null
@@ -0,0 +1,91 @@
+# 2017-10-11
+#
+# 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.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this file is testing the checkindex extension.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix checkindex
+
+ifcapable !vtab||!compound {
+  finish_test
+  return
+}
+
+if {[file exists ../checkindex.so]==0} {
+  finish_test
+  return
+}
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a, b);
+  CREATE INDEX i1 ON t1(a);
+  INSERT INTO t1 VALUES('one', 2);
+  INSERT INTO t1 VALUES('two', 4);
+  INSERT INTO t1 VALUES('three', 6);
+  INSERT INTO t1 VALUES('four', 8);
+  INSERT INTO t1 VALUES('five', 10);
+}
+
+db enable_load_extension 1
+do_execsql_test 1.1 {
+  SELECT load_extension('../checkindex.so');
+} {{}}
+
+do_execsql_test 1.2 {
+  SELECT errmsg IS NULL, current_key FROM incremental_index_check('i1');
+} {
+  1 five,5
+  1 four,4
+  1 one,1
+  1 three,3
+  1 two,2
+}
+
+
+do_test 1.3 {
+  set tblroot [db one { SELECT rootpage FROM sqlite_master WHERE name='t1' }]
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $tblroot
+  db eval {CREATE TABLE xt1(a, b)}
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
+
+  execsql {
+    UPDATE xt1 SET a='six' WHERE rowid=3;
+    DELETE FROM xt1 WHERE rowid = 5;
+  }
+
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 1
+} {}
+
+do_execsql_test 1.4 {
+  SELECT errmsg IS NULL, current_key FROM incremental_index_check('i1');
+} {
+  0 five,5
+  1 four,4
+  1 one,1
+  0 three,3
+  1 two,2
+}
+do_execsql_test 1.5 {
+  SELECT errmsg, current_key FROM incremental_index_check('i1');
+} {
+  {row missing} five,5
+  {} four,4
+  {} one,1
+  {row data mismatch} three,3
+  {} two,2
+}
+
+
+
+finish_test
+