级别: 中级 Alister Lewis-Bowen (alister.lewisbowen@gmail.com), 高级软件工程师, IBM
2007 年 8 月 17 日 在本系列中,IBM® Internet Technology Group 利用一个免费的软件套件,为一家虚拟的企业(International Business Council)设计、开发及部署外部网的 Web 站点。在这一期中,我们将探索 Drupal 数据库抽象层,了解如何在开发 Web 站点时有效地利用它。发现适于模块开发人员的最佳实践以及关于使用 Drupal 数据库功能的详细内容。您还能学会实现必需的代码来支持新数据库 —— IBM DB2® Express-C。
简介
在这个 系列 的文章中,学习如何为一家虚拟的企业 —— International Business Coucil(IBC) —— 创建一个定制的 Web 站点。该站点需要的功能包括文档存储、讨论组、专门工作组、研讨会进度安排和排定会谈的说明。
在这篇文章中,您将探索 Drupal 数据库层,这是一个非常瘦的代码层,包装了低级 PHP 数据库例程。Drupal 标准提供对 MySQL 和 PostgreSQL 的支持,但如果它支持 PHP 接口,那么添加一个额外的数据库相对来说也会简单一些。作为一名模块开发人员,您应尽可能地利用这一数据库层来节省开发时间,并提高您的模块的可移植性。
Drupal 的数据库层拆分为两个主文件。第一个文件是 includes/database.inc,它定义了不特定于具体数据库实现的函数。该文件包含两个您会在开发模块时遇到的函数:
db_query()
| 向数据库发出查询的主函数 |
db_query_range()
| 将查询结果限制在特定范围内 |
第二个文件是 includes/database.<type>.inc,它定义了提供特定于各数据库类型的原始 PHP 函数的抽象所需的全部函数。Drupal 默认支持 MySQL 和 PostgreSQL,但也可添加其他数据库,只需简单地实现可在 includes/database.mysql.inc 中找到的用于该数据库的函数即可。在本文中介绍到的常用函数如下:
db_result()
| 从上一次查询返回一个结果 |
db_fetch_object()
| 将下一行作为 PHP 对象返回 |
db_fetch_array()
| 将下一行作为值数组返回 |
db_num_rows()
| 返回上一次查询所返回 的结果行数 |
db_affected_rows()
| 返回上一次查询所更改 的行数 |
Drupal 在整个代码基(code base)中使用自己的数据库抽象层。而有两个特殊的应用是展示其合理用法的良好、独立的例子:
下面将详细介绍这两种系统。
分页系统
许多 Web 站点都提供在多个连续页面中显示的内容。浏览搜索结果正是此类行为的一个例子。用户输入搜索词,然后将其提交给站点。如果搜索结果的数量超出了一个页面能够显示的总数,分页系统将被调用。图 1 展示了分页系统发挥作用的一个例子。
图 1. 使用分页系统的 Drupal Web 站点上的搜索结果
您可能会问:“这跟 Drupal 数据库抽象层有什么关系?” 答案就是:pager_query() 函数使用 db_query_range() 函数来选择恰当的结果页。
基本上,分页系统是一个构建在数据库抽象层之上的小型应用程序,它提供了一个较为丰富的数据库接口,还有定制输出所必需的主题元素。图 2 所示的分页系统的关键函数如下:
pager_query()
| 将对一个特定结果页的查询发送到数据库 |
theme_pager()
| 显示查询分页情况的主题主函数。其他所有主题函数都是在此函数中使用的。 |
theme_pager_first()
| 显示第一页结果的链接(图 2,A 部分) |
theme_pager_last()
| 显示最后一页结果的链接(图 2,E 部分) |
theme_pager_link()
| 显示一个特定结果页的链接(图 2,所有部分) |
theme_pager_list()
| 显示邻近结果页的列表(图 2,C 部分) |
theme_pager_next()
| 显示下一个结果页的链接(图 2,D 部分) |
theme_pager_previous()
| 显示前一个结果页的链接(图 2,B 部分) |
图 2. 浏览分为多页的搜索结果的页面控件示例
上面我们已经介绍了分页系统的组件,下面来看一个示例。我们将在示例中使用上一篇文章介绍的公告模块。
分页示例
在这个例子中,我们希望使站点管理员能够查看已输入 IBC Web 站点的所有公告。这是一个非常常见的模式,也是 Drupal 分页系统的极佳用例。首先在 announcement_menu() 中定义 URL 及其相应的菜单项条目。代码如 清单 1 所示。
清单 1. 添加到 announcement_menu 以调用分页代码
$items[] = array('path' => 'announcements/pager',
'title' => t('Announcements Pager Example'),
'access' => user_access('administer site'),
'type' => MENU_CALLBACK,
'callback' => 'announcement_pager');
|
我们将 URL announcements/pager 连接到函数 announcement_pager(),仅允许站点管理员查看完整的公告列表。现在我们创建代码,来显示带有用于导航的分页控件的页面,如 清单 2 所示。
清单 2. 回调函数 announcement_pager
function announcement_pager() {
$query = "SELECT n.nid, n.created FROM {node} " .
"n WHERE type = 'announcement' ORDER BY n.created DESC";
$result = pager_query(db_rewrite_sql($query), 10);
while ($announcement = db_fetch_object($result)) {
$output .= node_view(node_load($announcement->nid), 1);
}
$output .= theme('pager', NULL, 10);
return $output;
}
|
这段代码发出查询,并使用了每页限为 10 条结果的分页系统。访问 URL announcements/pager 时,您将看到类似于 图 3 的页面。
图 3. 使用分页系统的公告
Drupal 分页系统是数据库应用层小型应用程序的一个极好的例子。它使用 db_query() 和 db_query_range() 来提供可能较大的数据集的分页结果。
管理模块数据库模式
本系列的 第 6 部分 介绍了为公告而创建新模块的步骤。要在数据库中存储额外的数据,就需要公告模块。我们介绍了额外的数据库表,还提供了用于向数据库添加表的 SQL CREATE TABLE 语句。添加 Drupal 框架以外的表是有问题的,因为这会使升级极为困难,在某些情况下甚至几乎不可能升级。
为了简化模块维护,并为模块开发人员提供一条顺利的升级途径,Drupal 提供了一种机制,通过一个 module.install 文件自动更新数据库。这个文件包含两段信息,我们将以 第 6 部分 的公告模块为实例详细介绍此文件。公告模块使用 清单 3 中的 SQL 代码来添加新数据库表。
清单 3. 创建支持公告模块的额外数据库的 SQL 代码
CREATE TABLE announcement (
nid int(10) unsigned NOT NULL default '0',
abstract varchar(255) default '',
publish_date integer NOT NULL default '0',
expiration_date integer NOT NULL default '0',
PRIMARY KEY (nid)
);
|
创建一个 .install 文件
.install 文件是一个非常简单的文件,它包含两个或更多的函数。第一个函数是 Drupal 的挂钩函数 hook_install(),这一部分将介绍此函数。它用于在初次启用模块时安装必要的数据库结构。第二个函数和所有后续函数都是用于管理模块的数据库模式更新的。这些函数和更新过程将在下一部分 管理模式更新 中介绍。
创建一个 .install 文件是非常简单的。只要创建一个名为 <module name>.install 的新文件并将其放置在与模块相同的目录中即可。在我们的示例中,文件名是 announcement.install。现在,我们必须创建公告模块的 hook_install() 实现。根据 第 5 部分 和 第 6 部分 概述的挂钩命名规则,清单 4 展示了 announcement_install 的主干实现。
清单 4. .install 文件中的 announcement_install 函数主干
function announcement_install() {
global $db_type;
switch ($db_type) {
case 'mysql':
case 'mysqli':
break;
case 'pgsql':
break;
}
}
|
这个简单的挂钩包含一个 switch / case 语句块,以数据库类型为判断依据。字符串 mysql 和 mysqli 表示符合 MySQL 的数据库代码,字符串 pgsql 表示符合 PostgreSQL 的数据库代码。现在,在 MySQL 的 case 语句中插入创建表的 SQL 代码,切记使用 db_query(),如 清单 5 所示。
清单 5. 使用 SQL 创建公告表的安装挂钩节选
case 'mysql':
case 'mysqli':
db_query("
CREATE TABLE {announcement} (
nid int(10) unsigned NOT NULL default '0',
abstract varchar(255) default '',
publish_date integer NOT NULL default '0',
expiration_date integer NOT NULL default '0',
PRIMARY KEY (nid)
);
");
break;
|
您应该注意到的第一个变化就是:我们使用了 Drupal 数据库层来创建公告表。Drupal 数据库层的使用是非常重要的。在 第 6 部分 中,您学习了如何以一种完全绕开 Drupal 数据库层的方式安装表。但那并不是好习惯,特别是在您打算将模块发布到社区时。您应使用数据库和安装函数,在站点的部署过程中提供灵活性。
应注意的第二个变化是:为表名称使用的 SQL 代码似乎是非法的 —— {announcement}。只要在 SQL 中引用一个表,就总是应该将其包含在大括号之间,就像这样:{announcement}。这是允许站点管理员使用一个字符串为所有 Drupal 表加前缀的机制的一部分。例如,如果管理员决定,所有 Drupal 表都必须加上 ibc_ 前缀,那么表名称 {announcement} 将在调用 db_query() 的过程中扩展为 ibc_announcement。如果您在 Drupal 和其他应用程序之间共享一个数据库,那么这种特性就是必不可少的,在其他许多情况下也是如此。
另外一个可能引起迷惑的地方是:仅在模块在模块管理页中初次启用时,此挂钩才会被调用。如果您在此后禁用了该模块,其数据库表也不会被移除。在您决定重新启用它时,hook_install() 不会再次执行。
管理模式更新
现在您已经创建了一个模块安装挂钩,它会创建必要的数据库对象,那么在您需要修改这些数据库对象时会发生什么情况呢?幸运的是,Drupal 提供了另外一个挂钩 hook_update_<N>(),其中 <N> 表示更新次数,这个更新此处从表示初次更新的 1 开始。因而,任何模块的初次更新函数的形式都是 <module name>_update_1()。此函数更新模块的模式版本和相关联的数据库对象。
每次您更新模块的数据库模式时,例如向一项属性添加一个 NOT NULL 修饰符,都应在下一个可用的 hook_update_<N>() 中记录下更新,并使 <N> 递增。在您释放或部署模块时,您和您所有的用户都可以通过一个简单的步骤更新到最新版本,也就是获取可用的最新模式。
让我们来利用 第 6 部分 中的公告模块,添加另外一个属性,由它来记录公告的重要程度。我们将此属性称为 priority。新的 CREATE TABLE 操作如 清单 6 所示。
清单 6. 经过更新、包含 priority 属性的公告表的创建
case 'mysql':
case 'mysqli':
db_query("
CREATE TABLE {announcement} (
nid int(10) unsigned NOT NULL default '0',
abstract varchar(255) default '',
publish_date integer NOT NULL default '0',
expiration_date integer NOT NULL default '0',
priority tinyint(2) default '0',
PRIMARY KEY (nid)
);
");
break;
|
我们需要为公告模块的任意新安装更改 hook_install() 中的 SQL 代码。现在,必须为公告模块的已有安装提供恰当的 hook_update_<N>()。在公告模块的情况下,必要的 PHP 代码如 清单 7 所示。
清单 7. 添加了 priority 属性的公告模块的数据库初次更新挂钩
function announcement_update_1() {
global $db_type;
switch ($db_type) {
case 'mysql':
case 'mysqli':
db_query('ALTER TABLE {announcement} ADD COLUMN priority
tinyint(2) default \'0\' ');
break;
case 'pgsql':
break;
}
}
|
与 hook_install() 的情况一样,我们为各种数据库类型提供了独立的 case 语句。请注意,使用的依然是 db_query(),并且将表名放在 { 和 } 之间。我们无法足够地强调编写可移植代码的重要性。您的模块将由许多人、在许多情况下使用,您必须为他们提供可能情况下的最高灵活性。如果您的公告模块新版本有较多的更新,那么应将其添加到 announcement_update_1() 函数中。
现在您已经创建了安装和更新公告模块所需的全部 PHP 和 SQL 支持代码,接下来需要指示 Drupal 来执行模式更新。这非常简单,使用您的浏览器和 Drupal 站点开始即可。首先,在尝试更新数据库模式之前备份您的数据库,并验证备份正确而完整。然后作为 Drupal 管理用户(uid == 1 的用户)登录,并转向 http://your.drupal.site/update.php。作为结果显示的页面如 图 4 所示。
图 4. Drupal 更新页
选择链接 Run the database upgrade script 来开始升级过程。您应看到一个类似于 图 5 的屏幕。根据您所安装的模块数量,您的 Drupal 安装可能有更多的下拉列表框供选择。由于我们正在升级的是公告模块,所以确保其下拉列表框中选中了最高的版本号,然后选择 Update。Drupal 处理模块的所有更新,从已安装的版本开始,到您所选择的版本结束。
图 5. IBC 安装的更新页
展开更新部分,确认刚刚为公告模块创建的更新已选中。接下来选择 Update。如果看到任何错误,请将数据库还原为之前创建的备份,并再次尝试。
如前所述,在模块管理页中初次启用模块时,hook_install() 将运行、数据库表将被创建。Drupal 还会将已安装的模块的模式版本设置为编号最高的 hook_update_<N>()。这有效地将相应模块标记为已安装且更新过的。
尽管上述对 .install 文件函数的讨论是围绕着数据库层展开的,但也并没有妨碍您关注数据库结构安装。如果您的模块需要额外的初始化和更新,只要将那些初始化项放在 hook_install() 或 hook_update_<N>() 中即可。这可能是增加 Drupal 变量、创建一个带有一组默认词的词汇表或者是您能想像到的任何事情。
维护数据库可移植性
Drupal 的数据库抽象层是在 PHP 数据库例程之上的一个瘦表层。从性能和复杂性的角度来考虑,这非常好,但它可能会促使您编写出不可移植的数据库代码。在提到数据库可移植性(database portability) 这个术语时,我们指的是:除了编写可跨多个数据库工作的 SQL 代码之外,还编写出恰当地使用了 Drupal 数据库抽象层、发挥了最大潜能的代码。您的 Drupal 模块可能会在各种各样的场景和配置下使用,因此良好的设计和预先的规划是非常重要的。
下面的示例展示了良好的数据库调用和糟糕的数据库调用之间的差别。
表名称
清单 8. 不正确:使用无格式表名称
db_query("SELECT * FROM node");
|
清单 9. 正确:将所有表名称包含在大括号之间,考虑到了表前缀
db_query("SELECT * FROM {node}");
|
Drupal 允许管理员为 Drupal 表添加前缀。前缀使管理员能够在两个 Drupal 站点之间共享表、在一个数据库中包含多个 Drupal 站点,以及在有多个主机的环境中更好地组织其数据库。如果管理员已将表前缀设置为 drupal_,那么 node 表将命名为 drupal_node,此查询将失败,因为名为 node 的表不存在。
清单 9 中的查询将考虑到 Drupal 站点管理员应用的任何数据库表前缀。如果您的模块将在复杂的 Drupal 设置或限制数据库函数的主机环境中使用,那么这个简单的更改是非常重要的。
嵌入的参数
清单 10. 不正确:直接将参数嵌入在查询字符串中
db_query("SELECT * FROM {node} WHERE nid = $nid");
|
清单 11. 正确:使用设置为恰当数据类型的变量占位符
db_query("SELECT * FROM {node} WHERE nid = %d", $nid);
|
这种方法的主要问题就是:如果变量 $nid 包含任何特殊字符,例如 ' 或 ",您会更容易受到 SQL 注入式攻击。(即恶意用户以导致问题为目的,尝试获取数据库以外的更多或不同信息。)为避免出现这种情况,您必须在向数据库发送特殊字符之前,将这些字符转义。遗憾的是,用于使值转义的方法在不同的数据库之间各有差异,这也就意味着,您需要了解 Drupal 使用的数据库类型,并相应地调整您的代码。幸运的是,如果您恰当地使用了 db_query() 函数,Drupal 会替您处理所有这一切。
在 清单 11 中,$nid 变量将根据数据库类型的规则进行转义,其值也会恰当地根据其类型代入查询。
值范围
清单 12. 不正确:在查询中嵌入 MySQL 的 LIMIT 子句
db_query("SELECT * FROM {node} WHERE nid = %d LIMIT 1, 10", $nid);
|
清单 13. 正确:使用 db_query_range() 从数据库中选择值范围
db_query_range("SELECT * FROM {node} WHERE nid = %d", $nid, 1, 10);
|
LIMIT 子句并非 SQL 标准的一部分,也不是对于所有数据库都可移植的。例如,此查询在 DB2 Express-C 上是无法工作的,使用这种形式的查询的模块必须首先重写,之后才能使用。
清单 13 利用了特定于任意数据库的 SQL 代码来检索结果集中的前 10 行。
这些就是 Drupal 数据库使用中诸多 “最佳实践” 中的一部分。随着 Drupal 的发展,其数据库层和使用它的最佳方法也会随之演进。
IBM DB2 Express-C
DB2 Express-C 是面向社区的一个 DB2 版本。它是 DB2 数据库服务器的全功能版本,可在日常生产和开发环境中使用。对于可利用的资源数量是有限制的(最多为两个双核 CPU、4GB 的 RAM),除此之外,没有其他限制值得一提。关于 DB2 Express-C 的更多信息,请参见 参考资料 部分。
在 Drupal 中添加对 DB2 Express-C 的支持是详细了解数据库抽象层的一种好方法。我们还能看出,在实现第三个数据库时,抽象层的表现有多么出色。在下一部分中,我们将简要介绍如何安装 DB2 Express-C,并查看如何为 Drupal 创建必要的代码。有必要理解,对于社区来说,这些代码只是一个起点,绝非最终实现。
安装
将 DB2 Express-C 安装包下载到本地系统之后,将压缩文件解压到您的计算机上,双击 db2setup.exe 启动安装过程。您应看到如 图 6 所示的面板。
图 6. IBM DB2 Express-C 欢迎面板
选择 Next 开始安装过程。现在,您应看到如 图 7 所示的面板。对于我们的需求,Typical 安装类型已足够。如果您的空间不足或者是这方面的专家,也可以使用其他安装方法。选择 Next 继续下一步。
图 7. 选择安装类型
选择安装和/或响应文件,如 图 8 所示。然后选择 Next。
图 8. 选择安装和/或响应文件
选择安装文件夹,如 图 9 所示,然后选择 Next。
图 9. 选择安装文件夹
输入管理服务器的 User name 和 Password,如 图 10 所示。选择 Next 继续下一步。
图 10. 为 DB2 Administration Server 设置用户信息
图 11 展示了配置 DB2 实例的方法。选择 Next 继续下一步。
图 11. 配置 DB2 实例
图 12 是安装开始之前的最后一个对话框。选择 Finish 开始复制文件。
图 12. 开始复制文件并创建一个响应文件
图 13 中的对话框允许您创建一个 First Steps 浏览器配置文件。
图 13. 创建一个 DB2 First Steps 浏览器配置文件
至此,IBM DB2 Express-C 的安装就完成了。
配置 DB2 和 PHP
必须首先为 PHP 安装一个扩展,之后它才能与 DB2 数据库通信。从 PHP 连接到 DB2 的方法有两种。第一种方法是使用 Open Database Connectivity(ODBC)PHP 应用程序接口(API)和兼容底层 ODBC、特定于数据库的模块。这种方法较为通用,且可移植,但可能会致使性能略微降低。
第二种方法是使用本地 DB2 模块,它利用 DB2 Call Level Interface(CLI),并直接与数据库通信。使用此模块使您能够利用任意特定于 DB2 的函数,同时公开 Drupal 数据库 API。关于本地 DB2 模块的更多信息,请参见 参考资料 部分。
此模块的安装是非常简单的,但应按照模块的参考页中提供的说明进行安装。(如果您不了解如何安装 PHP 扩展,在 参考资料 中可以找到更多信息。)必须首先安装好一个带有应用程序开发包的 DB2 版本,之后才能安装 PHP DB2 扩展。Linux™ 上的基本安装命令如 清单 14 所示。
清单 14. 在 Linux 中构建和安装 IBM DB2 PHP 扩展的命令
$ cd ibm_db2
$ phpize
$ ./configure --with-IBM_DB2=/path/to/DB2
$ make
$ su - root
# make install
|
这些命令会启动 PHP 扩展构建环境,配置构建环境以使用特定版本的 DB2,还会编译并安装扩展。根据您所使用的发布版,可能要对 清单 14 中的命令略加更改。
如果您使用的是 Windows®,那么可以 下载 IBM DB2 PHP 扩展。
根据您的 PHP 版本选择扩展,在扩展目录中保存文件。如果您按照本系列的 第 3 部分 中介绍的步骤进行了操作,那么这个目录应该是 C:\PHP\ext。保存文件后,立即在启用了其他扩展的 php.ini 文件中添加 清单 15 所示的代码行。
清单 15. 启用 IBM DB2 PHP 扩展
extension=php_ibm_db2.dll
|
安装好 IBM DB2 PHP 扩展后,您需要让 PHP 了解它应使用哪个 DB2 实例。清单 16 展示了必须添加到 php.ini 文件中的必要信息。
清单 16. 向 php.ini 文件中添加 IBM DB2 实例信息
ibm_db2.instance_name = <name of your instance here>
|
DB2 现在应已启用,并在 PHP 内得到了配置。可通过列出 PHP 了解的扩展验证安装。您应在列表中看到 ibm_db2,列表应类似于 清单 17。
清单 17. 验证 IBM DB2 PHP 扩展是否安装成功
[PHP Modules]
bcmath
calendar
com_dotnet
ctype
date
dom
ftp
hash
iconv
ibm_db2
ldap
libxml
mysql
odbc
pcre
Reflection
session
SimpleXML
SPL
standard
tokenizer
wddx
xml
xmlreader
xmlwriter
zlib
|
Drupal 与 IBM DB2 Express-C
要使 Drupal 能够使用 MySQL 或 PostgreSQL 以外的数据库后端,需要做很多工作。在 Drupal 能够使用您的数据库之前,必须完成两个开发任务。第一项任务是生成初始数据库模式脚本。以下文件分别用于初始化 MySQL HE PostgreSQL 数据库:
- database/database.4.1.mysql
- database/database.pgsql
这些文件包含 SQL 表定义及其初始值。清单 18 展示了用于 MySQL 数据库的 poll 表,清单 19 展示了用于 DB2 数据库的此表。
清单 18. 用于 MySQL 数据库的 Poll 表
CREATE TABLE poll (
nid int(10) unsigned NOT NULL default '0',
runtime int(10) NOT NULL default '0',
active int(2) unsigned NOT NULL default '0',
PRIMARY KEY (nid)
)
DEFAULT CHARACTER SET utf8;
|
清单 19. 用于 DB2 数据库的 Poll 表
CREATE TABLE "POLL" (
"NID" INTEGER NOT NULL WITH DEFAULT 0,
"RUNTIME" INTEGER NOT NULL WITH DEFAULT 0,
"ACTIVE" INTEGER NOT NULL WITH DEFAULT 0)
IN "TS_DRPL"
;
|
各数据库都有自己的语法特点,这也就意味着,您必须将 MySQL 或 PostgreSQL 转换成您所用数据库的语法。
第二个任务就是实现 PHP 代码,提供特定于数据库的函数。可在文件 includes/database.mysql.inc 中找到支持 MySQL 的 PHP 代码。该文件中有超过一打的函数,都是您必须专门为您的数据库实现的。最后,完成这些任务之后,必须彻底地测试您的 Drupal 安装,以确保新数据库得到了恰当的使用。
在下面的部分中,我们将介绍用于创建 SQL 初始化脚本和 Drupal PHP 数据库代码的过程和工具。要理解的重要一点是:这只是使 Drupal 与 DB2 Express-C 协同工作的起点,绝非经过充分测试的完整解决方案。
转换数据库模式
为 Drupal 启用新数据库的第一步就是将数据库模式从 MySQL 转换为恰当的形式。建议您使用可用的任何工具来自动化这一任务:何必浪费好几个小时的时间去做那些机器几秒钟就能完成的事情呢?IBM Migration Toolkit(MTK)提供了一个用于此任务的很好的起点。我们曾使用 MTK 成功地在几分钟内将一个初始的空 Drupal 数据库移植到 DB2 数据库。(在 参考资料 中可以找到关于 MTK 及其用法的更多信息。)
尽管 MTK 自动化了此过程的繁重工作,但它未提供可用于设置 Drupal 的完整安装脚本。在移植过程中,我们遇到了以下几个问题:
- 缓冲池和表空间配置
- Long Object(LOB)一致性
- 具有默认值的表属性
- 默认数据初始化
可以快速、轻松地解决这些问题。我们认为,最重要的问题就是为数据库创建缓冲池、表空间和临时表空间,如 清单 20 所示。
清单 20. 创建缓冲池、表空间和临时表空间
------------------------------------------------
-- Change to your own bufferpool length and pagesize
------------------------------------------------
CREATE BUFFERPOOL "BP_DRPL" IMMEDIATE SIZE 1000 AUTOMATIC PAGESIZE 4K;
CREATE TABLESPACE "TS_DRPL" PAGESIZE 4K BUFFERPOOL "BP_DRPL";
CREATE SYSTEM TEMPORARY TABLESPACE "TMPTS_DRPL" PAGESIZE 4K BUFFERPOOL "BP_DRPL";
|
这个问题的最后一部分就是更新表创建定义,以将表放置在新的表空间 TS_DRPL 之中。为此,可将 清单 21 所示的片段添加到数据库中所有表的 CREATE TABLE 指令中。
清单 21. 将此片段添加到 CREATE TABLE 指令中,以将表放在恰当的表空间之中
下一步就是将所有不同的 LOB 更改为具有恰当大小的 CLOB。我们使用了一个全局查找与替换操作来更改所有需要重新定义的 LOB,如 清单 22 所示。
清单 22. 将 LOB 替换为 CLOB
CLOB(10M) NOT LOGGED NOT COMPACT
|
在这里,还能还需要添加其他子句,例如 NOT NULL 或 WITH DEFAULT,具体取决于原始的 MySQL 表定义。
第三个任务就是向数据库初始化脚本添加必要的 WITH DEFAULT 子句。这是整个移植过程中最乏味的部分。我们要一个接一个地浏览所有表,根据原始 MySQL 初始化脚本添加必要的默认值子句。这是因为 Drupal 在查询中广泛地应用了默认值,如果没有这些默认值,Drupal 就无法正常工作。
最后,MySQL 初始化脚本将许多默认值插入新创建的表中。为要与 Drupal 设置机制一致的 DB2 初始化脚本实现同样的行为。现在,您可以 下载 完整的初始化脚本。
创建数据库接口文件
此过程的下一步就是创建与数据库交互的必要 PHP 代码。这些代码遵循 includes/database.mysql.inc 中的预定义函数接口。实现过程的第一步是将完整的 MySQL 实现文件复制到 includes/database.ibmdb2.inc 中。请注意文件名中的 ibmdb2。此标记用作数据库类型标识符,将在 sites/.../settings.php 文件的数据库连接字符串中使用。使用 PHP DB2 模块实现 Drupal 数据库接口非常简单,但有一些需要注意的 “要点”。
- 实现
db_query_range()
- MySQL 提供了一个子句,使您可以显示一个查询返回的结果数。这些结果可以是所返回结果的开头、中间或末尾。DB2 Express-C 未提供与 MySQL 相同的语法元素,此函数可使用 清单 23 所示的形式实现。
清单 23. 实现与 db_query_range 相同的函数
SELECT c1, c2, ... FROM (SELECT ROW_NUMBER() OVER() AS rn, t.* FROM (<original query>)
AS X WHERE rn BETWEEN n AND m
|
该函数使用了 ROW_NUMBER() OVER() 结构向原始查询返回的行添加必要的范围信息。此约束是通过使用 BETWEEN n AND m 结构实现的,它类似于 LIMIT num OFFSET o。清单 24 展示了 db_query_range() 函数相关的部分代码。
清单 24. db_query_range 函数的相关代码
$query = preg_replace("/SELECT/i", "SELECT O.* FROM
(SELECT I.*, ROW_NUMBER() OVER () sys_row_num FROM (SELECT", $query);
if(!$query) {
return false;
}
$last_record = $count + $from;
$query .= ") AS I) AS O WHERE sys_row_num BETWEEN $from AND $last_record";
|
这段代码创建了恰当的限制查询,此查询最终被发送到 _db_query() 中的特定数据库实现。
- 实现
db_num_rows()
- 阅读了
mysql_num_rows() 和 db2_num_rows() 函数的文档之后,您会注意到,它们用于解决不同的问题。DB2 函数更像是 mysql_affected_rows(),用在一个 UPDATE, INSERT 或 DELETE 查询之后。为了实现 Drupal db_num_rows() 函数,我们需要返回最后一条请求结果数而非结果本身的查询。这是一个分为两步的过程。第一步需要对 _db_query() 函数略加修改,如 清单 25 所示。
清单 25. 为实现 db_num_rows 函数对 _db_query() 进行的修改
global $last_query;
if ($result) {
$last_query = $query;
return $query
}
|
这段代码将所发出的最后一条查询记录在一个全局变量中,以便在 db_num_rows() 调用中重用它。
第二步是对第一步中记录下的查询所返回的结果数进行记数。您将看到 清单 26 所示代码段的使用。
清单 26. 查询结果的代码段
return db_result(db_query("SELECT COUNT(*) FROM ($last_query) AS C"));
|
此函数发出相同的查询,但仅请求所找到的结果数量。这个实现并不理想,因为它使用了全局变量跨多个函数调用保持信息,但这是一种有效的方法,可在不修改核心 Drupal 数据库抽象层的情况下在 Drupal 中支持 DB2 Express-C。
关于我们为 IBM DB2 Express-C 提供的 Drupal 数据库抽象层初始实现的完整信息,请参见 参考资料 部分
结束语
在这篇文章中,我们探索了 Drupal 数据库抽象层,它允许 Drupal 通过一个通用的 API 支持许多不同的数据库。您还了解了 Drupal 分页系统,它允许您以一种面向页面的方式浏览较大的数据集。分页系统是数据库抽象层的一个示例应用程序。您了解了如何着手使 Drupal 使用 IBM DB2 Express-C,这是 IBM 的 DB2 产品的一个免费版本。另外,您还学会了如何编写可移植的数据库代码,以及如何一个版本又一个版本地安装和维护您的模块的数据库模式。
请继续关注本系列的下一篇文章。您将看到我们对一个外部网的定义,使其适用于客户需求,还会学习用于将 IBC 站点作为一个外部网的 Web 站点创建的实现技术。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| ibmdb2 代码 | i-osource9code.zip | 9KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | 
|  | Alister Lewis-Bowen 是 IBM 的 Internet Technology Group 的高级软件工程师。他从 1993 年开始作为 IBM 英国职员从事互联网和 Web 技术方面的工作。Alister 后来到美国为 IBM 赞助的体育活动的 Web 站点工作,之后成为 ibm.com 的高级网管。他当前正在帮助创建语义 Web 原型。 |
对本文的评价
|