You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by br...@apache.org on 2012/11/10 16:14:47 UTC

svn commit: r1407831 - in /subversion/branches/wc-collate-path/subversion: include/private/svn_utf_private.h include/svn_error_codes.h libsvn_subr/utf8proc.c tests/libsvn_subr/utf-test.c

Author: brane
Date: Sat Nov 10 15:14:46 2012
New Revision: 1407831

URL: http://svn.apache.org/viewvc?rev=1407831&view=rev
Log:
On the wc-collate-path branch: Implement sqlite's LIKE and GLOB operators.

* subversion/include/svn_error_codes.h (SVN_ERR_UTF8_GLOB): New error code.
* subversion/include/private/svn_utf_private.h (svn_utf__glob):
   Change prototype; add another temporary buffer parameter.

* subversion/libsvn_subr/utf8proc.c: Imclude apr_fnmatch.h.
  (encode_ucs4): Reverse parameters to be more consistent with stringbuf API.
  (svn_utf__glob): Update prototype and implement.

* subversion/tests/libsvn_subr/utf-test.c (test_utf_pattern_match) New test.
  (test_funcs): Register test_utf_pattern_match.

Modified:
    subversion/branches/wc-collate-path/subversion/include/private/svn_utf_private.h
    subversion/branches/wc-collate-path/subversion/include/svn_error_codes.h
    subversion/branches/wc-collate-path/subversion/libsvn_subr/utf8proc.c
    subversion/branches/wc-collate-path/subversion/tests/libsvn_subr/utf-test.c

Modified: subversion/branches/wc-collate-path/subversion/include/private/svn_utf_private.h
URL: http://svn.apache.org/viewvc/subversion/branches/wc-collate-path/subversion/include/private/svn_utf_private.h?rev=1407831&r1=1407830&r2=1407831&view=diff
==============================================================================
--- subversion/branches/wc-collate-path/subversion/include/private/svn_utf_private.h (original)
+++ subversion/branches/wc-collate-path/subversion/include/private/svn_utf_private.h Sat Nov 10 15:14:46 2012
@@ -110,7 +110,7 @@ svn_utf__normcmp(const char *str1, apr_s
  * null-terminated; otherwise, consider the string only up to the
  * given length.
  *
- * Use buffers BUF1 and BUF2 for temporary storage.
+ * Use buffers PATTERN_BUF, STRING_BUF and TEMP_BUF for temporary storage.
  *
  * If SQL_LIKE is true, interpret PATTERN as a pattern used by the SQL
  * LIKE operator and notice ESCAPE. Otherwise it's a Unix fileglob
@@ -122,8 +122,11 @@ svn_error_t *
 svn_utf__glob(const char *pattern, apr_size_t pattern_len,
               const char *string, apr_size_t string_len,
               const char *escape, apr_size_t escape_len,
-              svn_stringbuf_t *buf1, svn_stringbuf_t *buf2,
-              svn_boolean_t sql_like, svn_boolean_t *match);
+              svn_boolean_t sql_like,
+              svn_stringbuf_t *pattern_buf,
+              svn_stringbuf_t *string_buf,
+              svn_stringbuf_t *temp_buf,
+              svn_boolean_t *match);
 
 /* Return the version of the wrapped utf8proc library. */
 const char *

Modified: subversion/branches/wc-collate-path/subversion/include/svn_error_codes.h
URL: http://svn.apache.org/viewvc/subversion/branches/wc-collate-path/subversion/include/svn_error_codes.h?rev=1407831&r1=1407830&r2=1407831&view=diff
==============================================================================
--- subversion/branches/wc-collate-path/subversion/include/svn_error_codes.h (original)
+++ subversion/branches/wc-collate-path/subversion/include/svn_error_codes.h Sat Nov 10 15:14:46 2012
@@ -1435,6 +1435,11 @@ SVN_ERROR_START
              SVN_ERR_MISC_CATEGORY_START + 39,
              "utf8proc library error")
 
+  /** @since New in 1.8. */
+  SVN_ERRDEF(SVN_ERR_UTF8_GLOB,
+             SVN_ERR_MISC_CATEGORY_START + 40,
+             "Bad arguments to SQL operators GLOB or LIKE")
+
   /* command-line client errors */
 
   SVN_ERRDEF(SVN_ERR_CL_ARG_PARSING_ERROR,

Modified: subversion/branches/wc-collate-path/subversion/libsvn_subr/utf8proc.c
URL: http://svn.apache.org/viewvc/subversion/branches/wc-collate-path/subversion/libsvn_subr/utf8proc.c?rev=1407831&r1=1407830&r2=1407831&view=diff
==============================================================================
--- subversion/branches/wc-collate-path/subversion/libsvn_subr/utf8proc.c (original)
+++ subversion/branches/wc-collate-path/subversion/libsvn_subr/utf8proc.c Sat Nov 10 15:14:46 2012
@@ -26,11 +26,17 @@
 #define UTF8PROC_INLINE
 #include "utf8proc/utf8proc.c"
 
+#include <apr_fnmatch.h>
+
 #include "private/svn_string_private.h"
 #include "private/svn_utf_private.h"
 #include "svn_private_config.h"
 #define UNUSED(x) ((void)(x))
 
+#ifdef SVN_DEBUG
+#include "private/svn_debug.h"
+#endif
+
 
 const char *svn_utf__utf8proc_version(void)
 {
@@ -142,7 +148,7 @@ svn_utf__normcmp(const char *str1, apr_s
  * A returned error indicates that the codepoint is invalud.
  */
 static svn_error_t *
-encode_ucs4(apr_int32_t ucs4chr, svn_stringbuf_t *buffer)
+encode_ucs4(svn_stringbuf_t *buffer, apr_int32_t ucs4chr)
 {
   char utf8buf[8];     /* The longest UTF-8 sequence has 4 bytes */
   const apr_size_t utf8len = utf8proc_encode_char(ucs4chr, (void *)utf8buf);
@@ -163,8 +169,110 @@ svn_error_t *
 svn_utf__glob(const char *pattern, apr_size_t pattern_len,
               const char *string, apr_size_t string_len,
               const char *escape, apr_size_t escape_len,
-              svn_stringbuf_t *buf1, svn_stringbuf_t *buf2,
-              svn_boolean_t sql_like, svn_boolean_t *match)
-{
+              svn_boolean_t sql_like,
+              svn_stringbuf_t *pattern_buf,
+              svn_stringbuf_t *string_buf,
+              svn_stringbuf_t *temp_buf,
+              svn_boolean_t *match)
+{
+  SVN_DBG((">ptn  : %s\n", pattern));
+  SVN_DBG((">str  : %s\n", string));
+  SVN_DBG((">esc  : %s\n", (escape ? escape : "(null)")));
+
+  /* If we're in LIKE mode, we don't do custom escape chars. */
+  if (escape && !sql_like)
+    return svn_error_create(SVN_ERR_UTF8_GLOB, NULL,
+                            "The GLOB operator does not allow"
+                            " a custom escape character");
+
+  /* Convert the patern to NFD UTF-8. We can't use the UCS-4 result
+     because apr_fnmatch can't handle it.*/
+  SVN_ERR(decompose_normalized(pattern, pattern_len, temp_buf));
+  svn_stringbuf_setempty(pattern_buf);
+  if (!sql_like)
+    {
+      const apr_int32_t *const glob = (void *)temp_buf->data;
+      apr_size_t i;
+      for (i = 0; i < temp_buf->len; ++i)
+        SVN_ERR(encode_ucs4(pattern_buf, glob[i]));
+    }
+  else
+    {
+      /* Convert a LIKE pattern to a GLOB pattern that apr_fnmatch can use. */
+      const apr_int32_t *like = (void *)temp_buf->data;
+      apr_int32_t ucs4esc;
+      svn_boolean_t escaped;
+      apr_size_t i;
+
+      if (!escape)
+        ucs4esc = -1;           /* Definitely an invalid UCS-4 character. */
+      else
+        {
+          const int nullterm = (escape_len == SVN_UTF__UNKNOWN_LENGTH
+                                ? UTF8PROC_NULLTERM : 0);
+          ssize_t result =
+            utf8proc_decompose((const void*) escape, escape_len, &ucs4esc, 1,
+                               UTF8PROC_DECOMPOSE | UTF8PROC_STABLE | nullterm);
+          if (result < 0)
+            return svn_error_create(SVN_ERR_UTF8PROC_ERROR, NULL,
+                                    gettext(utf8proc_errmsg(result)));
+          if (result > 1)
+            return svn_error_create(SVN_ERR_UTF8_GLOB, NULL,
+                                    "The ESCAPE parameter is too long");
+          if ((ucs4esc & 0xFF) != ucs4esc)
+            return svn_error_createf(SVN_ERR_UTF8_GLOB, NULL,
+                                     "Invalid ESCAPE character U+%04lX",
+                                     (long)ucs4esc);
+          SVN_DBG(("<esc  : %c (U+%04lX)\n", (char)(ucs4esc & 0xFF), (long)ucs4esc));
+        }
+
+      for (i = 0, escaped = FALSE; i < temp_buf->len; ++i, ++like)
+        {
+          if (*like == ucs4esc && !escaped)
+            {
+              svn_stringbuf_appendbyte(pattern_buf, '\\');
+              escaped = TRUE;
+            }
+          else if (escaped)
+            {
+              SVN_ERR(encode_ucs4(pattern_buf, *like));
+              escaped = FALSE;
+            }
+          else
+            {
+              if ((*like == '[' || *like == '\\') && !escaped)
+                {
+                  /* Escape brackets and backslashes which are always
+                     literals in LIKE patterns. */
+                  svn_stringbuf_appendbyte(pattern_buf, '\\');
+                  escaped = TRUE;
+                  --i; --like;
+                  continue;
+                }
+
+              /* Replace LIKE wildcards with their GLOB equivalents. */
+              if (*like == '%')
+                  svn_stringbuf_appendbyte(pattern_buf, '*');
+              else if (*like == '_')
+                  svn_stringbuf_appendbyte(pattern_buf, '?');
+              else
+                SVN_ERR(encode_ucs4(pattern_buf, *like));
+            }
+        }
+    }
+  SVN_DBG(("glob  : %s\n", pattern_buf->data));
+
+  /* Now normalize the string */
+  SVN_ERR(decompose_normalized(string, string_len, temp_buf));
+  svn_stringbuf_setempty(string_buf);
+  {
+    const apr_int32_t *const ucs4nfd = (void *)temp_buf->data;
+    apr_size_t i;
+    for (i = 0; i < temp_buf->len; ++i)
+      SVN_ERR(encode_ucs4(string_buf, ucs4nfd[i]));
+  }
+  SVN_DBG(("string: %s\n", string_buf->data));
+
+  *match = !apr_fnmatch(pattern_buf->data, string_buf->data, 0);
   return SVN_NO_ERROR;
 }

Modified: subversion/branches/wc-collate-path/subversion/tests/libsvn_subr/utf-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/wc-collate-path/subversion/tests/libsvn_subr/utf-test.c?rev=1407831&r1=1407830&r2=1407831&view=diff
==============================================================================
--- subversion/branches/wc-collate-path/subversion/tests/libsvn_subr/utf-test.c (original)
+++ subversion/branches/wc-collate-path/subversion/tests/libsvn_subr/utf-test.c Sat Nov 10 15:14:46 2012
@@ -27,6 +27,10 @@
 
 #include "private/svn_utf_private.h"
 
+#ifdef SVN_DEBUG
+#include "private/svn_debug.h"
+#endif
+
 /* Random number seed.  Yes, it's global, just pretend you can't see it. */
 static apr_uint32_t diff_diff3_seed;
 
@@ -481,6 +485,93 @@ test_utf_collated_compare(apr_pool_t *po
 }
 
 
+
+static svn_error_t *
+test_utf_pattern_match(apr_pool_t *pool)
+{
+  static const struct glob_test_t {
+    svn_boolean_t sql_like;
+    svn_boolean_t matches;
+    const char *pattern;
+    const char *string;
+    const char *escape;
+  } glob_tests[] = {
+#define LIKE_MATCH TRUE, TRUE
+#define LIKE_FAIL  TRUE, FALSE
+#define GLOB_MATCH FALSE, TRUE
+#define GLOB_FAIL  FALSE, FALSE
+
+    {LIKE_FAIL,  "",     "test", NULL},
+    {GLOB_FAIL,  "",     "test", NULL},
+    {LIKE_FAIL,  "",     "%",    NULL},
+    {GLOB_FAIL,  "",     "*",    NULL},
+    {LIKE_FAIL,  "test", "%",    NULL},
+    {GLOB_FAIL,  "test", "*",    NULL},
+    {LIKE_MATCH, "test", "test", NULL},
+    {GLOB_MATCH, "test", "test", NULL},
+
+    {LIKE_MATCH, "t\xe1\xb8\x9dst", "te\xcc\xa7\xcc\x86st", NULL},
+    {GLOB_MATCH, "te\xcc\xa7\xcc\x86st", "t\xe1\xb8\x9dst", NULL},
+#undef LIKE_MATCH
+#undef LIKE_FAIL
+#undef GLOB_MATCH
+#undef GLOB_FAIL
+
+    {FALSE, FALSE, NULL, NULL, NULL}
+  };
+
+  svn_stringbuf_t *bufa = svn_stringbuf_create_empty(pool);
+  svn_stringbuf_t *bufb = svn_stringbuf_create_empty(pool);
+  svn_stringbuf_t *bufc = svn_stringbuf_create_empty(pool);
+  const struct glob_test_t *gt;
+
+  srand(79);
+  for (gt = glob_tests; gt->pattern; ++gt)
+    {
+      const svn_boolean_t implicit_size = (rand() % 13) & 1;
+      const apr_size_t lenptn = (implicit_size
+                                 ? SVN_UTF__UNKNOWN_LENGTH
+                                 : strlen(gt->pattern));
+      const apr_size_t lenstr = (implicit_size
+                                 ? SVN_UTF__UNKNOWN_LENGTH
+                                 : strlen(gt->string));
+      const apr_size_t lenesc = (implicit_size
+                                 ? SVN_UTF__UNKNOWN_LENGTH
+                                 : (gt->escape ? strlen(gt->escape) : 0));
+      svn_boolean_t match;
+      svn_error_t *err;
+
+
+      SVN_DBG(("--- %d ----------\n", (int)(gt - glob_tests)));
+
+      err = svn_utf__glob(gt->pattern, lenptn,
+                          gt->string, lenstr,
+                          gt->escape, lenesc,
+                          gt->sql_like, bufa, bufb, bufc, &match);
+      if (gt->sql_like && gt->escape && !err)
+        return svn_error_create
+          (SVN_ERR_TEST_FAILED, NULL, "Failed to detect GLOB ESCAPE");
+
+      if (!match != !gt->matches)
+        {
+          if (gt->sql_like)
+            return svn_error_createf
+              (SVN_ERR_TEST_FAILED, NULL,
+               "Wrong result: %s'%s' LIKE '%s'%s%s%s%s",
+               (gt->matches ? "NOT " : ""), gt->string, gt->pattern,
+               (gt->escape ? " ESCAPE " : ""), (gt->escape ? "'" : ""),
+               (gt->escape ? gt->escape : ""), (gt->escape ? "'" : ""));
+          else
+            return svn_error_createf
+              (SVN_ERR_TEST_FAILED, NULL, "Wrong result: %s%s GLOB %s",
+               (gt->matches ? "NOT " : ""), gt->string, gt->pattern);
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+
 
 /* The test table.  */
 
@@ -497,5 +588,7 @@ struct svn_test_descriptor_t test_funcs[
                    "test svn_utf_cstring_from_utf8_ex2"),
     SVN_TEST_PASS2(test_utf_collated_compare,
                    "test svn_utf__normcmp"),
+    SVN_TEST_PASS2(test_utf_pattern_match,
+                   "test svn_utf__match"),
     SVN_TEST_NULL
   };