diff --git a/manifest b/manifest index 1c0bb018fa..baccd627ce 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Added\sshell\scommand\s".databases"\sto\slist\sname\sand\sfile\sof\sopen\sones.\nAdded\sseveral\smissing\sshell\scommands.\s(CVS\s954) -D 2003-05-04T07:31:10 +C Fix\sdeficiencies\sin\ssqlite_complete()\spointed\sout\sby\sR.\sDennis\sCote.\s(CVS\s955) +D 2003-05-04T17:58:26 F Makefile.in 004acec253ecdde985c8ecd5b7c9accdb210378f F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -33,7 +33,7 @@ F src/func.c 882c3ed5a02be18cd904715c7ec62947a34a3605 F src/hash.c 4fc39feb7b7711f6495ee9f2159559bedb043e1f F src/hash.h cd0433998bc1a3759d244e1637fe5a3c13b53bf8 F src/insert.c c230a8c216f6788095d46f0e270406a93aae45af -F src/main.c 5265058c9a598b4714dc9e528152b81fcb31e7ad +F src/main.c 16e68905793b118552a9cf43a9f77ca1d9e395a9 F src/md5.c fe4f9c9c6f71dfc26af8da63e4d04489b1430565 F src/os.c 94b618c0c0a76210e53857d77c96d2f042dc33b1 F src/os.h 9e5bbddff123187295e3d00d49af06192cd1cd49 @@ -54,7 +54,7 @@ F src/test1.c 4596acd9d9f2a49fda0160a8a6dee5bfc7c6c325 F src/test2.c 5014337d8576b731cce5b5a14bec4f0daf432700 F src/test3.c 30985ebdfaf3ee1462a9b0652d3efbdc8d9798f5 F src/threadtest.c d641a5219e718e18a1a80a50eb9bb549f451f42e -F src/tokenize.c 067d1a477a94af7712ca74e09aaa6bd0f7299527 +F src/tokenize.c 65b99ca0c4b8bc89f9d4530762288eea7c5bca62 F src/trigger.c 8ee811986080de60d9d883ad96daffea82014f27 F src/update.c dc3b680b4cf2cb6d839950b68d632850746639b9 F src/util.c 87635cfdfffa056a8d3147719357aa442374f78c @@ -90,7 +90,7 @@ F test/ioerr.test 5dbaf09f96b56ee01cf3edd762b96eb4ad2c9ca4 F test/join.test c97267c19294bf1fa4e81087edad179828bced88 F test/limit.test 9ffb965a0f5bf7152187ef3d8d1249b96e5620bf F test/lock.test 388a3a10962d2d571c0c1821cc35bf069ee73473 -F test/main.test a028743affca67670e24c97527d1f4ad4bc2aad3 +F test/main.test 6a851b5992c4881a725a3d9647e629199df8de9d F test/malloc.test 7ba32a9ebd3aeed52ae4aaa6d42ca37e444536fd F test/memdb.test 3acf1453a5705d1aa524d3027667ca3d9c77c69f F test/memleak.test a18e6810cae96d2f6f5136920267adbefc8e1e90 @@ -165,7 +165,7 @@ F www/speed.tcl cb4c10a722614aea76d2c51f32ee43400d5951be F www/sqlite.tcl ffde644361e1d8e2a44a235ff23ad3b43d640df2 F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331 F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218 -P 741a5a8d3975fb5db18914b7879b12aead59279b -R dd130a1f7dd3e8a6528c790b087edc44 -U jplyon -Z 8e9e207bfa04e253d2be1e2c73c48742 +P dd57d6ae6a247824e44a6073bc7e73ecb3c500fd +R afd858f78403413353ccd46911cf14bc +U drh +Z 317ec3dddb2f5501f438a98dd3716c3d diff --git a/manifest.uuid b/manifest.uuid index f23493adb5..209be605fb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dd57d6ae6a247824e44a6073bc7e73ecb3c500fd \ No newline at end of file +54b33a5ed9f7a89435c2f1395a3177e8c778bb8a \ No newline at end of file diff --git a/src/main.c b/src/main.c index f02c61de22..d7c71f7e51 100644 --- a/src/main.c +++ b/src/main.c @@ -14,7 +14,7 @@ ** other files are for internal use by SQLite and should not be ** accessed by users of the library. ** -** $Id: main.c,v 1.129 2003/04/29 16:20:46 drh Exp $ +** $Id: main.c,v 1.130 2003/05/04 17:58:26 drh Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -553,132 +553,6 @@ void sqlite_close(sqlite *db){ 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 '\r': - 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; - seenText = 1; - seenCreate = 0; - break; - } - zSql += 2; - while( zSql[0] && (zSql[0]!='*' || zSql[1]!='/') ){ zSql++; } - if( zSql[0]==0 ) return 0; - zSql += 2; - 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; -} - /* ** Rollback all database files. */ diff --git a/src/tokenize.c b/src/tokenize.c index 290c84a2ae..652a96596d 100644 --- a/src/tokenize.c +++ b/src/tokenize.c @@ -15,7 +15,7 @@ ** individual tokens and sends those tokens one-by-one over to the ** parser for analysis. ** -** $Id: tokenize.c,v 1.58 2003/04/21 18:48:47 drh Exp $ +** $Id: tokenize.c,v 1.59 2003/05/04 17:58:26 drh Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -496,3 +496,189 @@ abort_parse: } return nErr; } + +/* +** Token types used by the sqlite_complete() routine. See the header +** comments on that procedure for additional information. +*/ +#define tkEXPLAIN 0 +#define tkCREATE 1 +#define tkTEMP 2 +#define tkTRIGGER 3 +#define tkEND 4 +#define tkSEMI 5 +#define tkWS 6 +#define tkOTHER 7 + +/* +** 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;". +** +** This implementation uses a state machine with 7 states: +** +** (0) START At the beginning or end of an SQL statement. This routine +** returns 1 if it ends in the START state and 0 if it ends +** in any other state. +** +** (1) EXPLAIN The keyword EXPLAIN has been seen at the beginning of +** a statement. +** +** (2) CREATE The keyword CREATE has been seen at the beginning of a +** statement, possibly preceeded by EXPLAIN and/or followed by +** TEMP or TEMPORARY +** +** (3) NORMAL We are in the middle of statement which ends with a single +** semicolon. +** +** (4) TRIGGER We are in the middle of a trigger definition that must be +** ended by a semicolon, the keyword END, and another semicolon. +** +** (5) SEMI We've seen the first semicolon in the ";END;" that occurs at +** the end of a trigger definition. +** +** (6) END We've seen the ";END" of the ";END;" that occurs at the end +** of a trigger difinition. +** +** Transitions between states above are determined by tokens extracted +** from the input. The following tokens are significant: +** +** (0) tkEXPLAIN The "explain" keyword. +** (1) tkCREATE The "create" keyword. +** (2) tkTEMP The "temp" or "temporary" keyword. +** (3) tkTRIGGER The "trigger" keyword. +** (4) tkEND The "end" keyword. +** (5) tkSEMI A semicolon. +** (6) tkWS Whitespace +** (7) tkOTHER Any other SQL token. +** +** Whitespace never causes a state transition and is always ignored. +*/ +int sqlite_complete(const char *zSql){ + u8 state = 0; /* Current state, using numbers defined in header comment */ + u8 token; /* Value of the next token */ + + /* The following matrix defines the transition from one state to another + ** according to what token is seen. trans[state][token] returns the + ** next state. + */ + static const u8 trans[7][8] = { + /* Token: + /* State: ** EXPLAIN CREATE TEMP TRIGGER END SEMI WS OTHER */ + /* 0 START: */ { 1, 2, 3, 3, 3, 0, 0, 3, }, + /* 1 EXPLAIN: */ { 3, 2, 3, 3, 3, 0, 1, 3, }, + /* 2 CREATE: */ { 3, 3, 2, 4, 3, 0, 2, 3, }, + /* 3 NORMAL: */ { 3, 3, 3, 3, 3, 0, 3, 3, }, + /* 4 TRIGGER: */ { 4, 4, 4, 4, 4, 5, 4, 4, }, + /* 5 SEMI: */ { 4, 4, 4, 4, 6, 5, 5, 4, }, + /* 6 END: */ { 4, 4, 4, 4, 4, 0, 6, 4, }, + }; + + while( *zSql ){ + switch( *zSql ){ + case ';': { /* A semicolon */ + token = tkSEMI; + break; + } + case ' ': + case '\r': + case '\t': + case '\n': + case '\f': { /* White space is ignored */ + token = tkWS; + break; + } + case '/': { /* C-style comments */ + if( zSql[1]!='*' ){ + token = tkOTHER; + break; + } + zSql += 2; + while( zSql[0] && (zSql[0]!='*' || zSql[1]!='/') ){ zSql++; } + if( zSql[0]==0 ) return 0; + zSql++; + token = tkWS; + break; + } + case '-': { /* SQL-style comments from "--" to end of line */ + if( zSql[1]!='-' ){ + token = tkOTHER; + break; + } + while( *zSql && *zSql!='\n' ){ zSql++; } + if( *zSql==0 ) return state==0; + token = tkWS; + break; + } + case '[': { /* Microsoft-style identifiers in [...] */ + zSql++; + while( *zSql && *zSql!=']' ){ zSql++; } + if( *zSql==0 ) return 0; + token = tkOTHER; + break; + } + case '"': /* single- and double-quoted strings */ + case '\'': { + int c = *zSql; + zSql++; + while( *zSql && *zSql!=c ){ zSql++; } + if( *zSql==0 ) return 0; + token = tkOTHER; + break; + } + default: { + if( isIdChar[*zSql] ){ + /* Keywords and unquoted identifiers */ + int nId; + for(nId=1; isIdChar[zSql[nId]]; nId++){} + switch( *zSql ){ + case 'c': case 'C': { + if( nId==6 && sqliteStrNICmp(zSql, "create", 6)==0 ){ + token = tkCREATE; + }else{ + token = tkOTHER; + } + break; + } + case 't': case 'T': { + if( nId==7 && sqliteStrNICmp(zSql, "trigger", 7)==0 ){ + token = tkTRIGGER; + }else if( nId==4 && sqliteStrNICmp(zSql, "temp", 4)==0 ){ + token = tkTEMP; + }else if( nId==9 && sqliteStrNICmp(zSql, "temporary", 9)==0 ){ + token = tkTEMP; + }else{ + token = tkOTHER; + } + break; + } + case 'e': case 'E': { + if( nId==3 && sqliteStrNICmp(zSql, "end", 3)==0 ){ + token = tkEND; + }else if( nId==7 && sqliteStrNICmp(zSql, "explain", 7)==0 ){ + token = tkEXPLAIN; + }else{ + token = tkOTHER; + } + break; + } + default: { + token = tkOTHER; + break; + } + } + zSql += nId-1; + }else{ + /* Operators and special symbols */ + token = tkOTHER; + } + break; + } + } + state = trans[state][token]; + zSql++; + } + return state==0; +} diff --git a/test/main.test b/test/main.test index 9efbe31063..2b98ace0c3 100644 --- a/test/main.test +++ b/test/main.test @@ -11,7 +11,7 @@ # 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.13 2003/04/26 02:31:54 drh Exp $ +# $Id: main.test,v 1.14 2003/05/04 17:58:27 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -138,19 +138,46 @@ do_test main-1.26 { UPDATE pqr SET a=5; } } {0} -do_test main-1.27 { +do_test main-1.27.1 { db complete { CREATE -- a comment TRIGGERX xyz AFTER DELETE backend BEGIN UPDATE pqr SET a=5; } } {1} +do_test main-1.27.2 { + db complete { + CREATE/**/TRIGGER xyz AFTER DELETE backend BEGIN + UPDATE pqr SET a=5; + } +} {0} +do_test main-1.27.3 { + db complete { + /* */ EXPLAIN -- A comment + CREATE/**/TRIGGER xyz AFTER DELETE backend BEGIN + UPDATE pqr SET a=5; + } +} {0} +do_test main-1.27.4 { + db complete { + BOGUS token + CREATE TRIGGER xyz AFTER DELETE backend BEGIN + UPDATE pqr SET a=5; + } +} {1} +do_test main-1.27.5 { + db complete { + EXPLAIN + CREATE TEMP TRIGGER xyz AFTER DELETE backend BEGIN + UPDATE pqr SET a=5; + } +} {0} do_test main-1.28 { db complete { CREATE TEMP TRIGGER xyz AFTER DELETE backend BEGIN UPDATE pqr SET a=5; } -} {1} +} {0} do_test main-1.29 { db complete { CREATE TRIGGER xyz AFTER DELETE backend BEGIN @@ -168,6 +195,11 @@ do_test main-1.31 { CREATE TABLE /* In comment ; */ hi; } } {1} +do_test main-1.31 { + db complete { + CREATE TABLE /* In comment ; */; + } +} {1} do_test main-1.32 { db complete { stuff; @@ -197,6 +229,9 @@ do_test main-1.34 { do_test main-1.35 { db complete {hi /**/ there;} } {1} +do_test main-1.36 { + db complete {hi there/***/;} +} {1} # Try to open a database with a corrupt database file.