跳转到主要内容
DrupalCMS 提交于 25 September 2012

Drupal正确运行取决于数据库。内容、评论、分类、菜单、用户、角色、权限几乎每一样东西都存储进数据库,并且作为Drupal用来渲染你的站点内容的必须的信息的来源,同时控制那些用户可以访问它。Drupal内部在你的代码和数据库之间有一个轻量级的数据库抽象层。抽象层移除了绝大多数数据库复杂的接口和不同数据库引擎对Drupal的屏蔽。在本章,你将学习数据库抽象层如何工作及怎样使用它,你将看到模块怎样能修改查询,然后,你将看到怎样去连接一个附加的数据库(诸如精灵数据库),最后,你将练习怎样在你的模块的.install文件中包含必要的查询来建立和更新数据库表。

定义数据库参数

Drupal依靠你的站点的settings.php文件知道用什么用户名和密码来连接哪个数据库。此文件一般是sites/axample.com/settings.php或sites/default/settings.php。默认的数据库连接代码看起来就像下面:

$database = array(
  'default' => array(
    'default' = array(
      'driver' => 'mysql',
      'database' => 'databasename',
      'username' => 'username',
      'password' => 'password',
      'host' => 'localhost',
      'port' => '',
      'prefix' => '',
    ),
  ),
);

这个例子连接到Mysql数据库。PostgreSQL用户的连接字符串前缀应该用pgsqldaitimysql。显然,此处使用的数据库名、用户名、密码对于你的数据库应该有效,它们是数据库要求的,不是Drupal要求的,并且它们是在你设置你数据库使用的连接账号时建立的。Drupal安装者询问在你的settings.php文件中能建立$database数组的用户名、密码。 如果你的站点数据库是使用sqlite,设置就相对简单,驱动应该设置到sqlite并且数据库应该是设置到包含数据库名称的路径。

$detabase['default']['default'] = array(
  'driver' => 'sqlite',
  'database' => '/path/to/databasefilename',
);

理解数据库抽象层

数据库抽象层API的工作你不能充分理解,直到你努力多做几次。是否你有个项目,在那里你需要改变数据库系统并且耗尽了多天以详察你的代码去改变针对特定数据库的函数和调用?当有一个数据库抽象层,只要你的查询是ANSI-SQL,你将不用为不同的数据库写不同的查询,例如你不用调用mysql_query()或pg_query(),Drupal使用db_query(),它保证了数据库无关的商业逻辑。 Drupal 7 数据库抽象层基于PHP's Data Object(PDO)库,用于两个打算,第一个是保证你的代码可用于任何数据库,第二个是预防用户提交数据放到查询中引发的注入攻击。抽象层建立的原则是:写一个SQL要比学习一个新的抽象层语言方便得多。 Drupal还提供了一个模式API,它允许你去描述你的数据库模式(就是你要用哪些表和字段)到一个Drupal通用的方式并且Drupal翻译它们到你使用的数据库的细节,我们将在谈到.install文件时再说它们。 Drupal使用检查settings.php文件的$databse数组的方法来决定连接的数据库类型,例如,如果$database['default']['default']['driver']设置成mysql,那么Drupal将包含includes/database.mysql.ic,如果设置等于pgsql,Drupal将包含includes/database.pgsql.inc,如果设置等于sqlite,Drupal将包含includes/database.sqlite.inc,这个机制见图5-1.

Figure 5-1. Drupal determines which database file to include by examining $databases.

如果你看见一个数据库Drupal还不支持,你能为你的数据库自己写个封装函数的实现。更多信息参见本章最后的“Writing Your Own Database Driver”节。

连接数据库

Drupal自动将连接数据库作为它自启动处理的一个部分,你不用担心它做了什么。 如果你是Drupal自身以外工作(如你写了一个PHP脚本或一个存在的Drupal自身之外的需访问Drupal数据库的PHP代码),你要使用下面的途径。

// Make Drupal PHP's current durectory.
chdir('/full/path/to/your/drupal/installation');

// Bootstrap Drupal up through the database phase.
include_once(('./includes/bootstrap.inc');
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);

// Now you can run queries using db_query().
$result = db_query('SELECT title FROM {node}');
...

警告:Drupal经常配置为在sites目录中有多个文件夹,所以站点能从展现阶段移到产品阶段而不改变数据库认证,例如,你有带有测试数据库服务器认证的sites/staging.example.com/settings.php和带有产品数据库服务器认证的sites/www.exapmle.com/settings.php,当监理一个数据库连接时,Drupal允许你使用sites/default/settings.php,因为只不是一个http请求。

执行简单查询

Drupal的db_query()函数常用来在活动连接上执行一个SELECT查询。还有其他函数执行INSERT、UPDATE、DELETE,我也要讲一讲这些,但是首先让我们看看从数据库中提取信息。 这里有些当写一些SQL语句时你需要知道的Drupal特有的语法,首先,表名要封装到大括号中,如果必要,你可以加上前缀,这能给它们以唯一的名字,这个惯例允许主机提供商限制的用户在他们能建立的数据库数量中去安装已有一个数据库的Drupal同时能利用在settings.php中指定数据库前缀来避免表名的冲突。这是个例子提取角色2的名字:

$result = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid', => 2));

注意是用:rid作为占位符。在Drupal中,查询总是用占位符来写,实际分配的值就像一个键=>值对,:rid占位符自动由数组分配给:rid的值替换,数组用来定义所有的分配给查询占位符的值,如下例,增加占位符意味着增加参数:

db_query('SELECT name FROM {role} WHERE rid > :rid AND rid < :max_rid', array(':rid' => 0, ':max_rid' => 3);

上面这行当由数据库有效执行时将变成下面这行:

SELECT name FROM role WHERE rid > 0  AND rid < 3

用户提交的数据必须总是在分隔开的参数中传递,这样值就能无害化以防止SQL注入攻击。 db_query()的第一个参数总是查询自身,剩下的参数是动态值,校验并插入查询字符串,它们的值作为键=>值对传递。 我们应该注意使用此语法是时TRUE、FALSE、NULL要转为他们等量的十进制值1或0,这应该没问题。 让我们看一下某些例子。我们使用一个叫joke的数据库表,它包含3个字段,一个节点ID(整型)、一个版本ID(整型)和一个文本字段包含一个妙语。 让我们卡是一个简单的查询,从表joke中获取所有行的所有字段,条件是字段vid(整型值)与$node->vid相同:

db_query('SELECT * FROM {joke} WHERE vid = :vid', array(':vid' => $node->vid));

下面,让我们使用db_insert()在joke表中插入新行,我们使用->fields和使用键是字段名,值是分配给此行内此字段键的值的键=>值对数组来定义要插入的字段,另外注意语句结尾处的->execute(),它文如其名,就是执行对数据库的插入。

$nid = db_insert('joke')
  ->fields(array(
    'nid' => '4',
    'vid' => 1,
    'punchline' => 'And the pig said oink!',
))
->execute();

下面让我们更新joke表中的所有字段,设置punchline等于“Take my wife,please!”,条件是nid大于或等于3。我要传递一个字段和值的数组去用->fields更新,并且我还要使用->condition设置条件。在这个例子中,我要更新joke表中任何nid大于等于3的记录的punchline字段:

$num_updated = db_update('joke')
  ->fields(array(
    'punchline' => 'Take my wife please!',
  ))
  ->condition('nid', 3, '>=')
  ->execute();

如果我们想看更新影响到了多少行,我能使用更新执行后分配的值$num_updated。 最终,让我们从joke表中删除所有punchline等于“Take my wife please!”的所有行,我将用db_delete函数和条件->修饰符去指定条件一从表中删除记录。

$num_deleted = db_delete('joke')
  ->condition('punchline', 'Take my wife please!')
  ->execute();

检索查询结果

有不同的方式去检索查询的结果,这依赖于你是否需要单个的行,或者全部结果集,或者你计划获得获得某一范围的结果,以便在分页显示的内部使用。

      获得单个值

如果你需要从数据库获得一个单个的值,你可以使用->fetchField()方法去检索这个值,这是一个例子获取joke表的记录数:

$nbr_record = db_query("SELECT count(nid) FROM {joke}")->fetchField();

     获得多行

大多数情况,你想从数据库返回多于一个字段的值,这是典型的迭代方式逐步访问结果集:

$type = 'page';
$status = 1;

$result = db_query("SELECT nid, title FROM {node} WHERE type = :type AND status =:status",
  array(
    ':type' => $type,
    ':status' => 1,
  ));
foreach ($result as $row) {
  echo $row->title . '<br />';
}

上面的代码片段将打印出所有的已发布的类型为page的节点的标题(status字段为0表示未发布的,为1表示已发布),调用db_query()返回一个结果数组,数组的每个元素是表中符合查询条件的一行,使用foreach循环访问结果集数组,并分行打印出每个节点的标题。

      使用查询生成器和查询对象

Drupal7的一个新特性是提供一个能力去用一个查询生成器来构建查询对象。在前面的例子中,我们的查询相对简单,但是如果一些复杂的查询怎么写?手头正好有查询对象使用的查询生成器,让我们展示一个例子,然后在这个概念上将演示去创建复杂的多的查询。 在较早例子中,我建立一个查询从role表中选择值,条件是role ID大于或等于2,如下:

$result = db_query('SELECT name FROM {role} WHERE rid = :rid', array(':rid' => 2));

我将使用查询对象和查询生成器写出相同的查询,首先我将用要select的表及分配给它一个的表标识(这里是r)来创建一个查询对象,然后,我就可以从这个表中引用字段。

$query = db_select('role', 'r')l

下一步,我将展开查询去包含一个条件就是rid=2的哪些字段是我想要从表中返回的。

$query
  ->condition('rid', 2)
  ->fields('r', array(name));

最终,我执行查询,将结果集指派给$result。

$result = $query->execute();

循环访问查询返回数组来打印输出。

foreach ($result as $row) {
  echo $row -> name . '<br />';
}

使用查询对象和查询生成器使构建复杂的数据库查询简单化,如何使用查询生成器的演示将在下面的例子中。

      获取有限范围的结果

执行可能返回几百甚至几千条记录的查询是一种风险,这是你写查询时需要思考的。有一个机制使这种风险最小化,就是使用范围修饰符去约束查询返回记录的最大数量。一个例子是返回所有类型为“page”的节点的查询,如果站点有超过1000的节点,这种查询如果执行了,用户可能就会被信息淹没,不知所措,你能使用范围修饰符去限制你的查询返回的行数,这可以缓解潜在的运行时间太长或信息太多的压力。 下面的查询增加了范围修饰符,设置了开始位置和最大返回数量。

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

$query
  ->condition('type', 'page')
  ->fields('n', array('title'))
  -range(0, 100);
$result = $query->execute();

foreach($result as $row) {
  echo $row->title. '<br />';
}

      分页显示结果

如果你的查询范围大量的行,你可能考虑使用分页器,一个分页程序在一个页面上显示有限的行数,并提供一个导航元素允许浏览者导航。一个例子是一个100行的查询,你能配置查询某一时间显示10行,可以点击“next”去显示下10行,“previous”显示前10行,“first”显示头10行,“last”显示最后10行,或者点击页面号码直接跳到指定页的结果(例如点击5将获得51行到60行)。 为演示分页程序,我将创建一个查询,它将返回node表中的所有节点,每页显示10行,下部有个分页器。 首先我建立查询对象,指令Drupal建立一个使用分页器的查询对象来扩展查询对象。

$query = db_select('node', 'n')->extend('PagerDefault');

下一步我将增加条件、字段、和同时显示行数的数量,使用limit修饰符。

$query
  ->condition('type', 'page')
  ->fields('n', array('title'))
  ->limit(10);

下一步执行查询,遍历结果集,将每一行都增加给一个输出遍历,命名为$output

$output = '';
foreach ($relult as $row) {
  $output .= $row->title . '<br />';
}

下一步调用themeing函数并应用分页器到我的输出,输出结果是同时显示10个项目,下部带有分页器(见图 5-2),并显示结果。分页器怎样处理数据库结果的细节和主题层渲染分页结果的细节见includes/pager.inc。

$output .= theme('pager');
print $output;

Figure 5-2. Drupal’s pager gives built-in navigation through a result set.

      其它常用查询

Drupal'7数据库层提供了另外几个你可能想使用的常用查询功能。第一个例子是排序结果集。使用orderBy方法允许你去给结果集排序,此例显示按标题的字母顺序排序结果集:

$query
  ->condition('type', 'page')
  ->fields('n', array('title'))
  ->orderBy('title', 'ASC');

下一个例子是反序

$query
  ->condition('type', 'page')
  ->fields('n', array('title', 'changed'))
  ->orderBy('changed', 'DESC')
  ->orderBy('title', 'ASC');

这是一个可能产生重复结果的查询,这种情况,可以使用distinct方法过滤掉重复的记录。

$query
  ->condition('type', 'page')
  ->fields('n', array('title', 'changed'))
  ->orderBy('changed', 'DESC')
  ->orderBy('title', 'ASC')
  ->distinct();

更多例子和细节见http://drupal.org/node/310069.

使用druapl_write_record()插入和更新

一个常见的问题是插入一个新的数据库行及更新一个存在的行。代码典型地是测试是插入还是更新,然后执行适当的操作。 因为Drupal的每个表都是用关系模式描述的,Drupal知道一个表中有什么字段及知道字段有什么默认值,通过传递一个键化的字段和值的数组给drupal_write_record(),你能让Drupal生成和执行SQL以代替你手工写它们。 假设你有个表,保持跟踪你的大量小兔子集合,你模块中描述这个表的图表钩子应该这样:

/**
 * Implements hook_schema().
 */
function bunny_schema() {
  $schema['bunnies'] = array(
    'description' => t('Store infomation abuot giant rabbits.'),
    'fields' => array(
      'bid' => array(
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'description' => t('Primary key: Aunique ID for each bunny.'),
      ),
      'name' => array(
        'type' => 'varchar',
        'length' => 64,
        'not null' => TRUE,
        'description' => t('Each bunny gets a name.'),
      ),
      'tons' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'description' => t('The weight of the bunny to the nearest ton.'),
      ),
    ),
    'primary key' => array('bid'),
    'indexes' => array(
      'tons' => array('tons'),
    ),
  );
  return $schema;
}

插入一个新纪录就如同更新一个记录一样简单:

$table = 'bunnies';
$record = new stdClass();
$record->name = t('Bortha');
$record->tons = 2;
drupal_write_record($table, $record);

// The new bunny ID, $record->bid, was st by drupal_write_record()
// since $record is passed by refrence.
watchdog('bunny', 'Added bunny with id %id.', array('%id' => $record->bid));
// Change our mind about the name.
$record->name = t('Bertha');
// Now update the record in the database.
// For updates we pass in the name of the table's primary key.
drupal_write_record($table, $record, 'bid');

watchdog('bunny', 'Updated bunny with id %id.', array('%id" => $record->bid));

数组语法也是支持的,虽然假如$record是个数组,drupal_write_record()也将在内部将其转换为对象。

Schema API

Drupal通过数据库抽象层支持多种数据库(Mysql、PostreSQL、SQLite等等)。每一个想拥有数据库表的模块都使用一个关系模式定义来为Drupal描述这个表,Drupal随后将这个定义翻译成数据库能懂得语法。

      使用模块的.install文件

如同第2章展示的,当你写一个模块它需要一个或多个数据库表来存储东西,在模块发布的.install文件中可以含有指令去建立和维护表结构。 

      创建表

在安装一个新模块时,Drupal自动检查是否在模块的.install文件中有模式定义(图5-3),如果模式定义存在,Drupal就建立模式定义的数据库表,下面演示一个模式定义的通常结构:

$schema['tablename'] = array(
  // table description
  'description' => t('Description of what the table is used for.'),
  'fields' => array(
    // Fields definition.
    'field1' => array(
      'type' => 'int',
      'unsigned' => TRUE,
      'not null' => TRUE,
      'default' => 0,
      'description' => t('Description of what this field is used for.'),
    ),
  ),
  // Index declarations.
  'primary key' => array('field1'),
);

Figure 5-3. The schema definition is used to create the database tables.

让我们看一下modules/book/book.install中的Drupal book表的模式定义。

/**
 * Implements hook_schema().
 */
function book_schema() {
  $schema['book'] = array(
    'description' => 'Stores book outline information. Uniquely connects each node in the outline to a link in {menu_links}',
    'fields' => array(
      'mlid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
        'description' => "The book page's {menu_links}.mlid.",
      ),
      'nid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
        'description' => "The book page's {node}.nid.",
      ),
      'bid' => array(
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'default' => 0,
        'description' => "The book ID is the {book}.nid of the top-level page.",
      ),
    ),
    'primary key' => array('mlid'),
    'unique keys' => array(
       'nid' => array('nid'),
    ),
    'indexes' => array(
      'bid' => array('bid'),
    ),
  );
  return $schema;
}

这个定义描述了book表,有三个int型字段,还有一个主键、一个唯一的索引(意味着此字段的所有条目都是唯一的)、一个常规索引。注意当一个字段在它的描述中引用其它的表时,要加花括号。然后激活模式模块去建立到表描述的方便的超链接。

      使用模式模块

此时此刻,你可能想“太折磨人啦,建立巨大的描述数组去告诉Drupal我的表时什么样的真是苦差事”,但是,不要烦恼,只需简单下载schema模块并激活它(http://drupal.org/project/schema),进入Structure->Schema将给你能力通过点击选项卡去看数据库的任何一个模式定义,如果你使用SQL建立你的表,你能使用schema模块获得模式定义,然后拷贝粘贴进你的.install文件。

Tip:你可能很少去白手起家写一个模式,作为替代,使用你已经存在的表和schema模块的选项卡去建立你自己的模式,你还能使用其它的工具,比如Table Wizard模块去将Drupal的任何表的细节显示给Views模块。

schema模块还允许你去任何模块的模式,例如图5-4展示book模块模式在schema模块中的现实情况,注意表及字段描述中的花括号内的表名是怎样变成有用的链接的。

Figure 5-4. The schema module displays the schema of the book module

      模式字段类型映射到数据库

在模式中声明的字段类型要映射到数据库中的实际类型。例如,一个以tiny尺寸声明的integer字段将成为Mysql的TINYINT字段或PostgreSQL的small int字段,实际映射能通过getFieldTypeMap()函数来打印出来便于查看,或者查看表5-1来查看。

  + Textual

Textual字段包含文本。

  + Varchar

varchar或者可变字符字段,是频繁使用的字段类型来存储长度小于256个字符的文本。字符串最大长度是有键length定义的,Mysql的varchar字段长度为0-255(5.0.2及以前版本)和0-65535(5.0.3及以后)PostgreSQL的varchar字段可能更长。

$field['fieldname'] = array(
  'type' => 'varchar', // Required
  'length' => 255, // Required
  'not null' => TRUE, // Default to FALSE.
  'default' => 'chocolate', // See below.
  'description' => t('Always state the purpose of your field.'),
);

如果default键没有设置同时not null键设置为FALSE,默认值将设置为NULL。

  + Char

char字段是定长的字符字段,它的字符串长度由length键定义,MySQL的char字段长度为0-255个字符。

$field['fieldname'] = array(
  'type' => 'char',
  'length' => 64,
  'not null' => TRUE,
  'default' => 'strawberry',
  'description' => t('Always state the purpose of your field.'),
);

如果default键没有设置同时not null键设置为FALSE,默认值将设置为NULL。

  + Text

Text字段为相当长的文本数据使用的,例如node_revisions表中的body字段(body文本在这里存储),text字段可以不使用默认值。

$field['filename'] = array(
  'type' => 'text',
  'size' => 'small',
  'not null' => TRUE,
  'description' => t('Alway state the purpose of your field.'),
);
  + Numerial

Numerical字段用于存储数字诸如整数、序列号、浮点数、和数值型。

  + Integer

这个字段类型用来存储整数例如节点ID,如果unsigned键为TRUE,那么就不允许负数。

$field['fieldname'] = array(
  'type' => 'int',  // Required
  'unsigned' => TRUE, // Defaults to FALSE.
  'size' => 'small', // tiny | small | medium | normal | big
  'not null' => TRUE, // Defaults to FALSE.
  'description' => t('Always state the purpose of you field.'),
);
  + Serial

serial字段保存增量字段,例如,一个节点增加了,node表的nid字段将增长。这是由插入一行并调用db_last_insert_id()完成的,如果一行的增加是和其它行的增加交织在一起的,并返回了最后的ID,正确的ID还是被返回,因为它是基于“每次连接”的,一个serial字段应该被索引,它通常用来索引成主键。

$field['fieldname'] = array(
  'type' => 'serial', // Required
  'unsigned' => TRUE, // Defaults to FALSE Serial number are usually positive.
  'size' => 'small', // tiny | small | medium | normal | big
  'not null' => TRUE,
  'description' => t('Always state the purpose of you field.'),
);
  + Float

float字段存储浮点数,这与tiny,small,medium,normal明显不同,与之对比,尺寸指明了双精度字段。

$field['fieldname'] = array(
  'type' => 'float', // Required
  'unsigned' => TRUE,
  'size' => 'normal', // tiny | small | medium | normal | big
  'not null' => TRUE,
  'description' => t('Always state the purpose of you field.'),
);
  + Numeric

Numeric数据类型允许你去指定一个数字的精度和小数位,精度是这个数字有效位的总数,小数位是小数点右边位数的总数,例如123.45的精度是5,小数位是2。写的时候不再使用size键了,numeric字段在Drupal的核心中不常用。

$field['fieldname'] = array(
  'type' => 'numeric', // Required
  'unsigned' => TRUE,
  'precision' => 5, // Significant digits.
  'scale' => 2, // Digita to the right of the decimal.
  'not null' => TRUE,
  'description' => t('Always state the purpose of you field.'),
);
  + 日期和时间:Datetime

Drupal核心不使用这个数据类型,更偏爱在整数字段中使用Unix时间戳,datetime格式是包含日期和时间的组合格式。

$field['fieldname'] = array(
  'type' => 'datetime',
  'not null' => TRUE,
  'description' => t('Always state the purpose of you field.'),
);
  + 二进制:Blob

二进制大对象数据(blob)类型用来存储二进制数据(例如,Drupal的缓存表去存储缓存数据),二进制数据可以包含音乐、图片、或视频,可用两个尺寸,normal和big。

$field['fieldname'] = array(
  'type' => 'blob',
  'size' => 'normal',
  'not null' => TRUE,
  'description' => t('Always state the purpose of you field.'),
);

      用mysql_type声明特别的列类型

如果你知道精确的数据库引擎列类型,在模式定义中你能设置mysql_type(或pgsql_type)键,这将覆盖type和size。例如MySQL有一个字段类型叫TINYBLOB对应小的二进制大对象,要指定Drupal在MySQL上运行时应该使用TINYBLOB,如果在另外不同的数据库引擎运行则使用正规的BLOB类型,你应该这样写模式定义:

$field['fieldname'] = array(
  'mysql_type' => 'TINYBLOB', // MySQL will use this.
  'type' => 'blob', // Other databases will use this.
  'size' => 'normal', // Other databases will use this.
  'not null' => TRUE,
  'description' => t('Wee little blobs.')
);

Mysql和PostgreSQL的真实类型如下表:

--------------------------------------------------------------------------------------- 模式定义使用的类型                                      数据库实际使用的类型 ---------------------------------------------------------------------------------------   Type          size            ____MySQL____    ___PostgerSQL___        ____SQLite____ --------------------------------------------------------------------------------------- varchar         normal          VARCHAR          varchar                 VARCHAR char            normal          CHAR             character               VARCHAR text            tiny            TINYTEXT         text                    TEXT text            small           TINYTEXT         text                    TEXT text            medium          MEDIUMTEXT       text                    TEXT text            big             LONGTEXT         text                    TEXT text            normal          TEXT             text                    TEXT serial          tiny            TINYINT          serial                  INTEGER serial          small           SMALLINT         serial                  INTEGER serial          medium          MEDIUMINT        serial                  INTEGER serial          big             BIGINT           bigserial               INTEGER serial          normal          INT              serial                  INTEGER int             tiny            TINYINT          smallint                INTEGER int             small           SMALLINT         smallint                INTEGER int             medium          MEDIUMINT        int                     INTEGER int             big             BIGINT           bigint                  INTEGER int             normal          INT              int                     INTEGER float           tiny            FLOAT            real                    FLOAT float           small           FLOAT            real                    FLOAT float           medium          FLOAT            real                    FLOAT float           big             DOUBLE           double                  FLOAT                                      precision

float           normal          FLOAT            real                    FLOAT numeric         normal          DECIMAL          numeric                 NUMERIC blob            big             LONGBLOB         bytea                   BLOB blob            normal          BLOB             bytea                   BLOB datetime        normal          DATETIME         timestamp               TIMESTAMP ---------------------------------------------------------------------------------------

      维护表

当你创建一个模块的新版本时,你可能改变数据库模式,也行增加一个新的列或在某一个列上增加所有,你不能简单地删除和重建表,因为它包含了数据。这展现了怎样平滑地改变数据库。

      1. 更新你的.install文件中的hook_schema(),那么新用户安装新版的模块就可以安装新的模式了。你的.install文件中的模式定义要永远是最后的表和字段的模式定义。

      2. 通过写一个更新函数给老用户一个更新方法。更新函数写法特殊,以基于Drupal版本号的数字开始,例如版本Drupal7的模块第一次更新函数叫modulename_update_7000(),第二次的就叫modulename_update_7001()。这有个modules/comment/comment.install的例子,展示表名有comments改为comment:

/**
* Rename {comments} table to {comment}.
*/
function comment_update_7002() {
  db_rename_table('comments', 'comment');
}

   3.  这个函数将在更新模块后运行http://exmple.com/update.php时运行。

警告:因为你每次添加一个表、字段、或者索引时,都会修改hook_schema()实现中的模式定义,所以你的更新函数千万不要使用这里的模式定义。你可以把hook_schema()实现中的模式定义看成是当前的,而把更新函数中的模式看成是过去的。参看http://drupal.org/node/150220

用来处理模式的函数的完整列表,可参看http://api.drupal.org/api/group/schemaapi/7

TIP:Drupal会追踪一个模块当前所用的模式版本。这一信息存储在system表中。在运行完本节所示的更新以后,评论模块对应记录中的schema_version的值就变成了6003。为了让Drupal忘记该项,可以使用devel模块中的“Reinstall Modules”(重装模块)选项,或者从system表中删除该模块的记录。

      在卸载时删除表

当一个模块被禁用后,这个模块存储在数据库中的任何数据都剩下来不可触及,以防管理员有心要重用这个模块。模块页面有一个卸载选项卡它可以自动从数据库删除数据,同时你可能想删除你定义的任何变量。这有个第2章我们写的annotation模块的例子:

/**
 * Implements hook_uninstall().
 */
function annotate_uninstall() {
  // Clean up our entry in the variables table.
  variable_del('annotate_nodetypes');
}

      用hool_schema_alter()改变存在的模式

通常模块创建和使用它自己的表,但是如果你的模块想修改一个存在的表的情况呢?假设你的模块绝对地增加一个列到node表,一种简单的方法是到你的数据库并增加一列,但是然后那个应该映射到实际数据库表的Drupal模式定义将会变得不一致;这有个最好的方式:hook_schema_alter()。 假设你有个模块它以某种方式标记节点,并且为效率的原因,你完全地在存在的node表上设置来代替你自己的关联到节点ID的表,你的模块需要做两个东西:在你的模块安装期间修改node表并且修改要映射到实际数据库表的模式定义。功能由hokk_install()完成,后来又由hook_schema_alter()完成。假定你的模块叫markednode.module,你的markednode.install文件应该包含下面的函数:

/**
 * Implements hook_install().
 */
function markednode_install() {
  $field = array(
    'type' => 'int',
    'unsigned' => TRUE,
    'not null' => TRUE,
    'default' => 0,
    'initial' => 0, // Sets initial value for preexisting nodes.
    'description' => t('Whether the node has been marked by the markednode module.'),
  );

  // Create a regular index called 'marked' on the field named 'marked'.
  $keys['indexes'] = array(
    'marked' => array('marked')
  );
  db_add_field('node', 'marked', $field, $keys);
}

/**
 * Implements hook_schema_alter(). We alter $schema by reference.
 *
 * @param $schema
 * The system-wide schema collected by drupal_get_schema().
 */
function markednode_schema_alter(&$schema) {
  // Add field to existing schema.
  $schema['node']['fields']['marked'] = array(
    'type' => 'int',
    'unsigned' => TRUE,
    'not null' => TRUE,
    'default' => 0,
    'description' => t('Whether the node has been marked by the markednode module.'),
  );
}

用hook_query_alter()修改其它模块的查询

这个钩子用于修改Drupal别的地方的你不能直接修改的模块的查询。所有动态选择的查询对象都通过execute()方法在查询字符串完成之前立即传递给hook_query_alter(),给模块修改查询的机会是非常希望的。hook_query_alter()接受单个参数:select查询对象本身。 hook_query_alter()怎样工作有个例子,一个模块叫dbtest利用hook_query_alter()去修改两个查询,第一个修改发生在找到一个标记“db_test_alter_add_range”时,如果找到这个标记,查询就做了修改,增加range(0,2)到查询,第二个修改当查询带有标记“db_test_alter_add_join”时发生,这时,一个关联增加到表test和people之间。

注意:Tags是标记一个查询的字符串,一个查询可以有任何数量的标记。标记常用来标记一个查询使修改钩子决定是否执行动作。标记必须全部小写且只包含字母、数子、下划线、一字母开头,就是说,它们追随通用PHP标识符的规则。

function dbtest_query_alter(SelectQuery $query) {
  // you might add a range
  if ($query->hasTag('db_test_alter_add_range')) {
    $query->range(0, 2),
  }

  // or add a join
  if ($query.hasTag('db_test_alter_add_join')) {
    $people_alias = $query->join('test', 'people', "test_task.pid=people.id");
    $name_field = $query->addField('name', 'people', 'name');
    $query->condition($people_alias . '.id', 2);
  }
 ...
?>

Drupal中连接多个数据库

虽然数据库抽象层使记住函数名变得简单,它还增加了内建的查询安全机制,但有时,我们需要连接第三方数据库或传统数据库,并且它同样需要大量使用Drupal数据库API及获得安全上的好处,好消息是我们能做到,例如,你的模块能打开一个到非Drupal数据库的连接并抽取数据。 在settings.php文件中,$databases是一个由多个数据库连接字符串组成的数组,这是默认语法,描述一个单个的连接:

array(
  'driver' => 'mysql',
  'database' => 'databasename',
  'username' => 'username',
  'password' => 'password',
  'host' => 'localhost',
  'port' => 3306,
  'prefix' => 'myprefix_',
);

作为一个例子,你可以有两个数据库,默认数据库和一个传统数据库:

$databases = array(
  'default' =>array(
    'default' =>array(
      'driver' => 'mysql',
      'database' => 'd7',
      'username' => 'username',
      'password' => 'userpassword',
      'host' => 'localhost',
      'port' => '',
      'prefix' => '',
    ),
  ),
  'legacy' =>array(
    'default' =>array(
      'driver' => 'mysql',
      'database' => 'legacydatabase',
      'username' => 'legacyusername',
      'password' => 'legacyuserpassword',
      'host' => '122.185.22.1',
      'port' => '6060',
    ),
  ),
);

注意:你Drupal站点使用的数据库应该总是用default键定义。

如果你要在Drupal中连接一个其他的数据库,你要用它的键名激活它并在完成后切换回默认连接。

// Get some infomation from a non-Drupal database.
db_set_active('legacy');
$result = db_query("SELECT * FROM lpad_user WHERE uid = :uid", array(':uid' => $user->uid));

// Switch back to default connection when finished.
db_set_active('default');

警告:如果你切换到不同的数据库连接,然后试图做某些东西,如t("text"),它将发生一个错误。t()函数需要数据库活动并且数据库连接保持切换,甚至在你切换它的代码空间之外,因此,为应对这个问题总是要小心地将数据库连接切换回default,并且在实践中你要小心不要调用能转换为数据库请求的代码。

因为数据库抽象层被设计成为每个数据库都使用标识的函数名,多种后端数据库不能同时使用,然而,你可以在http://drupal.org/node/19522查看更多信息,看怎样在同一个站点上允许Mysql和postgreSQL两种链接。

使用一个临时表

如果你进行大量的处理,你可能需要在请求发生时去建立一个临时表,你能使用下面格式来调用db_query_temporary():

$tablename = db_query_temporary($query, $arguments, $options);

  + $query 是准备好了的要执行的查询语句   + $arguments 是要代入查询的值的数组   + $options 控制怎样查询操作的选项数组

返回值是临时表的名字 然后你就能用临时表的名字查询临时表

$final_result = db_query('SELECT foo FROM ' . $tablename);

注意临时表的表名不需要花括号,临时表是暂时性的,不需要通过表前缀处理。与此相反,永久表总是通过用花括号包围来支持表前缀。

注意: Drupal核心不使用临时表,并且使用这个到数据库连接的Drupal数据库用户也可能没有建立临时表的权限,所以模块开发者不要以为运行Drupal的每个人都有这个权限。

自己的数据库驱动

假设我们想为一个新的、未来的使用分子计算来提高效率的名叫DNAbase的数据库系统写一个数据库抽象层,优于白手起家,我们将拷贝一个存在的抽象层来修改它,我们使用PostgreSQL的实现。 首先,我们拷贝includes/database/pgsql/database.inc到includes/database/dnabase/database.inc,然后,改变逻辑,内部的每一个封装器函数映射到DNAbase功能,替换掉原来的PostgreSQL功能。 我们在Drupal中测试连接到DNAbase数据库,在settings.php中使用$databases。 查看http://drupal.org/node/310087获取更多的创建自己的数据库驱动的细节。

小结

读完这一章,你应该可以

      + 理解Drupal的数据库抽象层       + 执行基本查询       + 从数据库中获取单一和多个结果       + 获取有限范围的结果       + 使用分页器       + 理解Drupal模式API       + 写出其它开发者能够修改的查询       + 干净地从其它模块修改查询       + 连接到多个数据库,包括原始数据库       + 写一个抽象层驱动