SQLite

Check-in [bbc385111b]
Login

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

Overview
Comment:Rename the sqlite3_log_hook() to sqlite3_wal_hook(). Added comments to wal.h.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | wal
Files: files | file ages | folders
SHA1: bbc385111b19071e20fe963fab814262c815b3e9
User & Date: drh 2010-04-28 14:42:19.000
Context
2010-04-28
17:21
Changes to the interface design for the xShmLock method of the VFS. (check-in: 348409de26 user: drh tags: wal)
14:42
Rename the sqlite3_log_hook() to sqlite3_wal_hook(). Added comments to wal.h. (check-in: bbc385111b user: drh tags: wal)
2010-04-27
18:49
Merge two "wal" leaves. (check-in: 8c2d43babd user: dan tags: wal)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/main.c.
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
  return pRet;
}

/*
** Register a callback to be invoked each time a transaction is written
** into the write-ahead-log by this database connection.
*/
void *sqlite3_log_hook(
  sqlite3 *db,                    /* Attach the hook to this db handle */
  int(*xCallback)(void *, sqlite3*, const char*, int),
  void *pArg                      /* First argument passed to xCallback() */
){
  void *pRet;
  sqlite3_mutex_enter(db->mutex);
  pRet = db->pLogArg;







|







1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
  return pRet;
}

/*
** Register a callback to be invoked each time a transaction is written
** into the write-ahead-log by this database connection.
*/
void *sqlite3_wal_hook(
  sqlite3 *db,                    /* Attach the hook to this db handle */
  int(*xCallback)(void *, sqlite3*, const char*, int),
  void *pArg                      /* First argument passed to xCallback() */
){
  void *pRet;
  sqlite3_mutex_enter(db->mutex);
  pRet = db->pLogArg;
Changes to src/sqlite.h.in.
5757
5758
5759
5760
5761
5762
5763
5764
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783
5784
5785
5786
5787
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
** a fixed-length buffer on the stack.  If the log message is longer than
** a few hundred characters, it will be truncated to the length of the
** buffer.
*/
void sqlite3_log(int iErrCode, const char *zFormat, ...);

/*
** Experimental WAL callback interface.
**
** The [sqlite3_log_hook()] function is used to register a callback that
** will be invoked each time a database connection commits data to a
** write-ahead-log (i.e. whenever a transaction is committed in
** journal_mode=WAL mode). 
**
** The callback is invoked by SQLite after the commit has taken place and 
** the associated write-lock on the database released, so the implementation 
** may read, write or checkpoint the database as required.
**
** The first parameter passed to the callback function when it is invoked
** is a copy of the third parameter passed to sqlite3_log_hook() when
** registering the callback. The second is a copy of the database handle.
** The third parameter is the name of the database that was written to -
** either "main" or the name of an ATTACHed database. The fourth parameter
** is the number of pages currently in the log file, including those that
** were just committed.
**
** If an invocation of the callback function returns non-zero, then a
** checkpoint is automatically run on the database. If zero is returned,
** no special action is taken.
**
** A single database handle may have at most a single log callback 
** registered at one time. Calling [sqlite3_log_hook()] replaces any
** previously registered log callback. 
*/
void *sqlite3_log_hook(
  sqlite3*, 
  int(*)(void *,sqlite3*,const char*,int),
  void*
);

/*
** Undo the hack that converts floating point types to integer for







|

|









|











|


|







5757
5758
5759
5760
5761
5762
5763
5764
5765
5766
5767
5768
5769
5770
5771
5772
5773
5774
5775
5776
5777
5778
5779
5780
5781
5782
5783
5784
5785
5786
5787
5788
5789
5790
5791
5792
5793
5794
5795
5796
5797
5798
** a fixed-length buffer on the stack.  If the log message is longer than
** a few hundred characters, it will be truncated to the length of the
** buffer.
*/
void sqlite3_log(int iErrCode, const char *zFormat, ...);

/*
** CAPI3REF: Write-Ahead Log Commit Hook
**
** The [sqlite3_wal_hook()] function is used to register a callback that
** will be invoked each time a database connection commits data to a
** write-ahead-log (i.e. whenever a transaction is committed in
** journal_mode=WAL mode). 
**
** The callback is invoked by SQLite after the commit has taken place and 
** the associated write-lock on the database released, so the implementation 
** may read, write or checkpoint the database as required.
**
** The first parameter passed to the callback function when it is invoked
** is a copy of the third parameter passed to sqlite3_wal_hook() when
** registering the callback. The second is a copy of the database handle.
** The third parameter is the name of the database that was written to -
** either "main" or the name of an ATTACHed database. The fourth parameter
** is the number of pages currently in the log file, including those that
** were just committed.
**
** If an invocation of the callback function returns non-zero, then a
** checkpoint is automatically run on the database. If zero is returned,
** no special action is taken.
**
** A single database handle may have at most a single log callback 
** registered at one time. Calling [sqlite3_wal_hook()] replaces any
** previously registered log callback. 
*/
void *sqlite3_wal_hook(
  sqlite3*, 
  int(*)(void *,sqlite3*,const char*,int),
  void*
);

/*
** Undo the hack that converts floating point types to integer for
Changes to src/tclsqlite.c.
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
  static const char *DB_strs[] = {
    "authorizer",         "backup",            "busy",
    "cache",              "changes",           "close",
    "collate",            "collation_needed",  "commit_hook",
    "complete",           "copy",              "enable_load_extension",
    "errorcode",          "eval",              "exists",
    "function",           "incrblob",          "interrupt",
    "last_insert_rowid",  "log_hook",          "nullvalue",
    "onecolumn",          "profile",           "progress",
    "rekey",              "restore",           "rollback_hook",     
    "status",             "timeout",           "total_changes",     
    "trace",              "transaction",       "unlock_notify",     
    "update_hook",        "version",            0                    
  };
  enum DB_enum {
    DB_AUTHORIZER,        DB_BACKUP,           DB_BUSY,
    DB_CACHE,             DB_CHANGES,          DB_CLOSE,
    DB_COLLATE,           DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
    DB_COMPLETE,          DB_COPY,             DB_ENABLE_LOAD_EXTENSION,
    DB_ERRORCODE,         DB_EVAL,             DB_EXISTS,
    DB_FUNCTION,          DB_INCRBLOB,         DB_INTERRUPT,
    DB_LAST_INSERT_ROWID, DB_LOG_HOOK,         DB_NULLVALUE,
    DB_ONECOLUMN,         DB_PROFILE,          DB_PROGRESS,
    DB_REKEY,             DB_RESTORE,          DB_ROLLBACK_HOOK,    
    DB_STATUS,            DB_TIMEOUT,          DB_TOTAL_CHANGES,    
    DB_TRACE,             DB_TRANSACTION,      DB_UNLOCK_NOTIFY,    
    DB_UPDATE_HOOK,       DB_VERSION
  };
  /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
    return TCL_ERROR;
  }







|
|
|
|
|
|








|
|
|
|
|
|







1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
  static const char *DB_strs[] = {
    "authorizer",         "backup",            "busy",
    "cache",              "changes",           "close",
    "collate",            "collation_needed",  "commit_hook",
    "complete",           "copy",              "enable_load_extension",
    "errorcode",          "eval",              "exists",
    "function",           "incrblob",          "interrupt",
    "last_insert_rowid",  "nullvalue",         "onecolumn",
    "profile",            "progress",          "rekey",
    "restore",            "rollback_hook",     "status",
    "timeout",            "total_changes",     "trace",
    "transaction",        "unlock_notify",     "update_hook",
    "version",            "wal_hook",          0
  };
  enum DB_enum {
    DB_AUTHORIZER,        DB_BACKUP,           DB_BUSY,
    DB_CACHE,             DB_CHANGES,          DB_CLOSE,
    DB_COLLATE,           DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
    DB_COMPLETE,          DB_COPY,             DB_ENABLE_LOAD_EXTENSION,
    DB_ERRORCODE,         DB_EVAL,             DB_EXISTS,
    DB_FUNCTION,          DB_INCRBLOB,         DB_INTERRUPT,
    DB_LAST_INSERT_ROWID, DB_NULLVALUE,        DB_ONECOLUMN,
    DB_PROFILE,           DB_PROGRESS,         DB_REKEY,
    DB_RESTORE,           DB_ROLLBACK_HOOK,    DB_STATUS,
    DB_TIMEOUT,           DB_TOTAL_CHANGES,    DB_TRACE,
    DB_TRANSACTION,       DB_UNLOCK_NOTIFY,    DB_UPDATE_HOOK,
    DB_VERSION,           DB_WAL_HOOK
  };
  /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
    return TCL_ERROR;
  }
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
      }
    }
#endif
    break;
  }

  /*
  **    $db log_hook ?script?
  **    $db update_hook ?script?
  **    $db rollback_hook ?script?
  */
  case DB_LOG_HOOK: 
  case DB_UPDATE_HOOK: 
  case DB_ROLLBACK_HOOK: {

    /* set ppHook to point at pUpdateHook or pRollbackHook, depending on 
    ** whether [$db update_hook] or [$db rollback_hook] was invoked.
    */
    Tcl_Obj **ppHook; 
    if( choice==DB_UPDATE_HOOK ){
      ppHook = &pDb->pUpdateHook;
    }else if( choice==DB_LOG_HOOK ){
      ppHook = &pDb->pLogHook;
    }else{
      ppHook = &pDb->pRollbackHook;
    }

    if( objc!=2 && objc!=3 ){
       Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");







|



|









|







2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
      }
    }
#endif
    break;
  }

  /*
  **    $db wal_hook ?script?
  **    $db update_hook ?script?
  **    $db rollback_hook ?script?
  */
  case DB_WAL_HOOK: 
  case DB_UPDATE_HOOK: 
  case DB_ROLLBACK_HOOK: {

    /* set ppHook to point at pUpdateHook or pRollbackHook, depending on 
    ** whether [$db update_hook] or [$db rollback_hook] was invoked.
    */
    Tcl_Obj **ppHook; 
    if( choice==DB_UPDATE_HOOK ){
      ppHook = &pDb->pUpdateHook;
    }else if( choice==DB_WAL_HOOK ){
      ppHook = &pDb->pLogHook;
    }else{
      ppHook = &pDb->pRollbackHook;
    }

    if( objc!=2 && objc!=3 ){
       Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
        *ppHook = objv[2];
        Tcl_IncrRefCount(*ppHook);
      }
    }

    sqlite3_update_hook(pDb->db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
    sqlite3_rollback_hook(pDb->db,(pDb->pRollbackHook?DbRollbackHandler:0),pDb);
    sqlite3_log_hook(pDb->db,(pDb->pLogHook?DbLogHandler:0),pDb);

    break;
  }

  /*    $db version
  **
  ** Return the version string for this database.







|







2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
        *ppHook = objv[2];
        Tcl_IncrRefCount(*ppHook);
      }
    }

    sqlite3_update_hook(pDb->db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
    sqlite3_rollback_hook(pDb->db,(pDb->pRollbackHook?DbRollbackHandler:0),pDb);
    sqlite3_wal_hook(pDb->db,(pDb->pLogHook?DbLogHandler:0),pDb);

    break;
  }

  /*    $db version
  **
  ** Return the version string for this database.
Changes to src/wal.h.
15
16
17
18
19
20
21

22

23
24
25
26
27
28
29






30
31
32
33
34



35
36
37
38
39
40
41
42


43



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62




63
64
65
*/

#ifndef _WAL_H_
#define _WAL_H_

#include "sqliteInt.h"


/* Connection to a log file. There is one object of this type for each pager. */

typedef struct Log Log;

/* Open and close a connection to a log file. */
int sqlite3WalOpen(sqlite3_vfs*, const char *zDb, Log **ppLog);
int sqlite3WalClose(Log *pLog, sqlite3_file *pFd, int sync_flags, u8 *zBuf);

/* Used by readers to open (lock) and close (unlock) a snapshot. */






int sqlite3WalOpenSnapshot(Log *pLog, int *);
void sqlite3WalCloseSnapshot(Log *pLog);

/* Read a page from the log, if it is present. */
int sqlite3WalRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut);



void sqlite3WalDbsize(Log *pLog, Pgno *pPgno);

/* Obtain or release the WRITER lock. */
int sqlite3WalWriteLock(Log *pLog, int op);

/* Undo any frames written (but not committed) to the log */
int sqlite3WalUndo(Log *pLog, int (*xUndo)(void *, Pgno), void *pUndoCtx);



u32 sqlite3WalSavepoint(Log *pLog);



int sqlite3WalSavepointUndo(Log *pLog, u32 iFrame);

/* Return true if data has been written but not committed to the log file. */
int sqlite3WalDirty(Log *pLog);

/* Write a frame or frames to the log. */
int sqlite3WalFrames(Log *pLog, int, PgHdr *, Pgno, int, int);

/* Copy pages from the log to the database file */ 
int sqlite3WalCheckpoint(
  Log *pLog,                      /* Log connection */
  sqlite3_file *pFd,              /* File descriptor open on db file */
  int sync_flags,                 /* Flags to sync db file with (or 0) */
  u8 *zBuf,                       /* Temporary buffer to use */
  int (*xBusyHandler)(void *),    /* Pointer to busy-handler function */
  void *pBusyHandlerArg           /* Argument to pass to xBusyHandler */
);

/* Return the value to pass to a log callback. Or 0 for no callback. */




int sqlite3WalCallback(Log *pLog);

#endif /* _WAL_H_ */







>
|
>


|



|
>
>
>
>
>
>



|

>
>
>








>
>

>
>
>


















|
>
>
>
>



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
*/

#ifndef _WAL_H_
#define _WAL_H_

#include "sqliteInt.h"

/* Connection to a write-ahead log (WAL) file. 
** There is one object of this type for each pager. 
*/
typedef struct Log Log;

/* Open and close a connection to a write-ahead log. */
int sqlite3WalOpen(sqlite3_vfs*, const char *zDb, Log **ppLog);
int sqlite3WalClose(Log *pLog, sqlite3_file *pFd, int sync_flags, u8 *zBuf);

/* Used by readers to open (lock) and close (unlock) a snapshot.  A 
** snapshot is like a read-transaction.  It is the state of the database
** at an instant in time.  sqlite3WalOpenSnapshot gets a read lock and
** preserves the current state even if the other threads or processes
** write to or checkpoint the WAL.  sqlite3WalCloseSnapshot() closes the
** transaction and releases the lock.
*/
int sqlite3WalOpenSnapshot(Log *pLog, int *);
void sqlite3WalCloseSnapshot(Log *pLog);

/* Read a page from the write-ahead log, if it is present. */
int sqlite3WalRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut);

/* Return the size of the database as it existed at the beginning
** of the snapshot */
void sqlite3WalDbsize(Log *pLog, Pgno *pPgno);

/* Obtain or release the WRITER lock. */
int sqlite3WalWriteLock(Log *pLog, int op);

/* Undo any frames written (but not committed) to the log */
int sqlite3WalUndo(Log *pLog, int (*xUndo)(void *, Pgno), void *pUndoCtx);

/* Return an integer that records the current (uncommitted) write
** position in the WAL */
u32 sqlite3WalSavepoint(Log *pLog);

/* Move the write position of the WAL back to iFrame.  Called in
** response to a ROLLBACK TO command. */
int sqlite3WalSavepointUndo(Log *pLog, u32 iFrame);

/* Return true if data has been written but not committed to the log file. */
int sqlite3WalDirty(Log *pLog);

/* Write a frame or frames to the log. */
int sqlite3WalFrames(Log *pLog, int, PgHdr *, Pgno, int, int);

/* Copy pages from the log to the database file */ 
int sqlite3WalCheckpoint(
  Log *pLog,                      /* Log connection */
  sqlite3_file *pFd,              /* File descriptor open on db file */
  int sync_flags,                 /* Flags to sync db file with (or 0) */
  u8 *zBuf,                       /* Temporary buffer to use */
  int (*xBusyHandler)(void *),    /* Pointer to busy-handler function */
  void *pBusyHandlerArg           /* Argument to pass to xBusyHandler */
);

/* Return the value to pass to a sqlite3_wal_hook callback, the
** number of frames in the WAL at the point of the last commit since
** sqlite3WalCallback() was called.  If no commits have occurred since
** the last call, then return 0.
*/
int sqlite3WalCallback(Log *pLog);

#endif /* _WAL_H_ */
Changes to test/tclsqlite.test.
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  set v [catch {sqlite3 bogus} msg]
  regsub {really_sqlite3} $msg {sqlite3} msg
  lappend v $msg
} [list 1 "wrong # args: should be \"$r\""]
do_test tcl-1.2 {
  set v [catch {db bogus} msg]
  lappend v $msg
} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, log_hook, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, or version}}
do_test tcl-1.2.1 {
  set v [catch {db cache bogus} msg]
  lappend v $msg
} {1 {bad option "bogus": must be flush or size}}
do_test tcl-1.2.2 {
  set v [catch {db cache} msg]
  lappend v $msg







|







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  set v [catch {sqlite3 bogus} msg]
  regsub {really_sqlite3} $msg {sqlite3} msg
  lappend v $msg
} [list 1 "wrong # args: should be \"$r\""]
do_test tcl-1.2 {
  set v [catch {db bogus} msg]
  lappend v $msg
} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, version, or wal_hook}}
do_test tcl-1.2.1 {
  set v [catch {db cache bogus} msg]
  lappend v $msg
} {1 {bad option "bogus": must be flush or size}}
do_test tcl-1.2.2 {
  set v [catch {db cache} msg]
  lappend v $msg
Changes to test/walhook.test.
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
  [lindex $args 0] eval { 
    PRAGMA journal_mode = wal;
    PRAGMA synchronous = normal;
    PRAGMA page_size = 1024;
  }
}
sqlite3_wal db test.db
db log_hook log_hook

set ::log_hook [list]
proc log_hook {zDb nEntry} {
  lappend ::log_hook $zDb $nEntry
  return 0
}

do_test walhook-1.1 {
  execsql { CREATE TABLE t1(i PRIMARY KEY, j) }
  set ::log_hook
} {main 3}
do_test walhook-1.2 {
  set ::log_hook [list]
  execsql { INSERT INTO t1 VALUES(1, 'one') }
  set ::log_hook
} {main 5}
do_test walhook-1.3 {
  proc log_hook {args} { return 1 }
  execsql { INSERT INTO t1 VALUES(2, 'two') }
  file size test.db
} [expr 3*1024]

do_test walhook-1.4 {
  proc log_hook {zDb nEntry} { 
    execsql { PRAGMA checkpoint }
    return 0
  }
  execsql { CREATE TABLE t2(a, b) }
  file size test.db
} [expr 4*1024]

do_test walhook-1.5 {
  sqlite3_wal db2 test.db
  proc log_hook {zDb nEntry} { 
    execsql { PRAGMA checkpoint } db2
    return 0
  }
  execsql { CREATE TABLE t3(a PRIMARY KEY, b) }
  file size test.db
} [expr 6*1024]

catch { db2 close }
catch { db close }
finish_test








|

|
|
|





|


|

|


|





|









|










<
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

  [lindex $args 0] eval { 
    PRAGMA journal_mode = wal;
    PRAGMA synchronous = normal;
    PRAGMA page_size = 1024;
  }
}
sqlite3_wal db test.db
db wal_hook wal_hook

set ::wal_hook [list]
proc wal_hook {zDb nEntry} {
  lappend ::wal_hook $zDb $nEntry
  return 0
}

do_test walhook-1.1 {
  execsql { CREATE TABLE t1(i PRIMARY KEY, j) }
  set ::wal_hook
} {main 3}
do_test walhook-1.2 {
  set ::wal_hook [list]
  execsql { INSERT INTO t1 VALUES(1, 'one') }
  set ::wal_hook
} {main 5}
do_test walhook-1.3 {
  proc wal_hook {args} { return 1 }
  execsql { INSERT INTO t1 VALUES(2, 'two') }
  file size test.db
} [expr 3*1024]

do_test walhook-1.4 {
  proc wal_hook {zDb nEntry} { 
    execsql { PRAGMA checkpoint }
    return 0
  }
  execsql { CREATE TABLE t2(a, b) }
  file size test.db
} [expr 4*1024]

do_test walhook-1.5 {
  sqlite3_wal db2 test.db
  proc wal_hook {zDb nEntry} { 
    execsql { PRAGMA checkpoint } db2
    return 0
  }
  execsql { CREATE TABLE t3(a PRIMARY KEY, b) }
  file size test.db
} [expr 6*1024]

catch { db2 close }
catch { db close }
finish_test