drupal 数据库查询

本文内容翻译自《Drupal 7 Module Development》一书附录A。

Drupal 7 的数据库访问层为提高安全性,可扩展性和开发灵活性,进行了全新的革新,一度被称为“DBTNG”(Database: The Next Generation)。现在这个数据访问层支持三种数据库:即Mysql与变体Maria DB, PostgreSQL和SQLite。并且也将推出SQL Server与Oracle的数据层驱动。

关于database API的详尽信息可以查看http://drupal.org/developing/api/databasehttp://api.drupal.org/api/group/database/7.

基本查询(Basic queries)

基本查询是指那些相对简单并且不会有太大改动的查询。Drupal也称这些查询为静态查询(static queries), 它们的使用很直接。

比如,为了获得所有系统里已启用的模块,我们可以运行如下查询:

$result = db_query("SELECT name, filename FROM {system} WHERE type = :type AND status = :status", array(':type' => 'module', ':status' => 1));

【题外话,如果我们要获得上述信息,最简单的办法是直接调用module_list(),此处主要是为了显示如何“手动”操作获得此数据。】

上述查询语句和我们能看到的普通SQL语句几乎一摸一样,除了以下几点:

  • 所有的表明都用大括号包裹。这是为了向数据访问层明确指出表名的位置,以便Drupal可以添加表名前缀(在多个Drupal实例使用同一个数据库的时候用于区分各自实例所使用的不同的数据表)。
  • 不包括MySQL自有的语法(或者任何特定于数据库的语法)。
  • 查询中没有字面量。与此同时,字面量都通过占位符来实现。占位符与对应的字面量都在一个数组参数中(也就是db_query()函数的第二个参数)。

那些占位符很显眼。它们允许我们把查询与值区分开来,并且分别传入到数据库服务层。数据库服务层则负责将查询字符串与占位符值结合起来,并解决数据类型的匹配问题。这样一来,几乎所有(尽管不是全部)的SQL注入攻击的机会都可以被消除。

关于占位符有三个要注意的地方:

  • 同一个查询中的占位符必须是唯一的,并且必须以冒号开头。
  • 占位符前后不能包括引号,不论是什么数据类型。数据库服务层将会做好剩下的事。
  • 任何字面值都应该使用占位符,不论值是否变化。

这三点是跨数据库适用性的保证,一旦剥离开字面值,就允许数据库驱动层针对不同数据库做具体的处理。

结果对象(Result objects)

db_query()的返回值是一个结果对象。为了访问数据库服务器返回的数据,我们需要遍历结果集。

$list = array();
foreach ($result as $record) {
   $list[] = t('@name: @filename', array(
      '@name' => $record->name,
      '@filename' => $record->filename,
   ));
}

默认的,结果集中的每个$record都是一个stdClass对象。但是,我们也可以告诉db_query(),我们需要返回的结果集为关联数组的形式。方法如下:

$result = db_query("SELECT name, filename FROM {system} WHERE type = :type AND status = :status", array(':type' => 'module', ':status' => 1), array('fetch' => PDO::FETCH_ASSOC));

在这里,我们为db_query()指出了第三个参数,参数是关联数组的形式。我们指定了一个选项,即获取方式,设置为PDO::FETCH_ASSOC. 这将告诉数据库层我们需要返回的是关联数组形式,而非stdClass对象。

我们也可以只取出单一的记录,甚至是单一的字段:

// 取出单一记录(对象形式)
$record = $result->fetchObject();
// 取出单一记录(数组形式)
$record = $result->fetchAssoc();
// 取出下一条记录的第一个字段
$field = $result->fetchField();
// 取出全部结果集到一个数组
$records = $result->fetchAll();
参看在线文档可以获得取出结果集中数据的许多方法。
动态查询(Dynamic queries)

虽然大多数SELECT查询是静态的,有些时候我们需要更加灵活的查询。也许是因为查询本身会随着传入的数据发生变化,或者我们想要让其他模块在查询运行前有修改查询的机会,或者我们想要利用某些数据库特有的功能。在这些情况下,Drupal提供了一个查询生成器来生成动态查询。

一开始,我们需要使用db_select()创建一个查询对象:

$query = db_select('node', 'n');

第一个参数是基础表(base table)的表名,第二个参数是表的别名(alias),稍后我们将用到它。然后我们可以在$query对象上调用更多的方法来依据要求动态的建立查询逻辑。比方说:

$query = db_select('node', 'n');
$query->fields('n', array('nid, title'));
$u_alias = $query->innerJoin('users' ,'u', '%alias.uid = n.uid');
$query->addField($u_alias, 'name', 'username');
$query->condition("{$u_alias}.name", 'Bob');
$query->condition('n.created', REQUEST_TIME - 604800, '>=');
$query->orderBy('n.created', 'DESC');
$query->range(0, 5);
$query->addTag('node_access');
$result = $query->execute();

fields()方法的第二个参数说明查询需要选择的来自第一个参数所代表的表中的字段。在这个例子中,直到第二行,我们的查询相当于:

SELECT n.nid AS nid, n.title AS title FROM {node} n

注意到大括号已经自动帮我们加好。还有每个字段的别名也定好了(与字段名一致)。如果我们要使用不同的字段别名,就需要使用addField()方法。很快将看到这个细节。我们还可以将其他的表连接进来一起查询,使用innerJoin(), leftJoin()和rightJoin()方法。join()则与innerJoin()等价。join()方法指出要连接的表(参数1),以及表的别名(参数2),当然还包括了以SQL片段描述的连接的条件(参数3)。注意到在这个例子上,我们在join语句中使用了%alias字符串,这是因为当我们在使用表的别名(本例为u)合并表的时候,可能发生表别名之前已经存在于查询中的情况,以至于u指代的并不是我们预想中的别名的情况。虽然在本例中相信不会发生这一现象,但注意到在query_alter() Hooks(钩子)中可能会导致这一情况的发生,所以这么做以避免错误是值得的。

Join()方法返回的是实际使用的表的别名,以便我们在之后的方法调用中使用。在本例中我们也将从用户表中选择一个字段,即用户名,并给予它别名“username”。同样的,因为有可能这个别名已经被使用了,所以addField()方法将会返回最终这个字段实际使用的别名。

我们的查询到现在为止看起来像是这样:

SELECT n.nid AS nid, n.title AS title, u.name AS username FROM {node} n INNER JOIN {users} u ON u.nid = n.nid

现在我们要向查询添加限制条件了,就是WHERE子句。通过condition()方法可以完成这个工作,方法接受一个字段名,一个用来匹配的值,以及一个可选的操作符。缺省的操作是“相等”操作。如例子里所描述的,增加一个WHERE子句,用来匹配用户名为“Bob”以及节点创建时间在上个星期至今(意味着,创建时间戳大于或等于现在的时间减去七天包含的秒数)。更复杂的条件也一样可以用SQL语句片段来进行描述。

然后我们让查询将结果按照创建时间进行排序,为降序(DESC)并且只返回从第零个结果开始的五个结果,也就是意味着最新的五个节点。至此我们的查询看起来相当于这样:

SELECT n.nid AS nid, n.title AS title, u.name AS username
FROM {node} n
INNER JOIN {users} u ON u.nid = n.nid
WHERE (n.created >= 1286213869)
AND (u.name = 'Bob')
ORDER BY n.created DESC
LIMIT 5 OFFSET 0

最后还有一个很重要的方法需要调用,即addTag()。这个方法并不直接影响查询,但是确实给查询做了个”标记“。一旦一个查询在转换为SQL字符串之前被标记上,它将会先通过实现了hook_query_alter()和hook_query_TAG_alter()的函数。这将允许其他的模块能够有机会来对查询进行修改。这里的标签”node_access“,重要性在于它允许节点访问系统(node access system)对查询进行修改,以便过滤掉结果中那些当前用户无权限访问的节点。

当查询节点表的时候,应该总是使用包含node_access标签的动态查询,如不然,则意味着有安全漏洞的存在。

另外还需注意到查询生成器的大多数方法都返回自身的查询对象,也就是他们可以被连接(chainable)起来。但 addField()和join()除外,因为他们返回的是系统生成的别名(如前所述)。本例也就可以像下面这样书写:

$query = db_select('node', 'n');
$u_alias = $query->innerJoin('users' ,'u', '%alias.uid = n.uid');
$query->addField($u_alias, 'name', 'username');
$result = $query
    ->fields('n', array('nid, title'));
    ->condition("{$u_alias}.name", 'Bob');
    ->condition('n.created', REQUEST_TIME - 604800, '>=');
    ->orderBy('n.created', 'DESC');
    ->range(0, 5);
    ->addTag('node_access')
    ->execute();

查询生成器能够用来生成比例子复杂得多的查询,包括子查询(subselects),复杂的AND或者OR条件等。可以查看在线文档以获得完整的信息。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值