$table) {
$out .= schema_phpprint_table($name, $table);
}
return $out;
}
function schema_phpprint_table($name, $table) {
$cols = array();
foreach ($table['fields'] as $colname => $col) {
$cols[] = "'$colname' => ".schema_phpprint_column($col);
}
$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);
}
}
$out = '';
$out .= "\$schema['".$name."'] = array(\n 'fields' => array(\n ";
$out .= implode(",\n ", $cols);
$out .= "),\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";
}
if (count($index) > 0) {
$out .= " 'indexes' => array(\n ";
$out .= implode(",\n ", $index);
$out .= "),\n";
}
$out .= ");\n";
return $out;
}
function schema_phpprint_column($col) {
$attrs = array();
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]'";
}
else if (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]";
}
}
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
//////////////////////////////////////////////////////////////////////
function schema_unprefix_table($name) {
global $db_prefix;
static $_db_prefix;
if (is_array($db_prefix)) {
if (!isset($_db_prefix)) {
foreach ($db_prefix as $key => $val) {
$_db_prefix[$val.$key] = $key;
}
}
if (isset($_db_prefix[$name])) {
return $_db_prefix[$name];
} else if (!empty($db_prefix['default']) && preg_match('@^'.$db_prefix['default'].'(.*)@', $name, $m)) {
return $m[1];
} else {
// On pgsql, key and index names are also prefixed
// (e.g. 'prefix_blocks_roles_rid_idx').
foreach ($db_prefix as $key => $val) {
if (($key != 'default' && preg_match('@^'.$val.'('.$key.'.*)@', $name, $m)) ||
($key == 'default' && preg_match('@^'.$val.'(.*)@', $name, $m))) {
return $m[1];
}
}
return $name;
}
} else if (!empty($db_prefix) && preg_match('@^'.$db_prefix.'(.*)@', $name, $m)) {
return $m[1];
}
return $name;
}
function schema_invoke($op) {
global $db_type;
$function = 'schema_'.$db_type.'_'.$op;
$args = func_get_args();
array_shift($args);
return call_user_func_array($function, $args);
}
function schema_engine_invoke($engine, $op) {
global $db_type;
if (!isset($engine)) {
$engine = $db_type;
}
$function = 'schema_'.$engine.'_'.$op;
$args = func_get_args();
array_shift($args);
return call_user_func_array($function, $args);
}
// Convert a column's Schema type into an engine-specific data type.
function schema_engine_type($col, $table, $field, $engine = NULL) {
$map = schema_engine_invoke($engine, 'engine_type_map');
$size = (isset($col['size']) ? $col['size'] : 'normal');
$type = $col['type'].':'.$size;
if (isset($map[$type])) {
return $map[$type];
} else {
drupal_set_message(t('%table.%field: no %engine type for Schema type %type.',
array('%engine' => $engine, '%type' => $type, '%table' => $table, '%field' => $field)),
'error');
return $col['type'];
}
}
// Convert an engine-specific data type into a Schema type.
function schema_schema_type($type, $table, $field, $engine = NULL) {
$map = schema_engine_invoke($engine, 'schema_type_map');
$type = strtolower($type);
if (isset($map[$type])) {
return explode(':', $map[$type]);
} else {
drupal_set_message(t('Field %table.%field: no Schema type for %engine type %type.',
array('%engine' => $engine, '%type' => $type, '%table' => $table, '%field' => $field)),
'error');
return array($type, 'normal');
}
}
// schema_compare_schemas: Compare two complete schemas. $ref is
// considered the reference copy and $inspect is compared against
// it. If $inspect is NULL, a schema for the currently database is
// generated and used.
function schema_compare_schemas($ref, $inspect = NULL) {
if (! isset($inspect)) {
$inspect = schema_invoke('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) {
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) {
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) {
$module = $table['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;
}
// schema_compare_table: Compare a reference specification (such as
// one returned by a module's hook_schema) to an inspected specification from
// the database. If $inspect is not provided, the database is inspected.
//
function schema_compare_table($ref, $inspect = NULL) {
global $db_type;
$_db_type = $db_type;
if ($_db_type == 'mysqli') {
$_db_type = 'mysql';
}
if (! isset($inspect)) {
$inspect = schema_invoke('inspect', $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'));
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);
$dbtype = schema_engine_type($col, $ref['name'], $colname);
list($col['type'], $col['size']) = schema_schema_type($dbtype, $ref['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;
}
// XXX These should be unified so one reason contains all
// mismatches between the columns.
$colcmp1 = array_diff_assoc($col, $inspect['fields'][$colname]);
if (count($colcmp1) != 0) {
foreach ($colcmp1 as $key => $val) {
$reasons[] = ("column $colname:
declared: ".
schema_phpprint_column($col).'
actual: '.
schema_phpprint_column($inspect['fields'][$colname]));
}
}
$colcmp2 = array_diff_assoc($inspect['fields'][$colname], $col);
if (count($colcmp2) != 0) {
foreach ($colcmp2 as $key => $val) {
if (isset($col_keys[$key]) && !isset($colcmp1[$key])) {
if (!isset($col['key']) && isset($inspect['fields'][$colname]) &&
$inspect['fields'][$colname][$key] === FALSE) {
$notes[] = "column $colname: key '$key' not set, ignoring inspected default value";
}
else {
$reasons[] = ("column $colname:
declared: ".
schema_phpprint_column($col).'
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";
}
else if ($ref['primary key'] !== $inspect['primary key']) {
$reasons[] = ("primary key:
declared: ".
schema_phpprint_key($ref['primary key']).'
actual: '.
schema_phpprint_key($inspect['primary key']));
}
}
else if (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:
declared: ".
schema_phpprint_key($key).'
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);
}
//////////////////////////////////////////////////////////////////////
// Schema administration and UI
//////////////////////////////////////////////////////////////////////
function schema_init() {
schema_require();
}
function schema_require() {
static $done = 0;
if ($done++) { return; }
$path = drupal_get_path('module', 'schema');
require_once("$path/schema_util.inc");
// Load all our module 'on behalfs' so they will be available for
// any module (including this one) that needs them.
$files = drupal_system_listing('schema_.*\.inc$', $path.'/modules','name',0);
foreach($files as $file) {
// The filename format is very specific. It must be schema_MODULENAME.inc
$module = substr_replace($file->name, '', 0, 7);
require_once("./$file->filename");
}
global $db_type, $schema_engines;
if (!isset($db_type)) {
return;
}
$schema_engines = array();
if (0) {
// Load the schema database engine for the currently active database.
$engine = (drupal_get_path('module', 'schema').
'/engines/schema_'.$db_type.'.inc');
if (is_file($engine)) {
require_once($engine);
$schema_engines[] = $db_type;
}
} else {
// Load all Schema database engines.
$files =drupal_system_listing('schema_.*\.inc$',$path.'/engines','name',0);
foreach($files as $file) {
require_once("./$file->filename");
$schema_engines[] = substr($file->filename, strlen($path)+16, -4);
}
}
if (array_search($db_type, $schema_engines) === FALSE) {
drupal_set_message('The Schema module does not support the "'.$db_type.
'" database type.', 'error');
}
}
function schema_perm() {
return array('administer schema');
}
function schema_menu() {
$items['admin/build/schema'] = array(
'title' => 'Schema',
'description' => 'Manage the database schema for this system.',
'page callback' => 'schema_report',
'access arguments' => array('administer schema'),
);
$items['admin/build/schema/report'] = array(
'title' => 'Compare',
'type' => MENU_DEFAULT_LOCAL_TASK,
'page callback' => 'schema_report',
'weight' => -10,
);
$items['admin/build/schema/describe'] = array(
'title' => 'Describe',
'type' => MENU_LOCAL_TASK,
'page callback' => 'schema_describe',
'weight' => -8,
);
$items['admin/build/schema/inspect'] = array(
'title' => 'Inspect',
'type' => MENU_LOCAL_TASK,
'page callback' => 'schema_inspect',
);
$items['admin/build/schema/sql'] = array(
'title' => 'SQL',
'type' => MENU_LOCAL_TASK,
'page callback' => 'schema_sql',
);
// This can't work unless we rename the functions in database.*.inc.
global $db_type, $schema_engines;
if (FALSE && isset($schema_engines) && is_array($schema_engines)) {
foreach ($schema_engines as $engine) {
$items['admin/build/schema/sql/'.$engine] = array(
'title' => t($engine),
'type' => ($engine == $db_type ? MENU_DEFAULT_LOCAL_TASK :
MENU_LOCAL_TASK),
'page callback' => 'schema_sql',
'callback arguments' => $engine,
);
}
}
$items['admin/build/schema/show'] = array(
'title' => 'Show',
'type' => MENU_LOCAL_TASK,
'page callback' => 'schema_show',
'weight' => 10,
);
return $items;
}
function _schema_process_description($desc) {
return preg_replace('@{([a-z_]+)}@i', '$1', $desc);
}
function schema_describe() {
$schema = drupal_get_schema(NULL, TRUE);
ksort($schema);
$row_hdrs = array(t('Name'), t('Type[:Size]'), t('Null?'), t('Default'));
$output = <<
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.
EOT; $output .= drupal_render($form); return $output; } function schema_sql($engine = NULL) { $schema = drupal_get_schema(NULL, TRUE); $sql = ''; foreach ($schema as $name => $table) { if (substr($name, 0, 1) == '#') { continue; } if ($engine) { $stmts = call_user_func('schema_'.$engine.'_create_table_sql', $table); } else { $stmts = db_create_table_sql($name, $table); } $sql .= implode(";\n", $stmts).";\n\n"; } $output = <<