Changeset: 4d0f6ba88845 for MonetDB
URL: https://dev.monetdb.org/hg/MonetDB/rev/4d0f6ba88845
Modified Files:
        monetdb5/modules/mal/pcre.c
Branch: Jun2023
Log Message:

Optimize the ILIKE operator performance when the pattern is ASCII

If the pattern of the ILIKE operator only contains ASCII characters we
can avoid using UTF-8 routines for comparison and we can compare bytes
after doing a cheap transformation to lowercase.


diffs (truncated from 792 to 300 lines):

diff --git a/monetdb5/modules/mal/pcre.c b/monetdb5/modules/mal/pcre.c
--- a/monetdb5/modules/mal/pcre.c
+++ b/monetdb5/modules/mal/pcre.c
@@ -51,7 +51,7 @@ typedef regex_t pcre;
 struct RE {
        char *k;
        uint32_t *w;
-       bool search:1, atend:1;
+       bool search:1, atend:1, is_ascii:1, case_ignore:1;
        size_t len;
        struct RE *n;
 };
@@ -337,6 +337,9 @@ re_is_pattern_properly_escaped(const cha
        return escaped ? false : true;
 }
 
+/* returns true if the pattern does not contain wildcard
+ * characters ('%' or '_') and no character is escaped
+ */
 static inline bool
 is_strcmpable(const char *pat, const char *esc)
 {
@@ -345,24 +348,115 @@ is_strcmpable(const char *pat, const cha
        return strlen(esc) == 0 || strNil(esc) || strstr(pat, esc) == NULL;
 }
 
-static inline bool
-re_match_ignore(const char *restrict s, const struct RE *restrict pattern)
+/* Compare two strings ignoring case. When both strings are
+ * lower case this function returns the same result as strcmp.
+ */
+static int
+istrcmp(const char *s1, const char *s2)
 {
-       const struct RE *r;
+       char c1, c2;
+       const char *p1, *p2;
+       for (p1 = s1, p2 = s2; *p1 && *p2; p1++, p2++) {
+               c1 = *p1;
+               c2 = *p2;
 
-       for (r = pattern; r; r = r->n) {
-               if (*r->w == 0 && (r->search || *s == 0))
-                       return true;
-               if (!*s ||
-                       (r->search
-                        ? (s = mywstrcasestr(s, r->w, r->atend)) == NULL
-                        : !mywstrncaseeq(s, r->w, r->len, r->atend)))
-                       return false;
-               s += r->len;
+               if ('A' <= c1 && c1 <= 'Z')
+                       c1 += 'a' - 'A';
+
+               if ('A' <= c2 && c2 <= 'Z')
+                       c2 += 'a' - 'A';
+
+               if (c1 != c2)
+                       return (c1 - c2);
        }
-       return true;
+
+       if (*p1 != *p2)
+               return *p1 - *p2;
+
+       return 0;
 }
 
+/* Compare at most len characters of two strings ignoring
+ * case. When both strings are lowercase this function
+ * returns the same result as strncmp.
+ */
+static int
+istrncmp(const char *s1, const char *s2, size_t len)
+{
+       char c1, c2;
+       const char *p1, *p2;
+       size_t n = 0;
+
+       for (p1 = s1, p2 = s2; *p1 && *p2 && (n < len); p1++, p2++, n++) {
+               c1 = *p1;
+               c2 = *p2;
+
+               if ('A' <= c1 && c1 <= 'Z')
+                       c1 += 'a' - 'A';
+
+               if ('A' <= c2 && c2 <= 'Z')
+                       c2 += 'a' - 'A';
+
+               if (c1 != c2)
+                       return c1 - c2;
+       }
+
+       if (*p1 != *p2 && n < len)
+               return *p1 - *p2;
+
+       return 0;
+}
+
+
+/* Find the first occurence of the substring needle in
+ * haystack ignoring case.
+ *
+ * NOTE: This function assumes that the needle is already
+ * lowercase.
+ */
+static const char *
+istrstr(const char *haystack, const char *needle)
+{
+       const char *ph;
+       const char *pn;
+       const char *p1;
+       bool match = true;
+
+       for (ph = haystack; *ph; ph++) {
+               match = true;
+               for (pn = needle, p1 = ph; *pn && *p1; pn++, p1++) {
+                       char c1 = *pn;
+                       char c2 = ('A' <= *p1 && *p1 <= 'Z') ? *p1 - 'A' + 'a' 
: *p1;
+                       if (c1 != c2) {
+                               match = false;
+                               break;
+                       }
+               }
+
+               /* We reached the end of the haystack, but we still have 
characters in
+                * needle. None of the future iterations will match.
+                */
+               if (*p1 == 0 && *pn != 0) {
+                       break;
+               }
+
+               if (match) {
+                       return ph;
+               }
+       }
+       return NULL;
+}
+
+/* Match regular expression by comparing bytes.
+ *
+ * This is faster than re_match_ignore, because it does not
+ * need to decode characters. This function should be used
+ * in all cases except when we need to perform UTF-8
+ * comparisons ignoring case.
+ *
+ * TODO: The name of the function is no longer accurate and
+ * needs to change.
+ */
 static inline bool
 re_match_no_ignore(const char *restrict s, const struct RE *restrict pattern)
 {
@@ -375,10 +469,44 @@ re_match_no_ignore(const char *restrict 
                if (!*s ||
                        (r->search
                         ? (r->atend
-                               ? (l = strlen(s)) < r->len || strcmp(s + l - 
r->len, r->k) != 0
-                               : (s = strstr(s, r->k)) == NULL)
+                               ? (r->case_ignore
+                                  ? (l = strlen(s)) < r->len || istrcmp(s + l 
- r->len, r->k) != 0
+                                  : (l = strlen(s)) < r->len || strcmp(s + l - 
r->len, r->k) != 0)
+                               : (r->case_ignore ? (s = istrstr(s, r->k)) == 
NULL
+                                  : (s = strstr(s, r->k)) == NULL))
                         : (r->atend
-                               ? strcmp(s, r->k) != 0 : strncmp(s, r->k, 
r->len) != 0)))
+                               ? (r->case_ignore ? istrcmp(s, r->k) != 0
+                                  : strcmp(s, r->k) != 0)
+                               : (r->case_ignore ? istrncmp(s, r->k, r->len) 
!= 0
+                                  : strncmp(s, r->k, r->len) != 0))))
+                       return false;
+               s += r->len;
+       }
+       return true;
+}
+
+/* Match a regular expression by comparing wide characters.
+ *
+ * This needs to be used when we need to perform a
+ * case-ignoring comparions involving UTF-8 characters.
+ */
+static inline bool
+re_match_ignore(const char *restrict s, const struct RE *restrict pattern)
+{
+       const struct RE *r;
+
+       /* Since the pattern is ascii, do the cheaper comparison */
+       if (pattern->is_ascii) {
+               return re_match_no_ignore(s, pattern);
+       }
+
+       for (r = pattern; r; r = r->n) {
+               if (*r->w == 0 && (r->search || *s == 0))
+                       return true;
+               if (!*s ||
+                       (r->search
+                        ? (s = mywstrcasestr(s, r->w, r->atend)) == NULL
+                        : !mywstrncaseeq(s, r->w, r->len, r->atend)))
                        return false;
                s += r->len;
        }
@@ -400,13 +528,16 @@ re_destroy(struct RE *p)
        }
 }
 
-/* Create a linked list of RE structures.  Depending on the caseignore
- * flag, the w (if true) or the k (if false) field is used.  These
- * fields in the first structure are allocated, whereas in all
- * subsequent structures the fields point into the allocated buffer of
- * the first. */
+/* Create a linked list of RE structures.  Depending on the
+ * caseignore and the ascii_pattern flags, the w
+ * (if caseignore == true && ascii_pattern == false) or the k
+ * (in every other case) field is used.  These in the first
+ * structure are allocated, whereas in all subsequent
+ * structures the fields point into the allocated buffer of
+ * the first.
+ */
 static struct RE *
-re_create(const char *pat, bool caseignore, uint32_t esc)
+re_create(const char *pat, bool caseignore, bool ascii_pattern, uint32_t esc)
 {
        struct RE *r = GDKmalloc(sizeof(struct RE)), *n = r;
        bool escaped = false;
@@ -419,7 +550,7 @@ re_create(const char *pat, bool caseigno
                pat++;                                  /* skip % */
                r->search = true;
        }
-       if (caseignore) {
+       if (caseignore && !ascii_pattern) {
                uint32_t *wp;
                uint32_t *wq;
                wp = utf8stoucs(pat);
@@ -465,6 +596,18 @@ re_create(const char *pat, bool caseigno
                        GDKfree(r);
                        return NULL;
                }
+               if (ascii_pattern)
+                       n->is_ascii = true;
+               if (caseignore)
+                       n->case_ignore = true;
+
+               if (ascii_pattern && caseignore) {
+                       for (q = p; *q != 0; q++) {
+                               if ('A' <= *q && *q <= 'Z')
+                                       *q += 'a' - 'A';
+                       }
+               }
+
                r->k = p;
                q = p;
                while (*p) {
@@ -487,11 +630,21 @@ re_create(const char *pat, bool caseigno
                                                .atend = true,
                                                .k = p + 1
                                        };
+                                       if (ascii_pattern) {
+                                               n->is_ascii = true;
+                                       }
+                                       if (caseignore) {
+                                               n->case_ignore = true;
+                                       }
                                }
                                *q = 0;
                                q = p + 1;
                        } else {
-                               *q++ = *p;
+                               char c = *p;
+                               if (ascii_pattern && caseignore && 'A' <= c && 
c <= 'Z') {
+                                       c += 'a' - 'A';
+                               }
+                               *q++ = c;
                                n->len++;
                        }
                        p++;
@@ -1299,15 +1452,30 @@ PCREsql2pcre(str *ret, const str *pat, c
        return sql2pcre(ret, *pat, *esc);
 }
 
+static bool
+is_ascii_str(const char *pat)
+{
+       size_t len = strlen(pat);
+       for (size_t i = 0; i < len; i++) {
+               if (pat[i] & 0x80)
+                       return false;
+       }
+
+       return true;
+}
+
 static inline str
 choose_like_path(char **ppat, bool *use_re, bool *use_strcmp, bool *empty,
-                                const char *pat, const char *esc)
+                                bool *ascii_pattern, const char *pat, const 
char *esc)
 {
        str res = MAL_SUCCEED;
        *use_re = false;
        *use_strcmp = false;
        *empty = false;
 
+
_______________________________________________
checkin-list mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to