Commit 3cafffe6 authored by Dries's avatar Dries

- Killer patch #144765 by bjaspan, frando et al: schema API 1 hits core. Oh, behave.

parent ae762838
......@@ -2513,6 +2513,160 @@ function drupal_common_themes() {
);
}
/**
* @ingroup schemaapi
* @{
*/
/**
* Get the schema defintion of a table, or the whole database schema.
* The returned schema will include any modifications made by any
* module that implements hook_schema_alter().
*
* @param $name
* The name of the table. If not given, the schema of all tables is returned.
* @param $rebuild
* If true, the schema will be rebuilt instead of retreived from the cache.
*/
function drupal_get_schema($name = NULL, $rebuild = FALSE) {
static $schema = array();
if (empty($schema) || $rebuild) {
// Try to load the schema from cache.
if (!$rebuild && $cached = cache_get('schema')) {
$schema = $cached->data;
}
// Otherwise, rebuild the schema cache.
else {
// Load the .schema files.
module_load_all_includes('schema');
// Invoke hook_schema for all modules.
foreach (module_implements('schema') as $module) {
$current = module_invoke($module, 'schema');
_drupal_initialize_schema($module, $current);
$schema = array_merge($current, $schema);
}
drupal_alter('schema', $schema);
cache_set('schema', $schema);
}
}
if (!isset($name)) {
return $schema;
}
elseif (isset($schema[$name])) {
return $schema[$name];
}
else {
return FALSE;
}
}
/**
* Create all tables that a module defines in its hook_schema().
*
* Note: This function does not pass the module's schema through
* hook_schema_alter(). The module's tables will be created exactly
* as the module defines them.
*
* @param $module
* The module for which the tables will be created.
*/
function drupal_install_schema($module) {
$schema = drupal_get_schema_unprocessed($module);
_drupal_initialize_schema($module, $schema);
$ret = array();
foreach ($schema as $table) {
db_create_table($ret, $table);
}
}
/**
* Remove all tables that a module defines in its hook_schema().
*
* Note: This function does not pass the module's schema through
* hook_schema_alter(). The module's tables will be created exactly
* as the module defines them.
*
* @param $module
* The module for which the tables will be removed.
*/
function drupal_uninstall_schema($module) {
$schema = drupal_get_schema_unprocessed($module);
_drupal_initialize_schema($module, $schema);
$ret = array();
foreach ($schema as $table) {
db_drop_table($ret, $table['name']);
}
}
/**
* Returns the unprocessed and unaltered version of a module's schema.
*
* Use this function only if you explicitly need the original
* specification of a schema, as it was defined in a module's
* hook_schema(). No additional default values will be set,
* hook_schema_alter() is not invoked and these unprocessed
* definitions won't be cached.
*
* This function can be used to retrieve a schema specification in
* hook_schema(), so it allows you to derive your tables from existing
* specifications.
*
* It is also used by drupal_install_schema() and
* drupal_uninstall_schema() to ensure that a module's tables are
* created exactly as specified without any changes introduced by a
* module that implements hook_schema_alter().
*
* @param $module
* The module to which the table belongs.
* @param $table
* The name of the table. If not given, the module's complete schema
* is returned.
*/
function drupal_get_schema_unprocessed($module, $table = NULL) {
// Load the .schema file.
module_load_include('schema', $module);
$schema = module_invoke($module, 'schema');
if (!is_null($table) && isset($schema[$table])) {
return $schema[$table];
}
else {
return $schema;
}
}
/**
* Fill in required default values for table definitions returned by
* hook_schema().
*
* @param $module
* The module for which hook_schema() was invoked.
* @param $schema
* The schema definition array as it was returned by the module's
* hook_schema().
*/
function _drupal_initialize_schema($module, &$schema) {
// Set the name and module key for all tables.
foreach ($schema as $name => $table) {
if (empty($table['module'])) {
$schema[$name]['module'] = $module;
}
if (!isset($table['name'])) {
$schema[$name]['name'] = $name;
}
}
}
/**
* @} End of "ingroup schemaapi".
*/
/**
* Parse Drupal info file format.
*
......
......@@ -43,6 +43,22 @@
* common pattern of iterating over the result set using db_fetch_object().
*/
/**
* Perform an SQL query and return success or failure.
*
* @param $sql
* A string containing a complete SQL query. %-substitution
* parameters are not supported.
* @return
* An array containing the keys:
* success: a boolean indicating whether the query succeeded
* query: the SQL query executed, passed through check_plain()
*/
function update_sql($sql) {
$result = db_query($sql, true);
return array('success' => $result !== FALSE, 'query' => check_plain($sql));
}
/**
* Append a database prefix to all tables in a query.
*
......@@ -317,3 +333,144 @@ function db_escape_table($string) {
* @} End of "defgroup database".
*/
/**
* @defgroup schemaapi Schema API
* @{
*
* A Drupal schema definition is an array structure representing one or
* more tables and their related keys and indexes. A schema is defined by
* hook_schema(), which usually lives in a modulename.schema file.
*
* By implenting hook_schema() and specifying the tables your module
* declares, you can easily create and drop these tables on all
* supported database engines. You don't have to deal with the
* different SQL dialects for table creation and alteration of the
* supported database engines.
*
* hook_schema() should return an array with a key for each table that
* the module defines.
*
* The following keys in the table definition are processed during
* table creation:
*
* - 'fields': An associative array ('fieldname' => specification)
* that describes the table's database columns. The specification
* is also an array. The following specification parameters are defined:
*
* - 'type': The generic datatype: 'varchar', 'int', 'serial'
* 'float', 'numeric', 'text', 'blob' or 'datetime'. Most types
* just map to the according database engine specific
* datatypes. Use 'serial' for auto incrementing fields. This
* will expand to 'int auto_increment' on mysql.
* - 'size': The data size: 'tiny', 'small', 'medium', 'normal',
* 'big'. This is a hint about the largest value the field will
* store and determines which of the database engine specific
* datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT).
* 'normal', the default, selects the base type (e.g. on MySQL,
* INT, VARCHAR, BLOB, etc.).
*
* Not all sizes are available for all data types. See
* db_type_map() for possible combinations.
* - 'not null': If true, no NULL values will be allowed in this
* database column. Defaults to false.
* - 'default': The field's default value. The PHP type of the
* value matters: '', '0', and 0 are all different. If you
* specify '0' as the default value for a type 'int' field it
* will not work because '0' is a string containing the
* character "zero", not an integer.
* - 'length': The maximal length of a type 'varchar' or 'text'
* field. Ignored for other field types.
* - 'unsigned': A boolean indicating whether a type 'int', 'float'
* and 'numeric' only is signed or unsigned. Defaults to
* FALSE. Ignored for other field types.
* - 'precision', 'scale': For type 'numeric' fields, indicates
* the precision (total number of significant digits) and scale
* (decimal digits right of the decimal point). Both values are
* mandatory. Ignored for other field types.
*
* All parameters apart from 'type' are optional except that type
* 'numeric' columns must specify 'precision' and 'scale'.
*
* - 'primary key': An array of one or more key column specifers (see below)
* that form the primary key.
* - 'unique key': An associative array of unique keys ('keyname' =>
* specification). Each specification is an array of one or more
* key column specifiers (see below) that form a unique key on the table.
* - 'indexes': An associative array of indexes ('indexame' =>
* specification). Each specification is an array of one or more
* key column specifiers (see below) that form an index on the
* table.
*
* A key column specifier is either a string naming a column or an
* array of two elements, column name and length, specifying a prefix
* of the named column.
*
* As an example, here is a SUBSET of the schema definition for
* Drupal's 'node' table. It show four fields (nid, vid, type, and
* title), the primary key on field 'nid', a unique key named 'vid' on
* field 'vid', and two indexes, one named 'nid' on field 'nid' and
* one named 'node_title_type' on the field 'title' and the first four
* bytes of the field 'type':
*
* $schema['node'] = array(
* 'fields' => array(
* 'nid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
* 'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
* 'type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
'title' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
* ),
* 'primary key' => array('nid'),
* 'unique keys' => array(
* 'vid' => array('vid')
* ),
* 'indexes' => array(
* 'nid' => array('nid'),
* 'node_title_type' => array('title', array('type', 4)),
* ),
* );
*
* @see drupal_install_schema()
*/
/**
* Create a new table from a Drupal table definition.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* A valid and processed table schema definition array.
*/
function db_create_table(&$ret, $table) {
$statements = db_create_table_sql($table);
foreach ($statements as $statement) {
$ret[] = update_sql($statement);
}
}
/**
* Return an array of field names from an array of key/index column
* specifiers. This is usually an identity function but if a
* key/index uses a column prefix specification, this function
* extracts just the name.
*
* @param $fields
* An array of key/index column specifiers.
* @return
* An array of field names.
*/
function db_field_names($fields) {
$ret = array();
foreach ($fields as $field) {
if (is_array($field)) {
$ret[] = $field[0];
}
else {
$ret[] = $field;
}
}
return $ret;
}
/**
* @} End of "defgroup schemaapi".
*/
<?php
// $Id$
/**
* @file
* Functions shared between mysql and mysqli database engines.
*/
/**
* @ingroup schemaapi
* @{
*/
/**
* Generate SQL to create a new table from a Drupal schema definition.
*
* @param $table
* A valid Drupal table definition array.
* @return
* An array of SQL statements to create the table.
*/
function db_create_table_sql($table) {
if (empty($table['mysql_suffix'])) {
$table['mysql_suffix'] = "/*!40100 DEFAULT CHARACTER SET UTF8 */";
}
$sql = "CREATE TABLE {". $table['name'] ."} (\n";
// Add the SQL statement for each field.
foreach ($table['fields'] as $name => $field) {
$sql .= _db_create_field_sql($name, _db_process_field($field)) .", \n";
}
// Process keys & indexes.
if (!empty($table['primary key'])) {
$sql .= " PRIMARY KEY (". _db_create_key_sql($table['primary key']) ."), \n";
}
if (!empty($table['unique keys'])) {
foreach ($table['unique keys'] as $key => $fields)
$sql .= " UNIQUE KEY $key (". _db_create_key_sql($fields) ."), \n";
}
if (!empty($table['indexes'])) {
foreach ($table['indexes'] as $index => $fields)
$sql .= " INDEX $index (". _db_create_key_sql($fields) ."), \n";
}
// Remove the last comma and space.
$sql = substr($sql, 0, -3) ."\n) ";
$sql .= $table['mysql_suffix'];
return array($sql);
}
function _db_create_key_sql($fields) {
$ret = array();
foreach ($fields as $field) {
if (is_array($field)) {
$ret[] = $field[0] .'('. $field[1] .')';
}
else {
$ret[] = $field;
}
}
return implode(', ', $ret);
}
/**
* Set database-engine specific properties for a field.
*
* @param $field
* A field description array, as specified in the schema documentation.
*/
function _db_process_field($field) {
if (!isset($field['size'])) {
$field['size'] = 'normal';
}
// Set the correct database-engine specific datatype.
if (!isset($field['mysql_type'])) {
$map = db_type_map();
$field['mysql_type'] = $map[$field['type'] .':'. $field['size']];
}
if ($field['type'] == 'serial') {
$field['auto_increment'] = TRUE;
}
return $field;
}
/**
* Create an SQL string for a field to be used in table creation or alteration.
*
* Before passing a field out of a schema definition into this function it has
* to be processed by _db_process_field().
*
* @param $name
* Name of the field.
* @param $spec
* The field specification, as per the schema data structure format.
*/
function _db_create_field_sql($name, $spec) {
$sql = "`". $name ."` ". $spec['mysql_type'];
if (isset($spec['length'])) {
$sql .= '('. $spec['length'] .')';
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
$sql .= '('. $spec['scale'] .', '. $spec['precision'] .')';
}
if (!empty($spec['unsigned'])) {
$sql .= ' unsigned';
}
if (!empty($spec['not null'])) {
$sql .= ' NOT NULL';
}
if (!empty($spec['auto_increment'])) {
$sql .= ' auto_increment';
}
if (isset($spec['default'])) {
if (is_string($spec['default'])) {
$spec['default'] = "'". $spec['default'] ."'";
}
$sql .= ' DEFAULT '. $spec['default'];
}
if (empty($spec['not null']) && !isset($spec['default'])) {
$sql .= ' DEFAULT NULL';
}
return $sql;
}
/**
* This maps a generic data type in combination with its data size
* to the engine-specific data type.
*/
function db_type_map() {
// Put :normal last so it gets preserved by array_flip. This makes
// it much easier for modules (such as schema.module) to map
// database types back into schema types.
$map = array(
'varchar:normal' => 'VARCHAR',
'text:tiny' => 'SMALLTEXT',
'text:small' => 'SMALLTEXT',
'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' => 'NUMERIC',
'blob:big' => 'LONGBLOB',
'blob:normal' => 'BLOB',
'datetime:normal' => 'DATETIME',
);
return $map;
}
/**
* Drop a table.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be dropped.
*/
function db_drop_table(&$ret, $table) {
$ret[] = update_sql('DROP TABLE {'. $table .'}');
}
/**
* Add a new field to a table.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* Name of the table to be altered.
* @param $field
* Name of the field to be added.
* @param $spec
* The field specification array, as taken from a schema definition
*/
function db_add_field(&$ret, $table, $field, $spec) {
$query = 'ALTER TABLE {'. $table .'} ADD '. $field .' ';
$query .= _db_create_field_sql($field, _db_process_field($spec));
$ret[] = update_sql($query);
}
/**
* Drop a field.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be dropped.
*/
function db_drop_field(&$ret, $table, $field) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP '. $field);
}
/**
* Set the default value for a field.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be altered.
* @param $default
* Default value to be set. NULL for 'default NULL'.
*/
function db_field_set_default(&$ret, $table, $field, $default) {
if ($default == NULL) {
$default = 'NULL';
}
else {
$default = is_string($default) ? "'$default'" : $default;
}
$ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default);
}
/**
* Set a field to have no default value.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be altered.
*/
function db_field_set_no_default(&$ret, $table, $field) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT');
}
/**
* Add a primary key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $fields
* Fields for the primary key.
*/
function db_add_primary_key(&$ret, $table, $fields) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('.
_db_create_key_sql($fields) .')');
}
/**
* Drop the primary key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
*/
function db_drop_primary_key(&$ret, $table) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP PRIMARY KEY');
}
/**
* Add a unique key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the key.
* @param $fields
* An array of field names.
*/
function db_add_unique_key(&$ret, $table, $name, $fields) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} ADD UNIQUE KEY '.
$name .' ('. _db_create_key_sql($fields) .')');