作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
通过数据库抽象层, Drupal可以支持多个数据库,比如内置支持的MySQL、PostreSQL、SQLite,以及通过第三方模块支持的SqlServer、Oracle等等。除此以外,Drupal在数据库方面,还提供了进一步的支持,这就是使用Schema来描述数据库表结构,这对于那些需要创建自己的数据库表的模块,提供了极大的方便。这样,我们创建好Schema定义,Drupal就能够将其翻译成具体数据库的语法,比如MySQL的、PostreSQL的。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
我们在第2章中已经看到,当我们编写的模块需要创建一个或者多个数据库表来存储信息时,创建和维护表结构的指令都放在了模块的install文件中。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
这个字段是用来存储整数的,比如节点id、vid。如果unsigned键为TRUE的话,那么将不允许使用负整数。Node表中vid字段就是采用的这种类型:
'vid' => array(
'description' => 'The current {node_revision}.vid version identifier.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
),
一个序列字段是用来保存自增数字的。例如,当添加一个节点时,node表中的nid字段将会自增。序列字段必须索引;通常会把它作为主键进行索引。
'nid' => array(
'description' => 'The primary identifier for a node.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
浮点数字是用来存储浮点数据类型的。对于浮点数字来说,tiny, small, medium, 和normal型浮点一般是没有区别的;另外,big型浮点用来声明双精度字段。Ubercart的uc_products表中weight字段用到了这一个类型。
'weight' => array(
'description' => 'Physical weight.',
'type' => 'float',
'not null' => TRUE,
'default' => 0.0,
),
数字数据类型允许你声明数字的精度和小数位数。精度指的是数字的有效数字位数。小数位数指的是小数点右边的数字位数。例如,123.45的精度为5,小数位数为2。这里不使用size键。到目前为止,Drupal核心中还没有用到该字段。Ubercart的uc_products表中list_price字段用到了这一个类型。
'list_price' => array(
'description' => 'Suggested retail price.',
'type' => 'numeric',
'precision' => 16,
'scale' => 5,
'not null' => TRUE,
'default' => 0.0,
),
二进位大型对象数据类型用于存储二进制数据。二进位数据包括音乐,图片,或者视频。Size的可选值有normal 和big。Field模块的field_config表中data字段就是使用的这种类型,用来存储序列化的数据。
'data' => array(
'type' => 'blob',
'size' => 'big',
'not null' => TRUE,
'serialize' => TRUE,
'description' => 'Serialized data containing the field properties that do not warrant a dedicated column.',
),
表.模式定义中的Type和Size键与本地数据库类型的对应关系
type |
size |
MySQL type & size/range |
PostgreSQL type & size/range |
SQLite type |
serial |
tiny |
tinyint, 1 B |
serial, 4 B |
integer |
serial |
small |
smallint, 2 B |
serial, 4 B |
integer |
serial |
medium |
mediumint, 3 B |
serial, 4 B |
integer |
serial |
big |
bigint, 8 B |
bigserial, 8 B |
integer |
serial |
normal |
int, 4 B |
serial, 4 B |
integer |
int |
tiny |
tinyint, 1 B |
smallint, 2 B |
integer |
int |
small |
smallint, 2 B |
smallint, 2 B |
integer |
int |
medium |
mediumint, 3 B |
int, 4 B |
integer |
int |
big |
bigint, 8 B |
bigint, 8 B |
integer |
int |
normal |
int, 4 B |
int, 4 B |
integer |
float |
tiny |
float, 4 B |
real, 6 digits |
float |
float |
small |
float, 4 B |
real, 6 digits |
float |
float |
medium |
float, 4 B |
real, 6 digits |
float |
float |
big |
double, 8 B |
double precision, 15 digits |
float |
float |
normal |
float, 4 B |
real, 6 digits |
float |
numeric |
normal |
numeric, 65 digits |
numeric, 1000 digits |
numeric |
varchar |
normal |
varchar, 255 B or 64 KB (1) |
varchar, 1 GB |
varchar |
char |
normal |
char, 255 B |
character, 1 GB |
(UNSUPPORTED) |
text |
tiny |
tinytext, 256 B |
text, unlimited |
text |
text |
small |
tinytext, 256 B |
text, unlimited |
text |
text |
medium |
mediumtext, 16 MB |
text, unlimited |
text |
text |
big |
longtext, 4 GB |
text, unlimited |
text |
text |
normal |
text, 16 KB |
text, unlimited |
text |
blob |
big |
longblob, 4 GB |
bytea, 4 GB |
blob |
blob |
normal |
blob, 16 KB |
bytea, 4 GB |
blob |
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
向数据库表添加一个字段。 |
|
向数据库表添加一个索引。 |
|
向数据库表添加一个主键。 |
|
向数据库表添加一个唯一键。 |
|
修改一个字段定义。 |
|
创建一个数据库表。 |
|
删除一个字段。 |
|
删除一个索引 |
|
删除数据库的主键。 |
|
删除一个表。 |
|
删除一个唯一键。 |
|
检查给定表中该字段是否存在。 |
|
根据指定的包含字段键的数组,返回包含字段名字的数组。 |
|
为字段设置默认值。 |
|
把一个字段设置为没有默认值。 |
|
按照给定的基表名字,查找对应的所有数据库表。 |
|
检查给定表中该索引是否存在。 |
|
重命名一个表。 |
|
检查该数据库表是否存在。 |
|
获取一个表的模式定义,或者整个数据库的模式定义。 |
|
返回一个模块的未处理过并且未修改过的模式(schema)。 |
|
创建模块中hook_schema里面定义的所有数据库表。 |
|
从一个表的模式定义中返回一列字段。该字段列表适用于SQL查询。 |
|
删除模块中hook_schema里面定义的所有数据库表。 |
|
基于数据库模式,向数据库中保存(插入或者更新)一个记录。 |
|
定义数据库模式的当前版本。 |
我们在前面定义了block_morelink这个数据库表。但是有人在使用的过程中,出现错误,并且在我们的项目页面提交了bug,http://drupal.org/node/1159446,“Install Exception on new site: Syntax error or access violation: 1071 Specified key was too long”。这个问题的原因是,url,title的长度加在一起超过了主键的限制。此时我们需要修改数据库模式的定义。
将block_morelink_schema中的主键
'primary key' => array('module', 'delta', 'url', 'title'),
修改为:
'primary key' => array('module', 'delta'),
对于那些已经安装了这个模块的用户来说,我们需要为其提供一个升级路径,代码如下:
/**
* change 'primary key' to array('module', 'delta').
*/
function block_morelink_update_7000(&$sandbox) {
db_drop_primary_key('block_morelink');
db_add_primary_key('block_morelink', array('module', 'delta'));
}
在这里我们实现了钩子函数hook_update_N(&$sandbox),由于这是block_morelink模块的第一个更新函数,所以我们将这里的N设置为了7000,那么第二个更新函数就应该是7001了。在钩子函数中,我们首先删除了原有的主键,接着添加了新版的主键。Drupal会追踪模块模式的当前版本,在运行完这个更新以后,该模块的模式版本就设置为了7000,这一信息存储在system表的schema_version列中。
我们再看一个例子,这段代码摘自于feeds模块的install文件:
function feeds_update_7100(&$sandbox) {
$spec = array(
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
'description' => 'State of import or clearing batches.',
'serialize' => TRUE,
);
db_change_field('feeds_source', 'batch', 'state', $spec);
$spec = array(
'type' => 'text',
'size' => 'big',
'not null' => FALSE,
'description' => 'Cache for fetcher result.',
'serialize' => TRUE,
);
db_add_field('feeds_source', 'fetcher_result', $spec);
}
在这个更新中,首先是将feeds_source中的字段batch重命名为了state,接着添加了一个新的字段fetcher_result。注意这里面分别用到了db_change_field和db_add_field。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
Drupal在安装过程中,一般将数据库表的创建,委托给drupal_install_schema()函数;而drupal_install_schema()负责从模块的schema钩子中获取模式定义,转换为具体数据库上的语法,最后创建相应的表结构。我们回顾一下,第二章中,我们创建的模式:
/**
* Implements hook_schema().
*/
function block_morelink_schema() {
$schema['block_morelink'] = array(
'description' => 'Stores more link path.',
'fields' => array(
'module' => array(
'type' => 'varchar',
'length' => 64,
'not null' => TRUE,
'description' => "The block's origin module, from {block}.module.",
),
'delta' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'description' => "The block's unique delta within module, from {block}.delta.",
),
'url' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => "The more link url of a block.",
),
'title' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => "The more link title of a block.",
),
),
'primary key' => array('module', 'delta', 'url', 'title'),
'indexes' => array(
'url' => array('url'),
),
);
return $schema;
}
这个模式定义描述了block_morelink表,它包含4个varchar类型的字段。它还定义了一个联合主键,为url定义了一个普通索引。注意,在字段描述中,引用另一个表中的字段时,需要为其使用花括号。这样模式模块(参看下一节)可以为表的描述构建方便的超链接。
hook_schema为模块定义的每一个数据库表,返回一个带有键的数组。在该数组中,可以使用以下键:
· 'description':一个纯文本字符串,用来描述这个表及其目的。如果这里引用了其它表,那么需要将其放在花括号中。例如,node_revisions表的描述字段包含“为每一个{node},存储每个修订本的标题和正文”。
· 'fields':一个关联数组,用来描述数据库表的列。它的定义仍然是一个关联数组,里面可以使用以下参数:
· 'description': 一个纯文本字符串,用来描述这个字段及其目的。如果这里引用了其它表,那么需要将其放在花括号中。例如,节点表的vid字段的描述包括“总是为这个字段保存最大的(最新的){node_revision}.vid的值”。
· 'type': 通用的数据类型有:'char'、 'varchar'、 'text'、'blob'、'int'、 'float'、 'numeric'、 'serial'。大多数类型会映射到对应数据库引擎上的具体数据库类型。对于自增字段,需要使用'serial'。在MySQL上,这就会转换为'INT auto_increment'。
· 'mysql_type', 'pgsql_type', 'sqlite_type',等等:如果你需要的类型,没有包含在前面所列的内置支持的数据类型列表中,那么你可以为每个后台的数据库指定一个类型。在这种情况下,你就用不到type参数了,但是你需要注意,对于有些数据库,如果你没有单独为其指定可用的类型,那么你的模式在该类型的数据库上将无法运行。可行的解决办法是使用"text"类型作为一个回退。
· 'serialize':一个布尔值,用来指示该字段是否将存储为序列化的字符串。
· 'size':数据的大小,可选值有:'tiny'、'small'、'medium'、'normal'、'big'。这个用来提示该字段可以存储的最大值,以及用来判定将会使用数据库引擎的哪个具体的数据类型;例如,MySQL上,TINYINT vs. INT vs. BIGINT。默认为'normal',表示选择基础类型,比如,在MySQL上,INT、VARCHAR、 BLOB等等。不是所有的大小,都适用于所有的数据类型。对于可用的联合情况,可参看DatabaseSchema::getFieldTypeMap()。
· 'not null':如果为true,那么在这个数据库中,就不允许存在NULL值;默认为false。
· 'default':字段的默认值。这里区别PHP的类型:'', '0', 和 0表示不同含义。如果你为一个'int'类型的字段指定默认值为'0',此时它将不能正常工作,因为这里的'0'是一个字符串,而不是一个整数。
· 'length': 'char'、'varchar'、 'text'字段类型的最大长度。对于其它字段类型,将忽略这个参数。
· 'unsigned':布尔值,用来指示'int'、 'float'、'numeric'类型的字段是否可以包含符号。默认为FALSE。对于其它字段类型,将忽略这个参数。
· 'precision', 'scale':用于'numeric'类型的字段,用来表示数字的精度和保留的小数位数。这两个值都是必须的。对于其它字段类型,将忽略这两个参数。
对于所有的类型,只有'type'是必须的,其它是可选的;对于'numeric'类型,除了'type'是必须的以外,'precision'和'scale'也是必须的。
· 'primary key':一个数组,里面包含一个或多个字段的键名,用来表示数据库表的主键。
· 'unique keys':包含唯一键的关联数组。它里面包含一个或多个字段的键名,用来表示数据库表的唯一键。
· 'foreign keys': 表关联的关联数组。它里面包含被引用表的名字,和一个包含对应列的数组。对应列的定义使用键值对的形式('source_column' => 'referenced_column')。
· 'indexes': 包含索引的关联数组。它里面包含一个或多个字段的键名,用来表示数据库表的一个索引。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
现在你可能会想,花这么大的功夫,创建一个这么复杂的数组,来向Drupal描述我们的表结构,是不是有点得不偿失啊?当你熟悉了这些语法结构以后,大多数的代码都是拷贝来,拷贝去,实际上你并不需要记忆太多,这非常类似于我们写的八股文。其次,存在第三方的模块,帮我们干这些脏活,这就是Schema(模式)模块,它的下载地址为:http://drupal.org/project/schema。下载了模块,将其启用。导航到“管理 〉结构 〉Schema”,点击Inspect(检查)标签,在这里,我们可以看到所有数据库表的模式定义。如果你为你的数据库表准备好了SQL脚本,那么使用模式模块,它就可以帮你自动生成模式定义,接着将其复制粘贴到你的.install文件中就可以了。
提示 你一般很少需要从头编写一个模式定义。一般情况下,你可以拷贝已有的模式,以此为基础定义自己的模式。或者,你可以使用已有的表,使用模式模块的Inspect(检查)标签,让它帮你创建模式定义。
模式模块还允许你查看任意模块的模式。如图所示,在模式模块中显示了block_morelink模块的模式。
图 4-1.模式模块显示了block_morelink模块的模式。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
在模式定义中声明的字段类型,将会映射成具体数据库中的本地字段类型。例如,一个size为tiny的整数字段将映射为MySQL中的TINYINT字段,或者PostgreSQL中的smallint字段。我们以mysql为例,看看实际的映射,在includes/database/mysql/schema.inc文件中,类DatabaseSchema_mysql中的公共方法getFieldTypeMap(),列出了对应的映射关系。
public function getFieldTypeMap() {
…
static $map = array(
'varchar:normal' => 'VARCHAR',
'char:normal' => 'CHAR',
'text:tiny' => 'TINYTEXT',
'text:small' => 'TINYTEXT',
'text:medium' => 'MEDIUMTEXT',
'text:big' => 'LONGTEXT',
'text:normal' => 'TEXT',
'serial:tiny' => 'TINYINT',
'serial:small' => 'SMALLINT',
'serial:medium' => 'MEDIUMINT',
'serial:big' => 'BIGINT',
'serial:normal' => 'INT',
'int:tiny' => 'TINYINT',
'int:small' => 'SMALLINT',
'int:medium' => 'MEDIUMINT',
'int:big' => 'BIGINT',
'int:normal' => 'INT',
'float:tiny' => 'FLOAT',
'float:small' => 'FLOAT',
'float:medium' => 'FLOAT',
'float:big' => 'DOUBLE',
'float:normal' => 'FLOAT',
'numeric:normal' => 'DECIMAL',
'blob:big' => 'LONGBLOB',
'blob:normal' => 'BLOB',
);
return $map;
}
'not null' => TRUE,
'default' => '',
),
如果default键未被设置,并且not null键被设置为了FALSE,那么默认值将被设置为NULL。
Text
Text字段用于大块的文本。例如,node_type表中的description字段就是这种类型。Text字段可以不使用默认值。
'description' => array(
'description' => 'A brief description of this type.',
'type' => 'text',
'not null' => TRUE,
'size' => 'medium',
'translatable' => TRUE,
),
其中size的类型可以为tiny、small、medium、big、normal。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
Varchar,也就是变长字符字段;对于长度小于256字符的文本,通常使用这一字段类型。它的最大的字符长度,可以使用length键定义。MySQL中 varchar 字段的长度为0–255字符(MySQL 5.0.2 及更早版本)和0–65,535字符(MySQL 5.0.3及以后版本);而PostgreSQL中varchar字段的长度则可以更大一些。
例如,node_chema中node表type字段的定义:
'type' => array(
'description' => 'The {node_type}.type of this node.',
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
如果default键未被设置,并且not null键被设置为了FALSE,那么默认值将被设置为NULL。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
Char字段是定长字符字段。该字段的字符长度,可以使用length键定义。MySQL中char字段的长度为0–255字符。Drupal核心中,我并没有找到char类型的实例。Ubercart模块中的uc_countries表的country_iso_code_2、country_iso_code_3字段用到这一类型:
'country_iso_code_2' => array(
'description' => 'The two-character ISO country code.',
'type' => 'char',
'length' => 2,Edit summary
'not null' => TRUE,
'default' => '',
),
如果default键未被设置,并且not null键被设置为了FALSE,那么默认值将被设置为NULL。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
Text字段用于大块的文本。例如,node_type表中的description字段就是这种类型。Text字段可以不使用默认值。
'description' => array(
'description' => 'A brief description of this type.',
'type' => 'text',
'not null' => TRUE,
'size' => 'medium',
'translatable' => TRUE,
),
其中size的类型可以为tiny、small、medium、big、normal。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
数字型数据类型是用来存储数字的,它包括integer(整数)、serial(序列数)、 float(浮点数)、 和numeric(数字)类型。