mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Add the sqlite3_log_hook() interface for scheduling checkpoints.
FossilOrigin-Name: 9bda601455705475075e33bfa85687bce34b15ff
This commit is contained in:
33
manifest
33
manifest
@ -1,5 +1,5 @@
|
|||||||
C Add\ssome\scomments\sregarding\sfile-locks\sto\slog.c.
|
C Add\sthe\ssqlite3_log_hook()\sinterface\sfor\sscheduling\scheckpoints.
|
||||||
D 2010-04-17T18:50:28
|
D 2010-04-19T18:03:52
|
||||||
F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
|
F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
|
||||||
F Makefile.in 4f2f967b7e58a35bb74fb7ec8ae90e0f4ca7868b
|
F Makefile.in 4f2f967b7e58a35bb74fb7ec8ae90e0f4ca7868b
|
||||||
F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
|
F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
|
||||||
@ -131,9 +131,9 @@ F src/journal.c b0ea6b70b532961118ab70301c00a33089f9315c
|
|||||||
F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f
|
F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f
|
||||||
F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e
|
F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e
|
||||||
F src/loadext.c 1c7a61ce1281041f437333f366a96aa0d29bb581
|
F src/loadext.c 1c7a61ce1281041f437333f366a96aa0d29bb581
|
||||||
F src/log.c fd6631f740b0cf2c8720477a036ffee8ca705c92
|
F src/log.c a5bbeb05c456ee7ee9d4f3dba5e72bae2d9b6530
|
||||||
F src/log.h 3cfec02156f5db3d233352897a47601930a29955
|
F src/log.h b8c45a6348d9ef57c5205a08c611d57d07ee9feb
|
||||||
F src/main.c c0e7192bad5b90544508b241eb2487ac661de890
|
F src/main.c 867de6aa444abd97771b2b70472f448d65c1c77e
|
||||||
F src/malloc.c a08f16d134f0bfab6b20c3cd142ebf3e58235a6a
|
F src/malloc.c a08f16d134f0bfab6b20c3cd142ebf3e58235a6a
|
||||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||||
F src/mem1.c 89d4ea8d5cdd55635cbaa48ad53132af6294cbb2
|
F src/mem1.c 89d4ea8d5cdd55635cbaa48ad53132af6294cbb2
|
||||||
@ -154,8 +154,8 @@ F src/os_common.h 240c88b163b02c21a9f21f87d49678a0aa21ff30
|
|||||||
F src/os_os2.c 75a8c7b9a00a2cf1a65f9fa4afbc27d46634bb2f
|
F src/os_os2.c 75a8c7b9a00a2cf1a65f9fa4afbc27d46634bb2f
|
||||||
F src/os_unix.c 5bf0015cebe2f21635da2af983c348eb88b3b4c1
|
F src/os_unix.c 5bf0015cebe2f21635da2af983c348eb88b3b4c1
|
||||||
F src/os_win.c 1c7453c2df4dab26d90ff6f91272aea18bcf7053
|
F src/os_win.c 1c7453c2df4dab26d90ff6f91272aea18bcf7053
|
||||||
F src/pager.c 344eddf3c59eed4b4aca31abbf762d0e301ae44d
|
F src/pager.c f9ad51dfa617131240ae77545c0083118324fc81
|
||||||
F src/pager.h ce5d076f3860a5f2d7460c582cd68383343b33cf
|
F src/pager.h aeaee9ceb4a4a6d15c2163ef4e11a7590fd54b59
|
||||||
F src/parse.y ace5c7a125d9f2a410e431ee3209034105045f7e
|
F src/parse.y ace5c7a125d9f2a410e431ee3209034105045f7e
|
||||||
F src/pcache.c ace8f6a5ecd4711cc66a1b23053be7109bd437cf
|
F src/pcache.c ace8f6a5ecd4711cc66a1b23053be7109bd437cf
|
||||||
F src/pcache.h c683390d50f856d4cd8e24342ae62027d1bb6050
|
F src/pcache.h c683390d50f856d4cd8e24342ae62027d1bb6050
|
||||||
@ -168,13 +168,13 @@ F src/resolve.c ac5f1a713cd1ae77f08b83cc69581e11bf5ae6f9
|
|||||||
F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
|
F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
|
||||||
F src/select.c b7c9a40bc1567bceff52ad4b73108734ee4bf268
|
F src/select.c b7c9a40bc1567bceff52ad4b73108734ee4bf268
|
||||||
F src/shell.c c40427c7245535a04a9cb4a417b6cc05c022e6a4
|
F src/shell.c c40427c7245535a04a9cb4a417b6cc05c022e6a4
|
||||||
F src/sqlite.h.in dc98616304e3e776008655671d81e3ad3028ada7
|
F src/sqlite.h.in b9b0365613d4a99a0e6bc0ae6dbc73cc4a7e7cb7
|
||||||
F src/sqlite3ext.h 69dfb8116af51b84a029cddb3b35062354270c89
|
F src/sqlite3ext.h 69dfb8116af51b84a029cddb3b35062354270c89
|
||||||
F src/sqliteInt.h bd7ff54663bdd5b57e0eb8b49aca5a3a3c60119a
|
F src/sqliteInt.h 6ed0ed77103d00006d6a534c0239baf6decadb3f
|
||||||
F src/sqliteLimit.h 3afab2291762b5d09ae20c18feb8e9fa935a60a6
|
F src/sqliteLimit.h 3afab2291762b5d09ae20c18feb8e9fa935a60a6
|
||||||
F src/status.c 4df6fe7dce2d256130b905847c6c60055882bdbe
|
F src/status.c 4df6fe7dce2d256130b905847c6c60055882bdbe
|
||||||
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
|
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
|
||||||
F src/tclsqlite.c ebf7a964f8311bc52266cf75afedae98cd743df3
|
F src/tclsqlite.c e27e0457109d72c7404df08af1726ff6ddc3d92c
|
||||||
F src/test1.c 8b4c246c41e75c3ff033edb2e8c2cf15820861ae
|
F src/test1.c 8b4c246c41e75c3ff033edb2e8c2cf15820861ae
|
||||||
F src/test2.c b6b43413d495addd039a88b87d65c839f86b18cb
|
F src/test2.c b6b43413d495addd039a88b87d65c839f86b18cb
|
||||||
F src/test3.c 4c21700c73a890a47fc685c1097bfb661346ac94
|
F src/test3.c 4c21700c73a890a47fc685c1097bfb661346ac94
|
||||||
@ -217,8 +217,8 @@ F src/vacuum.c b1d542c8919d4d11119f78069e1906a1ad07e0ee
|
|||||||
F src/vdbe.c 2e2aaa765de667dd15e0462cf853efd1b2f97998
|
F src/vdbe.c 2e2aaa765de667dd15e0462cf853efd1b2f97998
|
||||||
F src/vdbe.h 471f6a3dcec4817ca33596fe7f6654d56c0e75f3
|
F src/vdbe.h 471f6a3dcec4817ca33596fe7f6654d56c0e75f3
|
||||||
F src/vdbeInt.h 19ebc8c2a2e938340051ee65af3f377fb99102d1
|
F src/vdbeInt.h 19ebc8c2a2e938340051ee65af3f377fb99102d1
|
||||||
F src/vdbeapi.c 11bcc381e81e797fcf3e81fa6a14ec16a04801cc
|
F src/vdbeapi.c 466044df5bc916f778833e927165fd02cdef6086
|
||||||
F src/vdbeaux.c 3028b2d50df39697e21263685349b5ff1333a29b
|
F src/vdbeaux.c be6ca4bb330c7730b358dd627d6d632ca93482c9
|
||||||
F src/vdbeblob.c 5327132a42a91e8b7acfb60b9d2c3b1c5c863e0e
|
F src/vdbeblob.c 5327132a42a91e8b7acfb60b9d2c3b1c5c863e0e
|
||||||
F src/vdbemem.c 2a82f455f6ca6f78b59fb312f96054c04ae0ead1
|
F src/vdbemem.c 2a82f455f6ca6f78b59fb312f96054c04ae0ead1
|
||||||
F src/vdbetrace.c 864cef96919323482ebd9986f2132435115e9cc2
|
F src/vdbetrace.c 864cef96919323482ebd9986f2132435115e9cc2
|
||||||
@ -599,7 +599,7 @@ F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
|
|||||||
F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3
|
F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3
|
||||||
F test/table.test bf102a5669c4db7a41330802f24a4a81a4204f83
|
F test/table.test bf102a5669c4db7a41330802f24a4a81a4204f83
|
||||||
F test/tableapi.test 7262a8cbaa9965d429f1cbd2747edc185fa56516
|
F test/tableapi.test 7262a8cbaa9965d429f1cbd2747edc185fa56516
|
||||||
F test/tclsqlite.test bf4227eb236a4c097aa7974a2bf7d3225acf34be
|
F test/tclsqlite.test 7378a4e7474d9ffdb7c930086112b829e5955fb0
|
||||||
F test/tempdb.test 1bf52da28a9c24e29717362a87722dff08feb72b
|
F test/tempdb.test 1bf52da28a9c24e29717362a87722dff08feb72b
|
||||||
F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
|
F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
|
||||||
F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
|
F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
|
||||||
@ -760,6 +760,7 @@ F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8
|
|||||||
F test/vtab_shared.test 0eff9ce4f19facbe0a3e693f6c14b80711a4222d
|
F test/vtab_shared.test 0eff9ce4f19facbe0a3e693f6c14b80711a4222d
|
||||||
F test/wal.test a56ff378f58b145fd3bf38c277fbfe792cd47bdd
|
F test/wal.test a56ff378f58b145fd3bf38c277fbfe792cd47bdd
|
||||||
F test/walcrash.test 45cfbab30bb7cbe0b2e9d5cabe90dbcad10cb89b
|
F test/walcrash.test 45cfbab30bb7cbe0b2e9d5cabe90dbcad10cb89b
|
||||||
|
F test/walhook.test 76a559e262f0715c470bade4a8d8333035f8ee47
|
||||||
F test/walslow.test 38076d5fad49e3678027be0f8110e6a32d531dc2
|
F test/walslow.test 38076d5fad49e3678027be0f8110e6a32d531dc2
|
||||||
F test/walthread.test 27e44ee6fd02f1f494a24f999c97086af3ab739d
|
F test/walthread.test 27e44ee6fd02f1f494a24f999c97086af3ab739d
|
||||||
F test/where.test de337a3fe0a459ec7c93db16a519657a90552330
|
F test/where.test de337a3fe0a459ec7c93db16a519657a90552330
|
||||||
@ -805,7 +806,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
|
|||||||
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
||||||
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||||
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
||||||
P 0ae91b0008b242a47385fc1f295c6b645483ee22
|
P 9d51c3b754f0b94fea5ef3d669ad583b93b2b024
|
||||||
R a205bea3ab4fa24118ce751726c3c719
|
R 8eb45c9f56fe0d743248bffdfb606bc1
|
||||||
U dan
|
U dan
|
||||||
Z c979f9f88d66076be76b02e7ab3fa216
|
Z e82f118f370901abf724c6ac84b89351
|
||||||
|
@ -1 +1 @@
|
|||||||
9d51c3b754f0b94fea5ef3d669ad583b93b2b024
|
9bda601455705475075e33bfa85687bce34b15ff
|
74
src/log.c
74
src/log.c
@ -202,6 +202,14 @@ struct LogSummary {
|
|||||||
#define LOG_REGION_C 0x04
|
#define LOG_REGION_C 0x04
|
||||||
#define LOG_REGION_D 0x08
|
#define LOG_REGION_D 0x08
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Values for the third parameter to logLockRegion().
|
||||||
|
*/
|
||||||
|
#define LOG_UNLOCK 0 /* Unlock a range of bytes */
|
||||||
|
#define LOG_RDLOCK 1 /* Put a SHARED lock on a range of bytes */
|
||||||
|
#define LOG_WRLOCK 2 /* Put an EXCLUSIVE lock on a byte-range */
|
||||||
|
#define LOG_WRLOCKW 3 /* Block on EXCLUSIVE lock on a byte-range */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** A single instance of this structure is allocated as part of each
|
** A single instance of this structure is allocated as part of each
|
||||||
** connection to a database log. All structures associated with the
|
** connection to a database log. All structures associated with the
|
||||||
@ -225,6 +233,7 @@ struct Log {
|
|||||||
sqlite3_file *pFd; /* File handle for log file */
|
sqlite3_file *pFd; /* File handle for log file */
|
||||||
int isLocked; /* Non-zero if a snapshot is held open */
|
int isLocked; /* Non-zero if a snapshot is held open */
|
||||||
int isWriteLocked; /* True if this is the writer connection */
|
int isWriteLocked; /* True if this is the writer connection */
|
||||||
|
u32 iCallback; /* Value to pass to log callback (or 0) */
|
||||||
LogSummaryHdr hdr; /* Log summary header for current snapshot */
|
LogSummaryHdr hdr; /* Log summary header for current snapshot */
|
||||||
LogLock lock; /* Lock held by this connection (if any) */
|
LogLock lock; /* Lock held by this connection (if any) */
|
||||||
};
|
};
|
||||||
@ -647,29 +656,29 @@ finished:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Values for the third parameter to logLockRegion().
|
** Place, modify or remove a lock on the log-summary file associated
|
||||||
|
** with pSummary.
|
||||||
*/
|
*/
|
||||||
#define LOG_UNLOCK 0
|
static int logLockFd(
|
||||||
#define LOG_RDLOCK 1
|
LogSummary *pSummary, /* The log-summary object to lock */
|
||||||
#define LOG_WRLOCK 2
|
int iStart, /* First byte to lock */
|
||||||
#define LOG_WRLOCKW 3
|
int nByte, /* Number of bytes to lock */
|
||||||
|
int op /* LOG_UNLOCK, RDLOCK, WRLOCK or WRLOCKW */
|
||||||
static int logLockFd(LogSummary *pSummary, int iStart, int nByte, int op){
|
){
|
||||||
int aType[4] = {
|
int aType[4] = {
|
||||||
F_UNLCK, /* LOG_UNLOCK */
|
F_UNLCK, /* LOG_UNLOCK */
|
||||||
F_RDLCK, /* LOG_RDLOCK */
|
F_RDLCK, /* LOG_RDLOCK */
|
||||||
F_WRLCK, /* LOG_WRLOCK */
|
F_WRLCK, /* LOG_WRLOCK */
|
||||||
F_WRLCK /* LOG_WRLOCKW */
|
F_WRLCK /* LOG_WRLOCKW */
|
||||||
};
|
};
|
||||||
int aOp[4] = {
|
int aOp[4] = {
|
||||||
F_SETLK, /* LOG_UNLOCK */
|
F_SETLK, /* LOG_UNLOCK */
|
||||||
F_SETLK, /* LOG_RDLOCK */
|
F_SETLK, /* LOG_RDLOCK */
|
||||||
F_SETLK, /* LOG_WRLOCK */
|
F_SETLK, /* LOG_WRLOCK */
|
||||||
F_SETLKW /* LOG_WRLOCKW */
|
F_SETLKW /* LOG_WRLOCKW */
|
||||||
};
|
};
|
||||||
|
struct flock f; /* Locking operation */
|
||||||
struct flock f; /* Locking operation */
|
int rc; /* Value returned by fcntl() */
|
||||||
int rc; /* Value returned by fcntl() */
|
|
||||||
|
|
||||||
assert( ArraySize(aType)==ArraySize(aOp) );
|
assert( ArraySize(aType)==ArraySize(aOp) );
|
||||||
assert( op>=0 && op<ArraySize(aType) );
|
assert( op>=0 && op<ArraySize(aType) );
|
||||||
@ -816,19 +825,28 @@ static int logLockRegion(Log *pLog, u32 mRegion, int op){
|
|||||||
return SQLITE_OK;
|
return SQLITE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Lock the DMH region, either with an EXCLUSIVE or SHARED lock. This
|
||||||
|
** function is never called with LOG_UNLOCK - the only way the DMH region
|
||||||
|
** is every completely unlocked is by by closing the file descriptor.
|
||||||
|
*/
|
||||||
static int logLockDMH(LogSummary *pSummary, int eLock){
|
static int logLockDMH(LogSummary *pSummary, int eLock){
|
||||||
|
assert( sqlite3_mutex_held(pSummary->mutex) );
|
||||||
assert( eLock==LOG_RDLOCK || eLock==LOG_WRLOCK );
|
assert( eLock==LOG_RDLOCK || eLock==LOG_WRLOCK );
|
||||||
return logLockFd(pSummary, LOG_LOCK_DMH, 1, eLock);
|
return logLockFd(pSummary, LOG_LOCK_DMH, 1, eLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Lock (or unlock) the MUTEX region. It is always locked using an
|
||||||
|
** EXCLUSIVE, blocking lock.
|
||||||
|
*/
|
||||||
static int logLockMutex(LogSummary *pSummary, int eLock){
|
static int logLockMutex(LogSummary *pSummary, int eLock){
|
||||||
|
assert( sqlite3_mutex_held(pSummary->mutex) );
|
||||||
assert( eLock==LOG_WRLOCKW || eLock==LOG_UNLOCK );
|
assert( eLock==LOG_WRLOCKW || eLock==LOG_UNLOCK );
|
||||||
logLockFd(pSummary, LOG_LOCK_MUTEX, 1, eLock);
|
logLockFd(pSummary, LOG_LOCK_MUTEX, 1, eLock);
|
||||||
return SQLITE_OK;
|
return SQLITE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** This function intializes the connection to the log-summary identified
|
** This function intializes the connection to the log-summary identified
|
||||||
** by struct pSummary.
|
** by struct pSummary.
|
||||||
@ -880,7 +898,7 @@ static int logSummaryInit(
|
|||||||
}
|
}
|
||||||
rc = logLockDMH(pSummary, LOG_RDLOCK);
|
rc = logLockDMH(pSummary, LOG_RDLOCK);
|
||||||
if( rc!=SQLITE_OK ){
|
if( rc!=SQLITE_OK ){
|
||||||
return SQLITE_IOERR;
|
rc = SQLITE_IOERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
@ -1461,7 +1479,7 @@ int sqlite3LogRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut){
|
|||||||
/*
|
/*
|
||||||
** Set *pPgno to the size of the database file (or zero, if unknown).
|
** Set *pPgno to the size of the database file (or zero, if unknown).
|
||||||
*/
|
*/
|
||||||
void sqlite3LogMaxpgno(Log *pLog, Pgno *pPgno){
|
void sqlite3LogDbsize(Log *pLog, Pgno *pPgno){
|
||||||
assert( pLog->isLocked );
|
assert( pLog->isLocked );
|
||||||
*pPgno = pLog->hdr.nPage;
|
*pPgno = pLog->hdr.nPage;
|
||||||
}
|
}
|
||||||
@ -1646,9 +1664,10 @@ int sqlite3LogFrames(
|
|||||||
if( isCommit && SQLITE_OK==(rc = logEnterMutex(pLog)) ){
|
if( isCommit && SQLITE_OK==(rc = logEnterMutex(pLog)) ){
|
||||||
logSummaryWriteHdr(pLog->pSummary, &pLog->hdr);
|
logSummaryWriteHdr(pLog->pSummary, &pLog->hdr);
|
||||||
logLeaveMutex(pLog);
|
logLeaveMutex(pLog);
|
||||||
|
pLog->iCallback = iFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SQLITE_OK;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1698,3 +1717,12 @@ int sqlite3LogCheckpoint(
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sqlite3LogCallback(Log *pLog){
|
||||||
|
u32 ret = 0;
|
||||||
|
if( pLog ){
|
||||||
|
ret = pLog->iCallback;
|
||||||
|
pLog->iCallback = 0;
|
||||||
|
}
|
||||||
|
return (int)ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ void sqlite3LogCloseSnapshot(Log *pLog);
|
|||||||
|
|
||||||
/* Read a page from the log, if it is present. */
|
/* Read a page from the log, if it is present. */
|
||||||
int sqlite3LogRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut);
|
int sqlite3LogRead(Log *pLog, Pgno pgno, int *pInLog, u8 *pOut);
|
||||||
void sqlite3LogMaxpgno(Log *pLog, Pgno *pPgno);
|
void sqlite3LogDbsize(Log *pLog, Pgno *pPgno);
|
||||||
|
|
||||||
/* Obtain or release the WRITER lock. */
|
/* Obtain or release the WRITER lock. */
|
||||||
int sqlite3LogWriteLock(Log *pLog, int op);
|
int sqlite3LogWriteLock(Log *pLog, int op);
|
||||||
@ -50,4 +50,7 @@ int sqlite3LogCheckpoint(
|
|||||||
void *pBusyHandlerArg /* Argument to pass to xBusyHandler */
|
void *pBusyHandlerArg /* Argument to pass to xBusyHandler */
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* Return the value to pass to a log callback. Or 0 for no callback. */
|
||||||
|
int sqlite3LogCallback(Log *pLog);
|
||||||
|
|
||||||
#endif /* _LOG_H_ */
|
#endif /* _LOG_H_ */
|
||||||
|
18
src/main.c
18
src/main.c
@ -1186,6 +1186,24 @@ void *sqlite3_rollback_hook(
|
|||||||
return pRet;
|
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;
|
||||||
|
db->xLogCallback = xCallback;
|
||||||
|
db->pLogArg = pArg;
|
||||||
|
sqlite3_mutex_leave(db->mutex);
|
||||||
|
return pRet;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** This function returns true if main-memory should be used instead of
|
** This function returns true if main-memory should be used instead of
|
||||||
** a temporary file for transient pager files and statement journals.
|
** a temporary file for transient pager files and statement journals.
|
||||||
|
@ -2686,7 +2686,7 @@ int sqlite3PagerPagecount(Pager *pPager, int *pnPage){
|
|||||||
i64 n = 0; /* File size in bytes returned by OsFileSize() */
|
i64 n = 0; /* File size in bytes returned by OsFileSize() */
|
||||||
|
|
||||||
if( pagerUseLog(pPager) ){
|
if( pagerUseLog(pPager) ){
|
||||||
sqlite3LogMaxpgno(pPager->pLog, &nPage);
|
sqlite3LogDbsize(pPager->pLog, &nPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if( nPage==0 ){
|
if( nPage==0 ){
|
||||||
@ -5709,4 +5709,8 @@ int sqlite3PagerCheckpoint(Pager *pPager){
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sqlite3PagerLogCallback(Pager *pPager){
|
||||||
|
return sqlite3LogCallback(pPager->pLog);
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* SQLITE_OMIT_DISKIO */
|
#endif /* SQLITE_OMIT_DISKIO */
|
||||||
|
@ -133,7 +133,9 @@ int sqlite3PagerRollback(Pager*);
|
|||||||
int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
|
int sqlite3PagerOpenSavepoint(Pager *pPager, int n);
|
||||||
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
|
int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
|
||||||
int sqlite3PagerSharedLock(Pager *pPager);
|
int sqlite3PagerSharedLock(Pager *pPager);
|
||||||
|
|
||||||
int sqlite3PagerCheckpoint(Pager *pPager);
|
int sqlite3PagerCheckpoint(Pager *pPager);
|
||||||
|
int sqlite3PagerLogCallback(Pager *pPager);
|
||||||
|
|
||||||
/* Functions used to query pager state and configuration. */
|
/* Functions used to query pager state and configuration. */
|
||||||
u8 sqlite3PagerIsreadonly(Pager*);
|
u8 sqlite3PagerIsreadonly(Pager*);
|
||||||
|
@ -5726,6 +5726,40 @@ int sqlite3_strnicmp(const char *, const char *, int);
|
|||||||
*/
|
*/
|
||||||
void sqlite3_log(int iErrCode, const char *zFormat, ...);
|
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
|
** Undo the hack that converts floating point types to integer for
|
||||||
** builds on processors without floating point support.
|
** builds on processors without floating point support.
|
||||||
|
@ -823,6 +823,8 @@ struct sqlite3 {
|
|||||||
void (*xRollbackCallback)(void*); /* Invoked at every commit. */
|
void (*xRollbackCallback)(void*); /* Invoked at every commit. */
|
||||||
void *pUpdateArg;
|
void *pUpdateArg;
|
||||||
void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
|
void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
|
||||||
|
int (*xLogCallback)(void *, sqlite3 *, const char *, u32);
|
||||||
|
void *pLogArg;
|
||||||
void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
|
void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
|
||||||
void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
|
void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
|
||||||
void *pCollNeededArg;
|
void *pCollNeededArg;
|
||||||
|
@ -123,6 +123,7 @@ struct SqliteDb {
|
|||||||
SqlFunc *pFunc; /* List of SQL functions */
|
SqlFunc *pFunc; /* List of SQL functions */
|
||||||
Tcl_Obj *pUpdateHook; /* Update hook script (if any) */
|
Tcl_Obj *pUpdateHook; /* Update hook script (if any) */
|
||||||
Tcl_Obj *pRollbackHook; /* Rollback hook script (if any) */
|
Tcl_Obj *pRollbackHook; /* Rollback hook script (if any) */
|
||||||
|
Tcl_Obj *pLogHook; /* WAL hook script (if any) */
|
||||||
Tcl_Obj *pUnlockNotify; /* Unlock notify script (if any) */
|
Tcl_Obj *pUnlockNotify; /* Unlock notify script (if any) */
|
||||||
SqlCollate *pCollate; /* List of SQL collation functions */
|
SqlCollate *pCollate; /* List of SQL collation functions */
|
||||||
int rc; /* Return code of most recent sqlite3_exec() */
|
int rc; /* Return code of most recent sqlite3_exec() */
|
||||||
@ -485,6 +486,9 @@ static void DbDeleteCmd(void *db){
|
|||||||
if( pDb->pRollbackHook ){
|
if( pDb->pRollbackHook ){
|
||||||
Tcl_DecrRefCount(pDb->pRollbackHook);
|
Tcl_DecrRefCount(pDb->pRollbackHook);
|
||||||
}
|
}
|
||||||
|
if( pDb->pLogHook ){
|
||||||
|
Tcl_DecrRefCount(pDb->pLogHook);
|
||||||
|
}
|
||||||
if( pDb->pCollateNeeded ){
|
if( pDb->pCollateNeeded ){
|
||||||
Tcl_DecrRefCount(pDb->pCollateNeeded);
|
Tcl_DecrRefCount(pDb->pCollateNeeded);
|
||||||
}
|
}
|
||||||
@ -589,6 +593,32 @@ static void DbRollbackHandler(void *clientData){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int DbLogHandler(
|
||||||
|
void *clientData,
|
||||||
|
sqlite3 *db,
|
||||||
|
const char *zDb,
|
||||||
|
int nEntry
|
||||||
|
){
|
||||||
|
int ret = 0;
|
||||||
|
Tcl_Obj *p;
|
||||||
|
SqliteDb *pDb = (SqliteDb*)clientData;
|
||||||
|
Tcl_Interp *interp = pDb->interp;
|
||||||
|
assert(pDb->pLogHook);
|
||||||
|
|
||||||
|
p = Tcl_DuplicateObj(pDb->pLogHook);
|
||||||
|
Tcl_IncrRefCount(p);
|
||||||
|
Tcl_ListObjAppendElement(interp, p, Tcl_NewStringObj(zDb, -1));
|
||||||
|
Tcl_ListObjAppendElement(interp, p, Tcl_NewIntObj(nEntry));
|
||||||
|
if( TCL_OK!=Tcl_EvalObjEx(interp, p, 0)
|
||||||
|
|| TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &ret)
|
||||||
|
){
|
||||||
|
Tcl_BackgroundError(interp);
|
||||||
|
}
|
||||||
|
Tcl_DecrRefCount(p);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
|
||||||
static void setTestUnlockNotifyVars(Tcl_Interp *interp, int iArg, int nArg){
|
static void setTestUnlockNotifyVars(Tcl_Interp *interp, int iArg, int nArg){
|
||||||
char zBuf[64];
|
char zBuf[64];
|
||||||
@ -1540,12 +1570,12 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
|||||||
"complete", "copy", "enable_load_extension",
|
"complete", "copy", "enable_load_extension",
|
||||||
"errorcode", "eval", "exists",
|
"errorcode", "eval", "exists",
|
||||||
"function", "incrblob", "interrupt",
|
"function", "incrblob", "interrupt",
|
||||||
"last_insert_rowid", "nullvalue", "onecolumn",
|
"last_insert_rowid", "log_hook", "nullvalue",
|
||||||
"profile", "progress", "rekey",
|
"onecolumn", "profile", "progress",
|
||||||
"restore", "rollback_hook", "status",
|
"rekey", "restore", "rollback_hook",
|
||||||
"timeout", "total_changes", "trace",
|
"status", "timeout", "total_changes",
|
||||||
"transaction", "unlock_notify", "update_hook",
|
"trace", "transaction", "unlock_notify",
|
||||||
"version", 0
|
"update_hook", "version", 0
|
||||||
};
|
};
|
||||||
enum DB_enum {
|
enum DB_enum {
|
||||||
DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
|
DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
|
||||||
@ -1554,12 +1584,12 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
|||||||
DB_COMPLETE, DB_COPY, DB_ENABLE_LOAD_EXTENSION,
|
DB_COMPLETE, DB_COPY, DB_ENABLE_LOAD_EXTENSION,
|
||||||
DB_ERRORCODE, DB_EVAL, DB_EXISTS,
|
DB_ERRORCODE, DB_EVAL, DB_EXISTS,
|
||||||
DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT,
|
DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT,
|
||||||
DB_LAST_INSERT_ROWID, DB_NULLVALUE, DB_ONECOLUMN,
|
DB_LAST_INSERT_ROWID, DB_LOG_HOOK, DB_NULLVALUE,
|
||||||
DB_PROFILE, DB_PROGRESS, DB_REKEY,
|
DB_ONECOLUMN, DB_PROFILE, DB_PROGRESS,
|
||||||
DB_RESTORE, DB_ROLLBACK_HOOK, DB_STATUS,
|
DB_REKEY, DB_RESTORE, DB_ROLLBACK_HOOK,
|
||||||
DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
|
DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES,
|
||||||
DB_TRANSACTION, DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK,
|
DB_TRACE, DB_TRANSACTION, DB_UNLOCK_NOTIFY,
|
||||||
DB_VERSION,
|
DB_UPDATE_HOOK, DB_VERSION
|
||||||
};
|
};
|
||||||
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
|
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
|
||||||
|
|
||||||
@ -2730,9 +2760,11 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
** $db log_hook ?script?
|
||||||
** $db update_hook ?script?
|
** $db update_hook ?script?
|
||||||
** $db rollback_hook ?script?
|
** $db rollback_hook ?script?
|
||||||
*/
|
*/
|
||||||
|
case DB_LOG_HOOK:
|
||||||
case DB_UPDATE_HOOK:
|
case DB_UPDATE_HOOK:
|
||||||
case DB_ROLLBACK_HOOK: {
|
case DB_ROLLBACK_HOOK: {
|
||||||
|
|
||||||
@ -2742,6 +2774,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
|||||||
Tcl_Obj **ppHook;
|
Tcl_Obj **ppHook;
|
||||||
if( choice==DB_UPDATE_HOOK ){
|
if( choice==DB_UPDATE_HOOK ){
|
||||||
ppHook = &pDb->pUpdateHook;
|
ppHook = &pDb->pUpdateHook;
|
||||||
|
}else if( choice==DB_LOG_HOOK ){
|
||||||
|
ppHook = &pDb->pLogHook;
|
||||||
}else{
|
}else{
|
||||||
ppHook = &pDb->pRollbackHook;
|
ppHook = &pDb->pRollbackHook;
|
||||||
}
|
}
|
||||||
@ -2767,6 +2801,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
|||||||
|
|
||||||
sqlite3_update_hook(pDb->db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
|
sqlite3_update_hook(pDb->db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
|
||||||
sqlite3_rollback_hook(pDb->db,(pDb->pRollbackHook?DbRollbackHandler:0),pDb);
|
sqlite3_rollback_hook(pDb->db,(pDb->pRollbackHook?DbRollbackHandler:0),pDb);
|
||||||
|
sqlite3_log_hook(pDb->db,(pDb->pLogHook?DbLogHandler:0),pDb);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -306,6 +306,23 @@ void sqlite3_result_error_nomem(sqlite3_context *pCtx){
|
|||||||
pCtx->s.db->mallocFailed = 1;
|
pCtx->s.db->mallocFailed = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int doLogCallbacks(sqlite3 *db){
|
||||||
|
int i;
|
||||||
|
int rc = SQLITE_OK;
|
||||||
|
for(i=0; i<db->nDb; i++){
|
||||||
|
Btree *pBt = db->aDb[i].pBt;
|
||||||
|
if( pBt ){
|
||||||
|
int nEntry = sqlite3PagerLogCallback(sqlite3BtreePager(pBt));
|
||||||
|
if( db->xLogCallback && nEntry>0 && rc==SQLITE_OK
|
||||||
|
&& db->xLogCallback(db->pLogArg, db, db->aDb[i].zName, nEntry)
|
||||||
|
){
|
||||||
|
rc = sqlite3PagerCheckpoint(sqlite3BtreePager(pBt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Execute the statement pStmt, either until a row of data is ready, the
|
** Execute the statement pStmt, either until a row of data is ready, the
|
||||||
** statement is completely executed or an error occurs.
|
** statement is completely executed or an error occurs.
|
||||||
@ -387,6 +404,14 @@ static int sqlite3Step(Vdbe *p){
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if( rc==SQLITE_DONE ){
|
||||||
|
assert( p->rc==SQLITE_OK );
|
||||||
|
p->rc = doLogCallbacks(db);
|
||||||
|
if( p->rc!=SQLITE_OK ){
|
||||||
|
rc = SQLITE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
db->errCode = rc;
|
db->errCode = rc;
|
||||||
if( SQLITE_NOMEM==sqlite3ApiExit(p->db, p->rc) ){
|
if( SQLITE_NOMEM==sqlite3ApiExit(p->db, p->rc) ){
|
||||||
p->rc = SQLITE_NOMEM;
|
p->rc = SQLITE_NOMEM;
|
||||||
|
@ -1651,7 +1651,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
|
|||||||
** one database file has an open write transaction, a master journal
|
** one database file has an open write transaction, a master journal
|
||||||
** file is required for an atomic commit.
|
** file is required for an atomic commit.
|
||||||
*/
|
*/
|
||||||
for(i=0; i<db->nDb; i++){
|
for(i=0; i<db->nDb; i++){
|
||||||
Btree *pBt = db->aDb[i].pBt;
|
Btree *pBt = db->aDb[i].pBt;
|
||||||
if( sqlite3BtreeIsInTrans(pBt) ){
|
if( sqlite3BtreeIsInTrans(pBt) ){
|
||||||
needXcommit = 1;
|
needXcommit = 1;
|
||||||
|
@ -35,7 +35,7 @@ do_test tcl-1.1 {
|
|||||||
do_test tcl-1.2 {
|
do_test tcl-1.2 {
|
||||||
set v [catch {db bogus} msg]
|
set v [catch {db bogus} msg]
|
||||||
lappend v $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, or version}}
|
} {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 {
|
do_test tcl-1.2.1 {
|
||||||
set v [catch {db cache bogus} msg]
|
set v [catch {db cache bogus} msg]
|
||||||
lappend v $msg
|
lappend v $msg
|
||||||
|
73
test/walhook.test
Normal file
73
test/walhook.test
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# 2010 April 19
|
||||||
|
#
|
||||||
|
# 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 operation of the library in
|
||||||
|
# "PRAGMA journal_mode=WAL" mode.
|
||||||
|
#
|
||||||
|
|
||||||
|
set testdir [file dirname $argv0]
|
||||||
|
source $testdir/tester.tcl
|
||||||
|
|
||||||
|
proc sqlite3_wal {args} {
|
||||||
|
eval sqlite3 $args
|
||||||
|
[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
|
||||||
|
|
Reference in New Issue
Block a user