Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -10,11 +10,11 @@ ** ************************************************************************* ** This file contains C code routines that are called by the parser ** to handle DELETE FROM statements. ** -** $Id: delete.c,v 1.75 2004/06/17 07:53:02 danielk1977 Exp $ +** $Id: delete.c,v 1.76 2004/06/21 06:50:27 danielk1977 Exp $ */ #include "sqliteInt.h" /* ** Look up every table that is named in pSrc. If any table is not found, @@ -140,10 +140,11 @@ */ v = sqlite3GetVdbe(pParse); if( v==0 ){ goto delete_from_cleanup; } + sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, row_triggers_exist, pTab->iDb); /* If we are trying to delete from a view, construct that view into ** a temporary table. */ @@ -269,11 +270,11 @@ if( !row_triggers_exist ){ addr = sqlite3VdbeAddOp(v, OP_ListRead, 0, end); } /* Delete the row */ - sqlite3GenerateRowDelete(db, v, pTab, iCur, pParse->trigStack==0); + sqlite3GenerateRowDelete(db, v, pTab, iCur, 1); } /* If there are row triggers, close all cursors then invoke ** the AFTER triggers */ @@ -301,11 +302,10 @@ } sqlite3VdbeAddOp(v, OP_Close, iCur, 0); pParse->nTab = iCur; } } - sqlite3VdbeAddOp(v, OP_SetCounts, 0, 0); sqlite3EndWriteOperation(pParse); /* ** Return the number of rows that were deleted. */ @@ -350,12 +350,11 @@ int count /* Increment the row change counter */ ){ int addr; addr = sqlite3VdbeAddOp(v, OP_NotExists, iCur, 0); sqlite3GenerateRowIndexDelete(db, v, pTab, iCur, 0); - sqlite3VdbeAddOp(v, OP_Delete, iCur, - (count?OPFLAG_NCHANGE:0) | OPFLAG_CSCHANGE); + sqlite3VdbeAddOp(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0)); sqlite3VdbeChangeP2(v, addr, sqlite3VdbeCurrentAddr(v)); } /* ** This routine generates VDBE code that causes the deletion of all Index: src/func.c ================================================================== --- src/func.c +++ src/func.c @@ -14,11 +14,11 @@ ** ** There is only one exported symbol in this file - the function ** sqliteRegisterBuildinFunctions() found at the bottom of the file. ** All other code has file scope. ** -** $Id: func.c,v 1.74 2004/06/19 17:33:07 drh Exp $ +** $Id: func.c,v 1.75 2004/06/21 06:50:28 danielk1977 Exp $ */ #include #include #include #include @@ -271,35 +271,36 @@ sqlite *db = sqlite3_user_data(context); sqlite3_result_int64(context, sqlite3_last_insert_rowid(db)); } /* -** Implementation of the change_count() SQL function. The return -** value is the same as the sqlite3_changes() API function. +** Implementation of the changes() SQL function. The return value is the +** same as the sqlite3_changes() API function. */ -static void change_count( +static void changes( sqlite3_context *context, int arg, sqlite3_value **argv ){ sqlite *db = sqlite3_user_data(context); sqlite3_result_int(context, sqlite3_changes(db)); } /* -** Implementation of the last_statement_change_count() SQL function. The -** return value is the same as the sqlite3_last_statement_changes() API -** function. +** Implementation of the total_changes() SQL function. The return value is +** the same as the sqlite3_total_changes() API function. */ -static void last_statement_change_count( - sqlite3_context *context, +static void total_changes( + sqlite3_context *context, int arg, sqlite3_value **argv ){ sqlite *db = sqlite3_user_data(context); - sqlite3_result_int(context, sqlite3_last_statement_changes(db)); + sqlite3_result_int(context, sqlite3_total_changes(db)); } + +#if 0 /* ** A LIKE pattern compiles to an instance of the following structure. Refer ** to the comment for compileLike() function for details. */ @@ -313,13 +314,10 @@ typedef struct LikePattern LikePattern; void deleteLike(void *pLike){ sqliteFree(pLike); } - - -#if 0 /* #define TRACE_LIKE */ #if defined(TRACE_LIKE) && !defined(NDEBUG) char *dumpLike(LikePattern *pLike){ int i; int k = 0; @@ -1039,13 +1037,12 @@ { "glob", 2, 0, SQLITE_UTF8, 0, globFunc }, { "nullif", 2, 0, SQLITE_UTF8, 0, nullifFunc }, { "sqlite_version", 0, 0, SQLITE_UTF8, 0, versionFunc}, { "quote", 1, 0, SQLITE_UTF8, 0, quoteFunc }, { "last_insert_rowid", 0, 1, SQLITE_UTF8, 0, last_insert_rowid }, - { "change_count", 0, 1, SQLITE_UTF8, 0, change_count }, - { "last_statement_change_count", 0, 1, SQLITE_UTF8, 0, - last_statement_change_count }, + { "changes", 0, 1, SQLITE_UTF8, 0, changes }, + { "total_changes", 0, 1, SQLITE_UTF8, 0, total_changes }, #ifdef SQLITE_SOUNDEX { "soundex", 1, 0, SQLITE_UTF8, 0, soundexFunc}, #endif #ifdef SQLITE_TEST { "randstr", 2, 0, SQLITE_UTF8, 0, randStr }, Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -10,11 +10,11 @@ ** ************************************************************************* ** This file contains C code routines that are called by the parser ** to handle INSERT statements in SQLite. ** -** $Id: insert.c,v 1.112 2004/06/17 07:53:03 danielk1977 Exp $ +** $Id: insert.c,v 1.113 2004/06/21 06:50:28 danielk1977 Exp $ */ #include "sqliteInt.h" /* ** Set P3 of the most recently inserted opcode to a column affinity @@ -243,10 +243,11 @@ /* Allocate a VDBE */ v = sqlite3GetVdbe(pParse); if( v==0 ) goto insert_cleanup; + sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, pSelect || row_triggers_exist, pTab->iDb); /* if there are row triggers, allocate a temp table for new.* references. */ if( row_triggers_exist ){ newIdx = pParse->nTab++; @@ -615,11 +616,10 @@ for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){ sqlite3VdbeAddOp(v, OP_Close, idx+base, 0); } } - sqlite3VdbeAddOp(v, OP_SetCounts, 0, 0); sqlite3EndWriteOperation(pParse); /* ** Return the number of rows inserted. */ @@ -965,10 +965,11 @@ ){ int i; Vdbe *v; int nIdx; Index *pIdx; + int pik_flags; v = sqlite3GetVdbe(pParse); assert( v!=0 ); assert( pTab->pSelect==0 ); /* This table is not a VIEW */ for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){} @@ -981,13 +982,13 @@ if( newIdx>=0 ){ sqlite3VdbeAddOp(v, OP_Dup, 1, 0); sqlite3VdbeAddOp(v, OP_Dup, 1, 0); sqlite3VdbeAddOp(v, OP_PutIntKey, newIdx, 0); } - sqlite3VdbeAddOp(v, OP_PutIntKey, base, - (pParse->trigStack?0:OPFLAG_NCHANGE) | - (isUpdate?0:OPFLAG_LASTROWID) | OPFLAG_CSCHANGE); + pik_flags = (OPFLAG_NCHANGE|(isUpdate?0:OPFLAG_LASTROWID)); + sqlite3VdbeAddOp(v, OP_PutIntKey, base, pik_flags); + if( isUpdate && recnoChng ){ sqlite3VdbeAddOp(v, OP_Pop, 1, 0); } } Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -12,11 +12,11 @@ ** Main file for the SQLite library. The routines in this file ** implement the programmer interface to the library. Routines in ** other files are for internal use by SQLite and should not be ** accessed by users of the library. ** -** $Id: main.c,v 1.230 2004/06/19 09:35:37 danielk1977 Exp $ +** $Id: main.c,v 1.231 2004/06/21 06:50:28 danielk1977 Exp $ */ #include "sqliteInt.h" #include "os.h" #include @@ -446,17 +446,14 @@ int sqlite3_changes(sqlite *db){ return db->nChange; } /* -** Return the number of changes produced by the last INSERT, UPDATE, or -** DELETE statement to complete execution. The count does not include -** changes due to SQL statements executed in trigger programs that were -** triggered by that statement +** Return the number of changes since the database handle was opened. */ -int sqlite3_last_statement_changes(sqlite *db){ - return db->lsChange; +int sqlite3_total_changes(sqlite3 *db){ + return db->nTotalChange; } /* ** Close an existing SQLite database */ @@ -950,11 +947,10 @@ if( sqlite3SafetyOn(db) ){ rc = SQLITE_MISUSE; goto prepare_out; } - if( db->pVdbe==0 ){ db->nChange = 0; } memset(&sParse, 0, sizeof(sParse)); sParse.db = db; sqlite3RunParser(&sParse, zSql, &zErrMsg); if( db->xTrace && !db->init.busy ){ Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -10,11 +10,11 @@ ** ************************************************************************* ** This header file defines the interface that the SQLite library ** presents to client programs. ** -** @(#) $Id: sqlite.h.in,v 1.104 2004/06/19 08:18:16 danielk1977 Exp $ +** @(#) $Id: sqlite.h.in,v 1.105 2004/06/21 06:50:28 danielk1977 Exp $ */ #ifndef _SQLITE_H_ #define _SQLITE_H_ #include /* Needed for the definition of va_list */ @@ -174,35 +174,47 @@ ** table. To get an accurate count of the number of rows deleted, use ** "DELETE FROM table WHERE 1" instead. */ int sqlite3_changes(sqlite3*); +/* +** This function returns the number of database rows that have been +** modified by INSERT, UPDATE or DELETE statements since the database handle +** was opened. This includes UPDATE, INSERT and DELETE statements executed +** as part of trigger programs. All changes are counted as soon as the +** statement that makes them is completed (when the statement handle is +** passed to sqlite3_reset() or sqlite_finalise()). +** +** SQLite implements the command "DELETE FROM table" without a WHERE clause +** by dropping and recreating the table. (This is much faster than going +** through and deleting individual elements form the table.) Because of +** this optimization, the change count for "DELETE FROM table" will be +** zero regardless of the number of elements that were originally in the +** table. To get an accurate count of the number of rows deleted, use +** "DELETE FROM table WHERE 1" instead. +*/ +int sqlite3_total_changes(sqlite3*); + /* ** This function returns the number of database rows that were changed -** by the last INSERT, UPDATE, or DELETE statment executed by sqlite3_exec(), -** or by the last VM to run to completion. The change count is not updated -** by SQL statements other than INSERT, UPDATE or DELETE. +** by the last INSERT, UPDATE, or DELETE statment completed. The change +** count is not updated by SQL statements other than INSERT, UPDATE or +** DELETE. ** ** Changes are counted, even if they are later undone by a ROLLBACK or ** ABORT. Changes associated with trigger programs that execute as a ** result of the INSERT, UPDATE, or DELETE statement are not counted. ** -** If a callback invokes sqlite3_exec() recursively, then the changes -** in the inner, recursive call are counted together with the changes -** in the outer call. -** ** SQLite implements the command "DELETE FROM table" without a WHERE clause ** by dropping and recreating the table. (This is much faster than going ** through and deleting individual elements form the table.) Because of ** this optimization, the change count for "DELETE FROM table" will be ** zero regardless of the number of elements that were originally in the ** table. To get an accurate count of the number of rows deleted, use ** "DELETE FROM table WHERE 1" instead. -** -******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ****** */ -int sqlite3_last_statement_changes(sqlite3*); +int sqlite3_changes(sqlite3*); /* This function causes any pending database operation to abort and ** return at its earliest opportunity. This routine is typically ** called in response to a user action such as pressing "Cancel" ** or Ctrl-C where the user wants a long query operation to halt Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -9,11 +9,11 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.295 2004/06/19 16:06:12 drh Exp $ +** @(#) $Id: sqliteInt.h,v 1.296 2004/06/21 06:50:28 danielk1977 Exp $ */ #include "config.h" #include "sqlite3.h" #include "hash.h" #include "parse.h" @@ -316,20 +316,10 @@ ** changes and so the view will need to be reset. */ #define DB_SchemaLoaded 0x0001 /* The schema has been loaded */ #define DB_UnresetViews 0x0002 /* Some views have defined column names */ -#if 0 -/* -** Possible values for the Db.textEnc field. -*/ -#define TEXT_Utf8 1 -#define TEXT_Utf16le 2 -#define TEXT_Utf16be 3 -#define TEXT_Utf16 (SQLITE_BIGENDIAN?TEXT_Utf16be:TEXT_Utf16le) -#endif - #define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) /* ** An instance of the following structure is used to store the busy-handler ** callback for a given sqlite handle. @@ -392,17 +382,16 @@ Hash aCollSeq; /* All collating sequences */ CollSeq *pDfltColl; /* The default collating sequence (BINARY) */ i64 lastRowid; /* ROWID of most recent insert (see above) */ i64 priorNewRowid; /* Last randomly generated ROWID */ int magic; /* Magic number for detect library misuse */ - int nChange; /* Number of rows changed (see above) */ - int lsChange; /* Last statement change count (see above) */ - int csChange; /* Current statement change count (see above) */ - struct sqlite3InitInfo { /* Information used during initialization */ - int iDb; /* When back is being initialized */ - int newTnum; /* Rootpage of table being initialized */ - u8 busy; /* TRUE if currently initializing */ + int nChange; /* Value returned by sqlite3_changes() */ + int nTotalChange; /* Value returned by sqlite3_total_changes() */ + struct sqlite3InitInfo { /* Information used during initialization */ + int iDb; /* When back is being initialized */ + int newTnum; /* Rootpage of table being initialized */ + u8 busy; /* TRUE if currently initializing */ } init; struct Vdbe *pVdbe; /* List of active virtual machines */ int activeVdbeCnt; /* Number of vdbes currently executing */ void (*xTrace)(void*,const char*); /* Trace function */ void *pTraceArg; /* Argument to the trace function */ @@ -1048,11 +1037,10 @@ /* ** Bitfield flags for P2 value in OP_PutIntKey and OP_Delete */ #define OPFLAG_NCHANGE 1 /* Set to update db->nChange */ #define OPFLAG_LASTROWID 2 /* Set to update db->lastRowid */ -#define OPFLAG_CSCHANGE 4 /* Set to update db->csChange */ /* * Each trigger present in the database schema is stored as an instance of * struct Trigger. * @@ -1400,12 +1388,13 @@ CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName, int nName); CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr); int sqlite3CheckCollSeq(Parse *, CollSeq *); int sqlite3CheckIndexCollSeq(Parse *, Index *); int sqlite3CheckObjectName(Parse *, const char *); +void sqlite3VdbeSetChanges(sqlite3 *, int); const void *sqlite3ValueText(sqlite3_value*, u8); int sqlite3ValueBytes(sqlite3_value*, u8); void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); void sqlite3ValueFree(sqlite3_value*); sqlite3_value *sqlite3ValueNew(); sqlite3_value *sqlite3GetTransientValue(sqlite *db); Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -9,11 +9,11 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* ** A TCL Interface to SQLite ** -** $Id: tclsqlite.c,v 1.89 2004/06/19 08:18:21 danielk1977 Exp $ +** $Id: tclsqlite.c,v 1.90 2004/06/21 06:50:28 danielk1977 Exp $ */ #ifndef NO_TCL /* Omit this whole file if TCL is unavailable */ #include "sqliteInt.h" #include "tcl.h" @@ -67,11 +67,10 @@ char *zProgress; /* The progress callback routine */ char *zAuth; /* The authorization callback routine */ SqlFunc *pFunc; /* List of SQL functions */ SqlCollate *pCollate; /* List of SQL collation functions */ int rc; /* Return code of most recent sqlite3_exec() */ - int nChange; /* Database changes for the most recent eval */ Tcl_Obj *pCollateNeeded; /* Collation needed script */ }; /* ** An instance of this structure passes information thru the sqlite @@ -393,22 +392,23 @@ int rc = TCL_OK; static const char *DB_strs[] = { "authorizer", "busy", "changes", "close", "commit_hook", "complete", "errorcode", "eval", "function", - "last_insert_rowid", "last_statement_changes", "onecolumn", + "last_insert_rowid", "onecolumn", "progress", "rekey", "timeout", "trace", "collate", "collation_needed", - 0 + "total_changes", 0 }; enum DB_enum { DB_AUTHORIZER, DB_BUSY, DB_CHANGES, DB_CLOSE, DB_COMMIT_HOOK, DB_COMPLETE, DB_ERRORCODE, DB_EVAL, DB_FUNCTION, - DB_LAST_INSERT_ROWID, DB_LAST_STATEMENT_CHANGES, DB_ONECOLUMN, + DB_LAST_INSERT_ROWID, DB_ONECOLUMN, DB_PROGRESS, DB_REKEY, DB_TIMEOUT, - DB_TRACE, DB_COLLATE, DB_COLLATION_NEEDED + DB_TRACE, DB_COLLATE, DB_COLLATION_NEEDED, + DB_TOTAL_CHANGES }; if( objc<2 ){ Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); return TCL_ERROR; @@ -545,47 +545,24 @@ return TCL_ERROR; } break; } - /* - ** $db changes - ** - ** Return the number of rows that were modified, inserted, or deleted by - ** the most recent "eval". - */ - case DB_CHANGES: { - Tcl_Obj *pResult; - int nChange; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 2, objv, ""); - return TCL_ERROR; - } - /* nChange = sqlite3_changes(pDb->db); */ - nChange = pDb->nChange; - pResult = Tcl_GetObjResult(interp); - Tcl_SetIntObj(pResult, nChange); - break; - } - - /* - ** $db last_statement_changes - ** - ** Return the number of rows that were modified, inserted, or deleted by - ** the last statment to complete execution (excluding changes due to - ** triggers) - */ - case DB_LAST_STATEMENT_CHANGES: { - Tcl_Obj *pResult; - int lsChange; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 2, objv, ""); - return TCL_ERROR; - } - lsChange = sqlite3_last_statement_changes(pDb->db); - pResult = Tcl_GetObjResult(interp); - Tcl_SetIntObj(pResult, lsChange); + /* $db changes + ** + ** Return the number of rows that were modified, inserted, or deleted by + ** the most recent INSERT, UPDATE or DELETE statement, not including + ** any changes made by trigger programs. + */ + case DB_CHANGES: { + Tcl_Obj *pResult; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + pResult = Tcl_GetObjResult(interp); + Tcl_SetIntObj(pResult, sqlite3_changes(pDb->db)); break; } /* $db close ** @@ -683,11 +660,10 @@ if( objc!=5 && objc!=3 ){ Tcl_WrongNumArgs(interp, 2, objv, "SQL ?ARRAY-NAME CODE?"); return TCL_ERROR; } - pDb->nChange = 0; zSql = Tcl_GetStringFromObj(objv[2], 0); while( zSql[0] ){ int i; if( SQLITE_OK!=sqlite3_prepare(pDb->db, zSql, -1, &pStmt, &zLeft) ){ @@ -748,11 +724,10 @@ Tcl_SetObjResult(interp, dbTextToObj(sqlite3_errmsg(pDb->db))); rc = TCL_ERROR; break; } - pDb->nChange += sqlite3_changes(pDb->db); zSql = zLeft; } if( rc==TCL_OK ){ Tcl_SetObjResult(interp, pRet); @@ -957,10 +932,27 @@ pDb->pCollateNeeded = Tcl_DuplicateObj(objv[2]); Tcl_IncrRefCount(pDb->pCollateNeeded); sqlite3_collation_needed(pDb->db, pDb, tclCollateNeeded); break; } + + /* + ** $db total_changes + ** + ** Return the number of rows that were modified, inserted, or deleted + ** since the database handle was created. + */ + case DB_TOTAL_CHANGES: { + Tcl_Obj *pResult; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 2, objv, ""); + return TCL_ERROR; + } + pResult = Tcl_GetObjResult(interp); + Tcl_SetIntObj(pResult, sqlite3_total_changes(pDb->db)); + break; + } } /* End of the SWITCH statement */ return rc; } Index: src/trigger.c ================================================================== --- src/trigger.c +++ src/trigger.c @@ -648,32 +648,38 @@ break; } case TK_UPDATE: { SrcList *pSrc; pSrc = targetSrcList(pParse, pTriggerStep); + sqlite3VdbeAddOp(pParse->pVdbe, OP_ResetCount, 0, 0); sqlite3VdbeAddOp(pParse->pVdbe, OP_ListPush, 0, 0); sqlite3Update(pParse, pSrc, sqlite3ExprListDup(pTriggerStep->pExprList), sqlite3ExprDup(pTriggerStep->pWhere), orconf); sqlite3VdbeAddOp(pParse->pVdbe, OP_ListPop, 0, 0); + sqlite3VdbeAddOp(pParse->pVdbe, OP_ResetCount, 1, 0); break; } case TK_INSERT: { SrcList *pSrc; pSrc = targetSrcList(pParse, pTriggerStep); + sqlite3VdbeAddOp(pParse->pVdbe, OP_ResetCount, 0, 0); sqlite3Insert(pParse, pSrc, sqlite3ExprListDup(pTriggerStep->pExprList), sqlite3SelectDup(pTriggerStep->pSelect), sqlite3IdListDup(pTriggerStep->pIdList), orconf); + sqlite3VdbeAddOp(pParse->pVdbe, OP_ResetCount, 1, 0); break; } case TK_DELETE: { SrcList *pSrc; + sqlite3VdbeAddOp(pParse->pVdbe, OP_ResetCount, 0, 0); sqlite3VdbeAddOp(pParse->pVdbe, OP_ListPush, 0, 0); pSrc = targetSrcList(pParse, pTriggerStep); sqlite3DeleteFrom(pParse, pSrc, sqlite3ExprDup(pTriggerStep->pWhere)); sqlite3VdbeAddOp(pParse->pVdbe, OP_ListPop, 0, 0); + sqlite3VdbeAddOp(pParse->pVdbe, OP_ResetCount, 1, 0); break; } default: assert(0); } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -10,11 +10,11 @@ ** ************************************************************************* ** This file contains C code routines that are called by the parser ** to handle UPDATE statements. ** -** $Id: update.c,v 1.84 2004/06/16 12:02:52 danielk1977 Exp $ +** $Id: update.c,v 1.85 2004/06/21 06:50:29 danielk1977 Exp $ */ #include "sqliteInt.h" /* ** Process an UPDATE statement. @@ -207,10 +207,11 @@ /* Begin generating code. */ v = sqlite3GetVdbe(pParse); if( v==0 ) goto update_cleanup; + sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, 1, pTab->iDb); /* If we are trying to update a view, construct that view into ** a temporary table. */ @@ -438,11 +439,10 @@ }else{ sqlite3VdbeAddOp(v, OP_Close, newIdx, 0); sqlite3VdbeAddOp(v, OP_Close, oldIdx, 0); } - sqlite3VdbeAddOp(v, OP_SetCounts, 0, 0); sqlite3EndWriteOperation(pParse); /* ** Return the number of rows that were changed. */ Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -41,11 +41,11 @@ ** documentation, headers files, or other derived files. The formatting ** of the code in this file is, therefore, important. See other comments ** 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.381 2004/06/19 15:40:23 drh Exp $ +** $Id: vdbe.c,v 1.382 2004/06/21 06:50:29 danielk1977 Exp $ */ #include "sqliteInt.h" #include "os.h" #include #include "vdbeInt.h" @@ -3064,15 +3064,13 @@ ** entry is overwritten. The data is the value on the top of the ** stack. The key is the next value down on the stack. The key must ** be an integer. The stack is popped twice by this instruction. ** ** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is -** incremented (otherwise not). If the OPFLAG_CSCHANGE flag is set, -** then the current statement change count is incremented (otherwise not). -** If the OPFLAG_LASTROWID flag of P2 is set, then rowid is -** stored for subsequent return by the sqlite3_last_insert_rowid() function -** (otherwise it's unmodified). +** incremented (otherwise not). If the OPFLAG_LASTROWID flag of P2 is set, +** then rowid is stored for subsequent return by the +** sqlite3_last_insert_rowid() function (otherwise it's unmodified). */ /* Opcode: PutStrKey P1 * * ** ** 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 @@ -3113,13 +3111,12 @@ }else{ nKey = sizeof(i64); zKey = (char*)&iKey; } - if( pOp->p2 & OPFLAG_NCHANGE ) db->nChange++; + if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++; if( pOp->p2 & OPFLAG_LASTROWID ) db->lastRowid = pNos->i; - if( pOp->p2 & OPFLAG_CSCHANGE ) db->csChange++; if( pC->nextRowidValid && pTos->i>=pC->nextRowid ){ pC->nextRowidValid = 0; } } if( pTos->flags & MEM_Null ){ @@ -3167,12 +3164,11 @@ ** record in the table. If it is left pointing at the next record, then ** the next Next instruction will be a no-op. Hence it is OK to delete ** a record from within an Next loop. ** ** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is -** incremented (otherwise not). If OPFLAG_CSCHANGE flag is set, -** then the current statement change count is incremented (otherwise not). +** incremented (otherwise not). ** ** If P1 is a pseudo-table, then this instruction is a no-op. */ case OP_Delete: { int i = pOp->p1; @@ -3184,23 +3180,26 @@ sqlite3VdbeCursorMoveto(pC); rc = sqlite3BtreeDelete(pC->pCursor); pC->nextRowidValid = 0; pC->cacheValid = 0; } - if( pOp->p2 & OPFLAG_NCHANGE ) db->nChange++; - if( pOp->p2 & OPFLAG_CSCHANGE ) db->csChange++; + if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++; break; } -/* Opcode: SetCounts * * * +/* Opcode: ResetCount P1 * * ** -** Called at end of statement. Updates lsChange (last statement change count) -** and resets csChange (current statement change count) to 0. +** This opcode resets the VMs internal change counter to 0. If P1 is true, +** then the value of the change counter is copied to the database handle +** change counter (returned by subsequent calls to sqlite3_changes()) +** before it is reset. This is used by trigger programs. */ -case OP_SetCounts: { - db->lsChange=db->csChange; - db->csChange=0; +case OP_ResetCount: { + if( pOp->p1 ){ + sqlite3VdbeSetChanges(p->db, p->nChange); + } + p->nChange = 0; break; } /* Opcode: KeyAsData P1 P2 * ** @@ -3992,13 +3991,16 @@ ** opcode. The list is empty after this is executed. */ case OP_ListPush: { p->keylistStackDepth++; assert(p->keylistStackDepth > 0); + + /* FIX ME: This should be allocated at compile time. */ p->keylistStack = sqliteRealloc(p->keylistStack, sizeof(Keylist *) * p->keylistStackDepth); if( p->keylistStack==0 ) goto no_mem; + p->keylistStack[p->keylistStackDepth - 1] = p->pList; p->pList = 0; break; } @@ -4026,17 +4028,24 @@ ** opcode. The context stores the last insert row id, the last statement change ** count, and the current statement change count. */ case OP_ContextPush: { p->contextStackDepth++; - assert(p->contextStackDepth > 0); + assert( p->contextStackDepth>0 ); + + /* FIX ME: This should be allocated as part of the vdbe at compile-time */ p->contextStack = sqliteRealloc(p->contextStack, sizeof(Context) * p->contextStackDepth); if( p->contextStack==0 ) goto no_mem; + p->contextStack[p->contextStackDepth - 1].lastRowid = p->db->lastRowid; + p->contextStack[p->contextStackDepth - 1].nChange = p->nChange; + +#if 0 p->contextStack[p->contextStackDepth - 1].lsChange = p->db->lsChange; p->contextStack[p->contextStackDepth - 1].csChange = p->db->csChange; +#endif break; } /* Opcode: ContextPop * * * ** @@ -4046,12 +4055,15 @@ */ case OP_ContextPop: { assert(p->contextStackDepth > 0); p->contextStackDepth--; p->db->lastRowid = p->contextStack[p->contextStackDepth].lastRowid; + p->nChange = p->contextStack[p->contextStackDepth].nChange; +#if 0 p->db->lsChange = p->contextStack[p->contextStackDepth].lsChange; p->db->csChange = p->contextStack[p->contextStackDepth].csChange; +#endif if( p->contextStackDepth == 0 ){ sqliteFree(p->contextStack); p->contextStack = 0; } break; Index: src/vdbe.h ================================================================== --- src/vdbe.h +++ src/vdbe.h @@ -13,11 +13,11 @@ ** ** This header defines the interface to the virtual database engine ** or VDBE. The VDBE implements an abstract machine that runs a ** simple program to access and modify the underlying database. ** -** $Id: vdbe.h,v 1.86 2004/06/19 14:49:12 drh Exp $ +** $Id: vdbe.h,v 1.87 2004/06/21 06:50:29 danielk1977 Exp $ */ #ifndef _SQLITE_VDBE_H_ #define _SQLITE_VDBE_H_ #include @@ -121,14 +121,15 @@ void sqlite3VdbeCompressSpace(Vdbe*,int); int sqlite3VdbeReset(Vdbe*,char **); int sqliteVdbeSetVariables(Vdbe*,int,const char**); void sqlite3VdbeSetNumCols(Vdbe*,int); int sqlite3VdbeSetColName(Vdbe*, int, const char *, int); +void sqlite3VdbeCountChanges(Vdbe*); #ifndef NDEBUG void sqlite3VdbeComment(Vdbe*, const char*, ...); # define VdbeComment(X) sqlite3VdbeComment X #else # define VdbeComment(X) #endif #endif Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -285,13 +285,12 @@ ** Elements of Context structure type make up the ContextStack, which is ** updated by the ContextPush and ContextPop opcodes (used by triggers) */ typedef struct Context Context; struct Context { - int lastRowid; /* Last insert rowid (from db->lastRowid) */ - int lsChange; /* Last statement change count (from db->lsChange) */ - int csChange; /* Current statement change count (from db->csChange) */ + int lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ + int nChange; /* Statement changes (Vdbe.nChanges) */ }; /* ** An instance of the virtual machine. This structure contains the complete ** state of the virtual machine. @@ -345,10 +344,12 @@ int popStack; /* Pop the stack this much on entry to VdbeExec() */ char *zErrMsg; /* Error message written here */ u8 resOnStack; /* True if there are result values on the stack */ u8 explain; /* True if EXPLAIN present on SQL command */ u8 autoCommitOn; /* True if autocommit got turned on by this program */ + int nChange; /* Number of db changes made since last reset */ + u8 changeCntOn; /* True to update the change-counter */ }; /* ** The following are allowed values for Vdbe.magic */ Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -643,10 +643,11 @@ p->returnDepth = 0; p->errorAction = OE_Abort; p->popStack = 0; p->explain |= isExplain; p->magic = VDBE_MAGIC_RUN; + p->nChange = 0; #ifdef VDBE_PROFILE { int i; for(i=0; inOp; i++){ p->aOp[i].cnt = 0; @@ -1245,10 +1246,19 @@ rc = xFunc(pBt); if( p->rc==SQLITE_OK ) p->rc = rc; } } + /* If this was an INSERT, UPDATE or DELETE, set the change counter. */ + if( p->changeCntOn ){ + if( !xFunc || xFunc==sqlite3BtreeCommitStmt ){ + sqlite3VdbeSetChanges(db, p->nChange); + }else{ + sqlite3VdbeSetChanges(db, 0); + } + p->nChange = 0; + } if( p->rc!=SQLITE_OK ){ sqlite3RollbackInternalChanges(db); }else if( db->flags & SQLITE_InternChanges ){ sqlite3CommitInternalChanges(db); @@ -1353,11 +1363,10 @@ Op *pOp = &p->aOp[i]; if( pOp->p3type==P3_DYNAMIC || pOp->p3type==P3_KEYINFO ){ sqliteFree(pOp->p3); } if( pOp->p3type==P3_VDBEFUNC ){ - int j; VdbeFunc *pVdbeFunc = (VdbeFunc *)pOp->p3; sqlite3VdbeDeleteAuxData(pVdbeFunc, -1); sqliteFree(pVdbeFunc); } #ifndef NDEBUG @@ -1753,5 +1762,25 @@ lenRowid = sqlite3VdbeIdxRowidLen(m.n, m.z); *res = sqlite3VdbeRecordCompare(pC->pKeyInfo, m.n-lenRowid, m.z, nKey, pKey); sqlite3VdbeMemRelease(&m); return SQLITE_OK; } + +/* +** This routine sets the value to be returned by subsequent calls to +** sqlite3_changes() on the database handle 'db'. +*/ +void sqlite3VdbeSetChanges(sqlite3 *db, int nChange){ + db->nChange = nChange; + db->nTotalChange += nChange; +} + +/* +** Set a flag in the vdbe to update the change counter when it is finalised +** or reset. +*/ +void sqlite3VdbeCountChanges(Vdbe *p){ + p->changeCntOn = 1; +} + + + Index: test/capi2.test ================================================================== --- test/capi2.test +++ test/capi2.test @@ -9,11 +9,11 @@ # #*********************************************************************** # This file implements regression tests for SQLite library. The # focus of this script testing the callback-free C/C++ API. # -# $Id: capi2.test,v 1.14 2004/06/19 00:16:31 drh Exp $ +# $Id: capi2.test,v 1.15 2004/06/21 06:50:29 danielk1977 Exp $ # set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -225,15 +225,15 @@ [get_column_names $VM] } {SQLITE_DONE 0 {} {}} # Update for v3 - the change has not actually happened until the query is # finalized. Is this going to cause trouble for anyone? Lee Nelson maybe? -do_test capi2-3.10b {sqlite3_changes $DB} {1} +do_test capi2-3.10b {db changes} {0} do_test capi2-3.11 { sqlite3_finalize $VM } {SQLITE_OK} -do_test capi2-3.11b {sqlite3_changes $DB} {1} +do_test capi2-3.11b {db changes} {1} do_test capi2-3.12 { sqlite3_finalize $VM } {SQLITE_MISUSE} do_test capi2-3.13 { set VM [sqlite3_prepare $DB {INSERT INTO t1 VALUES(1,3,4)} -1 TAIL] @@ -240,11 +240,15 @@ list [sqlite3_step $VM] \ [sqlite3_column_count $VM] \ [get_row_values $VM] \ [get_column_names $VM] } {SQLITE_ERROR 0 {} {}} -do_test capi2-3.13b {db changes} {0} + +# Update for v3: Preparing a statement does not affect the change counter. +# (Test result changes from 0 to 1). +do_test capi2-3.13b {db changes} {1} + do_test capi2-3.14 { list [sqlite3_finalize $VM] [sqlite3_errmsg $DB] } {SQLITE_CONSTRAINT {column a is not unique}} do_test capi2-3.15 { set VM [sqlite3_prepare $DB {CREATE TABLE t2(a NOT NULL, b)} -1 TAIL] @@ -600,13 +604,20 @@ stepsql $DB { UPDATE t1 SET a=a-20; SELECT * FROM t1; } } {0 4 1 2 3 2 3 4 3 4 5 4 5 6} + +# Update for version 3: A SELECT statement no longer resets the change +# counter (Test result changes from 0 to 4). do_test capi2-7.11 { sqlite3_changes $DB -} {0} +} {4} +do_test capi2-7.11a { + execsql {SELECT count(*) FROM t1} +} {4} + do_test capi2-7.12 { set x [stepsql $DB {EXPLAIN SELECT * FROM t1}] lindex $x 0 } {0} Index: test/conflict.test ================================================================== --- test/conflict.test +++ test/conflict.test @@ -11,11 +11,11 @@ # This file implements regression tests for SQLite library. # # This file implements tests for the conflict resolution extension # to SQLite. # -# $Id: conflict.test,v 1.21 2004/05/31 08:55:34 danielk1977 Exp $ +# $Id: conflict.test,v 1.22 2004/06/21 06:50:29 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # Create tables for the first group of tests. @@ -381,13 +381,16 @@ do_test conflict-7.7 { execsql { SELECT count(*) FROM t1; } } {1} + +# Update for version 3: A SELECT statement no longer resets the change +# counter (Test result changes from 0 to 50). do_test conflict-7.7.1 { db changes -} {0} +} {50} # Make sure the row count is right for rows that are ignored on # an insert. # do_test conflict-8.1 { Index: test/insert2.test ================================================================== --- test/insert2.test +++ test/insert2.test @@ -10,11 +10,11 @@ #*********************************************************************** # This file implements regression tests for SQLite library. The # focus of this file is testing the INSERT statement that takes is # result from a SELECT. # -# $Id: insert2.test,v 1.10 2002/06/25 13:16:04 drh Exp $ +# $Id: insert2.test,v 1.11 2004/06/21 06:50:29 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # Create some tables with data that we can select against @@ -123,10 +123,11 @@ execsql { SELECT * from t4; } } {1 2} do_test insert2-3.2 { + set x [db total_changes] execsql { BEGIN; INSERT INTO t4 VALUES(2,4); INSERT INTO t4 VALUES(3,6); INSERT INTO t4 VALUES(4,8); @@ -136,11 +137,11 @@ INSERT INTO t4 VALUES(8,16); INSERT INTO t4 VALUES(9,18); INSERT INTO t4 VALUES(10,20); COMMIT; } - db changes + expr [db total_changes] - $x } {9} do_test insert2-3.2.1 { execsql { SELECT count(*) FROM t4; } Index: test/laststmtchanges.test ================================================================== --- test/laststmtchanges.test +++ test/laststmtchanges.test @@ -5,29 +5,30 @@ # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # -# Tests to make sure that value returned by last_statement_change_count() -# (LSCC) is updated properly, especially inside triggers -# -# Note 1: LSCC remains constant within a statement and only updates once -# the statement is finished (triggers count as part of statement) -# Note 2: LSCC is changed within the context of a trigger -# much like last_insert_rowid() (see lastinsert.test), -# but is restored once the trigger exits -# Note 3: LSCC is not changed by a change to a view (since everything -# is done within instead of trigger context) +# Tests to make sure that values returned by changes() and total_changes() +# are updated properly, especially inside triggers +# +# Note 1: changes() remains constant within a statement and only updates +# once the statement is finished (triggers count as part of +# statement). +# Note 2: changes() is changed within the context of a trigger much like +# last_insert_rowid() (see lastinsert.test), but is restored once +# the trigger exits. +# Note 3: changes() is not changed by a change to a view (since everything +# is done within instead of trigger context). # set testdir [file dirname $argv0] source $testdir/tester.tcl # ---------------------------------------------------------------------------- # 1.x - basic tests (no triggers) -# LSCC set properly after insert +# changes() set properly after insert do_test laststmtchanges-1.1 { catchsql { create table t0 (x); insert into t0 values (1); insert into t0 values (1); @@ -35,136 +36,148 @@ insert into t0 values (2); insert into t0 values (1); insert into t0 values (1); insert into t0 values (1); insert into t0 values (2); - select last_statement_change_count(); + select changes(), total_changes(); } -} {0 1} +} {0 {1 8}} -# LSCC set properly after update +# changes() set properly after update do_test laststmtchanges-1.2 { catchsql { update t0 set x=3 where x=1; - select last_statement_change_count(); + select changes(), total_changes(); } -} {0 5} +} {0 {5 13}} -# LSCC unchanged within an update statement +# changes() unchanged within an update statement do_test laststmtchanges-1.3 { catchsql { - update t0 set x=x+last_statement_change_count() where x=3; + update t0 set x=x+changes() where x=3; select count() from t0 where x=8; } } {0 5} -# LSCC set properly after update on table where no rows changed +# changes() set properly after update on table where no rows changed do_test laststmtchanges-1.4 { catchsql { update t0 set x=77 where x=88; - select last_statement_change_count(); + select changes(); } } {0 0} -# LSCC set properly after delete from table +# changes() set properly after delete from table do_test laststmtchanges-1.5 { catchsql { delete from t0 where x=2; - select last_statement_change_count(); + select changes(); } } {0 3} # ---------------------------------------------------------------------------- # 2.x - tests with after insert trigger -# LSCC changed properly after insert into table containing after trigger +# changes() changed properly after insert into table containing after trigger do_test laststmtchanges-2.1 { + set ::tc [db total_changes] catchsql { create table t1 (k integer primary key); create table t2 (k integer primary key, v1, v2); create trigger r1 after insert on t1 for each row begin - insert into t2 values (NULL, last_statement_change_count(), NULL); + insert into t2 values (NULL, changes(), NULL); update t0 set x=x; - update t2 set v2=last_statement_change_count(); + update t2 set v2=changes(); end; insert into t1 values (77); - select last_statement_change_count(); + select changes(); } } {0 1} -# LSCC unchanged upon entry into after insert trigger +# changes() unchanged upon entry into after insert trigger do_test laststmtchanges-2.2 { catchsql { select v1 from t2; } } {0 3} -# LSCC changed properly by update within context of after insert trigger +# changes() changed properly by update within context of after insert trigger do_test laststmtchanges-2.3 { catchsql { select v2 from t2; } } {0 5} + +# Total changes caused by firing the trigger above: +# +# 1 from "insert into t1 values(77)" + +# 1 from "insert into t2 values (NULL, changes(), NULL);" + +# 5 from "update t0 set x=x;" + +# 1 from "update t2 set v2=changes();" +# +do_test laststmtchanges-2.4 { + expr [db total_changes] - $::tc +} {8} # ---------------------------------------------------------------------------- # 3.x - tests with after update trigger -# LSCC changed properly after update into table containing after trigger +# changes() changed properly after update into table containing after trigger do_test laststmtchanges-3.1 { catchsql { drop trigger r1; delete from t2; delete from t2; create trigger r1 after update on t1 for each row begin - insert into t2 values (NULL, last_statement_change_count(), NULL); + insert into t2 values (NULL, changes(), NULL); delete from t0 where oid=1 or oid=2; - update t2 set v2=last_statement_change_count(); + update t2 set v2=changes(); end; update t1 set k=k; - select last_statement_change_count(); + select changes(); } } {0 1} -# LSCC unchanged upon entry into after update trigger +# changes() unchanged upon entry into after update trigger do_test laststmtchanges-3.2 { catchsql { select v1 from t2; } } {0 0} -# LSCC changed properly by delete within context of after update trigger +# changes() changed properly by delete within context of after update trigger do_test laststmtchanges-3.3 { catchsql { select v2 from t2; } } {0 2} # ---------------------------------------------------------------------------- # 4.x - tests with before delete trigger -# LSCC changed properly on delete from table containing before trigger +# changes() changed properly on delete from table containing before trigger do_test laststmtchanges-4.1 { catchsql { drop trigger r1; delete from t2; delete from t2; create trigger r1 before delete on t1 for each row begin - insert into t2 values (NULL, last_statement_change_count(), NULL); + insert into t2 values (NULL, changes(), NULL); insert into t0 values (5); - update t2 set v2=last_statement_change_count(); + update t2 set v2=changes(); end; delete from t1; - select last_statement_change_count(); + select changes(); } } {0 1} -# LSCC unchanged upon entry into before delete trigger +# changes() unchanged upon entry into before delete trigger do_test laststmtchanges-4.2 { catchsql { select v1 from t2; } } {0 0} -# LSCC changed properly by insert within context of before delete trigger +# changes() changed properly by insert within context of before delete trigger do_test laststmtchanges-4.3 { catchsql { select v2 from t2; } } {0 1} @@ -189,44 +202,44 @@ insert into t0 values (1); insert into t0 values (2); insert into t0 values (2); insert into t0 values (1); create temp trigger r1 instead of insert on v1 for each row begin - insert into n1 values (NULL, last_statement_change_count()); + insert into n1 values (NULL, changes()); update t0 set x=x*10 where x=1; - insert into n1 values (NULL, last_statement_change_count()); + insert into n1 values (NULL, changes()); insert into t1 values (NEW.k); - insert into n1 values (NULL, last_statement_change_count()); + insert into n1 values (NULL, changes()); update t0 set x=x*10 where x=0; insert into v2 values (100+NEW.k); - insert into n1 values (NULL, last_statement_change_count()); + insert into n1 values (NULL, changes()); end; create temp trigger r2 instead of insert on v2 for each row begin - insert into n2 values (NULL, last_statement_change_count()); + insert into n2 values (NULL, changes()); insert into t2 values (1000+NEW.k); - insert into n2 values (NULL, last_statement_change_count()); + insert into n2 values (NULL, changes()); update t0 set x=x*100 where x=0; - insert into n2 values (NULL, last_statement_change_count()); + insert into n2 values (NULL, changes()); delete from t0 where x=2; - insert into n2 values (NULL, last_statement_change_count()); + insert into n2 values (NULL, changes()); end; insert into t1 values (77); - select last_statement_change_count(); + select changes(); } } {0 1} do_test laststmtchanges-5.2 { catchsql { delete from t1 where k=88; - select last_statement_change_count(); + select changes(); } } {0 0} do_test laststmtchanges-5.3 { catchsql { insert into v1 values (5); - select last_statement_change_count(); + select changes(); } } {0 0} do_test laststmtchanges-5.4 { catchsql { Index: test/tclsqlite.test ================================================================== --- test/tclsqlite.test +++ test/tclsqlite.test @@ -13,11 +13,11 @@ # # Actually, all tests are based on the TCL interface, so the main # interface is pretty well tested. This file contains some addition # tests for fringe issues that the main test suite does not cover. # -# $Id: tclsqlite.test,v 1.24 2004/06/19 00:16:31 drh Exp $ +# $Id: tclsqlite.test,v 1.25 2004/06/21 06:50:29 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # Check the error messages generated by tclsqlite @@ -32,11 +32,11 @@ lappend v $msg } [list 1 "wrong # args: should be \"$r\""] do_test tcl-1.2 { set v [catch {db bogus} msg] lappend v $msg -} {1 {bad option "bogus": must be authorizer, busy, changes, close, commit_hook, complete, errorcode, eval, function, last_insert_rowid, last_statement_changes, onecolumn, progress, rekey, timeout, trace, collate, or collation_needed}} +} {1 {bad option "bogus": must be authorizer, busy, changes, close, commit_hook, complete, errorcode, eval, function, last_insert_rowid, onecolumn, progress, rekey, timeout, trace, collate, collation_needed, or total_changes}} do_test tcl-1.3 { execsql {CREATE TABLE t1(a int, b int)} execsql {INSERT INTO t1 VALUES(10,20)} set v [catch { db eval {SELECT * FROM t1} data {