Allow CREATE and DROP TRIGGER on attached databases. (CVS 1488)

FossilOrigin-Name: 4060a37d0baaa60c50f2dde4a1ab344133fcabbb
This commit is contained in:
danielk1977 2004-05-29 02:37:19 +00:00
parent 51846b56ed
commit ef2cb63e9e
11 changed files with 211 additions and 398 deletions

View File

@ -1,5 +1,5 @@
C Factor\scommon\scode\sfor\sgenerating\sindex\skeys\sinto\sa\sprocedure.\s\sOther\nspeed\simprovements\sand\sbug\sfixes.\s(CVS\s1487)
D 2004-05-28T16:00:22
C Allow\sCREATE\sand\sDROP\sTRIGGER\son\sattached\sdatabases.\s(CVS\s1488)
D 2004-05-29T02:37:19
F Makefile.in ab7b0d5118e2da97bac66be8684a1034e3500f5a
F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906
F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd
@ -26,7 +26,7 @@ F src/attach.c c315c58cb16fd6e913b3bfa6412aedecb4567fa5
F src/auth.c 5c2f0bea4729c98c2be3b69d6b466fc51448fe79
F src/btree.c 6db76fbf63efd6008c5e6cb038ea40f94abffcf7
F src/btree.h b65140b5ae891f30d2a39e64b9f0343225553545
F src/build.c 60ec4b38c0f158c9f2e4778ef6af13d19af7bfcd
F src/build.c ed09cd54a48ef2ef700c7e3a63b5e35224bde9cc
F src/date.c 0eb922af5c5f5e2455f8dc2f98023ed3e04a857e
F src/delete.c 72f8febf6170cda830f509c8f9dffbed3df3596c
F src/encode.c a876af473d1d636faa3dca51c7571f2e007eea37
@ -48,28 +48,28 @@ F src/os_win.c 92b51a38437b98d8aa3ac05b57c71e1d1092e5be
F src/os_win.h 5d41af24caaef6c13a2d8e2399caa1c57d45c84d
F src/pager.c 6ff6b906427d4824099140776cb8768f922f3dc5
F src/pager.h 78a00ac280899bcba1a89dc51585dcae6b7b3253
F src/parse.y 9d3be712abc9005495701efbec741c58408f1343
F src/parse.y fbb2378795cad3f6141836fb2035b97bd5ddad4e
F src/pragma.c 0c17b613d719c62a0dbad659b7d8a6e7ce7e9733
F src/printf.c ef750e8e2398ca7e8b58be991075f08c6a7f0e53
F src/random.c eff68e3f257e05e81eae6c4d50a51eb88beb4ff3
F src/select.c ee54bf2faa76f5e30832fd589a1f10c485047469
F src/shell.c ed4d237b3e52a0a42512bfcc53530e46de20c28f
F src/sqlite.h.in edc6408c7f53c2104f781a76b926036e17018ec9
F src/sqliteInt.h d95d08442d19e2ee592ce1ec7865cbbcf23640bd
F src/sqliteInt.h 01f9250ee3a1ab681b7ed91ad2b3748c2f230521
F src/table.c af14284fa36c8d41f6829e3f2819dce07d3e2de2
F src/tclsqlite.c 877d0b96013a25b03ed6bd2d32917c42e84403bc
F src/tclsqlite.c b314f12760547e4ef090e055f1298f70627450d3
F src/test1.c 32934478366531503d634968db414df17cb38238
F src/test2.c 6195a1ca2c8d0d2d93644e86da3289b403486872
F src/test3.c 5e4a6d596f982f6f47a5f9f75ede9b4a3b739968
F src/test4.c 34848a9fd31aa65857b20a8bfc03aff77d8c3426
F src/test5.c 9a1f15133f6955f067c5246e564723b5f23ff221
F src/tokenize.c 50a87c7414de54a008427c9fed22e4e86efb6844
F src/trigger.c 9ab75040aec65b593b54a7c1d4546f2f9ca058ef
F src/trigger.c 9040e5dd7e5586e863c20acdca6808e8f7bb9727
F src/update.c 96461bcf4e946697e83c09c77c7e61b545a2f66e
F src/utf.c d87fffc1ea7e52d73014ccea06afe1382bbb28b2
F src/util.c 4df9d9b0d930d81ec581bcb68748e7c48bdc8c7d
F src/vacuum.c 8734f89742f246abd91dbd3e087fc153bddbfbad
F src/vdbe.c 68f3583ba2cb13ef0121be99e0edccfff133281a
F src/vdbe.c ea010d63dfdf84b7d23781144fe2cd11add2c1bd
F src/vdbe.h e73f890e0f2a6c42b183d7d6937947930fe4fdeb
F src/vdbeInt.h c2bcd6e5a6e6a3753e4c5a368629c3a625719bfc
F src/vdbeapi.c 0c5d64c81871cb4fe5407e639604ee95738b6942
@ -77,9 +77,9 @@ F src/vdbeaux.c bbcf1bb953526130495b01b23751cf756cfed2fb
F src/vdbemem.c c97c145ff6d9fc5b4236704c04a65849117e6214
F src/where.c efe5d25fe18cd7381722457898cd863e84097a0c
F test/all.test 569a92a8ee88f5300c057cc4a8f50fbbc69a3242
F test/attach.test cb9b884344e6cfa5e165965d5b1adea679a24c83
F test/attach.test e872e1cf3e97949727d1a2c9582efeaf04b192a3
F test/attach2.test 5472d442bb2ef1ee587e0ae7472bb68b52509a38
F test/attach3.test 8c55071e4629fe781f3b1955454db2b7f33c943b
F test/attach3.test 65c52f1e5f435518db06a877eed6afe2cac652c9
F test/auth.test 95809b8f6a9bec18b94d28cafd03fe27d2f8a9e9
F test/bigfile.test ea904b853ce2d703b16c5ce90e2b54951bc1ae81
F test/bigrow.test 8ab252dba108f12ad64e337b0f2ff31a807ac578
@ -151,7 +151,7 @@ F test/thread1.test 53f050d5be6932d9430df7756edd379366508ff6
F test/threadtest1.c f7f896e62ed46feae1dc411114a48c15a0f82ee2
F test/threadtest2.c d94ca4114fd1504f7e0ae724bcd83d4b40931d86
F test/trans.test 0cb8256daff1ae0da75321e00125338c6681158d
F test/trigger1.test 4538c1c7d6bbca5dfe619ea6e1682b07ece95b21
F test/trigger1.test 99b7cd9a568ac60aa04bbc3b9db9575ffa97709a
F test/trigger2.test 0767ab30cb5a2c8402c8524f3d566b410b6f5263
F test/trigger3.test a95ccace88291449f5eae7139ec438a42f90654d
F test/trigger4.test 542afce45774e8f8e1130b96b8675f414d6e4bd8
@ -204,7 +204,7 @@ F www/sqlite.tcl 3c83b08cf9f18aa2d69453ff441a36c40e431604
F www/tclsqlite.tcl b9271d44dcf147a93c98f8ecf28c927307abd6da
F www/vdbe.tcl 9b9095d4495f37697fd1935d10e14c6015e80aa1
F www/whentouse.tcl a8335bce47cc2fddb07f19052cb0cb4d9129a8e4
P c8a40218c20cf5d0abad330e8fa59ca4c36e7608
R 3143f6cfe246debfe7547a873f48094e
U drh
Z 33a80435a29a583c43c0f69454c7f7ae
P 6661bb5f9c1692f94b8b7d900b6be07f027e6324
R fde9d916f771cc8e55efbaa7078f8e6a
U danielk1977
Z b5bca915020b46ffea1984f7a4d9cf29

View File

@ -1 +1 @@
6661bb5f9c1692f94b8b7d900b6be07f027e6324
4060a37d0baaa60c50f2dde4a1ab344133fcabbb

View File

@ -23,7 +23,7 @@
** ROLLBACK
** PRAGMA
**
** $Id: build.c,v 1.197 2004/05/28 16:00:22 drh Exp $
** $Id: build.c,v 1.198 2004/05/29 02:37:19 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
@ -425,7 +425,7 @@ int findDb(sqlite3 *db, Token *pName){
return -1;
}
static int resolveSchemaName(
int sqlite3TwoPartName(
Parse *pParse,
Token *pName1,
Token *pName2,
@ -501,7 +501,7 @@ void sqlite3StartTable(
** set to the index of the database that the table or view is to be
** created in.
*/
iDb = resolveSchemaName(pParse, pName1, pName2, &pName);
iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
if( iDb<0 ) return;
if( isTemp && iDb>1 ){
/* If creating a temp table, the name may not be qualified */
@ -1166,7 +1166,7 @@ void sqlite3CreateView(
sqlite3SelectDelete(pSelect);
return;
}
resolveSchemaName(pParse, pName1, pName2, &pName);
sqlite3TwoPartName(pParse, pName1, pName2, &pName);
if( sqlite3FixInit(&sFix, pParse, p->iDb, "view", pName)
&& sqlite3FixSelect(&sFix, pSelect)
){
@ -1606,7 +1606,7 @@ void sqlite3CreateIndex(
Parse *pParse, /* All information about this parse */
Token *pName1, /* First part of index name. May be NULL */
Token *pName2, /* Second part of index name. May be NULL */
Token *pTblName, /* Name of the table to index. Use pParse->pNewTable if 0 */
SrcList *pTblName, /* Name of the table to index. Use pParse->pNewTable if 0 */
IdList *pList, /* A list of columns to be indexed */
int onError, /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
Token *pStart, /* The CREATE token that begins a CREATE TABLE statement */
@ -1624,50 +1624,38 @@ void sqlite3CreateIndex(
int iDb; /* Index of the database that is being written */
Token *pName = 0; /* Unqualified name of the index to create */
/*
if( pParse->nErr || sqlite3_malloc_failed ) goto exit_create_index;
if( db->init.busy
&& sqlite3FixInit(&sFix, pParse, db->init.iDb, "index", pName)
&& sqlite3FixSrcList(&sFix, pTable)
){
goto exit_create_index;
}
*/
/*
** Find the table that is to be indexed. Return early if not found.
*/
if( pTblName!=0 ){
char *zTblName;
/* Use the two-part index name to determine the database
** to search for the table. If no database name is specified,
** iDb is set to 0. In this case search both the temp and main
** databases for the named table.
** to search for the table. 'Fix' the table name to this db
** before looking up the table.
*/
assert( pName1 && pName2 );
iDb = resolveSchemaName(pParse, pName1, pName2, &pName);
iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
if( iDb<0 ) goto exit_create_index;
/* Now search for the table in the database iDb. If iDb is
** zero, then search both the "main" and "temp" databases.
/* If the index name was unqualified, check if the the table
** is a temp table. If so, set the database to 1.
*/
zTblName = sqlite3TableNameFromToken(pTblName);
if( !zTblName ){
pParse->nErr++;
pParse->rc = SQLITE_NOMEM;
pTab = sqlite3SrcListLookup(pParse, pTblName);
if( pName2 && pName2->n==0 && pTab && pTab->iDb==1 ){
iDb = 1;
}
if( sqlite3FixInit(&sFix, pParse, iDb, "index", pName) &&
sqlite3FixSrcList(&sFix, pTblName)
){
goto exit_create_index;
}
assert( pName1!=0 );
if( iDb==0 ){
pTab = sqlite3FindTable(db, zTblName, "temp");
}
if( !pTab ){
pTab = sqlite3LocateTable(pParse, zTblName, db->aDb[iDb].zName);
}
sqliteFree( zTblName );
pTab = sqlite3LocateTable(pParse, pTblName->a[0].zName,
pTblName->a[0].zDatabase);
if( !pTab ) goto exit_create_index;
iDb = pTab->iDb;
assert( iDb==pTab->iDb );
}else{
assert( pName==0 );
pTab = pParse->pNewTable;
@ -1679,12 +1667,6 @@ void sqlite3CreateIndex(
sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
goto exit_create_index;
}
/*
if( pTab->iDb>=2 && db->init.busy==0 ){
sqlite3ErrorMsg(pParse, "table %s may not have indices added", pTab->zName);
goto exit_create_index;
}
*/
if( pTab->pSelect ){
sqlite3ErrorMsg(pParse, "views may not be indexed");
goto exit_create_index;

View File

@ -14,7 +14,7 @@
** the parser. Lemon will also generate a header file containing
** numeric codes for all of the tokens.
**
** @(#) $Id: parse.y,v 1.122 2004/05/28 12:33:31 danielk1977 Exp $
** @(#) $Id: parse.y,v 1.123 2004/05/29 02:37:19 danielk1977 Exp $
*/
%token_prefix TK_
%token_type {Token}
@ -730,10 +730,11 @@ expritem(A) ::= . {A = 0;}
///////////////////////////// The CREATE INDEX command ///////////////////////
//
cmd ::= CREATE(S) uniqueflag(U) INDEX nm(X) dbnm(D)
ON nm(Y) LP idxlist(Z) RP(E) onconf(R). {
ON nm(Y) dbnm(C) LP idxlist(Z) RP(E) onconf(R). {
if( U!=OE_None ) U = R;
if( U==OE_Default) U = OE_Abort;
sqlite3CreateIndex(pParse, &X, &D, &Y, Z, U, &S, &E);
sqlite3CreateIndex(pParse, &X, &D, sqlite3SrcListAppend(0,&Y,&C),
Z, U, &S, &E);
}
%type uniqueflag {int}
@ -788,10 +789,10 @@ cmd ::= CREATE(A) trigger_decl BEGIN trigger_cmd_list(S) END(Z). {
sqlite3FinishTrigger(pParse, S, &all);
}
trigger_decl ::= temp(T) TRIGGER nm(B) trigger_time(C) trigger_event(D)
trigger_decl ::= temp(T) TRIGGER nm(B) dbnm(Z) trigger_time(C) trigger_event(D)
ON nm(E) dbnm(DB) foreach_clause(F) when_clause(G). {
SrcList *pTab = sqlite3SrcListAppend(0, &E, &DB);
sqlite3BeginTrigger(pParse, &B, C, D.a, D.b, pTab, F, G, T);
sqlite3BeginTrigger(pParse, &B, &Z, C, D.a, D.b, pTab, F, G, T);
}
%type trigger_time {int}

View File

@ -11,7 +11,7 @@
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.260 2004/05/28 16:00:22 drh Exp $
** @(#) $Id: sqliteInt.h,v 1.261 2004/05/29 02:37:19 danielk1977 Exp $
*/
#include "config.h"
#include "sqlite.h"
@ -229,7 +229,7 @@ extern int sqlite3_iMallocFail; /* Fail sqliteMalloc() after this many calls *
/*
** The name of the schema table.
*/
#define SCHEMA_TABLE(x) (x?TEMP_MASTER_NAME:MASTER_NAME)
#define SCHEMA_TABLE(x) (x==1?TEMP_MASTER_NAME:MASTER_NAME)
/*
** A convenience macro that returns the number of elements in
@ -1221,7 +1221,8 @@ void sqlite3SrcListAddAlias(SrcList*, Token*);
void sqlite3SrcListAssignCursors(Parse*, SrcList*);
void sqlite3IdListDelete(IdList*);
void sqlite3SrcListDelete(SrcList*);
void sqlite3CreateIndex(Parse*,Token*,Token*,Token*,IdList*,int,Token*,Token*);
void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,IdList*,int,Token*,
Token*);
void sqlite3DropIndex(Parse*, SrcList*);
void sqlite3AddKeyType(Vdbe*, ExprList*);
void sqlite3AddIdxKeyType(Vdbe*, Index*);
@ -1286,7 +1287,8 @@ int sqlite3SafetyOn(sqlite*);
int sqlite3SafetyOff(sqlite*);
int sqlite3SafetyCheck(sqlite*);
void sqlite3ChangeCookie(sqlite*, Vdbe*, int);
void sqlite3BeginTrigger(Parse*, Token*,int,int,IdList*,SrcList*,int,Expr*,int);
void sqlite3BeginTrigger(Parse*, Token*,Token*,int,int,IdList*,SrcList*,
int,Expr*,int);
void sqlite3FinishTrigger(Parse*, TriggerStep*, Token*);
void sqlite3DropTrigger(Parse*, SrcList*);
void sqlite3DropTriggerPtr(Parse*, Trigger*, int);
@ -1352,3 +1354,4 @@ void sqlite3Error(sqlite *, int, const char*,...);
int sqlite3utfTranslate(const void *, int , u8 , void **, int *, u8);
u8 sqlite3UtfReadBom(const void *zData, int nData);
void *sqlite3HexToBlob(const char *z);
int sqlite3TwoPartName(Parse *, Token *, Token *, Token **);

View File

@ -11,7 +11,7 @@
*************************************************************************
** A TCL Interface to SQLite
**
** $Id: tclsqlite.c,v 1.75 2004/05/27 13:35:20 danielk1977 Exp $
** $Id: tclsqlite.c,v 1.76 2004/05/29 02:37:19 danielk1977 Exp $
*/
#ifndef NO_TCL /* Omit this whole file if TCL is unavailable */
@ -75,182 +75,6 @@ struct CallbackData {
char **azColName; /* Column names translated to UTF-8 */
};
#ifdef UTF_TRANSLATION_NEEDED
/*
** Called for each row of the result.
**
** This version is used when TCL expects UTF-8 data but the database
** uses the ISO8859 format. A translation must occur from ISO8859 into
** UTF-8.
*/
static int DbEvalCallback(
void *clientData, /* An instance of CallbackData */
int nCol, /* Number of columns in the result */
char ** azCol, /* Data for each column */
char ** azN /* Name for each column */
){
CallbackData *cbData = (CallbackData*)clientData;
int i, rc;
Tcl_DString dCol;
Tcl_DStringInit(&dCol);
if( cbData->azColName==0 ){
assert( cbData->once );
cbData->once = 0;
if( cbData->zArray[0] ){
Tcl_SetVar2(cbData->interp, cbData->zArray, "*", "", 0);
}
cbData->azColName = malloc( nCol*sizeof(char*) );
if( cbData->azColName==0 ){ return 1; }
cbData->nColName = nCol;
for(i=0; i<nCol; i++){
Tcl_ExternalToUtfDString(NULL, azN[i], -1, &dCol);
cbData->azColName[i] = malloc( Tcl_DStringLength(&dCol) + 1 );
if( cbData->azColName[i] ){
strcpy(cbData->azColName[i], Tcl_DStringValue(&dCol));
}else{
return 1;
}
if( cbData->zArray[0] ){
Tcl_SetVar2(cbData->interp, cbData->zArray, "*",
Tcl_DStringValue(&dCol), TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
if( azN[nCol]!=0 ){
Tcl_DString dType;
Tcl_DStringInit(&dType);
Tcl_DStringAppend(&dType, "typeof:", -1);
Tcl_DStringAppend(&dType, Tcl_DStringValue(&dCol), -1);
Tcl_DStringFree(&dCol);
Tcl_ExternalToUtfDString(NULL, azN[i+nCol], -1, &dCol);
Tcl_SetVar2(cbData->interp, cbData->zArray,
Tcl_DStringValue(&dType), Tcl_DStringValue(&dCol),
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
Tcl_DStringFree(&dType);
}
}
Tcl_DStringFree(&dCol);
}
}
if( azCol!=0 ){
if( cbData->zArray[0] ){
for(i=0; i<nCol; i++){
char *z = azCol[i];
if( z==0 ) z = "";
Tcl_DStringInit(&dCol);
Tcl_ExternalToUtfDString(NULL, z, -1, &dCol);
Tcl_SetVar2(cbData->interp, cbData->zArray, cbData->azColName[i],
Tcl_DStringValue(&dCol), 0);
Tcl_DStringFree(&dCol);
}
}else{
for(i=0; i<nCol; i++){
char *z = azCol[i];
if( z==0 ) z = "";
Tcl_DStringInit(&dCol);
Tcl_ExternalToUtfDString(NULL, z, -1, &dCol);
Tcl_SetVar(cbData->interp, cbData->azColName[i],
Tcl_DStringValue(&dCol), 0);
Tcl_DStringFree(&dCol);
}
}
}
rc = Tcl_EvalObj(cbData->interp, cbData->pCode);
if( rc==TCL_CONTINUE ) rc = TCL_OK;
cbData->tcl_rc = rc;
return rc!=TCL_OK;
}
#endif /* UTF_TRANSLATION_NEEDED */
#ifndef UTF_TRANSLATION_NEEDED
/*
** Called for each row of the result.
**
** This version is used when either of the following is true:
**
** (1) This version of TCL uses UTF-8 and the data in the
** SQLite database is already in the UTF-8 format.
**
** (2) This version of TCL uses ISO8859 and the data in the
** SQLite database is already in the ISO8859 format.
*/
static int DbEvalCallback(
void *clientData, /* An instance of CallbackData */
int nCol, /* Number of columns in the result */
char ** azCol, /* Data for each column */
char ** azN /* Name for each column */
){
CallbackData *cbData = (CallbackData*)clientData;
int i, rc;
if( azCol==0 || (cbData->once && cbData->zArray[0]) ){
Tcl_SetVar2(cbData->interp, cbData->zArray, "*", "", 0);
for(i=0; i<nCol; i++){
Tcl_SetVar2(cbData->interp, cbData->zArray, "*", azN[i],
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
if( azN[nCol] ){
char *z = sqlite3_mprintf("typeof:%s", azN[i]);
Tcl_SetVar2(cbData->interp, cbData->zArray, z, azN[i+nCol],
TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
sqlite3_freemem(z);
}
}
cbData->once = 0;
}
if( azCol!=0 ){
if( cbData->zArray[0] ){
for(i=0; i<nCol; i++){
char *z = azCol[i];
if( z==0 ) z = "";
Tcl_SetVar2(cbData->interp, cbData->zArray, azN[i], z, 0);
}
}else{
for(i=0; i<nCol; i++){
char *z = azCol[i];
if( z==0 ) z = "";
Tcl_SetVar(cbData->interp, azN[i], z, 0);
}
}
}
rc = Tcl_EvalObj(cbData->interp, cbData->pCode);
if( rc==TCL_CONTINUE ) rc = TCL_OK;
cbData->tcl_rc = rc;
return rc!=TCL_OK;
}
#endif
/*
** This is an alternative callback for database queries. Instead
** of invoking a TCL script to handle the result, this callback just
** appends each column of the result to a list. After the query
** is complete, the list is returned.
*/
static int DbEvalCallback2(
void *clientData, /* An instance of CallbackData */
int nCol, /* Number of columns in the result */
char ** azCol, /* Data for each column */
char ** azN /* Name for each column */
){
Tcl_Obj *pList = (Tcl_Obj*)clientData;
int i;
if( azCol==0 ) return 0;
for(i=0; i<nCol; i++){
Tcl_Obj *pElem;
if( azCol[i] && *azCol[i] ){
#ifdef UTF_TRANSLATION_NEEDED
Tcl_DString dCol;
Tcl_DStringInit(&dCol);
Tcl_ExternalToUtfDString(NULL, azCol[i], -1, &dCol);
pElem = Tcl_NewStringObj(Tcl_DStringValue(&dCol), -1);
Tcl_DStringFree(&dCol);
#else
pElem = Tcl_NewStringObj(azCol[i], -1);
#endif
}else{
pElem = Tcl_NewObj();
}
Tcl_ListObjAppendElement(0, pList, pElem);
}
return 0;
}
/*
** This is a second alternative callback for database queries. A the
** first column of the first row of the result is made the TCL result.
@ -475,6 +299,26 @@ static int auth_callback(
}
#endif /* SQLITE_OMIT_AUTHORIZATION */
/*
** zText is a pointer to text obtained via an sqlite3_result_text()
** or similar interface. This routine returns a Tcl string object,
** reference count set to 0, containing the text. If a translation
** between iso8859 and UTF-8 is required, it is preformed.
*/
static Tcl_Obj *dbTextToObj(char const *zText){
Tcl_Obj *pVal;
#ifdef UTF_TRANSLATION_NEEDED
Tcl_DString dCol;
Tcl_DStringInit(&dCol);
Tcl_ExternalToUtfDString(NULL, zText, -1, &dCol);
pVal = Tcl_NewStringObj(Tcl_DStringValue(&dCol), -1);
Tcl_DStringFree(&dCol);
#else
pVal = Tcl_NewStringObj(zText, -1);
#endif
return pVal;
}
/*
** The "sqlite" command below creates a new Tcl command for each
** connection it opens to an SQLite database. This routine is invoked
@ -777,7 +621,9 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
char const *zSql;
char const *zLeft;
sqlite3_stmt *pStmt;
Tcl_Obj *pRet = 0;
Tcl_Obj *pRet = Tcl_NewObj();
Tcl_IncrRefCount(pRet);
if( objc!=5 && objc!=3 ){
Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME CODE?");
@ -790,7 +636,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
int i;
if( SQLITE_OK!=sqlite3_prepare(pDb->db, zSql, -1, &pStmt, &zLeft) ){
Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_STATIC);
Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db)));
rc = TCL_ERROR;
break;
}
@ -801,7 +647,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
for(i=0; i<sqlite3_column_count(pStmt); i++){
Tcl_ListObjAppendElement(interp, pColList,
Tcl_NewStringObj(sqlite3_column_name(pStmt, i), -1)
dbTextToObj(sqlite3_column_name(pStmt, i))
);
}
Tcl_ObjSetVar2(interp,objv[3],Tcl_NewStringObj("*",-1),pColList,0);
@ -813,14 +659,14 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
/* Set pVal to contain the i'th column of this row. */
if( SQLITE3_BLOB!=sqlite3_column_type(pStmt, i) ){
pVal = Tcl_NewStringObj(sqlite3_column_text(pStmt, i), -1);
pVal = dbTextToObj(sqlite3_column_text(pStmt, i));
}else{
int bytes = sqlite3_column_bytes(pStmt, i);
pVal = Tcl_NewByteArrayObj(sqlite3_column_blob(pStmt, i), bytes);
}
if( objc==5 ){
Tcl_Obj *pName = Tcl_NewStringObj(sqlite3_column_name(pStmt, i), -1);
Tcl_Obj *pName = dbTextToObj(sqlite3_column_name(pStmt, i));
Tcl_IncrRefCount(pName);
if( !strcmp("", Tcl_GetString(objv[3])) ){
Tcl_ObjSetVar2(interp, pName, 0, pVal, 0);
@ -829,10 +675,6 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
}
Tcl_DecrRefCount(pName);
}else{
if( !pRet ){
pRet = Tcl_NewObj();
Tcl_IncrRefCount(pRet);
}
Tcl_ListObjAppendElement(interp, pRet, pVal);
}
}
@ -848,7 +690,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
}
if( pStmt && SQLITE_OK!=sqlite3_errcode(pDb->db) ){
Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_STATIC);
Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db)));
rc = TCL_ERROR;
break;
}
@ -857,83 +699,13 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
zSql = zLeft;
}
if( rc==TCL_OK && pRet ){
if( rc==TCL_OK ){
Tcl_SetObjResult(interp, pRet);
Tcl_DecrRefCount(pRet);
}
Tcl_DecrRefCount(pRet);
break;
}
#if 0
case DB_EVAL: {
CallbackData cbData;
char *zErrMsg;
char *zSql;
#ifdef UTF_TRANSLATION_NEEDED
Tcl_DString dSql;
int i;
#endif
if( objc!=5 && objc!=3 ){
Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME CODE?");
return TCL_ERROR;
}
pDb->interp = interp;
zSql = Tcl_GetStringFromObj(objv[2], 0);
#ifdef UTF_TRANSLATION_NEEDED
Tcl_DStringInit(&dSql);
Tcl_UtfToExternalDString(NULL, zSql, -1, &dSql);
zSql = Tcl_DStringValue(&dSql);
#endif
Tcl_IncrRefCount(objv[2]);
if( objc==5 ){
cbData.interp = interp;
cbData.once = 1;
cbData.zArray = Tcl_GetStringFromObj(objv[3], 0);
cbData.pCode = objv[4];
cbData.tcl_rc = TCL_OK;
cbData.nColName = 0;
cbData.azColName = 0;
zErrMsg = 0;
Tcl_IncrRefCount(objv[3]);
Tcl_IncrRefCount(objv[4]);
rc = sqlite3_exec(pDb->db, zSql, DbEvalCallback, &cbData, &zErrMsg);
Tcl_DecrRefCount(objv[4]);
Tcl_DecrRefCount(objv[3]);
if( cbData.tcl_rc==TCL_BREAK ){ cbData.tcl_rc = TCL_OK; }
}else{
Tcl_Obj *pList = Tcl_NewObj();
cbData.tcl_rc = TCL_OK;
rc = sqlite3_exec(pDb->db, zSql, DbEvalCallback2, pList, &zErrMsg);
Tcl_SetObjResult(interp, pList);
}
pDb->rc = rc;
if( rc==SQLITE_ABORT ){
if( zErrMsg ) free(zErrMsg);
rc = cbData.tcl_rc;
}else if( zErrMsg ){
Tcl_SetResult(interp, zErrMsg, TCL_VOLATILE);
free(zErrMsg);
rc = TCL_ERROR;
}else if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, sqlite3_error_string(rc), 0);
rc = TCL_ERROR;
}else{
}
Tcl_DecrRefCount(objv[2]);
#ifdef UTF_TRANSLATION_NEEDED
Tcl_DStringFree(&dSql);
if( objc==5 && cbData.azColName ){
for(i=0; i<cbData.nColName; i++){
if( cbData.azColName[i] ) free(cbData.azColName[i]);
}
free(cbData.azColName);
cbData.azColName = 0;
}
#endif
return rc;
}
#endif
/*
** $db function NAME SCRIPT

View File

@ -40,7 +40,8 @@ void sqlite3DeleteTriggerStep(TriggerStep *pTriggerStep){
*/
void sqlite3BeginTrigger(
Parse *pParse, /* The parse context of the CREATE TRIGGER statement */
Token *pName, /* The name of the trigger */
Token *pName1, /* The name of the trigger */
Token *pName2, /* The name of the trigger */
int tr_tm, /* One of TK_BEFORE, TK_AFTER, TK_INSTEAD */
int op, /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
IdList *pColumns, /* column list if this is an UPDATE OF trigger */
@ -49,76 +50,100 @@ void sqlite3BeginTrigger(
Expr *pWhen, /* WHEN clause */
int isTemp /* True if the TEMPORARY keyword is present */
){
Trigger *nt;
Table *tab;
Trigger *pTrigger;
Table *pTab;
char *zName = 0; /* Name of the trigger */
sqlite *db = pParse->db;
int iDb; /* When database to store the trigger in */
int iDb; /* The database to store the trigger in */
Token *pName; /* The unqualified db name */
DbFixer sFix;
/* Check that:
** 1. the trigger name does not already exist.
** 2. the table (or view) does exist in the same database as the trigger.
** 3. that we are not trying to create a trigger on the sqlite_master table
** 4. That we are not trying to create an INSTEAD OF trigger on a table.
** 5. That we are not trying to create a BEFORE or AFTER trigger on a view.
if( isTemp ){
/* If TEMP was specified, then the trigger name may not be qualified. */
if( pName2 && pName2->n>0 ){
sqlite3ErrorMsg(pParse, "temporary trigger may not have qualified name");
goto trigger_cleanup;
}
iDb = 1;
pName = pName1;
}else{
/* Figure out the db that the the trigger will be created in */
iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName);
if( iDb<0 ){
goto trigger_cleanup;
}
}
/* If the trigger name was unqualified, and the table is a temp table,
** then set iDb to 1 to create the trigger in the temporary database.
** If sqlite3SrcListLookup() returns 0, indicating the table does not
** exist, the error is caught by the block below.
*/
pTab = sqlite3SrcListLookup(pParse, pTableName);
if( pName2->n==0 && pTab && pTab->iDb==1 ){
iDb = 1;
}
/* Ensure the table name matches database name and that the table exists */
if( sqlite3_malloc_failed ) goto trigger_cleanup;
assert( pTableName->nSrc==1 );
if( db->init.busy
&& sqlite3FixInit(&sFix, pParse, db->init.iDb, "trigger", pName)
&& sqlite3FixSrcList(&sFix, pTableName)
){
if( sqlite3FixInit(&sFix, pParse, iDb, "trigger", pName) &&
sqlite3FixSrcList(&sFix, pTableName) ){
goto trigger_cleanup;
}
tab = sqlite3SrcListLookup(pParse, pTableName);
if( !tab ){
goto trigger_cleanup;
}
iDb = isTemp ? 1 : tab->iDb;
if( iDb>=2 && !db->init.busy ){
sqlite3ErrorMsg(pParse, "triggers may not be added to auxiliary "
"database %s", db->aDb[tab->iDb].zName);
pTab = sqlite3SrcListLookup(pParse, pTableName);
if( !pTab ){
/* The table does not exist. */
goto trigger_cleanup;
}
/* Check that no trigger of the specified name exists */
zName = sqliteStrNDup(pName->z, pName->n);
sqlite3Dequote(zName);
if( sqlite3HashFind(&(db->aDb[iDb].trigHash), zName,pName->n+1) ){
sqlite3ErrorMsg(pParse, "trigger %T already exists", pName);
goto trigger_cleanup;
}
if( sqlite3StrNICmp(tab->zName, "sqlite_", 7)==0 ){
/* Do not create a trigger on a system table */
if( (iDb!=1 && sqlite3StrICmp(pTab->zName, MASTER_NAME)==0) ||
(iDb==1 && sqlite3StrICmp(pTab->zName, TEMP_MASTER_NAME)==0)
){
sqlite3ErrorMsg(pParse, "cannot create trigger on system table");
pParse->nErr++;
goto trigger_cleanup;
}
if( tab->pSelect && tr_tm != TK_INSTEAD ){
/* INSTEAD of triggers are only for views and views only support INSTEAD
** of triggers.
*/
if( pTab->pSelect && tr_tm!=TK_INSTEAD ){
sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S",
(tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0);
goto trigger_cleanup;
}
if( !tab->pSelect && tr_tm == TK_INSTEAD ){
if( !pTab->pSelect && tr_tm==TK_INSTEAD ){
sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF"
" trigger on table: %S", pTableName, 0);
goto trigger_cleanup;
}
#ifndef SQLITE_OMIT_AUTHORIZATION
{
int code = SQLITE_CREATE_TRIGGER;
const char *zDb = db->aDb[tab->iDb].zName;
const char *zDb = db->aDb[pTab->iDb].zName;
const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb;
if( tab->iDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;
if( sqlite3AuthCheck(pParse, code, zName, tab->zName, zDbTrig) ){
if( pTab->iDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;
if( sqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){
goto trigger_cleanup;
}
if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(tab->iDb), 0, zDb)){
if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(pTab->iDb), 0, zDb)){
goto trigger_cleanup;
}
}
#endif
/* INSTEAD OF triggers can only appear on views and BEGIN triggers
/* INSTEAD OF triggers can only appear on views and BEFORE triggers
** cannot appear on views. So we might as well translate every
** INSTEAD OF trigger into a BEFORE trigger. It simplifies code
** elsewhere.
@ -128,22 +153,22 @@ void sqlite3BeginTrigger(
}
/* Build the Trigger object */
nt = (Trigger*)sqliteMalloc(sizeof(Trigger));
if( nt==0 ) goto trigger_cleanup;
nt->name = zName;
pTrigger = (Trigger*)sqliteMalloc(sizeof(Trigger));
if( pTrigger==0 ) goto trigger_cleanup;
pTrigger->name = zName;
zName = 0;
nt->table = sqliteStrDup(pTableName->a[0].zName);
pTrigger->table = sqliteStrDup(pTableName->a[0].zName);
if( sqlite3_malloc_failed ) goto trigger_cleanup;
nt->iDb = iDb;
nt->iTabDb = tab->iDb;
nt->op = op;
nt->tr_tm = tr_tm;
nt->pWhen = sqlite3ExprDup(pWhen);
nt->pColumns = sqlite3IdListDup(pColumns);
nt->foreach = foreach;
sqlite3TokenCopy(&nt->nameToken,pName);
pTrigger->iDb = iDb;
pTrigger->iTabDb = pTab->iDb;
pTrigger->op = op;
pTrigger->tr_tm = tr_tm;
pTrigger->pWhen = sqlite3ExprDup(pWhen);
pTrigger->pColumns = sqlite3IdListDup(pColumns);
pTrigger->foreach = foreach;
sqlite3TokenCopy(&pTrigger->nameToken,pName);
assert( pParse->pNewTrigger==0 );
pParse->pNewTrigger = nt;
pParse->pNewTrigger = pTrigger;
trigger_cleanup:
sqliteFree(zName);
@ -198,7 +223,7 @@ void sqlite3FinishTrigger(
/* Make an entry in the sqlite_master table */
v = sqlite3GetVdbe(pParse);
if( v==0 ) goto triggerfinish_cleanup;
sqlite3BeginWriteOperation(pParse, 0, 0);
sqlite3BeginWriteOperation(pParse, 0, nt->iDb);
sqlite3OpenMasterTable(v, nt->iDb);
addr = sqlite3VdbeAddOpList(v, ArraySize(insertTrig), insertTrig);
sqlite3VdbeChangeP3(v, addr+2, nt->name, 0);
@ -425,11 +450,6 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger, int nested){
sqlite *db = pParse->db;
assert( pTrigger->iDb<db->nDb );
if( pTrigger->iDb>=2 ){
sqlite3ErrorMsg(pParse, "triggers may not be removed from "
"auxiliary database %s", db->aDb[pTrigger->iDb].zName);
return;
}
pTable = sqlite3FindTable(db, pTrigger->table,db->aDb[pTrigger->iTabDb].zName);
assert(pTable);
assert( pTable->iDb==pTrigger->iDb || pTrigger->iDb==1 );
@ -438,7 +458,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger, int nested){
int code = SQLITE_DROP_TRIGGER;
const char *zDb = db->aDb[pTrigger->iDb].zName;
const char *zTab = SCHEMA_TABLE(pTrigger->iDb);
if( pTrigger->iDb ) code = SQLITE_DROP_TEMP_TRIGGER;
if( pTrigger->iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER;
if( sqlite3AuthCheck(pParse, code, pTrigger->name, pTable->zName, zDb) ||
sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
return;
@ -462,7 +482,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger, int nested){
{ OP_Next, 0, ADDR(1), 0}, /* 8 */
};
sqlite3BeginWriteOperation(pParse, 0, 0);
sqlite3BeginWriteOperation(pParse, 0, pTrigger->iDb);
sqlite3OpenMasterTable(v, pTrigger->iDb);
base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger);
sqlite3VdbeChangeP3(v, base+1, pTrigger->name, 0);

View File

@ -43,7 +43,7 @@
** in this file for details. If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.345 2004/05/28 16:00:22 drh Exp $
** $Id: vdbe.c,v 1.346 2004/05/29 02:37:19 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "os.h"
@ -2142,12 +2142,12 @@ case OP_MakeRecord: {
pTos++;
pTos->n = nByte;
if( nByte<=sizeof(zTemp) ){
assert( zNewRecord==zTemp );
assert( zNewRecord==(unsigned char *)zTemp );
pTos->z = pTos->zShort;
memcpy(pTos->zShort, zTemp, nByte);
pTos->flags = MEM_Blob | MEM_Short;
}else{
assert( zNewRecord!=zTemp );
assert( zNewRecord!=(unsigned char *)zTemp );
pTos->z = zNewRecord;
pTos->flags = MEM_Blob | MEM_Dyn;
}

View File

@ -12,7 +12,7 @@
# focus of this script is testing the ATTACH and DETACH commands
# and related functionality.
#
# $Id: attach.test,v 1.16 2004/05/11 09:57:35 drh Exp $
# $Id: attach.test,v 1.17 2004/05/29 02:37:20 danielk1977 Exp $
#
set testdir [file dirname $argv0]
@ -488,7 +488,7 @@ do_test attach-5.1 {
SELECT 'no-op';
END;
} db2
} {1 {triggers may not be added to auxiliary database orig}}
} {1 {trigger r1 cannot reference objects in database orig}}
do_test attach-5.2 {
catchsql {
CREATE TABLE t5(x,y);

View File

@ -12,7 +12,7 @@
# focus of this script is testing the ATTACH and DETACH commands
# and schema changes to attached databases.
#
# $Id: attach3.test,v 1.3 2004/05/28 12:33:32 danielk1977 Exp $
# $Id: attach3.test,v 1.4 2004/05/29 02:37:20 danielk1977 Exp $
#
@ -63,7 +63,7 @@ do_test attach3-1.5 {
} {1 2}
# Create an index on the auxilary database table.
do_test attach4-2.1 {
do_test attach3-2.1 {
execsql {
CREATE INDEX aux.i1 on t3(e);
}
@ -71,31 +71,31 @@ do_test attach4-2.1 {
execsql {
pragma vdbe_trace = off;
}
do_test attach4-2.2 {
do_test attach3-2.2 {
execsql {
SELECT * FROM sqlite_master WHERE name = 'i1';
}
} {}
do_test attach4-2.3 {
do_test attach3-2.3 {
execsql {
SELECT * FROM aux.sqlite_master WHERE name = 'i1';
}
} {index i1 t3 5 {CREATE INDEX i1 on t3(e)}}
# Drop the index on the aux database table.
do_test attach4-3.1 {
do_test attach3-3.1 {
execsql {
DROP INDEX aux.i1;
SELECT * FROM aux.sqlite_master WHERE name = 'i1';
}
} {}
do_test attach4-3.2 {
do_test attach3-3.2 {
execsql {
CREATE INDEX aux.i1 on t3(e);
SELECT * FROM aux.sqlite_master WHERE name = 'i1';
}
} {index i1 t3 5 {CREATE INDEX i1 on t3(e)}}
do_test attach4-3.3 {
do_test attach3-3.3 {
execsql {
DROP INDEX i1;
SELECT * FROM aux.sqlite_master WHERE name = 'i1';
@ -103,20 +103,20 @@ do_test attach4-3.3 {
} {}
# Drop tables t1 and t2 in the auxilary database.
do_test attach4-4.1 {
do_test attach3-4.1 {
execsql {
DROP TABLE aux.t1;
SELECT name FROM aux.sqlite_master;
}
} {t2 t3}
do_test attach4-4.2 {
do_test attach3-4.2 {
# This will drop main.t2
execsql {
DROP TABLE t2;
SELECT name FROM aux.sqlite_master;
}
} {t2 t3}
do_test attach4-4.3 {
do_test attach3-4.3 {
execsql {
DROP TABLE t2;
SELECT name FROM aux.sqlite_master;
@ -124,17 +124,17 @@ do_test attach4-4.3 {
} {t3}
# Create a view in the auxilary database.
do_test attach4-5.1 {
do_test attach3-5.1 {
execsql {
CREATE VIEW aux.v1 AS SELECT * FROM t3;
}
} {}
do_test attach4-5.2 {
do_test attach3-5.2 {
execsql {
SELECT * FROM aux.sqlite_master WHERE name = 'v1';
}
} {view v1 v1 0 {CREATE VIEW v1 AS SELECT * FROM t3}}
do_test attach4-5.3 {
do_test attach3-5.3 {
execsql {
INSERT INTO aux.t3 VALUES('hello', 'world');
SELECT * FROM v1;
@ -142,17 +142,52 @@ do_test attach4-5.3 {
} {1 2 hello world}
# Drop the view
do_test attach4-6.1 {
do_test attach3-6.1 {
execsql {
DROP VIEW aux.v1;
}
} {}
do_test attach4-5.2 {
do_test attach3-6.2 {
execsql {
SELECT * FROM aux.sqlite_master WHERE name = 'v1';
}
} {}
# Create a trigger in the auxilary database.
do_test attach3-7.1 {
execsql {
CREATE TRIGGER aux.tr1 AFTER INSERT ON t3 BEGIN
INSERT INTO t3 VALUES(new.e*2, new.f*2);
END;
}
} {}
do_test attach3-7.2 {
execsql {
DELETE FROM t3;
INSERT INTO t3 VALUES(10, 20);
SELECT * FROM t3;
}
} {10 20 20 40}
do_test attach3-5.3 {
execsql {
SELECT * FROM aux.sqlite_master WHERE name = 'tr1';
}
} {trigger tr1 t3 0 {CREATE TRIGGER aux.tr1 AFTER INSERT ON t3 BEGIN
INSERT INTO t3 VALUES(new.e*2, new.f*2);
END}}
# Drop the trigger
do_test attach3-8.1 {
execsql {
DROP TRIGGER aux.tr1;
}
} {}
do_test attach3-8.2 {
execsql {
SELECT * FROM aux.sqlite_master WHERE name = 'tr1';
}
} {}
finish_test

View File

@ -36,7 +36,7 @@ do_test trigger1-1.1.2 {
SELECT * from sqlite_master;
END;
}
} {1 {no such table: no_such_table}}
} {1 {no such table: main.no_such_table}}
do_test trigger1-1.1.2 {
catchsql {
CREATE TEMP TRIGGER trig UPDATE ON no_such_table BEGIN
@ -166,7 +166,7 @@ do_test trigger1-1.12 {
delete from t1 WHERE a=old.a+2;
end;
}
} {1 {cannot create INSTEAD OF trigger on table: t1}}
} {1 {cannot create INSTEAD OF trigger on table: main.t1}}
# Ensure that we cannot create BEFORE triggers on views
do_test trigger1-1.13 {
catchsql {
@ -175,7 +175,7 @@ do_test trigger1-1.13 {
delete from t1 WHERE a=old.a+2;
end;
}
} {1 {cannot create BEFORE trigger on view: v1}}
} {1 {cannot create BEFORE trigger on view: main.v1}}
# Ensure that we cannot create AFTER triggers on views
do_test trigger1-1.14 {
catchsql {
@ -185,7 +185,7 @@ do_test trigger1-1.14 {
delete from t1 WHERE a=old.a+2;
end;
}
} {1 {cannot create AFTER trigger on view: v1}}
} {1 {cannot create AFTER trigger on view: main.v1}}
# Check for memory leaks in the trigger parser
#