]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Have r-tree virtual tables support on-conflict clauses.
authordan <dan@noemail.net>
Thu, 28 Apr 2011 18:46:46 +0000 (18:46 +0000)
committerdan <dan@noemail.net>
Thu, 28 Apr 2011 18:46:46 +0000 (18:46 +0000)
FossilOrigin-Name: 822ab52f1023b1c4973c806cc75454acd4e95fd0

ext/rtree/rtree.c
ext/rtree/rtree1.test
manifest
manifest.uuid
test/fts3conf.test
test/tester.tcl

index ebf430a98c6218d3970fa3dafd977c20c8aee46b..4529f3aabb08ce0f9bce77e7fd51d751b131f6b6 100644 (file)
@@ -2626,113 +2626,119 @@ static int newRowid(Rtree *pRtree, i64 *piRowid){
 }
 
 /*
-** The xUpdate method for rtree module virtual tables.
+** Remove the entry with rowid=iDelete from the r-tree structure.
 */
-static int rtreeUpdate(
-  sqlite3_vtab *pVtab, 
-  int nData, 
-  sqlite3_value **azData, 
-  sqlite_int64 *pRowid
-){
-  Rtree *pRtree = (Rtree *)pVtab;
-  int rc = SQLITE_OK;
+static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){
+  int rc;                         /* Return code */
+  RtreeNode *pLeaf;               /* Leaf node containing record iDelete */
+  int iCell;                      /* Index of iDelete cell in pLeaf */
+  RtreeNode *pRoot;               /* Root node of rtree structure */
 
-  rtreeReference(pRtree);
 
-  assert(nData>=1);
+  /* Obtain a reference to the root node to initialise Rtree.iDepth */
+  rc = nodeAcquire(pRtree, 1, 0, &pRoot);
 
-  /* If azData[0] is not an SQL NULL value, it is the rowid of a
-  ** record to delete from the r-tree table. The following block does
-  ** just that.
+  /* Obtain a reference to the leaf node that contains the entry 
+  ** about to be deleted. 
   */
-  if( sqlite3_value_type(azData[0])!=SQLITE_NULL ){
-    i64 iDelete;                /* The rowid to delete */
-    RtreeNode *pLeaf;           /* Leaf node containing record iDelete */
-    int iCell;                  /* Index of iDelete cell in pLeaf */
-    RtreeNode *pRoot;
-
-    /* Obtain a reference to the root node to initialise Rtree.iDepth */
-    rc = nodeAcquire(pRtree, 1, 0, &pRoot);
+  if( rc==SQLITE_OK ){
+    rc = findLeafNode(pRtree, iDelete, &pLeaf);
+  }
 
-    /* Obtain a reference to the leaf node that contains the entry 
-    ** about to be deleted. 
-    */
+  /* Delete the cell in question from the leaf node. */
+  if( rc==SQLITE_OK ){
+    int rc2;
+    rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell);
     if( rc==SQLITE_OK ){
-      iDelete = sqlite3_value_int64(azData[0]);
-      rc = findLeafNode(pRtree, iDelete, &pLeaf);
+      rc = deleteCell(pRtree, pLeaf, iCell, 0);
     }
-
-    /* Delete the cell in question from the leaf node. */
+    rc2 = nodeRelease(pRtree, pLeaf);
     if( rc==SQLITE_OK ){
-      int rc2;
-      rc = nodeRowidIndex(pRtree, pLeaf, iDelete, &iCell);
-      if( rc==SQLITE_OK ){
-        rc = deleteCell(pRtree, pLeaf, iCell, 0);
-      }
-      rc2 = nodeRelease(pRtree, pLeaf);
-      if( rc==SQLITE_OK ){
-        rc = rc2;
-      }
+      rc = rc2;
     }
+  }
 
-    /* Delete the corresponding entry in the <rtree>_rowid table. */
+  /* Delete the corresponding entry in the <rtree>_rowid table. */
+  if( rc==SQLITE_OK ){
+    sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
+    sqlite3_step(pRtree->pDeleteRowid);
+    rc = sqlite3_reset(pRtree->pDeleteRowid);
+  }
+
+  /* Check if the root node now has exactly one child. If so, remove
+  ** it, schedule the contents of the child for reinsertion and 
+  ** reduce the tree height by one.
+  **
+  ** This is equivalent to copying the contents of the child into
+  ** the root node (the operation that Gutman's paper says to perform 
+  ** in this scenario).
+  */
+  if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
+    int rc2;
+    RtreeNode *pChild;
+    i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
+    rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
     if( rc==SQLITE_OK ){
-      sqlite3_bind_int64(pRtree->pDeleteRowid, 1, iDelete);
-      sqlite3_step(pRtree->pDeleteRowid);
-      rc = sqlite3_reset(pRtree->pDeleteRowid);
-    }
-
-    /* Check if the root node now has exactly one child. If so, remove
-    ** it, schedule the contents of the child for reinsertion and 
-    ** reduce the tree height by one.
-    **
-    ** This is equivalent to copying the contents of the child into
-    ** the root node (the operation that Gutman's paper says to perform 
-    ** in this scenario).
-    */
-    if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){
-      int rc2;
-      RtreeNode *pChild;
-      i64 iChild = nodeGetRowid(pRtree, pRoot, 0);
-      rc = nodeAcquire(pRtree, iChild, pRoot, &pChild);
-      if( rc==SQLITE_OK ){
-        rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
-      }
-      rc2 = nodeRelease(pRtree, pChild);
-      if( rc==SQLITE_OK ) rc = rc2;
-      if( rc==SQLITE_OK ){
-        pRtree->iDepth--;
-        writeInt16(pRoot->zData, pRtree->iDepth);
-        pRoot->isDirty = 1;
-      }
+      rc = removeNode(pRtree, pChild, pRtree->iDepth-1);
     }
-
-    /* Re-insert the contents of any underfull nodes removed from the tree. */
-    for(pLeaf=pRtree->pDeleted; pLeaf; pLeaf=pRtree->pDeleted){
-      if( rc==SQLITE_OK ){
-        rc = reinsertNodeContent(pRtree, pLeaf);
-      }
-      pRtree->pDeleted = pLeaf->pNext;
-      sqlite3_free(pLeaf);
+    rc2 = nodeRelease(pRtree, pChild);
+    if( rc==SQLITE_OK ) rc = rc2;
+    if( rc==SQLITE_OK ){
+      pRtree->iDepth--;
+      writeInt16(pRoot->zData, pRtree->iDepth);
+      pRoot->isDirty = 1;
     }
+  }
 
-    /* Release the reference to the root node. */
+  /* Re-insert the contents of any underfull nodes removed from the tree. */
+  for(pLeaf=pRtree->pDeleted; pLeaf; pLeaf=pRtree->pDeleted){
     if( rc==SQLITE_OK ){
-      rc = nodeRelease(pRtree, pRoot);
-    }else{
-      nodeRelease(pRtree, pRoot);
+      rc = reinsertNodeContent(pRtree, pLeaf);
     }
+    pRtree->pDeleted = pLeaf->pNext;
+    sqlite3_free(pLeaf);
   }
 
-  /* If the azData[] array contains more than one element, elements
-  ** (azData[2]..azData[argc-1]) contain a new record to insert into
-  ** the r-tree structure.
+  /* Release the reference to the root node. */
+  if( rc==SQLITE_OK ){
+    rc = nodeRelease(pRtree, pRoot);
+  }else{
+    nodeRelease(pRtree, pRoot);
+  }
+
+  return rc;
+}
+
+/*
+** The xUpdate method for rtree module virtual tables.
+*/
+static int rtreeUpdate(
+  sqlite3_vtab *pVtab, 
+  int nData, 
+  sqlite3_value **azData, 
+  sqlite_int64 *pRowid
+){
+  Rtree *pRtree = (Rtree *)pVtab;
+  int rc = SQLITE_OK;
+  RtreeCell cell;                 /* New cell to insert if nData>1 */
+  int bHaveRowid = 0;             /* Set to 1 after new rowid is determined */
+
+  rtreeReference(pRtree);
+  assert(nData>=1);
+
+  /* Constraint handling. A write operation on an r-tree table may return
+  ** SQLITE_CONSTRAINT for two reasons:
+  **
+  **   1. A duplicate rowid value, or
+  **   2. The supplied data violates the "x2>=x1" constraint.
+  **
+  ** In the first case, if the conflict-handling mode is REPLACE, then
+  ** the conflicting row can be removed before proceeding. In the second
+  ** case, SQLITE_CONSTRAINT must be returned regardless of the
+  ** conflict-handling mode specified by the user.
   */
-  if( rc==SQLITE_OK && nData>1 ){
-    /* Insert a new record into the r-tree */
-    RtreeCell cell;
+  if( nData>1 ){
     int ii;
-    RtreeNode *pLeaf;
 
     /* Populate the cell.aCoord[] array. The first coordinate is azData[3]. */
     assert( nData==(pRtree->nDim*2 + 3) );
@@ -2756,18 +2762,49 @@ static int rtreeUpdate(
       }
     }
 
-    /* Figure out the rowid of the new row. */
-    if( sqlite3_value_type(azData[2])==SQLITE_NULL ){
-      rc = newRowid(pRtree, &cell.iRowid);
-    }else{
+    /* If a rowid value was supplied, check if it is already present in 
+    ** the table. If so, the constraint has failed. */
+    if( sqlite3_value_type(azData[2])!=SQLITE_NULL ){
       cell.iRowid = sqlite3_value_int64(azData[2]);
-      sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
-      if( SQLITE_ROW==sqlite3_step(pRtree->pReadRowid) ){
-        sqlite3_reset(pRtree->pReadRowid);
-        rc = SQLITE_CONSTRAINT;
-        goto constraint;
+      if( sqlite3_value_type(azData[0])==SQLITE_NULL
+       || sqlite3_value_int64(azData[0])!=cell.iRowid
+      ){
+        int steprc;
+        sqlite3_bind_int64(pRtree->pReadRowid, 1, cell.iRowid);
+        steprc = sqlite3_step(pRtree->pReadRowid);
+        rc = sqlite3_reset(pRtree->pReadRowid);
+        if( SQLITE_ROW==steprc ){
+          if( sqlite3_vtab_on_conflict(pRtree->db)==SQLITE_REPLACE ){
+            rc = rtreeDeleteRowid(pRtree, cell.iRowid);
+          }else{
+            rc = SQLITE_CONSTRAINT;
+            goto constraint;
+          }
+        }
       }
-      rc = sqlite3_reset(pRtree->pReadRowid);
+      bHaveRowid = 1;
+    }
+  }
+
+  /* If azData[0] is not an SQL NULL value, it is the rowid of a
+  ** record to delete from the r-tree table. The following block does
+  ** just that.
+  */
+  if( sqlite3_value_type(azData[0])!=SQLITE_NULL ){
+    rc = rtreeDeleteRowid(pRtree, sqlite3_value_int64(azData[0]));
+  }
+
+  /* If the azData[] array contains more than one element, elements
+  ** (azData[2]..azData[argc-1]) contain a new record to insert into
+  ** the r-tree structure.
+  */
+  if( rc==SQLITE_OK && nData>1 ){
+    /* Insert the new record into the r-tree */
+    RtreeNode *pLeaf;
+
+    /* Figure out the rowid of the new row. */
+    if( bHaveRowid==0 ){
+      rc = newRowid(pRtree, &cell.iRowid);
     }
     *pRowid = cell.iRowid;
 
@@ -3008,6 +3045,8 @@ static int rtreeInit(
     return SQLITE_ERROR;
   }
 
+  sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1);
+
   /* Allocate the sqlite3_vtab structure */
   nDb = strlen(argv[1]);
   nName = strlen(argv[2]);
index fe5fa0ae50ed2bfa1dbc861c299c37e6c0a80a34..583b028507638f1c7b7c77b8093ff9ff6f5414cb 100644 (file)
@@ -31,6 +31,8 @@ source $testdir/tester.tcl
 #   rtree-7.*: Test renaming an r-tree table.
 #   rtree-8.*: Test constrained scans of r-tree data.
 #
+#   rtree-12.*: Test that on-conflict clauses are supported.
+#
 
 ifcapable !rtree {
   finish_test
@@ -416,4 +418,83 @@ do_test rtree-11.2 {
   }
 } {2}
 
+#-------------------------------------------------------------------------
+# Test on-conflict clause handling.
+#
+db_delete_and_reopen
+do_execsql_test 12.0 {
+  CREATE VIRTUAL TABLE t1 USING rtree_i32(idx, x1, x2, y1, y2);
+  INSERT INTO t1 VALUES(1,   1, 2, 3, 4);
+  INSERT INTO t1 VALUES(2,   2, 3, 4, 5);
+  INSERT INTO t1 VALUES(3,   3, 4, 5, 6);
+
+  CREATE TABLE source(idx, x1, x2, y1, y2);
+  INSERT INTO source VALUES(5, 8, 8, 8, 8);
+  INSERT INTO source VALUES(2, 7, 7, 7, 7);
+  
+}
+db_save_and_close
+foreach {tn sql_template testdata} {
+  1    "INSERT %CONF% INTO t1 VALUES(2, 7, 7, 7, 7)" {
+    ROLLBACK 0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
+    ABORT    0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    IGNORE   0 0 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    FAIL     0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    REPLACE  0 0 {1 1 2 3 4   2 7 7 7 7   3 3 4 5 6   4 4 5 6 7}
+  }
+
+  2    "INSERT %CONF% INTO t1 SELECT * FROM source" {
+    ROLLBACK 1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
+    ABORT    1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    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}
+    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}
+    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}
+  }
+
+  3    "UPDATE %CONF% t1 SET idx = 2 WHERE idx = 4" {
+    ROLLBACK 1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
+    ABORT    1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    IGNORE   1 0 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    FAIL     1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    REPLACE  1 0 {1 1 2 3 4   2 4 5 6 7   3 3 4 5 6}
+  }
+
+  3    "UPDATE %CONF% t1 SET idx = ((idx+1)%5)+1 WHERE idx > 2" {
+    ROLLBACK 1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
+    ABORT    1 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    IGNORE   1 0 {1 1 2 3 4   2 2 3 4 5               4 4 5 6 7   5 3 4 5 6}
+    FAIL     1 1 {1 1 2 3 4   2 2 3 4 5               4 4 5 6 7   5 3 4 5 6}
+    REPLACE  1 0 {1 4 5 6 7   2 2 3 4 5                           5 3 4 5 6}
+  }
+
+  4    "INSERT %CONF% INTO t1 VALUES(2, 7, 6, 7, 7)" {
+    ROLLBACK 0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6}
+    ABORT    0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    IGNORE   0 0 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    FAIL     0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+    REPLACE  0 1 {1 1 2 3 4   2 2 3 4 5   3 3 4 5 6   4 4 5 6 7}
+  }
+
+} {
+  foreach {mode uses error data} $testdata {
+    db_restore_and_reopen
+
+    set sql [string map [list %CONF% "OR $mode"] $sql_template]
+    set testname "12.$tn.[string tolower $mode]"
+
+    execsql {
+      BEGIN;
+        INSERT INTO t1 VALUES(4,   4, 5, 6, 7);
+    }
+
+    set res(0) {0 {}}
+    set res(1) {1 {constraint failed}}
+    do_catchsql_test $testname.1 $sql $res($error)
+    do_test $testname.2 [list sql_uses_stmt db $sql] $uses
+    do_execsql_test $testname.3 { SELECT * FROM t1 ORDER BY idx } $data
+
+    do_test $testname.4 { rtree_check db t1 } 0
+    db close
+  }
+}
 finish_test
index 86233879ca834ff368a7f9de851dbb733048ff34..bd7a00eb95ea41130eb75d40f06052b7c58afd5b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sdocumentation\sfor\sthe\snewly\sintroduced\ssqlite3_vtab_config()\sand\son_conflict()\sAPI\sfunctions.\sTest\sthat\sencountering\san\sSQLITE_MISMATCH\sin\sfts3\sdoes\snot\scorrupt\sthe\sfull\stext\sindex.
-D 2011-04-27T16:02:46.459
+C Have\sr-tree\svirtual\stables\ssupport\son-conflict\sclauses.
+D 2011-04-28T18:46:46.861
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 7a4d9524721d40ef9ee26f93f9bd6a51dba106f2
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -82,9 +82,9 @@ F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
 F ext/icu/icu.c eb9ae1d79046bd7871aa97ee6da51eb770134b5a
 F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
-F ext/rtree/rtree.c f5fa951eba03c41d292958064604a033021acdee
+F ext/rtree/rtree.c 829c6901a2b065ff93a68d431f9eaba8de7128e0
 F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
-F ext/rtree/rtree1.test dbd4250ac0ad367a262eb9676f7e3080b0368206
+F ext/rtree/rtree1.test 28e1b8da4da98093ce3210187434dd760a8d89d8
 F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba
 F ext/rtree/rtree3.test a494da55c30ee0bc9b01a91c80c81b387b22d2dc
 F ext/rtree/rtree4.test 0061e6f464fd3dc6a79f82454c5a1c3dadbe42af
@@ -456,7 +456,7 @@ F test/fts3aux1.test 0b02743955d56fc0d4d66236a26177bd1b726de0
 F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984
 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958
 F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c
-F test/fts3conf.test 600b366bb658842d78ed72e05476d14c2409d539
+F test/fts3conf.test 8e65ea56f88ced6cdd2252bdddb1a8327ae5af7e
 F test/fts3corrupt.test 7890cc202406858386ddf390a879dcf80bc10abf
 F test/fts3corrupt2.test 6d96efae2f8a6af3eeaf283aba437e6d0e5447ba
 F test/fts3cov.test e0fb00d8b715ddae4a94c305992dfc3ef70353d7
@@ -688,7 +688,7 @@ F test/tclsqlite.test 8c154101e704170c2be10f137a5499ac2c6da8d3
 F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
 F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
 F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
-F test/tester.tcl 6fa3d2f581b479a3a088b1b5b0d145e548ebe662
+F test/tester.tcl d5139260aadd64f318ecbcf982316d5bbc254b1b
 F test/thread001.test a3e6a7254d1cb057836cb3145b60c10bf5b7e60f
 F test/thread002.test afd20095e6e845b405df4f2c920cb93301ca69db
 F test/thread003.test b824d4f52b870ae39fc5bae4d8070eca73085dca
@@ -932,7 +932,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P ff69f823f23e6fb6e8b2857c4576d9c0732d9572
-R da0922898dc0b07ed5b043ad92b8294b
+P abdd70ae0424ccadb7edaf16e970c78b5257d23c
+R c8f4a330c2633adfb2862fa8f9efbab2
 U dan
-Z fc25e1a6e640452f4b097e45b1091129
+Z e17a424fc4dea753d2cbffe0969a3e63
index d439dc7406d17885e797d83ab7029bfbd98f0382..e2827a6e323af3f92ceb22e2dde5aa2760397ff0 100644 (file)
@@ -1 +1 @@
-abdd70ae0424ccadb7edaf16e970c78b5257d23c
\ No newline at end of file
+822ab52f1023b1c4973c806cc75454acd4e95fd0
\ No newline at end of file
index 4f5478c06405fd1f25d908ec12fc1a47d1d3a22b..ce410277cac4fe6a9183667abc4a65f5a2533021 100644 (file)
@@ -56,16 +56,6 @@ proc fts3_integrity {tn db tbl} {
   uplevel [list do_test $tn [list set {} $m1] $m2]
 }
 
-# Return true if the SQL statement passed as the second argument uses a
-# statement transaction.
-#
-proc sql_uses_stmt {db sql} {
-  set stmt [sqlite3_prepare db $sql -1 dummy]
-  set uses [uses_stmt_journal $stmt]
-  sqlite3_finalize $stmt
-  return $uses
-}
-
 do_execsql_test 1.0.1 {
   CREATE VIRTUAL TABLE t1 USING fts3(x);
   INSERT INTO t1(rowid, x) VALUES(1, 'a b c d');
index bae10530c6b30a3ed6a0d733c7523652fb26dc7e..7e15ab07d670655b0433d402a2a4024864d3b2da 100644 (file)
@@ -747,6 +747,17 @@ proc integrity_check {name {db db}} {
   }
 }
 
+
+# Return true if the SQL statement passed as the second argument uses a
+# statement transaction.
+#
+proc sql_uses_stmt {db sql} {
+  set stmt [sqlite3_prepare $db $sql -1 dummy]
+  set uses [uses_stmt_journal $stmt]
+  sqlite3_finalize $stmt
+  return $uses
+}
+
 proc fix_ifcapable_expr {expr} {
   set ret ""
   set state 0