/ Check-in [bdaf9575]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

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

Overview
Comment:Modify the sessions module to ignore all operations on tables with no primary keys as documented.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: bdaf9575cd9ebb33dc5da4062a84bca79e7b0fec
User & Date: dan 2015-06-02 09:19:22
Context
2015-06-02
09:20
Add the "finish_test" command to the end of new test script sessionE.test. check-in: fb391407 user: dan tags: sessions
09:19
Modify the sessions module to ignore all operations on tables with no primary keys as documented. check-in: bdaf9575 user: dan tags: sessions
2015-05-29
19:04
Import recent enhancements from trunk. check-in: 54bec164 user: drh tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/session1.test.

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
...
521
522
523
524
525
526
527









































528
529
530
531
532
533
534
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}

set testprefix session1

proc do_changeset_test {tn session res} {
  set r [list]
  foreach x $res {lappend r $x}
  uplevel do_test $tn [list [subst -nocommands {
    set x [list]
    sqlite3session_foreach c [$session changeset] { lappend x [set c] }
    set x
  }]] [list $r]
}

proc do_changeset_invert_test {tn session res} {
  set r [list]
  foreach x $res {lappend r $x}
  uplevel do_test $tn [list [subst -nocommands {
    set x [list]
    set changeset [sqlite3changeset_invert [$session changeset]]
    sqlite3session_foreach c [set changeset] { lappend x [set c] }
................................................................................
  S attach *
  execsql { UPDATE t7 SET b=2, d=2 }
} {}
do_changeset_test 9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}}
S delete
catch { db2 close }
 









































#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test 10.1.1 {
  execsql "







<
<
<
<
<
<
<
<
<
<







 







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







16
17
18
19
20
21
22










23
24
25
26
27
28
29
...
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}

set testprefix session1











proc do_changeset_invert_test {tn session res} {
  set r [list]
  foreach x $res {lappend r $x}
  uplevel do_test $tn [list [subst -nocommands {
    set x [list]
    set changeset [sqlite3changeset_invert [$session changeset]]
    sqlite3session_foreach c [set changeset] { lappend x [set c] }
................................................................................
  S attach *
  execsql { UPDATE t7 SET b=2, d=2 }
} {}
do_changeset_test 9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}}
S delete
catch { db2 close }
 
#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test 10.1.1 {
  execsql "
    CREATE TABLE $tblname (a PRIMARY KEY, b);
    INSERT INTO $tblname VALUES('xyz', 'def');
  "
  sqlite3session S db main
  S attach $tblname
  execsql " 
    INSERT INTO $tblname VALUES('uvw', 'abc');
    DELETE FROM $tblname WHERE a = 'xyz';
  "
} {}
breakpoint
do_changeset_test 10.1.2 S "
  {INSERT $tblname 0 X. {} {t uvw t abc}}
  {DELETE $tblname 0 X. {t xyz t def} {}}
"
do_test 10.1.4 { S delete } {}

#---------------------------------------------------------------
reset_db
do_execsql_test 11.1 {
  CREATE TABLE t1(a, b);
}
do_test 11.2 {
  sqlite3session S db main
  S attach t1
  execsql {
    INSERT INTO t1 VALUES(1, 2);
  }
  S changeset
} {}

S delete


#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test 10.1.1 {
  execsql "

Added ext/session/sessionE.test.









































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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
# 2015 June 02
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file implements regression tests for the sessions module.
# Specifically, it tests that operations on tables without primary keys
# are ignored.
#



if {![info exists testdir]} {
  set testdir [file join [file dirname [info script]] .. .. test]
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}
set testprefix sessionE

#
# Test plan:
#
#    1.*: Test that non-PK tables are not auto-attached.
#    2.*: Test that explicitly attaching a non-PK table is a no-op.
#    3.*: Test that sqlite3session_diff() on a non-PK table is a no-op.
#


#--------------------------------------------------------------------------
reset_db
do_execsql_test 1.0 {
  CREATE TABLE t1(a, b);
  CREATE TABLE t2(a PRIMARY KEY, b);
}
do_test 1.1 {
  sqlite3session S db main
  S attach *
  breakpoint
  execsql {
    INSERT INTO t1 VALUES(1, 2);
    INSERT INTO t2 VALUES(1, 2);
  }
} {}
do_changeset_test 1.2 S {
  {INSERT t2 0 X. {} {i 1 i 2}}
}
S delete

reset_db
do_execsql_test 2.0 {
  CREATE TABLE t1(a, b);
  CREATE TABLE t2(a PRIMARY KEY, b);
}
do_test 2.1 {
  sqlite3session S db main
  S attach t1
  S attach t2
  breakpoint
  execsql {
    INSERT INTO t1 VALUES(3, 4);
    INSERT INTO t2 VALUES(3, 4);
    INSERT INTO t1 VALUES(5, 6);
    INSERT INTO t2 VALUES(5, 6);
  }
} {}
do_changeset_test 2.2 S {
  {INSERT t2 0 X. {} {i 3 i 4}}
  {INSERT t2 0 X. {} {i 5 i 6}}
}
S delete

reset_db
forcedelete test.db2
do_execsql_test 3.0 {
  ATTACH 'test.db2' AS aux;
  CREATE TABLE aux.t1(a, b);
  CREATE TABLE aux.t2(a PRIMARY KEY, b);

  CREATE TABLE t1(a, b);
  CREATE TABLE t2(a PRIMARY KEY, b);

  INSERT INTO t1 VALUES(1, 2);
  INSERT INTO t2 VALUES(3, 4);
}
do_test 3.1 {
  sqlite3session S db main
  S attach t1
  S diff aux t1

  S attach t2
  S diff aux t2
} {}
do_changeset_test 3.2 S {
  {INSERT t2 0 X. {} {i 3 i 4}}
}
do_execsql_test 3.3 {
  INSERT INTO t1 VALUES(5, 6);
  INSERT INTO t2 VALUES(7, 8);
}
do_changeset_test 3.4 S {
  {INSERT t2 0 X. {} {i 3 i 4}}
  {INSERT t2 0 X. {} {i 7 i 8}}
}


S delete



Changes to ext/session/session_common.tcl.











1
2
3
4
5
6
7












proc do_conflict_test {tn args} {
  proc xConflict {args} { 
    lappend ::xConflict $args
    return "" 
  }
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

proc do_changeset_test {tn session res} {
  set r [list]
  foreach x $res {lappend r $x}
  uplevel do_test $tn [list [subst -nocommands {
    set x [list]
    sqlite3session_foreach c [$session changeset] { lappend x [set c] }
    set x
  }]] [list $r]
}


proc do_conflict_test {tn args} {
  proc xConflict {args} { 
    lappend ::xConflict $args
    return "" 
  }

Changes to ext/session/sqlite3session.c.

1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025

1026
1027
1028
1029
1030
1031
1032
1033
1034


1035



1036
1037
1038
1039
1040
1041
1042
1043
....
1053
1054
1055
1056
1057
1058
1059







1060
1061
1062
1063
1064
1065
1066
....
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
  sqlite3_finalize(pStmt);
  return rc;
}

/*
** This function is only called from within a pre-update handler for a
** write to table pTab, part of session pSession. If this is the first
** write to this table, set the SessionTable.nCol variable to the number
** of columns in the table.
**
** Otherwise, if this is not the first time this table has been written
** to, check that the number of columns in the table has not changed. If
** it has not, return zero.
**
** If the number of columns in the table has changed since the last write
** was recorded, set the session error-code to SQLITE_SCHEMA and return
** non-zero. Users are not allowed to change the number of columns in a table
** for which changes are being recorded by the session module. If they do so, 
** it is an error.
*/
static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
  if( pTab->nCol==0 ){

    assert( pTab->azCol==0 || pTab->abPK==0 );
    pSession->rc = sessionTableInfo(pSession->db, pSession->zDb, 
        pTab->zName, &pTab->nCol, 0, &pTab->azCol, &pTab->abPK
    );
  }
  if( pSession->rc==SQLITE_OK 
   && pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx)
  ){
    pSession->rc = SQLITE_SCHEMA;


  }



  return pSession->rc;
}

/*
** This function is only called from with a pre-update-hook reporting a 
** change on table pTab (attached to session pSession). The type of change
** (UPDATE, INSERT, DELETE) is specified by the first argument.
**
................................................................................
  int bNull = 0; 
  int rc = SQLITE_OK;

  if( pSession->rc ) return;

  /* Load table details if required */
  if( sessionInitTable(pSession, pTab) ) return;








  /* Grow the hash table if required */
  if( sessionGrowHash(0, pTab) ){
    pSession->rc = SQLITE_NOMEM;
    return;
  }

................................................................................
    char *zExpr = 0;
    sqlite3 *db = pSession->db;
    SessionTable *pTo;            /* Table zTbl */

    /* Locate and if necessary initialize the target table object */
    rc = sessionFindTable(pSession, zTbl, &pTo);
    if( pTo==0 ) goto diff_out;
    if( pTo->nCol==0 ){
      rc = pSession->rc = sessionTableInfo(db, zDb, 
          pTo->zName, &pTo->nCol, 0, &pTo->azCol, &pTo->abPK
      );
    }

    /* Check the table schemas match */
    if( rc==SQLITE_OK ){
      int bHasPk = 0;
      int bMismatch = 0;
      int nCol;                   /* Columns in zFrom.zTbl */







|
|

|
|
|
|
|
<
<
<
<



>


|

<
|
|
|
|
>
>
|
>
>
>
|







 







>
>
>
>
>
>
>







 







|
|
|
<







1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018




1019
1020
1021
1022
1023
1024
1025
1026

1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
....
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
....
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478

1479
1480
1481
1482
1483
1484
1485
  sqlite3_finalize(pStmt);
  return rc;
}

/*
** This function is only called from within a pre-update handler for a
** write to table pTab, part of session pSession. If this is the first
** write to this table, initalize the SessionTable.nCol, azCol[] and
** abPK[] arrays accordingly.
**
** If an error occurs, an error code is stored in sqlite3_session.rc and
** non-zero returned. Or, if no error occurs but the table has no primary
** key, sqlite3_session.rc is left set to SQLITE_OK and non-zero returned to
** indicate that updates on this table should be ignored. SessionTable.abPK 
** is set to NULL in this case.




*/
static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
  if( pTab->nCol==0 ){
    u8 *abPK;
    assert( pTab->azCol==0 || pTab->abPK==0 );
    pSession->rc = sessionTableInfo(pSession->db, pSession->zDb, 
        pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK
    );

    if( pSession->rc==SQLITE_OK ){
      int i;
      for(i=0; i<pTab->nCol; i++){
        if( abPK[i] ){
          pTab->abPK = abPK;
          break;
        }
      }
    }
  }
  return (pSession->rc || pTab->abPK==0);
}

/*
** This function is only called from with a pre-update-hook reporting a 
** change on table pTab (attached to session pSession). The type of change
** (UPDATE, INSERT, DELETE) is specified by the first argument.
**
................................................................................
  int bNull = 0; 
  int rc = SQLITE_OK;

  if( pSession->rc ) return;

  /* Load table details if required */
  if( sessionInitTable(pSession, pTab) ) return;

  /* Check the number of columns in this xPreUpdate call matches the 
  ** number of columns in the table.  */
  if( pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx) ){
    pSession->rc = SQLITE_SCHEMA;
    return;
  }

  /* Grow the hash table if required */
  if( sessionGrowHash(0, pTab) ){
    pSession->rc = SQLITE_NOMEM;
    return;
  }

................................................................................
    char *zExpr = 0;
    sqlite3 *db = pSession->db;
    SessionTable *pTo;            /* Table zTbl */

    /* Locate and if necessary initialize the target table object */
    rc = sessionFindTable(pSession, zTbl, &pTo);
    if( pTo==0 ) goto diff_out;
    if( sessionInitTable(pSession, pTo) ){
      rc = pSession->rc;
      goto diff_out;

    }

    /* Check the table schemas match */
    if( rc==SQLITE_OK ){
      int bHasPk = 0;
      int bMismatch = 0;
      int nCol;                   /* Columns in zFrom.zTbl */