/ Check-in [d9e48cd5]
Login

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

Overview
Comment:Require the INSTEAD OF syntax to create triggers on database views. (CVS 591)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:d9e48cd5180e2aae7672b9d54e18c37d0fe5258a
User & Date: danielk1977 2002-05-26 23:24:41
Context
2002-05-27
01:04
Bug fixes and additional test cases for the distinct-NULL patch. (CVS 592) check-in: 0e268d0c user: drh tags: trunk
2002-05-26
23:24
Require the INSTEAD OF syntax to create triggers on database views. (CVS 591) check-in: d9e48cd5 user: danielk1977 tags: trunk
21:34
Change functions to handle NULLs correctly. Added the NULLIF() function. (CVS 590) check-in: 46ce1a9a user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/trigger.c.

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
..
34
35
36
37
38
39
40



41
42
43
44
45
46
47
..
63
64
65
66
67
68
69















70
71
72
73
74
75
76
** This is called by the parser when it sees a CREATE TRIGGER statement. See
** comments surrounding struct Trigger in sqliteInt.h for a description of 
** how triggers are stored.
*/
void sqliteCreateTrigger(
  Parse *pParse,      /* The parse context of the CREATE TRIGGER statement */
  Token *pName,       /* The name of the trigger */
  int tr_tm,          /* One of TK_BEFORE, TK_AFTER */
  int op,             /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
  IdList *pColumns,   /* column list if this is an UPDATE OF trigger */
  Token *pTableName,  /* The name of the table/view the trigger applies to */
  int foreach,        /* One of TK_ROW or TK_STATEMENT */
  Expr *pWhen,        /* WHEN clause */
  TriggerStep *pStepList, /* The triggered program */
  char const *zData,  /* The string data to make persistent */
................................................................................
  Table   *tab;
  int offset;
  TriggerStep *ss;

  /* Check that: 
  ** 1. the trigger name does not already exist.
  ** 2. the table (or view) does exist.



  */
  {
    char *tmp_str = sqliteStrNDup(pName->z, pName->n);
    if( sqliteHashFind(&(pParse->db->trigHash), tmp_str, pName->n + 1) ){
      sqliteSetNString(&pParse->zErrMsg, "trigger ", -1,
          pName->z, pName->n, " already exists", -1, 0);
      sqliteFree(tmp_str);
................................................................................
    }
    if( sqliteStrICmp(tab->zName, MASTER_NAME)==0 ){
      sqliteSetString(&pParse->zErrMsg, "cannot create trigger on system "
         "table: " MASTER_NAME, 0);
      pParse->nErr++;
      goto trigger_cleanup;
    }















  }

  /* Build the Trigger object */
  nt = (Trigger*)sqliteMalloc(sizeof(Trigger));
  if( nt==0 ) goto trigger_cleanup;
  nt->name = sqliteStrNDup(pName->z, pName->n);
  nt->table = sqliteStrNDup(pTableName->z, pTableName->n);







|







 







>
>
>







 







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







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
..
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
..
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
** This is called by the parser when it sees a CREATE TRIGGER statement. See
** comments surrounding struct Trigger in sqliteInt.h for a description of 
** how triggers are stored.
*/
void sqliteCreateTrigger(
  Parse *pParse,      /* The parse context of the CREATE TRIGGER statement */
  Token *pName,       /* The name of the trigger */
  int tr_tm,          /* One of TK_BEFORE, TK_AFTER , TK_INSTEAD */
  int op,             /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
  IdList *pColumns,   /* column list if this is an UPDATE OF trigger */
  Token *pTableName,  /* The name of the table/view the trigger applies to */
  int foreach,        /* One of TK_ROW or TK_STATEMENT */
  Expr *pWhen,        /* WHEN clause */
  TriggerStep *pStepList, /* The triggered program */
  char const *zData,  /* The string data to make persistent */
................................................................................
  Table   *tab;
  int offset;
  TriggerStep *ss;

  /* Check that: 
  ** 1. the trigger name does not already exist.
  ** 2. the table (or view) does exist.
  ** 3. that we are not trying to create a trigger on the sqlite_master table
  ** 4. That we are not trying to create an INSTEAD OF trigger on a table.
  ** 5. That we are not trying to create a BEFORE or AFTER trigger on a view.
  */
  {
    char *tmp_str = sqliteStrNDup(pName->z, pName->n);
    if( sqliteHashFind(&(pParse->db->trigHash), tmp_str, pName->n + 1) ){
      sqliteSetNString(&pParse->zErrMsg, "trigger ", -1,
          pName->z, pName->n, " already exists", -1, 0);
      sqliteFree(tmp_str);
................................................................................
    }
    if( sqliteStrICmp(tab->zName, MASTER_NAME)==0 ){
      sqliteSetString(&pParse->zErrMsg, "cannot create trigger on system "
         "table: " MASTER_NAME, 0);
      pParse->nErr++;
      goto trigger_cleanup;
    }
    if( tab->pSelect && tr_tm != TK_INSTEAD ){
      sqliteSetNString(&pParse->zErrMsg, "cannot create ", -1,
	  (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", -1, " trigger on view: ", -1
          , pTableName->z, pTableName->n, 0);
      goto trigger_cleanup;
    }
    if( !tab->pSelect && tr_tm == TK_INSTEAD ){
      sqliteSetNString(&pParse->zErrMsg, "cannot create INSTEAD OF", -1, 
	  " trigger on table: ", -1, pTableName->z, pTableName->n, 0);
      goto trigger_cleanup;
    }
  }

  if (tr_tm == TK_INSTEAD){
    tr_tm = TK_BEFORE;
  }

  /* Build the Trigger object */
  nt = (Trigger*)sqliteMalloc(sizeof(Trigger));
  if( nt==0 ) goto trigger_cleanup;
  nt->name = sqliteStrNDup(pName->z, pName->n);
  nt->table = sqliteStrNDup(pTableName->z, pTableName->n);

Changes to test/trigger1.test.

15
16
17
18
19
20
21






22
23
24
25
26
27
28
...
140
141
142
143
144
145
146
147





























148
# trig-1.2: Error if trigger already exists
# trig-1.3: Created triggers are deleted if the transaction is rolled back
# trig-1.4: DROP TRIGGER removes trigger
# trig-1.5: Dropped triggers are restored if the transaction is rolled back
# trig-1.6: Error if dropped trigger doesn't exist
# trig-1.7: Dropping the table automatically drops all triggers
# trig-1.8: A trigger created on a TEMP table is not inserted into sqlite_master






#

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

do_test trig_cd-1.1 {
   catchsql {
................................................................................
      delete from t1 WHERE a=old.a+2;
    end;
    update t1 set b='x-' || b where a in (1,3);
    select * from t1;
    drop table t1;
  }
} {1 x-a 2 b 4 d}






























finish_test







>
>
>
>
>
>







 








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

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
...
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
180
181
182
183
# trig-1.2: Error if trigger already exists
# trig-1.3: Created triggers are deleted if the transaction is rolled back
# trig-1.4: DROP TRIGGER removes trigger
# trig-1.5: Dropped triggers are restored if the transaction is rolled back
# trig-1.6: Error if dropped trigger doesn't exist
# trig-1.7: Dropping the table automatically drops all triggers
# trig-1.8: A trigger created on a TEMP table is not inserted into sqlite_master
# trig-1.9: Ensure that we cannot create a trigger on sqlite_master
# trig-1.10:
# trig-1.11:
# trig-1.12: Ensure that INSTEAD OF triggers cannot be created on tables
# trig-1.13: Ensure that AFTER triggers cannot be created on views
# trig-1.14: Ensure that BEFORE triggers cannot be created on views
#

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

do_test trig_cd-1.1 {
   catchsql {
................................................................................
      delete from t1 WHERE a=old.a+2;
    end;
    update t1 set b='x-' || b where a in (1,3);
    select * from t1;
    drop table t1;
  }
} {1 x-a 2 b 4 d}

# Ensure that we cannot create INSTEAD OF triggers on tables
do_test trig_cd-1.12 {
  catchsql {
    create table t1(a,b);
    create trigger t1t instead of update on t1 for each row begin
      delete from t1 WHERE a=old.a+2;
    end;
  }
} {1 {cannot create INSTEAD OF trigger on table: t1}}
# Ensure that we cannot create BEFORE triggers on views
do_test trig_cd-1.13 {
  catchsql {
    create view v1 as select * from t1;
    create trigger v1t before update on v1 for each row begin
      delete from t1 WHERE a=old.a+2;
    end;
  }
} {1 {cannot create BEFORE trigger on view: v1}}
# Ensure that we cannot create AFTER triggers on views
do_test trig_cd-1.14 {
  catchsql {
    create table t1(a,b);
    create view v1 as select * from t1;
    create trigger v1t AFTER update on v1 for each row begin
      delete from t1 WHERE a=old.a+2;
    end;
  }
} {1 {cannot create AFTER trigger on view: v1}}

finish_test

Changes to www/lang.tcl.

1
2
3
4
5
6
7
8
9
10
11
...
348
349
350
351
352
353
354






355
356
357
358
359
360
361
...
421
422
423
424
425
426
427
428

429
430
431
432
433
434
435
436
437
438
439
440
#
# Run this Tcl script to generate the sqlite.html file.
#
set rcsid {$Id: lang.tcl,v 1.35 2002/05/15 11:43:16 drh Exp $}

puts {<html>
<head>
  <title>Query Language Understood By SQLite</title>
</head>
<body bgcolor=white>
<h1 align=center>
................................................................................
Section {CREATE TRIGGER} createtrigger

Syntax {sql-statement} {
CREATE TRIGGER <trigger-name> [ BEFORE | AFTER ]
<database-event> ON <table-name>
<trigger-action>
}







Syntax {database-event} {
DELETE | 
INSERT | 
UPDATE | 
UPDATE OF <column-list>
}
................................................................................
<i>trigger-step</i>. However if an ON CONFLICT clause is specified as part of 
the statement causing the trigger to fire, then this conflict handling
policy is used instead.</p>

<p>Triggers are automatically dropped when the table that they are 
associated with is dropped.</p>

<p>Triggers may be created on views, as well as ordinary tables. If one or

more INSERT, DELETE or UPDATE triggers are defined on a view, then it is not
an error to execute an INSERT, DELETE or UPDATE statement on the view, 
respectively. Thereafter, executing an INSERT, DELETE or UPDATE on the view
causes the associated triggers to fire. The real tables underlying the view
are not modified (except possibly explicitly, by a trigger program).</p>

<p><b>Example:</b></p>

<p>Assuming that customer records are stored in the "customers" table, and
that order records are stored in the "orders" table, the following trigger
ensures that all associated orders are redirected when a customer changes
his or her address:</p>



|







 







>
>
>
>
>
>







 







|
>
|
|
|
|
|







1
2
3
4
5
6
7
8
9
10
11
...
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
...
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
#
# Run this Tcl script to generate the sqlite.html file.
#
set rcsid {$Id: lang.tcl,v 1.36 2002/05/26 23:24:41 danielk1977 Exp $}

puts {<html>
<head>
  <title>Query Language Understood By SQLite</title>
</head>
<body bgcolor=white>
<h1 align=center>
................................................................................
Section {CREATE TRIGGER} createtrigger

Syntax {sql-statement} {
CREATE TRIGGER <trigger-name> [ BEFORE | AFTER ]
<database-event> ON <table-name>
<trigger-action>
}

Syntax {sql-statement} {
CREATE TRIGGER <trigger-name> INSTEAD OF
<database-event> ON <view-name>
<trigger-action>
}

Syntax {database-event} {
DELETE | 
INSERT | 
UPDATE | 
UPDATE OF <column-list>
}
................................................................................
<i>trigger-step</i>. However if an ON CONFLICT clause is specified as part of 
the statement causing the trigger to fire, then this conflict handling
policy is used instead.</p>

<p>Triggers are automatically dropped when the table that they are 
associated with is dropped.</p>

<p>Triggers may be created on views, as well as ordinary tables, by specifying
INSTEAD OF in the CREATE TRIGGER statement. If one or more ON INSERT, ON DELETE
or ON UPDATE triggers are defined on a view, then it is not an error to execute
an INSERT, DELETE or UPDATE statement on the view, respectively. Thereafter,
executing an INSERT, DELETE or UPDATE on the view causes the associated
  triggers to fire. The real tables underlying the view are not modified
  (except possibly explicitly, by a trigger program).</p>

<p><b>Example:</b></p>

<p>Assuming that customer records are stored in the "customers" table, and
that order records are stored in the "orders" table, the following trigger
ensures that all associated orders are redirected when a customer changes
his or her address:</p>