服务器 频道

DB2中如何实现正则表达式

  改进性能

  尽管上面的函数按照预期的方式工作,但还可以改进它以获得更佳的性能。注:函数内部的执行完成得越快,DB2 处理整个 SQL 语句的速度也就越快。

  SQL 旨在处理多组行,这意味着通常会针对一个模式匹配多个行。在大多数情况下,模式本身对于整个 SQL 语句都是不变的;即,它不会随行的更改而更改。 清单 5 中的 C 代码展示了对每一行都调用函数 pcre_compile() ,该函数将给定模式转换成内部表示法。

  DB2 通过使用所谓的“高速暂存(scratchpad)”提供了在 UDF 调用之间传递信息的机制。此外,您可以标识特定调用“类型”;即它是对该 UDF 的第一次调用、普通调用还是最后一次(最终)调用。使用高速暂存和调用类型,有可能只对模式编译一次,然后将该已编译模式的内部表示法重用于对该 UDF 的所有后续调用。在最后一次调用时,释放在处理期间分配的资源。

  如 清单 6所示,对 CREATE FUNCTION 语句进行修改,告诉 DB2 向外部 C 代码提供高速暂存和调用类型:

  清单 6. 将高速暂存和调用类型添加到 CREATE FUNCTION 语句


CREATE FUNCTION regex2(pattern VARCHAR(2048), string CLOB(10M))
    RETURNS INTEGER
    SPECIFIC regexPerf
    EXTERNAL NAME ''regexUdf!regexpPerf''
    LANGUAGE C
    PARAMETER STYLE DB2SQL
    DETERMINISTIC
    NOT FENCED
    RETURNS NULL ON NULL INPUT
    NO SQL
    NO EXTERNAL ACTION
          
            
    SCRATCHPAD 50
    FINAL CALL
          
    ALLOW PARALLEL;
        

  UDF 入口点看起来很不一样,因为必须改写函数内部的逻辑。参数方面唯一的更改是使用 SQLUDF_TRAIL_ARGS_ALL 代替了 SQLUDF_TRAIL_ARGS ,如 清单 7所示。

  清单 7. regex2 的 C UDF 入口点


#include <pcre.h>
#include <sqludf.h>
// data structure mapped on the scratchpad for easier use and access
// to the objects
// the size of the scratchpad defined in the CREATE FUNCTION statement
// must be at least as large as sizeof(scratchPadMapping)
struct scratchPadMapping {
    pcre *re;
    pcre_extra *extra;
    const char *error;
    int errOffset;
};
void regexpPerf(
    // input parameters
    SQLUDF_VARCHAR *pattern,      SQLUDF_CLOB *str,
    // output
    SQLUDF_INTEGER *match,
    // null indicators
    SQLUDF_NULLIND *pattern_ind,  SQLUDF_NULLIND *str_ind,
    SQLUDF_NULLIND *match_ind,
    SQLUDF_TRAIL_ARGS_ALL) // SQLUDF_SCRAT & SQLUDF_CALLT
{
    int rc = 0;
    struct scratchPadMapping *scratch = NULL;
    // map the buffer of the scratchpad and assume successful return
    scratch = (struct scratchPadMapping *)SQLUDF_SCRAT->data;
    *match_ind = 0;
    switch (SQLUDF_CALLT) {
      case SQLUDF_FIRST_CALL:
        // initialize data on the scratchpad
        scratch->re = NULL;
        scratch->extra = NULL;
        scratch->error = NULL;
        scratch->errOffset = 0;
        // compile the pattern (only in the FIRST call
        scratch->re = pcre_compile(pattern, 0 /* default options */,
            &scratch->error, &scratch->errOffset, NULL);
        if (scratch->re == NULL) {
            snprintf(SQLUDF_MSGTX, 70, "Regexp compilation failed at "
                "offset %d: %s\\n", scratch->errOffset, scratch->error);
            strcpy(SQLUDF_STATE, "38900");
            rc = -1;
            break;
        }
        // further analyze the pattern (might return NULL)
        scratch->extra = pcre_study(scratch->re,
            0 /* default options */, &scratch->error);
        /* fall through to NORMAL call because DB2 expects a result
           already in the FIRST call */
      case SQLUDF_NORMAL_CALL:
        // match the current string
        rc = pcre_exec(scratch->re, scratch->extra, str->data,
              str->length, 0, 0 /* default options */, NULL, 0);
        switch (rc) {
          case PCRE_ERROR_NOMATCH:
            *match = 0;
            rc = 0;
            break;
          case PCRE_ERROR_BADOPTION:
            snprintf(SQLUDF_MSGTX, 70, "An unrecognized bit was set "
                "in the options argument");
            strcpy(SQLUDF_STATE, "38901");
            rc = -1;
            break;
          case PCRE_ERROR_NOMEMORY:
            snprintf(SQLUDF_MSGTX, 70, "Not enough memory available.");
            strcpy(SQLUDF_STATE, "38902");
            rc = -1;
            break;
          default:
            if (rc < 0) {
                snprintf(SQLUDF_MSGTX, 70, "A regexp match error "
                    "occured: %d", rc);
                strcpy(SQLUDF_STATE, "38903");
                rc = -1;
            }
            else {
                *match = 1;
                rc = 0;
            }
            break;
        }
        break;
      }
      // cleanup in FINAL call, or if we encountered an error in
      // the FIRST call (DB2 will make a FINAL call if we encounter
      // an error in any NORMAL call)
      if (SQLUDF_CALLT == SQLUDF_FINAL_CALL ||
          (SQLUDF_CALLT == SQLUDF_FIRST_CALL && rc < 0)) {
          (*pcre_free)(scratch->re);
          (*pcre_free)(scratch->extra);
      }
      return;
}

  为了进一步改进该函数的性能,我添加了对函数 pcre_study() 的调用,该函数是由模式匹配引擎提供的。该函数进一步分析了该模式,并将额外的信息存储在独立的结构中。然后,在实际的匹配期间使用这些额外的信息来加快处理速度。通过使用一个非常简单的模式和大约 4000 行的表,我获得了 5% 的执行时间的改善。当然,模式越复杂,差异将越显著。

  我先前提到该实现假定模式在处理期间不会随行的不同而更改。当然,如果模式确实更改了,您可以进行少量的改写以再次编译一个模式。要这样做,有必要跟踪当前(已编译的)模式并在每次调用中将它与所提供的模式进行比较。也可以在高速暂存中维护当前模式。但必须将它复制到独立的缓冲区,并且不能通过指针模式直接引用它,因为这个指针或它所引用的数据可能会更改或变为无效。至于相应的代码更改,就当作练习留给读者了。

  返回匹配子串

  大多数模式匹配引擎提供了一种方法,返回与指定模式或其一部分相匹配的子串。如果想在 SQL 中使用这种能力,则必须使用不同的方法来实现匹配函数。给定的字符串可能包含不止一个匹配的子串。例如,当解析类似“abc = 123;”或“def = ''some text'';”这样的字符串时,用户可能会希望检索由等号分隔的两个子串。您可以使用模式“\\w+\\s*=\\s*(\\d+|''[\\w\\s] *'');”来表示适用于该字符串的语法规则。Perl 兼容的正则表达式允许您捕获等号两边的子串。最后,必须将要捕获的子串用括号括起来。我已经用该方式编写了第二个子串,但第一个子串不是这样编写的。用于该用途的最终模式是这样的:


(\\w+)\\s*=\\s*(\\d+|''[\\w\\s]*'');

  当把这个模式应用于字符串“abc= 123;”或“def = ''some text'';”时,“abc”或“def”分别与“(\\w+)”匹配,空格和等号是通过“\\s*=\\s*”查找的,并用另外的“(\\d+|''[\ \w\\s*]'')”涵盖了余下的子串。在“(\\d+|''[\\w\\s*]'')”中,第一个选项与任何至少由一个数字“\\d+”组成的数匹配,而第二个选项解析任何由字母和空格组成的由单引号括起的字符串“''[\\w\\s]*''”。

  在 DB2 中做到这一点的需求可以描述成:为一次 UDF 调用返回多个结果。换句话说,就是返回针对模式进行匹配的单个字符串的多个子串。DB2 的表函数是完成这一任务的完美工具。

  实现表 UDF

  和以前一样,必须在数据库中创建该函数。 清单 8中的下列语句正是用于这一任务的:

  清单 8. 注册名为 regex3 的表 UDF


CREATE FUNCTION regex3(pattern VARCHAR(2048), string CLOB(10M))
    RETURNS TABLE ( position INTEGER, substring VARCHAR(2048) )
    SPECIFIC regexSubstr
    EXTERNAL NAME ''regexUdf!regexpSubstr''
    LANGUAGE C
    PARAMETER STYLE DB2SQL
    DETERMINISTIC
    NOT FENCED
    RETURNS NULL ON NULL INPUT
    NO SQL
    NO EXTERNAL ACTION
    SCRATCHPAD 50
    NO FINAL CALL
    DISALLOW PARALLEL;

  实现该函数的实际逻辑的 C 代码与 清单 7中的代码非常相似,但根据表函数所必须满足的特殊需求对它进行了改编,如 清单 9所示。

0
相关文章