/ Check-in [1e5e00fb]
Login

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

Overview
Comment:Fix triggers to work in an ATTACHed database. Ticket #295. (CVS 915)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 1e5e00fb73c308378efd034cb291caf338c9fe84
User & Date: drh 2003-04-17 22:57:53
Context
2003-04-18
02:31
The VACUUM command is now functioning (again). Need to do more testing. (CVS 916) check-in: 6e948d9a user: drh tags: trunk
2003-04-17
22:57
Fix triggers to work in an ATTACHed database. Ticket #295. (CVS 915) check-in: 1e5e00fb user: drh tags: trunk
12:44
Make sure the min() and max() optimizations work on empty indexed tables. Ticket #296. (CVS 914) check-in: 98ef6110 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/build.c.

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
105
106
107
108
109
110
111

112


113
114
115
116
117
118
119
...
121
122
123
124
125
126
127








































128

129
130
131
132
133
134
135
....
2074
2075
2076
2077
2078
2079
2080


2081




2082
2083
2084
2085
2086
2087
2088
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**     PRAGMA
**
** $Id: build.c,v 1.145 2003/04/15 01:19:48 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Check to see if the schema for the database needs
................................................................................
  pParse->nSet = 0;
  pParse->nAgg = 0;
}

/*
** Locate the in-memory structure that describes 
** a particular database table given the name

** of that table.  Return NULL if not found.


*/
Table *sqliteFindTable(sqlite *db, const char *zName, const char *zDatabase){
  Table *p = 0;
  int i;
  for(i=0; i<db->nDb; i++){
    int j = (i<2) ? i^1 : i;   /* Search TEMP before MAIN */
    if( zDatabase!=0 && sqliteStrICmp(zDatabase, db->aDb[j].zName) ) continue;
................................................................................
    if( p ) break;
  }
  return p;
}

/*
** Locate the in-memory structure that describes 








































** a particular index given the name of that index.

** Return NULL if not found.
*/
Index *sqliteFindIndex(sqlite *db, const char *zName, const char *zDb){
  Index *p = 0;
  int i;
  for(i=0; i<db->nDb; i++){
    int j = (i<2) ? i^1 : i;  /* Search TEMP before MAIN */
................................................................................
  Vdbe *v;
  v = sqliteGetVdbe(pParse);
  if( v==0 ) return;
  if( pParse->trigStack ) return; /* if this is in a trigger */
  if( (pParse->db->flags & SQLITE_InTrans)==0 ){
    sqliteVdbeAddOp(v, OP_Transaction, 1, 0);
    if( !tempOnly ){


      sqliteVdbeAddOp(v, OP_Transaction, 0, 0);




      sqliteCodeVerifySchema(pParse);
    }
  }else if( setCheckpoint ){
    sqliteVdbeAddOp(v, OP_Checkpoint, 0, 0);
    sqliteVdbeAddOp(v, OP_Checkpoint, 1, 0);
  }
}







|







 







>
|
>
>







 







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







 







>
>

>
>
>
>







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
...
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
....
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**     PRAGMA
**
** $Id: build.c,v 1.146 2003/04/17 22:57:53 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Check to see if the schema for the database needs
................................................................................
  pParse->nSet = 0;
  pParse->nAgg = 0;
}

/*
** Locate the in-memory structure that describes 
** a particular database table given the name
** of that table and (optionally) the name of the database
** containing the table.  Return NULL if not found.
**
** See also sqliteLocateTable().
*/
Table *sqliteFindTable(sqlite *db, const char *zName, const char *zDatabase){
  Table *p = 0;
  int i;
  for(i=0; i<db->nDb; i++){
    int j = (i<2) ? i^1 : i;   /* Search TEMP before MAIN */
    if( zDatabase!=0 && sqliteStrICmp(zDatabase, db->aDb[j].zName) ) continue;
................................................................................
    if( p ) break;
  }
  return p;
}

/*
** Locate the in-memory structure that describes 
** a particular database table given the name
** of that table and (optionally) the name of the database
** containing the table.  Return NULL if not found.
**
** If pParse->useDb is not negative, then the table must be
** located in that database.  If a different database is specified,
** an error message is generated into pParse->zErrMsg.
*/
Table *sqliteLocateTable(Parse *pParse, const char *zName, const char *zDbase){
  sqlite *db;
  const char *zUse;
  Table *p;
  db = pParse->db;
  if( pParse->useDb<0 ){
    p = sqliteFindTable(db, zName, zDbase);
  }else {
    assert( pParse->useDb<db->nDb );
    assert( db->aDb[pParse->useDb].pBt!=0 );
    zUse = db->aDb[pParse->useDb].zName;
    if( zDbase && pParse->useDb!=1 && sqliteStrICmp(zDbase, zUse)!=0 ){
      sqliteErrorMsg(pParse,"cannot use database %s in this context", zDbase);
      return 0;
    }
    p = sqliteFindTable(db, zName, zUse);
    if( p==0 && pParse->useDb==1 && zDbase==0 ){
      p = sqliteFindTable(db, zName, 0);
    }
  }
  if( p==0 ){
    if( zDbase ){
      sqliteErrorMsg(pParse, "no such table: %s.%s", zDbase, zName);
    }else{
      sqliteErrorMsg(pParse, "no such table: %s", zName);
    }
  }
  return p;
}

/*
** Locate the in-memory structure that describes 
** a particular index given the name of that index
** and the name of the database that contains the index.
** Return NULL if not found.
*/
Index *sqliteFindIndex(sqlite *db, const char *zName, const char *zDb){
  Index *p = 0;
  int i;
  for(i=0; i<db->nDb; i++){
    int j = (i<2) ? i^1 : i;  /* Search TEMP before MAIN */
................................................................................
  Vdbe *v;
  v = sqliteGetVdbe(pParse);
  if( v==0 ) return;
  if( pParse->trigStack ) return; /* if this is in a trigger */
  if( (pParse->db->flags & SQLITE_InTrans)==0 ){
    sqliteVdbeAddOp(v, OP_Transaction, 1, 0);
    if( !tempOnly ){
      int i;
      sqlite *db = pParse->db;
      sqliteVdbeAddOp(v, OP_Transaction, 0, 0);
      for(i=2; i<db->nDb; i++){
        if( db->aDb[i].pBt==0 ) continue;
        sqliteVdbeAddOp(v, OP_Transaction, i, 0);
      }
      sqliteCodeVerifySchema(pParse);
    }
  }else if( setCheckpoint ){
    sqliteVdbeAddOp(v, OP_Checkpoint, 0, 0);
    sqliteVdbeAddOp(v, OP_Checkpoint, 1, 0);
  }
}

Changes to src/delete.c.

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
..
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
..
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

117
118
119
120
121
122
123
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle DELETE FROM statements.
**
** $Id: delete.c,v 1.51 2003/04/15 19:22:23 drh Exp $
*/
#include "sqliteInt.h"

/*
** Look up every table that is named in pSrc.  If any table is not found,
** add an error message to pParse->zErrMsg and return NULL.  If all tables
** are found, return a pointer to the last table.
................................................................................
*/
Table *sqliteSrcListLookup(Parse *pParse, SrcList *pSrc){
  Table *pTab = 0;
  int i;
  for(i=0; i<pSrc->nSrc; i++){
    const char *zTab = pSrc->a[i].zName;
    const char *zDb = pSrc->a[i].zDatabase;
    pTab = sqliteFindTable(pParse->db, zTab, zDb);
    if( pTab==0 ){
      sqliteErrorMsg(pParse, "no such table: %S", pSrc, 0);
      break;
    }
    pSrc->a[i].pTab = pTab;
  }
  return pTab;
}

/*
** Check to make sure the given table is writable.  If it is not
................................................................................
void sqliteDeleteFrom(
  Parse *pParse,         /* The parser context */
  SrcList *pTabList,     /* The table from which we should delete things */
  Expr *pWhere           /* The WHERE clause.  May be null */
){
  Vdbe *v;               /* The virtual database engine */
  Table *pTab;           /* The table from which records will be deleted */
  char *zTab;            /* Name of the table from which we are deleting */
  char *zDb;             /* Name of database containing table zTab */
  int end, addr;         /* A couple addresses of generated code */
  int i;                 /* Loop counter */
  WhereInfo *pWInfo;     /* Information about the WHERE clause */
  Index *pIdx;           /* For looping over indices of the table */
  int base;              /* Index of the first available table cursor */
  sqlite *db;            /* Main database structure */

................................................................................
  if( pParse->nErr || sqlite_malloc_failed ){
    pTabList = 0;
    goto delete_from_cleanup;
  }
  db = pParse->db;
  assert( pTabList->nSrc==1 );

  /* Check for the special case of a VIEW with one or more ON DELETE triggers 
  ** defined 
  */
  zTab = pTabList->a[0].zName;
  zDb = pTabList->a[0].zDatabase;
  if( zTab != 0 ){
    pTab = sqliteFindTable(pParse->db, zTab, zDb);
    if( pTab ){
      before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
                             TK_DELETE, TK_BEFORE, TK_ROW, 0);
      after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
                             TK_DELETE, TK_AFTER, TK_ROW, 0);
      row_triggers_exist = before_triggers || after_triggers;
    }
    if( row_triggers_exist &&  pTab->pSelect ){
      /* Just fire VIEW triggers */
      sqliteSrcListDelete(pTabList);
      sqliteViewTriggers(pParse, pTab, pWhere, OE_Replace, 0);
      return;
    }
  }

  /* Locate the table which we want to delete.  This table has to be
  ** put in an SrcList structure because some of the subroutines we
  ** will be calling are designed to work with multiple tables and expect
  ** an SrcList* parameter instead of just a Table* parameter.
  */
  pTab = sqliteSrcListLookup(pParse, pTabList);
  if( pTab==0 || sqliteIsReadOnly(pParse, pTab) ){
    goto delete_from_cleanup;










  }

  assert( pTab->pSelect==0 );  /* This table is not a view */
  if( sqliteAuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0) ){
    goto delete_from_cleanup;
  }

  /* Allocate a cursor used to store the old.* data for a trigger.
  */







|







 







|
<
<
<
<







 







<
<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






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

>







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
..
54
55
56
57
58
59
60


61
62
63
64
65
66
67
..
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
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle DELETE FROM statements.
**
** $Id: delete.c,v 1.52 2003/04/17 22:57:53 drh Exp $
*/
#include "sqliteInt.h"

/*
** Look up every table that is named in pSrc.  If any table is not found,
** add an error message to pParse->zErrMsg and return NULL.  If all tables
** are found, return a pointer to the last table.
................................................................................
*/
Table *sqliteSrcListLookup(Parse *pParse, SrcList *pSrc){
  Table *pTab = 0;
  int i;
  for(i=0; i<pSrc->nSrc; i++){
    const char *zTab = pSrc->a[i].zName;
    const char *zDb = pSrc->a[i].zDatabase;
    pTab = sqliteLocateTable(pParse, zTab, zDb);




    pSrc->a[i].pTab = pTab;
  }
  return pTab;
}

/*
** Check to make sure the given table is writable.  If it is not
................................................................................
void sqliteDeleteFrom(
  Parse *pParse,         /* The parser context */
  SrcList *pTabList,     /* The table from which we should delete things */
  Expr *pWhere           /* The WHERE clause.  May be null */
){
  Vdbe *v;               /* The virtual database engine */
  Table *pTab;           /* The table from which records will be deleted */


  int end, addr;         /* A couple addresses of generated code */
  int i;                 /* Loop counter */
  WhereInfo *pWInfo;     /* Information about the WHERE clause */
  Index *pIdx;           /* For looping over indices of the table */
  int base;              /* Index of the first available table cursor */
  sqlite *db;            /* Main database structure */

................................................................................
  if( pParse->nErr || sqlite_malloc_failed ){
    pTabList = 0;
    goto delete_from_cleanup;
  }
  db = pParse->db;
  assert( pTabList->nSrc==1 );























  /* Locate the table which we want to delete.  This table has to be
  ** put in an SrcList structure because some of the subroutines we
  ** will be calling are designed to work with multiple tables and expect
  ** an SrcList* parameter instead of just a Table* parameter.
  */
  pTab = sqliteSrcListLookup(pParse, pTabList);

  if( pTab==0 )  goto delete_from_cleanup;
  before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
                         TK_DELETE, TK_BEFORE, TK_ROW, 0);
  after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
                         TK_DELETE, TK_AFTER, TK_ROW, 0);
  row_triggers_exist = before_triggers || after_triggers;
  if( row_triggers_exist &&  pTab->pSelect ){
    /* Just fire VIEW triggers */
    sqliteSrcListDelete(pTabList);
    sqliteViewTriggers(pParse, pTab, pWhere, OE_Replace, 0);
    return;
  }
  if( sqliteIsReadOnly(pParse, pTab) ) goto delete_from_cleanup;
  assert( pTab->pSelect==0 );  /* This table is not a view */
  if( sqliteAuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0) ){
    goto delete_from_cleanup;
  }

  /* Allocate a cursor used to store the old.* data for a trigger.
  */

Changes to src/main.c.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
65
66
67
68
69
70
71

72
73
74
75
76
77
78
...
316
317
318
319
320
321
322

323
324
325
326
327
328
329
...
715
716
717
718
719
720
721

722
723
724
725
726
727
728
**
*************************************************************************
** Main file for the SQLite library.  The routines in this file
** implement the programmer interface to the library.  Routines in
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.125 2003/04/16 20:24:52 drh Exp $
*/
#include "sqliteInt.h"
#include "os.h"
#include <ctype.h>

/*
** A pointer to this structure is used to communicate information
................................................................................
        ** or executed.  All the parser does is build the internal data
        ** structures that describe the table, index, or view.
        */
        memset(&sParse, 0, sizeof(sParse));
        sParse.db = pData->db;
        sParse.initFlag = 1;
        sParse.iDb = atoi(argv[4]);

        sParse.newTnum = atoi(argv[2]);
        sParse.useCallback = 1;
        sqliteRunParser(&sParse, argv[3], pData->pzErrMsg);
      }else{
        /* If the SQL column is blank it means this is an index that
        ** was created to be the PRIMARY KEY or to fulfill a UNIQUE
        ** constraint for a CREATE TABLE.  The index should have already
................................................................................
  /* Read the schema information out of the schema tables
  */
  memset(&sParse, 0, sizeof(sParse));
  sParse.db = db;
  sParse.xCallback = sqliteInitCallback;
  sParse.pArg = (void*)&initData;
  sParse.initFlag = 1;

  sParse.useCallback = 1;
  if( iDb==0 ){
    sqliteRunParser(&sParse,
        db->file_format>=2 ? init_script : older_init_script,
        pzErrMsg);
  }else{
    char *zSql = 0;
................................................................................
    return SQLITE_ERROR;
  }
  if( db->pVdbe==0 ){ db->nChange = 0; }
  memset(&sParse, 0, sizeof(sParse));
  sParse.db = db;
  sParse.xCallback = xCallback;
  sParse.pArg = pArg;

  sParse.useCallback = ppVm==0;
#ifndef SQLITE_OMIT_TRACE
  if( db->xTrace ) db->xTrace(db->pTraceArg, zSql);
#endif
  sqliteRunParser(&sParse, zSql, pzErrMsg);
  if( sqlite_malloc_failed ){
    sqliteSetString(pzErrMsg, "out of memory", 0);







|







 







>







 







>







 







>







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
...
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
...
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
**
*************************************************************************
** Main file for the SQLite library.  The routines in this file
** implement the programmer interface to the library.  Routines in
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.126 2003/04/17 22:57:54 drh Exp $
*/
#include "sqliteInt.h"
#include "os.h"
#include <ctype.h>

/*
** A pointer to this structure is used to communicate information
................................................................................
        ** or executed.  All the parser does is build the internal data
        ** structures that describe the table, index, or view.
        */
        memset(&sParse, 0, sizeof(sParse));
        sParse.db = pData->db;
        sParse.initFlag = 1;
        sParse.iDb = atoi(argv[4]);
        sParse.useDb = -1;
        sParse.newTnum = atoi(argv[2]);
        sParse.useCallback = 1;
        sqliteRunParser(&sParse, argv[3], pData->pzErrMsg);
      }else{
        /* If the SQL column is blank it means this is an index that
        ** was created to be the PRIMARY KEY or to fulfill a UNIQUE
        ** constraint for a CREATE TABLE.  The index should have already
................................................................................
  /* Read the schema information out of the schema tables
  */
  memset(&sParse, 0, sizeof(sParse));
  sParse.db = db;
  sParse.xCallback = sqliteInitCallback;
  sParse.pArg = (void*)&initData;
  sParse.initFlag = 1;
  sParse.useDb = -1;
  sParse.useCallback = 1;
  if( iDb==0 ){
    sqliteRunParser(&sParse,
        db->file_format>=2 ? init_script : older_init_script,
        pzErrMsg);
  }else{
    char *zSql = 0;
................................................................................
    return SQLITE_ERROR;
  }
  if( db->pVdbe==0 ){ db->nChange = 0; }
  memset(&sParse, 0, sizeof(sParse));
  sParse.db = db;
  sParse.xCallback = xCallback;
  sParse.pArg = pArg;
  sParse.useDb = -1;
  sParse.useCallback = ppVm==0;
#ifndef SQLITE_OMIT_TRACE
  if( db->xTrace ) db->xTrace(db->pTraceArg, zSql);
#endif
  sqliteRunParser(&sParse, zSql, pzErrMsg);
  if( sqlite_malloc_failed ){
    sqliteSetString(pzErrMsg, "out of memory", 0);

Changes to src/parse.y.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
790
791
792
793
794
795
796
797


798
799
800
801
802
803
804
**
*************************************************************************
** This file contains SQLite's grammar for SQL.  Process this file
** using the lemon parser generator to generate C code that runs
** the parser.  Lemon will also generate a header file containing
** numeric codes for all of the tokens.
**
** @(#) $Id: parse.y,v 1.94 2003/03/31 00:30:48 drh Exp $
*/
%token_prefix TK_
%token_type {Token}
%default_type {Token}
%extra_argument {Parse *pParse}
%syntax_error {
  if( pParse->zErrMsg==0 ){
................................................................................

%type when_clause {Expr *}
when_clause(A) ::= .             { A = 0; }
when_clause(A) ::= WHEN expr(X). { A = X; }

%type trigger_cmd_list {TriggerStep *}
trigger_cmd_list(A) ::= trigger_cmd(X) SEMI trigger_cmd_list(Y). {
  X->pNext = Y ; A = X; }


trigger_cmd_list(A) ::= . { A = 0; }

%type trigger_cmd {TriggerStep *}
// UPDATE 
trigger_cmd(A) ::= UPDATE orconf(R) nm(X) SET setlist(Y) where_opt(Z).  
               { A = sqliteTriggerUpdateStep(&X, Y, Z, R); }








|







 







|
>
>







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
**
*************************************************************************
** This file contains SQLite's grammar for SQL.  Process this file
** using the lemon parser generator to generate C code that runs
** the parser.  Lemon will also generate a header file containing
** numeric codes for all of the tokens.
**
** @(#) $Id: parse.y,v 1.95 2003/04/17 22:57:54 drh Exp $
*/
%token_prefix TK_
%token_type {Token}
%default_type {Token}
%extra_argument {Parse *pParse}
%syntax_error {
  if( pParse->zErrMsg==0 ){
................................................................................

%type when_clause {Expr *}
when_clause(A) ::= .             { A = 0; }
when_clause(A) ::= WHEN expr(X). { A = X; }

%type trigger_cmd_list {TriggerStep *}
trigger_cmd_list(A) ::= trigger_cmd(X) SEMI trigger_cmd_list(Y). {
  X->pNext = Y;
  A = X;
}
trigger_cmd_list(A) ::= . { A = 0; }

%type trigger_cmd {TriggerStep *}
// UPDATE 
trigger_cmd(A) ::= UPDATE orconf(R) nm(X) SET setlist(Y) where_opt(Z).  
               { A = sqliteTriggerUpdateStep(&X, Y, Z, R); }

Changes to src/select.c.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle SELECT statements in SQLite.
**
** $Id: select.c,v 1.131 2003/04/17 12:44:24 drh Exp $
*/
#include "sqliteInt.h"


/*
** Allocate a new Select structure and return a pointer to that
** structure.
................................................................................
      if( pTab==0 ){
        return 1;
      }
      pTab->isTransient = 1;
    }else{
      /* An ordinary table or view name in the FROM clause */
      pTabList->a[i].pTab = pTab = 
        sqliteFindTable(pParse->db, pTabList->a[i].zName,
                                    pTabList->a[i].zDatabase);
      if( pTab==0 ){
        sqliteErrorMsg(pParse, "no such table: %S", pTabList, i);
        return 1;
      }
      if( pTab->pSelect ){
        if( sqliteViewGetColumnNames(pParse, pTab) ){
          return 1;
        }
        sqliteSelectDelete(pTabList->a[i].pSelect);







|







 







<
|

<







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
904
905
906
907
908
909
910

911
912

913
914
915
916
917
918
919
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle SELECT statements in SQLite.
**
** $Id: select.c,v 1.132 2003/04/17 22:57:54 drh Exp $
*/
#include "sqliteInt.h"


/*
** Allocate a new Select structure and return a pointer to that
** structure.
................................................................................
      if( pTab==0 ){
        return 1;
      }
      pTab->isTransient = 1;
    }else{
      /* An ordinary table or view name in the FROM clause */
      pTabList->a[i].pTab = pTab = 

        sqliteLocateTable(pParse,pTabList->a[i].zName,pTabList->a[i].zDatabase);
      if( pTab==0 ){

        return 1;
      }
      if( pTab->pSelect ){
        if( sqliteViewGetColumnNames(pParse, pTab) ){
          return 1;
        }
        sqliteSelectDelete(pTabList->a[i].pSelect);

Changes to src/shell.c.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
281
282
283
284
285
286
287
288

289
290
291
292
293
294
295
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains code to implement the "sqlite" command line
** utility for accessing SQLite databases.
**
** $Id: shell.c,v 1.69 2003/04/17 02:54:14 drh Exp $
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "sqlite.h"
#include <ctype.h>

................................................................................
      if( azArg==0 ) break;
      for(i=0; i<nArg; i++){
        int len = strlen(azCol[i]);
        if( len>w ) w = len;
      }
      if( p->cnt++>0 ) fprintf(p->out,"\n");
      for(i=0; i<nArg; i++){
        fprintf(p->out,"%*s = %s\n", w, azCol[i], azArg[i] ? azArg[i] : p->nullvalue);

      }
      break;
    }
    case MODE_Column: {
      if( p->cnt++==0 ){
        for(i=0; i<nArg; i++){
          int w, n;







|







 







|
>







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains code to implement the "sqlite" command line
** utility for accessing SQLite databases.
**
** $Id: shell.c,v 1.70 2003/04/17 22:57:54 drh Exp $
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "sqlite.h"
#include <ctype.h>

................................................................................
      if( azArg==0 ) break;
      for(i=0; i<nArg; i++){
        int len = strlen(azCol[i]);
        if( len>w ) w = len;
      }
      if( p->cnt++>0 ) fprintf(p->out,"\n");
      for(i=0; i<nArg; i++){
        fprintf(p->out,"%*s = %s\n", w, azCol[i], 
                azArg[i] ? azArg[i] : p->nullvalue);
      }
      break;
    }
    case MODE_Column: {
      if( p->cnt++==0 ){
        for(i=0; i<nArg; i++){
          int w, n;

Changes to src/sqliteInt.h.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
...
821
822
823
824
825
826
827

828
829
830
831
832
833
834
...
914
915
916
917
918
919
920

921
922
923
924
925
926
927
....
1058
1059
1060
1061
1062
1063
1064

1065
1066
1067
1068
1069
1070
1071
**    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.
**
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.174 2003/04/16 02:17:35 drh Exp $
*/
#include "config.h"
#include "sqlite.h"
#include "hash.h"
#include "vdbe.h"
#include "parse.h"
#include "btree.h"
................................................................................
typedef struct FKey FKey;
typedef struct Db Db;

/*
** Each database file to be accessed by the system is an instance
** of the following structure.  There are normally two of these structures
** in the sqlite.aDb[] array.  aDb[0] is the main database file and
** aDb[1] is the database file used to hold temporary tables.  But
** additional databases may be attached to the engine.
*/
struct Db {
  char *zName;         /* Name of this database */
  Btree *pBt;          /* The B*Tree structure for this database file */
  int schema_cookie;   /* Database schema version number for this file */
  Hash tblHash;        /* All tables indexed by name */
  Hash idxHash;        /* All (named) indices indexed by name */
................................................................................
  u8 nameClash;        /* A permanent table name clashes with temp table name */
  u8 useAgg;           /* If true, extract field values from the aggregator
                       ** while generating expressions.  Normally false */
  u8 schemaVerified;   /* True if an OP_VerifySchema has been coded someplace
                       ** other than after an OP_Transaction */
  u8 iDb;              /* Index of database whose schema is being parsed */
  u8 useCallback;      /* True if callbacks should be used to report results */

  int newTnum;         /* Table number to use when reparsing CREATE TABLEs */
  int nErr;            /* Number of errors seen */
  int nTab;            /* Number of previously allocated VDBE cursors */
  int nMem;            /* Number of memory cells used so far */
  int nSet;            /* Number of sets used so far */
  int nAgg;            /* Number of aggregate expressions */
  AggExpr *aAgg;       /* An array of aggregate expressions */
................................................................................
 *              them to. See sqliteUpdate() documentation of "pChanges"
 *              argument.
 * 
 */
struct TriggerStep {
  int op;              /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
  int orconf;          /* OE_Rollback etc. */


  Select *pSelect;     /* Valid for SELECT and sometimes 
			  INSERT steps (when pExprList == 0) */
  Token target;        /* Valid for DELETE, UPDATE, INSERT steps */
  Expr *pWhere;        /* Valid for DELETE, UPDATE steps */
  ExprList *pExprList; /* Valid for UPDATE statements and sometimes 
			   INSERT steps (when pSelect == 0)         */
................................................................................
void sqliteUpdate(Parse*, SrcList*, ExprList*, Expr*, int);
WhereInfo *sqliteWhereBegin(Parse*, int, SrcList*, Expr*, int, ExprList**);
void sqliteWhereEnd(WhereInfo*);
void sqliteExprCode(Parse*, Expr*);
void sqliteExprIfTrue(Parse*, Expr*, int, int);
void sqliteExprIfFalse(Parse*, Expr*, int, int);
Table *sqliteFindTable(sqlite*,const char*, const char*);

Index *sqliteFindIndex(sqlite*,const char*, const char*);
void sqliteUnlinkAndDeleteIndex(sqlite*,Index*);
void sqliteCopy(Parse*, SrcList*, Token*, Token*, int);
void sqliteVacuum(Parse*, Token*);
int sqliteGlobCompare(const unsigned char*,const unsigned char*);
int sqliteLikeCompare(const unsigned char*,const unsigned char*);
char *sqliteTableNameFromToken(Token*);







|







 







|
|







 







>







 







>







 







>







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
...
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
...
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
....
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
**    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.
**
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.175 2003/04/17 22:57:54 drh Exp $
*/
#include "config.h"
#include "sqlite.h"
#include "hash.h"
#include "vdbe.h"
#include "parse.h"
#include "btree.h"
................................................................................
typedef struct FKey FKey;
typedef struct Db Db;

/*
** Each database file to be accessed by the system is an instance
** of the following structure.  There are normally two of these structures
** in the sqlite.aDb[] array.  aDb[0] is the main database file and
** aDb[1] is the database file used to hold temporary tables.  Additional
** databases may be attached.
*/
struct Db {
  char *zName;         /* Name of this database */
  Btree *pBt;          /* The B*Tree structure for this database file */
  int schema_cookie;   /* Database schema version number for this file */
  Hash tblHash;        /* All tables indexed by name */
  Hash idxHash;        /* All (named) indices indexed by name */
................................................................................
  u8 nameClash;        /* A permanent table name clashes with temp table name */
  u8 useAgg;           /* If true, extract field values from the aggregator
                       ** while generating expressions.  Normally false */
  u8 schemaVerified;   /* True if an OP_VerifySchema has been coded someplace
                       ** other than after an OP_Transaction */
  u8 iDb;              /* Index of database whose schema is being parsed */
  u8 useCallback;      /* True if callbacks should be used to report results */
  int useDb;           /* Restrict references to tables in this database */
  int newTnum;         /* Table number to use when reparsing CREATE TABLEs */
  int nErr;            /* Number of errors seen */
  int nTab;            /* Number of previously allocated VDBE cursors */
  int nMem;            /* Number of memory cells used so far */
  int nSet;            /* Number of sets used so far */
  int nAgg;            /* Number of aggregate expressions */
  AggExpr *aAgg;       /* An array of aggregate expressions */
................................................................................
 *              them to. See sqliteUpdate() documentation of "pChanges"
 *              argument.
 * 
 */
struct TriggerStep {
  int op;              /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
  int orconf;          /* OE_Rollback etc. */
  Trigger *pTrig;      /* The trigger that this step is a part of */

  Select *pSelect;     /* Valid for SELECT and sometimes 
			  INSERT steps (when pExprList == 0) */
  Token target;        /* Valid for DELETE, UPDATE, INSERT steps */
  Expr *pWhere;        /* Valid for DELETE, UPDATE steps */
  ExprList *pExprList; /* Valid for UPDATE statements and sometimes 
			   INSERT steps (when pSelect == 0)         */
................................................................................
void sqliteUpdate(Parse*, SrcList*, ExprList*, Expr*, int);
WhereInfo *sqliteWhereBegin(Parse*, int, SrcList*, Expr*, int, ExprList**);
void sqliteWhereEnd(WhereInfo*);
void sqliteExprCode(Parse*, Expr*);
void sqliteExprIfTrue(Parse*, Expr*, int, int);
void sqliteExprIfFalse(Parse*, Expr*, int, int);
Table *sqliteFindTable(sqlite*,const char*, const char*);
Table *sqliteLocateTable(Parse*,const char*, const char*);
Index *sqliteFindIndex(sqlite*,const char*, const char*);
void sqliteUnlinkAndDeleteIndex(sqlite*,Index*);
void sqliteCopy(Parse*, SrcList*, Token*, Token*, int);
void sqliteVacuum(Parse*, Token*);
int sqliteGlobCompare(const unsigned char*,const unsigned char*);
int sqliteLikeCompare(const unsigned char*,const unsigned char*);
char *sqliteTableNameFromToken(Token*);

Changes to src/trigger.c.

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
120
121
122
123
124
125
126





127
128
129
130
131
132
133
...
508
509
510
511
512
513
514

515
516

517
518
519
520
521
522
523
...
550
551
552
553
554
555
556

557
558
559
560
561
562
563
  */
  if( sqlite_malloc_failed ) goto trigger_cleanup;
  assert( pTableName->nSrc==1 );
  tab = sqliteSrcListLookup(pParse, pTableName);
  if( !tab ){
    goto trigger_cleanup;
  }
  if( tab->iDb>=2 ){
    sqliteErrorMsg(pParse, "triggers may not be added to auxiliary "
       "database %s", db->aDb[tab->iDb].zName);
    goto trigger_cleanup;
  }

  zName = sqliteStrNDup(pName->z, pName->n);
  if( sqliteHashFind(&(db->aDb[tab->iDb].trigHash), zName,pName->n+1) ){
................................................................................
  nt->tr_tm = tr_tm;
  nt->pWhen = sqliteExprDup(pWhen);
  sqliteExprDelete(pWhen);
  nt->pColumns = sqliteIdListDup(pColumns);
  sqliteIdListDelete(pColumns);
  nt->foreach = foreach;
  nt->step_list = pStepList;






  /* if we are not initializing, and this trigger is not on a TEMP table, 
  ** build the sqlite_master entry
  */
  if( !pParse->initFlag ){
    static VdbeOp insertTrig[] = {
      { OP_NewRecno,   0, 0,  0          },
................................................................................
  int orconfin              /* Conflict algorithm. (OE_Abort, etc) */  
){
  TriggerStep * pTriggerStep = pStepList;
  int orconf;

  while( pTriggerStep ){
    int saveNTab = pParse->nTab;

    orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin;
    pParse->trigStack->orconf = orconf;

    switch( pTriggerStep->op ){
      case TK_SELECT: {
	Select * ss = sqliteSelectDup(pTriggerStep->pSelect);		  
	assert(ss);
	assert(ss->pSrc);
	sqliteSelect(pParse, ss, SRT_Discard, 0, 0, 0, 0);
	sqliteSelectDelete(ss);
................................................................................
        sqliteVdbeAddOp(pParse->pVdbe, OP_ListPop, 0, 0);
        break;
      }
      default:
        assert(0);
    } 
    pParse->nTab = saveNTab;

    pTriggerStep = pTriggerStep->pNext;
  }

  return 0;
}

/*







|







 







>
>
>
>
>







 







>


>







 







>







61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
...
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
...
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
...
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
  */
  if( sqlite_malloc_failed ) goto trigger_cleanup;
  assert( pTableName->nSrc==1 );
  tab = sqliteSrcListLookup(pParse, pTableName);
  if( !tab ){
    goto trigger_cleanup;
  }
  if( tab->iDb>=2 && !pParse->initFlag ){
    sqliteErrorMsg(pParse, "triggers may not be added to auxiliary "
       "database %s", db->aDb[tab->iDb].zName);
    goto trigger_cleanup;
  }

  zName = sqliteStrNDup(pName->z, pName->n);
  if( sqliteHashFind(&(db->aDb[tab->iDb].trigHash), zName,pName->n+1) ){
................................................................................
  nt->tr_tm = tr_tm;
  nt->pWhen = sqliteExprDup(pWhen);
  sqliteExprDelete(pWhen);
  nt->pColumns = sqliteIdListDup(pColumns);
  sqliteIdListDelete(pColumns);
  nt->foreach = foreach;
  nt->step_list = pStepList;
  while( pStepList ){
    pStepList->pTrig = nt;
    pStepList = pStepList->pNext;
  }
  pStepList = nt->step_list;

  /* if we are not initializing, and this trigger is not on a TEMP table, 
  ** build the sqlite_master entry
  */
  if( !pParse->initFlag ){
    static VdbeOp insertTrig[] = {
      { OP_NewRecno,   0, 0,  0          },
................................................................................
  int orconfin              /* Conflict algorithm. (OE_Abort, etc) */  
){
  TriggerStep * pTriggerStep = pStepList;
  int orconf;

  while( pTriggerStep ){
    int saveNTab = pParse->nTab;
    int saveUseDb = pParse->useDb;
    orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin;
    pParse->trigStack->orconf = orconf;
    pParse->useDb = pTriggerStep->pTrig->iDb;
    switch( pTriggerStep->op ){
      case TK_SELECT: {
	Select * ss = sqliteSelectDup(pTriggerStep->pSelect);		  
	assert(ss);
	assert(ss->pSrc);
	sqliteSelect(pParse, ss, SRT_Discard, 0, 0, 0, 0);
	sqliteSelectDelete(ss);
................................................................................
        sqliteVdbeAddOp(pParse->pVdbe, OP_ListPop, 0, 0);
        break;
      }
      default:
        assert(0);
    } 
    pParse->nTab = saveNTab;
    pParse->useDb = saveUseDb;
    pTriggerStep = pTriggerStep->pNext;
  }

  return 0;
}

/*

Changes to src/update.c.

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
..
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
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle UPDATE statements.
**
** $Id: update.c,v 1.59 2003/04/15 19:22:24 drh Exp $
*/
#include "sqliteInt.h"

/*
** Process an UPDATE statement.
*/
void sqliteUpdate(
................................................................................
  Parse *pParse,         /* The parser context */
  SrcList *pTabList,     /* The table in which we should change things */
  ExprList *pChanges,    /* Things to be changed */
  Expr *pWhere,          /* The WHERE clause.  May be null */
  int onError            /* How to handle constraint errors */
){
  int i, j;              /* Loop counters */
  char *zTab;            /* Name of the table to be updated */
  char *zDb;             /* Name of the database holding zTab */
  Table *pTab;           /* The table to be updated */
  int addr;              /* VDBE instruction address of the start of the loop */
  WhereInfo *pWInfo;     /* Information about the WHERE clause */
  Vdbe *v;               /* The virtual database engine */
  Index *pIdx;           /* For looping over indices */
  int nIdx;              /* Number of indices that need updating */
  int nIdxTotal;         /* Total number of indices */
................................................................................
  int newIdx      = -1;  /* index of trigger "new" temp table       */
  int oldIdx      = -1;  /* index of trigger "old" temp table       */

  if( pParse->nErr || sqlite_malloc_failed ) goto update_cleanup;
  db = pParse->db;
  assert( pTabList->nSrc==1 );

  /* Check for the special case of a VIEW with one or more ON UPDATE triggers 
   * defined 
   */
  zTab = pTabList->a[0].zName;
  zDb = pTabList->a[0].zDatabase;
  if( zTab != 0 ){
    pTab = sqliteFindTable(pParse->db, zTab, zDb);
    if( pTab ){
      before_triggers =
        sqliteTriggersExist(pParse, pTab->pTrigger, 
            TK_UPDATE, TK_BEFORE, TK_ROW, pChanges);
      after_triggers = 
        sqliteTriggersExist(pParse, pTab->pTrigger, 
            TK_UPDATE, TK_AFTER, TK_ROW, pChanges);
      row_triggers_exist = before_triggers || after_triggers;
    }

    if( row_triggers_exist &&  pTab->pSelect ){
      /* Just fire VIEW triggers */
      sqliteSrcListDelete(pTabList);
      sqliteViewTriggers(pParse, pTab, pWhere, onError, pChanges);
      return;
    }
  }

  /* Locate the table which we want to update.  This table has to be
  ** put in an SrcList structure because some of the subroutines we
  ** will be calling are designed to work with multiple tables and expect
  ** an SrcList* parameter instead of just a Table* parameter.
  */
  pTab = sqliteSrcListLookup(pParse, pTabList);












  if( pTab==0 || sqliteIsReadOnly(pParse, pTab) ) goto update_cleanup;
  assert( pTab->pSelect==0 );  /* This table is not a VIEW */
  aXRef = sqliteMalloc( sizeof(int) * pTab->nCol );
  if( aXRef==0 ) goto update_cleanup;
  for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;

  /* If there are FOR EACH ROW triggers, allocate temp tables */
  if( row_triggers_exist ){







|







 







<
<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






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







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
..
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
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains C code routines that are called by the parser
** to handle UPDATE statements.
**
** $Id: update.c,v 1.60 2003/04/17 22:57:54 drh Exp $
*/
#include "sqliteInt.h"

/*
** Process an UPDATE statement.
*/
void sqliteUpdate(
................................................................................
  Parse *pParse,         /* The parser context */
  SrcList *pTabList,     /* The table in which we should change things */
  ExprList *pChanges,    /* Things to be changed */
  Expr *pWhere,          /* The WHERE clause.  May be null */
  int onError            /* How to handle constraint errors */
){
  int i, j;              /* Loop counters */


  Table *pTab;           /* The table to be updated */
  int addr;              /* VDBE instruction address of the start of the loop */
  WhereInfo *pWInfo;     /* Information about the WHERE clause */
  Vdbe *v;               /* The virtual database engine */
  Index *pIdx;           /* For looping over indices */
  int nIdx;              /* Number of indices that need updating */
  int nIdxTotal;         /* Total number of indices */
................................................................................
  int newIdx      = -1;  /* index of trigger "new" temp table       */
  int oldIdx      = -1;  /* index of trigger "old" temp table       */

  if( pParse->nErr || sqlite_malloc_failed ) goto update_cleanup;
  db = pParse->db;
  assert( pTabList->nSrc==1 );


























  /* Locate the table which we want to update.  This table has to be
  ** put in an SrcList structure because some of the subroutines we
  ** will be calling are designed to work with multiple tables and expect
  ** an SrcList* parameter instead of just a Table* parameter.
  */
  pTab = sqliteSrcListLookup(pParse, pTabList);
  if( pTab==0 ) goto update_cleanup;
  before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
            TK_UPDATE, TK_BEFORE, TK_ROW, pChanges);
  after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
            TK_UPDATE, TK_AFTER, TK_ROW, pChanges);
  row_triggers_exist = before_triggers || after_triggers;
  if( row_triggers_exist &&  pTab->pSelect ){
    /* Just fire VIEW triggers */
    sqliteSrcListDelete(pTabList);
    sqliteViewTriggers(pParse, pTab, pWhere, onError, pChanges);
    return;
  }
  if( sqliteIsReadOnly(pParse, pTab) ) goto update_cleanup;
  assert( pTab->pSelect==0 );  /* This table is not a VIEW */
  aXRef = sqliteMalloc( sizeof(int) * pTab->nCol );
  if( aXRef==0 ) goto update_cleanup;
  for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;

  /* If there are FOR EACH ROW triggers, allocate temp tables */
  if( row_triggers_exist ){

Changes to test/attach.test.

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
196
197
198
199
200
201
202





































203
204
205
206
207
208
209
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.  The
# focus of this script is testing the ATTACH and DETACH commands
# and related functionality.
#
# $Id: attach.test,v 1.2 2003/04/05 16:56:30 drh Exp $
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl

for {set i 2} {$i<=15} {incr i} {
  file delete -force test$i.db
................................................................................
} {0 {}}
do_test attach-1.29 {
  execsql {
    PRAGMA database_list
  }
} {0 main 1 temp}







































for {set i 2} {$i<=15} {incr i} {
  catch {db$i close}
}


finish_test







|







 







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







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.  The
# focus of this script is testing the ATTACH and DETACH commands
# and related functionality.
#
# $Id: attach.test,v 1.3 2003/04/17 22:57:55 drh Exp $
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl

for {set i 2} {$i<=15} {incr i} {
  file delete -force test$i.db
................................................................................
} {0 {}}
do_test attach-1.29 {
  execsql {
    PRAGMA database_list
  }
} {0 main 1 temp}

do_test attach-2.1 {
  execsql {
    CREATE TABLE tx(x1,x2,y1,y2);
    CREATE TRIGGER r1 AFTER UPDATE ON t2 FOR EACH ROW BEGIN
      INSERT INTO tx(x1,x2,y1,y2) VALUES(OLD.x,NEW.x,OLD.y,NEW.y);
    END;
    SELECT * FROM tx;
  } db2;
} {}
do_test attach-2.2 {
  execsql {
    UPDATE t2 SET x=x+10;
    SELECT * FROM tx;
  } db2;
} {1 11 x x 2 12 y y}
do_test attach-2.3 {
  execsql {
    CREATE TABLE tx(x1,x2,y1,y2);
    SELECT * FROM tx;
  }
} {}
do_test attach-2.4 {
  execsql {
    ATTACH 'test2.db' AS db2;
  }
} {}
do_test attach-2.5 {
  execsql {
    UPDATE db2.t2 SET x=x+10;
    SELECT * FROM db2.tx;
  }
} {1 11 x x 2 12 y y 11 21 x x 12 22 y y}
do_test attach-2.6 {
  execsql {
    SELECT * FROM main.tx;
  }
} {}

for {set i 2} {$i<=15} {incr i} {
  catch {db$i close}
}


finish_test