/ Check-in [c40c3c62]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix "after" parameter handling in the incremental_index_check code.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | checkindex
Files: files | file ages | folders
SHA3-256: c40c3c62e996044f31ca49ffc2edb2cc0320e69956f7ee6fe3e9012200e0d9a0
User & Date: dan 2017-10-28 20:31:25
Context
2017-10-30
08:04
Fix an issue in incremental_index_check with indexes that use non-default collation sequences. check-in: 3ebb2351 user: dan tags: checkindex
2017-10-28
20:31
Fix "after" parameter handling in the incremental_index_check code. check-in: c40c3c62 user: dan tags: checkindex
2017-10-27
20:53
Add the start of the "incremental_index_check" virtual table in ext/repair/checkindex.c. For incremental verification of index contents. check-in: d5b9dada user: dan tags: checkindex
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/repair/checkindex.c.

    40     40     sqlite3 *db;
    41     41   };
    42     42   
    43     43   struct CidxCursor {
    44     44     sqlite3_vtab_cursor base;       /* Base class.  Must be first */
    45     45     sqlite3_stmt *pStmt;
    46     46   };
           47  +
           48  +typedef struct CidxColumn CidxColumn;
           49  +struct CidxColumn {
           50  +  char *zName;
           51  +  char *zColl;
           52  +  int bDesc;
           53  +};
    47     54   
    48     55   static void *cidxMalloc(int *pRc, int n){
    49     56     void *pRet = 0;
    50     57     assert( n!=0 );
    51     58     if( *pRc==SQLITE_OK ){
    52     59       pRet = sqlite3_malloc(n);
    53     60       if( pRet ){
................................................................................
   188    195   /* We have reached EOF if previous sqlite3_step() returned
   189    196   ** anything other than SQLITE_ROW;
   190    197   */
   191    198   static int cidxEof(sqlite3_vtab_cursor *pCursor){
   192    199     CidxCursor *pCsr = (CidxCursor*)pCursor;
   193    200     return pCsr->pStmt==0;
   194    201   }
          202  +
          203  +static char *cidxMprintf(int *pRc, const char *zFmt, ...){
          204  +  char *zRet = 0;
          205  +  va_list ap;
          206  +  va_start(ap, zFmt);
          207  +  zRet = sqlite3_vmprintf(zFmt, ap);
          208  +  if( *pRc==SQLITE_OK ){
          209  +    if( zRet==0 ){
          210  +      *pRc = SQLITE_NOMEM;
          211  +    }
          212  +  }else{
          213  +    sqlite3_free(zRet);
          214  +    zRet = 0;
          215  +  }
          216  +  va_end(ap);
          217  +  return zRet;
          218  +}
   195    219   
   196    220   static sqlite3_stmt *cidxPrepare(
   197    221     int *pRc, CidxCursor *pCsr, const char *zFmt, ...
   198    222   ){
   199    223     sqlite3_stmt *pRet = 0;
   200    224     char *zSql;
   201    225     va_list ap;                     /* ... printf arguments */
................................................................................
   233    257     }
   234    258     return zRet;
   235    259   }
   236    260   
   237    261   static int cidxLookupIndex(
   238    262     CidxCursor *pCsr,               /* Cursor object */
   239    263     const char *zIdx,               /* Name of index to look up */
          264  +  int *pnCol,                     /* OUT: Number of columns in index */
          265  +  CidxColumn **paCol,             /* OUT: Columns */
   240    266     char **pzTab,                   /* OUT: Table name */
   241    267     char **pzCurrentKey,            /* OUT: Expression for current_key */
   242    268     char **pzOrderBy,               /* OUT: ORDER BY expression list */
   243    269     char **pzSubWhere,              /* OUT: sub-query WHERE clause */
   244    270     char **pzSubExpr                /* OUT: sub-query WHERE clause */
   245    271   ){
   246    272     int rc = SQLITE_OK;
   247    273     char *zTab = 0;
   248    274     char *zCurrentKey = 0;
   249    275     char *zOrderBy = 0;
   250    276     char *zSubWhere = 0;
   251    277     char *zSubExpr = 0;
          278  +  CidxColumn *aCol = 0;
   252    279   
   253    280     sqlite3_stmt *pFindTab = 0;
   254    281     sqlite3_stmt *pGroup = 0;
   255    282       
   256    283     /* Find the table */
   257    284     pFindTab = cidxPrepare(&rc, pCsr, 
   258    285         "SELECT tbl_name FROM sqlite_master WHERE name=%Q AND type='index'",
................................................................................
   264    291     cidxFinalize(&rc, pFindTab);
   265    292     if( rc==SQLITE_OK && zTab==0 ){
   266    293       rc = SQLITE_ERROR;
   267    294     }
   268    295   
   269    296     pGroup = cidxPrepare(&rc, pCsr,
   270    297         "SELECT group_concat("
   271         -      "  coalesce(name, 'rowid'), '|| '','' ||'"
          298  +      "  coalesce('quote(' || name || ')', 'rowid'), '|| '','' ||'"
   272    299         ") AS zCurrentKey,"
   273    300         "       group_concat("
   274    301         "  coalesce(name, 'rowid') || CASE WHEN desc THEN ' DESC' ELSE '' END,"
   275    302         "  ', '"
   276    303         ") AS zOrderBy,"
   277    304         "       group_concat("
   278    305         "         CASE WHEN key==1 THEN NULL ELSE "
   279    306         "  coalesce(name, 'rowid') || ' IS \"%w\".' || coalesce(name, 'rowid') "
   280    307         "         END,"
   281         -      "  'AND '"
          308  +      "  ' AND '"
   282    309         ") AS zSubWhere,"
   283    310         "       group_concat("
   284    311         "         CASE WHEN key==0 THEN NULL ELSE "
   285    312         "  coalesce(name, 'rowid') || ' IS \"%w\".' || coalesce(name, 'rowid') "
   286    313         "         END,"
   287         -      "  'AND '"
   288         -      ") AS zSubExpr "
          314  +      "  ' AND '"
          315  +      ") AS zSubExpr,"
          316  +      "       count(*) AS nCol"
   289    317         " FROM pragma_index_xinfo(%Q);"
   290    318         , zIdx, zIdx, zIdx
   291    319     );
   292    320     if( rc==SQLITE_OK && sqlite3_step(pGroup)==SQLITE_ROW ){
   293    321       zCurrentKey = cidxStrdup(&rc, (const char*)sqlite3_column_text(pGroup, 0));
   294    322       zOrderBy = cidxStrdup(&rc, (const char*)sqlite3_column_text(pGroup, 1));
   295    323       zSubWhere = cidxStrdup(&rc, (const char*)sqlite3_column_text(pGroup, 2));
   296    324       zSubExpr = cidxStrdup(&rc, (const char*)sqlite3_column_text(pGroup, 3));
          325  +    *pnCol = sqlite3_column_int(pGroup, 4);
   297    326     }
   298    327     cidxFinalize(&rc, pGroup);
          328  +
          329  +  pGroup = cidxPrepare(&rc, pCsr, "PRAGMA index_xinfo(%Q)", zIdx);
          330  +  if( rc==SQLITE_OK ){
          331  +    int nByte = 0;
          332  +    int nCol = 0;
          333  +    while( sqlite3_step(pGroup)==SQLITE_ROW ){
          334  +      const char *zName = (const char*)sqlite3_column_text(pGroup, 2);
          335  +      const char *zColl = (const char*)sqlite3_column_text(pGroup, 4);
          336  +      if( zName==0 ) zName = "rowid";
          337  +      nCol++;
          338  +      nByte += strlen(zName)+1 + strlen(zColl)+1;
          339  +    }
          340  +    rc = sqlite3_reset(pGroup);
          341  +    aCol = (CidxColumn*)cidxMalloc(&rc, sizeof(CidxColumn)*nCol + nByte);
          342  +
          343  +    if( rc==SQLITE_OK ){
          344  +      int iCol = 0;
          345  +      char *z = (char*)&aCol[nCol];
          346  +      while( sqlite3_step(pGroup)==SQLITE_ROW ){
          347  +        int nName, nColl;
          348  +        const char *zName = (const char*)sqlite3_column_text(pGroup, 2);
          349  +        const char *zColl = (const char*)sqlite3_column_text(pGroup, 4);
          350  +        if( zName==0 ) zName = "rowid";
          351  +
          352  +        nName = strlen(zName);
          353  +        nColl = strlen(zColl);
          354  +        memcpy(z, zName, nName);
          355  +        aCol[iCol].zName = z;
          356  +        z += nName+1;
          357  +
          358  +        memcpy(z, zColl, nColl);
          359  +        aCol[iCol].zColl = z;
          360  +        z += nColl+1;
          361  +
          362  +        aCol[iCol].bDesc = sqlite3_column_int(pGroup, 3);
          363  +        iCol++;
          364  +      }
          365  +    }
          366  +    cidxFinalize(&rc, pGroup);
          367  +  }
   299    368     
   300    369     if( rc!=SQLITE_OK ){
   301    370       sqlite3_free(zTab);
   302    371       sqlite3_free(zCurrentKey);
   303    372       sqlite3_free(zOrderBy);
   304    373       sqlite3_free(zSubWhere);
   305    374       sqlite3_free(zSubExpr);
          375  +    sqlite3_free(aCol);
   306    376     }else{
   307    377       *pzTab = zTab;
   308    378       *pzCurrentKey = zCurrentKey;
   309    379       *pzOrderBy = zOrderBy;
   310    380       *pzSubWhere = zSubWhere;
   311    381       *pzSubExpr = zSubExpr;
          382  +    *paCol = aCol;
          383  +  }
          384  +
          385  +  return rc;
          386  +}
          387  +
          388  +static int cidxDecodeAfter(
          389  +  CidxCursor *pCsr, 
          390  +  int nCol, 
          391  +  const char *zAfterKey, 
          392  +  char ***pazAfter
          393  +){
          394  +  char **azAfter;
          395  +  int rc = SQLITE_OK;
          396  +  int nAfterKey = strlen(zAfterKey);
          397  +
          398  +  azAfter = cidxMalloc(&rc, sizeof(char*)*nCol + nAfterKey+1);
          399  +  if( rc==SQLITE_OK ){
          400  +    int i;
          401  +    char *zCopy = (char*)&azAfter[nCol];
          402  +    char *p = zCopy;
          403  +    memcpy(zCopy, zAfterKey, nAfterKey+1);
          404  +    for(i=0; i<nCol; i++){
          405  +      while( *p==' ' ) p++;
          406  +
          407  +      /* Check NULL values */
          408  +      if( *p=='N' ){
          409  +        if( memcmp(p, "NULL", 4) ) goto parse_error;
          410  +        p += 4;
          411  +      }
          412  +
          413  +      /* Check strings and blob literals */
          414  +      else if( *p=='X' || *p=='\'' ){
          415  +        azAfter[i] = p;
          416  +        if( *p=='X' ) p++;
          417  +        if( *p!='\'' ) goto parse_error;
          418  +        p++;
          419  +        while( 1 ){
          420  +          if( *p=='\0' ) goto parse_error;
          421  +          if( *p=='\'' ){
          422  +            p++;
          423  +            if( *p!='\'' ) break;
          424  +          }
          425  +          p++;
          426  +        }
          427  +      }
          428  +
          429  +      /* Check numbers */
          430  +      else{
          431  +        azAfter[i] = p;
          432  +        while( (*p>='0' && *p<='9') 
          433  +            || *p=='.' || *p=='+' || *p=='-' || *p=='e' || *p=='E'
          434  +        ){
          435  +          p++;
          436  +        }
          437  +      }
          438  +
          439  +      while( *p==' ' ) p++;
          440  +      if( *p!=(i==(nCol-1) ? '\0' : ',') ){
          441  +        goto parse_error;
          442  +      }
          443  +      *p++ = '\0';
          444  +    }
   312    445     }
   313    446   
          447  +  *pazAfter = azAfter;
   314    448     return rc;
          449  +
          450  + parse_error:
          451  +  sqlite3_free(azAfter);
          452  +  *pazAfter = 0;
          453  +  cidxCursorError(pCsr, "%s", "error parsing after value");
          454  +  return SQLITE_ERROR;
          455  +}
          456  +
          457  +static char *cidxWhere(
          458  +  int *pRc, CidxColumn *aCol, char **azAfter, int iGt, int bLastIsNull
          459  +){
          460  +  char *zRet = 0;
          461  +  const char *zSep = "";
          462  +  int i;
          463  +
          464  +  for(i=0; i<iGt; i++){
          465  +    zRet = cidxMprintf(pRc, "%z%s%s COLLATE %s IS %s", zRet, 
          466  +        zSep, aCol[i].zName, aCol[i].zColl, (azAfter[i] ? azAfter[i] : "NULL")
          467  +    );
          468  +    zSep = " AND ";
          469  +  }
          470  +
          471  +  if( bLastIsNull ){
          472  +    zRet = cidxMprintf(pRc, "%z%s%s IS NULL", zRet, zSep, aCol[iGt].zName);
          473  +  }
          474  +  else if( azAfter[iGt] ){
          475  +    zRet = cidxMprintf(pRc, "%z%s%s COLLATE %s %s %s", zRet, 
          476  +        zSep, aCol[iGt].zName, aCol[iGt].zColl, (aCol[iGt].bDesc ? "<" : ">"), 
          477  +        azAfter[iGt]
          478  +    );
          479  +  }else{
          480  +    zRet = cidxMprintf(pRc, "%z%s%s IS NOT NULL", zRet, zSep, aCol[iGt].zName);
          481  +  }
          482  +
          483  +  return zRet;
          484  +}
          485  +
          486  +static char *cidxColumnList(int *pRc, CidxColumn *aCol, int nCol){
          487  +  int i;
          488  +  char *zRet = 0;
          489  +  const char *zSep = "";
          490  +  for(i=0; i<nCol; i++){
          491  +    zRet = cidxMprintf(pRc, "%z%s%s", zRet, zSep, aCol[i].zName);
          492  +    zSep = ",";
          493  +  }
          494  +  return zRet;
   315    495   }
   316    496   
   317    497   /* 
   318    498   ** Position a cursor back to the beginning.
   319    499   */
   320    500   static int cidxFilter(
   321    501     sqlite3_vtab_cursor *pCursor, 
................................................................................
   331    511       zIdxName = (const char*)sqlite3_value_text(argv[0]);
   332    512       if( argc>1 ){
   333    513         zAfterKey = (const char*)sqlite3_value_text(argv[1]);
   334    514       }
   335    515     }
   336    516   
   337    517     if( zIdxName ){
          518  +    int nCol = 0;
   338    519       char *zTab = 0;
   339    520       char *zCurrentKey = 0;
   340    521       char *zOrderBy = 0;
   341    522       char *zSubWhere = 0;
   342    523       char *zSubExpr = 0;
          524  +    char **azAfter = 0;
          525  +    CidxColumn *aCol = 0;
   343    526   
   344    527       rc = cidxLookupIndex(pCsr, zIdxName, 
   345         -        &zTab, &zCurrentKey, &zOrderBy, &zSubWhere, &zSubExpr
          528  +        &nCol, &aCol, &zTab, &zCurrentKey, &zOrderBy, &zSubWhere, &zSubExpr
   346    529       );
   347         -    pCsr->pStmt = cidxPrepare(&rc, pCsr, 
   348         -        "SELECT (SELECT %s FROM %Q WHERE %s), %s FROM %Q AS %Q ORDER BY %s",
   349         -        zSubExpr, zTab, zSubWhere, zCurrentKey, zTab, zIdxName, zOrderBy
   350         -    );
          530  +
          531  +    if( rc==SQLITE_OK && zAfterKey ){
          532  +      rc = cidxDecodeAfter(pCsr, nCol, zAfterKey, &azAfter);
          533  +    }
          534  +
          535  +    if( rc || zAfterKey==0 ){
          536  +      pCsr->pStmt = cidxPrepare(&rc, pCsr, 
          537  +          "SELECT (SELECT %s FROM %Q WHERE %s), %s FROM %Q AS %Q ORDER BY %s",
          538  +          zSubExpr, zTab, zSubWhere, zCurrentKey, zTab, zIdxName, zOrderBy
          539  +      );
          540  +      /* printf("SQL: %s\n", sqlite3_sql(pCsr->pStmt)); */
          541  +    }else{
          542  +      char *zList = cidxColumnList(&rc, aCol, nCol);
          543  +      const char *zSep = "";
          544  +      char *zSql;
          545  +      int i;
          546  +
          547  +      zSql = cidxMprintf(&rc, "SELECT (SELECT %s FROM %Q WHERE %s), %s FROM (",
          548  +          zSubExpr, zTab, zSubWhere, zCurrentKey
          549  +      );
          550  +      for(i=nCol-1; i>=0; i--){
          551  +        int j;
          552  +        if( aCol[i].bDesc && azAfter[i]==0 ) continue;
          553  +        for(j=0; j<2; j++){
          554  +          char *zWhere = cidxWhere(&rc, aCol, azAfter, i, j);
          555  +          zSql = cidxMprintf(&rc, 
          556  +              "%z%s SELECT * FROM (SELECT %s FROM %Q WHERE %z ORDER BY %s)",
          557  +              zSql, zSep, zList, zTab, zWhere, zOrderBy
          558  +              );
          559  +          zSep = " UNION ALL ";
          560  +          if( aCol[i].bDesc==0 ) break;
          561  +        }
          562  +      }
          563  +      zSql = cidxMprintf(&rc, "%z) AS %Q", zSql, zIdxName);
          564  +      sqlite3_free(zList);
          565  +
          566  +      /* printf("SQL: %s\n", zSql); */
          567  +      pCsr->pStmt = cidxPrepare(&rc, pCsr, "%z", zSql);
          568  +    }
   351    569   
   352    570       sqlite3_free(zTab);
   353    571       sqlite3_free(zCurrentKey);
   354    572       sqlite3_free(zOrderBy);
   355    573       sqlite3_free(zSubWhere);
   356    574       sqlite3_free(zSubExpr);
          575  +    sqlite3_free(aCol);
          576  +    sqlite3_free(azAfter);
   357    577     }
   358    578   
   359    579     if( pCsr->pStmt ){
   360    580       assert( rc==SQLITE_OK );
   361    581       rc = cidxNext(pCursor);
   362    582     }
   363    583     return rc;

Changes to test/checkindex.test.

    30     30     CREATE TABLE t1(a, b);
    31     31     CREATE INDEX i1 ON t1(a);
    32     32     INSERT INTO t1 VALUES('one', 2);
    33     33     INSERT INTO t1 VALUES('two', 4);
    34     34     INSERT INTO t1 VALUES('three', 6);
    35     35     INSERT INTO t1 VALUES('four', 8);
    36     36     INSERT INTO t1 VALUES('five', 10);
           37  +
           38  +  CREATE INDEX i2 ON t1(a DESC);
    37     39   }
    38     40   
    39     41   db enable_load_extension 1
    40     42   do_execsql_test 1.1 {
    41     43     SELECT load_extension('../checkindex.so');
    42     44   } {{}}
           45  +
           46  +proc incr_index_check {idx nStep} {
           47  +  set Q {
           48  +    SELECT errmsg, current_key FROM incremental_index_check($idx, $after)
           49  +    LIMIT $nStep
           50  +  }
           51  +
           52  +  set res [list]
           53  +  while {1} {
           54  +    unset -nocomplain current_key
           55  +    set res1 [db eval $Q]
           56  +    if {[llength $res1]==0} break
           57  +    set res [concat $res $res1]
           58  +    set after [lindex $res end]
           59  +  }
           60  +
           61  +  return $res
           62  +}
           63  +
           64  +proc do_index_check_test {tn idx res} {
           65  +  uplevel [list do_execsql_test $tn.1 "
           66  +    SELECT errmsg, current_key FROM incremental_index_check('$idx');
           67  +  " $res]
           68  +
           69  +  uplevel [list do_test $tn.2 "incr_index_check $idx 1" [list {*}$res]]
           70  +  #uplevel [list do_test $tn.3 "incr_index_check $idx 2" [list {*}$res]]
           71  +  #uplevel [list do_test $tn.4 "incr_index_check $idx 5" [list {*}$res]]
           72  +}
    43     73   
    44     74   do_execsql_test 1.2 {
    45     75     SELECT errmsg IS NULL, current_key FROM incremental_index_check('i1');
    46     76   } {
    47         -  1 five,5
    48         -  1 four,4
    49         -  1 one,1
    50         -  1 three,3
    51         -  1 two,2
           77  +  1 'five',5
           78  +  1 'four',4
           79  +  1 'one',1
           80  +  1 'three',3
           81  +  1 'two',2
           82  +}
           83  +
           84  +do_index_check_test 1.3 i1 {
           85  +  {} 'five',5
           86  +  {} 'four',4
           87  +  {} 'one',1
           88  +  {} 'three',3
           89  +  {} 'two',2
           90  +}
           91  +
           92  +do_index_check_test 1.4 i2 {
           93  +  {} 'two',2
           94  +  {} 'three',3
           95  +  {} 'one',1
           96  +  {} 'four',4
           97  +  {} 'five',5
    52     98   }
    53     99   
    54         -
    55         -do_test 1.3 {
          100  +do_test 1.5 {
    56    101     set tblroot [db one { SELECT rootpage FROM sqlite_master WHERE name='t1' }]
    57    102     sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $tblroot
    58    103     db eval {CREATE TABLE xt1(a, b)}
    59    104     sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
    60    105   
    61    106     execsql {
    62    107       UPDATE xt1 SET a='six' WHERE rowid=3;
    63    108       DELETE FROM xt1 WHERE rowid = 5;
    64    109     }
    65    110   
    66    111     sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 1
    67    112   } {}
    68    113   
    69         -do_execsql_test 1.4 {
    70         -  SELECT errmsg IS NULL, current_key FROM incremental_index_check('i1');
    71         -} {
    72         -  0 five,5
    73         -  1 four,4
    74         -  1 one,1
    75         -  0 three,3
    76         -  1 two,2
          114  +do_index_check_test 1.6 i1 {
          115  +  {row missing} 'five',5
          116  +  {} 'four',4
          117  +  {} 'one',1
          118  +  {row data mismatch} 'three',3
          119  +  {} 'two',2
          120  +}
          121  +
          122  +do_index_check_test 1.7 i2 {
          123  +  {} 'two',2
          124  +  {row data mismatch} 'three',3
          125  +  {} 'one',1
          126  +  {} 'four',4
          127  +  {row missing} 'five',5
          128  +}
          129  +
          130  +#--------------------------------------------------------------------------
          131  +do_execsql_test 2.0 {
          132  +
          133  +  CREATE TABLE t2(a INTEGER PRIMARY KEY, b, c, d);
          134  +
          135  +  INSERT INTO t2 VALUES(1, NULL, 1, 1);
          136  +  INSERT INTO t2 VALUES(2, 1, NULL, 1);
          137  +  INSERT INTO t2 VALUES(3, 1, 1, NULL);
          138  +
          139  +  INSERT INTO t2 VALUES(4, 2, 2, 1);
          140  +  INSERT INTO t2 VALUES(5, 2, 2, 2);
          141  +  INSERT INTO t2 VALUES(6, 2, 2, 3);
          142  +
          143  +  INSERT INTO t2 VALUES(7, 2, 2, 1);
          144  +  INSERT INTO t2 VALUES(8, 2, 2, 2);
          145  +  INSERT INTO t2 VALUES(9, 2, 2, 3);
          146  +
          147  +  CREATE INDEX i3 ON t2(b, c, d);
          148  +  CREATE INDEX i4 ON t2(b DESC, c DESC, d DESC);
          149  +  CREATE INDEX i5 ON t2(d, c DESC, b);
          150  +}
          151  +
          152  +do_index_check_test 2.1 i3 {
          153  +  {} NULL,1,1,1 
          154  +  {} 1,NULL,1,2 
          155  +  {} 1,1,NULL,3 
          156  +  {} 2,2,1,4 
          157  +  {} 2,2,1,7 
          158  +  {} 2,2,2,5
          159  +  {} 2,2,2,8 
          160  +  {} 2,2,3,6 
          161  +  {} 2,2,3,9
          162  +}
          163  +
          164  +do_index_check_test 2.2 i4 {
          165  +  {} 2,2,3,6 
          166  +  {} 2,2,3,9
          167  +  {} 2,2,2,5
          168  +  {} 2,2,2,8 
          169  +  {} 2,2,1,4 
          170  +  {} 2,2,1,7 
          171  +  {} 1,1,NULL,3 
          172  +  {} 1,NULL,1,2 
          173  +  {} NULL,1,1,1 
          174  +}
          175  +
          176  +do_index_check_test 2.3 i5 {
          177  +  {} NULL,1,1,3 
          178  +  {} 1,2,2,4 
          179  +  {} 1,2,2,7 
          180  +  {} 1,1,NULL,1 
          181  +  {} 1,NULL,1,2 
          182  +  {} 2,2,2,5 
          183  +  {} 2,2,2,8 
          184  +  {} 3,2,2,6 
          185  +  {} 3,2,2,9
    77    186   }
    78         -do_execsql_test 1.5 {
    79         -  SELECT errmsg, current_key FROM incremental_index_check('i1');
    80         -} {
    81         -  {row missing} five,5
    82         -  {} four,4
    83         -  {} one,1
    84         -  {row data mismatch} three,3
    85         -  {} two,2
          187  +
          188  +do_execsql_test 3.0 {
          189  +
          190  +  CREATE TABLE t3(w, x, y, z PRIMARY KEY) WITHOUT ROWID;
          191  +  CREATE INDEX t3wxy ON t3(w, x, y);
          192  +  CREATE INDEX t3wxy2 ON t3(w DESC, x DESC, y DESC);
          193  +
          194  +  INSERT INTO t3 VALUES(NULL, NULL, NULL, 1);
          195  +  INSERT INTO t3 VALUES(NULL, NULL, NULL, 2);
          196  +  INSERT INTO t3 VALUES(NULL, NULL, NULL, 3);
          197  +
          198  +  INSERT INTO t3 VALUES('a', NULL, NULL, 4);
          199  +  INSERT INTO t3 VALUES('a', NULL, NULL, 5);
          200  +  INSERT INTO t3 VALUES('a', NULL, NULL, 6);
          201  +
          202  +  INSERT INTO t3 VALUES('a', 'b', NULL, 7);
          203  +  INSERT INTO t3 VALUES('a', 'b', NULL, 8);
          204  +  INSERT INTO t3 VALUES('a', 'b', NULL, 9);
          205  +
    86    206   }
    87    207   
    88         -
          208  +do_index_check_test 3.1 t3wxy {
          209  +  {} NULL,NULL,NULL,1 {} NULL,NULL,NULL,2 {} NULL,NULL,NULL,3 
          210  +  {} 'a',NULL,NULL,4  {} 'a',NULL,NULL,5  {} 'a',NULL,NULL,6 
          211  +  {} 'a','b',NULL,7   {} 'a','b',NULL,8   {} 'a','b',NULL,9 
          212  +}
          213  +do_index_check_test 3.2 t3wxy2 {
          214  +  {} 'a','b',NULL,7   {} 'a','b',NULL,8   {} 'a','b',NULL,9 
          215  +  {} 'a',NULL,NULL,4  {} 'a',NULL,NULL,5  {} 'a',NULL,NULL,6 
          216  +  {} NULL,NULL,NULL,1 {} NULL,NULL,NULL,2 {} NULL,NULL,NULL,3 
          217  +}
    89    218   
    90    219   finish_test
    91    220