/ Check-in [1feaf2d3]
Login

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

Overview
Comment:Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirect() API.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 1feaf2d35fd9ec777319717ae2c2929d66fe7baa
User & Date: dan 2011-03-23 16:03:12
Context
2011-03-23
22:48
Merge in all the latest changes from the trunk, and especially the interface changes to the SystemCall methods of the VFS. check-in: 9c3a6e47 user: drh tags: sessions
16:03
Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirect() API. check-in: 1feaf2d3 user: dan tags: sessions
2011-03-22
18:45
Add API function sqlite3_preupdate_depth(), for determining the depth of the trigger stack from within a pre-update callback. check-in: bdea7089 user: dan tags: sessions
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/session/session1.test.

    84     84     sqlite3session S db main
    85     85     S attach t1
    86     86     execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
    87     87     execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') }
    88     88     execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
    89     89   } {}
    90     90   do_changeset_test 2.1.2 S {
    91         -  {INSERT t1 {} {i 1 t Sukhothai}}
    92         -  {INSERT t1 {} {i 2 t Ayutthaya}}
    93         -  {INSERT t1 {} {i 3 t Thonburi}}
           91  +  {INSERT t1 0 {} {i 1 t Sukhothai}}
           92  +  {INSERT t1 0 {} {i 2 t Ayutthaya}}
           93  +  {INSERT t1 0 {} {i 3 t Thonburi}}
    94     94   }
    95     95   do_changeset_invert_test 2.1.3 S {
    96         -  {DELETE t1 {i 1 t Sukhothai} {}}
    97         -  {DELETE t1 {i 2 t Ayutthaya} {}}
    98         -  {DELETE t1 {i 3 t Thonburi} {}}
           96  +  {DELETE t1 0 {i 1 t Sukhothai} {}}
           97  +  {DELETE t1 0 {i 2 t Ayutthaya} {}}
           98  +  {DELETE t1 0 {i 3 t Thonburi} {}}
    99     99   }
   100    100   do_test 2.1.4 { S delete } {}
   101    101   
   102    102   do_test 2.2.1 {
   103    103     sqlite3session S db main
   104    104     S attach t1
   105    105     execsql { DELETE FROM t1 WHERE 1 }
   106    106   } {}
   107    107   do_changeset_test 2.2.2 S {
   108         -  {DELETE t1 {i 1 t Sukhothai} {}}
   109         -  {DELETE t1 {i 2 t Ayutthaya} {}}
   110         -  {DELETE t1 {i 3 t Thonburi} {}}
          108  +  {DELETE t1 0 {i 1 t Sukhothai} {}}
          109  +  {DELETE t1 0 {i 2 t Ayutthaya} {}}
          110  +  {DELETE t1 0 {i 3 t Thonburi} {}}
   111    111   }
   112    112   do_changeset_invert_test 2.2.3 S {
   113         -  {INSERT t1 {} {i 1 t Sukhothai}}
   114         -  {INSERT t1 {} {i 2 t Ayutthaya}}
   115         -  {INSERT t1 {} {i 3 t Thonburi}}
          113  +  {INSERT t1 0 {} {i 1 t Sukhothai}}
          114  +  {INSERT t1 0 {} {i 2 t Ayutthaya}}
          115  +  {INSERT t1 0 {} {i 3 t Thonburi}}
   116    116   }
   117    117   do_test 2.2.4 { S delete } {}
   118    118   
   119    119   do_test 2.3.1 {
   120    120     execsql { DELETE FROM t1 }
   121    121     sqlite3session S db main
   122    122     execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
................................................................................
   127    127       UPDATE t1 SET x = 10 WHERE x = 1;
   128    128       UPDATE t1 SET y = 'Surin' WHERE x = 2;
   129    129       UPDATE t1 SET x = 20, y = 'Thapae' WHERE x = 3;
   130    130     }
   131    131   } {}
   132    132   
   133    133   do_changeset_test 2.3.2 S {
   134         -  {INSERT t1 {} {i 10 t Sukhothai}} 
   135         -  {DELETE t1 {i 1 t Sukhothai} {}} 
   136         -  {UPDATE t1 {i 2 t Ayutthaya} {{} {} t Surin}} 
   137         -  {DELETE t1 {i 3 t Thonburi} {}} 
   138         -  {INSERT t1 {} {i 20 t Thapae}} 
          134  +  {INSERT t1 0 {} {i 10 t Sukhothai}} 
          135  +  {DELETE t1 0 {i 1 t Sukhothai} {}} 
          136  +  {UPDATE t1 0 {i 2 t Ayutthaya} {{} {} t Surin}} 
          137  +  {DELETE t1 0 {i 3 t Thonburi} {}} 
          138  +  {INSERT t1 0 {} {i 20 t Thapae}} 
   139    139   }
   140    140   
   141    141   do_changeset_invert_test 2.3.3 S {
   142         -  {DELETE t1 {i 10 t Sukhothai} {}} 
   143         -  {INSERT t1 {} {i 1 t Sukhothai}} 
   144         -  {UPDATE t1 {{} {} t Surin} {i 2 t Ayutthaya}} 
   145         -  {INSERT t1 {} {i 3 t Thonburi}} 
   146         -  {DELETE t1 {i 20 t Thapae} {}}
          142  +  {DELETE t1 0 {i 10 t Sukhothai} {}} 
          143  +  {INSERT t1 0 {} {i 1 t Sukhothai}} 
          144  +  {UPDATE t1 0 {{} {} t Surin} {i 2 t Ayutthaya}} 
          145  +  {INSERT t1 0 {} {i 3 t Thonburi}} 
          146  +  {DELETE t1 0 {i 20 t Thapae} {}}
   147    147   }
   148    148   do_test 2.3.4 { S delete } {}
   149    149   
   150    150   do_test 2.4.1 {
   151    151     sqlite3session S db main
   152    152     S attach t1
   153    153     execsql { INSERT INTO t1 VALUES(100, 'Bangkok') }

Changes to ext/session/session2.test.

    37     37     CREATE TABLE t1(a PRIMARY KEY, b);
    38     38     INSERT INTO t1 VALUES('i', 'one');
    39     39   }
    40     40   do_iterator_test 1.1 t1 {
    41     41     DELETE FROM t1 WHERE a = 'i';
    42     42     INSERT INTO t1 VALUES('ii', 'two');
    43     43   } {
    44         -  {DELETE t1 {t i t one} {}} 
    45         -  {INSERT t1 {} {t ii t two}}
           44  +  {DELETE t1 0 {t i t one} {}} 
           45  +  {INSERT t1 0 {} {t ii t two}}
    46     46   }
    47     47   do_iterator_test 1.2 t1 {
    48     48     INSERT INTO t1 VALUES(1.5, 99.9)
    49     49   } {
    50         -  {INSERT t1 {} {f 1.5 f 99.9}}
           50  +  {INSERT t1 0 {} {f 1.5 f 99.9}}
    51     51   }
    52     52   
    53     53   
    54     54   # Execute each of the following blocks of SQL on database [db1]. Collect
    55     55   # changes using a session object. Apply the resulting changeset to
    56     56   # database [db2]. Then check that the contents of the two databases are
    57     57   # identical.
................................................................................
   224    224   
   225    225   foreach {tn sql changeset} {
   226    226     1 {
   227    227       INSERT INTO t1 VALUES(123);
   228    228       INSERT INTO t1 VALUES(NULL);
   229    229       INSERT INTO t1 VALUES(456);
   230    230     } {
   231         -    {INSERT t1 {} {i 456}} 
   232         -    {INSERT t1 {} {i 123}}
          231  +    {INSERT t1 0 {} {i 456}} 
          232  +    {INSERT t1 0 {} {i 123}}
   233    233     }
   234    234   
   235    235     2 {
   236    236       UPDATE t1 SET a = NULL;
   237    237     } {
   238         -    {DELETE t1 {i 456} {}}
   239         -    {DELETE t1 {i 123} {}}
          238  +    {DELETE t1 0 {i 456} {}}
          239  +    {DELETE t1 0 {i 123} {}}
   240    240     }
   241    241   
   242    242     3 { DELETE FROM t1 } { }
   243    243   
   244    244     4 { 
   245    245       INSERT INTO t3 VALUES(NULL, NULL)
   246    246     } {
   247         -    {INSERT t3 {} {n {} i 1}} 
          247  +    {INSERT t3 0 {} {n {} i 1}} 
   248    248     }
   249    249   
   250    250     5 { INSERT INTO t2 VALUES(1, 2, NULL) }    { }
   251    251     6 { INSERT INTO t2 VALUES(1, NULL, 3) }    { }
   252    252     7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { }
   253         -  8 { INSERT INTO t2 VALUES(1, 2, 3) }       { {INSERT t2 {} {i 1 i 2 i 3}} }
   254         -  9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 {i 1 i 2 i 3} {}} }
          253  +  8 { INSERT INTO t2 VALUES(1, 2, 3) }       { {INSERT t2 0 {} {i 1 i 2 i 3}} }
          254  +  9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 0 {i 1 i 2 i 3} {}} }
   255    255   
   256    256   } {
   257    257     do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
   258    258   }
   259    259   
   260    260   
   261    261   #-------------------------------------------------------------------------
................................................................................
   265    265   test_reset
   266    266   do_execsql_test 5.0 {
   267    267     CREATE TABLE t1(a PRIMARY KEY);
   268    268     CREATE TABLE t2(x, y PRIMARY KEY);
   269    269   }
   270    270   
   271    271   foreach {tn sql changeset} {
   272         -  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 {} {i 35}} }
   273         -  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 {} {i 36 i 37}} }
          272  +  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 0 {} {i 35}} }
          273  +  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 {} {i 36 i 37}} }
   274    274     3 { 
   275    275       DELETE FROM t1 WHERE 1;
   276    276       UPDATE t2 SET x = 34;
   277    277     } { 
   278         -    {UPDATE t2 {i 36 i 37} {i 34 {} {}}}
   279         -    {DELETE t1 {i 35} {}}
          278  +    {UPDATE t2 0 {i 36 i 37} {i 34 {} {}}}
          279  +    {DELETE t1 0 {i 35} {}}
   280    280     }
   281    281   } {
   282    282     do_iterator_test 5.$tn * $sql $changeset
   283    283   }
   284    284   
   285         -finish_test
          285  +#-------------------------------------------------------------------------
          286  +# The next block of tests verify that the "indirect" flag is set 
          287  +# correctly within changesets. The indirect flag is set for a change
          288  +# if either of the following are true:
          289  +#
          290  +#   * The sqlite3session_indirect() API has been used to set the session
          291  +#     indirect flag to true, or
          292  +#   * The change was made by a trigger.
          293  +#
          294  +# If the same row is updated more than once during a session, then the 
          295  +# change is considered indirect only if all changes meet the criteria 
          296  +# above.
          297  +#
          298  +test_reset
          299  +db function indirect [list S indirect]
          300  +
          301  +do_execsql_test 6.0 {
          302  +  CREATE TABLE t1(a PRIMARY KEY, b, c);
          303  +
          304  +  CREATE TABLE t2(x PRIMARY KEY, y);
          305  +  CREATE TRIGGER AFTER INSERT ON t2 WHEN new.x%2 BEGIN
          306  +    INSERT INTO t2 VALUES(new.x+1, NULL);
          307  +  END;
          308  +}
          309  +
          310  +do_iterator_test 6.1.1 * {
          311  +  INSERT INTO t1 VALUES(1, 'one', 'i');
          312  +  SELECT indirect(1);
          313  +  INSERT INTO t1 VALUES(2, 'two', 'ii');
          314  +  SELECT indirect(0);
          315  +  INSERT INTO t1 VALUES(3, 'three', 'iii');
          316  +} {
          317  +  {INSERT t1 0 {} {i 1 t one t i}}
          318  +  {INSERT t1 1 {} {i 2 t two t ii}}
          319  +  {INSERT t1 0 {} {i 3 t three t iii}}
          320  +}
          321  +
          322  +do_iterator_test 6.1.2 * {
          323  +  SELECT indirect(1);
          324  +  UPDATE t1 SET c = 'I' WHERE a = 1;
          325  +  SELECT indirect(0);
          326  +} {
          327  +  {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}}
          328  +}
          329  +do_iterator_test 6.1.3 * {
          330  +  SELECT indirect(1);
          331  +  UPDATE t1 SET c = '.' WHERE a = 1;
          332  +  SELECT indirect(0);
          333  +  UPDATE t1 SET c = 'o' WHERE a = 1;
          334  +} {
          335  +  {UPDATE t1 0 {i 1 {} {} t I} {{} {} {} {} t o}}
          336  +}
          337  +do_iterator_test 6.1.4 * {
          338  +  SELECT indirect(0);
          339  +  UPDATE t1 SET c = 'x' WHERE a = 1;
          340  +  SELECT indirect(1);
          341  +  UPDATE t1 SET c = 'i' WHERE a = 1;
          342  +} {
          343  +  {UPDATE t1 0 {i 1 {} {} t o} {{} {} {} {} t i}}
          344  +}
          345  +do_iterator_test 6.1.4 * {
          346  +  SELECT indirect(1);
          347  +  UPDATE t1 SET c = 'y' WHERE a = 1;
          348  +  SELECT indirect(1);
          349  +  UPDATE t1 SET c = 'I' WHERE a = 1;
          350  +} {
          351  +  {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}}
          352  +}
          353  +
          354  +do_iterator_test 6.1.5 * {
          355  +  INSERT INTO t2 VALUES(1, 'x');
          356  +} {
          357  +  {INSERT t2 0 {} {i 1 t x}}
          358  +  {INSERT t2 1 {} {i 2 n {}}}
          359  +}
          360  +
          361  +do_iterator_test 6.1.6 * {
          362  +  SELECT indirect(1);
          363  +  INSERT INTO t2 VALUES(3, 'x');
          364  +  SELECT indirect(0);
          365  +  UPDATE t2 SET y = 'y' WHERE x>2;
          366  +} {
          367  +  {INSERT t2 0 {} {i 3 t y}}
          368  +  {INSERT t2 0 {} {i 4 t y}}
          369  +}
          370  +
          371  +do_iterator_test 6.1.7 * {
          372  +  SELECT indirect(1);
          373  +  DELETE FROM t2 WHERE x = 4;
          374  +  SELECT indirect(0);
          375  +  INSERT INTO t2 VALUES(4, 'new');
          376  +} {
          377  +  {UPDATE t2 0 {i 4 t y} {{} {} t new}}
          378  +}
   286    379   
          380  +finish_test

Changes to ext/session/sessionfault.test.

    34     34   #-------------------------------------------------------------------------
    35     35   # Test OOM error handling when collecting and applying a simple changeset.
    36     36   #
    37     37   # Test 1.1 attaches tables individually by name to the session object. 
    38     38   # Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all
    39     39   # tables.
    40     40   #
    41         -do_faultsim_test pagerfault-1.1 -faults oom-* -prep {
           41  +do_faultsim_test 1.1 -faults oom-* -prep {
    42     42     catch {db2 close}
    43     43     catch {db close}
    44     44     faultsim_restore_and_reopen
    45     45     sqlite3 db2 test.db2
    46     46   } -body {
    47     47     do_then_apply_sql {
    48     48       INSERT INTO t1 VALUES(7, 8, 9);
................................................................................
    51     51     }
    52     52   } -test {
    53     53     faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
    54     54     faultsim_integrity_check
    55     55     if {$testrc==0} { compare_db db db2 }
    56     56   }
    57     57   
    58         -do_faultsim_test pagerfault-1.2 -faults oom-* -prep {
           58  +do_faultsim_test 1.2 -faults oom-* -prep {
    59     59     catch {db2 close}
    60     60     catch {db close}
    61     61     faultsim_restore_and_reopen
    62     62   } -body {
    63     63     sqlite3session S db main
    64     64     S attach *
    65     65     execsql {
................................................................................
    78     78       sqlite3 db2 test.db2
    79     79       sqlite3changeset_apply db2 $::changeset xConflict
    80     80       compare_db db db2 
    81     81     }
    82     82   }
    83     83   
    84     84   #-------------------------------------------------------------------------
    85         -# The following block of tests - pagerfault-2.* - are designed to check 
           85  +# The following block of tests - 2.* - are designed to check 
    86     86   # the handling of faults in the sqlite3changeset_apply() function.
    87     87   #
    88     88   catch {db close}
    89     89   catch {db2 close}
    90     90   forcedelete test.db2 test.db
    91     91   sqlite3 db2 test.db2
    92     92   sqlite3 db test.db
................................................................................
   106    106     3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' }            {}
   107    107     4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } {
   108    108       INSERT INTO t2 VALUES('keyvalue', 'value 2');
   109    109     }
   110    110   } {
   111    111     proc xConflict args [list return $conflict_policy]
   112    112   
   113         -  do_faultsim_test pagerfault-2.$tn -faults oom-transient -prep {
          113  +  do_faultsim_test 2.$tn -faults oom-transient -prep {
   114    114       catch {db2 close}
   115    115       catch {db close}
   116    116       faultsim_restore_and_reopen
   117    117       set ::changeset [changeset_from_sql $::sql]
   118    118       sqlite3 db2 test.db2
   119    119       sqlite3_db_config_lookaside db2 0 0 0
   120    120       execsql $::sql2 db2
................................................................................
   128    128   }
   129    129   
   130    130   #-------------------------------------------------------------------------
   131    131   # This test case is designed so that a malloc() failure occurs while
   132    132   # resizing the session object hash-table from 256 to 512 buckets. This
   133    133   # is not an error, just a sub-optimal condition.
   134    134   #
   135         -do_faultsim_test pagerfault-3 -faults oom-* -prep {
          135  +do_faultsim_test 3 -faults oom-* -prep {
   136    136     catch {db2 close}
   137    137     catch {db close}
   138    138     faultsim_restore_and_reopen
   139    139     sqlite3 db2 test.db2
   140    140   
   141    141     sqlite3session S db main
   142    142     S attach t1
................................................................................
   187    187       INSERT INTO t1 VALUES(4, 16);
   188    188     } db2
   189    189   } {}
   190    190   
   191    191   faultsim_save_and_close
   192    192   db2 close
   193    193   
   194         -do_faultsim_test pagerfault-4 -faults oom-* -prep {
          194  +do_faultsim_test 4 -faults oom-* -prep {
   195    195     catch {db2 close}
   196    196     catch {db close}
   197    197     faultsim_restore_and_reopen
   198    198     sqlite3 db2 test.db2
   199    199     sqlite3session S db main
   200    200     S attach t1
   201    201     execsql {
................................................................................
   230    230   set changeset [changeset_from_sql {
   231    231     INSERT INTO t1 VALUES('xxx', 'yyy');
   232    232     DELETE FROM t1 WHERE a = 'string';
   233    233     UPDATE t1 SET a = 20 WHERE b = 2;
   234    234   }]
   235    235   db close
   236    236   
   237         -do_faultsim_test pagerfault-5 -faults oom* -body {
          237  +do_faultsim_test 5 -faults oom* -body {
   238    238     set ::inverse [sqlite3changeset_invert $::changeset]
   239    239     set {} {}
   240    240   } -test {
   241    241     faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
   242    242     if {$testrc==0} {
   243    243       set x [list]
   244    244       sqlite3session_foreach c $::inverse { lappend x $c }
   245    245       foreach c {
   246         -        {DELETE t1 {t xxx t yyy} {}} 
   247         -        {INSERT t1 {} {t string i 1}} 
   248         -        {UPDATE t1 {i 20 {} {}} {i 4 i 2}}
          246  +        {DELETE t1 0 {t xxx t yyy} {}} 
          247  +        {INSERT t1 0 {} {t string i 1}} 
          248  +        {UPDATE t1 0 {i 20 {} {}} {i 4 i 2}}
   249    249       } { lappend y $c }
   250    250       if {$x != $y} { error "changeset no good" }
   251    251     }
   252    252   }
   253    253   
   254    254   finish_test

Changes to ext/session/sqlite3session.c.

    15     15   /*
    16     16   ** Session handle structure.
    17     17   */
    18     18   struct sqlite3_session {
    19     19     sqlite3 *db;                    /* Database handle session is attached to */
    20     20     char *zDb;                      /* Name of database session is attached to */
    21     21     int bEnable;                    /* True if currently recording */
           22  +  int bIndirect;                  /* True if all changes are indirect */
    22     23     int bAutoAttach;                /* True to auto-attach tables */
    23     24     int rc;                         /* Non-zero if an error has occurred */
    24     25     sqlite3_session *pNext;         /* Next session object on same db. */
    25     26     SessionTable *pTable;           /* List of attached tables */
    26     27   };
    27     28   
    28     29   /*
................................................................................
    33     34     int nChangeset;                 /* Number of bytes in aChangeset */
    34     35     u8 *pNext;                      /* Pointer to next change within aChangeset */
    35     36     int rc;                         /* Iterator error code */
    36     37     sqlite3_stmt *pConflict;        /* Points to conflicting row, if any */
    37     38     char *zTab;                     /* Current table */
    38     39     int nCol;                       /* Number of columns in zTab */
    39     40     int op;                         /* Current operation */
           41  +  int bIndirect;                  /* True if current change was indirect */
    40     42     sqlite3_value **apValue;        /* old.* and new.* values */
    41     43   };
    42     44   
    43     45   /*
    44     46   ** Each session object maintains a set of the following structures, one
    45     47   ** for each table the session object is monitoring. The structures are
    46     48   ** stored in a linked list starting at sqlite3_session.pTable.
................................................................................
   127    129   
   128    130   /*
   129    131   ** For each row modified during a session, there exists a single instance of
   130    132   ** this structure stored in a SessionTable.aChange[] hash table.
   131    133   */
   132    134   struct SessionChange {
   133    135     int bInsert;                    /* True if row was inserted this session */
          136  +  int bIndirect;                  /* True if this change is "indirect" */
   134    137     int nRecord;                    /* Number of bytes in buffer aRecord[] */
   135    138     u8 *aRecord;                    /* Buffer containing old.* record */
   136    139     SessionChange *pNext;           /* For hash-table collisions */
   137    140   };
   138    141   
   139    142   /*
   140    143   ** Instances of this structure are used to build strings or binary records.
................................................................................
   656    659   
   657    660   static void sessionPreupdateOneChange(
   658    661     int op,
   659    662     sqlite3_session *pSession,
   660    663     SessionTable *pTab
   661    664   ){
   662    665     sqlite3 *db = pSession->db;
   663         -  SessionChange *pChange;
   664         -  SessionChange *pC;
   665    666     int iHash; 
   666    667     int bNullPk = 0; 
   667    668     int rc = SQLITE_OK;
   668    669   
   669    670     if( pSession->rc ) return;
   670    671   
   671    672     /* Load table details if required */
................................................................................
   675    676     if( sessionGrowHash(pSession, pTab) ) return;
   676    677   
   677    678     /* Search the hash table for an existing entry for rowid=iKey2. If
   678    679     ** one is found, store a pointer to it in pChange and unlink it from
   679    680     ** the hash table. Otherwise, set pChange to NULL.
   680    681     */
   681    682     rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk);
   682         -  if( bNullPk==0 ){
          683  +  if( rc==SQLITE_OK && bNullPk==0 ){
          684  +    SessionChange *pC;
   683    685       for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){
   684    686         int bEqual;
   685    687         rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual);
   686    688         if( bEqual ) break;
   687    689       }
   688    690       if( pC==0 ){
   689    691         /* Create a new change object containing all the old values (if
   690    692          ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
   691    693          ** values (if this is an INSERT). */
          694  +      SessionChange *pChange; /* New change object */
   692    695         int nByte;              /* Number of bytes to allocate */
   693    696         int i;                  /* Used to iterate through columns */
   694    697     
          698  +      assert( rc==SQLITE_OK );
   695    699         pTab->nEntry++;
   696    700     
   697    701         /* Figure out how large an allocation is required */
   698    702         nByte = sizeof(SessionChange);
   699    703         for(i=0; i<pTab->nCol && rc==SQLITE_OK; i++){
   700    704           sqlite3_value *p = 0;
   701    705           if( op!=SQLITE_INSERT ){
................................................................................
   728    732           }
   729    733           if( p && rc==SQLITE_OK ){
   730    734             rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
   731    735           }
   732    736         }
   733    737         if( rc==SQLITE_OK ){
   734    738           /* Add the change back to the hash-table */
          739  +        if( pSession->bIndirect || sqlite3_preupdate_depth(pSession->db) ){
          740  +          pChange->bIndirect = 1;
          741  +        }
   735    742           pChange->nRecord = nByte;
   736    743           pChange->bInsert = (op==SQLITE_INSERT);
   737    744           pChange->pNext = pTab->apChange[iHash];
   738    745           pTab->apChange[iHash] = pChange;
   739    746         }else{
   740    747           sqlite3_free(pChange);
          748  +      }
          749  +    }else if( rc==SQLITE_OK && pC->bIndirect ){
          750  +      /* If the existing change is considered "indirect", but this current
          751  +      ** change is "direct", mark the change object as direct. */
          752  +      if( sqlite3_preupdate_depth(pSession->db)==0 && pSession->bIndirect==0 ){
          753  +        pC->bIndirect = 0;
   741    754         }
   742    755       }
   743    756     }
   744    757   
   745    758     /* If an error has occurred, mark the session object as failed. */
   746    759     if( rc!=SQLITE_OK ){
   747    760       pSession->rc = rc;
................................................................................
  1132   1145       SessionBuffer buf2 = {0,0,0}; /* Buffer to accumulate new.* record in */
  1133   1146       int bNoop = 1;                /* Set to zero if any values are modified */
  1134   1147       int nRewind = pBuf->nBuf;     /* Set to zero if any values are modified */
  1135   1148       int i;                        /* Used to iterate through columns */
  1136   1149       u8 *pCsr = p->aRecord;        /* Used to iterate through old.* values */
  1137   1150   
  1138   1151       sessionAppendByte(pBuf, SQLITE_UPDATE, pRc);
         1152  +    sessionAppendByte(pBuf, p->bIndirect, pRc);
  1139   1153       for(i=0; i<sqlite3_column_count(pStmt); i++){
  1140   1154         int bChanged = 0;
  1141   1155         int nAdvance;
  1142   1156         int eType = *pCsr;
  1143   1157         switch( eType ){
  1144   1158           case SQLITE_NULL:
  1145   1159             nAdvance = 1;
................................................................................
  1362   1376           for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
  1363   1377             rc = sessionSelectBind(pSel, nCol, abPK, p->aRecord, p->nRecord);
  1364   1378             if( rc==SQLITE_OK ){
  1365   1379               if( sqlite3_step(pSel)==SQLITE_ROW ){
  1366   1380                 int iCol;
  1367   1381                 if( p->bInsert ){
  1368   1382                   sessionAppendByte(&buf, SQLITE_INSERT, &rc);
         1383  +                sessionAppendByte(&buf, p->bIndirect, &rc);
  1369   1384                   for(iCol=0; iCol<nCol; iCol++){
  1370   1385                     sessionAppendCol(&buf, pSel, iCol, &rc);
  1371   1386                   }
  1372   1387                 }else{
  1373   1388                   sessionAppendUpdate(&buf, pSel, p, abPK, &rc);
  1374   1389                 }
  1375   1390               }else if( !p->bInsert ){
  1376   1391                 /* A DELETE change */
  1377   1392                 sessionAppendByte(&buf, SQLITE_DELETE, &rc);
         1393  +              sessionAppendByte(&buf, p->bIndirect, &rc);
  1378   1394                 sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
  1379   1395               }
  1380   1396               if( rc==SQLITE_OK ){
  1381   1397                 rc = sqlite3_reset(pSel);
  1382   1398               }
  1383   1399             }
  1384   1400           }
................................................................................
  1411   1427     if( bEnable>=0 ){
  1412   1428       pSession->bEnable = bEnable;
  1413   1429     }
  1414   1430     ret = pSession->bEnable;
  1415   1431     sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
  1416   1432     return ret;
  1417   1433   }
         1434  +
         1435  +/*
         1436  +** Enable or disable the session object passed as the first argument.
         1437  +*/
         1438  +int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){
         1439  +  int ret;
         1440  +  sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
         1441  +  if( bIndirect>=0 ){
         1442  +    pSession->bIndirect = bIndirect;
         1443  +  }
         1444  +  ret = pSession->bIndirect;
         1445  +  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
         1446  +  return ret;
         1447  +}
  1418   1448   
  1419   1449   /*
  1420   1450   ** Create an iterator used to iterate through the contents of a changeset.
  1421   1451   */
  1422   1452   int sqlite3changeset_start(
  1423   1453     sqlite3_changeset_iter **pp,    /* OUT: Changeset iterator handle */
  1424   1454     int nChangeset,                 /* Size of buffer pChangeset in bytes */
................................................................................
  1544   1574     c = *(aChange++);
  1545   1575     if( c=='T' ){
  1546   1576       int nByte;                    /* Bytes to allocate for apValue */
  1547   1577       aChange += sessionVarintGet(aChange, &p->nCol);
  1548   1578       p->zTab = (char *)aChange;
  1549   1579       aChange += (strlen((char *)aChange) + 1);
  1550   1580       p->op = *(aChange++);
         1581  +    p->bIndirect = *(aChange++);
  1551   1582       sqlite3_free(p->apValue);
  1552   1583       nByte = sizeof(sqlite3_value *) * p->nCol * 2;
  1553   1584       p->apValue = (sqlite3_value **)sqlite3_malloc(nByte);
  1554   1585       if( !p->apValue ){
  1555   1586         return (p->rc = SQLITE_NOMEM);
  1556   1587       }
  1557   1588       memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
  1558   1589     }else{
  1559   1590       p->op = c;
         1591  +    p->bIndirect = *(aChange++);
  1560   1592     }
  1561   1593     if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
  1562   1594       return (p->rc = SQLITE_CORRUPT);
  1563   1595     }
  1564   1596   
  1565   1597     /* If this is an UPDATE or DELETE, read the old.* record. */
  1566   1598     if( p->op!=SQLITE_INSERT ){
................................................................................
  1583   1615   ** from a changeset iterator. They may only be called after changeset_next()
  1584   1616   ** has returned SQLITE_ROW.
  1585   1617   */
  1586   1618   int sqlite3changeset_op(
  1587   1619     sqlite3_changeset_iter *pIter,  /* Iterator handle */
  1588   1620     const char **pzTab,             /* OUT: Pointer to table name */
  1589   1621     int *pnCol,                     /* OUT: Number of columns in table */
  1590         -  int *pOp                        /* OUT: SQLITE_INSERT, DELETE or UPDATE */
         1622  +  int *pOp,                       /* OUT: SQLITE_INSERT, DELETE or UPDATE */
         1623  +  int *pbIndirect                 /* OUT: True if change is indirect */
  1591   1624   ){
  1592   1625     *pOp = pIter->op;
  1593   1626     *pnCol = pIter->nCol;
  1594   1627     *pzTab = pIter->zTab;
         1628  +  if( pbIndirect ) *pbIndirect = pIter->bIndirect;
  1595   1629     return SQLITE_OK;
  1596   1630   }
  1597   1631   
  1598   1632   /*
  1599   1633   ** This function may only be called while the iterator is pointing to an
  1600   1634   ** SQLITE_UPDATE or SQLITE_DELETE change (see sqlite3changeset_op()).
  1601   1635   ** Otherwise, SQLITE_MISUSE is returned.
................................................................................
  1736   1770           i += nByte;
  1737   1771           break;
  1738   1772         }
  1739   1773   
  1740   1774         case SQLITE_INSERT:
  1741   1775         case SQLITE_DELETE: {
  1742   1776           int nByte;
  1743         -        u8 *aEnd = &aIn[i+1];
         1777  +        u8 *aEnd = &aIn[i+2];
  1744   1778   
  1745   1779           sessionReadRecord(&aEnd, nCol, 0);
  1746   1780           aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE);
  1747         -        nByte = aEnd - &aIn[i+1];
  1748         -        memcpy(&aOut[i+1], &aIn[i+1], nByte);
  1749         -        i += 1 + nByte;
         1781  +        aOut[i+1] = aIn[i+1];
         1782  +        nByte = aEnd - &aIn[i+2];
         1783  +        memcpy(&aOut[i+2], &aIn[i+2], nByte);
         1784  +        i += 2 + nByte;
  1750   1785           break;
  1751   1786         }
  1752   1787   
  1753   1788         case SQLITE_UPDATE: {
  1754   1789           int nByte1;              /* Size of old.* record in bytes */
  1755   1790           int nByte2;              /* Size of new.* record in bytes */
  1756         -        u8 *aEnd = &aIn[i+1];    
         1791  +        u8 *aEnd = &aIn[i+2];    
  1757   1792   
  1758   1793           sessionReadRecord(&aEnd, nCol, 0);
  1759         -        nByte1 = aEnd - &aIn[i+1];
         1794  +        nByte1 = aEnd - &aIn[i+2];
  1760   1795           sessionReadRecord(&aEnd, nCol, 0);
  1761         -        nByte2 = aEnd - &aIn[i+1] - nByte1;
         1796  +        nByte2 = aEnd - &aIn[i+2] - nByte1;
  1762   1797   
  1763   1798           aOut[i] = SQLITE_UPDATE;
  1764         -        memcpy(&aOut[i+1], &aIn[i+1+nByte1], nByte2);
  1765         -        memcpy(&aOut[i+1+nByte2], &aIn[i+1], nByte1);
         1799  +        aOut[i+1] = aIn[i+1];
         1800  +        memcpy(&aOut[i+2], &aIn[i+2+nByte1], nByte2);
         1801  +        memcpy(&aOut[i+2+nByte2], &aIn[i+2], nByte1);
  1766   1802   
  1767         -        i += 1 + nByte1 + nByte2;
         1803  +        i += 2 + nByte1 + nByte2;
  1768   1804           break;
  1769   1805         }
  1770   1806   
  1771   1807         default:
  1772   1808           sqlite3_free(aOut);
  1773   1809           return SQLITE_CORRUPT;
  1774   1810       }
................................................................................
  2093   2129     sqlite3_stmt *pSelect           /* SELECT statement from sessionSelectRow() */
  2094   2130   ){
  2095   2131     int rc;                         /* Return code */
  2096   2132     int nCol;                       /* Number of columns in table */
  2097   2133     int op;                         /* Changset operation (SQLITE_UPDATE etc.) */
  2098   2134     const char *zDummy;             /* Unused */
  2099   2135   
  2100         -  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
         2136  +  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
  2101   2137     rc = sessionBindRow(pIter, 
  2102   2138         op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old,
  2103   2139         nCol, abPK, pSelect
  2104   2140     );
  2105   2141   
  2106   2142     if( rc==SQLITE_OK ){
  2107   2143       rc = sqlite3_step(pSelect);
................................................................................
  2156   2192   ){
  2157   2193     int res;                        /* Value returned by conflict handler */
  2158   2194     int rc;
  2159   2195     int nCol;
  2160   2196     int op;
  2161   2197     const char *zDummy;
  2162   2198   
  2163         -  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
         2199  +  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
  2164   2200   
  2165   2201     assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA );
  2166   2202     assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
  2167   2203     assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND );
  2168   2204   
  2169   2205     /* Bind the new.* PRIMARY KEY values to the SELECT statement. */
  2170   2206     if( pbReplace ){
................................................................................
  2244   2280     int nCol;
  2245   2281     int rc = SQLITE_OK;
  2246   2282   
  2247   2283     assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
  2248   2284     assert( p->azCol && p->abPK );
  2249   2285     assert( !pbReplace || *pbReplace==0 );
  2250   2286   
  2251         -  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
         2287  +  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
  2252   2288   
  2253   2289     if( op==SQLITE_DELETE ){
  2254   2290   
  2255   2291       /* Bind values to the DELETE statement. */
  2256   2292       rc = sessionBindRow(pIter, sqlite3changeset_old, nCol, 0, p->pDelete);
  2257   2293       if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){
  2258   2294         rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0);
................................................................................
  2358   2394     rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
  2359   2395     while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
  2360   2396       int nCol;
  2361   2397       int op;
  2362   2398       int bReplace = 0;
  2363   2399       int bRetry = 0;
  2364   2400       const char *zNew;
  2365         -    sqlite3changeset_op(pIter, &zNew, &nCol, &op);
         2401  +    sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0);
  2366   2402   
  2367   2403       if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
  2368   2404         sqlite3_free(sApply.azCol);
  2369   2405         sqlite3_finalize(sApply.pDelete);
  2370   2406         sqlite3_finalize(sApply.pUpdate); 
  2371   2407         sqlite3_finalize(sApply.pInsert);
  2372   2408         sqlite3_finalize(sApply.pSelect);

Changes to ext/session/sqlite3session.h.

    86     86   ** no-op, and may be used to query the current state of the session.
    87     87   **
    88     88   ** The return value indicates the final state of the session object: 0 if 
    89     89   ** the session is disabled, or 1 if it is enabled.
    90     90   */
    91     91   int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
    92     92   
           93  +/*
           94  +** CAPI3REF: Set Or Clear the Indirect Change Flag
           95  +**
           96  +** Each change recorded by a session object is marked as either direct or
           97  +** indirect. A change is marked as indirect if either:
           98  +**
           99  +** <ul>
          100  +**   <li> The session object "indirect" flag is set when the change is
          101  +**        made, or
          102  +**   <li> The change is made by an SQL trigger or foreign key action 
          103  +**        instead of directly as a result of a users SQL statement.
          104  +** </ul>
          105  +**
          106  +** If a single row is affected by more than one operation within a session,
          107  +** then the change is considered indirect if all operations meet the criteria
          108  +** for an indirect change above, or direct otherwise.
          109  +**
          110  +** This function is used to set, clear or query the session object indirect
          111  +** flag.  If the second argument passed to this function is zero, then the
          112  +** indirect flag is cleared. If it is greater than zero, the indirect flag
          113  +** is set. Passing a value less than zero does not modify the current value
          114  +** of the indirect flag, and may be used to query the current state of the 
          115  +** indirect flag for the specified session object.
          116  +**
          117  +** The return value indicates the final state of the indirect flag: 0 if 
          118  +** it is clear, or 1 if it is set.
          119  +*/
          120  +int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
          121  +
    93    122   /*
    94    123   ** CAPI3REF: Attach A Table To A Session Object
    95    124   **
    96    125   ** If argument zTab is not NULL, then it is the name of a table to attach
    97    126   ** to the session object passed as the first argument. All subsequent changes 
    98    127   ** made to the table while the session object is enabled will be recorded. See 
    99    128   ** documentation for [sqlite3session_changeset()] for further details.
................................................................................
   288    317   ** is not the case, this function returns [SQLITE_MISUSE].
   289    318   **
   290    319   ** If argument pzTab is not NULL, then *pzTab is set to point to a
   291    320   ** nul-terminated utf-8 encoded string containing the name of the table
   292    321   ** affected by the current change. The buffer remains valid until either
   293    322   ** sqlite3changeset_next() is called on the iterator or until the 
   294    323   ** conflict-handler function returns. If pnCol is not NULL, then *pnCol is 
   295         -** set to the number of columns in the table affected by the change. Finally,
   296         -** if pOp is not NULL, then *pOp is set to one of [SQLITE_INSERT], 
   297         -** [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the type of change that 
   298         -** the iterator currently points to.
          324  +** set to the number of columns in the table affected by the change. If
          325  +** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change
          326  +** is an indirect change, or false (0) otherwise. See the documentation for
          327  +** [sqlite3session_indirect()] for a description of direct and indirect
          328  +** changes. Finally, if pOp is not NULL, then *pOp is set to one of 
          329  +** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the 
          330  +** type of change that the iterator currently points to.
   299    331   **
   300    332   ** If no error occurs, SQLITE_OK is returned. If an error does occur, an
   301    333   ** SQLite error code is returned. The values of the output variables may not
   302    334   ** be trusted in this case.
   303    335   */
   304    336   int sqlite3changeset_op(
   305    337     sqlite3_changeset_iter *pIter,  /* Iterator object */
   306    338     const char **pzTab,             /* OUT: Pointer to table name */
   307    339     int *pnCol,                     /* OUT: Number of columns in table */
   308         -  int *pOp                        /* OUT: SQLITE_INSERT, DELETE or UPDATE */
          340  +  int *pOp,                       /* OUT: SQLITE_INSERT, DELETE or UPDATE */
          341  +  int *pbIndirect                 /* OUT: True for an 'indirect' change */
   309    342   );
   310    343   
   311    344   /*
   312    345   ** CAPI3REF: Obtain old.* Values From A Changeset Iterator
   313    346   **
   314    347   ** The pIter argument passed to this function may either be an iterator
   315    348   ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator

Changes to ext/session/test_session.c.

    13     13   }
    14     14   
    15     15   /*
    16     16   ** Tclcmd:  $session attach TABLE
    17     17   **          $session changeset
    18     18   **          $session delete
    19     19   **          $session enable BOOL
           20  +**          $session indirect BOOL
    20     21   */
    21     22   static int test_session_cmd(
    22     23     void *clientData,
    23     24     Tcl_Interp *interp,
    24     25     int objc,
    25     26     Tcl_Obj *CONST objv[]
    26     27   ){
................................................................................
    30     31       int nArg;
    31     32       const char *zMsg;
    32     33       int iSub;
    33     34     } aSub[] = {
    34     35       { "attach",    1, "TABLE", }, /* 0 */
    35     36       { "changeset", 0, "",      }, /* 1 */
    36     37       { "delete",    0, "",      }, /* 2 */
    37         -    { "enable",    1, "",      }, /* 3 */
           38  +    { "enable",    1, "BOOL",  }, /* 3 */
           39  +    { "indirect",  1, "BOOL",  }, /* 4 */
    38     40       { 0 }
    39     41     };
    40     42     int iSub;
    41     43     int rc;
    42     44   
    43     45     if( objc<2 ){
    44     46       Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
................................................................................
    84     86       case 3: {      /* enable */
    85     87         int val;
    86     88         if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
    87     89         val = sqlite3session_enable(pSession, val);
    88     90         Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
    89     91         break;
    90     92       }
           93  +
           94  +    case 4: {      /* indirect */
           95  +      int val;
           96  +      if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
           97  +      val = sqlite3session_indirect(pSession, val);
           98  +      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
           99  +      break;
          100  +    }
    91    101     }
    92    102   
    93    103     return TCL_OK;
    94    104   }
    95    105   
    96    106   static void test_session_del(void *clientData){
    97    107     sqlite3_session *pSession = (sqlite3_session *)clientData;
................................................................................
   201    211     int op;                         /* SQLITE_UPDATE, DELETE or INSERT */
   202    212     const char *zTab;               /* Name of table conflict is on */
   203    213     int nCol;                       /* Number of columns in table zTab */
   204    214   
   205    215     pEval = Tcl_DuplicateObj(p->pScript);
   206    216     Tcl_IncrRefCount(pEval);
   207    217   
   208         -  sqlite3changeset_op(pIter, &zTab, &nCol, &op);
          218  +  sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
   209    219   
   210    220     /* Append the operation type. */
   211    221     Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
   212    222         op==SQLITE_INSERT ? "INSERT" :
   213    223         op==SQLITE_UPDATE ? "UPDATE" : 
   214    224         "DELETE", -1
   215    225     ));
................................................................................
   392    402     while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
   393    403       int nCol;                     /* Number of columns in table */
   394    404       int op;                       /* SQLITE_INSERT, UPDATE or DELETE */
   395    405       const char *zTab;             /* Name of table change applies to */
   396    406       Tcl_Obj *pVar;                /* Tcl value to set $VARNAME to */
   397    407       Tcl_Obj *pOld;                /* Vector of old.* values */
   398    408       Tcl_Obj *pNew;                /* Vector of new.* values */
          409  +    int bIndirect;
   399    410   
   400         -    sqlite3changeset_op(pIter, &zTab, &nCol, &op);
          411  +    sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
   401    412       pVar = Tcl_NewObj();
   402    413       Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
   403    414             op==SQLITE_INSERT ? "INSERT" :
   404    415             op==SQLITE_UPDATE ? "UPDATE" : 
   405    416             "DELETE", -1
   406    417       ));
   407    418       Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
          419  +    Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
   408    420   
   409    421       pOld = Tcl_NewObj();
   410    422       if( op!=SQLITE_INSERT ){
   411    423         int i;
   412    424         for(i=0; i<nCol; i++){
   413    425           sqlite3_value *pVal;
   414    426           sqlite3changeset_old(pIter, i, &pVal);