剖析 `dbDelta()` 函数的源码,解释它如何通过正则匹配解析 `CREATE TABLE` 语句并生成 `ALTER TABLE` 语句?

大家好,欢迎来到今天的“WordPress数据库诊所”。我是今天的“主刀医生”——老码农。今天要给大家解剖的是WordPress里一个非常关键,但又经常被忽视的函数:dbDelta()

这玩意儿,说白了,就是WordPress用来升级数据库结构的利器。它能自动检测数据库表结构的变化,然后生成并执行相应的ALTER TABLE语句,让你的数据库始终保持最新状态。听起来是不是很厉害?

不过,别被它唬住了。它的核心原理其实并不复杂,就是一个“正则表达式狂魔”加上一些简单的逻辑判断。今天,我们就来扒一扒它的源码,看看它是如何“玩转”正则表达式,将CREATE TABLE语句变成ALTER TABLE语句的。

第一步:认识“病患”——CREATE TABLE语句

首先,我们要了解dbDelta()要处理的对象,也就是CREATE TABLE语句。一个标准的CREATE TABLE语句看起来大概是这样的:

CREATE TABLE wp_options (
  option_id bigint(20) unsigned NOT NULL auto_increment,
  option_name varchar(191) NOT NULL default '',
  option_value longtext NOT NULL,
  autoload varchar(20) NOT NULL default 'yes',
  PRIMARY KEY  (option_id),
  UNIQUE KEY option_name (option_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

这个语句定义了一个名为wp_options的表,包含了字段名、数据类型、约束、索引等等信息。dbDelta()的任务,就是解析这个语句,提取出这些信息,然后与现有的数据库表结构进行比较,最终生成ALTER TABLE语句。

第二步:祭出神器——正则表达式

dbDelta()的核心就是使用正则表达式来解析CREATE TABLE语句。它定义了一系列正则表达式,用于匹配不同的语法元素,例如表名、字段名、数据类型、索引等等。

// 摘自wp-admin/includes/upgrade.php,简化版
function dbDelta( $queries, $execute = true ) {
  global $wpdb;

  $queries = trim( $queries );
  if ( empty( $queries ) ) {
    return;
  }

  $queries = preg_split( '/[rn]+/', $queries );

  $cqueries = array();

  foreach ( $queries as $query ) {
    $query = trim( $query );
    if ( empty( $query ) ) {
      continue;
    }

    // Capture CREATE TABLE statements.
    if ( ! preg_match( '/CREATE TABLEs+(if not existss+)?`?([^`]*)`?s*((.*))s*(.*)/is', $query, $matches ) ) {
      continue;
    }

    $cqueries[] = $query;

    $table = $matches[2];
    $tablefields = trim( $matches[3] );
    $after_table = trim( $matches[4] );

    // Extract field definitions.
    $field_queries = preg_split( '/,s*/', $tablefields );
    $index_queries = array();
    $fields = array();

    foreach ( $field_queries as $field_query ) {
      $field_query = trim( $field_query );

      // Capture PRIMARY KEY and UNIQUE KEY definitions.
      if ( preg_match( '/PRIMARY KEYs+((.*))/i', $field_query, $key_match ) ||
           preg_match( '/UNIQUE KEYs+`?([^`]*)`?s+((.*))/i', $field_query, $key_match ) ||
           preg_match( '/KEYs+`?([^`]*)`?s+((.*))/i', $field_query, $key_match ) ) {
        $index_queries[] = $field_query;
      } else {
        // Capture field definitions.
        if ( preg_match( '/`?([^`]*)`?s+([^s]*)s*(.*)/i', $field_query, $field_match ) ) {
          $fields[ $field_match[1] ] = array(
            'type' => $field_match[2],
            'attributes' => $field_match[3]
          );
        }
      }
    }

    // 获取当前数据库表结构
    $existing_table = $wpdb->get_results( "DESCRIBE `$table`;" );

    if ( empty($existing_table) ) {
      // 表不存在,直接创建
      $wpdb->query($query);
    } else {
      // 表存在,进行比较
      $alter_queries = array();
      $existing_fields = array();
      foreach ( $existing_table as $field ) {
        $existing_fields[ $field->Field ] = $field;
      }

      // 检查字段是否需要添加或修改
      foreach ( $fields as $field_name => $field_definition ) {
        if ( ! isset( $existing_fields[ $field_name ] ) ) {
          // 字段不存在,添加字段
          $alter_queries[] = "ADD COLUMN `$field_name` " . $field_definition['type'] . " " . $field_definition['attributes'];
        } else {
          // 字段存在,检查是否需要修改
          $existing_field = $existing_fields[ $field_name ];
          $existing_type = strtoupper($existing_field->Type); // 转换为大写以便比较
          $new_type = strtoupper($field_definition['type']); // 转换为大写以便比较

          if ( $existing_type != $new_type ) {
            $alter_queries[] = "MODIFY COLUMN `$field_name` " . $field_definition['type'] . " " . $field_definition['attributes'];
          }
        }
      }

      // 执行 ALTER TABLE 语句
      if ( ! empty( $alter_queries ) ) {
        $alter_table_query = "ALTER TABLE `$table` " . implode( ', ', $alter_queries );
        $wpdb->query( $alter_table_query );
      }
    }
  }
}

让我们逐个分析一下这些正则表达式:

  • */CREATE TABLEs+(if not existss+)??([^`])`?s((.))s(.)/is**: 这是最关键的一个正则表达式,用于匹配整个CREATE TABLE`语句。

    • CREATE TABLEs+: 匹配CREATE TABLE关键字,以及后面的一个或多个空格。
    • (if not existss+)?: 匹配可选的IF NOT EXISTS子句,用于判断表是否已经存在。
    • `?([^`])`?s`: 匹配表名,表名可以用反引号括起来,也可以不括。
    • ((.*))s*: 匹配括号内的字段定义,也就是CREATE TABLE语句中最重要的部分。
    • (.*): 匹配括号后的其他内容,例如ENGINEDEFAULT CHARSET等。
    • /is: i表示忽略大小写,s表示.可以匹配换行符。
  • *`/PRIMARY KEYs+((.))/i`**: 匹配主键定义。

    • PRIMARY KEYs+(: 匹配PRIMARY KEY关键字和左括号。
    • (.*): 匹配括号内的字段列表。
    • )/i: 匹配右括号,i表示忽略大小写。
  • /UNIQUE KEYs+?([^`])`?s+((.))/i`: 匹配唯一索引定义。

    • UNIQUE KEYs+: 匹配UNIQUE KEY关键字和空格。
    • `?([^`]*)`?s+`: 匹配索引名,索引名可以用反引号括起来,也可以不括。
    • ((.*))/i: 匹配括号内的字段列表。
  • /KEYs+?([^`])`?s+((.))/i`: 匹配普通索引定义。

    • KEYs+: 匹配KEY关键字和空格。
    • `?([^`]*)`?s+`: 匹配索引名,索引名可以用反引号括起来,也可以不括。
    • ((.*))/i: 匹配括号内的字段列表。
  • /?([^`])`?s+([^s])s(.)/i`: 匹配字段定义。

    • `?([^`]*)`?s+`: 匹配字段名,字段名可以用反引号括起来,也可以不括。
    • ([^s]*)s*: 匹配数据类型。
    • (.*): 匹配字段属性,例如NOT NULLDEFAULT等。

这些正则表达式就像一把把锋利的手术刀,将CREATE TABLE语句切割成一个个小的片段,方便dbDelta()进行分析和比较。

第三步:数据库“体检”——获取现有表结构

在解析了CREATE TABLE语句之后,dbDelta()需要获取现有的数据库表结构,才能进行比较。它使用DESCRIBE语句来获取表的字段信息。

$existing_table = $wpdb->get_results( "DESCRIBE `$table`;" );

DESCRIBE语句会返回一个包含表字段信息的数组,例如字段名、数据类型、是否允许为空、主键等等。

第四步:对比“病情”——生成ALTER TABLE语句

有了CREATE TABLE语句的解析结果和现有的数据库表结构,dbDelta()就可以开始进行比较,生成ALTER TABLE语句了。

它主要会进行以下几个方面的比较:

  • 表是否存在: 如果表不存在,直接执行CREATE TABLE语句创建表。
  • 字段是否存在: 如果字段不存在,使用ALTER TABLE ADD COLUMN语句添加字段。
  • 字段类型是否一致: 如果字段类型不一致,使用ALTER TABLE MODIFY COLUMN语句修改字段类型。
  • 索引是否存在: (简化版的代码里没体现,但实际dbDelta会检查索引) 如果索引不存在,使用ALTER TABLE ADD INDEX语句添加索引。
  • 索引定义是否一致: (简化版的代码里没体现,但实际dbDelta会检查索引) 如果索引定义不一致,使用ALTER TABLE DROP INDEXALTER TABLE ADD INDEX语句更新索引。
// 检查字段是否需要添加或修改
foreach ( $fields as $field_name => $field_definition ) {
  if ( ! isset( $existing_fields[ $field_name ] ) ) {
    // 字段不存在,添加字段
    $alter_queries[] = "ADD COLUMN `$field_name` " . $field_definition['type'] . " " . $field_definition['attributes'];
  } else {
    // 字段存在,检查是否需要修改
    $existing_field = $existing_fields[ $field_name ];
    $existing_type = strtoupper($existing_field->Type); // 转换为大写以便比较
    $new_type = strtoupper($field_definition['type']); // 转换为大写以便比较

    if ( $existing_type != $new_type ) {
      $alter_queries[] = "MODIFY COLUMN `$field_name` " . $field_definition['type'] . " " . $field_definition['attributes'];
    }
  }
}

// 执行 ALTER TABLE 语句
if ( ! empty( $alter_queries ) ) {
  $alter_table_query = "ALTER TABLE `$table` " . implode( ', ', $alter_queries );
  $wpdb->query( $alter_table_query );
}

举个栗子

假设我们有一个名为wp_test的表,初始结构如下:

CREATE TABLE wp_test (
  id bigint(20) unsigned NOT NULL auto_increment,
  name varchar(255) NOT NULL default '',
  PRIMARY KEY  (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

现在,我们需要添加一个email字段,并将name字段的长度修改为500。我们可以这样写CREATE TABLE语句:

CREATE TABLE wp_test (
  id bigint(20) unsigned NOT NULL auto_increment,
  name varchar(500) NOT NULL default '',
  email varchar(255) NOT NULL default '',
  PRIMARY KEY  (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

dbDelta()执行这个CREATE TABLE语句时,它会检测到email字段不存在,因此会生成ALTER TABLE ADD COLUMN语句来添加这个字段。同时,它会检测到name字段的长度不一致,因此会生成ALTER TABLE MODIFY COLUMN语句来修改字段长度。最终,dbDelta()会执行以下ALTER TABLE语句:

ALTER TABLE wp_test ADD COLUMN `email` varchar(255) NOT NULL default '', MODIFY COLUMN `name` varchar(500) NOT NULL default '';

第五步:注意事项

  • dbDelta()只处理CREATE TABLE语句: 它不会处理DROP TABLERENAME TABLE等语句。
  • 正则表达式是关键: dbDelta()的解析能力取决于正则表达式的准确性。如果正则表达式写错了,可能会导致解析失败或者生成错误的ALTER TABLE语句。
  • 字段类型比较需要注意: dbDelta()在比较字段类型时,会忽略大小写。但是,对于一些特殊的数据类型,例如TEXTBLOB等,可能需要进行更细致的比较。
  • 索引处理比较复杂: 索引的处理涉及到索引名、索引类型、索引字段等等,比较复杂。dbDelta()的索引处理逻辑也比较复杂,需要仔细分析。
  • 不要滥用dbDelta(): 每次插件或主题升级都运行dbDelta()可能会导致不必要的数据库操作,影响性能。应该只在数据库结构发生变化时才运行dbDelta()

总结

dbDelta()是一个功能强大的数据库升级工具,它通过正则表达式解析CREATE TABLE语句,然后与现有的数据库表结构进行比较,最终生成ALTER TABLE语句。虽然它的核心原理并不复杂,但是需要对正则表达式和数据库结构有深入的了解才能正确使用。

表格总结

步骤 描述 使用技术
1. 识别病患 识别并获取 CREATE TABLE 语句。 SQL
2. 祭出神器 使用正则表达式解析 CREATE TABLE 语句,提取表名、字段、索引等信息。 正则表达式
3. 数据库体检 获取现有数据库表结构。 DESCRIBE SQL语句
4. 对比病情 对比 CREATE TABLE 语句和现有表结构,生成 ALTER TABLE 语句。 SQL, PHP逻辑判断
5. 执行手术 执行 ALTER TABLE 语句,升级数据库结构。 SQL

希望今天的“数据库诊所”能让你对dbDelta()有更深入的了解。记住,正则表达式是它的灵魂,细致的比较是它的心脏。只有掌握了这些,你才能更好地驾驭这个强大的工具,让你的WordPress数据库始终保持健康状态。

好了,今天的讲座就到这里。有问题可以在评论区留言,老码农会尽力解答。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注