第四章 数据库API

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    Drupal 7数据库API提供了一个标准的、与数据库供应商无关的抽象层,用来访问数据库服务器。该API尽可能的保留了SQL的语法和功能,同时还提供:

· 轻松支持多个数据库服务器;

· 允许开发者使用更复杂的功能,比如事务;

· 为查询语句的动态构建提供了一个结构化接口;

· 执行安全检查和其它良好习惯;

· 为模块提供了一个干净的接口,用来拦截和修改站点的查询语句。

    数据库API文档(http://api.drupal.org/api/group/database/7)直接来自于代码中的注释。这个手册对标准文档进一步的做了补充,为模块开发者提供了与数据库交互的教程,同时方便其他人员从概貌上把握Drupal的数据库系统。注意本手册没有涵盖数据库API的所有特性。

    数据库API采用了面向对象的设计理念,因此本文档假定大家对面向对象有所了解。不过,大多数操作都有面向过程的形式,程序员为了代码的可读性,仍然可以使用面向过程的形式。


Drupal版本:

1 一般概念

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    Drupal数据库层是建立在PHP的PDO基础之上的。PDO提供了一个统一的、面向对象的API,用来访问不同的数据库,但是它没有对不同数据库的方言提供抽象。


Drupal版本:

10 依赖于PDO

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    由于Drupal的数据库层依赖于PHP的PDO库,所以你在选用主机时,确保你的PHP是支持PDO的。


Drupal版本:

11 静态查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    Drupal中最常见的查询方式就是静态查询。静态查询通常会被一字不差的传递给数据库。通常只有选择查询才可以是静态的。

    对于特别简单的查询,通常应该适用静态查询机制。对于更加复杂的查询,比如动态生成的查询或者其它类似查询,则应该使用动态查询。

    静态查询的内部方式是使用query方法:

<?php
$result $conn->query("SELECT nid, title FROM {node}");
?>

    经过过程化包装的方式,则更常用:

<?php
$result db_query("SELECT nid, title FROM {node}");
?>

    上面db_query()函数的调用,等价于下面的语句:

<?php
$result Database::getConnection()->query("SELECT nid, title FROM {node}");
?>

    为什么过程化的版本更受欢迎?其中因为Drupal本身主要采用过程化的方式,此外使用db_query是为了保持对Drupal6的兼容,还有就是这样语法更加简洁。

    db_query()有3个参数。第一个参数就是查询字符串,在这里所有的数据库表名需要使用花括号进行封装,此外在合适的地方可以使用占位符。第2个参数是包含占位符值的数组。第3个参数是一个可选的包含配置指令的数组,用来指示查询的执行方式。


Drupal版本:

12 前缀化

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

在静态查询中,所有的数据库表名都必须使用{}包装。这样,当你的Drupal安装使用了数据库前缀时,数据库系统就可以在表名前面添加一个前缀字符串。通过前缀化,就可以实现在同一个数据库上运行多个站点,或者在特定的情况下,可以实现多个站点之间的表共享。


Drupal版本:

13 占位符

    作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

占位符表示为了执行查询,将会向该位置插入对应的文字。通过将其从查询本身中分离出来,我们允许数据库区分SQL语法与用户输入值,因此可以避免SQL注入。

<?php
$result db_query("SELECT nid, title FROM {node} WHERE created > :created", array(
  ':created' => REQUEST_TIME 3600,
));
?>

    上面的代码,将选择过去一小时(3600秒)内创建的所有节点。在查询运行时,占位符:created将会被动态的替换为REQUEST_TIME – 3600的当前值。一个查询可以包含任意多个占位符,但是占位符之间,即便是它们的值相同,但是它们的名字必须是唯一的。根据具体情况,占位符数组可以内联指定(如上例所示),也可以先构建好,然后再传递过来。数组内,元素的顺序对结果并不影响。

    以"db_"开头的占位符是为内部系统所保留的,绝不应该在自定义模块中使用。

    无论占位符的类型如何,它们都不能被转义或者添加引号。这是因为它们是单独传递给数据库的,数据库服务器可以区分查询字符串和占位符的对应值。

<?php
// 错误:
$result db_query("SELECT nid, title FROM {node} WHERE type = ':type'", array(
  ':type' => 'page',
));

// 正确:
$result db_query("SELECT nid, title FROM {node} WHERE type = :type", array(
  ':type' => 'page',
));
?>


Drupal版本:

14 占位符数组

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    Drupal的数据库层,包含了有关占位符的一个附加特性。对于一个占位符,如果传递给它的值是一个数组,它就会自动地将占位符扩展为逗号分隔的占位符列表,分别对应于数组中的元素。这意味着开发者不需要考虑他们需要多少个占位符。

    我们通过实例来学习一下这一特性:

<?php
// This code:
db_query("SELECT * FROM {node} WHERE nid IN (:nids)", array(':nids' => array(1342144));

// Will get turned into this prepared statement equivalent automatically:
db_query("SELECT * FROM {node} WHERE nid IN (:nids_1, :nids_2, :nids_3)", array(
  ':nids_1' => 13
  ':nids_2' => 42
  ':nids_3' => 144,
));

// Which is equivalent to the following literal query:
db_query("SELECT * FROM {node} WHERE nid IN (13, 42, 144)");
?>


Drupal版本:

15 查询选项

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    db_query()的第3个参数,也就是连接对象query方法的第3个参数,是一个选项数组,用来指示查询应该如何执行。对于大多数的查询来说,常用的指令只有两个。其它值则大多数时候在内部使用。

    "target"键用来指定所要使用的目标数据库。如果没有指定,则使用默认的"default"。在下面的例子中,有效值只有一个"slave",这表示如果存在一个从数据库,查询则运行在它的上面。

    "fetch"键用来指定用何种方式,来获取查询返回的结果记录。有效的值包括PDO::FETCH_OBJPDO::FETCH_ASSOCPDO::FETCH_NUMPDO::FETCH_BOTH,或者是一个表示类名的字符串。如果指定的是一个字符串,取回的每条记录都会被放在该类的一个新创建的对象中。其它值对应的行为是由PDO定义的,将返回的记录相应的存放在stdClass对象中、关联数组中、数值数组中、或者数值和关联数组中。有关PDO的相关信息可参考http://php.net/manual/en/pdostatement.fetch.php。默认值为PDO::FETCH_OBJ,通常情况下,尽可能的使用默认值就可以了,在特定情况下,也可以改用其它值。

    下面的例子,将在从服务器上(如果存在的话)执行一个查询,并将从结果集中返回的记录存放在关联数组中。

<?php
$result db_query("SELECT nid, title FROM {node}", array(), array(
  'target' => 'slave',
  'fetch' => PDO::FETCH_ASSOC,
));
?>


Drupal版本:

16 结果集

   作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

选择查询总是返回一个结果集对象,它包含零个或多个记录。根据具体情况的不同,有多种方式可以用来从结果集中获取数据。

    最常用的就是使用foreach()循环,对结果集进行迭代处理。

<?php
$result db_query("SELECT nid, title FROM {node}");
foreach ($result as $record) {
  // Do something with each $record
}
?>

    由于所需返回结果的不同,除此以外,还有其它一些方式来获取记录。

    为了明确的获取下一条记录,可以使用:

<?php
$record $result->fetch();            // Use the default fetch mode.
$record $result->fetchObject();  // Fetch as a stdClass object.
$record $result->fetchAssoc();   // Fetch as an associative array.
?>

    如果没有下一条记录,此时则返回FALSE。通常情况下,应该尽可能的避免使用fetch(),而是选择fetchObject()fetchAssoc(),后两个语义明确,便于理解。如果你需要使用PDO所支持的其它获取模式,则可以使用fetch()

    为了从结果集中获取单个字段,可以使用:

<?php
$record $result->fetchField($column_index);
?>

    $column_index的默认值为0,表示第一个字段。

    为了计算返回记录的总数,可以使用:

<?php
$number_of_rows $result->rowCount();
?>

    为了将所有记录放在单个数组中,可以使用下面的任意一种方式:

<?php
// Retrieve all records into an indexed array of stdClass objects.
$result->fetchAll();

// Retrieve all records into an associative array keyed by the field in the result specified.
$result->fetchAllAssoc($field);

// Retrieve a 2-column result set as an associative array of field 1 => field 2.
$result->fetchAllKeyed();
// You can also specify which two fields to use by specifying the column numbers for each field
$result->fetchAllKeyed(0,2); // would be field 0 => field 2
$result->fetchAllKeyed(1,0); // would be field 1 => field 0

// Retrieve a 1-column result set as one single array.
$result->fetchCol();
// Column number can be specified otherwise defaults to first column
$result->fetchCol($column_index);
?>

    注意,fetchAll() fetchAllAssoc()在默认情况下,使用在查询上设置的获取模式(数值数组、关联数组、或对象)。通过向其传递一个新的获取模式常量,就可以修改默认的获取模式了。对于fetchAll(),它是第一个参数。对于fetchAllAssoc(),它是第二个参数。

    由于PHP 支持对返回的对象调用链式方法,所以通常情况下,会完全跳过$result变量,采用下面的简洁方式:

<?php
// Get an associative array of nids to titles.
$nodes db_query("SELECT nid, title FROM {node}")->fetchAllKeyed();

// Get a single record out of the database.
$node db_query("SELECT * FROM {node} WHERE nid = :nid", array(':nid' => $nid))->fetchObject();

// Get a single value out of the database.
$title db_query("SELECT title FROM {node} WHERE nid = :nid", array(':nid' => $nid))->fetchField();
?>


Drupal版本:

17 存到类中

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    也可以把查询的结果存放在自定义类的对象中去。例如,如果我们有一个名为ExampleClass的类,下面的查询则会返回类型为exampleClass的对象。

<?php
$result db_query("SELECT id, title FROM {example_table}", array(), array(
  'fetch' => 'ExampleClass',
));
?>

    如果该类拥有构造函数__construct(),那么将会创建相应的对象,并会向对象中添加相应的属性,接着将会调用__construct()方法。例如,如果你有下面的类和查询:

<?php
class exampleClass {
  function __construct() {
    // Do something
  }
}

$result db_query("SELECT id, title FROM {example_table}", array(), array(
  'fetch' => 'ExampleClass',
));
?>

    将会创建相应的对象,并向该对象中添加属idtitle,接着执行__construct()。这些事件的执行顺序,是由低于PHP5.2的PHP版本中的一个bug(http://bugs.php.net/bug.php?id=46139)引起的。

    如果该对象上有一个__construct()方法,并且该方法需要在属性添加到对象上的前面执行,则可以使用下面例子中所给的方法:

<?php
$result db_query("SELECT id, title FROM {example_table}");
foreach ($result->fetchAll(PDO::FETCH_CLASS PDO::FETCH_PROPS_LATE'ExampleClass') as $record) {
  // Do something
}
?>

    传递给fetchAll的参数,也可以原封不动的用在fetch上。PDO::FETCH_CLASS告诉fetchAll获取返回的结果集,并将值作为属性添加到ExampleClass类型(第二个参数)的对象上。PDO::FETCH_PROPS_LATE告诉fetchAll,先调用__construct(),然后再将结果集作为属性添加到对象上。

 


Drupal版本:

18 动态查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    动态查询指的是由Drupal动态的构建查询,而不是直接提供一个查询字符串。所有的插入、更新、删除、和合并查询都应该是动态的。选择查询可以是静态的,也可以是动态的。因此,一般动态查询指的就是动态的选择查询。

    所有的动态构建的查询都是使用查询对象构建的,而查询对象则源自于对应的连接对象。和静态查询一样,在绝大多数的情况下,我们使用过程化的方式来获取该对象。然而,随后的查询指令,都是采用触发查询对象上的方法的形式,进行调用。


Drupal版本:

19 内容结构

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

1. 概貌

2. 关联

3. 字段

4. 唯一

5. 表达式

6. 排序

7. 随机排序

8. 分组

9. 范围和限制

10. 表排序

11. 条件语句

12. 执行查询

13. 总计查询

14. 调试

    动态的选择查询由db_select()函数开始,如下所示:

<?php
$query db_select('node''n'$options);
?>

    在这个例子中,"node"是查询的基表;也就是说,FROM语句中紧跟着FROM的第一个表。注意,这里没有使用花括号;查询构建器会自动地处理这一点。第二个参数是表的别名;如果没有指定的话,将会使用表名。$options数组是可选的,它与静态查询中的$options数组完全相同。

    动态选择查询可以非常简单,也可以非常复杂。我们在这里只讲述它的基本原理,如果完全展开的话,一本书也未必写的完。


Drupal版本:

2 驱动

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    由于数据库的不同,需要的交互方式也不相同,所以Drupal的数据库层需要为每个数据库类型提供一个驱动。驱动对应文件都放在includes/database/driver在这里driver就是表示该驱动的唯一字符串。大多数情况,驱动的键,就是数据库名字的小写版,比如"mysql", "pgsql","mycustomdriver"

    每个驱动都包含多个类,它们继承自核心数据库系统中的父类。这些特定于驱动的类,可以根据数据库类型的需要,来覆写各种特性。这些特定于驱动的类,它们的命名方式采用“父类名字”+“_”+驱动名字形式。例如,InsertQueryMySQL下的版本的名字就是InsertQuery_mysql


Drupal版本:

20 概貌

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    下面是一个有关users表的相对简单的查询。接下来我们会分析这个查询中的每个构成部分,以及会学习一些高级技巧比如表之间的关联。

<?php
$query db_select('users''u');

$query
  ->condition('u.uid'0'<>')
  ->fields('u', array('uid''name''status''created''access'))
  ->range(050);

$result $query->execute();
?>

    上述代码,大致等价于下面的语句:
$result = db_query("SELECT uid, name, status, created, access FROM {users} u WHERE uid <> 0 LIMIT 50 OFFSET 0");

    这个语句,源自于用户的管理页面所对应的代码,我们在这里做了简化处理,我们在后面的学习中,会进一步的用到这一语句。


Drupal版本:

22 字段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    为了向选择查询添加一个字段,可以使用addField()方法:

<?php
$title_field $query->addField('n''title''my_title');
?>

    上述代码将会指示查询,从别名为"n"的表中选择字段"title",并为其设定一个别名"my_title"。如果没有指定别名,那么就会自动生成一个。在大多数情况下,自动生成的别名就是字段名字本身。在这个例子中,如果没有指定别名,自动生成的别名将会是"title"。如果该别名已经存在,那么别名就是表名+字段名。在这个例子中,它将会是"n_title"。如果这个别名也存在了,那么就会在这个别名后面追加一个计数器,直到找到一个未使用的别名,比如"n_title_2"

    注意,如果你自己创建并填充查询,并且没有指定别名,而默认的别名又不可用,通常此时你的代码中肯定存在一个bug。然而,如果你正在实现hook_query_alter()钩子,由于你不确定都已经使用了哪些别名,所以你应该总是使用自动生成的别名。

    注意,为了选择多个字段,只需要简单得依次调用addField()多次就可以了。注意,在大多数的情况下,字段之间的顺序并不影响结果;如果它影响了,通常意味着该模块中存在一个漏洞。

    我们也可以使用fields()方法,一次添加多个字段:

<?php
$query->fields('n', array('nid''title''created''uid'));
?>

    上述方法等价于调用四次addField(),一次对应一个字段。不过,fields()中不支持为字段指定别名。并且它返回的是查询对象本身,而不是返回生成的各种别名,此时可以对其继续使用链式方法。如果你需要知道生成的别名,你可以使用addField(),也可以使用getFields()来访问原始的内部字段结构。

    调用fields()方法,而没有列出字段,此时相当于使用了一个"SELECT *"查询。

<?php
$query->fields('n');
?>

    这将会在查询的字段列表中包含一个"n.*"。此时不会自动生成别名。如果使用SELECT *的表包含了一个字段,而这个字段又恰好在另一个表中被指定,此时,在结果集中,就有可能出现字段名字冲突。在这种情况下,结果集中,只会包含一个同名字段。因此,尽量不要使用SELECT *


Drupal版本:

23 Distinct

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

   有些查询肯能回产生一些重复结果。在静态查询中,可以使用关键字"DISTINCT"来过滤重复的记录。在动态查询中,可以使用distinct()方法。

<?php

//强制过滤结果集中的重复记录。
$query->distinct()
?>

    注意,DISTINCT可能会带来性能上的损失,只有在已经没有其它方法可用的情况下,才会考虑这种方式。


Drupal版本:

24 表达式

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    选择查询构建器支持在字段列表中使用表达式。表达式的例子包括“年龄字段的两倍”,“所有名字字段的总计”,或者是标题字符串的一个子集字符串。注意,很多表达式使用SQL函数,并不是所有的函数都可以跨数据库。因此模块开发者,需要自己确保只使用跨数据库的表达式。相关信息可参考:http://drupal.org/node/773090

    为了向一个查询添加表达式,可以使用addExpression()方法。

<?php
$count_alias $query->addExpression('COUNT(uid)''uid_count');
$count_alias $query->addExpression('created - :offset''uid_count', array(':offset' => 3600));
?>

    上面的第一行,将会向查询中添加"COUNT(uid) AS uid_count"。第二个参数是字段的别名。在个别情况下,别名已被使用,此时将会生成一个新的别名,而addExpression()的返回值将会是正被使用的别名。如果没有指定别名,将会生成一个默认的"expression" (或者 expression_2 expression_3 等等。)

    第三个参数,是一个可选的关联数组,里面包含了供表达式使用的占位符的值。

    注意,有些表达式,只在Group By语句存在的情况下才起作用。对于查询本身的有效性,需要程序员自己来确保。


Drupal版本:

25 排序

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    为了给动态查询添加一个order by语句,可以使用orderBy()方法:

<?php
$query->orderBy('title''DESC');
?>

    上面的代码将会指示查询,按照标题字段的降序排列。第二个参数的可选值有"ASC""DESC",分别表示升序和降序,默认值为"ASC"。注意,这里的字段名字,应该是addField() addExpression()方法中创建的别名,所在,在大多数情况下,你会在这里使用这些方法的返回值,从而确保使用了正确的别名。为了按照多个字段排序,简单的依次调用orderBy()多次就可以了。


Drupal版本:

26 随机排序

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    查询的随机排序,在不同的数据库系统上,所需要的语法也有所不同。因此,对于随机排序,最好使用动态查询。

    为了让给定查询采用随机排序,需要在查询上面调用orderRandom()方法。

<?php
$query->orderRandom();
?>

    注意,orderRandom()是一个链式方法,可以与orderBy()一起堆叠使用。也就是说,下面的代码是成立的:

<?php
$query->orderBy('term')->orderRandom()->execute();
?>

    在上面的例子中,首先按照"term"字段排序,对于拥有同一术语的记录,则采用随机顺序。


Drupal版本:

27 分组

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

为了按照给定字段分组,可以使用groupBy()方法。

<?php
$query->groupBy('uid');
?>

    上述代码将会指示查询按照uid字段分组。注意,这里的字段名,应该是由addField()addExpression()等方法创建的别名,所以大多数情况下,你需要使用这些方法的返回值,从而确保使用了正确的别名。为了按照多个字段分组,只需要简单得依次调groupBy()多遍就可以了。


Drupal版本:

28 范围和限制

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    有时候,我们需要对查询返回的结果集做一下限制,取其特定的子集。通常这被称作“范围查询”。在MySQL中,通过使用LIMIT语句来实现这一点。为了限制一个动态查询的范围,可以使用range()方法:

<?php
$query->range(510);
?>

    上述代码指示结果集从第6个记录开始,而不是从第一个,并且只返回10条记录。通常我们需要的是返回“前n个记录”,为了实现这一点,需要把第一个参数设置为0,把第二个参数设置为n。

    如果调用了range()两次,那么最后一次的调用,会覆写掉前面的。如果调用了这个方法,而不带任何参数,这意味着删除查询上面的所有范围限制。


Drupal版本:

29 表排序

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    为了生成一个可以按照任意一列排序的结果集表,可以使用TableSort扩展器,接着添加表头。注意,扩展器会返回一个新的查询对象,从这一点起,你使用的就是这个新的查询对象了。

<?php
$query $query
  ->extend('TableSort')
  ->orderByHeader($header);
?>


Drupal版本:

3 连接

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    一个连接就是类DatabaseConnection的一个对象,这个类继承自PDO类。Drupal要连接的每个数据库,都有一个唯一的连接对象与之关联。对于每个独立的驱动,该连接对象必须是一个子类。

    为了访问(并打开,如果需要的话)一个连接对象,使用:

<?php
$conn Database::getConnection($target$key);
?>

    为了访问当前连接,使用:

<?php
$conn Database::getConnection();
?>

    这将得到当前连接的默认目标。

    注意,大多数情况下,你不需要直接请求连接对象。因为很多过程语句都帮你封装好了。除非你要连接多个数据库,并且需要做复杂的操作而又不想改变当前活动的数据库,此时你可以考虑直接访问连接对象。

    为设置活动连接,使用:

<?php
db_set_active($key);
?>

    关于连接的键和目标,参看下面的一节,数据库配置。


Drupal版本:

30 条件语句

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    条件语句是一个很复杂的课题,在选择、更新、删除查询中,都会用到条件语句。因此,我们把它独立出来,单独讲解。与更新和删除查询不同,选择查询有两种类型的条件语句:WHERE语句和HAVING语句。Having语句效果和WHERE语句完全相同,唯一的区别是它使用方法havingCondition()having(),而不是condition()where()


Drupal版本:

31 执行查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    一旦构建好了查询,就可以调用execute()来编译和运行查询了。

<?php
$result $query->execute();
?>

    execute()方法将会返回一个结果集/语句对象,这与db_query()返回的结果完全一致,此外,它的迭代和获取方式也完全相同:

<?php
$result $query->execute();
foreach ($result as $record) {
  // Do something with each $record
}
?>


Drupal版本:

32 总计查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    任何查询都有一个对应的“总计查询”。总计查询返回的是源查询的结果集记录的总数。为了获得一个总计查询,可以使用countQuery()方法。

<?php
$count_query $query->countQuery();
?>

    $count_query现在是一个新的动态选择查询,它没有排序限制,当执行时,它返回的结果只有一个值,也就是源查询匹配记录的总数。由于PHP支持在返回的对象上使用链式方法,下面的代码是常见的调用方式:

<?php
$num_rows $query->countQuery()->execute()->fetchField();
?>


Drupal版本:

33 调试

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    为了检查查询对象在其生命周期中的特定一点所构建的SQL查询,可以调用它的__toString()方法:

<?php
  print_r($query->__toString());
?>

· 扩展器

· 修改查询


Drupal版本:

34 扩展器

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    选择查询支持“扩展器”这一概念。一个扩展器,就是在运行时向选择查询添加功能的一种方式。添加功能的方式有两种,可以是附加新的方法,也可以是改变已有方法的行为。

    对于熟悉面向对象设计模式的人来说,扩展器就是装饰模式的一个实现。它通过一种灵活的方式,而不是采用子类继承的方式,从而向对象动态的追加额外的扩展功能。


Drupal版本:

35 使用一个扩展器

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    为了使用一个扩展器,你首先需要有一个查询对象。extend()方法由查询对象返回一个新的对象,来替换原有的查询对象。例如:

<?php
$query $query->extend('PagerDefault');
?>

    上面的代码中,查询对象调用extend()方法,创建了一个新的PagerDefault查询对象,新对象包含了原来的选择查询,最后返回这个新创建的对象。现在,$query除了具有原来查询对象的各种功能以外,现在又有了新的附加方法可用。

    注意,$query的变更不是当即生效的。从extend()中返回了一个新的对象,我们需要把这个对象存放在一个变量中,否则它就会丢失。例如,下面的代码就不会按照你的期望执行:

<?php
$query db_select('node''n');
$query
  ->fields('n', array('nid''title')
  ->extend('PagerDefault')  //这一行返回一个新的PagerDefault对象
  ->limit(5);            //这一行能够工作,因为正被调用的是PagerDefault对象。

//extend()中返回的对象没有被保存,所以$query仍然是最初的选择对象。
$query->orderBy('title');

//这一行执行的是最初的选择对象,而不是扩展。扩展现在已不存在。
$result $query->execute();
?>

    为了避免这一问题,推荐大家在初次定义查询时,就对选择查询进行扩展。

<?php
$query db_select('node''n')->extend('PagerDefault')->extend('TableSort');
$query->fields(...);
// ...
?>

    这就确保了,从一开始,$query就是完全扩展后的对象。

    还有需要注意的是,尽管扩展器可被堆叠调用(如上例所示),但是并不是所有的扩展器都相互兼容,并且扩展的先后顺序也有可能会对结果产生影响。例如,同时扩展了分页和表排序行为的查询,必须要先扩展PagerDefault


Drupal版本:

36 创建一个新的扩展器

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    一个扩展器,就是一个简单的实现了SelectQueryInterface接口的类;并且在它的构造函数中,有两个参数:一个选择查询(或者是一个实现了SelectQueryInterface接口的对象)和一个DatabaseConnection对象。它必须重新实现SelectQueryInterface接口中的方法,并把它们传递给构造函数中指定的查询对象,在合适的时候返回自己。

    在大多数情况下,所有的这些工作都可以通过继承SelectQueryExtender类来实现,因为后者帮我们处理了这些工作。因此,在实践中,一个扩展器就是SelectQueryExtender的一个子类。类的名字,就是在查询对象的extend()方法中指定的字符串。

    那么,也就是扩展类负责添加或者覆写方法。对于它没有覆写的方法,将会透明的传递给包装后的查询对象。当覆写一个方法时,扩展器可能会也可能不会调用底层的查询对象,但是它必须返回SelectQuery接口规定的相同值。在大多数情况下,也就是查询对象本身,或者是扩展器对象本身。

    下面的例子,可能帮我们更好的理解这一点。

<?php
class ExampleExtender extends SelectQueryExtender {

  /**
   * Override the normal orderBy behavior.
   */
  public function orderBy($field$direction 'ASC') {
    return $this;
  }

  /**
   * Add a new method of our own.
   */
  public function orderByForReal($field$direction 'ASC') {
    $this->query->orderBy($field$direction);
    return $this;
  }
}
?>

    在上面的例子中,我们覆写了orderBy()方法,使其不做任何工作;同时添加了另一个方法,orderByForReal(),它实现了实际的排序行为。当然,这完全是一个例子,仅仅用来说明扩展器是如何工作的,它本身没有任何实际意义。注意,在两个方法中,被返回的$this就是扩展器对象本身。通过返回查询对象,就确保了扩展器没有“迷失”。

    任何模块都可以声明一个扩展器。Drupal核心自带了两个非常有用的扩展器:PagerDefault TableSort。对于如何在你的代码中使用这些扩展器,可以参看对应的API文档。


Drupal版本:

37 修改查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    动态选择查询的一个重要特性,就是其它模块能够修改它们。这就允许了其它模块把它们自己的限制注入到查询中,这可以是修改一个模块的行为,也可以是在查询运行时应用限制条件,比如节点访问限制。查询变更包含三个组成部分:tagging(标签化)、元数据(meta data)和hook_query_alter()


Drupal版本:

38 标签化

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    任何动态的选择查询,都可以在它上面添加一个或多个标签。这些标签用来标示查询的类型,它允许hook_query_alter()钩子了来判定它们是否需要采取行动。标签应该是小写的字母数字字符,命名规则和PHP变量相同。也就是说,只能包含字母,数字,下划线,并且必须以字母开头。为了向查询中添加一个标签,可以使用addTag()方法:

<?php
$query->addTag('node_access');
?>

    为了判定给定查询对象是否具有给定标签,有三种方法可用:

<?php
//如果这个查询对象拥有这个标签,返回TRUE
$query->hasTag('example');

//如果查询对象具有所有的指定标签,返回TRUE
$query->hasAllTags('example1''example2');

//如果查询对象具有任意一个指定的标签,返回TRUE
$query->hasAnyTag('example1''example2');
?>

    hasAllTags() hasAnyTag()可以带有任意数量的参数,每个参数表示一个标签。在这里,参数的位置对结果没有影响。

    这里没有硬性规定使用哪些标签,但通常使用一些标准标签。下面是部分标准标签:

node_access

这个查询应该包含基于节点访问控制的限制。

translatable

这个查询应该包含可被翻译的列。

term_access

这个查询应该包含基于分类术语的限制。

views

这个查询由views模块生成


Drupal版本:

39 元数据

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    还可以为查询添加一些元数据,从而为alter钩子提供附加的上下文信息。元数据可以是任意的PHP变量,也可以用一个字符串作为键。

<?php
$node node_load($nid);
// ... Create a $query object here.
$query->addMetaData('node'$node);
?>

    元数据没有内在的意义,它本身对查询对象没有任何影响。它的存在仅仅是为了向alter钩子提供附加信息,并且通常只有当查询具有特定标签时才起作用。

    为了访问一个查询上的给定元数据,使用getMetaData()方法。

<?php
$node $query->getMetaData('node');
?>

    如果没有为该键指定元数据,那么将会返回NULL。


Drupal版本:

4 查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    一个查询就是传递给数据库连接的SQL语句。Drupal的数据库系统支持6种类型的查询:静态、动态、插入、更新、删除,还有合并。有些查询采用SQL字符串模板(准备语句)的形式,有些则采用面向对象的查询构建器。“查询对象”表示一个查询构建器的实例,适用于各种查询类型。


Drupal版本:

40 hook_query_alter()

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    标签化和元数据本身并不起任何作用。它们的存在就是为hook_query_alter()提供信息,在hook_query_alter()中,可对选择查询作任何修改。

    所有的动态选择对象都是通过execute()方法,在查询字符串被编译之前,传递给hook_query_alter()的。这就为模块提供了一个机会,方便它们按照自己的需要来操作查询。hook_query_alter()接收单个参数:选择查询对象本身。

<?php
/**

* 实现hook_query_alter()
*/
function example_query_alter(QueryAlterableInterface $query) {
  // ...
}
?>

    还有一个特定于标签的alter钩子,hook_query_TAG_NAME_alter(),它会在通用的alter钩子调用后,对于给定查询具有的所有标签,调用这个钩子。对于拥有标签'node_access'的查询,会为其调用下面的代码:

<?php
function example_query_node_access_alter(QueryAlterableInterface $query) {
  // ...
}
?>

对于hook_query_alter(),有两点需要明确一下。

1. $query参数不是通过引用传递的。由于它本身是一个对象,由于PHP5及后续版本处理对象的特定方式,所以我们这里不需要使用引用。因此这里使用引用是没有必要的。另外alter钩子没有返回值。

2. 参数类型被明确指定为了QueryAlterableInterface。虽然不是必须的,但是明确的指定参数类型,可以更好的避免在运行时传递过来错误的变量类型。还有类型被指定为了QueryAlterableInterface,而不是简单的使用SelectQuery,这是为了提供更好的向前兼容性。

    在alter钩子函数中,除了避免执行查询本身以外(因为这样会造成无限循环),我们可以对查询对象采取任何操作。 alter钩子可以根据查询上的标签和元数据来决定执行哪些动作。模块开发者,可以在查询对象上调用前面所列的各种附加方法,从而为查询添加额外的字段、关联、条件语句等等;也可以请求访问查询对象的内部数据结构,从而对它们进行直接操作。第一种方法主要用来向查询添加新的信息,而后一种方法允许alter钩子从查询中删除信息,或者操作已经排好队列的指令。

<?php
$fields =& $query->getFields();
$expressions =& $query->getExpressions();
$tables =& $query->getTables();
$order =& $query->getOrderBy();
$where =& $query->conditions();
$having =& $query->havingConditions();
?>

    这里特别提醒的一点是,在上面的代码中,所有返回的值都是引用形式(=&),这样就确保了alter钩子访问的数据结构和对象的完全一致。上面的所有方法,返回的都是一个数组,它们的结构,在includes/database/select.inc中,SelectQuery的内联文档中有具体介绍。


Drupal版本:

41 插入查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    插入查询必须使用一个查询构建器对象。对于LOB (大对象比如MySQL中的TEXT) BLOB二进制大对象)字段,某些数据库需要特殊处理,所以我们需要一个抽象层,从而允许独立的数据库驱动,按照它们自己的要求实现自己的特殊处理。

    插入查询使用db_insert()函数作为开始,如下所示:

<?php
$query db_insert('node'$options);
?>

    上述代码创建了一个插入查询对象,它将向节点表中插入一个或多个记录。注意这里没有为表名使用花括号,这是因为查询构建器能够自动的处理这一点。

    插入查询对象使用的API具有链式特性。也就是说,所有的方法(除了execute()),返回的都是查询对象本身,这样,这些方法调用就可以采用链式结构了。在大多数情况下,这也就意味着,我们不需要将查询对象保存在一个变量中了。

    插入查询对象支持多个不同的用法模式,用来满足不同的需求。在一般情况下,它的工作流程包括,指定需要插入到的字段,为这些字段指定将要插入的对应值,执行查询。下面列出了最常见的使用模式。


Drupal版本:

42 紧凑形式

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    对于大多数插入查询,推荐的形式就是紧凑形式:

<?php
$nid db_insert('node')
  ->fields(array(
    'title' => 'Example',
    'uid' => 1,
    'created' => REQUEST_TIME,
  ))
  ->execute();
?>

    上面的代码就等价于执行下面的查询:

INSERT INTO {node} (title, uid, created) VALUES ('Example', 1, 1221717405);

    上面的代码,把插入流程的关键部分,链到了一起。

<?php
db_insert('node')
?>

    这一行代码为节点表创建了一个新的插入查询对象。

<?php
  ->fields(array(
    'title' => 'Example',
    'uid' => 1,
    'created' => REQUEST_TIME,
  ))
?>

    fields()方法的参数有多种形式,但是最常见的就是单个关联数组。数组的键,就是要插入的列名,数组的值就是要插入的对应值。这将会为指定的表生成一个插入查询对象。

<?php
  ->execute();
?>

    execute()方法告诉查询开始运行。除非调用这个方法,否则查询就不会执行。

    对于查询对象上的其它方法,返回的都是查询对象本身,但是execute()方法与此不同,它返回的是插入查询向数据库中插入记录的自增字段(如果存在的话)的值。这也就是为什么,在上面的例子中,我们把它的返回值,分配给了$nid。如果没有自增字段,那么execute()的返回值就未被定义,此时就不应该使用它的返回值。

    在大多数情况下,这就是插入查询的推荐形式。


Drupal版本:

43 退化形式

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

<?php
$nid db_insert('node')
  ->fields(array('title''uid''created'))
  ->values(array(
    'title' => 'Example',
    'uid' => 1,
    'created' => REQUEST_TIME,
  ))
  ->execute();
?>

    这段代码和前面的代码相比,输出的结果完全相同,就是罗嗦了一点。

<?php
  ->fields(array('title''uid''created'))
?>

    当调用fields()时,使用的参数是一个索引数组而不是关联数组,此时它只设置了在查询中用到的字段(数据库列),而没有为它们设置值。这在插入多值的查询中非常有用,我们在后面会讲到这一点。

<?php
  ->values(array(
    'title' => 'Example',
    'uid' => 1,
    'created' => REQUEST_TIME,
  ))
?>

    这个方法指定一个关联数组,数组的结构就是左边的为要插入的字段名字,右边为对应的值。values()也可以使用索引数组。如果使用的是索引数组,那么值的顺序必须要与fields()方法中字段顺序保持一致。如果用的是关联数组,则数组内元素的顺序可以随意。一般情况下,为了代码的可读性,尽量选用关联数组的形式。

    由于大家大多数时候都使用紧凑形式,所以这种查询形式很少使用。只有当运行多值插入查询时,才会将fields()values()分开使用。


Drupal版本:

44 多值插入形式

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    插入查询对象上还可以使用多值集合。也就是说,values()可被调用多次,从而将多个插入语句排在一起调用。这种形式的具体执行情况,则取决于数据库自身的能力。对于大多数的数据库,多值插入语句都会放在一个事务中一起执行,这样能够保证更好的数据一致性和执行效率。对于MySQL,它将会使用MySQL的多值插入语法。

 

<?php
$values = array(
  array(
    'title' => 'Example',
    'uid' => 1,
    'created' => REQUEST_TIME,
  ),
  array(
    'title' => 'Example 2',
    'uid' => 1,
    'created' => REQUEST_TIME,
  ),
  array(
    'title' => 'Example 3',
    'uid' => 2,
    'created' => REQUEST_TIME,
  ),
);
$query db_insert('node')->fields(array('title''uid''created'));
foreach ($values as $record) {
  $query->values($record);
}
$query->execute();
?>

    在上面的例子中,我们把三个插入查询放在一个单元中执行,对于不同的数据库驱动,将会为其使用最有效的方法。注意,在这里我们把查询对象保存在了一个变量中,这样我们就可以对$values进行循环并重复的调用values()方法了。

    在退化形式下,上面的例子等价于执行下面3条查询:

INSERT INTO {node} (title, uid, created) VALUES ('Example', 1, 1221717405);
INSERT INTO {node} (title, uid, created) VALUES ('Example2', 1, 1221717405);
INSERT INTO {node} (title, uid, created) VALUES ('Example3', 2, 1221717405);

    注意,对于一个多值插入查询,execute()的返回值未被定义,因此不要调用它的返回值,数据库驱动的不同,也会对返回值产生影响。


Drupal版本:

45 基于选择查询的结果插入

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    如果你想使用其它数据库表中的结果来填充当前的数据库表,你可以使用SELECT语句将其取出,然后在PHP中对其进行迭代并将其保存到新表中去,另外你还可以使用INSERT INTO...SELECT FROM查询,在这里从SELECT查询返回的每条记录都会送入到INSERT查询中。

    在这个例子中,我们想构建一个表"mytable",它包含两个字段节点ID和用户名,我们从数据库中取出所有page类型节点的对应信息,然后插入到表"mytable"中。
Drupal 6 

<?php
db_query('INSERT INTO {mytable} (nid, name) SELECT n.nid, u.name FROM {node} n LEFT JOIN {users} u on n.uid = u.uid WHERE n.type = "%s"', array ('page'));
?>

Drupal 7 

<?php

//构建选择查询
$query db_select('node''n');

//关联users
$query->join('users''u''n.uid = u.uid');

//添加我们想要的字段
$query->addField('n','nid');
$query->addField('u','name');

//添加一个条件,把节点限制在类型page上去。
$query->condition('type''page');

//执行插入
db_insert('mytable')
  ->from($query)
  ->execute();
?>


Drupal版本:

46 默认值

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    在正常情况下,如果你没有为给定字段指定一个值,而表的模式(schema)定义了默认值,那么数据库会自动的为你插入默认值。然而,在大多数的情况下,你需要明确的指示数据库使用默认值。它包括你是否想为整个记录使用所有的默认值。为了明确的告诉数据库,为给定的字段使用默认值,我们可以使用useDefaults()方法。

<?php
$query->useDefaults(array('field1''field2'));
?>

    这段代码指示查询,为字段field1field2使用数据库定义的默认值。注意,如果你指定的字段,已经在fields()values()中使用,此时就会抛出一个异常,并提示你存在一个SQL错误。


Drupal版本:

47 更新查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    更新查询必须使用一个查询构建器对象。对于LOB (大对象比如MySQL中的TEXT) BLOB二进制大对象)字段,某些数据库需要特殊处理,所以我们需要一个抽象层,从而允许独立的数据库驱动,按照它们自己的要求实现自己的特殊处理。

    更新查询使用db_update()函数作为开始,如下所示:

<?php
$query db_update('node'$options);
?>

    上述代码创建了一个更新查询对象,它将修改节点表中的一个或多个记录。注意这里没有为表名使用花括号,这是因为查询构建器能够自动的处理这一点。

    更新查询对象使用的API具有链式特性。也就是说,所有的方法(除了execute()),返回的都是查询对象本身,这样,这些方法调用就可以采用链式结构了。在大多数情况下,这也就意味着,我们不需要将查询对象保存在一个变量中了。

    更新查询的结构是非常简单的,它包含一个要设置的键值对儿集合,还有一个WHERE语句。有关WHERE语句的结构,我们在这里只是简单的说明,更详细的介绍可参看后面的条件语句一节。

    典型的更新查询如下所示。

<?php
$num_updated db_update('node')
  ->fields(array(
    'uid' => 5,
    'status' => 1,
  ))
  ->condition('created'REQUEST_TIME 3600'>=')
  ->execute();
?>

    上面的查询将会更新节点表中最近一小时内创建的所有记录,把它们的uid字段设置为5,把status字段设置为1。fields()方法中的参数是单个关联数组,用来指定要设置哪些字段以及对应的值,当指定的条件满足时,就会进行相应更新。注意,与插入查询不同,UpdateQuery::fields()只接收一个关联数组。还有,数组中字段的顺序,以及方法的调用顺序,与结果无关。

    上面的代码就等价于执行下面的查询:

UPDATE {node} SET uid=5, status=1 WHERE created >= 1221717405;

    execute()将返回查询影响到的记录总数。注意,影响到的与匹配到的通常不一致。在上面的查询中,如果一个已有记录,它的uid为5,status为1,并且满足条件语句,此时它就是匹配的,但是由于它里面的数据没有被修改,也就是它没有被查询影响到,因此不会记入到返回值中。作为一个副作用,这使得更新查询无法判定一个记录是否存在。


Drupal版本:

48 删除查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    删除查询必须使用一个查询构建器对象。它们使用db_delete()函数作为开始,如下所示:

<?php
$query db_delete('node'$options);
?>

    上述代码创建了一个删除查询对象,它将会删除节点表中的对应记录。注意这里没有为表名使用花括号,这是因为查询构建器能够自动的处理这一点。

    删除查询对象使用的API具有链式特性。也就是说,所有的方法(除了execute()),返回的都是查询对象本身,这样,这些方法调用就可以采用链式结构了。在大多数情况下,这也就意味着,我们不需要将查询对象保存在一个变量中了。

    删除查询的结构是非常简单的,它只包含一个WHERE语句。有关WHERE语句的结构,我们在这里只是简单的说明,更详细的介绍可参看后面的条件语句一节。

    下面是一个完整的删除查询示例。

<?php
$num_deleted db_delete('node')
  ->condition('nid'5)
  ->execute();
?>

    上面的查询将会删除{node}表中nid为5的所有记录。它等价于执行下面的查询:

DELETE FROM {node} WHERE nid=5;

    execute()方法将会返回被删除的记录数量,作为查询的结果。

 


Drupal版本:

49 合并查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    合并查询是一种特殊的混合查询类型。虽然在SQL2003规范中为它们定义了语法,但实际上几乎没有任何数据库支持该标准语法。然而,大多数数据库通过使用特定于数据库的语法,都提供了一些替代实现。在Drupal中,合并查询构建器把合并查询的概念抽象成为了一个结构化的对象,根据数据库的不同,它们会被编译成特定于数据库的语法。

    在一般意义上,合并查询就是插入查询和更新查询的联合体。如果满足了给定条件,比如包含给定主键的记录已经存在,此时运行一个更新查询。如果给定条件未满足,则运行其它查询,比如插入查询。在大多数情况下,它等价于:

<?php
if (db_query("SELECT COUNT(*) FROM {example} WHERE id=:id", array(':id' => $id))->fetchField()) {
  // Run an update using WHERE id = $id
}
else {
  // Run an insert, inserting $id for id 
}
?>

    实际的具体实现,在不同的数据库之间,大不相同。注意,尽管合并查询在概念上是一个原子操作,但它们是否真的是原子操作,则取决于具体数据库的实现。例如,在MySQL中,它实现为了单个原子查询,不过上面的退化情况则不是原子操作。

    下面列出了合并查询的常见的习语。


Drupal版本:

5 语句

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    语句对象就是选择(Select)查询的结果。它的类型应该是DatabaseStatement,或者是DatabaseStatement的子类。DatabaseStatement扩展了PDOStatement类。

    Drupal为所有的查询使用预处理语句(prepared statements)。一个预处理语句就是一个查询的模板,将会向模板中填充数值然后执行。我们可以把预处理语句看作是SQL的函数形式,需要向里面传递一些参数才可使用。

    在正常的PDO中,首先必须明确的准备一个语句对象,然后把相应的值绑定到查询中的占位符上,并执行查询。接着,语句可以作为结果集被迭代处理。也就是说,在语句执行以后,语句和结果集就成为了同义词。

    Drupal没有直接暴露预处理语句对象。模块开发者需要使用一个查询对象或者一个SQL字符串来执行查询,接着该查询就会返回相应的语句对象。因此,术语“语句对象”和“结果集对象”表示的含义基本相同。


Drupal版本:

50 只是设置它

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

<?php
db_merge('example')
  ->key(array('name' => $name))
  ->fields(array(
      'field1' => $value1,
      'field2' => $value2,
  ))
  ->execute();
?>

    在上面的例子中,我们指示查询在"example"表上进行操作。我们接着指定了一个主键字段,'name',它的值为$name。我们接着指定了要设置的值的数组。

    如果存在这样一个记录,它的"name"字段的值为$name,那么在这个记录中,字段field1field2将会被设置为对应的值。如果不存在这样的记录,那么就会创建一个,其中"name"的值为$name"field1"的值为$value1"field2"的值为$value2。因此,在查询的最后,无论记录是否已经存在,最终的结果都是相同的。


Drupal版本:

51 有条件设置

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    在有些情况下,我们可能会需要根据记录是否存在(由key()中的字段标识),来有条件的设置值。这样做有两种方式。

<?php
db_merge('example')
  ->key(array('name' => $name))
  ->fields(array(
      'field1' => $value1,
      'field2' => $value2,
  ))
  ->update(array(
    'field1' => $alternate1,
  ))
  ->execute();
?>

    上面的例子和最初的例子相比,在对应记录不存在时,结果完全相同;在记录已经存在时,在这里我们更新它,把field1字段的值设置为$alternate1,而不是$value1,对于field2字段则不作任何操作。update()方法的参数可以是一个关联数组,也可以是两个数值数组,一个字段一个值,并且必须使用相同的顺序。

<?php
db_merge('example')
  ->key(array('name' => $name))
  ->fields(array(
      'field1' => $value1,
      'field2' => $value2,
  ))
  ->expression('field1''field1 + :inc', array(':inc' => 1))
  ->execute();
?>

    在这个例子中,如果记录已经存在,那么field1的值将被设置为当前值加1。如果当特定事件发生时你想为计数器作加法,对于这种计数器查询,上述方式非常有用。无论记录是否存在,field2仍然被设置为相同的值。

    注意, expression()可被调用多次,每次对应一个需要设置为表达式的字段,如果记录存在,就会对该字段按表达式进行设置。第一个参数是字段;第二个参数是一个SQL片断,也就是表达式,它用来指示应该如何设置字段;第三个参数是可选的数组,里面包含了占位符的值,这些值将插入到表达式中。

    如果一个字段已经出现在了fields()当中,这个字段仍然可以用在expression()中,两者之间没有冲突关系。


Drupal版本:

52 有限制的更新

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

<?php
db_merge('example')
  ->key(array('name' => $name))
  ->fields(array(
      'field1' => $value1,
      'field2' => $value2,
  ))
  ->updateExcept('field1')
  ->execute();
?>

    updateExcept()方法,可以使用由字段构成的数组作为参数,也可以使用一列字段分别作为单独的参数。对于updateExcept()中指定的字段,如果记录已经存在,那么它们就不受影响。也就是说,如果存在一个name = $name的记录,那么field2将被设置为$value2,而field1将被完全忽略,原来什么值就是什么值,但是如果记录不存在,那么它会被设置为$value1


Drupal版本:

53 优先级

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    使用上面的API,程序员很有可能定义出来没有任何意义的查询,比如一个字段,在假定记录存在的情况下,既被设置为了忽略,又被设置为了表达式。下面的规则,可以帮助减少潜在的错误:

· 如果一个字段设置成一个expression(),那么它的优先级高于update()updateExcept()

· 如果在update()中已经指定了值,那么updateExcept()将被忽略。也就是update()的优先级高于updateExcept()

· 如果值被指定在update()中,那么在记录已存在时,只有这些字段才会被修改。没有在update()中指定的字段将不受影响。

    即便是遵守这些规则,仍然有可能定义出来没有意义的查询。查询的有效性,需要开发者自己来掌握,尽量避免定义出没有意义的查询。


Drupal版本:

54 条件语句

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    “条件语句”是查询的一部分,它通过特定的条件,来限制匹配的记录。在SQL中,它就是SELECT UPDATE、或DELETE查询中的WHEREHAVING部分。在Drupal的所有动态查询中,条件语句的实现机制都相同。如果没有特别说明,下面所讲的适用于所有的查询类型。


Drupal版本:

55 概念

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

条件语句片断

条件语句片断是条件语句自包含的一部分。

连接词

在条件语句中,条件语句片断使用连接词连接。连接词通常为ANDOR,它能够把两个语句连到一块。

条件语句对象

Drupal把所有的条件语句片断都处理成了QueryConditional类的实例。一个条件语句对象就是该类的一个实例。

    作为一个例子,下面的查询可以这样分解:

查询:

SELECT FROM {mytable} WHERE (a = 1 AND b = 'foo' OR (c = 'bar'))

条件语句:

WHERE (a = 1 AND b = 'foo' OR (c = 'bar'))

条件语句片断:

(a = 1 AND b = 'foo' OR (c = 'bar'))

(c = 'bar')

连接词:

ANDOR

    选择、更新、和删除查询对象都实现了QueryConditionalInterface接口,这样它们在条件语句上的接口是完全相同的。在内部,它们封装了一个QueryConditional对象。也可以直接实例化QueryConditional类。

    条件语句中的每一片断都是使用连接词连在一起的。如果一个条件语句包含多个片断,那么片断之间将会使用指定的连接词。默认情况下,连接词为AND。每个条件语句片断本身就是一个带有不同连接词的条件语句对象,这样就允许条件语句中片断之间的相互嵌套。这样,就可以构建任意复杂的条件语句了。


Drupal版本:

56 API

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    对于所有的条件语句对象,主要使用的方法有两个:

$query->condition($field, $value = NULL, $operator = '=')

condition()方法允许添加一个标准的条件语句片断,其形式为$field $value $operator。它包含了二进制比较的各种情况,比如= <,>= LIKE、等等。如果没有指定操作符,则使用默认的=。这意味着,最常见的情况的就是condition('myfield', $value),它生成一个条件语句片断myfield = :value;其中,当查询运行时,:value将被替换为$value

$query->where($snippet, $args = array())

where()方法允许使用任意的SQL作为条件语句片断。$snippet可以包含任何合法的SQL片断,如果它里面包含了变量内容,那么必须使用命名占位符的形式。$args是占位符数组,它里面的值将会替换到SQL片断中去。开发者需要自己确保SQL的有效性。不要对SQL片断作任何特定于数据库的修改。

    在大多数的情况下,推荐使用condition()方法,除非出现$field $value $operator形式不再适用的情况,比如当你需要使用表达式时,或者一个条件作用于两个字段时。两个方法返回的都是对应的条件语句对象,所以它们可被无限的链式调用。

    condition()还可以用于一些其它特殊情况。


Drupal版本:

57 数组运算符

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    一些运算符用于value参数为数组时。最常用的就是IN BETWEEN。那么$value应该是一个数组,它包含了字段可能等于的值。因此,下面的调用将会这样解析:

<?php
$query->condition('myfield', array(123), 'IN');

//变成了: myfield IN (:db_placeholder_1, :db_placeholder_2, :db_placeholder_3)
?>

    如果运算符为BETWEEN,那么$value是一个包含两个元素的数组,字段位于两者之间。例如:

<?php
$query->condition('myfield', array(510), 'BETWEEN');

//变成了: myfield BETWEEN :db_placeholder_1 AND :db_placeholder_2
?>


Drupal版本:

58 嵌套的条件语句

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    condition()的第一个参数,也可以是一个条件语句对象。内部的条件语句对象将被纳入到外部的条件语句中去,并放在括号中间。内部对象所使用的连接词,可以与外部不同。这样,就可以通过“自下而上”的方式,来创建条件语句对象,从而就可以构建一个复杂的嵌套的条件语句了。

    db_condition()帮助函数将返回一个新的条件语句对象。它只有一个参数,就是对象使用的连接词。一般情况下,帮助方法db_and()db_or()、和db_xor()就可以涵盖大多数情况了。它允许条件语句以内联的方式插入到查询中,这样代码看起来更加紧凑。

    例如,看一下下面的结构体:

<?php
$query
  ->condition('field1', array(12), 'IN')
  ->condition(db_or()->condition('field2'5)->condition('field3'6))

// 对应结果:
// (field1 IN (:db_placeholder_1, :db_placeholder_2) AND (field2 = :db_placeholder3 OR field3 = :db_placeholder_4))
?>


Drupal版本:

59 Null值

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    在有些情况下,我们可能会根据一个字段的值是不是NULL来进行过滤。当然,此时也可以使用condition(),不过我们更推荐使用下面的实用方法,因为它们更易于理解:

<?php
$query->isNull('myfield');

//结果为:(myfield IS NULL)

$query->isNotNull('myfield');

//结果为:(myfield IS NOT NULL)
?>

    两个方法都可被链式调用,根据需要可以和condition()where()结合使用。


Drupal版本:

6 数据库配置

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    定义一个数据库连接的主要方式,是使用settings.php中的$databases数组。从它的名字,我们就可以看出,$databases允许定义多个数据库连接。它还支持定义多个目标。只有在对该数据库运行查询时,才会打开对应的数据库连接,或者说是创建相应的连接对象。


Drupal版本:

60 子查询

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    condition()中的$value还可以使用子查询的形式。为了使用子查询,首先需要使用db_select()构建一个SelectQuery对象。接着,我们不执行这个Select查询,而是将其作为condition()的value参数传递给另一个查询。当主查询执行时,它将被自动的集成过来。

    子查询通常只在两种情况下使用:当子查询生成的结构只包含一行并且只有一个值时,同时运算符为=<><=>=;或者当子查询只返回单列信息,而运算符为IN。除此之外,大多数其它的关联方式都会造成语法错误。

    注意,在一些数据库上,特别是MySQL,条件语句中的子查询,运行效率有点慢。如果可能的话,尽量在FROM语句中使用关联、子查询,或者使用多个简单的条件语句片断来代替子查询。


Drupal版本:

61 示例

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    下面有几个例子,希望能够更好的帮助理解条件语句。为了清晰起见,我们在注释中给出等价的查询字符串,当然在实际的应用中,将会使用占位符和预备语句(prepared statements)。

<?php
db_delete('sessions')
  ->condition('timestamp'REQUEST_TIME $lifetime'<')
  ->execute();
// DELETE FROM {sessions} WHERE (timestamp < 1228713473)
?>

<?php
db_update('sessions')
  ->fields(array(
    'sid' => session_id()
  ))
  ->condition('sid'$old_session_id)
  ->execute();
// UPDATE {sessions} SET sid = 'abcde' WHERE (sid = 'fghij');
?>

<?php
// From taxonomy_term_save():
$or db_or()->condition('tid1'5)->condition('tid2'6);
db_delete('term_relation')->condition($or)->execute();
// DELETE FROM {term_relation} WHERE ((tid1 = 5 OR tid2 = 6))
?>


Drupal版本:

62 编写数据库驱动

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    这部分内容很快就会加上!webchick坚持让我的文档写了多少,发布多少。到目前为止,有关数据库驱动的还没有写好。我将尽快地把它补上来。在此以前,大家可以参考对应的单元测试(http://cvs.drupal.org/viewvc.py/drupal/drupal/modules/simpletest/tests/database_test.test?&view=markup)。


Drupal版本:

63 PDO

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    Drupal7使用PDO(PHP数据对象)来访问数据库。更多信息可参看:http://drupal.org/node/549702


Drupal版本:

64 错误处理

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    数据库API在遇到错误时,就会抛出异常,我们可以把数据库操作放在try {} catch() {}区块中,这样异常就会被我们抓住,相关信息可参看http://api.drupal.org/api/group/database/7中的最后一个例子。


Drupal版本:

65 事务

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    Drupal还支持事务,对于那些不支持事务的数据库,Drupal还包含了一个透明的回退。然而,当你在同一时间,尝试并启动两个事务时,事务就会变得复杂起来。数据库不同,此时的行为也不相同。

    在C/C++里面的嵌套锁中,也存在类似的问题。如果代码已经获得锁A,并尝试去获取锁A时,代码就会进入死循环。你可以在代码中添加检查语句,如果已经获得了锁,那么就不再尝试获取它了,这样就可以避免死循环;但是你也可以提前释放锁。

    在SQL中,我们也存在同样的问题。如果你的代码已经处于一个事务中了,那么启动一个新的事务,这会给你带来非常惊讶和不幸的后果。

    Java通过支持类似于我们下面测试所用的嵌套结构,成功解决了它的锁嵌套问题。Java允许你把函数标记为“同步”("synchronized"),函数在运行前,首先等待锁的释放;当它获得了锁以后,就会执行;当它不再需要时,就会把锁释放。如果在同一个类中,一个同步函数调用另一个,Java将会追踪锁的嵌套。外部的函数获取锁,内部函数执行无锁操作;当外部函数返回时,释放锁。

    尽管我们不能在PHP中把函数声明为“事务的”,但是通过使用带有构造函数和析构函数的对象,我们就可以模拟Java的嵌套逻辑。在一个函数中,在它的第一个操作或者接近于第一个操作的地方,简单的调用"$txn = db_transaction();",就使得函数具有事务特性了。如果一个事务函数调用了另一个,我们的事务抽象层通过在内部嵌套层执行非事务操作(至少在数据库看来是这样),来实现嵌套。

    为了启动一个新事务,在你的代码中简单的调用$txn = db_transaction();即可。只要变量$txn仍然在范围内,那么事务就会保持打开的状态。当$txn被销毁时,事务将被提交。如果你的事务嵌套在另一个里面,那么Drupal会追踪每一个事务,只有当最后一个事务对象超出范围,也就是所有相关的查询都成功执行完毕时,Drupal才会提交最外面的事务。

例如:

<?php
function my_transaction_function() {

  //在这里打开事务
  $txn db_transaction();

  $id db_insert('example')
    ->fields(array(
      'field1' => 'mystring',
      'field2' => 5,
    ))
    ->execute();

  my_other_function($id);

  return $id;

  //在这里,$txn超出了范围,整个事务被提交。
}

function my_other_function($id) {
   //此处,事务仍然是开着的。

  if ($id == 0) {
    db_update('example')
      ->condition('id'$id)
      ->fields(array('field2' => 10))
      ->execute();
  }
}
?>


Drupal版本:

66 链式

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    我们已经讲了很多有关数据库API函数的链式调用了,例如:

<?php
$result db_select('mytable')
  ->fields('mytable')
  ->condition('myfield''myvalue')
  ->execute();
?>

    然后,并不是所有的函数都可以采用这种链式结构调用,有些函数不能采用链式调用,否则就会导致你的代码不能正常工作。

    对于不能使用链式形式的函数,只能按照下面的方式调用:

<?php
$query db_select('mytable');
$query->addField('mytable''myfield''myalias');
$query->addField('mytable''anotherfield''anotheralias');
$result $query->condition('myfield''myvalue')
  ->execute();
?>

    如果一个函数是链式的,那么它的返回值,必须是查询对象本身。为了查看任意数据库API函数的返回值,可以参看http://api.drupal.org

    在这个页面,我们列出了一些常用的链式函数,和一些非链式函数,方便大家快速查看,不过这里面并没有涵盖所有的函数。如果你觉得哪个函数比较常用,也可以将其添加进来。

    注意:如果一个函数不是链式的,这只意味着你不可以在它的后面链接更多的函数,但是你仍然可以在它前面使用链式函数。

下面是链式函数:

· distinct()

· fields()

· condition()

· where()

· isNull()

· isNotNull()

· orderBy()

· groupBy()

· having()

· union()

· exists()

· notExists()

· range()

· addTag()

· addMetaData()

下面是非链式函数:

· addField()

· addExpression()

· execute()

· addJoin()

· join()

· innerJoin()

· leftJoin()

· rightJoin()

· extend()


Drupal版本:

67 函数和运算符

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    Drupal的数据库层,没有为SQL函数提供跨数据库的抽象。为了让你的代码更好的兼容所支持的数据库引擎,你使用的函数应该符合ANSI标准,并且在Drupal支持的所有数据库中都能正常工作。下面是一个正在完善的列表。这里所列的,是推荐大家使用的,其它的语法变体可能不垮数据库。

    注意,数据库层没有提供运算符的白名单,所以你可以使用非标准的函数,比如REPLACE(),对于支持该语法的数据库,代码是可以正常工作的。


Drupal版本:

68 逻辑运算符

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

AND

OR

NOT


Drupal版本:

69 比较运算符

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

<

>

<=

>=

<>

LIKE


Drupal版本:

7 连接键

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    连接键是给定数据库连接的唯一标识符。对于给定站点,连接键必须是唯一的,并且必须有一个"default"连接,用作Drupal的主数据库。对于大多数站点,这可能也是唯一定义的连接。


Drupal版本:

70 类型操作运算符

类型操作运算符

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Drupal版本:

71 字符串函数和运算符

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

CONCAT(string1, string2)

SUBSTRING(string, from, length)

SUBSTRING_INDEX(string, delimiter, count)

LENGTH(string)


Drupal版本:

72 数学函数和运算符

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

GREATEST(num1, num2)

POW(num1, num2)

LOG(base, value)

RAND()


Drupal版本:

73 日期/时间函数

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

日期/时间函数


Drupal版本:

74 聚合函数

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

COUNT(expression)

SUM(expression)

AVG(expression)

MIN(expression)

MAX(expression)


Drupal版本:

75 总结

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

通过本章的学习,你应该可以

了解数据库API的一般概念

熟悉数据库的配置

能够熟练的掌握静态查询,

能够熟练的掌握动态查询,插入、更新、删除、合并查询

熟悉条件语句

了解事务机制和链式结构


Drupal版本:

8 目标

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    一个给定的连接键,必须有一个或者多个目标。一个目标就是一个可选的备用数据库。如果请求的目标没有定义,系统就会自动采用“默认”目标,这是必须要定义的。

    目标的主要用途就是主从数据库。默认目标是主SQL服务器。接着可以定义一个或多个“从”目标。对于标记为尝试使用从服务器的查询,如果从服务器可用,那么它们就会尝试访问“从”目标。如果有一个从服务器可用,那么就会打开对应的连接,并运行相应的查询。如果没有从服务器可用,那么查询就会运行在主服务器上。这样,就有了一个透明的回退,如果有从服务器可用,代码就会利用从服务器,如果没有从服务器可用,代码无需修改,仍然能够正常工作。


Drupal版本:

9 $databases语法

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    $databases数组是一个至少包含3层的嵌套数组。第一层定义了数据库的键。第二层定义了数据库目标。每个目标的值就是对应的连接信息。通过实例我们可以更好的理解这一点。

<?php
$databases['default']['default'] = array(
  'driver' => 'mysql',
  'database' => 'drupaldb',
  'username' => 'username',
  'password' => 'secret',
  'host' => 'localhost',
);
?>

    上面的$databases数组定一个单个的连接键("default"),单个的目标("default")。连接采用的数据库为MySQL,数据库名字为“drupaldb”,访问数据库的用户名密码分别为“username”、“secret”,数据库所在的主机为“localhost”。上面的例子代表着Drupal单SQL服务器安装的典型情况,对于大多数站点,这就足够了。

    对于主/从服务器的配置,我们则需要这样定义:

<?php
$databases['default']['default'] = array(
  'driver' => 'mysql',
  'database' => 'drupaldb1',
  'username' => 'username',
  'password' => 'secret',
  'host' => 'dbserver1',
);
$databases['default']['slave'][] = array(
  'driver' => 'mysql',
  'database' => 'drupaldb2',
  'username' => 'username',
  'password' => 'secret',
  'host' => 'dbserver2',
);
$databases['default']['slave'][] = array(
  'driver' => 'mysql',
  'database' => 'drupaldb3',
  'username' => 'username',
  'password' => 'secret',
  'host' => 'dbserver3',
);
?>

    这个定义提供了一个“主”服务器和两个“从”服务器。注意,“从”服务器的键是一个数组。如果有目标被定义成为连接信息的数组形式,那么对于该目标的每个页面请求,系统会随机的从中选择一个服务器。也就是说,对于一个页面请求,所有的从查询会发送给dbserver2,而对于下一个请求,则可能都会发送给dbserver3

<?php
$databases['default']['default'] = array(
  'driver' => 'mysql',
  'database' => 'drupaldb1',
  'username' => 'username',
  'password' => 'secret',
  'host' => 'dbserver1',
);
$databases['extra']['default'] = array(
  'driver' => 'sqlite',
  'database' => 'files/extradb.sqlite',
);
?>

    这个配置定义了一个单独的主Drupal数据库,和一个键为"extra"的附加数据库,后者采用SQLite。注意SQLite的连接信息的结构和MySQL的大不相同。对于每个驱动,由于情况不同,所以可以有不同的配置。

    注意,无论你定义了多少个连接,Drupal只有在该连接被用到时才会打开它。


Drupal版本: