/ Check-in [093d8cd8]
Login

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

Overview
Comment:Add the experimental sqlite3_transaction_hook() API.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 093d8cd8e2f3a6af5d40cf810e396f4919eb5cef
User & Date: dan 2011-03-03 20:06:00
Context
2011-03-08
19:22
Add start of sessions feature. check-in: 269a81a3 user: dan tags: sessions
2011-03-03
20:06
Add the experimental sqlite3_transaction_hook() API. check-in: 093d8cd8 user: dan tags: sessions
2011-03-01
18:42
Add the experimental sqlite3_preupdate_hook() API. check-in: 6145d7b8 user: dan tags: sessions
Changes
Hide Diffs Unified Diffs Show Whitespace Changes Patch

Changes to src/main.c.

760
761
762
763
764
765
766




















767
768
769
770
771
772
773
...
792
793
794
795
796
797
798




799
800
801
802
803
804
805
....
1285
1286
1287
1288
1289
1290
1291


















1292
1293
1294
1295
1296
1297
1298
  if( db->lookaside.bMalloced ){
    sqlite3_free(db->lookaside.pStart);
  }
  sqlite3_free(db);
  return SQLITE_OK;
}





















/*
** Rollback all database files.
*/
void sqlite3RollbackAll(sqlite3 *db){
  int i;
  int inTrans = 0;
  assert( sqlite3_mutex_held(db->mutex) );
................................................................................
  /* Any deferred constraint violations have now been resolved. */
  db->nDeferredCons = 0;

  /* If one has been configured, invoke the rollback-hook callback */
  if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){
    db->xRollbackCallback(db->pRollbackArg);
  }




}

/*
** Return a static string that describes the kind of error specified in the
** argument.
*/
const char *sqlite3ErrStr(int rc){
................................................................................
  sqlite3_mutex_enter(db->mutex);
  pRet = db->pPreUpdateArg;
  db->xPreUpdateCallback = xCallback;
  db->pPreUpdateArg = pArg;
  sqlite3_mutex_leave(db->mutex);
  return pRet;
}



















#ifndef SQLITE_OMIT_WAL
/*
** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint().
** Invoke sqlite3_wal_checkpoint if the number of frames in the log file
** is greater than sqlite3.pWalArg cast to an integer (the value configured by
** wal_autocheckpoint()).







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
...
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
....
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
  if( db->lookaside.bMalloced ){
    sqlite3_free(db->lookaside.pStart);
  }
  sqlite3_free(db);
  return SQLITE_OK;
}


/*
** Invoke the transaction-hook.
*/
void sqlite3TransactionHook(sqlite3 *db, int op, int iLevel){
  assert( op==SQLITE_BEGIN || op==SQLITE_COMMIT || op==SQLITE_ROLLBACK );
  assert( op==SQLITE_BEGIN || iLevel<db->iOpenTrans || iLevel==0 );
  assert( op==SQLITE_BEGIN || iLevel<db->iOpenTrans || db->iOpenTrans==0 );
  assert( op!=SQLITE_BEGIN || iLevel==db->iOpenTrans || iLevel==0 );
  assert( op!=SQLITE_BEGIN || iLevel==db->iOpenTrans || db->iOpenTrans==1 );

  if( op==SQLITE_BEGIN && iLevel!=db->iOpenTrans ) return;
  if( op!=SQLITE_BEGIN && iLevel>=db->iOpenTrans ) return;

  if( db->xTransCallback ){
    db->xTransCallback(db->pTransArg, op, iLevel);
  }
  db->iOpenTrans = iLevel + (op==SQLITE_BEGIN);
}

/*
** Rollback all database files.
*/
void sqlite3RollbackAll(sqlite3 *db){
  int i;
  int inTrans = 0;
  assert( sqlite3_mutex_held(db->mutex) );
................................................................................
  /* Any deferred constraint violations have now been resolved. */
  db->nDeferredCons = 0;

  /* If one has been configured, invoke the rollback-hook callback */
  if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){
    db->xRollbackCallback(db->pRollbackArg);
  }

  /* If a transaction-hook is configured, invoke it now to report on 
  ** the rollback operation. */
  sqlite3TransactionHook(db, SQLITE_ROLLBACK, 0);
}

/*
** Return a static string that describes the kind of error specified in the
** argument.
*/
const char *sqlite3ErrStr(int rc){
................................................................................
  sqlite3_mutex_enter(db->mutex);
  pRet = db->pPreUpdateArg;
  db->xPreUpdateCallback = xCallback;
  db->pPreUpdateArg = pArg;
  sqlite3_mutex_leave(db->mutex);
  return pRet;
}

/*
** Register a callback to be invoked each time a transaction or savepoint
** is opened, committed or rolled back.
*/
void *sqlite3_transaction_hook(
  sqlite3 *db,                              /* Database handle */
  void(*xCallback)(void *, int, int),       /* Callback function */
  void *pArg                                /* First callback argument */
){
  void *pRet;
  sqlite3_mutex_enter(db->mutex);
  pRet = db->pTransArg;
  db->xTransCallback = xCallback;
  db->pTransArg = pArg;
  sqlite3_mutex_leave(db->mutex);
  return pRet;
}

#ifndef SQLITE_OMIT_WAL
/*
** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint().
** Invoke sqlite3_wal_checkpoint if the number of frames in the log file
** is greater than sqlite3.pWalArg cast to an integer (the value configured by
** wal_autocheckpoint()).

Changes to src/sqlite.h.in.

6340
6341
6342
6343
6344
6345
6346








6347
6348
6349
6350
6351
6352
6353
6354
6355
6356
6357
6358
6359



6360
6361
6362
6363
6364
6365
6366
6367
6368
6369




6370
6371
6372
6373
6374
6375
6376
** documentation for additional information about the meaning and use of
** each of these values.
*/
#define SQLITE_CHECKPOINT_PASSIVE 0
#define SQLITE_CHECKPOINT_FULL    1
#define SQLITE_CHECKPOINT_RESTART 2









void *sqlite3_preupdate_hook(
  sqlite3 *db,
  void(*xPreUpdate)(
    void *pCtx,                   /* Copy of third arg to preupdate_hook() */
    sqlite3 *db,                  /* Database handle */
    int op,                       /* SQLITE_UPDATE, DELETE or INSERT */
    char const *zDb,              /* Database name */
    char const *zName,            /* Table name */
    sqlite3_int64 iKey1,          /* Rowid of row about to be deleted/updated */
    sqlite3_int64 iKey2           /* New rowid value (for a rowid UPDATE) */
  ),
  void*
);




/*
** The following APIs may only be used from within a pre-update callback. More
** specifically, the preupdate_old() API may only be used from within an
** SQLITE_UPDATE or SQLITE_DELETE pre-update callback. The preupdate_modified()
** API may only be used from within an SQLITE_UPDATE pre-update callback.
*/
int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
int sqlite3_preupdate_modified(sqlite3 *, int, int *);
int sqlite3_preupdate_count(sqlite3 *);






/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
#ifdef SQLITE_OMIT_FLOATING_POINT







>
>
>
>
>
>
>
>
|












>
>
>


<
<
<
|

|
|
<
>
>
>
>







6340
6341
6342
6343
6344
6345
6346
6347
6348
6349
6350
6351
6352
6353
6354
6355
6356
6357
6358
6359
6360
6361
6362
6363
6364
6365
6366
6367
6368
6369
6370
6371
6372



6373
6374
6375
6376

6377
6378
6379
6380
6381
6382
6383
6384
6385
6386
6387
** documentation for additional information about the meaning and use of
** each of these values.
*/
#define SQLITE_CHECKPOINT_PASSIVE 0
#define SQLITE_CHECKPOINT_FULL    1
#define SQLITE_CHECKPOINT_RESTART 2


/*
** CAPI3REF: The pre-update hook.
** 
** The preupdate_old() API may only be used from within an SQLITE_UPDATE or
** SQLITE_DELETE pre-update callback. The preupdate_modified() API may only 
** be used from within an SQLITE_UPDATE pre-update callback.
*/
SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook(
  sqlite3 *db,
  void(*xPreUpdate)(
    void *pCtx,                   /* Copy of third arg to preupdate_hook() */
    sqlite3 *db,                  /* Database handle */
    int op,                       /* SQLITE_UPDATE, DELETE or INSERT */
    char const *zDb,              /* Database name */
    char const *zName,            /* Table name */
    sqlite3_int64 iKey1,          /* Rowid of row about to be deleted/updated */
    sqlite3_int64 iKey2           /* New rowid value (for a rowid UPDATE) */
  ),
  void*
);
SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
SQLITE_EXPERIMENTAL int sqlite3_preupdate_modified(sqlite3 *, int, int *);
SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *);

/*



** CAPI3REF: The transaction hook.
*/
SQLITE_EXPERIMENTAL
void *sqlite3_transaction_hook(sqlite3 *, void(*)(void *, int, int), void *);


#define SQLITE_BEGIN    1
#define SQLITE_COMMIT   2
#define SQLITE_ROLLBACK 3


/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
#ifdef SQLITE_OMIT_FLOATING_POINT

Changes to src/sqliteInt.h.

829
830
831
832
833
834
835



836
837
838
839
840
841
842
....
2744
2745
2746
2747
2748
2749
2750

2751
2752
2753
2754
2755
2756
2757
  void *pUpdateArg;
  void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
  void *pPreUpdateArg;          /* First argument to xPreUpdateCallback */
  void (*xPreUpdateCallback)(   /* Registered using sqlite3_preupdate_hook() */
    void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64
  );
  PreUpdate *pPreUpdate;        /* Context for active pre-update callback */



#ifndef SQLITE_OMIT_WAL
  int (*xWalCallback)(void *, sqlite3 *, const char *, int);
  void *pWalArg;
#endif
  void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
  void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
  void *pCollNeededArg;
................................................................................
void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
Vdbe *sqlite3GetVdbe(Parse*);
void sqlite3PrngSaveState(void);
void sqlite3PrngRestoreState(void);
void sqlite3PrngResetState(void);
void sqlite3RollbackAll(sqlite3*);

void sqlite3CodeVerifySchema(Parse*, int);
void sqlite3BeginTransaction(Parse*, int);
void sqlite3CommitTransaction(Parse*);
void sqlite3RollbackTransaction(Parse*);
void sqlite3Savepoint(Parse*, int, Token*);
void sqlite3CloseSavepoints(sqlite3 *);
int sqlite3ExprIsConstant(Expr*);







>
>
>







 







>







829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
....
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
  void *pUpdateArg;
  void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
  void *pPreUpdateArg;          /* First argument to xPreUpdateCallback */
  void (*xPreUpdateCallback)(   /* Registered using sqlite3_preupdate_hook() */
    void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64
  );
  PreUpdate *pPreUpdate;        /* Context for active pre-update callback */
  void *pTransArg;              /* First argument to xTransCallback */
  void (*xTransCallback)(void*,int,int);
  int iOpenTrans;               /* Open transaction (xTransCallback) plus 1 */
#ifndef SQLITE_OMIT_WAL
  int (*xWalCallback)(void *, sqlite3 *, const char *, int);
  void *pWalArg;
#endif
  void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
  void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*);
  void *pCollNeededArg;
................................................................................
void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*);
void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*);
Vdbe *sqlite3GetVdbe(Parse*);
void sqlite3PrngSaveState(void);
void sqlite3PrngRestoreState(void);
void sqlite3PrngResetState(void);
void sqlite3RollbackAll(sqlite3*);
void sqlite3TransactionHook(sqlite3 *, int, int);
void sqlite3CodeVerifySchema(Parse*, int);
void sqlite3BeginTransaction(Parse*, int);
void sqlite3CommitTransaction(Parse*);
void sqlite3RollbackTransaction(Parse*);
void sqlite3Savepoint(Parse*, int, Token*);
void sqlite3CloseSavepoints(sqlite3 *);
int sqlite3ExprIsConstant(Expr*);

Changes to src/tclsqlite.c.

120
121
122
123
124
125
126

127
128
129
130
131
132
133
...
592
593
594
595
596
597
598
























599
600
601
602
603
604
605
....
1624
1625
1626
1627
1628
1629
1630

1631
1632
1633
1634
1635
1636
1637
....
1655
1656
1657
1658
1659
1660
1661

1662
1663
1664
1665
1666
1667
1668
1669
....
1670
1671
1672
1673
1674
1675
1676
1677

1678
1679
1680
1681
1682
1683
1684
....
2910
2911
2912
2913
2914
2915
2916

2917
2918
2919
2920

2921
2922
2923
2924
2925
2926
2927
2928
2929

2930
2931
2932
2933
2934
2935
2936
  char *zAuth;               /* The authorization callback routine */
  int disableAuth;           /* Disable the authorizer if it exists */
  char *zNull;               /* Text to substitute for an SQL NULL value */
  SqlFunc *pFunc;            /* List of SQL functions */
  Tcl_Obj *pUpdateHook;      /* Update hook script (if any) */
  Tcl_Obj *pPreUpdateHook;   /* Pre-update hook script (if any) */
  Tcl_Obj *pRollbackHook;    /* Rollback hook script (if any) */

  Tcl_Obj *pWalHook;         /* WAL hook script (if any) */
  Tcl_Obj *pUnlockNotify;    /* Unlock notify script (if any) */
  SqlCollate *pCollate;      /* List of SQL collation functions */
  int rc;                    /* Return code of most recent sqlite3_exec() */
  Tcl_Obj *pCollateNeeded;   /* Collation needed script */
  SqlPreparedStmt *stmtList; /* List of prepared statements*/
  SqlPreparedStmt *stmtLast; /* Last statement in the list */
................................................................................
static void DbRollbackHandler(void *clientData){
  SqliteDb *pDb = (SqliteDb*)clientData;
  assert(pDb->pRollbackHook);
  if( TCL_OK!=Tcl_EvalObjEx(pDb->interp, pDb->pRollbackHook, 0) ){
    Tcl_BackgroundError(pDb->interp);
  }
}

























/*
** This procedure handles wal_hook callbacks.
*/
static int DbWalHandler(
  void *clientData, 
  sqlite3 *db, 
................................................................................
    }
  }

  sqlite3_preupdate_hook(db, (pDb->pPreUpdateHook?DbPreUpdateHandler:0), pDb);
  sqlite3_update_hook(db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
  sqlite3_rollback_hook(db, (pDb->pRollbackHook?DbRollbackHandler:0), pDb);
  sqlite3_wal_hook(db, (pDb->pWalHook?DbWalHandler:0), pDb);

}

/*
** The "sqlite" command below creates a new Tcl command for each
** connection it opens to an SQLite database.  This routine is invoked
** whenever one of those connection-specific commands is executed
** in Tcl.  For example, if you run Tcl code like this:
................................................................................
    "errorcode",          "eval",              "exists",
    "function",           "incrblob",          "interrupt",
    "last_insert_rowid",  "nullvalue",         "onecolumn",
    "preupdate",
    "profile",            "progress",          "rekey",
    "restore",            "rollback_hook",     "status",
    "timeout",            "total_changes",     "trace",

    "transaction",        "unlock_notify",     "update_hook",
    "version",            "wal_hook",          0
  };
  enum DB_enum {
    DB_AUTHORIZER,        DB_BACKUP,           DB_BUSY,
    DB_CACHE,             DB_CHANGES,          DB_CLOSE,
    DB_COLLATE,           DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
    DB_COMPLETE,          DB_COPY,             DB_ENABLE_LOAD_EXTENSION,
................................................................................
    DB_ERRORCODE,         DB_EVAL,             DB_EXISTS,
    DB_FUNCTION,          DB_INCRBLOB,         DB_INTERRUPT,
    DB_LAST_INSERT_ROWID, DB_NULLVALUE,        DB_ONECOLUMN,
    DB_PREUPDATE,
    DB_PROFILE,           DB_PROGRESS,         DB_REKEY,
    DB_RESTORE,           DB_ROLLBACK_HOOK,    DB_STATUS,
    DB_TIMEOUT,           DB_TOTAL_CHANGES,    DB_TRACE,
    DB_TRANSACTION,       DB_UNLOCK_NOTIFY,    DB_UPDATE_HOOK,

    DB_VERSION,           DB_WAL_HOOK
  };
  /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */

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

  /*
  **    $db wal_hook ?script?
  **    $db update_hook ?script?
  **    $db rollback_hook ?script?

  */
  case DB_WAL_HOOK: 
  case DB_UPDATE_HOOK: 
  case DB_ROLLBACK_HOOK: {

    sqlite3 *db = pDb->db;

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

    if( objc>3 ){
       Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
       return TCL_ERROR;
    }

    DbHookCmd(interp, pDb, (objc==3 ? objv[2] : 0), ppHook);
    break;







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>







 







>
|







 







|
>







 







>



|
>









>







120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
...
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
....
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
....
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
....
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
....
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
  char *zAuth;               /* The authorization callback routine */
  int disableAuth;           /* Disable the authorizer if it exists */
  char *zNull;               /* Text to substitute for an SQL NULL value */
  SqlFunc *pFunc;            /* List of SQL functions */
  Tcl_Obj *pUpdateHook;      /* Update hook script (if any) */
  Tcl_Obj *pPreUpdateHook;   /* Pre-update hook script (if any) */
  Tcl_Obj *pRollbackHook;    /* Rollback hook script (if any) */
  Tcl_Obj *pTransHook;       /* Transaction hook script (if any) */
  Tcl_Obj *pWalHook;         /* WAL hook script (if any) */
  Tcl_Obj *pUnlockNotify;    /* Unlock notify script (if any) */
  SqlCollate *pCollate;      /* List of SQL collation functions */
  int rc;                    /* Return code of most recent sqlite3_exec() */
  Tcl_Obj *pCollateNeeded;   /* Collation needed script */
  SqlPreparedStmt *stmtList; /* List of prepared statements*/
  SqlPreparedStmt *stmtLast; /* Last statement in the list */
................................................................................
static void DbRollbackHandler(void *clientData){
  SqliteDb *pDb = (SqliteDb*)clientData;
  assert(pDb->pRollbackHook);
  if( TCL_OK!=Tcl_EvalObjEx(pDb->interp, pDb->pRollbackHook, 0) ){
    Tcl_BackgroundError(pDb->interp);
  }
}

/*
** sqlite3_transaction_hook() callback.
*/
static void DbTransHandler(void *clientData, int op, int iLevel){
  static const char *azStr[] = { "BEGIN", "COMMIT", "ROLLBACK" };
  SqliteDb *pDb = (SqliteDb*)clientData;
  Tcl_Interp *interp = pDb->interp;
  Tcl_Obj *pScript;

  assert(pDb->pTransHook);
  assert( SQLITE_BEGIN==1 );
  assert( SQLITE_COMMIT==2 );
  assert( SQLITE_ROLLBACK==3 );

  pScript = Tcl_DuplicateObj(pDb->pTransHook);
  Tcl_IncrRefCount(pScript);
  Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(azStr[op-1], -1));
  Tcl_ListObjAppendElement(interp, pScript, Tcl_NewIntObj(iLevel));
  if( TCL_OK!=Tcl_EvalObjEx(interp, pScript, 0) ){
    Tcl_BackgroundError(interp);
  }
  Tcl_DecrRefCount(pScript);
}

/*
** This procedure handles wal_hook callbacks.
*/
static int DbWalHandler(
  void *clientData, 
  sqlite3 *db, 
................................................................................
    }
  }

  sqlite3_preupdate_hook(db, (pDb->pPreUpdateHook?DbPreUpdateHandler:0), pDb);
  sqlite3_update_hook(db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
  sqlite3_rollback_hook(db, (pDb->pRollbackHook?DbRollbackHandler:0), pDb);
  sqlite3_wal_hook(db, (pDb->pWalHook?DbWalHandler:0), pDb);
  sqlite3_transaction_hook(db, (pDb->pTransHook?DbTransHandler:0), pDb);
}

/*
** The "sqlite" command below creates a new Tcl command for each
** connection it opens to an SQLite database.  This routine is invoked
** whenever one of those connection-specific commands is executed
** in Tcl.  For example, if you run Tcl code like this:
................................................................................
    "errorcode",          "eval",              "exists",
    "function",           "incrblob",          "interrupt",
    "last_insert_rowid",  "nullvalue",         "onecolumn",
    "preupdate",
    "profile",            "progress",          "rekey",
    "restore",            "rollback_hook",     "status",
    "timeout",            "total_changes",     "trace",
    "transaction",        "transaction_hook",
    "unlock_notify",     "update_hook",
    "version",            "wal_hook",          0
  };
  enum DB_enum {
    DB_AUTHORIZER,        DB_BACKUP,           DB_BUSY,
    DB_CACHE,             DB_CHANGES,          DB_CLOSE,
    DB_COLLATE,           DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
    DB_COMPLETE,          DB_COPY,             DB_ENABLE_LOAD_EXTENSION,
................................................................................
    DB_ERRORCODE,         DB_EVAL,             DB_EXISTS,
    DB_FUNCTION,          DB_INCRBLOB,         DB_INTERRUPT,
    DB_LAST_INSERT_ROWID, DB_NULLVALUE,        DB_ONECOLUMN,
    DB_PREUPDATE,
    DB_PROFILE,           DB_PROGRESS,         DB_REKEY,
    DB_RESTORE,           DB_ROLLBACK_HOOK,    DB_STATUS,
    DB_TIMEOUT,           DB_TOTAL_CHANGES,    DB_TRACE,
    DB_TRANSACTION,       DB_TRANSACTION_HOOK,
    DB_UNLOCK_NOTIFY,     DB_UPDATE_HOOK,
    DB_VERSION,           DB_WAL_HOOK
  };
  /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */

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

  /*
  **    $db wal_hook ?script?
  **    $db update_hook ?script?
  **    $db rollback_hook ?script?
  **    $db transaction_hook ?script?
  */
  case DB_WAL_HOOK: 
  case DB_UPDATE_HOOK: 
  case DB_ROLLBACK_HOOK:
  case DB_TRANSACTION_HOOK: {
    sqlite3 *db = pDb->db;

    /* set ppHook to point at pUpdateHook or pRollbackHook, depending on 
    ** whether [$db update_hook] or [$db rollback_hook] was invoked.
    */
    Tcl_Obj **ppHook; 
    if( choice==DB_WAL_HOOK ) ppHook = &pDb->pWalHook;
    if( choice==DB_UPDATE_HOOK ) ppHook = &pDb->pUpdateHook;
    if( choice==DB_ROLLBACK_HOOK ) ppHook = &pDb->pRollbackHook;
    if( choice==DB_TRANSACTION_HOOK ) ppHook = &pDb->pTransHook;
    if( objc>3 ){
       Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
       return TCL_ERROR;
    }

    DbHookCmd(interp, pDb, (objc==3 ? objv[2] : 0), ppHook);
    break;

Changes to src/vdbe.c.

2564
2565
2566
2567
2568
2569
2570


2571
2572
2573
2574
2575
2576
2577
....
2631
2632
2633
2634
2635
2636
2637







2638
2639
2640
2641
2642
2643
2644
....
2706
2707
2708
2709
2710
2711
2712



2713
2714
2715
2716
2717
2718
2719
....
2792
2793
2794
2795
2796
2797
2798

2799
2800
2801
2802
2803
2804
2805
        ** "transaction savepoint". */
        if( db->autoCommit ){
          db->autoCommit = 0;
          db->isTransactionSavepoint = 1;
        }else{
          db->nSavepoint++;
        }


    
        /* Link the new savepoint into the database handle's list. */
        pNew->pNext = db->pSavepoint;
        db->pSavepoint = pNew;
        pNew->nDeferredCons = db->nDeferredCons;
      }
    }
................................................................................
          }
        }
        if( p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){
          sqlite3ExpirePreparedStatements(db);
          sqlite3ResetInternalSchema(db, 0);
          db->flags = (db->flags | SQLITE_InternChanges);
        }







      }
  
      /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all 
      ** savepoints nested inside of the savepoint being operated on. */
      while( db->pSavepoint!=pSavepoint ){
        pTmp = db->pSavepoint;
        db->pSavepoint = pTmp->pNext;
................................................................................
    if( iRollback ){
      assert( desiredAutoCommit==1 );
      sqlite3RollbackAll(db);
      db->autoCommit = 1;
    }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
      goto vdbe_return;
    }else{



      db->autoCommit = (u8)desiredAutoCommit;
      if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
        p->pc = pc;
        db->autoCommit = (u8)(1-desiredAutoCommit);
        p->rc = rc = SQLITE_BUSY;
        goto vdbe_return;
      }
................................................................................
      assert( sqlite3BtreeIsInTrans(pBt) );
      if( p->iStatement==0 ){
        assert( db->nStatement>=0 && db->nSavepoint>=0 );
        db->nStatement++; 
        p->iStatement = db->nSavepoint + db->nStatement;
      }
      rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);


      /* Store the current value of the database handles deferred constraint
      ** counter. If the statement transaction needs to be rolled back,
      ** the value of this counter needs to be restored too.  */
      p->nStmtDefCons = db->nDeferredCons;
    }
  }







>
>







 







>
>
>
>
>
>
>







 







>
>
>







 







>







2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
....
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
....
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
....
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
        ** "transaction savepoint". */
        if( db->autoCommit ){
          db->autoCommit = 0;
          db->isTransactionSavepoint = 1;
        }else{
          db->nSavepoint++;
        }

        sqlite3TransactionHook(db, SQLITE_BEGIN, db->nSavepoint);
    
        /* Link the new savepoint into the database handle's list. */
        pNew->pNext = db->pSavepoint;
        db->pSavepoint = pNew;
        pNew->nDeferredCons = db->nDeferredCons;
      }
    }
................................................................................
          }
        }
        if( p1==SAVEPOINT_ROLLBACK && (db->flags&SQLITE_InternChanges)!=0 ){
          sqlite3ExpirePreparedStatements(db);
          sqlite3ResetInternalSchema(db, 0);
          db->flags = (db->flags | SQLITE_InternChanges);
        }

        assert( SAVEPOINT_ROLLBACK+1==SQLITE_ROLLBACK );
        assert( SAVEPOINT_RELEASE+1==SQLITE_COMMIT );
        sqlite3TransactionHook(db, p1+1, iSavepoint+1);
        if( p1==SAVEPOINT_ROLLBACK ){
          sqlite3TransactionHook(db, SQLITE_BEGIN, iSavepoint+1);
        }
      }
  
      /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all 
      ** savepoints nested inside of the savepoint being operated on. */
      while( db->pSavepoint!=pSavepoint ){
        pTmp = db->pSavepoint;
        db->pSavepoint = pTmp->pNext;
................................................................................
    if( iRollback ){
      assert( desiredAutoCommit==1 );
      sqlite3RollbackAll(db);
      db->autoCommit = 1;
    }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
      goto vdbe_return;
    }else{
      if( desiredAutoCommit==0 ){
        sqlite3TransactionHook(db, SQLITE_BEGIN, 0);
      }
      db->autoCommit = (u8)desiredAutoCommit;
      if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
        p->pc = pc;
        db->autoCommit = (u8)(1-desiredAutoCommit);
        p->rc = rc = SQLITE_BUSY;
        goto vdbe_return;
      }
................................................................................
      assert( sqlite3BtreeIsInTrans(pBt) );
      if( p->iStatement==0 ){
        assert( db->nStatement>=0 && db->nSavepoint>=0 );
        db->nStatement++; 
        p->iStatement = db->nSavepoint + db->nStatement;
      }
      rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
      sqlite3TransactionHook(db, SQLITE_BEGIN, p->iStatement);

      /* Store the current value of the database handles deferred constraint
      ** counter. If the statement transaction needs to be rolled back,
      ** the value of this counter needs to be restored too.  */
      p->nStmtDefCons = db->nDeferredCons;
    }
  }

Changes to src/vdbeapi.c.

397
398
399
400
401
402
403
404








405
406
407
408
409
410
411
#ifndef SQLITE_OMIT_TRACE
    if( db->xProfile && !db->init.busy ){
      sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime);
    }
#endif

    db->activeVdbeCnt++;
    if( p->readOnly==0 ) db->writeVdbeCnt++;








    p->pc = 0;
  }
#ifndef SQLITE_OMIT_EXPLAIN
  if( p->explain ){
    rc = sqlite3VdbeList(p);
  }else
#endif /* SQLITE_OMIT_EXPLAIN */







|
>
>
>
>
>
>
>
>







397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
#ifndef SQLITE_OMIT_TRACE
    if( db->xProfile && !db->init.busy ){
      sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime);
    }
#endif

    db->activeVdbeCnt++;
    if( p->readOnly==0 ){ 
      /* If this statement will open an implicit transaction, invoke the
      ** transaction-hook here. */
      if( db->autoCommit && db->writeVdbeCnt==0 ){
        assert( db->nSavepoint==0 );
        sqlite3TransactionHook(db, SQLITE_BEGIN, 0);
      }
      db->writeVdbeCnt++;
    }
    p->pc = 0;
  }
#ifndef SQLITE_OMIT_EXPLAIN
  if( p->explain ){
    rc = sqlite3VdbeList(p);
  }else
#endif /* SQLITE_OMIT_EXPLAIN */

Changes to src/vdbeaux.c.

1840
1841
1842
1843
1844
1845
1846






1847
1848
1849
1850
1851
1852
1853
....
1942
1943
1944
1945
1946
1947
1948



1949
1950
1951
1952
1953
1954
1955
    }
    sqlite3EndBenignMalloc();
    enable_simulated_io_errors();

    sqlite3VtabCommit(db);
  }
#endif







  return rc;
}

/* 
** This routine checks that the sqlite3.activeVdbeCnt count variable
** matches the number of vdbe's in the list sqlite3.pVdbe that are
................................................................................
        }
        if( rc==SQLITE_OK ){
          rc = rc2;
        }
      }
    }
    db->nStatement--;



    p->iStatement = 0;

    /* If the statement transaction is being rolled back, also restore the 
    ** database handles deferred constraint counter to the value it had when 
    ** the statement transaction was opened.  */
    if( eOp==SAVEPOINT_ROLLBACK ){
      db->nDeferredCons = p->nStmtDefCons;







>
>
>
>
>
>







 







>
>
>







1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
....
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
    }
    sqlite3EndBenignMalloc();
    enable_simulated_io_errors();

    sqlite3VtabCommit(db);
  }
#endif

  /* If a transaction-hook is configured, invoke it now to report on the
  ** successful commit operation. */
  if( rc==SQLITE_OK ){
    sqlite3TransactionHook(db, SQLITE_COMMIT, 0);
  }

  return rc;
}

/* 
** This routine checks that the sqlite3.activeVdbeCnt count variable
** matches the number of vdbe's in the list sqlite3.pVdbe that are
................................................................................
        }
        if( rc==SQLITE_OK ){
          rc = rc2;
        }
      }
    }
    db->nStatement--;
    assert( SAVEPOINT_ROLLBACK+1==SQLITE_ROLLBACK );
    assert( SAVEPOINT_RELEASE+1==SQLITE_COMMIT );
    sqlite3TransactionHook(db, eOp+1, p->iStatement);
    p->iStatement = 0;

    /* If the statement transaction is being rolled back, also restore the 
    ** database handles deferred constraint counter to the value it had when 
    ** the statement transaction was opened.  */
    if( eOp==SAVEPOINT_ROLLBACK ){
      db->nDeferredCons = p->nStmtDefCons;

Changes to test/hook.test.

612
613
614
615
616
617
618




619
620
621
622
623
624
625
626
627
628
629
630



































































































































631

do_execsql_test 7.5.2.0 {
  CREATE TABLE t8(a, b);
  INSERT INTO t8 VALUES('one', 'two');
  INSERT INTO t8 VALUES('three', 'four');
  ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx';
}





do_preupdate_test 7.5.2.1 {
  DELETE FROM t8 WHERE a = 'one'
} {
  DELETE main t8 1 1   one two xxx
}

do_preupdate_test 7.5.2.2 {
  UPDATE t8 SET b = 'five'
} {
  UPDATE main t8 2 2   three four xxx
}




































































































































finish_test








>
>
>
>





<






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
do_execsql_test 7.5.2.0 {
  CREATE TABLE t8(a, b);
  INSERT INTO t8 VALUES('one', 'two');
  INSERT INTO t8 VALUES('three', 'four');
  ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx';
}

# At time of writing, these two are broken. They demonstraight that the
# sqlite3_preupdate_old() method does not handle the case where ALTER TABLE
# has been used to add a column with a default value other than NULL.
#
do_preupdate_test 7.5.2.1 {
  DELETE FROM t8 WHERE a = 'one'
} {
  DELETE main t8 1 1   one two xxx
}

do_preupdate_test 7.5.2.2 {
  UPDATE t8 SET b = 'five'
} {
  UPDATE main t8 2 2   three four xxx
}

#----------------------------------------------------------------------------
# The following tests - hook-8.* - test the transaction hook.
#
db close
forcedelete test.db
sqlite3 db test.db

proc transaction_hook {op iLevel} {
  lappend ::transaction_hook $op $iLevel
}
db transaction_hook transaction_hook

proc do_transaction_test {tn sql x} {
  set X [list]
  foreach elem $x {lappend X $elem}

  uplevel do_test $tn [list "
    set ::transaction_hook \[list\]
    catchsql { $sql }
    set ::transaction_hook
  "] [list $X]
}

do_transaction_test 8.1.1 "CREATE TABLE t1(x)" {BEGIN 0 COMMIT 0}
do_transaction_test 8.1.2 "BEGIN"              {BEGIN 0}
do_transaction_test 8.1.3 "COMMIT"             {COMMIT 0}
do_transaction_test 8.1.4 "BEGIN ; ROLLBACK"   {BEGIN 0 ROLLBACK 0}

do_execsql_test 8.2.0 {
  CREATE TABLE t2(a PRIMARY KEY, b);
  INSERT INTO t2 VALUES(1, 'one');
  INSERT INTO t2 VALUES(2, 'two');
  INSERT INTO t2 VALUES(3, 'three');
}
do_transaction_test 8.2.1 {
  INSERT INTO t2 VALUES(2, 'xxx')
} {BEGIN 0 ROLLBACK 0}

do_transaction_test 8.2.2 {
  BEGIN; INSERT INTO t2 SELECT a-2, b FROM t2;
} {BEGIN 0 BEGIN 1 ROLLBACK 1}

do_transaction_test 8.2.3 {
  INSERT OR ROLLBACK INTO t2 SELECT a-2, b FROM t2;
} {ROLLBACK 0}

do_transaction_test 8.2.4 {
  BEGIN; INSERT INTO t2 SELECT a+3, b FROM t2;
} {BEGIN 0 BEGIN 1 COMMIT 1}

do_transaction_test 8.2.5 "COMMIT" {COMMIT 0}

do_transaction_test 8.3.1 {SELECT * FROM t2} {}

do_transaction_test 8.4.1 {
  SAVEPOINT top;
  RELEASE top;
} {BEGIN 0 COMMIT 0}

do_transaction_test 8.4.2 {
  SAVEPOINT top;
  ROLLBACK TO top;
  RELEASE top;
} {BEGIN 0 ROLLBACK 0 BEGIN 0 COMMIT 0}

do_transaction_test 8.4.3 {
  SAVEPOINT zero;
    SAVEPOINT one;
      SAVEPOINT two;
        SAVEPOINT three;
  ROLLBACK TO zero;
    SAVEPOINT one;
      SAVEPOINT two;
        SAVEPOINT three;
    ROLLBACK TO one;
      SAVEPOINT two;
  RELEASE zero;

  SAVEPOINT zero;
    SAVEPOINT one;
      SAVEPOINT two;
      RELEASE two;
      SAVEPOINT two;
        SAVEPOINT three;
    RELEASE one;
  ROLLBACK TO zero;
  RELEASE zero;
} {
  BEGIN 0 BEGIN 1 BEGIN 2 BEGIN 3 ROLLBACK 0 BEGIN 0 
  BEGIN 1 BEGIN 2 BEGIN 3 ROLLBACK 1 BEGIN 1 BEGIN 2 
  COMMIT 0

  BEGIN 0 BEGIN 1 BEGIN 2 COMMIT 2 BEGIN 2 BEGIN 3 COMMIT 1 
  ROLLBACK 0 BEGIN 0 COMMIT 0
}

do_transaction_test 8.4.4 {
  BEGIN;
    SAVEPOINT zero;
      SAVEPOINT one;
        SAVEPOINT two;
          SAVEPOINT three;
    ROLLBACK TO zero;
      SAVEPOINT one;
        SAVEPOINT two;
          SAVEPOINT three;
      ROLLBACK TO one;
        SAVEPOINT two;
    RELEASE zero;
  
    SAVEPOINT zero;
      SAVEPOINT one;
        SAVEPOINT two;
        RELEASE two;
        SAVEPOINT two;
          SAVEPOINT three;
      RELEASE one;
    ROLLBACK TO zero;
    RELEASE zero;
  COMMIT;
} {
  BEGIN 0
  BEGIN 1 BEGIN 2 BEGIN 3 BEGIN 4 ROLLBACK 1 BEGIN 1 
  BEGIN 2 BEGIN 3 BEGIN 4 ROLLBACK 2 BEGIN 2 BEGIN 3 
  COMMIT 1

  BEGIN 1 BEGIN 2 BEGIN 3 COMMIT 3 BEGIN 3 BEGIN 4 COMMIT 2 
  ROLLBACK 1 BEGIN 1 COMMIT 1
  COMMIT 0
}

finish_test

Changes to test/tclsqlite.test.

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







|







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