/ Check-in [f45c4b76]
Login

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

Overview
Comment:Fix for ticket #41: Better handling of CREATE TRIGGER in the sqlite_complete() function. (CVS 567)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:f45c4b767a6b1451787836060235ff7499dea0de
User & Date: drh 2002-05-15 14:17:45
Context
2002-05-15
23:26
Fix a typo in the c_interface.html documentation file. (CVS 568) check-in: 454879fa user: drh tags: trunk
14:17
Fix for ticket #41: Better handling of CREATE TRIGGER in the sqlite_complete() function. (CVS 567) check-in: f45c4b76 user: drh tags: trunk
12:45
Beginning to clean up the trigger code. Still lots of work to do. (CVS 566) check-in: b1034681 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/main.c.

10
11
12
13
14
15
16
17
18
19
20

21
22
23
24
25
26
27
...
472
473
474
475
476
477
478




479
480
481


482
483
484
485
486


487
488
489
490
491
492
493
494
495
496


497
498
499
500
501

502

503
504
505
506
507
508
509
510
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
**
*************************************************************************
** 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.73 2002/05/15 11:44:14 drh Exp $
*/
#include "sqliteInt.h"
#include "os.h"


/*
** This is the callback routine for the code that initializes the
** database.  See sqliteInit() below for additional information.
**
** Each callback contains the following information:
**
................................................................................
  }
  sqliteHashClear(&db->aFunc);
  sqliteFree(db);
}

/*
** Return TRUE if the given SQL string ends in a semicolon.




*/
int sqlite_complete(const char *zSql){
  int isComplete = 0;


  int seenCreate = 0;
  while( *zSql ){
    switch( *zSql ){
      case ';': {
        isComplete = 1;


        break;
      }
      case ' ':
      case '\t':
      case '\n':
      case '\f': {
        break;
      }
      case '[': {
        isComplete = 0;


        zSql++;
        while( *zSql && *zSql!=']' ){ zSql++; }
        if( *zSql==0 ) return 0;
        break;
      }

      case '\'': {

        isComplete = 0;
        zSql++;
        while( *zSql && *zSql!='\'' ){ zSql++; }
        if( *zSql==0 ) return 0;
        break;
      }
      case '"': {
        isComplete = 0;
        zSql++;
        while( *zSql && *zSql!='"' ){ zSql++; }
        if( *zSql==0 ) return 0;
        break;
      }
      case '-': {
        if( zSql[1]!='-' ){
          isComplete = 0;

          break;
        }
        while( *zSql && *zSql!='\n' ){ zSql++; }
        if( *zSql==0 ) return isComplete;
        break;
      }
      default: {
        if (seenCreate && !sqliteStrNICmp(zSql, "trigger", 7)){




          while (sqliteStrNICmp(zSql, "end", 3)){
            if (!*++zSql) return 0;








          }











        }







        if (!sqliteStrNICmp(zSql, "create", 6)) {
          zSql = zSql + 5;



          seenCreate = 1;
        }else{
          seenCreate = 0;

        }





        isComplete = 0;
        break;
      }
    }
    zSql++;
  }
  return isComplete;
}

/*
** Execute SQL code.  Return one of the SQLITE_ success/failure
** codes.  Also write an error message into memory obtained from
** malloc() and make *pzErrMsg point to that message.
**







|



>







 







>
>
>
>


|
>
>





>
>










>
>





>

>

<
<
|
<
<
<
|

|






>



|


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

>
>
>
>
>






|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
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
566
567
568
569
570
571
572
573


574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
**
*************************************************************************
** 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.74 2002/05/15 14:17:45 drh Exp $
*/
#include "sqliteInt.h"
#include "os.h"
#include <ctype.h>

/*
** This is the callback routine for the code that initializes the
** database.  See sqliteInit() below for additional information.
**
** Each callback contains the following information:
**
................................................................................
  }
  sqliteHashClear(&db->aFunc);
  sqliteFree(db);
}

/*
** Return TRUE if the given SQL string ends in a semicolon.
**
** Special handling is require for CREATE TRIGGER statements.
** Whenever the CREATE TRIGGER keywords are seen, the statement
** must end with ";END;".
*/
int sqlite_complete(const char *zSql){
  int isComplete = 1;
  int requireEnd = 0;
  int seenText = 0;
  int seenCreate = 0;
  while( *zSql ){
    switch( *zSql ){
      case ';': {
        isComplete = 1;
        seenText = 1;
        seenCreate = 0;
        break;
      }
      case ' ':
      case '\t':
      case '\n':
      case '\f': {
        break;
      }
      case '[': {
        isComplete = 0;
        seenText = 1;
        seenCreate = 0;
        zSql++;
        while( *zSql && *zSql!=']' ){ zSql++; }
        if( *zSql==0 ) return 0;
        break;
      }
      case '"':
      case '\'': {
        int c = *zSql;
        isComplete = 0;


        seenText = 1;



        seenCreate = 0;
        zSql++;
        while( *zSql && *zSql!=c ){ zSql++; }
        if( *zSql==0 ) return 0;
        break;
      }
      case '-': {
        if( zSql[1]!='-' ){
          isComplete = 0;
          seenCreate = 0;
          break;
        }
        while( *zSql && *zSql!='\n' ){ zSql++; }
        if( *zSql==0 ) return seenText && isComplete && requireEnd==0;
        break;
      }

      case 'c':
      case 'C': {
        seenText = 1;
        if( !isComplete ) break;
        isComplete = 0;
        if( sqliteStrNICmp(zSql, "create", 6)!=0 ) break;

        if( !isspace(zSql[6]) ) break;
        zSql += 5;
        seenCreate = 1;
        while( isspace(zSql[1]) ) zSql++;
        if( sqliteStrNICmp(&zSql[1],"trigger", 7)!=0 ) break;
        zSql += 7;
        requireEnd++;
        break;
      }
      case 't':
      case 'T': {
        seenText = 1;
        if( !seenCreate ) break;
        seenCreate = 0;
        isComplete = 0;
        if( sqliteStrNICmp(zSql, "trigger", 7)!=0 ) break;
        if( !isspace(zSql[7]) ) break;
        zSql += 6;
        requireEnd++;
        break;
      }
      case 'e':
      case 'E': {
        seenCreate = 0;
        seenText = 1;
        if( !isComplete ) break;
        isComplete = 0;
        if( requireEnd==0 ) break;
        if( sqliteStrNICmp(zSql, "end", 3)!=0 ) break;
        zSql += 2;
        while( isspace(zSql[1]) ) zSql++;
        if( zSql[1]==';' ){
          zSql++;
          isComplete = 1;


          requireEnd--;
        }
        break;
      }
      default: {
        seenCreate = 0;
        seenText = 1;
        isComplete = 0;
        break;
      }
    }
    zSql++;
  }
  return seenText && isComplete && requireEnd==0;
}

/*
** Execute SQL code.  Return one of the SQLITE_ success/failure
** codes.  Also write an error message into memory obtained from
** malloc() and make *pzErrMsg point to that message.
**

Changes to src/parse.y.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
116
117
118
119
120
121
122

123
124
125
126
127
128
129
...
688
689
690
691
692
693
694
695
**
*************************************************************************
** 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.64 2002/05/15 08:30:14 danielk1977 Exp $
*/
%token_prefix TK_
%token_type {Token}
%default_type {Token}
%extra_argument {Parse *pParse}
%syntax_error {
  sqliteSetString(&pParse->zErrMsg,"syntax error",0);
................................................................................
id(A) ::= ID(X).         {A = X;}
id(A) ::= IGNORE(X).     {A = X;}
id(A) ::= KEY(X).        {A = X;}
id(A) ::= OFFSET(X).     {A = X;}
id(A) ::= PRAGMA(X).     {A = X;}
id(A) ::= REPLACE(X).    {A = X;}
id(A) ::= TEMP(X).       {A = X;}

id(A) ::= VACUUM(X).     {A = X;}
id(A) ::= VIEW(X).       {A = X;}

// And "ids" is an identifer-or-string.
//
%type ids {Token}
ids(A) ::= id(X).        {A = X;}
................................................................................
// SELECT
trigger_cmd(A) ::= select(X).  {A = sqliteTriggerSelectStep(X); }

////////////////////////  DROP TRIGGER statement //////////////////////////////
cmd ::= DROP TRIGGER ids(X). {
    sqliteDropTrigger(pParse,&X,0);
}








|







 







>







 







<
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
...
689
690
691
692
693
694
695

**
*************************************************************************
** 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.65 2002/05/15 14:17:45 drh Exp $
*/
%token_prefix TK_
%token_type {Token}
%default_type {Token}
%extra_argument {Parse *pParse}
%syntax_error {
  sqliteSetString(&pParse->zErrMsg,"syntax error",0);
................................................................................
id(A) ::= ID(X).         {A = X;}
id(A) ::= IGNORE(X).     {A = X;}
id(A) ::= KEY(X).        {A = X;}
id(A) ::= OFFSET(X).     {A = X;}
id(A) ::= PRAGMA(X).     {A = X;}
id(A) ::= REPLACE(X).    {A = X;}
id(A) ::= TEMP(X).       {A = X;}
id(A) ::= TRIGGER(X).    {A = X;}
id(A) ::= VACUUM(X).     {A = X;}
id(A) ::= VIEW(X).       {A = X;}

// And "ids" is an identifer-or-string.
//
%type ids {Token}
ids(A) ::= id(X).        {A = X;}
................................................................................
// SELECT
trigger_cmd(A) ::= select(X).  {A = sqliteTriggerSelectStep(X); }

////////////////////////  DROP TRIGGER statement //////////////////////////////
cmd ::= DROP TRIGGER ids(X). {
    sqliteDropTrigger(pParse,&X,0);
}

Changes to test/main.test.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
66
67
68
69
70
71
72

























































































73
74
75
76
77
78
79
#    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 SQLite library.  The
# focus of this file is exercising the code in main.c.
#
# $Id: main.test,v 1.10 2002/05/10 13:14:08 drh Exp $

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

# Tests of the sqlite_complete() function.
#
do_test main-1.1 {
................................................................................
} {1}
do_test main-1.14 {
  db complete {SELECT a-b FROM t1; }
} {1}
do_test main-1.15 {
  db complete {SELECT a-b FROM t1 }
} {0}


























































































# Try to open a database with a corrupt database file.
#
do_test main-2.0 {
  catch {db close}
  file delete -force test.db
  set fd [open test.db w]







|







 







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







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
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
117
118
119
120
121
122
123
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
#    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 SQLite library.  The
# focus of this file is exercising the code in main.c.
#
# $Id: main.test,v 1.11 2002/05/15 14:17:45 drh Exp $

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

# Tests of the sqlite_complete() function.
#
do_test main-1.1 {
................................................................................
} {1}
do_test main-1.14 {
  db complete {SELECT a-b FROM t1; }
} {1}
do_test main-1.15 {
  db complete {SELECT a-b FROM t1 }
} {0}
do_test main-1.16 {
  db complete {
    CREATE TABLE abc(x,y);
  }
} {1}
do_test main-1.17 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE abc BEGIN UPDATE pqr;
  }
} {0}
do_test main-1.18 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE abc BEGIN UPDATE pqr; END;
  }
} {1}
do_test main-1.19 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE abc BEGIN
       UPDATE pqr;
       unknown command;
  }
} {0}
do_test main-1.20 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE backend BEGIN
       UPDATE pqr;
  }
} {0}
do_test main-1.21 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE end BEGIN
       SELECT a, b FROM end;
  }
} {0}
do_test main-1.22 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE end BEGIN
       SELECT a, b FROM end;
    END;
  }
} {1}
do_test main-1.23 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE end BEGIN
       SELECT a, b FROM end;
    END;
    SELECT a, b FROM end;
  }
} {1}
do_test main-1.24 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE [;end;] BEGIN
       UPDATE pqr;
  }
} {0}
do_test main-1.25 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE backend BEGIN
       UPDATE pqr SET a=[;end;];;;
  }
} {0}
do_test main-1.26 {
  db complete {
    CREATE -- a comment
    TRIGGER xyz AFTER DELETE backend BEGIN
       UPDATE pqr SET a=5;
  }
} {0}
do_test main-1.27 {
  db complete {
    CREATE -- a comment
    TRIGGERX xyz AFTER DELETE backend BEGIN
       UPDATE pqr SET a=5;
  }
} {1}
do_test main-1.28 {
  db complete {
    CREATE TEMP TRIGGER xyz AFTER DELETE backend BEGIN
       UPDATE pqr SET a=5;
  }
} {1}
do_test main-1.29 {
  db complete {
    CREATE TRIGGER xyz AFTER DELETE backend BEGIN
       UPDATE pqr SET a=5;
       EXPLAIN select * from xyz;
  }
} {0}


# Try to open a database with a corrupt database file.
#
do_test main-2.0 {
  catch {db close}
  file delete -force test.db
  set fd [open test.db w]