/ Check-in [e9037e4e]
Login

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

Overview
Comment:If a NULL pointer is passed to sqlite3session_attach() in place of a table name, attach all database tables to the session object.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: e9037e4e4ccaa5c633759c4d041b60b631b92c6c
User & Date: dan 2011-03-22 15:21:04
Context
2011-03-22
16:54
Fix a crash that can follow an OOM when "all tables" are registered with a session module. check-in: 183c236e user: dan tags: sessions
15:21
If a NULL pointer is passed to sqlite3session_attach() in place of a table name, attach all database tables to the session object. check-in: e9037e4e user: dan tags: sessions
12:08
Add OOM tests and related fixes for the session module. check-in: 06048a68 user: dan tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/session2.test.

253
254
255
256
257
258
259
260
























261
262
  8 { INSERT INTO t2 VALUES(1, 2, 3) }       { {INSERT t2 {} {i 1 i 2 i 3}} }
  9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 {i 1 i 2 i 3} {}} }

} {
  do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
}


























finish_test









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


253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
  8 { INSERT INTO t2 VALUES(1, 2, 3) }       { {INSERT t2 {} {i 1 i 2 i 3}} }
  9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 {i 1 i 2 i 3} {}} }

} {
  do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
}


#-------------------------------------------------------------------------
# Test that if NULL is passed to sqlite3session_attach(), all database
# tables are attached to the session object.
#
test_reset
do_execsql_test 5.0 {
  CREATE TABLE t1(a PRIMARY KEY);
  CREATE TABLE t2(x, y PRIMARY KEY);
}

foreach {tn sql changeset} {
  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 {} {i 35}} }
  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 {} {i 36 i 37}} }
  3 { 
    DELETE FROM t1 WHERE 1;
    UPDATE t2 SET x = 34;
  } { 
    {UPDATE t2 {i 36 i 37} {i 34 {} {}}}
    {DELETE t1 {i 35} {}}
  }
} {
  do_iterator_test 5.$tn * $sql $changeset
}

finish_test

Changes to ext/session/session_common.tcl.

73
74
75
76
77
78
79

80

81
82
83
84
85
86
87
  catch { S delete }

  if {$rc} {error $msg}
}

proc do_iterator_test {tn tbl_list sql res} {
  sqlite3session S db main

  foreach t $tbl_list {S attach $t}

  execsql $sql

  set r [list]
  foreach v $res { lappend r $v }

  set x [list]
  sqlite3session_foreach c [S changeset] { lappend x $c }







>

>







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
  catch { S delete }

  if {$rc} {error $msg}
}

proc do_iterator_test {tn tbl_list sql res} {
  sqlite3session S db main
  if {[llength $tbl_list]==0} { S attach * }
  foreach t $tbl_list {S attach $t}

  execsql $sql

  set r [list]
  foreach v $res { lappend r $v }

  set x [list]
  sqlite3session_foreach c [S changeset] { lappend x $c }

Changes to ext/session/sqlite3session.c.

15
16
17
18
19
20
21

22
23
24
25
26
27
28
...
771
772
773
774
775
776
777
778









779
780
781
782
783
784
785
...
872
873
874
875
876
877
878
879
880
881

882

883



884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903

904
905
906
907
908
909
910
/*
** Session handle structure.
*/
struct sqlite3_session {
  sqlite3 *db;                    /* Database handle session is attached to */
  char *zDb;                      /* Name of database session is attached to */
  int bEnable;                    /* True if currently recording */

  int rc;                         /* Non-zero if an error has occurred */
  sqlite3_session *pNext;         /* Next session object on same db. */
  SessionTable *pTable;           /* List of attached tables */
};

/*
** Structure for changeset iterators.
................................................................................
    /* If this session is attached to a different database ("main", "temp" 
    ** etc.), or if it is not currently enabled, there is nothing to do. Skip 
    ** to the next session object attached to this database. */
    if( pSession->bEnable==0 ) continue;
    if( pSession->rc ) continue;
    if( sqlite3_strnicmp(zDb, pSession->zDb, nDb+1) ) continue;

    for(pTab=pSession->pTable; pTab; pTab=pTab->pNext){









      if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ){
        sessionPreupdateOneChange(op, pSession, pTab);
        if( op==SQLITE_UPDATE ){
          sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab);
        }
        break;
      }
................................................................................
** not matter if the PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias)
** or not.
*/
int sqlite3session_attach(
  sqlite3_session *pSession,      /* Session object */
  const char *zName               /* Table name */
){
  SessionTable *pTab;             /* New table object (if required) */
  int nName;                      /* Number of bytes in string zName */
  int rc = SQLITE_OK;



  sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));




  /* First search for an existing entry. If one is found, this call is
  ** a no-op. Return early. */
  nName = strlen(zName);
  for(pTab=pSession->pTable; pTab; pTab=pTab->pNext){
    if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ) break;
  }

  if( !pTab ){
    /* Allocate new SessionTable object. */
    pTab = (SessionTable *)sqlite3_malloc(sizeof(SessionTable) + nName + 1);
    if( !pTab ){
      rc = SQLITE_NOMEM;
    }else{
      /* Populate the new SessionTable object and link it into the list. */
      memset(pTab, 0, sizeof(SessionTable));
      pTab->zName = (char *)&pTab[1];
      memcpy(pTab->zName, zName, nName+1);
      pTab->pNext = pSession->pTable;
      pSession->pTable = pTab;

    }
  }

  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
  return rc;
}








>







 







|
>
>
>
>
>
>
>
>
>







 







<
<

>

>
|
>
>
>

|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
>







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
...
882
883
884
885
886
887
888


889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
/*
** Session handle structure.
*/
struct sqlite3_session {
  sqlite3 *db;                    /* Database handle session is attached to */
  char *zDb;                      /* Name of database session is attached to */
  int bEnable;                    /* True if currently recording */
  int bAutoAttach;                /* True to auto-attach tables */
  int rc;                         /* Non-zero if an error has occurred */
  sqlite3_session *pNext;         /* Next session object on same db. */
  SessionTable *pTable;           /* List of attached tables */
};

/*
** Structure for changeset iterators.
................................................................................
    /* If this session is attached to a different database ("main", "temp" 
    ** etc.), or if it is not currently enabled, there is nothing to do. Skip 
    ** to the next session object attached to this database. */
    if( pSession->bEnable==0 ) continue;
    if( pSession->rc ) continue;
    if( sqlite3_strnicmp(zDb, pSession->zDb, nDb+1) ) continue;

    for(pTab=pSession->pTable; pTab || pSession->bAutoAttach; pTab=pTab->pNext){
      if( !pTab ){
        /* This branch is taken if table zName has not yet been attached to
        ** this session and the auto-attach flag is set.  */
        pSession->rc = sqlite3session_attach(pSession,zName);
        if( pSession->rc ) continue;
        pTab = pSession->pTable;
        assert( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) );
      }

      if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ){
        sessionPreupdateOneChange(op, pSession, pTab);
        if( op==SQLITE_UPDATE ){
          sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab);
        }
        break;
      }
................................................................................
** not matter if the PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias)
** or not.
*/
int sqlite3session_attach(
  sqlite3_session *pSession,      /* Session object */
  const char *zName               /* Table name */
){


  int rc = SQLITE_OK;
  sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));

  if( !zName ){
    pSession->bAutoAttach = 1;
  }else{
    SessionTable *pTab;           /* New table object (if required) */
    int nName;                    /* Number of bytes in string zName */

    /* First search for an existing entry. If one is found, this call is
    ** a no-op. Return early. */
    nName = strlen(zName);
    for(pTab=pSession->pTable; pTab; pTab=pTab->pNext){
      if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ) break;
    }

    if( !pTab ){
      /* Allocate new SessionTable object. */
      pTab = (SessionTable *)sqlite3_malloc(sizeof(SessionTable) + nName + 1);
      if( !pTab ){
        rc = SQLITE_NOMEM;
      }else{
        /* Populate the new SessionTable object and link it into the list. */
        memset(pTab, 0, sizeof(SessionTable));
        pTab->zName = (char *)&pTab[1];
        memcpy(pTab->zName, zName, nName+1);
        pTab->pNext = pSession->pTable;
        pSession->pTable = pTab;
      }
    }
  }

  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
  return rc;
}

Changes to ext/session/sqlite3session.h.

89
90
91
92
93
94
95
96

97
98





99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
** the session is disabled, or 1 if it is enabled.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable);

/*
** CAPI3REF: Attach A Table To A Session Object
**
** Attach a table to a session. All subsequent changes made to the table

** while the session object is enabled will be recorded. See documentation
** for [sqlite3session_changeset()] for further details.





**
** Changes can only be recorded for tables that have a PRIMARY KEY explicitly
** defined as part of their CREATE TABLE statement. It does not matter if the 
** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY
** KEY may consist of a single column, or may be a composite key.
** 
** It is not an error if the named table does not exist in the database. Nor
** is it an error if the named table does not have a PRIMARY KEY. However,
** no changes will be recorded in either of these scenarios.
**
** Changes are not recorded for individual rows that have NULL values stored
** in one or more of their PRIMARY KEY columns.
**
** SQLITE_OK is returned if the table is successfully attached to the session
** object. Or, if an error occurs, an SQLite error code (e.g. SQLITE_NOMEM)
** is returned.
*/
int sqlite3session_attach(
  sqlite3_session *pSession,      /* Session object */
  const char *zTab                /* Table name */
);

/*







|
>
|
|
>
>
>
>
>













|
|
<







89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

120
121
122
123
124
125
126
** the session is disabled, or 1 if it is enabled.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable);

/*
** CAPI3REF: Attach A Table To A Session Object
**
** If argument zTab is not NULL, then it is the name of a table to attach
** to the session object passed as the first argument. All subsequent changes 
** made to the table while the session object is enabled will be recorded. See 
** documentation for [sqlite3session_changeset()] for further details.
**
** Or, if argument zTab is NULL, then changes are recorded for all tables
** in the database. If additional tables are added to the database (by 
** executing "CREATE TABLE" statements) after this call is made, changes for 
** the new tables are also recorded.
**
** Changes can only be recorded for tables that have a PRIMARY KEY explicitly
** defined as part of their CREATE TABLE statement. It does not matter if the 
** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY
** KEY may consist of a single column, or may be a composite key.
** 
** It is not an error if the named table does not exist in the database. Nor
** is it an error if the named table does not have a PRIMARY KEY. However,
** no changes will be recorded in either of these scenarios.
**
** Changes are not recorded for individual rows that have NULL values stored
** in one or more of their PRIMARY KEY columns.
**
** SQLITE_OK is returned if the call completes without error. Or, if an error 
** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.

*/
int sqlite3session_attach(
  sqlite3_session *pSession,      /* Session object */
  const char *zTab                /* Table name */
);

/*

Changes to ext/session/test_session.c.

50
51
52
53
54
55
56
57


58
59
60
61

62
63
64
65
66
67
68
  if( rc!=TCL_OK ) return rc;
  if( objc!=2+aSub[iSub].nArg ){
    Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
    return TCL_ERROR;
  }

  switch( iSub ){
    case 0:        /* attach */


      rc = sqlite3session_attach(pSession, Tcl_GetString(objv[2]));
      if( rc!=SQLITE_OK ){
        return test_session_error(interp, rc);
      }

      break;

    case 1: {      /* changeset */
      int nChange;
      void *pChange;
      rc = sqlite3session_changeset(pSession, &nChange, &pChange);
      if( rc==SQLITE_OK ){







|
>
>
|



>







50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
  if( rc!=TCL_OK ) return rc;
  if( objc!=2+aSub[iSub].nArg ){
    Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
    return TCL_ERROR;
  }

  switch( iSub ){
    case 0: {      /* attach */
      char *zArg = Tcl_GetString(objv[2]);
      if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
      rc = sqlite3session_attach(pSession, zArg);
      if( rc!=SQLITE_OK ){
        return test_session_error(interp, rc);
      }
    }
      break;

    case 1: {      /* changeset */
      int nChange;
      void *pChange;
      rc = sqlite3session_changeset(pSession, &nChange, &pChange);
      if( rc==SQLITE_OK ){