本文内容翻译自《Drupal 7 Module Development》一书附录A。 Drupal 7 数据库操作之一:静态与动态查询 (Database API) Drupal 7 的数据库访问层为提高安全性,可扩展性和开发灵活性,进行了全新的革新,一度被称为“DBTNG”(Database: The Next Generation)。现在这个数据访问层支持三种数据库:即Mysql与变体Maria DB, PostgreSQL和SQLite。并且也将推出SQL Server与Oracle的数据层驱动。 关于database API的详尽信息可以查看http://drupal.org/developing/api/database和http://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条件等。可以查看在线文档以获得完整的信息。 -------------------------------------------------------------------------------------------------------------------- Drupal 7 数据库操作之二:插入,更新,删除查询(Insert, Update, Delete queries) (Database API)
插入操作我们已经看到查询操作有两种形式,即静态与动态,而INSERT, UPDATE, DELETE 和MERGE都只有动态方式。这么做主要是基于对大部分SQL数据库的支持的考量。许多数据库的某些字段类型都需要有针对性的处理。这么做的好处是,这些查询操作的动态版本将会比静态操作(如果有的话)更容易,并且日后添加针对某种数据库的优化也会更简单。下面我们一起来看看插入操作(Insert queries)。 就像Select查询那样,插入查询也是从一个构造函数开始的,并且支持连接(chainable)。事实上,插入查询的所有方法都支持连接操作。 $id = db_insert('imports') ->fields(array( 'name' => 'Groucho', 'address' => '123 Casablanca Ave.', 'phone' => '555-1212', )) ->execute();
首先db_insert()方法基于imports表创建一个插入查询对象。随后我们在对象上使用fields()方法。fields()方法接受一个数组代表要插入的值。在这个例子中,我们添加了一名世界上伟大的喜剧演员之一作为一项记录。然后我们执行这一查询,插入操作被翻译为恰当的查询字符串并得到执行。如果imports表当中还有自增或序列字段,查询将会返回生成的ID。这就是全部过程。 db_insert()也可以变得更有趣。比方说,它支持多个查询语句。为了达到这个目的,我们先是要调用fields(),传入一个数组以说明我们将要使用到的字段,然后循环调用values()方法以便将值数组中的值一条条插入进去。 $values[] = array( 'name' => 'Groucho', 'address' => '123 Casablanca Ave.', 'phone' => '555-1212', ); $values[] = array( 'name' => 'Chico', 'address' => '456 Races St.', 'phone' => '555-1234', ); $values[] = array( 'name' => 'Harpo', 'address' => '789 Horn Ave.', 'phone' => '555-1234', ); $values[] = array( 'name' => 'Zeppo', 'address' => '22 University Way', 'phone' => '555-3579', ); $insert = db_insert('imports')->fields(array('name', 'address', 'phone' => '555-1212')); foreach ($values as $value) { $insert->values($value); } $insert->execute();
在支持多个插入语句的数据库中,上述代码将会作为一条查询语句被执行。在不支持的数据库中,则上述代码会变为多个插入语句分开执行(但会包含在单一的事务中(Transaction))。这使得针对大量的数据输入,它们非常强大并有效。请注意插入查询的execute()方法的返回值并无含义,忽略它即可。 更新查询(Update queries)更新查询看起来就像是插入与选择的结合。它们包含了字段设置以及条件约束。 db_update('imports') ->condition('name', 'Chico') ->fields(array('address' => 'Go West St.')) ->execute();
condition()方法的使用和在选择查询中完全一样,我们也可以根据需要添加多个条件。fields()方法接受一个值数组。上述查询相当于: UPDATE {imports} SET address = 'Go West St.' WHERE name = 'Chico';
我们仍然应该使用动态方法二不是db_query(),因为在一些数据库上(比如PostgreSQL或者Oracle),有可能上述查询不能正常工作,而我们需要运行多个绑定值查询,并考虑其他边界例子。所有这些处理都由数据库层帮我们安排好了。 更新查询的execute()方法的返回值是改变了记录的数量。请注意,”改变“一词并不意味着”匹配“。如果WHERE部分匹配到一条记录但记录已经包含了和将要设置的数据一样的数据,那么它将不会被更新,也不会包含在返回值的计数中。 删除查询(Delete queries)删除查询正像你想想的那样,他们只包含WHERE字句,如下: db_delete('imports') ->condition('name' => 'Zeppo') ->execute();
删除查询的execute()方法返回被删除的记录的条数。 合并查询(Merge queries)合并查询是经常被遗忘的SQL查询之一。部分原因是,许多流行的开源数据库都不直接支持合并查询,尽管它是SQL规范中的一部分。 一个合并查询实际上意味着,”如果这条记录存在,用本查询更新它,如果不存在则用其他查询创建它“。它的语法有点含糊,但这是非常强大的概念。最有用的地方就是在设定一条也许存在或不存在的记录,合并数据到表中。它也能被应用在递增计数器上。 一个真正的合并查询是原子化的(atomic),意味着,我们保证它会在不受干扰的情况下一次完成,否则的话就是完全失败(fail completely)。基于大多数Drupal支持的数据库均不直接支持合并查询,Drupal模仿了这一操作(即拆分为放置在一个事物中的多个查询),在大多数情况下实现它。 合并查询的语法和其他我们看到过的查询相似。下述例子就是关于变量系统的(variable system,Drupal用于存储系统变量的地方): db_merge('variable') ->key(array('name' => $name)) ->fields(array('value' => serialize($value))) ->execute();
key()方法接受一个数组(包含字段/值对)
本文选自:Drupal 7 朗歌私坊菜,谢谢!
9 k* d9 Q+ t9 k r
|