深入理解 WordPress `wpdb` 类的 `dbDelta()` 方法源码:它是如何解析 `CREATE TABLE` 语句来生成 `ALTER TABLE` 的。

好嘞!今天咱们就来扒一扒 WordPress 里 wpdb 类的 dbDelta() 方法,看看它怎么把一个简单的 CREATE TABLE 语句,变成一堆复杂的 ALTER TABLE,简直就是个变魔术的!

开场白:dbDelta() 的江湖地位

各位观众,晚上好!今天我们要聊的 dbDelta() 函数,在 WordPress 数据库操作中,那可是个重量级人物。它负责处理插件和主题更新时,数据库表的创建和升级。想象一下,你新装了个插件,需要往数据库里加张表,或者修改现有表结构,如果没有 dbDelta(),那可就麻烦大了。

dbDelta() 的基本原理:对比与变更

dbDelta() 的核心思想很简单:对比。它会比较你提供的 CREATE TABLE 语句和数据库里实际表的结构,然后生成必要的 ALTER TABLE 语句,让数据库表的结构和你想要的保持一致。

举个栗子:初识 CREATE TABLE

先来看一个简单的 CREATE TABLE 语句:

CREATE TABLE `wp_my_table` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `name` varchar(255) NOT NULL default '',
  `email` varchar(100) default NULL,
  `created_at` datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

这个语句定义了一个名为 wp_my_table 的表,包含 id, name, email, created_at 四个字段,以及一个主键和一个索引。

dbDelta() 的解析流程:庖丁解牛

dbDelta() 的解析流程大致如下:

  1. 分词:CREATE TABLE 语句分解成一个个的词语,例如 CREATE, TABLE, wp_my_table, id, bigint, 等等。
  2. 语法分析: 根据 SQL 语法规则,将这些词语组织成一个语法树,理解语句的含义。例如,知道 id bigint(20) unsigned NOT NULL auto_increment 是一个字段定义。
  3. 表结构提取: 从语法树中提取出表的名称、字段名称、字段类型、字段属性、主键、索引等信息。
  4. 数据库表结构获取: 查询数据库,获取当前已存在的同名表的结构信息。
  5. 结构对比: 将从 CREATE TABLE 语句中提取的表结构和数据库中已存在的表结构进行对比,找出差异。
  6. 生成 ALTER TABLE 语句: 根据对比结果,生成 ALTER TABLE 语句,用于修改数据库表的结构,使其与 CREATE TABLE 语句定义的结构一致。
  7. 执行 ALTER TABLE 语句: 将生成的 ALTER TABLE 语句提交给数据库执行,完成表结构的修改。

源码剖析:深入 dbDelta() 的内部

dbDelta() 函数位于 wp-admin/includes/upgrade.php 文件中。它的核心逻辑在于处理 CREATE TABLE 语句,并将其转化为一系列的 ALTER TABLE 语句。

咱们先来简化一下 dbDelta() 函数的结构,方便理解:

function my_dbDelta( $sql ) {
  global $wpdb;

  $queries = explode( ';', $sql ); // 分割SQL语句

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

    // 1. 获取表名
    preg_match( '/CREATE TABLE `?(.+?)`?/is', $query, $matches );
    if ( ! isset( $matches[1] ) ) {
      continue; // 如果不是 CREATE TABLE 语句,跳过
    }
    $table_name = $matches[1];

    // 2. 获取当前数据库中表的结构
    $existing_columns = $wpdb->get_results( "DESCRIBE `$table_name`" );
    $existing_columns = wp_list_pluck( $existing_columns, 'Type', 'Field' );

    // 3. 解析 CREATE TABLE 语句,提取字段信息
    $new_columns = my_parse_create_table( $query ); // 假设这是个自定义的解析函数

    // 4. 对比新旧字段,生成 ALTER TABLE 语句
    $alter_queries = my_compare_columns( $table_name, $existing_columns, $new_columns ); // 假设这是个自定义的对比函数

    // 5. 执行 ALTER TABLE 语句
    foreach ( $alter_queries as $alter_query ) {
      $wpdb->query( $alter_query );
    }
  }
}

关键步骤详解:

  1. 分割 SQL 语句: explode( ';', $sql ) 将传入的 SQL 语句按照分号分割成多个独立的 SQL 语句。这是因为 dbDelta() 可以一次性处理多个 CREATE TABLE 语句。

  2. 获取表名: 使用正则表达式 preg_match( '/CREATE TABLE?(.+?)?/is', $query, $matches )CREATE TABLE 语句中提取表名。正则表达式 CREATE TABLE?(.+?)? 用于匹配 CREATE TABLE 语句,并捕获表名。? 表示可选的,(.+?) 表示捕获一个或多个字符,直到下一个 ? 出现。is 是正则表达式的修饰符,i 表示不区分大小写,s 表示点号 . 匹配所有字符,包括换行符。

  3. 获取当前数据库中表的结构: 使用 DESCRIBE$table_name` SQL 语句获取当前数据库中表的结构信息。wp_list_pluck( $existing_columns, ‘Type’, ‘Field’ )` 将结果转换为一个关联数组,其中键是字段名,值是字段类型。

  4. 解析 CREATE TABLE 语句,提取字段信息: 这是一个关键步骤,需要解析 CREATE TABLE 语句,提取出字段名称、字段类型、字段属性、主键、索引等信息。由于 SQL 语法比较复杂,所以解析过程也比较复杂。为了简化示例,我们假设有一个自定义的解析函数 my_parse_create_table(),它可以完成这个任务。

  5. 对比新旧字段,生成 ALTER TABLE 语句: 这是另一个关键步骤,需要对比从 CREATE TABLE 语句中提取的字段信息和从数据库中获取的字段信息,找出差异,然后生成 ALTER TABLE 语句。为了简化示例,我们假设有一个自定义的对比函数 my_compare_columns(),它可以完成这个任务。

  6. 执行 ALTER TABLE 语句: 使用 $wpdb->query( $alter_query ) 执行生成的 ALTER TABLE 语句,修改数据库表的结构。

自定义解析函数 my_parse_create_table() 的实现

为了更深入地理解 dbDelta() 的工作原理,我们需要实现一个简单的 my_parse_create_table() 函数。这个函数的作用是解析 CREATE TABLE 语句,提取出字段信息。

function my_parse_create_table( $sql ) {
  $columns = array();
  $lines = explode( "n", $sql );

  foreach ( $lines as $line ) {
    $line = trim( $line );

    // 提取字段定义
    if ( preg_match( '/`(.+?)` (.+?)(sNOT NULL)?(sdefault '(.+?)')?/', $line, $matches ) ) {
      $field_name = $matches[1];
      $field_type = $matches[2];
      $not_null = ! empty( $matches[3] );
      $default_value = isset( $matches[5] ) ? $matches[5] : null;

      $columns[ $field_name ] = array(
        'type' => $field_type,
        'not_null' => $not_null,
        'default' => $default_value,
      );
    }
  }

  return $columns;
}

这个函数使用正则表达式 preg_match( '/(.+?)(.+?)(sNOT NULL)?(sdefault '(.+?)')?/', $line, $matches ) 来提取字段定义。这个正则表达式比较复杂,咱们来解释一下:

  • `(.+?)` 匹配字段名,用反引号括起来。
  • (.+?) 匹配字段类型,例如 bigint(20), varchar(255)
  • (sNOT NULL)? 匹配 NOT NULL 属性,? 表示可选。
  • (sdefault '(.+?)')? 匹配 default 属性,? 表示可选。

自定义对比函数 my_compare_columns() 的实现

接下来,我们需要实现一个简单的 my_compare_columns() 函数。这个函数的作用是对比新旧字段信息,生成 ALTER TABLE 语句。

function my_compare_columns( $table_name, $existing_columns, $new_columns ) {
  $alter_queries = array();

  // 添加新字段
  foreach ( $new_columns as $field_name => $field_info ) {
    if ( ! isset( $existing_columns[ $field_name ] ) ) {
      $alter_queries[] = "ALTER TABLE `$table_name` ADD COLUMN `$field_name` {$field_info['type']}" .
                         ( $field_info['not_null'] ? ' NOT NULL' : '' ) .
                         ( isset( $field_info['default'] ) ? " DEFAULT '{$field_info['default']}'" : '' );
    }
  }

  // 修改字段类型
  foreach ( $new_columns as $field_name => $field_info ) {
    if ( isset( $existing_columns[ $field_name ] ) && $existing_columns[ $field_name ] != $field_info['type'] ) {
      $alter_queries[] = "ALTER TABLE `$table_name` MODIFY COLUMN `$field_name` {$field_info['type']}" .
                         ( $field_info['not_null'] ? ' NOT NULL' : '' ) .
                         ( isset( $field_info['default'] ) ? " DEFAULT '{$field_info['default']}'" : '' );
    }
  }

  return $alter_queries;
}

这个函数主要做了两件事:

  1. 添加新字段: 如果新字段在现有表中不存在,则生成 ALTER TABLE ADD COLUMN 语句。
  2. 修改字段类型: 如果字段类型在新旧表中不一致,则生成 ALTER TABLE MODIFY COLUMN 语句。

一个完整的例子:

现在,咱们来用一个完整的例子来演示 dbDelta() 的工作过程。

$sql = "
CREATE TABLE `wp_my_table` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `name` varchar(255) NOT NULL default '',
  `email` varchar(100) default NULL,
  `created_at` datetime NOT NULL default '0000-00-00 00:00:00',
  PRIMARY KEY  (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
";

my_dbDelta( $sql );

如果数据库中不存在 wp_my_table 表,my_dbDelta() 函数会生成 CREATE TABLE 语句,创建该表。如果数据库中已经存在 wp_my_table 表,但是表结构与 CREATE TABLE 语句不一致,my_dbDelta() 函数会生成 ALTER TABLE 语句,修改表结构。

dbDelta() 的局限性:

虽然 dbDelta() 很强大,但它也有一些局限性:

  • 不支持删除字段: dbDelta() 不会自动删除数据库中已存在的字段,即使这些字段在 CREATE TABLE 语句中不存在。
  • 不支持修改主键: 修改主键可能会导致数据丢失,所以 dbDelta() 不支持修改主键。
  • 复杂 SQL 语句支持有限: 对于一些复杂的 SQL 语句,例如包含子查询或存储过程的语句,dbDelta() 可能无法正确解析。

总结:dbDelta() 的精髓

dbDelta() 的精髓在于对比和变更。它通过对比 CREATE TABLE 语句和数据库中实际表的结构,找出差异,然后生成 ALTER TABLE 语句,让数据库表的结构和你想要的保持一致。虽然 dbDelta() 有一些局限性,但它仍然是 WordPress 数据库操作中一个非常重要的工具。

扩展阅读:

希望今天的讲座能帮助你更好地理解 dbDelta() 函数的工作原理。如果有什么问题,欢迎提问!

发表回复

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