From 7c6303c042a8faa25dcb822d4baecf3d07c5c261 Mon Sep 17 00:00:00 2001 From: danielk1977 Date: Wed, 17 Nov 2004 16:41:29 +0000 Subject: [PATCH] Add the ESCAPE clause to the LIKE operator. Not fully tested yet. (CVS 2107) FossilOrigin-Name: 49268c2b7a84c4c618214dac8bef0f541440fe6b --- manifest | 19 +++---- manifest.uuid | 2 +- src/func.c | 47 +++++++++++++---- src/parse.y | 12 ++++- test/expr.test | 41 ++++++++++++++- test/lock4.test | 117 +++++++++++++++++++++++++++++++++++++++++++ tool/mkkeywordhash.c | 1 + 7 files changed, 216 insertions(+), 23 deletions(-) create mode 100644 test/lock4.test diff --git a/manifest b/manifest index 64fbf45085..5003cf12d1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Extra\stests\sand\sresulting\sbugfixes\sfor\sbtree\scursors.\s(CVS\s2106) -D 2004-11-17T10:22:03 +C Add\sthe\sESCAPE\sclause\sto\sthe\sLIKE\soperator.\sNot\sfully\stested\syet.\s(CVS\s2107) +D 2004-11-17T16:41:29 F Makefile.in e747bb5ba34ccbdd81f79dcf1b2b33c02817c21d F Makefile.linux-gcc a9e5a0d309fa7c38e7c14d3ecf7690879d3a5457 F README a01693e454a00cc117967e3f9fdab2d4d52e9bc1 @@ -35,7 +35,7 @@ F src/build.c a95eb1181247368b0ffe2eed121a43735976a964 F src/date.c 65536e7ea04fdde6e0551264fca15966966e171f F src/delete.c be9d039b819f4a5d0fdfaeceace139ba189ef819 F src/expr.c 4ee3e47358c92a919062255b14057a7a8f641e01 -F src/func.c 181ea4b8bbc621457838494a440d2e4e2307ab70 +F src/func.c 897c1c130af08b29cdd89dd89f8c1832bab766b4 F src/hash.c a97721a55440b7bea31ffe471bb2f6b4123cddd5 F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84 F src/insert.c 9524a6c3e86cbdbae3313f6a083bb9a3e7a2462b @@ -54,7 +54,7 @@ F src/os_win.c 9482dfc92f289b68205bb2c9315757c7e3946bfb F src/os_win.h 41a946bea10f61c158ce8645e7646b29d44f122b F src/pager.c ee88fcecb081e3635c281bc09d604e934429e2f5 F src/pager.h 9eba8c53dd91eae7f3f90743b2ee242da02a9862 -F src/parse.y 3282026b619e1c7f932fd8ecef9627fa86da048a +F src/parse.y 0a4bdfd7b65d9761b41a862d09a17c90c1f526f7 F src/pragma.c 0b43b8cac4870bfa041bf2ca29d7ce47b76362d6 F src/printf.c 3d20b21cfecadacecac3fb7274e746cb81d3d357 F src/random.c eff68e3f257e05e81eae6c4d50a51eb88beb4ff3 @@ -122,7 +122,7 @@ F test/diskfull.test e2f6cfd868713ead06dc82b84a4938e868128fc0 F test/enc.test 7a03417a1051fe8bc6c7641cf4c8c3f7e0066d52 F test/enc2.test 6d1a2650e9da43eab499d18ca694a0cb6ec69dee F test/enc3.test f6a5f0b7b7f3a88f030d3143729b87cd5c86d837 -F test/expr.test 8a96b21644b9702cabc3695f2b73ae0861376765 +F test/expr.test bf826516ea0ba159eb9680fbcea955148bfe9bc3 F test/fkey1.test 81bb13caaa78f58d7d191d7f535529f7c91d821a F test/func.test 830d352574c7f5cd15149a9be58a6dcc2b995c05 F test/hook.test f8605cde4c77b2c6a4a73723bf6c507796a64dda @@ -143,6 +143,7 @@ F test/limit.test f7c06fccd76755e8d083b61c06bc31cf461b9c35 F test/lock.test ba72c211499b0874c56643b9ede1df4018bb20de F test/lock2.test 59c3dd7d9b24d1bf7ec91b2d1541c37e97939d5f F test/lock3.test 615111293cf32aa2ed16d01c6611737651c96fb9 +F test/lock4.test 07768b4d4e942693d6036f1e6502199a3fa22a4f F test/main.test 5f9deae11b93336da1ccc5f91cf8be075c91ddf1 F test/malloc.test 769b240d89a7ef3320d88919fdb6765f9395a51f F test/memdb.test 34ee8598de307a16ccc3ac91b85cee9c668ae5ed @@ -206,7 +207,7 @@ F tool/lempar.c 1e61d2b6cb9d8affa264a13336bc0c088498caa4 F tool/memleak.awk b744b6109566206c746d826f6ecdba34662216bc F tool/memleak2.awk 9cc20c8e8f3c675efac71ea0721ee6874a1566e8 F tool/memleak3.tcl 336eb50b0849dbf99b1d5462d9c37291b01b2b43 -F tool/mkkeywordhash.c 5f0d8bd4928e84e736469f9c989dae239314138e +F tool/mkkeywordhash.c c2254c191456316ce5d3f72a6b44fbf3c6492816 F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e x F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c F tool/report1.txt 9eae07f26a8fc53889b45fc833a66a33daa22816 @@ -258,7 +259,7 @@ F www/tclsqlite.tcl 560ecd6a916b320e59f2917317398f3d59b7cc25 F www/vdbe.tcl 095f106d93875c94b47367384ebc870517431618 F www/version3.tcl 092a01f5ef430d2c4acc0ae558d74c4bb89638a0 F www/whentouse.tcl fdacb0ba2d39831e8a6240d05a490026ad4c4e4c -P e05f52d907e267b4f9ea204427229e7d7ef58641 -R 41b0c2001282ed7479703649a89b1f7a +P e1530854c9004c25f5ffa21f9cfb9c44c83cc7f0 +R fc41b89835631b13a4149106e5db9d17 U danielk1977 -Z f5df5e3af5cd05954b652b1bc18662f8 +Z a4a4e7382bd227dede1f241a7eed092d diff --git a/manifest.uuid b/manifest.uuid index 8a18be2105..22885cbb58 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e1530854c9004c25f5ffa21f9cfb9c44c83cc7f0 \ No newline at end of file +49268c2b7a84c4c618214dac8bef0f541440fe6b \ No newline at end of file diff --git a/src/func.c b/src/func.c index 067fa8d982..bc65e7f6ad 100644 --- a/src/func.c +++ b/src/func.c @@ -16,7 +16,7 @@ ** sqliteRegisterBuildinFunctions() found at the bottom of the file. ** All other code has file scope. ** -** $Id: func.c,v 1.87 2004/11/14 21:56:30 drh Exp $ +** $Id: func.c,v 1.88 2004/11/17 16:41:30 danielk1977 Exp $ */ #include #include @@ -347,10 +347,11 @@ static const struct compareInfo likeInfo = { '%', '_', 0, 1 }; ** ** abc[*]xyz Matches "abc*xyz" only */ -int patternCompare( +static int patternCompare( const u8 *zPattern, /* The glob pattern */ const u8 *zString, /* The string to compare against the glob */ - const struct compareInfo *pInfo /* Information about how to do the compare */ + const struct compareInfo *pInfo, /* Information about how to do the compare */ + const int esc /* The escape character */ ){ register int c; int invert; @@ -360,9 +361,10 @@ int patternCompare( u8 matchAll = pInfo->matchAll; u8 matchSet = pInfo->matchSet; u8 noCase = pInfo->noCase; + int prevEscape = 0; /* True if the previous character was 'escape' */ while( (c = *zPattern)!=0 ){ - if( c==matchAll ){ + if( !prevEscape && c==matchAll ){ while( (c=zPattern[1]) == matchAll || c == matchOne ){ if( c==matchOne ){ if( *zString==0 ) return 0; @@ -370,9 +372,15 @@ int patternCompare( } zPattern++; } + if( c && sqlite3ReadUtf8(&zPattern[1])==esc ){ + u8 const *zTemp = &zPattern[1]; + sqliteNextChar(zTemp); + c = *zTemp; + } if( c==0 ) return 1; if( c==matchSet ){ - while( *zString && patternCompare(&zPattern[1],zString,pInfo)==0 ){ + assert( esc==0 ); /* This is GLOB, not LIKE */ + while( *zString && patternCompare(&zPattern[1],zString,pInfo,esc)==0 ){ sqliteNextChar(zString); } return *zString!=0; @@ -386,17 +394,18 @@ int patternCompare( while( c2 != 0 && c2 != c ){ c2 = *++zString; } } if( c2==0 ) return 0; - if( patternCompare(&zPattern[1],zString,pInfo) ) return 1; + if( patternCompare(&zPattern[1],zString,pInfo,esc) ) return 1; sqliteNextChar(zString); } return 0; } - }else if( c==matchOne ){ + }else if( !prevEscape && c==matchOne ){ if( *zString==0 ) return 0; sqliteNextChar(zString); zPattern++; }else if( c==matchSet ){ int prior_c = 0; + assert( esc==0 ); /* This only occurs for GLOB, not LIKE */ seen = 0; invert = 0; c = sqliteCharVal(zString); @@ -424,6 +433,9 @@ int patternCompare( if( c2==0 || (seen ^ invert)==0 ) return 0; sqliteNextChar(zString); zPattern++; + }else if( !prevEscape && sqlite3ReadUtf8(zPattern)==esc){ + prevEscape = 1; + sqliteNextChar(zPattern); }else{ if( noCase ){ if( sqlite3UpperToLower[c] != sqlite3UpperToLower[*zString] ) return 0; @@ -432,6 +444,7 @@ int patternCompare( } zPattern++; zString++; + prevEscape = 0; } } return *zString==0; @@ -457,8 +470,21 @@ static void likeFunc( ){ const unsigned char *zA = sqlite3_value_text(argv[0]); const unsigned char *zB = sqlite3_value_text(argv[1]); + int escape = 0; + if( argc==3 ){ + /* The escape character string must consist of a single UTF-8 character. + ** Otherwise, return an error. + */ + const unsigned char *zEsc = sqlite3_value_text(argv[2]); + if( sqlite3utf8CharLen(zEsc, -1)!=1 ){ + sqlite3_result_error(context, + "ESCAPE expression must be a single character", -1); + return; + } + escape = sqlite3ReadUtf8(zEsc); + } if( zA && zB ){ - sqlite3_result_int(context, patternCompare(zA, zB, &likeInfo)); + sqlite3_result_int(context, patternCompare(zA, zB, &likeInfo, escape)); } } @@ -469,13 +495,13 @@ static void likeFunc( ** ** A GLOB B ** -** is implemented as glob(A,B). +** is implemented as glob(B,A). */ static void globFunc(sqlite3_context *context, int arg, sqlite3_value **argv){ const unsigned char *zA = sqlite3_value_text(argv[0]); const unsigned char *zB = sqlite3_value_text(argv[1]); if( zA && zB ){ - sqlite3_result_int(context, patternCompare(zA, zB, &globInfo)); + sqlite3_result_int(context, patternCompare(zA, zB, &globInfo, 0)); } } @@ -992,6 +1018,7 @@ void sqlite3RegisterBuiltinFunctions(sqlite3 *db){ { "ifnull", 2, 0, SQLITE_UTF8, 1, ifnullFunc }, { "random", -1, 0, SQLITE_UTF8, 0, randomFunc }, { "like", 2, 0, SQLITE_UTF8, 0, likeFunc }, + { "like", 3, 0, SQLITE_UTF8, 0, likeFunc }, { "glob", 2, 0, SQLITE_UTF8, 0, globFunc }, { "nullif", 2, 0, SQLITE_UTF8, 1, nullifFunc }, { "sqlite_version", 0, 0, SQLITE_UTF8, 0, versionFunc}, diff --git a/src/parse.y b/src/parse.y index c223d232ac..e0d2701974 100644 --- a/src/parse.y +++ b/src/parse.y @@ -14,7 +14,7 @@ ** the parser. Lemon will also generate a header file containing ** numeric codes for all of the tokens. ** -** @(#) $Id: parse.y,v 1.156 2004/11/13 15:59:15 drh Exp $ +** @(#) $Id: parse.y,v 1.157 2004/11/17 16:41:30 danielk1977 Exp $ */ %token_prefix TK_ %token_type {Token} @@ -169,6 +169,7 @@ id(A) ::= ID(X). {A = X;} %right NOT. %left IS LIKE GLOB BETWEEN IN ISNULL NOTNULL NE EQ. %left GT LE LT GE. +%right ESCAPE. %left BITAND BITOR LSHIFT RSHIFT. %left PLUS MINUS. %left STAR SLASH REM. @@ -637,14 +638,21 @@ likeop(A) ::= LIKE. {A.opcode = TK_LIKE; A.not = 0;} likeop(A) ::= GLOB. {A.opcode = TK_GLOB; A.not = 0;} likeop(A) ::= NOT LIKE. {A.opcode = TK_LIKE; A.not = 1;} likeop(A) ::= NOT GLOB. {A.opcode = TK_GLOB; A.not = 1;} -expr(A) ::= expr(X) likeop(OP) expr(Y). [LIKE] { +%type escape {Expr*} +escape(X) ::= ESCAPE expr(A). [ESCAPE] {X = A;} +escape(X) ::= . [ESCAPE] {X = 0;} +expr(A) ::= expr(X) likeop(OP) expr(Y) escape(E). [LIKE] { ExprList *pList = sqlite3ExprListAppend(0, Y, 0); pList = sqlite3ExprListAppend(pList, X, 0); + if( E ){ + pList = sqlite3ExprListAppend(pList, E, 0); + } A = sqlite3ExprFunction(pList, 0); if( A ) A->op = OP.opcode; if( OP.not ) A = sqlite3Expr(TK_NOT, A, 0, 0); sqlite3ExprSpan(A, &X->span, &Y->span); } + expr(A) ::= expr(X) ISNULL(E). { A = sqlite3Expr(TK_ISNULL, X, 0, 0); sqlite3ExprSpan(A,&X->span,&E); diff --git a/test/expr.test b/test/expr.test index a3100e71b8..8bc66c432f 100644 --- a/test/expr.test +++ b/test/expr.test @@ -11,7 +11,7 @@ # This file implements regression tests for SQLite library. The # focus of this file is testing expressions. # -# $Id: expr.test,v 1.39 2004/11/15 01:40:48 drh Exp $ +# $Id: expr.test,v 1.40 2004/11/17 16:41:29 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -267,6 +267,45 @@ test_expr expr-5.55 {t1='abc', t2=NULL} {t1 NOT LIKE t2} {{}} test_expr expr-5.56 {t1='abc', t2=NULL} {t2 LIKE t1} {{}} test_expr expr-5.57 {t1='abc', t2=NULL} {t2 NOT LIKE t1} {{}} +# LIKE expressions that use ESCAPE characters. +test_expr expr-5.58 {t1='abc', t2='A_C'} {t1 LIKE t2 ESCAPE '7'} 1 +test_expr expr-5.59 {t1='a_c', t2='A7_C'} {t1 LIKE t2 ESCAPE '7'} 1 +test_expr expr-5.60 {t1='abc', t2='A7_C'} {t1 LIKE t2 ESCAPE '7'} 0 +test_expr expr-5.61 {t1='a7Xc', t2='A7_C'} {t1 LIKE t2 ESCAPE '7'} 0 +test_expr expr-5.62 {t1='abcde', t2='A%E'} {t1 LIKE t2 ESCAPE '7'} 1 +test_expr expr-5.63 {t1='abcde', t2='A7%E'} {t1 LIKE t2 ESCAPE '7'} 0 +test_expr expr-5.64 {t1='a7cde', t2='A7%E'} {t1 LIKE t2 ESCAPE '7'} 0 +test_expr expr-5.65 {t1='a7cde', t2='A77%E'} {t1 LIKE t2 ESCAPE '7'} 1 +test_expr expr-5.66 {t1='abc7', t2='A%77'} {t1 LIKE t2 ESCAPE '7'} 1 +test_expr expr-5.67 {t1='abc_', t2='A%7_'} {t1 LIKE t2 ESCAPE '7'} 1 +test_expr expr-5.68 {t1='abc7', t2='A%7_'} {t1 LIKE t2 ESCAPE '7'} 0 + +# These are the same test as the block above, but using a multi-byte +# character as the escape character. +if {"\u1234"!="u1234"} { + test_expr expr-5.69 "t1='abc', t2='A_C'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 1 + test_expr expr-5.70 "t1='a_c', t2='A\u1234_C'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 1 + test_expr expr-5.71 "t1='abc', t2='A\u1234_C'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 0 + test_expr expr-5.72 "t1='a\u1234Xc', t2='A\u1234_C'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 0 + test_expr expr-5.73 "t1='abcde', t2='A%E'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 1 + test_expr expr-5.74 "t1='abcde', t2='A\u1234%E'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 0 + test_expr expr-5.75 "t1='a\u1234cde', t2='A\u1234%E'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 0 + test_expr expr-5.76 "t1='a\u1234cde', t2='A\u1234\u1234%E'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 1 + test_expr expr-5.77 "t1='abc\u1234', t2='A%\u1234\u1234'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 1 + test_expr expr-5.78 "t1='abc_', t2='A%\u1234_'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 1 + test_expr expr-5.79 "t1='abc\u1234', t2='A%\u1234_'" \ + "t1 LIKE t2 ESCAPE '\u1234'" 0 +} test_expr expr-6.1 {t1='abc', t2='xyz'} {t1 GLOB t2} 0 test_expr expr-6.2 {t1='abc', t2='ABC'} {t1 GLOB t2} 0 diff --git a/test/lock4.test b/test/lock4.test new file mode 100644 index 0000000000..835bb53b19 --- /dev/null +++ b/test/lock4.test @@ -0,0 +1,117 @@ +# 2001 September 15 +# +# 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 SQLite library. The focus +# of this file is modifications made to tables while SELECT queries are +# active on the tables. Using this capability in a program is tricky +# because results can be difficult to predict, but can be useful. +# +# $Id: lock4.test,v 1.1 2004/11/17 16:41:29 danielk1977 Exp $ +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test lock4-1.0 { + execsql { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + } +} {} + +# Check that we can INSERT into a table while doing a SELECT on it. +do_test lock4-1.1 { + db eval {SELECT * FROM t1} { + if {$a<5} { + execsql "INSERT INTO t1 VALUES($a+1, ($a+1)*2)" + } + } +} {} +do_test lock4-1.2 { + execsql { + SELECT * FROM t1 + } +} {1 2 2 4 3 6 4 8 5 10} + +# Check that we can UPDATE a table while doing a SELECT on it. +do_test lock4-1.3 { + db eval {SELECT * FROM t1 WHERE (a%2)=0} { + execsql "UPDATE t1 SET b = b/2 WHERE a = $a" + } +} {} +do_test lock4-1.4 { + execsql { + SELECT * FROM t1 + } +} {1 2 2 2 3 6 4 4 5 10} + +# Check that we can DELETE from a table while doing a SELECT on it. +do_test lock4-1.5 { + db eval {SELECT * FROM t1 WHERE (a%2)=0} { + execsql "DELETE FROM t1 WHERE a = $a" + } +} {} +do_test lock4-1.6 { + execsql { + SELECT * FROM t1 + } +} {1 2 3 6 5 10} + +# Check what happens when a row is deleted while a cursor is still using +# the row (because of a SELECT that does a join). +do_test lock4-2.0 { + execsql { + CREATE TABLE t2(c); + INSERT INTO t2 VALUES('one'); + INSERT INTO t2 VALUES('two'); + } +} {} +do_test lock4-2.1 { + set res [list] + db eval {SELECT a, b, c FROM t1, t2} { + lappend res $a $b $c + if {0==[string compare $c one]} { + execsql "DELETE FROM t1 WHERE a = $a" + } + } + set res +} {1 2 one 1 2 two 3 6 one 3 6 two 5 10 one 5 10 two} +do_test lock4-2.2 { + execsql { + SELECT * FROM t1; + } +} {} + +# do_test lock4-2.3 { +# execsql " +# INSERT INTO t1 VALUES('[string repeat 1 750]', '[string repeat 2 750]') +# " +# } {} +# do_test lock4-2.4 { +# set res [list] +# db eval {SELECT a, b, c FROM t1, t2} { +# lappend res $a $b $c +# if {0==[string compare $c one]} { +# execsql "DELETE FROM t1 WHERE a = '$a'" +# } +# } +# set res +# } [list \ +# [string repeat 1 750] [string repeat 2 750] one \ +# [string repeat 1 750] [string repeat 2 750] two +# ] +# do_test lock4-2.5 { +# execsql { +# SELECT * FROM t1; +# } +# } {} + +finish_test + diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c index 57e0201b92..3ab67f5833 100644 --- a/tool/mkkeywordhash.c +++ b/tool/mkkeywordhash.c @@ -133,6 +133,7 @@ static Keyword aKeywordTable[] = { { "END", "TK_END", ALWAYS }, { "EACH", "TK_EACH", TRIGGER }, { "ELSE", "TK_ELSE", ALWAYS }, + { "ESCAPE", "TK_ESCAPE", ALWAYS }, { "EXCEPT", "TK_EXCEPT", COMPOUND }, { "EXCLUSIVE", "TK_EXCLUSIVE", ALWAYS }, { "EXPLAIN", "TK_EXPLAIN", EXPLAIN },