mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Add the sqlite3_update_hook() API. (CVS 2820)
FossilOrigin-Name: 36229018817eebfbfca7a66d2285e4faf7b39845
This commit is contained in:
28
manifest
28
manifest
@ -1,5 +1,5 @@
|
|||||||
C Move\smalloc(),\sfree(),\srealloc()\sand\sallocationSize()\sinto\sthe\sOs\svtbl.\s(CVS\s2819)
|
C Add\sthe\ssqlite3_update_hook()\sAPI.\s(CVS\s2820)
|
||||||
D 2005-12-15T10:50:54
|
D 2005-12-15T15:22:09
|
||||||
F Makefile.in e3c6b3a38d734d41574c04f2fc90d18de2b87102
|
F Makefile.in e3c6b3a38d734d41574c04f2fc90d18de2b87102
|
||||||
F Makefile.linux-gcc aee18d8a05546dcf1888bd4547e442008a49a092
|
F Makefile.linux-gcc aee18d8a05546dcf1888bd4547e442008a49a092
|
||||||
F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028
|
F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028
|
||||||
@ -40,15 +40,15 @@ F src/build.c 306dde3134acd8f1c9f3821d81c3cb598af91280
|
|||||||
F src/callback.c 62066afd516f220575e81b1a1239ab92a2eae252
|
F src/callback.c 62066afd516f220575e81b1a1239ab92a2eae252
|
||||||
F src/complete.c df1681cef40dec33a286006981845f87b194e7a4
|
F src/complete.c df1681cef40dec33a286006981845f87b194e7a4
|
||||||
F src/date.c bb079317bff6a2b78aba5c0d2ddae5f6f03acfb7
|
F src/date.c bb079317bff6a2b78aba5c0d2ddae5f6f03acfb7
|
||||||
F src/delete.c 6010a081edda9871895260def092e852f0bb60a0
|
F src/delete.c ebb83753b6eca870c90edf2de736ab547685c326
|
||||||
F src/experimental.c 50c1e3b34f752f4ac10c36f287db095c2b61766d
|
F src/experimental.c 50c1e3b34f752f4ac10c36f287db095c2b61766d
|
||||||
F src/expr.c abdd20121595e37cc2db3612f3c0e6f5db1a9652
|
F src/expr.c abdd20121595e37cc2db3612f3c0e6f5db1a9652
|
||||||
F src/func.c 25f1e5710b71cb345b492a18088f546188599f6b
|
F src/func.c 25f1e5710b71cb345b492a18088f546188599f6b
|
||||||
F src/hash.c 8747cf51d12de46512880dfcf1b68b4e24072863
|
F src/hash.c 8747cf51d12de46512880dfcf1b68b4e24072863
|
||||||
F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84
|
F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84
|
||||||
F src/insert.c 5393479164f317ea0aeec954c6500cafa097ef33
|
F src/insert.c a5629e462560d6aa9be7e0dafb6ed1518bb623bb
|
||||||
F src/legacy.c 59757d857ab95fcbb0ac27692d3201e35f093dd7
|
F src/legacy.c 59757d857ab95fcbb0ac27692d3201e35f093dd7
|
||||||
F src/main.c a12aa72335036bb22249418e222124abfc581b85
|
F src/main.c 066356cdc7f7fbef389c1849b1ccbeec96a5b348
|
||||||
F src/md5.c c5fdfa5c2593eaee2e32a5ce6c6927c986eaf217
|
F src/md5.c c5fdfa5c2593eaee2e32a5ce6c6927c986eaf217
|
||||||
F src/os.c 7b4a002d9c9421580276db55d2329636a604e8ef
|
F src/os.c 7b4a002d9c9421580276db55d2329636a604e8ef
|
||||||
F src/os.h e941992043b127fdb1bd114f0b4319ae1c4562a7
|
F src/os.h e941992043b127fdb1bd114f0b4319ae1c4562a7
|
||||||
@ -68,10 +68,10 @@ F src/printf.c f47a2f4b5387cd2ebb12e9117a1a5d6bd9a2b812
|
|||||||
F src/random.c ff5e9a8cad790e2a51cd4d2e7737dc8540e09d1d
|
F src/random.c ff5e9a8cad790e2a51cd4d2e7737dc8540e09d1d
|
||||||
F src/select.c 2292b065bc6be61e01aad39a2e1b93e332fb7e57
|
F src/select.c 2292b065bc6be61e01aad39a2e1b93e332fb7e57
|
||||||
F src/shell.c 4872acee1d2a826c73c914961e469e563204b7f9
|
F src/shell.c 4872acee1d2a826c73c914961e469e563204b7f9
|
||||||
F src/sqlite.h.in 9d587d1f78129f08f8923b544afb7774563125e1
|
F src/sqlite.h.in 31ab5ef5268c7f1ee909151a0fc45c659290ec15
|
||||||
F src/sqliteInt.h 09f9addda8de78e2b632597840b0ec6bb70a7c99
|
F src/sqliteInt.h 927c7fbc25d105c89aa90dc148c68c0b33051048
|
||||||
F src/table.c 486dcfce532685b53b5a2b5da8bba0ded6fb2316
|
F src/table.c 486dcfce532685b53b5a2b5da8bba0ded6fb2316
|
||||||
F src/tclsqlite.c 5753ac020a9a6619190d8dc125cb00bb0bc61c96
|
F src/tclsqlite.c c2303e1b1e6602b8cfef4dad00e4d76a47868c5e
|
||||||
F src/test1.c d6924b182773b2ad3b22e435e4d3bfd5a846da9e
|
F src/test1.c d6924b182773b2ad3b22e435e4d3bfd5a846da9e
|
||||||
F src/test2.c 36390cdfc70c08e5ee0b466d0654a117f398bbff
|
F src/test2.c 36390cdfc70c08e5ee0b466d0654a117f398bbff
|
||||||
F src/test3.c 7c97833e33496c2b69f4fe6b9882ac60a481da97
|
F src/test3.c 7c97833e33496c2b69f4fe6b9882ac60a481da97
|
||||||
@ -84,9 +84,9 @@ F src/update.c ec8e540617b116725b5a55c8d6b4db8bc67fdd7d
|
|||||||
F src/utf.c d2360f55ecd666f3e472738191f8dae717b95e5e
|
F src/utf.c d2360f55ecd666f3e472738191f8dae717b95e5e
|
||||||
F src/util.c 8bb5e0553692d36c769d6cd033eb7f68a7586648
|
F src/util.c 8bb5e0553692d36c769d6cd033eb7f68a7586648
|
||||||
F src/vacuum.c fbfdd3967fd34e2f260fafed88dcbf3c10856b94
|
F src/vacuum.c fbfdd3967fd34e2f260fafed88dcbf3c10856b94
|
||||||
F src/vdbe.c d09c185f4badac6c79f2a919cbf661e7b5618293
|
F src/vdbe.c a9acffc91d3e06af24dc1dcf5bf7d5e77460b1c5
|
||||||
F src/vdbe.h 8729a4ee16ff9aeab2af9667df3cf300ff978e13
|
F src/vdbe.h 8729a4ee16ff9aeab2af9667df3cf300ff978e13
|
||||||
F src/vdbeInt.h 0055c37eccbf3a189fd893a90f8eb6a5fa60c871
|
F src/vdbeInt.h 7b8b8c5dcb203243e4a9a4414c9e488473f67741
|
||||||
F src/vdbeapi.c b270b680cbc5d20b5a1abfdb08339667985df94e
|
F src/vdbeapi.c b270b680cbc5d20b5a1abfdb08339667985df94e
|
||||||
F src/vdbeaux.c f714ee9e91f1e47d4b6ae83798e0b263ffe224cc
|
F src/vdbeaux.c f714ee9e91f1e47d4b6ae83798e0b263ffe224cc
|
||||||
F src/vdbefifo.c 9efb94c8c3f4c979ebd0028219483f88e57584f5
|
F src/vdbefifo.c 9efb94c8c3f4c979ebd0028219483f88e57584f5
|
||||||
@ -153,7 +153,7 @@ F test/enc3.test f6a5f0b7b7f3a88f030d3143729b87cd5c86d837
|
|||||||
F test/expr.test 06381174d8c25fbbfd9ed335fe0cde835d39123d
|
F test/expr.test 06381174d8c25fbbfd9ed335fe0cde835d39123d
|
||||||
F test/fkey1.test 153004438d51e6769fb1ce165f6313972d6263ce
|
F test/fkey1.test 153004438d51e6769fb1ce165f6313972d6263ce
|
||||||
F test/func.test e891bebf5a939fd45c9453ad35e2280f41a30687
|
F test/func.test e891bebf5a939fd45c9453ad35e2280f41a30687
|
||||||
F test/hook.test f8605cde4c77b2c6a4a73723bf6c507796a64dda
|
F test/hook.test 4a5e598af0dd2436f757706e1650be3e4bbe30a6
|
||||||
F test/in.test cead6165aebbe0d451bb2263a307173acfeb6240
|
F test/in.test cead6165aebbe0d451bb2263a307173acfeb6240
|
||||||
F test/index.test 3871c47ec475f779f0b99dc36a3d177951995712
|
F test/index.test 3871c47ec475f779f0b99dc36a3d177951995712
|
||||||
F test/index2.test 9ad98243fd7fe833795a9cc662f371f0eed4ff4f
|
F test/index2.test 9ad98243fd7fe833795a9cc662f371f0eed4ff4f
|
||||||
@ -327,7 +327,7 @@ F www/tclsqlite.tcl ddcf912ea48695603c8ed7efb29f0812ef8d1b49
|
|||||||
F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
|
F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
|
||||||
F www/version3.tcl a99cf5f6d8bd4d5537584a2b342f0fb9fa601d8b
|
F www/version3.tcl a99cf5f6d8bd4d5537584a2b342f0fb9fa601d8b
|
||||||
F www/whentouse.tcl 97e2b5cd296f7d8057e11f44427dea8a4c2db513
|
F www/whentouse.tcl 97e2b5cd296f7d8057e11f44427dea8a4c2db513
|
||||||
P c1ed79f594fb85009c2e9e5e281cbe66a9d2fa17
|
P 81a41f66370ea7f6810dfb323ba5cea60f240edd
|
||||||
R e31df3de6303938f8025bb0d1c87c829
|
R eb3fb006708e7ea223867bd463f746c4
|
||||||
U danielk1977
|
U danielk1977
|
||||||
Z d08eb08fd8a87ac6ccdc5f99bd5ce249
|
Z d4836e8df9c05467b16e6242a013ac68
|
||||||
|
@ -1 +1 @@
|
|||||||
81a41f66370ea7f6810dfb323ba5cea60f240edd
|
36229018817eebfbfca7a66d2285e4faf7b39845
|
@ -12,7 +12,7 @@
|
|||||||
** This file contains C code routines that are called by the parser
|
** This file contains C code routines that are called by the parser
|
||||||
** in order to generate code for DELETE FROM statements.
|
** in order to generate code for DELETE FROM statements.
|
||||||
**
|
**
|
||||||
** $Id: delete.c,v 1.112 2005/12/06 12:52:59 danielk1977 Exp $
|
** $Id: delete.c,v 1.113 2005/12/15 15:22:09 danielk1977 Exp $
|
||||||
*/
|
*/
|
||||||
#include "sqliteInt.h"
|
#include "sqliteInt.h"
|
||||||
|
|
||||||
@ -215,6 +215,9 @@ void sqlite3DeleteFrom(
|
|||||||
}
|
}
|
||||||
if( !isView ){
|
if( !isView ){
|
||||||
sqlite3VdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb);
|
sqlite3VdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb);
|
||||||
|
if( !pParse->nested ){
|
||||||
|
sqlite3VdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
|
||||||
|
}
|
||||||
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
|
||||||
sqlite3VdbeAddOp(v, OP_Clear, pIdx->tnum, pIdx->iDb);
|
sqlite3VdbeAddOp(v, OP_Clear, pIdx->tnum, pIdx->iDb);
|
||||||
}
|
}
|
||||||
@ -380,6 +383,9 @@ void sqlite3GenerateRowDelete(
|
|||||||
addr = sqlite3VdbeAddOp(v, OP_NotExists, iCur, 0);
|
addr = sqlite3VdbeAddOp(v, OP_NotExists, iCur, 0);
|
||||||
sqlite3GenerateRowIndexDelete(db, v, pTab, iCur, 0);
|
sqlite3GenerateRowIndexDelete(db, v, pTab, iCur, 0);
|
||||||
sqlite3VdbeAddOp(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0));
|
sqlite3VdbeAddOp(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0));
|
||||||
|
if( count ){
|
||||||
|
sqlite3VdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
|
||||||
|
}
|
||||||
sqlite3VdbeJumpHere(v, addr);
|
sqlite3VdbeJumpHere(v, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
** This file contains C code routines that are called by the parser
|
** This file contains C code routines that are called by the parser
|
||||||
** to handle INSERT statements in SQLite.
|
** to handle INSERT statements in SQLite.
|
||||||
**
|
**
|
||||||
** $Id: insert.c,v 1.150 2005/12/06 12:52:59 danielk1977 Exp $
|
** $Id: insert.c,v 1.151 2005/12/15 15:22:09 danielk1977 Exp $
|
||||||
*/
|
*/
|
||||||
#include "sqliteInt.h"
|
#include "sqliteInt.h"
|
||||||
|
|
||||||
@ -1079,9 +1079,13 @@ void sqlite3CompleteInsertion(
|
|||||||
if( pParse->nested ){
|
if( pParse->nested ){
|
||||||
pik_flags = 0;
|
pik_flags = 0;
|
||||||
}else{
|
}else{
|
||||||
pik_flags = (OPFLAG_NCHANGE|(isUpdate?0:OPFLAG_LASTROWID));
|
pik_flags = OPFLAG_NCHANGE;
|
||||||
|
pik_flags |= (isUpdate?OPFLAG_ISUPDATE:OPFLAG_LASTROWID);
|
||||||
}
|
}
|
||||||
sqlite3VdbeAddOp(v, OP_Insert, base, pik_flags);
|
sqlite3VdbeAddOp(v, OP_Insert, base, pik_flags);
|
||||||
|
if( !pParse->nested ){
|
||||||
|
sqlite3VdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
|
||||||
|
}
|
||||||
|
|
||||||
if( isUpdate && rowidChng ){
|
if( isUpdate && rowidChng ){
|
||||||
sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
|
sqlite3VdbeAddOp(v, OP_Pop, 1, 0);
|
||||||
|
15
src/main.c
15
src/main.c
@ -14,7 +14,7 @@
|
|||||||
** other files are for internal use by SQLite and should not be
|
** other files are for internal use by SQLite and should not be
|
||||||
** accessed by users of the library.
|
** accessed by users of the library.
|
||||||
**
|
**
|
||||||
** $Id: main.c,v 1.309 2005/12/15 03:04:10 drh Exp $
|
** $Id: main.c,v 1.310 2005/12/15 15:22:09 danielk1977 Exp $
|
||||||
*/
|
*/
|
||||||
#include "sqliteInt.h"
|
#include "sqliteInt.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
@ -548,6 +548,19 @@ void *sqlite3_commit_hook(
|
|||||||
return pOld;
|
return pOld;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Register a callback to be invoked each time a row is updated,
|
||||||
|
** inserted or deleted using this database connection.
|
||||||
|
*/
|
||||||
|
void sqlite3_update_hook(
|
||||||
|
sqlite3 *db, /* Attach the hook to this database */
|
||||||
|
void (*xCallback)(void*,int,char const *,char const *,sqlite_int64),
|
||||||
|
void *pArg /* Argument to the function */
|
||||||
|
){
|
||||||
|
db->xUpdateCallback = xCallback;
|
||||||
|
db->pUpdateArg = pArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** This routine is called to create a connection to a database BTree
|
** This routine is called to create a connection to a database BTree
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
** This header file defines the interface that the SQLite library
|
** This header file defines the interface that the SQLite library
|
||||||
** presents to client programs.
|
** presents to client programs.
|
||||||
**
|
**
|
||||||
** @(#) $Id: sqlite.h.in,v 1.145 2005/12/15 10:11:32 danielk1977 Exp $
|
** @(#) $Id: sqlite.h.in,v 1.146 2005/12/15 15:22:09 danielk1977 Exp $
|
||||||
*/
|
*/
|
||||||
#ifndef _SQLITE3_H_
|
#ifndef _SQLITE3_H_
|
||||||
#define _SQLITE3_H_
|
#define _SQLITE3_H_
|
||||||
@ -1291,9 +1291,30 @@ sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
|
|||||||
*/
|
*/
|
||||||
void sqlite3_soft_heap_limit(int);
|
void sqlite3_soft_heap_limit(int);
|
||||||
|
|
||||||
|
/*
|
||||||
int sqlite3_set_io_routine(int, void *);
|
** Register a callback function with the database connection identified by the
|
||||||
void *sqlite3_get_io_routine(int);
|
** first argument to be invoked whenever a row is updated, inserted or deleted.
|
||||||
|
** Any callback set by a previous call to this function for the same
|
||||||
|
** database connection is overridden.
|
||||||
|
**
|
||||||
|
** The second argument is a pointer to the function to invoke when a
|
||||||
|
** row is updated, inserted or deleted. The first argument to the callback is
|
||||||
|
** a copy of the third argument to sqlite3_update_hook. The second callback
|
||||||
|
** argument is one of SQLITE_INSERT, SQLITE_DELETE or SQLITE_UPDATE, depending
|
||||||
|
** on the operation that caused the callback to be invoked. The third and
|
||||||
|
** fourth arguments to the callback contain pointers to the database and
|
||||||
|
** table name containing the affected row. The final callback parameter is
|
||||||
|
** the rowid of the row. In the case of an update, this is the rowid after
|
||||||
|
** the update takes place.
|
||||||
|
**
|
||||||
|
** The update hook is not invoked when internal system tables are
|
||||||
|
** modified (i.e. sqlite_master and sqlite_sequence).
|
||||||
|
*/
|
||||||
|
void sqlite3_update_hook(
|
||||||
|
sqlite3*,
|
||||||
|
void(*)(void *,int ,char const *,char const *,sqlite_int64),
|
||||||
|
void*
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Undo the hack that converts floating point types to integer for
|
** Undo the hack that converts floating point types to integer for
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
*************************************************************************
|
*************************************************************************
|
||||||
** Internal interface definitions for SQLite.
|
** Internal interface definitions for SQLite.
|
||||||
**
|
**
|
||||||
** @(#) $Id: sqliteInt.h,v 1.436 2005/12/15 10:11:32 danielk1977 Exp $
|
** @(#) $Id: sqliteInt.h,v 1.437 2005/12/15 15:22:09 danielk1977 Exp $
|
||||||
*/
|
*/
|
||||||
#ifndef _SQLITEINT_H_
|
#ifndef _SQLITEINT_H_
|
||||||
#define _SQLITEINT_H_
|
#define _SQLITEINT_H_
|
||||||
@ -441,6 +441,8 @@ struct sqlite3 {
|
|||||||
void *pProfileArg; /* Argument to profile function */
|
void *pProfileArg; /* Argument to profile function */
|
||||||
void *pCommitArg; /* Argument to xCommitCallback() */
|
void *pCommitArg; /* Argument to xCommitCallback() */
|
||||||
int (*xCommitCallback)(void*);/* Invoked at every commit. */
|
int (*xCommitCallback)(void*);/* Invoked at every commit. */
|
||||||
|
void *pUpdateArg;
|
||||||
|
void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
|
||||||
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;
|
||||||
@ -1234,6 +1236,7 @@ struct AuthContext {
|
|||||||
*/
|
*/
|
||||||
#define OPFLAG_NCHANGE 1 /* Set to update db->nChange */
|
#define OPFLAG_NCHANGE 1 /* Set to update db->nChange */
|
||||||
#define OPFLAG_LASTROWID 2 /* Set to update db->lastRowid */
|
#define OPFLAG_LASTROWID 2 /* Set to update db->lastRowid */
|
||||||
|
#define OPFLAG_ISUPDATE 4 /* This OP_Insert is an sql UPDATE */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Each trigger present in the database schema is stored as an instance of
|
* Each trigger present in the database schema is stored as an instance of
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
*************************************************************************
|
*************************************************************************
|
||||||
** A TCL Interface to SQLite
|
** A TCL Interface to SQLite
|
||||||
**
|
**
|
||||||
** $Id: tclsqlite.c,v 1.138 2005/12/15 10:11:32 danielk1977 Exp $
|
** $Id: tclsqlite.c,v 1.139 2005/12/15 15:22:10 danielk1977 Exp $
|
||||||
*/
|
*/
|
||||||
#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
|
#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
|
||||||
|
|
||||||
@ -99,6 +99,7 @@ struct SqliteDb {
|
|||||||
char *zAuth; /* The authorization callback routine */
|
char *zAuth; /* The authorization callback routine */
|
||||||
char *zNull; /* Text to substitute for an SQL NULL value */
|
char *zNull; /* Text to substitute for an SQL NULL value */
|
||||||
SqlFunc *pFunc; /* List of SQL functions */
|
SqlFunc *pFunc; /* List of SQL functions */
|
||||||
|
Tcl_Obj *pUpdateHook; /* Update hook 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() */
|
||||||
Tcl_Obj *pCollateNeeded; /* Collation needed script */
|
Tcl_Obj *pCollateNeeded; /* Collation needed script */
|
||||||
@ -210,6 +211,12 @@ static void DbDeleteCmd(void *db){
|
|||||||
if( pDb->zNull ){
|
if( pDb->zNull ){
|
||||||
Tcl_Free(pDb->zNull);
|
Tcl_Free(pDb->zNull);
|
||||||
}
|
}
|
||||||
|
if( pDb->pUpdateHook ){
|
||||||
|
Tcl_DecrRefCount(pDb->pUpdateHook);
|
||||||
|
}
|
||||||
|
if( pDb->pCollateNeeded ){
|
||||||
|
Tcl_DecrRefCount(pDb->pCollateNeeded);
|
||||||
|
}
|
||||||
Tcl_Free((char*)pDb);
|
Tcl_Free((char*)pDb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,6 +304,29 @@ static int DbCommitHandler(void *cd){
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void DbUpdateHandler(
|
||||||
|
void *p,
|
||||||
|
int op,
|
||||||
|
const char *zDb,
|
||||||
|
const char *zTbl,
|
||||||
|
sqlite_int64 rowid
|
||||||
|
){
|
||||||
|
SqliteDb *pDb = (SqliteDb *)p;
|
||||||
|
Tcl_Obj *pCmd;
|
||||||
|
|
||||||
|
assert( pDb->pUpdateHook );
|
||||||
|
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
|
||||||
|
|
||||||
|
pCmd = Tcl_DuplicateObj(pDb->pUpdateHook);
|
||||||
|
Tcl_IncrRefCount(pCmd);
|
||||||
|
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(
|
||||||
|
( (op==SQLITE_INSERT)?"INSERT":(op==SQLITE_UPDATE)?"UPDATE":"DELETE"), -1));
|
||||||
|
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zDb, -1));
|
||||||
|
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1));
|
||||||
|
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(rowid));
|
||||||
|
Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT);
|
||||||
|
}
|
||||||
|
|
||||||
static void tclCollateNeeded(
|
static void tclCollateNeeded(
|
||||||
void *pCtx,
|
void *pCtx,
|
||||||
sqlite3 *db,
|
sqlite3 *db,
|
||||||
@ -625,7 +655,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
|||||||
"nullvalue", "onecolumn", "profile",
|
"nullvalue", "onecolumn", "profile",
|
||||||
"progress", "rekey", "soft_heap_limit",
|
"progress", "rekey", "soft_heap_limit",
|
||||||
"timeout", "total_changes", "trace",
|
"timeout", "total_changes", "trace",
|
||||||
"transaction", "version", 0
|
"transaction", "update_hook", "version",
|
||||||
|
0
|
||||||
};
|
};
|
||||||
enum DB_enum {
|
enum DB_enum {
|
||||||
DB_AUTHORIZER, DB_BUSY, DB_CACHE,
|
DB_AUTHORIZER, DB_BUSY, DB_CACHE,
|
||||||
@ -636,7 +667,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
|||||||
DB_NULLVALUE, DB_ONECOLUMN, DB_PROFILE,
|
DB_NULLVALUE, DB_ONECOLUMN, DB_PROFILE,
|
||||||
DB_PROGRESS, DB_REKEY, DB_SOFT_HEAP_LIMIT,
|
DB_PROGRESS, DB_REKEY, DB_SOFT_HEAP_LIMIT,
|
||||||
DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
|
DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
|
||||||
DB_TRANSACTION, DB_VERSION
|
DB_TRANSACTION, 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 */
|
||||||
|
|
||||||
@ -1839,6 +1870,33 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** $db update_hook ?script?
|
||||||
|
*/
|
||||||
|
case DB_UPDATE_HOOK: {
|
||||||
|
if( objc!=2 && objc!=3 ){
|
||||||
|
Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
|
||||||
|
return TCL_ERROR;
|
||||||
|
}
|
||||||
|
if( pDb->pUpdateHook ){
|
||||||
|
Tcl_SetObjResult(interp, pDb->pUpdateHook);
|
||||||
|
if( objc==3 ){
|
||||||
|
Tcl_DecrRefCount(pDb->pUpdateHook);
|
||||||
|
pDb->pUpdateHook = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( objc==3 ){
|
||||||
|
if( Tcl_GetCharLength(objv[2])>0 ){
|
||||||
|
pDb->pUpdateHook = objv[2];
|
||||||
|
Tcl_IncrRefCount(pDb->pUpdateHook);
|
||||||
|
sqlite3_update_hook(pDb->db, DbUpdateHandler, pDb);
|
||||||
|
}else{
|
||||||
|
sqlite3_update_hook(pDb->db, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* $db version
|
/* $db version
|
||||||
**
|
**
|
||||||
** Return the version string for this database.
|
** Return the version string for this database.
|
||||||
|
84
src/vdbe.c
84
src/vdbe.c
@ -43,7 +43,7 @@
|
|||||||
** in this file for details. If in doubt, do not deviate from existing
|
** in this file for details. If in doubt, do not deviate from existing
|
||||||
** commenting and indentation practices when changing or adding code.
|
** commenting and indentation practices when changing or adding code.
|
||||||
**
|
**
|
||||||
** $Id: vdbe.c,v 1.504 2005/12/09 20:02:06 drh Exp $
|
** $Id: vdbe.c,v 1.505 2005/12/15 15:22:10 danielk1977 Exp $
|
||||||
*/
|
*/
|
||||||
#include "sqliteInt.h"
|
#include "sqliteInt.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
@ -160,13 +160,16 @@ static void popStack(Mem **ppTos, int N){
|
|||||||
** Allocate cursor number iCur. Return a pointer to it. Return NULL
|
** Allocate cursor number iCur. Return a pointer to it. Return NULL
|
||||||
** if we run out of memory.
|
** if we run out of memory.
|
||||||
*/
|
*/
|
||||||
static Cursor *allocateCursor(Vdbe *p, int iCur){
|
static Cursor *allocateCursor(Vdbe *p, int iCur, int iDb){
|
||||||
Cursor *pCx;
|
Cursor *pCx;
|
||||||
assert( iCur<p->nCursor );
|
assert( iCur<p->nCursor );
|
||||||
if( p->apCsr[iCur] ){
|
if( p->apCsr[iCur] ){
|
||||||
sqlite3VdbeFreeCursor(p->apCsr[iCur]);
|
sqlite3VdbeFreeCursor(p->apCsr[iCur]);
|
||||||
}
|
}
|
||||||
p->apCsr[iCur] = pCx = sqliteMalloc( sizeof(Cursor) );
|
p->apCsr[iCur] = pCx = sqliteMalloc( sizeof(Cursor) );
|
||||||
|
if( pCx ){
|
||||||
|
pCx->iDb = iDb;
|
||||||
|
}
|
||||||
return pCx;
|
return pCx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2525,7 +2528,7 @@ case OP_OpenWrite: { /* no-push */
|
|||||||
assert( p2>=2 );
|
assert( p2>=2 );
|
||||||
}
|
}
|
||||||
assert( i>=0 );
|
assert( i>=0 );
|
||||||
pCur = allocateCursor(p, i);
|
pCur = allocateCursor(p, i, iDb);
|
||||||
if( pCur==0 ) goto no_mem;
|
if( pCur==0 ) goto no_mem;
|
||||||
pCur->nullRow = 1;
|
pCur->nullRow = 1;
|
||||||
if( pX==0 ) break;
|
if( pX==0 ) break;
|
||||||
@ -2603,7 +2606,7 @@ case OP_OpenVirtual: { /* no-push */
|
|||||||
int i = pOp->p1;
|
int i = pOp->p1;
|
||||||
Cursor *pCx;
|
Cursor *pCx;
|
||||||
assert( i>=0 );
|
assert( i>=0 );
|
||||||
pCx = allocateCursor(p, i);
|
pCx = allocateCursor(p, i, -1);
|
||||||
if( pCx==0 ) goto no_mem;
|
if( pCx==0 ) goto no_mem;
|
||||||
pCx->nullRow = 1;
|
pCx->nullRow = 1;
|
||||||
rc = sqlite3BtreeFactory(db, 0, 1, TEMP_PAGES, &pCx->pBt);
|
rc = sqlite3BtreeFactory(db, 0, 1, TEMP_PAGES, &pCx->pBt);
|
||||||
@ -2655,7 +2658,7 @@ case OP_OpenPseudo: { /* no-push */
|
|||||||
int i = pOp->p1;
|
int i = pOp->p1;
|
||||||
Cursor *pCx;
|
Cursor *pCx;
|
||||||
assert( i>=0 );
|
assert( i>=0 );
|
||||||
pCx = allocateCursor(p, i);
|
pCx = allocateCursor(p, i, -1);
|
||||||
if( pCx==0 ) goto no_mem;
|
if( pCx==0 ) goto no_mem;
|
||||||
pCx->nullRow = 1;
|
pCx->nullRow = 1;
|
||||||
pCx->pseudoTable = 1;
|
pCx->pseudoTable = 1;
|
||||||
@ -3194,7 +3197,7 @@ case OP_NewRowid: {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opcode: Insert P1 P2 *
|
/* Opcode: Insert P1 P2 P3
|
||||||
**
|
**
|
||||||
** Write an entry into the table of cursor P1. A new entry is
|
** Write an entry into the table of cursor P1. A new entry is
|
||||||
** created if it doesn't already exist or the data for an existing
|
** created if it doesn't already exist or the data for an existing
|
||||||
@ -3261,12 +3264,23 @@ case OP_Insert: { /* no-push */
|
|||||||
pC->rowidIsValid = 0;
|
pC->rowidIsValid = 0;
|
||||||
pC->deferredMoveto = 0;
|
pC->deferredMoveto = 0;
|
||||||
pC->cacheValid = 0;
|
pC->cacheValid = 0;
|
||||||
|
|
||||||
|
/* Invoke the update-hook if required. */
|
||||||
|
if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p3 ){
|
||||||
|
const char *zDb = db->aDb[pC->iDb].zName;
|
||||||
|
const char *zTbl = pOp->p3;
|
||||||
|
int op = ((pOp->p2 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
|
||||||
|
assert( pC->isTable );
|
||||||
|
db->xUpdateCallback(db->pUpdateArg, op, zDb, zTbl, iKey);
|
||||||
|
assert( pC->iDb>=0 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
popStack(&pTos, 2);
|
popStack(&pTos, 2);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Opcode: Delete P1 P2 *
|
/* Opcode: Delete P1 P2 P3
|
||||||
**
|
**
|
||||||
** Delete the record at which the P1 cursor is currently pointing.
|
** Delete the record at which the P1 cursor is currently pointing.
|
||||||
**
|
**
|
||||||
@ -3287,11 +3301,37 @@ case OP_Delete: { /* no-push */
|
|||||||
pC = p->apCsr[i];
|
pC = p->apCsr[i];
|
||||||
assert( pC!=0 );
|
assert( pC!=0 );
|
||||||
if( pC->pCursor!=0 ){
|
if( pC->pCursor!=0 ){
|
||||||
|
i64 iKey;
|
||||||
|
|
||||||
|
/* If the update-hook will be invoked, set iKey to the rowid of the
|
||||||
|
** row being deleted.
|
||||||
|
*/
|
||||||
|
if( db->xUpdateCallback && pOp->p3 ){
|
||||||
|
assert( pC->isTable );
|
||||||
|
if( pC->rowidIsValid ){
|
||||||
|
iKey = pC->lastRowid;
|
||||||
|
}else{
|
||||||
|
rc = sqlite3BtreeKeySize(pC->pCursor, &iKey);
|
||||||
|
if( rc ){
|
||||||
|
goto abort_due_to_error;
|
||||||
|
}
|
||||||
|
iKey = keyToInt(iKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rc = sqlite3VdbeCursorMoveto(pC);
|
rc = sqlite3VdbeCursorMoveto(pC);
|
||||||
if( rc ) goto abort_due_to_error;
|
if( rc ) goto abort_due_to_error;
|
||||||
rc = sqlite3BtreeDelete(pC->pCursor);
|
rc = sqlite3BtreeDelete(pC->pCursor);
|
||||||
pC->nextRowidValid = 0;
|
pC->nextRowidValid = 0;
|
||||||
pC->cacheValid = 0;
|
pC->cacheValid = 0;
|
||||||
|
|
||||||
|
/* Invoke the update-hook if required. */
|
||||||
|
if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p3 ){
|
||||||
|
const char *zDb = db->aDb[pC->iDb].zName;
|
||||||
|
const char *zTbl = pOp->p3;
|
||||||
|
db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, zTbl, iKey);
|
||||||
|
assert( pC->iDb>=0 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++;
|
if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++;
|
||||||
break;
|
break;
|
||||||
@ -3826,6 +3866,35 @@ case OP_Destroy: {
|
|||||||
** See also: Destroy
|
** See also: Destroy
|
||||||
*/
|
*/
|
||||||
case OP_Clear: { /* no-push */
|
case OP_Clear: { /* no-push */
|
||||||
|
Btree *pBt = db->aDb[pOp->p2].pBt;
|
||||||
|
if( db->xUpdateCallback && pOp->p3 ){
|
||||||
|
const char *zDb = db->aDb[pOp->p2].zName;
|
||||||
|
const char *zTbl = pOp->p3;
|
||||||
|
BtCursor *pCur = 0;
|
||||||
|
int fin = 0;
|
||||||
|
|
||||||
|
rc = sqlite3BtreeCursor(pBt, pOp->p1, 0, 0, 0, &pCur);
|
||||||
|
if( rc!=SQLITE_OK ){
|
||||||
|
goto abort_due_to_error;
|
||||||
|
}
|
||||||
|
for(
|
||||||
|
rc=sqlite3BtreeFirst(pCur, &fin);
|
||||||
|
rc==SQLITE_OK && !fin;
|
||||||
|
rc=sqlite3BtreeNext(pCur, &fin)
|
||||||
|
){
|
||||||
|
i64 iKey;
|
||||||
|
rc = sqlite3BtreeKeySize(pCur, &iKey);
|
||||||
|
if( rc ){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iKey = keyToInt(iKey);
|
||||||
|
db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, zTbl, iKey);
|
||||||
|
}
|
||||||
|
sqlite3BtreeCloseCursor(pCur);
|
||||||
|
if( rc!=SQLITE_OK ){
|
||||||
|
goto abort_due_to_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, pOp->p1);
|
rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, pOp->p1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -4344,7 +4413,6 @@ case OP_Expire: { /* no-push */
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* An other opcode is illegal...
|
/* An other opcode is illegal...
|
||||||
*/
|
*/
|
||||||
default: {
|
default: {
|
||||||
|
@ -60,6 +60,7 @@ typedef unsigned char Bool;
|
|||||||
*/
|
*/
|
||||||
struct Cursor {
|
struct Cursor {
|
||||||
BtCursor *pCursor; /* The cursor structure of the backend */
|
BtCursor *pCursor; /* The cursor structure of the backend */
|
||||||
|
int iDb; /* Index of cursor database in db->aDb[] (or -1) */
|
||||||
i64 lastRowid; /* Last rowid from a Next or NextIdx operation */
|
i64 lastRowid; /* Last rowid from a Next or NextIdx operation */
|
||||||
i64 nextRowid; /* Next rowid returned by OP_NewRowid */
|
i64 nextRowid; /* Next rowid returned by OP_NewRowid */
|
||||||
Bool zeroed; /* True if zeroed out and ready for reuse */
|
Bool zeroed; /* True if zeroed out and ready for reuse */
|
||||||
|
125
test/hook.test
125
test/hook.test
@ -14,8 +14,9 @@
|
|||||||
# The focus of the tests in this file is the following interface:
|
# The focus of the tests in this file is the following interface:
|
||||||
#
|
#
|
||||||
# sqlite_commit_hook
|
# sqlite_commit_hook
|
||||||
|
# sqlite_update_hook (tests hook-4 onwards)
|
||||||
#
|
#
|
||||||
# $Id: hook.test,v 1.5 2004/06/29 12:39:08 drh Exp $
|
# $Id: hook.test,v 1.6 2005/12/15 15:22:10 danielk1977 Exp $
|
||||||
|
|
||||||
set testdir [file dirname $argv0]
|
set testdir [file dirname $argv0]
|
||||||
source $testdir/tester.tcl
|
source $testdir/tester.tcl
|
||||||
@ -90,4 +91,126 @@ do_test hook-3.9 {
|
|||||||
set ::commit_cnt
|
set ::commit_cnt
|
||||||
} {}
|
} {}
|
||||||
|
|
||||||
|
# Very simple tests. Test that the update hook is invoked correctly for INSERT,
|
||||||
|
# DELETE and UPDATE statements, including DELETE statements with no WHERE
|
||||||
|
# clause.
|
||||||
|
#
|
||||||
|
do_test hook-4.1 {
|
||||||
|
catchsql {
|
||||||
|
DROP TABLE t1;
|
||||||
|
}
|
||||||
|
execsql {
|
||||||
|
CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
|
||||||
|
INSERT INTO t1 VALUES(1, 'one');
|
||||||
|
INSERT INTO t1 VALUES(2, 'two');
|
||||||
|
INSERT INTO t1 VALUES(3, 'three');
|
||||||
|
}
|
||||||
|
db update_hook [list lappend ::update_hook]
|
||||||
|
} {}
|
||||||
|
do_test hook-4.2 {
|
||||||
|
execsql {
|
||||||
|
INSERT INTO t1 VALUES(4, 'four');
|
||||||
|
DELETE FROM t1 WHERE b = 'two';
|
||||||
|
UPDATE t1 SET b = '' WHERE a = 1 OR a = 3;
|
||||||
|
DELETE FROM t1;
|
||||||
|
}
|
||||||
|
set ::update_hook
|
||||||
|
} [list \
|
||||||
|
INSERT main t1 4 \
|
||||||
|
DELETE main t1 2 \
|
||||||
|
UPDATE main t1 1 \
|
||||||
|
UPDATE main t1 3 \
|
||||||
|
DELETE main t1 1 \
|
||||||
|
DELETE main t1 3 \
|
||||||
|
DELETE main t1 4 \
|
||||||
|
]
|
||||||
|
|
||||||
|
# Check that the update-hook is invoked for rows modified by trigger
|
||||||
|
# bodies.
|
||||||
|
#
|
||||||
|
set ::update_hook {}
|
||||||
|
do_test hook-5.1 {
|
||||||
|
catchsql {
|
||||||
|
DROP TABLE t2;
|
||||||
|
}
|
||||||
|
execsql {
|
||||||
|
CREATE TABLE t2(c INTEGER PRIMARY KEY, d);
|
||||||
|
CREATE TRIGGER t1_trigger AFTER INSERT ON t1 BEGIN
|
||||||
|
INSERT INTO t2 VALUES(new.a, new.b);
|
||||||
|
UPDATE t2 SET d = d || ' via trigger' WHERE new.a = c;
|
||||||
|
DELETE FROM t2 WHERE new.a = c;
|
||||||
|
END;
|
||||||
|
}
|
||||||
|
} {}
|
||||||
|
do_test hook-5.2 {
|
||||||
|
execsql {
|
||||||
|
INSERT INTO t1 VALUES(1, 'one');
|
||||||
|
INSERT INTO t1 VALUES(2, 'two');
|
||||||
|
}
|
||||||
|
set ::update_hook
|
||||||
|
} [list \
|
||||||
|
INSERT main t1 1 \
|
||||||
|
INSERT main t2 1 \
|
||||||
|
UPDATE main t2 1 \
|
||||||
|
DELETE main t2 1 \
|
||||||
|
INSERT main t1 2 \
|
||||||
|
INSERT main t2 2 \
|
||||||
|
UPDATE main t2 2 \
|
||||||
|
DELETE main t2 2 \
|
||||||
|
]
|
||||||
|
|
||||||
|
set ::update_hook {}
|
||||||
|
do_test hook-6.1 {
|
||||||
|
file delete -force test2.db
|
||||||
|
execsql {
|
||||||
|
ATTACH 'test2.db' AS aux;
|
||||||
|
CREATE TABLE aux.t3(a INTEGER PRIMARY KEY, b);
|
||||||
|
INSERT INTO aux.t3 SELECT * FROM t1;
|
||||||
|
UPDATE t3 SET b = 'two or so' WHERE a = 2;
|
||||||
|
DELETE FROM t3;
|
||||||
|
}
|
||||||
|
set ::update_hook
|
||||||
|
} [list \
|
||||||
|
INSERT aux t3 1 \
|
||||||
|
INSERT aux t3 2 \
|
||||||
|
UPDATE aux t3 2 \
|
||||||
|
DELETE aux t3 1 \
|
||||||
|
DELETE aux t3 2 \
|
||||||
|
]
|
||||||
|
|
||||||
|
# Do some sorting, grouping, compound queries, population and depopulation
|
||||||
|
# of indices, to make sure the update-hook is not invoked incorrectly.
|
||||||
|
#
|
||||||
|
set ::update_hook {}
|
||||||
|
do_test hook-7.1 {
|
||||||
|
execsql {
|
||||||
|
DROP TRIGGER t1_trigger;
|
||||||
|
CREATE INDEX t1_i ON t1(b);
|
||||||
|
INSERT INTO t1 VALUES(3, 'three');
|
||||||
|
UPDATE t1 SET b = '';
|
||||||
|
DELETE FROM t1 WHERE a > 1;
|
||||||
|
}
|
||||||
|
set ::update_hook
|
||||||
|
} [list \
|
||||||
|
INSERT main t1 3 \
|
||||||
|
UPDATE main t1 1 \
|
||||||
|
UPDATE main t1 2 \
|
||||||
|
UPDATE main t1 3 \
|
||||||
|
DELETE main t1 2 \
|
||||||
|
DELETE main t1 3 \
|
||||||
|
]
|
||||||
|
set ::update_hook {}
|
||||||
|
do_test hook-7.2 {
|
||||||
|
execsql {
|
||||||
|
SELECT * FROM t1 UNION SELECT * FROM t3;
|
||||||
|
SELECT * FROM t1 UNION ALL SELECT * FROM t3;
|
||||||
|
SELECT * FROM t1 INTERSECT SELECT * FROM t3;
|
||||||
|
SELECT * FROM t1 EXCEPT SELECT * FROM t3;
|
||||||
|
SELECT * FROM t1 ORDER BY b;
|
||||||
|
SELECT * FROM t1 GROUP BY b;
|
||||||
|
}
|
||||||
|
set ::update_hook
|
||||||
|
} [list]
|
||||||
|
|
||||||
finish_test
|
finish_test
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user