schema.module in Schema 7
Same filename and directory in other branches
The Schema module provides functionality built on the Schema API.
File
schema.moduleView source
<?php
/**
* @file
* The Schema module provides functionality built on the Schema API.
*/
/**
* Implements hooK_help().
*/
function schema_help($path, $arg) {
switch ($path) {
case 'admin/structure/schema':
return '<p>' . t('This page compares the live database as it currently exists against the combination of all schema information provided by all enabled modules.') . '<p>';
case 'admin/structure/schema/describe':
return '<p>' . t("This page describes the Drupal database schema. Click on a table name to see that table's description and fields. Table names within a table or field description are hyperlinks to that table's description.") . '</p>';
case 'admin/structure/schema/inspect':
$output = '<p>' . t("This page shows the live database schema as it currently exists on this system. Known tables are grouped by the module that defines them; unknown tables are all grouped together.") . '</p>';
$output .= '<p>' . t("To implement hook_schema() for a module that has existing tables, copy the schema structure for those tables directly into the module's hook_schema() and return \$schema.") . '</p>';
return $output;
case 'admin/structure/schema/sql':
return '<p>' . t('This page shows the CREATE TABLE statements that the Schema API generates for the selected database engine for each table defined by a module. It is for debugging purposes.') . '</p>';
case 'admin/structure/schema/show':
return '<p>' . t('This page displays the Drupal database schema data structure. It is for debugging purposes.') . '</p>';
}
}
/**
* Implements hook_permission().
*/
function schema_permission() {
return array(
'administer schema' => array(
'title' => t('Administer schema module'),
),
);
}
/**
* Implements hook_menu().
*/
function schema_menu() {
$items['admin/structure/schema'] = array(
'title' => 'Schema',
'description' => 'Manage the database schema for this system.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'schema_compare',
),
'access arguments' => array(
'administer schema',
),
'file' => 'schema.pages.inc',
);
$items['admin/structure/schema/compare'] = array(
'title' => 'Compare',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['admin/structure/schema/describe'] = array(
'title' => 'Describe',
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'schema_describe',
),
'weight' => -8,
'access arguments' => array(
'administer schema',
),
'file' => 'schema.pages.inc',
);
$items['admin/structure/schema/inspect'] = array(
'title' => 'Inspect',
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'schema_inspect',
),
'weight' => -6,
'access arguments' => array(
'administer schema',
),
'file' => 'schema.pages.inc',
);
$items['admin/structure/schema/sql'] = array(
'title' => 'SQL',
'type' => MENU_LOCAL_TASK,
'page callback' => 'schema_sql',
'weight' => -4,
'access arguments' => array(
'administer schema',
),
'file' => 'schema.pages.inc',
);
// This can't work unless we rename the functions in database.*.inc.
// global $_schema_engines;
// if (FALSE && isset($_schema_engines) && is_array($_schema_engines)) {
// foreach ($_schema_engines as $engine) {
// $items['admin/structure/schema/sql/' . $engine] = array(
// 'title' => $engine,
// 'type' => ($engine == db_driver() ? MENU_DEFAULT_LOCAL_TASK :
// MENU_LOCAL_TASK),
// 'page callback' => 'schema_sql',
// 'callback arguments' => $engine,
// 'access arguments' => array('administer schema'),
// );
// }
// }
$items['admin/structure/schema/show'] = array(
'title' => 'Show',
'type' => MENU_LOCAL_TASK,
'page callback' => 'schema_show',
'weight' => -2,
'access arguments' => array(
'administer schema',
),
'file' => 'schema.pages.inc',
);
$items['admin/structure/schema/settings'] = array(
'title' => 'Settings',
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array(
'schema_settings_form',
),
'weight' => 50,
'access arguments' => array(
'administer schema',
),
'file' => 'schema.admin.inc',
);
return $items;
}
/**
* Fetch the schema engine class name for a given database connection.
*
* @param string $connection
* A database connection key, defaults to 'default'.
*
* @return
* The schema engine class name if available, otherwise FALSE.
*/
function schema_get_connection_engine_class($connection = 'default') {
if ($info = Database::getConnectionInfo($connection)) {
$driver = $info['default']['driver'];
$class_name = 'SchemaDatabaseSchema_' . $driver;
if (class_exists($class_name)) {
return $class_name;
}
}
return FALSE;
}
/**
* Fetch a schema engine class instance for a given database connection.
*
* @param string $connection
* A database connection key, defaults to the schema_database_connection
* variable, which itself defaults to 'default'.
*
* @return object
* A schema engine class set to the given connection.
*/
function schema_dbobject($connection = NULL) {
if (!isset($connection)) {
$connection = variable_get('schema_database_connection', 'default');
}
if ($class = schema_get_connection_engine_class($connection)) {
return new $class(Database::getConnection('default', $connection));
}
}
/**
* Get an array of connection options that are supported by schema inspection.
*/
function schema_get_connection_options() {
$options =& drupal_static(__FUNCTION__);
if (!isset($options)) {
foreach ($GLOBALS['databases'] as $connection => $targets) {
// Only support connections that can be inspected by schema module.
if (!schema_get_connection_engine_class($connection)) {
continue;
}
$options[$connection] = $connection;
}
}
return $options;
}
//////////////////////////////////////////////////////////////////////
// Schema print functions
//////////////////////////////////////////////////////////////////////
/**
* Builds a pretty ASCII-formatted version of a $schema array.
*
* This is nothing more than a specialized variation of var_dump and
* similar functions and is used only as a convenience to generate the
* PHP for existing database tables (to bootstrap support for modules
* that previously used CREATE TABLE explicitly) and for debugging.
*/
function schema_phpprint($schema) {
$out = '';
foreach ($schema as $name => $table) {
$out .= schema_phpprint_table($name, $table);
}
return $out;
}
function schema_phpprint_table($name, $table) {
$cols = array();
if (isset($table['fields'])) {
foreach ($table['fields'] as $colname => $col) {
$cols[] = "'{$colname}' => " . schema_phpprint_column($col, TRUE);
}
}
$unique = $index = array();
if (isset($table['unique keys'])) {
foreach ($table['unique keys'] as $keyname => $key) {
$unique[] = "'{$keyname}' => " . schema_phpprint_key($key);
}
}
if (isset($table['indexes'])) {
foreach ($table['indexes'] as $keyname => $key) {
$index[] = "'{$keyname}' => " . schema_phpprint_key($key);
}
}
if ($table['description']) {
$description = $table['description'];
}
else {
$description = t('TODO: please describe this table!');
}
$out = '';
$out .= "\$schema['" . $name . "'] = array(\n";
$out .= " 'description' => '{$description}',\n";
$out .= " 'fields' => array(\n ";
$out .= implode(",\n ", $cols);
$out .= ",\n ),\n";
if (isset($table['primary key'])) {
$out .= " 'primary key' => array('" . implode("', '", $table['primary key']) . "'),\n";
}
if (count($unique) > 0) {
$out .= " 'unique keys' => array(\n ";
$out .= implode(",\n ", $unique);
$out .= "\n ),\n";
}
if (count($index) > 0) {
$out .= " 'indexes' => array(\n ";
$out .= implode(",\n ", $index);
$out .= ",\n ),\n";
}
$out .= ");\n";
return $out;
}
function schema_phpprint_column($col, $multiline = FALSE) {
$attrs = array();
if (isset($col['description']) && $col['description']) {
$description = $col['description'];
}
else {
$description = t('TODO: please describe this field!');
}
unset($col['description']);
$attrs[] = "'description' => '{$description}'";
if ($col['type'] == 'varchar' || $col['size'] == 'normal') {
unset($col['size']);
}
foreach (array(
'type',
'unsigned',
'size',
'length',
'not null',
'default',
) as $attr) {
if (isset($col[$attr])) {
if (is_string($col[$attr])) {
$attrs[] = "'{$attr}' => '{$col[$attr]}'";
}
elseif (is_bool($col[$attr])) {
$attrs[] = "'{$attr}' => " . ($col[$attr] ? 'TRUE' : 'FALSE');
}
else {
$attrs[] = "'{$attr}' => {$col[$attr]}";
}
unset($col[$attr]);
}
}
foreach (array_keys($col) as $attr) {
if (is_string($col[$attr])) {
$attrs[] = "'{$attr}' => '{$col[$attr]}'";
}
else {
$attrs[] = "'{$attr}' => {$col[$attr]}";
}
}
if ($multiline) {
return "array(\n " . implode(",\n ", $attrs) . ",\n )";
}
return "array(" . implode(', ', $attrs) . ")";
}
function schema_phpprint_key($keys) {
$ret = array();
foreach ($keys as $key) {
if (is_array($key)) {
$ret[] = "array('{$key[0]}', {$key[1]})";
}
else {
$ret[] = "'{$key}'";
}
}
return "array(" . implode(", ", $ret) . ")";
}
//////////////////////////////////////////////////////////////////////
// Schema comparison functions
//////////////////////////////////////////////////////////////////////
/**
* Unprefix a table name.
*
* This is pretty much the converse of DatabaseConnection::prefixTables().
*
* @param string $name
* The prefixed table name.
* @param DatabaseConnection $connection
* An optional database connection object.
*
* @return string
* The unprefixed table name.
*/
function schema_unprefix_table($name, $connection = NULL) {
$prefixes =& drupal_static(__FUNCTION__, array());
if (!isset($connection)) {
$connection = Database::getConnection();
}
$key = $connection
->getKey();
if (!isset($prefixes[$key])) {
$prefixes[$key] = array();
$info = $connection
->getConnectionOptions();
if (isset($info['prefix'])) {
if (is_array($info['prefix'])) {
$info['prefix'] = $info['prefix'] + array(
'default' => '',
);
}
else {
$info['prefix'] = array(
'default' => $info['prefix'],
);
}
foreach ($info['prefix'] as $table => $prefix) {
if ($table != 'default') {
$prefixes[$key][$prefix . $table] = $table;
}
elseif ($prefix !== '') {
$prefixes[$key][$prefix] = '';
}
}
}
}
return !empty($prefixes[$key]) ? strtr($name, $prefixes[$key]) : $name;
}
/**
* Converts a column's Schema type into an engine-specific data type.
*/
function schema_engine_type($col, $table, $field, $engine = NULL) {
$map = schema_dbobject()
->getFieldTypeMap();
$size = isset($col['size']) ? $col['size'] : 'normal';
$type = $col['type'] . ':' . $size;
if (isset($map[$type])) {
return $map[$type];
}
else {
trigger_error(t('@table.@field: no @engine type for schema type @type.', array(
'@engine' => $engine,
'@type' => $type,
'@table' => $table,
'@field' => $field,
)), E_USER_WARNING);
return $col['type'];
}
}
/**
* Convert an engine-specific data type into a Schema type.
*/
function schema_schema_type($type, $table, $field, $engine = NULL) {
$map = schema_dbobject()
->schema_type_map();
$type = strtolower($type);
if (isset($map[$type])) {
return explode(':', $map[$type]);
}
else {
if (!variable_get('schema_suppress_type_warnings', FALSE)) {
trigger_error(t('@table.@field: no schema type for @engine type @type.', array(
'@engine' => $engine,
'@type' => $type,
'@table' => $table,
'@field' => $field,
)), E_USER_WARNING);
}
return array(
$type,
'normal',
);
}
}
/**
* Compares two complete schemas.
* @param $ref is considered the reference copy
* @param $inspect is compared against it. If $inspect is NULL, a
* schema for the active database is generated and used.
*/
function schema_compare_schemas($ref, $inspect = NULL) {
if (!isset($inspect)) {
$inspect = schema_dbobject()
->inspect();
}
$info = array();
// Error checks to consider adding:
// All type serial columns must be in an index or key.
// All columns in a primary or unique key must be NOT NULL.
// Error check: column type and default type must match
foreach ($ref as $t_name => $table) {
if (!isset($table['fields']) || !is_array($table['fields'])) {
drupal_set_message(t('Table %table: Missing or invalid \'fields\' array.', array(
'%table' => $t_name,
)), 'warning');
continue;
}
foreach ($table['fields'] as $c_name => $col) {
switch ($col['type']) {
case 'int':
case 'float':
case 'numeric':
if (isset($col['default']) && (!is_numeric($col['default']) || is_string($col['default']))) {
$info['warn'][] = t('%table.%column is type %type but its default %default is PHP type %phptype', array(
'%table' => $t_name,
'%column' => $c_name,
'%type' => $col['type'],
'%default' => $col['default'],
'%phptype' => gettype($col['default']),
));
}
break;
default:
if (isset($col['default']) && !is_string($col['default'])) {
$info['warn'][] = t('%table.%column is type %type but its default %default is PHP type %phptype', array(
'%table' => $t_name,
'%column' => $c_name,
'%type' => $col['type'],
'%default' => $col['default'],
'%phptype' => gettype($col['default']),
));
}
break;
}
}
}
// Error check: 'text' and 'blob' columns cannot have a default value
foreach ($ref as $t_name => $table) {
if (!isset($table['fields'])) {
continue;
}
foreach ($table['fields'] as $c_name => $col) {
switch ($col['type']) {
case 'text':
case 'blob':
if (isset($col['default'])) {
$info['warn'][] = t('%table.%column is type %type and may not have a default value', array(
'%table' => $t_name,
'%column' => $c_name,
'%type' => $col['type'],
));
}
break;
}
}
}
// Error check: primary keys must be 'not null'
foreach ($ref as $t_name => $table) {
if (isset($table['primary key'])) {
$keys = db_field_names($table['primary key']);
foreach ($keys as $key) {
if (!isset($table['fields'][$key]['not null']) || $table['fields'][$key]['not null'] != TRUE) {
$info['warn'][] = t('%table.%column is part of the primary key but is not specified to be \'not null\'.', array(
'%table' => $t_name,
'%column' => $key,
));
}
}
}
}
foreach ($ref as $name => $table) {
if (isset($table['module'])) {
$module = $table['module'];
}
else {
$module = '';
}
if (!isset($inspect[$name])) {
$info['missing'][$module][$name] = array(
'status' => 'missing',
);
}
else {
$status = schema_compare_table($table, $inspect[$name]);
$info[$status['status']][$module][$name] = $status;
unset($inspect[$name]);
}
}
foreach ($inspect as $name => $table) {
$info['extra'][] = $name;
}
return $info;
}
/**
* Compares a reference specification (such as one returned by a
* module's hook_schema) to an inspected specification from the
* database.
* @param $inspect if not provided, the database is inspected.
*/
function schema_compare_table($ref, $inspect = NULL) {
$_db_type = db_driver();
if (!isset($inspect)) {
// TODO: Handle prefixing the D7 way
// $ref_name = db_prefix_tables('{' . $ref['name'] . '}');
$ref_name = $ref['name'];
$inspect = schema_dbobject()
->inspect(NULL, $ref_name);
$inspect = $inspect[$ref['name']];
}
if (!isset($inspect)) {
return array(
'status' => 'missing',
);
}
$reasons = $notes = array();
$col_keys = array_flip(array(
'type',
'size',
'not null',
'length',
'unsigned',
'default',
'scale',
'precision',
));
foreach ($ref['fields'] as $colname => $col) {
// Many Schema types can map to the same engine type (e.g. in
// PostgresSQL, text:{small,medium,big} are all just text). When
// we inspect the database, we see the common type, but the
// reference we are comparing against can have a specific type.
// We therefore run the reference's specific type through the
// type conversion cycle to get its common type for comparison.
//
// Sadly, we need a special-case hack for 'serial'.
$serial = $col['type'] == 'serial' ? TRUE : FALSE;
$name = isset($ref['name']) ? $ref['name'] : '';
$dbtype = schema_engine_type($col, $name, $colname);
list($col['type'], $col['size']) = schema_schema_type($dbtype, $name, $colname);
if ($serial) {
$col['type'] = 'serial';
}
// If an engine-specific type is specified, use it. XXX $inspect
// will contain the schema type for the engine type, if one
// exists, whereas dbtype_type contains the engine type.
if (isset($col[$_db_type . '_type'])) {
$col['type'] = $col[$_db_type . '_type'];
}
$col = array_intersect_key($col, $col_keys);
if (!isset($inspect['fields'][$colname])) {
$reasons[] = "{$colname}: not in database";
continue;
}
// Account for schemas that contain unnecessary 'default' => NULL
if (!isset($col['default']) || is_null($col['default']) && !isset($inspect['fields'][$colname]['default'])) {
unset($col['default']);
}
$kdiffs = array();
foreach ($col_keys as $key => $val) {
if (!(isset($col[$key]) && !is_null($col[$key]) && $col[$key] !== FALSE && isset($inspect['fields'][$colname][$key]) && $inspect['fields'][$colname][$key] !== FALSE && $col[$key] == $inspect['fields'][$colname][$key] || (!isset($col[$key]) || is_null($col[$key]) || $col[$key] === FALSE) && (!isset($inspect['fields'][$colname][$key]) || $inspect['fields'][$colname][$key] === FALSE))) {
// One way or another, difference between the two so note it to explicitly identify it later.
$kdiffs[] = $key;
}
}
if (count($kdiffs) != 0) {
$reasons[] = "column {$colname} - difference" . (count($kdiffs) > 1 ? 's' : '') . " on: " . implode(', ', $kdiffs) . "<br/>declared: " . schema_phpprint_column($col) . '<br/>actual: ' . schema_phpprint_column($inspect['fields'][$colname]);
}
unset($inspect['fields'][$colname]);
}
foreach ($inspect['fields'] as $colname => $col) {
$reasons[] = "{$colname}: unexpected column in database";
}
if (isset($ref['primary key'])) {
if (!isset($inspect['primary key'])) {
$reasons[] = "primary key: missing in database";
}
elseif ($ref['primary key'] !== $inspect['primary key']) {
$reasons[] = "primary key:<br />declared: " . schema_phpprint_key($ref['primary key']) . '<br />actual: ' . schema_phpprint_key($inspect['primary key']);
}
}
elseif (isset($inspect['primary key'])) {
$reasons[] = "primary key: missing in schema";
}
foreach (array(
'unique keys',
'indexes',
) as $type) {
if (isset($ref[$type])) {
foreach ($ref[$type] as $keyname => $key) {
if (!isset($inspect[$type][$keyname])) {
$reasons[] = "{$type} {$keyname}: missing in database";
continue;
}
// $key is column list
if ($key !== $inspect[$type][$keyname]) {
$reasons[] = "{$type} {$keyname}:<br />declared: " . schema_phpprint_key($key) . '<br />actual: ' . schema_phpprint_key($inspect[$type][$keyname]);
}
unset($inspect[$type][$keyname]);
}
}
if (isset($inspect[$type])) {
foreach ($inspect[$type] as $keyname => $col) {
// this is not an error, the dba might have added it on purpose
$notes[] = "{$type} {$keyname}: unexpected (not an error)";
}
}
}
$status = count($reasons) ? 'different' : 'same';
return array(
'status' => $status,
'reasons' => $reasons,
'notes' => $notes,
);
}
/**
* Implements hook_schema_field_type_map_alter() on behalf of field.module.
*/
function field_schema_field_type_map_alter(array &$map, DatabaseSchema $schema, DatabaseConnection $connection) {
$db_type = $connection
->databaseType() . '_type';
// Load all fields in order to inspect each field's schema.
$include_additional = array(
'include_deleted' => TRUE,
'include_inactive' => TRUE,
);
$fields = field_read_fields(array(), $include_additional);
foreach ($fields as $field) {
$cols = $field['columns'];
// Loop through each field column and add any missing mappings.
foreach ($cols as $col) {
if (!isset($col['type'])) {
continue;
}
$size = isset($col['size']) ? $col['size'] : 'normal';
$generic_type = $col['type'] . ':' . $size;
if (!isset($map[$generic_type])) {
// Use engine specific type if it exists.
$map[$generic_type] = isset($col[$db_type]) ? $col[$db_type] : $col['type'];
$map[$generic_type] = drupal_strtoupper($map[$generic_type]);
}
}
}
}
/**
* A copy of drupal_get_schema() optimized for schema module use.
*/
function schema_get_schema() {
$schema =& drupal_static(__FUNCTION__);
if (!isset($schema)) {
$schema = array();
module_load_all_includes('install');
// Invoke hook_schema for all modules.
foreach (module_implements('schema') as $module) {
// Cast the result of hook_schema() to an array, as a NULL return value
// would cause array_merge() to set the $schema variable to NULL as well.
// That would break modules which use $schema further down the line.
$current = (array) module_invoke($module, 'schema');
// Set 'module' and 'name' keys for each table. Keep descriptions,
// they are slow but very useful for this module.
_drupal_schema_initialize($current, $module, FALSE);
$schema = array_merge($schema, $current);
}
drupal_alter('schema', $schema);
}
return $schema;
}
Functions
Name | Description |
---|---|
field_schema_field_type_map_alter | Implements hook_schema_field_type_map_alter() on behalf of field.module. |
schema_compare_schemas | Compares two complete schemas. |
schema_compare_table | Compares a reference specification (such as one returned by a module's hook_schema) to an inspected specification from the database. |
schema_dbobject | Fetch a schema engine class instance for a given database connection. |
schema_engine_type | Converts a column's Schema type into an engine-specific data type. |
schema_get_connection_engine_class | Fetch the schema engine class name for a given database connection. |
schema_get_connection_options | Get an array of connection options that are supported by schema inspection. |
schema_get_schema | A copy of drupal_get_schema() optimized for schema module use. |
schema_help | Implements hooK_help(). |
schema_menu | Implements hook_menu(). |
schema_permission | Implements hook_permission(). |
schema_phpprint | Builds a pretty ASCII-formatted version of a $schema array. |
schema_phpprint_column | |
schema_phpprint_key | |
schema_phpprint_table | |
schema_schema_type | Convert an engine-specific data type into a Schema type. |
schema_unprefix_table | Unprefix a table name. |