/ Check-in [822ab52f]
Login

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

Overview
Comment:Have r-tree virtual tables support on-conflict clauses.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | vtab-conflict
Files: files | file ages | folders
SHA1: 822ab52f1023b1c4973c806cc75454acd4e95fd0
User & Date: dan 2011-04-28 18:46:46
Context
2011-05-04
12:52
Optimize "ORDER BY rowid/docid DESC/ASC" clauses on FTS tables. check-in: 13395121 user: dan tags: vtab-conflict
2011-04-28
18:46
Have r-tree virtual tables support on-conflict clauses. check-in: 822ab52f user: dan tags: vtab-conflict
2011-04-27
16:02
Add documentation for the newly introduced sqlite3_vtab_config() and on_conflict() API functions. Test that encountering an SQLITE_MISMATCH in fts3 does not corrupt the full text index. check-in: abdd70ae user: dan tags: vtab-conflict
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/rtree/rtree.c.

  2620   2620     sqlite3_bind_null(pRtree->pWriteRowid, 1);
  2621   2621     sqlite3_bind_null(pRtree->pWriteRowid, 2);
  2622   2622     sqlite3_step(pRtree->pWriteRowid);
  2623   2623     rc = sqlite3_reset(pRtree->pWriteRowid);
  2624   2624     *piRowid = sqlite3_last_insert_rowid(pRtree->db);
  2625   2625     return rc;
  2626   2626   }
         2627  +
         2628  +/*
         2629  +** Remove the entry with rowid=iDelete from the r-tree structure.
         2630  +*/
         2631  +static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
         2632  +  int rc;                         /* Return code */
         2633  +  RtreeNode *pLeaf;               /* Leaf node containing record iDelete */
         2634  +  int iCell;                      /* Index of iDelete cell in pLeaf */
         2635  +  RtreeNode *pRoot;               /* Root node of rtree structure */
         2636  +
         2637  +
         2638  +  /* Obtain a reference to the root node to initialise Rtree.iDepth */
         2639  +  rc = nodeAcquire(pRtree, 1, 0, &pRoot);
         2640  +
         2641  +  /* Obtain a reference to the leaf node that contains the entry 
         2642  +  ** about to be deleted. 
         2643  +  */
         2644  +  if( rc==SQLITE_OK ){
         2645  +    rc = findLeafNode(pRtree, iDelete, &pLeaf);
         2646  +  }
         2647  +
         2648  +  /* Delete the cell in question from the leaf node. */
         2649  +  if( rc==SQLITE_OK ){
         2650  +    int rc2;
         2651  +    rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell);
         2652  +    if( rc==SQLITE_OK ){
         2653  +      rc = deleteCell(pRtree, pLeaf, iCell, 0);
         2654  +    }
         2655  +    rc2 = nodeRelease(pRtree, pLeaf);
         2656  +    if( rc==SQLITE_OK ){
         2657  +      rc = rc2;
         2658  +    }
         2659  +  }
         2660  +
         2661  +  /* Delete the corresponding entry in the <rtree>_rowid table. */
         2662  +  if( rc==SQLITE_OK ){
         2663  +    sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
         2664  +    sqlite3_step(pRtree->pDeleteRowid);
         2665  +    rc = sqlite3_reset(pRtree->pDeleteRowid);
         2666  +  }
         2667  +
         2668  +  /* Check if the root node now has exactly one child. If so, remove
         2669  +  ** it, schedule the contents of the child for reinsertion and 
         2670  +  ** reduce the tree height by one.
         2671  +  **
         2672  +  ** This is equivalent to copying the contents of the child into
         2673  +  ** the root node (the operation that Gutman's paper says to perform 
         2674  +  ** in this scenario).
         2675  +  */
         2676  +  if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
         2677  +    int rc2;
         2678  +    RtreeNode *pChild;
         2679  +    i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
         2680  +    rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
         2681  +    if( rc==SQLITE_OK ){
         2682  +      rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
         2683  +    }
         2684  +    rc2 = nodeRelease(pRtree, pChild);
         2685  +    if( rc==SQLITE_OK ) rc = rc2;
         2686  +    if( rc==SQLITE_OK ){
         2687  +      pRtree->iDepth--;
         2688  +      writeInt16(pRoot->zData, pRtree->iDepth);
         2689  +      pRoot->isDirty = 1;
         2690  +    }
         2691  +  }
         2692  +
         2693  +  /* Re-insert the contents of any underfull nodes removed from the tree. */
         2694  +  for(pLeaf=pRtree->pDeleted; pLeaf; pLeaf=pRtree->pDeleted){
         2695  +    if( rc==SQLITE_OK ){
         2696  +      rc = reinsertNodeContent(pRtree, pLeaf);
         2697  +    }
         2698  +    pRtree->pDeleted = pLeaf->pNext;
         2699  +    sqlite3_free(pLeaf);
         2700  +  }
         2701  +
         2702  +  /* Release the reference to the root node. */
         2703  +  if( rc==SQLITE_OK ){
         2704  +    rc = nodeRelease(pRtree, pRoot);
         2705  +  }else{
         2706  +    nodeRelease(pRtree, pRoot);
         2707  +  }
         2708  +
         2709  +  return rc;
         2710  +}
  2627   2711   
  2628   2712   /*
  2629   2713   ** The xUpdate method for rtree module virtual tables.
  2630   2714   */
  2631   2715   static int rtreeUpdate(
  2632   2716     sqlite3_vtab *pVtab, 
  2633   2717     int nData, 
  2634   2718     sqlite3_value **azData, 
  2635   2719     sqlite_int64 *pRowid
  2636   2720   ){
  2637   2721     Rtree *pRtree = (Rtree *)pVtab;
  2638   2722     int rc = SQLITE_OK;
         2723  +  RtreeCell cell;                 /* New cell to insert if nData>1 */
         2724  +  int bHaveRowid = 0;             /* Set to 1 after new rowid is determined */
  2639   2725   
  2640   2726     rtreeReference(pRtree);
  2641         -
  2642   2727     assert(nData>=1);
  2643   2728   
  2644         -  /* If azData[0] is not an SQL NULL value, it is the rowid of a
  2645         -  ** record to delete from the r-tree table. The following block does
  2646         -  ** just that.
         2729  +  /* Constraint handling. A write operation on an r-tree table may return
         2730  +  ** SQLITE_CONSTRAINT for two reasons:
         2731  +  **
         2732  +  **   1. A duplicate rowid value, or
         2733  +  **   2. The supplied data violates the "x2>=x1" constraint.
         2734  +  **
         2735  +  ** In the first case, if the conflict-handling mode is REPLACE, then
         2736  +  ** the conflicting row can be removed before proceeding. In the second
         2737  +  ** case, SQLITE_CONSTRAINT must be returned regardless of the
         2738  +  ** conflict-handling mode specified by the user.
  2647   2739     */
  2648         -  if( sqlite3_value_type(azData[0])!=SQLITE_NULL ){
  2649         -    i64 iDelete;                /* The rowid to delete */
  2650         -    RtreeNode *pLeaf;           /* Leaf node containing record iDelete */
  2651         -    int iCell;                  /* Index of iDelete cell in pLeaf */
  2652         -    RtreeNode *pRoot;
  2653         -
  2654         -    /* Obtain a reference to the root node to initialise Rtree.iDepth */
  2655         -    rc = nodeAcquire(pRtree, 1, 0, &pRoot);
  2656         -
  2657         -    /* Obtain a reference to the leaf node that contains the entry 
  2658         -    ** about to be deleted. 
  2659         -    */
  2660         -    if( rc==SQLITE_OK ){
  2661         -      iDelete = sqlite3_value_int64(azData[0]);
  2662         -      rc = findLeafNode(pRtree, iDelete, &pLeaf);
  2663         -    }
  2664         -
  2665         -    /* Delete the cell in question from the leaf node. */
  2666         -    if( rc==SQLITE_OK ){
  2667         -      int rc2;
  2668         -      rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell);
  2669         -      if( rc==SQLITE_OK ){
  2670         -        rc = deleteCell(pRtree, pLeaf, iCell, 0);
  2671         -      }
  2672         -      rc2 = nodeRelease(pRtree, pLeaf);
  2673         -      if( rc==SQLITE_OK ){
  2674         -        rc = rc2;
  2675         -      }
  2676         -    }
  2677         -
  2678         -    /* Delete the corresponding entry in the <rtree>_rowid table. */
  2679         -    if( rc==SQLITE_OK ){
  2680         -      sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
  2681         -      sqlite3_step(pRtree->pDeleteRowid);
  2682         -      rc = sqlite3_reset(pRtree->pDeleteRowid);
  2683         -    }
  2684         -
  2685         -    /* Check if the root node now has exactly one child. If so, remove
  2686         -    ** it, schedule the contents of the child for reinsertion and 
  2687         -    ** reduce the tree height by one.
  2688         -    **
  2689         -    ** This is equivalent to copying the contents of the child into
  2690         -    ** the root node (the operation that Gutman's paper says to perform 
  2691         -    ** in this scenario).
  2692         -    */
  2693         -    if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
  2694         -      int rc2;
  2695         -      RtreeNode *pChild;
  2696         -      i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
  2697         -      rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
  2698         -      if( rc==SQLITE_OK ){
  2699         -        rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
  2700         -      }
  2701         -      rc2 = nodeRelease(pRtree, pChild);
  2702         -      if( rc==SQLITE_OK ) rc = rc2;
  2703         -      if( rc==SQLITE_OK ){
  2704         -        pRtree->iDepth--;
  2705         -        writeInt16(pRoot->zData, pRtree->iDepth);
  2706         -        pRoot->isDirty = 1;
  2707         -      }
  2708         -    }
  2709         -
  2710         -    /* Re-insert the contents of any underfull nodes removed from the tree. */
  2711         -    for(pLeaf=pRtree->pDeleted; pLeaf; pLeaf=pRtree->pDeleted){
  2712         -      if( rc==SQLITE_OK ){
  2713         -        rc = reinsertNodeContent(pRtree, pLeaf);
  2714         -      }
  2715         -      pRtree->pDeleted = pLeaf->pNext;
  2716         -      sqlite3_free(pLeaf);
  2717         -    }
  2718         -
  2719         -    /* Release the reference to the root node. */
  2720         -    if( rc==SQLITE_OK ){
  2721         -      rc = nodeRelease(pRtree, pRoot);
  2722         -    }else{
  2723         -      nodeRelease(pRtree, pRoot);
  2724         -    }
  2725         -  }
  2726         -
  2727         -  /* If the azData[] array contains more than one element, elements
  2728         -  ** (azData[2]..azData[argc-1]) contain a new record to insert into
  2729         -  ** the r-tree structure.
  2730         -  */
  2731         -  if( rc==SQLITE_OK && nData>1 ){
  2732         -    /* Insert a new record into the r-tree */
  2733         -    RtreeCell cell;
         2740  +  if( nData>1 ){
  2734   2741       int ii;
  2735         -    RtreeNode *pLeaf;
  2736   2742   
  2737   2743       /* Populate the cell.aCoord[] array. The first coordinate is azData[3]. */
  2738   2744       assert( nData==(pRtree->nDim*2 + 3) );
  2739   2745       if( pRtree->eCoordType==RTREE_COORD_REAL32 ){
  2740   2746         for(ii=0; ii<(pRtree->nDim*2); ii+=2){
  2741   2747           cell.aCoord[ii].f = (float)sqlite3_value_double(azData[ii+3]);
  2742   2748           cell.aCoord[ii+1].f = (float)sqlite3_value_double(azData[ii+4]);
................................................................................
  2752   2758           if( cell.aCoord[ii].i>cell.aCoord[ii+1].i ){
  2753   2759             rc = SQLITE_CONSTRAINT;
  2754   2760             goto constraint;
  2755   2761           }
  2756   2762         }
  2757   2763       }
  2758   2764   
  2759         -    /* Figure out the rowid of the new row. */
  2760         -    if( sqlite3_value_type(azData[2])==SQLITE_NULL ){
  2761         -      rc = newRowid(pRtree, &cell.iRowid);
  2762         -    }else{
         2765  +    /* If a rowid value was supplied, check if it is already present in 
         2766  +    ** the table. If so, the constraint has failed. */
         2767  +    if( sqlite3_value_type(azData[2])!=SQLITE_NULL ){
  2763   2768         cell.iRowid = sqlite3_value_int64(azData[2]);
  2764         -      sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
  2765         -      if( SQLITE_ROW==sqlite3_step(pRtree->pReadRowid) ){
  2766         -        sqlite3_reset(pRtree->pReadRowid);
  2767         -        rc = SQLITE_CONSTRAINT;
  2768         -        goto constraint;
         2769  +      if( sqlite3_value_type(azData[0])==SQLITE_NULL
         2770  +       || sqlite3_value_int64(azData[0])!=cell.iRowid
         2771  +      ){
         2772  +        int steprc;
         2773  +        sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
         2774  +        steprc = sqlite3_step(pRtree->pReadRowid);
         2775  +        rc = sqlite3_reset(pRtree->pReadRowid);
         2776  +        if( SQLITE_ROW==steprc ){
         2777  +          if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
         2778  +            rc = rtreeDeleteRowid(pRtree, cell.iRowid);
         2779  +          }else{
         2780  +            rc = SQLITE_CONSTRAINT;
         2781  +            goto constraint;
         2782  +          }
         2783  +        }
  2769   2784         }
  2770         -      rc = sqlite3_reset(pRtree->pReadRowid);
         2785  +      bHaveRowid = 1;
         2786  +    }
         2787  +  }
         2788  +
         2789  +  /* If azData[0] is not an SQL NULL value, it is the rowid of a
         2790  +  ** record to delete from the r-tree table. The following block does
         2791  +  ** just that.
         2792  +  */
         2793  +  if( sqlite3_value_type(azData[0])!=SQLITE_NULL ){
         2794  +    rc = rtreeDeleteRowid(pRtree, sqlite3_value_int64(azData[0]));
         2795  +  }
         2796  +
         2797  +  /* If the azData[] array contains more than one element, elements
         2798  +  ** (azData[2]..azData[argc-1]) contain a new record to insert into
         2799  +  ** the r-tree structure.
         2800  +  */
         2801  +  if( rc==SQLITE_OK && nData>1 ){
         2802  +    /* Insert the new record into the r-tree */
         2803  +    RtreeNode *pLeaf;
         2804  +
         2805  +    /* Figure out the rowid of the new row. */
         2806  +    if( bHaveRowid==0 ){
         2807  +      rc = newRowid(pRtree, &cell.iRowid);
  2771   2808       }
  2772   2809       *pRowid = cell.iRowid;
  2773   2810   
  2774   2811       if( rc==SQLITE_OK ){
  2775   2812         rc = ChooseLeaf(pRtree, &cell, 0, &pLeaf);
  2776   2813       }
  2777   2814       if( rc==SQLITE_OK ){
................................................................................
  3003   3040     };
  3004   3041   
  3005   3042     int iErr = (argc<6) ? 2 : argc>(RTREE_MAX_DIMENSIONS*2+4) ? 3 : argc%2;
  3006   3043     if( aErrMsg[iErr] ){
  3007   3044       *pzErr = sqlite3_mprintf("%s", aErrMsg[iErr]);
  3008   3045       return SQLITE_ERROR;
  3009   3046     }
         3047  +
         3048  +  sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
  3010   3049   
  3011   3050     /* Allocate the sqlite3_vtab structure */
  3012   3051     nDb = strlen(argv[1]);
  3013   3052     nName = strlen(argv[2]);
  3014   3053     pRtree = (Rtree *)sqlite3_malloc(sizeof(Rtree)+nDb+nName+2);
  3015   3054     if( !pRtree ){
  3016   3055       return SQLITE_NOMEM;

Changes to ext/rtree/rtree1.test.

    27     27   #   rtree-3.*: Linear scans of r-tree data.
    28     28   #   rtree-4.*: Test INSERT
    29     29   #   rtree-5.*: Test DELETE
    30     30   #   rtree-6.*: Test UPDATE
    31     31   #   rtree-7.*: Test renaming an r-tree table.
    32     32   #   rtree-8.*: Test constrained scans of r-tree data.
    33     33   #
           34  +#   rtree-12.*: Test that on-conflict clauses are supported.
           35  +#
    34     36   
    35     37   ifcapable !rtree {
    36     38     finish_test
    37     39     return
    38     40   }
    39     41   
    40     42   #----------------------------------------------------------------------------
................................................................................
   412    414   do_test rtree-11.2 {
   413    415     execsql {
   414    416       INSERT INTO t8 VALUES(NULL, 1.0, 1.0, 2.0, 2.0);
   415    417       SELECT last_insert_rowid();
   416    418     }
   417    419   } {2}
   418    420   
          421  +#-------------------------------------------------------------------------
          422  +# Test on-conflict clause handling.
          423  +#
          424  +db_delete_and_reopen
          425  +do_execsql_test 12.0 {
          426  +  CREATE VIRTUAL TABLE t1 USING rtree_i32(idx, x1, x2, y1, y2);
          427  +  INSERT INTO t1 VALUES(1,   1, 2, 3, 4);
          428  +  INSERT INTO t1 VALUES(2,   2, 3, 4, 5);
          429  +  INSERT INTO t1 VALUES(3,   3, 4, 5, 6);
          430  +
          431  +  CREATE TABLE source(idx, x1, x2, y1, y2);
          432  +  INSERT INTO source VALUES(5, 8, 8, 8, 8);
          433  +  INSERT INTO source VALUES(2, 7, 7, 7, 7);
          434  +  
          435  +}
          436  +db_save_and_close
          437  +foreach {tn sql_template testdata} {
          438  +  1    "INSERT %CONF% INTO t1 VALUES(2, 7, 7, 7, 7)" {
          439  +    ROLLBACK 0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
          440  +    ABORT    0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          441  +    IGNORE   0 0 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          442  +    FAIL     0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          443  +    REPLACE  0 0 {1 1 2 3 4   2 7 7 7 7   3 3 4 5 6   4 4 5 6 7}
          444  +  }
          445  +
          446  +  2    "INSERT %CONF% INTO t1 SELECT * FROM source" {
          447  +    ROLLBACK 1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
          448  +    ABORT    1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          449  +    IGNORE   1 0 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7  5 8 8 8 8}
          450  +    FAIL     1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7  5 8 8 8 8}
          451  +    REPLACE  1 0 {1 1 2 3 4   2 7 7 7 7   3 3 4 5 6   4 4 5 6 7  5 8 8 8 8}
          452  +  }
          453  +
          454  +  3    "UPDATE %CONF% t1 SET idx = 2 WHERE idx = 4" {
          455  +    ROLLBACK 1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
          456  +    ABORT    1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          457  +    IGNORE   1 0 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          458  +    FAIL     1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          459  +    REPLACE  1 0 {1 1 2 3 4   2 4 5 6 7   3 3 4 5 6}
          460  +  }
          461  +
          462  +  3    "UPDATE %CONF% t1 SET idx = ((idx+1)%5)+1 WHERE idx > 2" {
          463  +    ROLLBACK 1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
          464  +    ABORT    1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          465  +    IGNORE   1 0 {1 1 2 3 4   2 2 3 4 5               4 4 5 6 7   5 3 4 5 6}
          466  +    FAIL     1 1 {1 1 2 3 4   2 2 3 4 5               4 4 5 6 7   5 3 4 5 6}
          467  +    REPLACE  1 0 {1 4 5 6 7   2 2 3 4 5                           5 3 4 5 6}
          468  +  }
          469  +
          470  +  4    "INSERT %CONF% INTO t1 VALUES(2, 7, 6, 7, 7)" {
          471  +    ROLLBACK 0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
          472  +    ABORT    0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          473  +    IGNORE   0 0 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          474  +    FAIL     0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          475  +    REPLACE  0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
          476  +  }
          477  +
          478  +} {
          479  +  foreach {mode uses error data} $testdata {
          480  +    db_restore_and_reopen
          481  +
          482  +    set sql [string map [list %CONF% "OR $mode"] $sql_template]
          483  +    set testname "12.$tn.[string tolower $mode]"
          484  +
          485  +    execsql {
          486  +      BEGIN;
          487  +        INSERT INTO t1 VALUES(4,   4, 5, 6, 7);
          488  +    }
          489  +
          490  +    set res(0) {0 {}}
          491  +    set res(1) {1 {constraint failed}}
          492  +    do_catchsql_test $testname.1 $sql $res($error)
          493  +    do_test $testname.2 [list sql_uses_stmt db $sql] $uses
          494  +    do_execsql_test $testname.3 { SELECT * FROM t1 ORDER BY idx } $data
          495  +
          496  +    do_test $testname.4 { rtree_check db t1 } 0
          497  +    db close
          498  +  }
          499  +}
   419    500   finish_test

Changes to test/fts3conf.test.

    52     52       DROP TABLE temp.fts3check2;
    53     53       DROP TABLE temp.fts3check3;
    54     54     }
    55     55     
    56     56     uplevel [list do_test $tn [list set {} $m1] $m2]
    57     57   }
    58     58   
    59         -# Return true if the SQL statement passed as the second argument uses a
    60         -# statement transaction.
    61         -#
    62         -proc sql_uses_stmt {db sql} {
    63         -  set stmt [sqlite3_prepare db $sql -1 dummy]
    64         -  set uses [uses_stmt_journal $stmt]
    65         -  sqlite3_finalize $stmt
    66         -  return $uses
    67         -}
    68         -
    69     59   do_execsql_test 1.0.1 {
    70     60     CREATE VIRTUAL TABLE t1 USING fts3(x);
    71     61     INSERT INTO t1(rowid, x) VALUES(1, 'a b c d');
    72     62     INSERT INTO t1(rowid, x) VALUES(2, 'e f g h');
    73     63   
    74     64     CREATE TABLE source(a, b);
    75     65     INSERT INTO source VALUES(4, 'z');

Changes to test/tester.tcl.

   743    743   #
   744    744   proc integrity_check {name {db db}} {
   745    745     ifcapable integrityck {
   746    746       do_test $name [list execsql {PRAGMA integrity_check} $db] {ok}
   747    747     }
   748    748   }
   749    749   
          750  +
          751  +# Return true if the SQL statement passed as the second argument uses a
          752  +# statement transaction.
          753  +#
          754  +proc sql_uses_stmt {db sql} {
          755  +  set stmt [sqlite3_prepare $db $sql -1 dummy]
          756  +  set uses [uses_stmt_journal $stmt]
          757  +  sqlite3_finalize $stmt
          758  +  return $uses
          759  +}
          760  +
   750    761   proc fix_ifcapable_expr {expr} {
   751    762     set ret ""
   752    763     set state 0
   753    764     for {set i 0} {$i < [string length $expr]} {incr i} {
   754    765       set char [string range $expr $i $i]
   755    766       set newstate [expr {[string is alnum $char] || $char eq "_"}]
   756    767       if {$newstate && !$state} {