]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the ESCAPE clause to the LIKE operator. Not fully tested yet. (CVS 2107)
authordanielk1977 <danielk1977@noemail.net>
Wed, 17 Nov 2004 16:41:29 +0000 (16:41 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Wed, 17 Nov 2004 16:41:29 +0000 (16:41 +0000)
FossilOrigin-Name: 49268c2b7a84c4c618214dac8bef0f541440fe6b

manifest
manifest.uuid
src/func.c
src/parse.y
test/expr.test
test/lock4.test [new file with mode: 0644]
tool/mkkeywordhash.c

index 64fbf45085bb742541a0c7db8fa660505e6899cf..5003cf12d1f0ca6cc2ad38981a41c2aec0ae1490 100644 (file)
--- 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
index 8a18be21059f394a30d2b7a15af111e71ddb8ba4..22885cbb5837b6a420a64e0b319e8d13b22846e0 100644 (file)
@@ -1 +1 @@
-e1530854c9004c25f5ffa21f9cfb9c44c83cc7f0
\ No newline at end of file
+49268c2b7a84c4c618214dac8bef0f541440fe6b
\ No newline at end of file
index 067fa8d9824c8d8ac88d404c2b4d70c38c7bffc9..bc65e7f6ad41042fe51691256cbdfb3df7fbe1c3 100644 (file)
@@ -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 <ctype.h>
 #include <math.h>
@@ -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},
index c223d232ac9759b5f0c43fadfe14c5824088dd0e..e0d27019746ccf1abb8760b4812cbcee30da650d 100644 (file)
@@ -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);
index a3100e71b81e5841c344914aa046f5b413597935..8bc66c432f50f3c5468b3150de3936a0d4a1da6f 100644 (file)
@@ -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 (file)
index 0000000..835bb53
--- /dev/null
@@ -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
+
index 57e0201b928f83a6d6cb38d7d6e2ad7ad185f4e9..3ab67f5833a97d41ed550007020faa7f8b5fa994 100644 (file)
@@ -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                },