You are here

schema.module in Schema 5

Same filename and directory in other branches
  1. 8 schema.module
  2. 6 schema.module
  3. 7 schema.module

File

schema.module
View source
<?php

global $schema_engines;

//////////////////////////////////////////////////////////////////////

// schema_phpprint: Return 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();
  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.  Do not warn about core tables because in this D5
  // version those are core tables and do not follow all rules.
  foreach ($ref as $t_name => $table) {
    foreach ($table['fields'] as $c_name => $col) {
      switch ($col['type']) {
        case 'text':
        case 'blob':
          if ($table['module'] != 'core' && 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}:<br/>declared: " . schema_phpprint_column($col) . '<br/>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}:<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";
    }
    else {
      if ($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']);
      }
    }
  }
  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}:<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,
  );
}

//////////////////////////////////////////////////////////////////////

// Schema administration and UI

//////////////////////////////////////////////////////////////////////
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($may_cache = NULL) {
  $items = array();
  if (!$may_cache) {
    schema_require();
    return $items;
  }
  $items[] = array(
    'path' => 'admin/build/schema',
    'title' => 'Schema',
    'description' => 'Manage the database schema for this system.',
    'callback' => 'schema_report',
    'access' => user_access('administer schema'),
  );
  $items[] = array(
    'path' => 'admin/build/schema/report',
    'title' => 'Compare',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'callback' => 'schema_report',
    'weight' => -10,
  );
  $items[] = array(
    'path' => 'admin/build/schema/describe',
    'title' => 'Describe',
    'type' => MENU_LOCAL_TASK,
    'callback' => 'schema_describe',
    'weight' => -8,
  );
  $items[] = array(
    'path' => 'admin/build/schema/inspect',
    'title' => 'Inspect',
    'type' => MENU_LOCAL_TASK,
    'callback' => 'schema_inspect',
  );

  /* Not supported in D5 version.
    $items[] = array(
      'path' => 'admin/build/schema/sql',
      'title' => 'SQL',
      'type' => MENU_LOCAL_TASK,
      'callback' => 'schema_sql',
      );
    */
  $items[] = array(
    'path' => 'admin/build/schema/show',
    'title' => 'Show',
    'type' => MENU_LOCAL_TASK,
    'callback' => 'schema_show',
    'weight' => 10,
  );
  return $items;
}
function _schema_process_description($desc) {
  return preg_replace('@{([a-z_]+)}@i', '<a href="#" onclick="Drupal.toggleFieldset($(\'#table-$1\')[0]); return false;">$1</a>', $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 = <<<EOT
<p>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>
EOT;
  foreach ($schema as $t_name => $t_spec) {
    $rows = array();
    foreach ($t_spec['fields'] as $c_name => $c_spec) {
      $row = array();
      $row[] = $c_name;
      $type = $c_spec['type'];
      if (!empty($c_spec['length'])) {
        $type .= '(' . $c_spec['length'] . ')';
      }
      if (!empty($c_spec['size']) && $c_spec['size'] != 'normal') {
        $type .= ':' . $c_spec['size'];
      }
      if ($c_spec['type'] == 'int' && !empty($c_spec['unsigned'])) {
        $type .= ', unsigned';
      }
      $row[] = $type;
      $row[] = !empty($c_spec['not null']) ? 'NO' : 'YES';
      $row[] = isset($c_spec['default']) ? is_string($c_spec['default']) ? '\'' . $c_spec['default'] . '\'' : $c_spec['default'] : '';
      $rows[] = $row;
      if (!empty($c_spec['description'])) {
        $desc = _schema_process_description($c_spec['description']);
        $rows[] = array(
          array(
            'colspan' => count($row_hdrs),
            'data' => $desc,
          ),
        );
      }
      else {
        if ($t_spec['module'] != 'core') {
          drupal_set_message(_schema_process_description(t('Field {!table}.@field has no description.', array(
            '!table' => $t_name,
            '@field' => $c_name,
          ))), 'error');
        }
      }
    }
    if (empty($t_spec['description']) && $t_spec['module'] != 'core') {
      drupal_set_message(_schema_process_description(t('Table {!table} has no description.', array(
        '!table' => $t_name,
      ))), 'error');
    }
    $form = array();
    $form[$t_name] = array(
      '#type' => 'fieldset',
      '#title' => t('@table (@module module)', array(
        '@table' => $t_name,
        '@module' => $t_spec['module'],
      )),
      '#description' => !empty($t_spec['description']) ? _schema_process_description($t_spec['description']) : '',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#attributes' => array(
        'id' => 'table-' . $t_name,
      ),
    );
    $form[$t_name]['content'] = array(
      '#value' => theme('table', $row_hdrs, $rows),
    );
    $output .= drupal_render($form);
  }
  return $output;
}

// This function just massages the data returned by
// schema_compare_schemas() into HTML.
function schema_report() {
  $states = array(
    'same' => t('Match'),
    'different' => t('Mismatch'),
    'missing' => t('Missing'),
    'extra' => t('Extra'),
  );
  $descs = array(
    'same' => 'Tables for which the schema and database agree.',
    'different' => 'Tables for which the schema and database are different.',
    'missing' => 'Tables in the schema that are not present in the database.',
    'extra' => 'Tables in the database that are not present in the schema.  This indicates previously installed modules that are disabled but not un-installed or modules that do not use the Schema API.',
  );
  $schema = drupal_get_schema(NULL, TRUE);
  $info = schema_compare_schemas($schema);
  foreach ($info as $state => $modules) {
    $counts[$state] = 0;
    $data[$state] = $state == 'extra' ? array() : '';
    if ($state == 'extra') {
      $data[$state] = array_merge($data[$state], $modules);
      $counts[$state] += count($modules);
      continue;
    }
    else {
      if ($state == 'warn') {
        foreach ($modules as $msg) {
          drupal_set_message($msg, 'error');
        }
        continue;
      }
    }
    foreach ($modules as $module => $tables) {
      $counts[$state] += count($tables);
      switch ($state) {
        case 'same':
        case 'missing':
          $data[$state] .= theme('item_list', array_keys($tables), $module);
          break;
        case 'different':
          $items = array();
          foreach ($tables as $name => $stuff) {
            $items[] = "<h4>{$name}</h4>" . theme('item_list', array_merge($tables[$name]['reasons'], $tables[$name]['notes']));
          }
          $form = array();
          $form[$module] = array(
            '#type' => 'fieldset',
            '#title' => t($module),
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
            '#value' => '',
          );
          $form[$module]['content'] = array(
            '#value' => theme('item_list', $items),
          );
          $data[$state] .= drupal_render($form);
          break;
      }
    }
  }
  if (isset($data['extra'])) {
    $data['extra'] = theme('item_list', $data['extra']);
  }
  $form = array();
  $weight = 0;
  foreach ($states as $state => $content) {
    $content = isset($data[$state]) ? $data[$state] : '';
    $form[$state] = array(
      '#type' => 'fieldset',
      '#title' => t('@state (@count)', array(
        '@state' => $states[$state],
        '@count' => isset($counts[$state]) ? $counts[$state] : 0,
      )),
      '#description' => t($descs[$state]),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#weight' => $weight++,
      '#value' => '',
    );
    $form[$state]['content'] = array(
      '#type' => 'markup',
      '#value' => $content,
    );
  }
  $output = <<<EOT
<p>This page compares the live database as it currently exists against
the combination of all schema information provided by all enabled modules.</p>
EOT;
  $output .= drupal_render($form);
  return $output;
}
function schema_inspect() {
  $mods = module_list();
  sort($mods);
  $mods = array_flip($mods);
  $schema = drupal_get_schema(NULL, TRUE);
  $inspect = schema_invoke('inspect');
  foreach ($inspect as $name => $table) {
    $module = isset($schema[$name]) ? $schema[$name]['module'] : 'Unknown';
    if (!isset($form[$module])) {
      $form[$module] = array(
        '#type' => 'fieldset',
        '#access' => TRUE,
        '#title' => check_plain($module),
        '#collapsible' => TRUE,
        '#collapsed' => $module != 'Unknown',
        '#weight' => $module == 'Unknown' ? 0 : $mods[$module] + 1,
        '#value' => '',
      );
    }
    $form[$module][$name] = array(
      '#type' => 'markup',
      '#value' => '<textarea style="width:100%" rows="10">' . check_plain(schema_phpprint_table($name, $table)) . '</textarea>',
    );
  }
  $output = <<<EOT
<p>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>

<p>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>
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 = <<<EOT
<p>This page shows the CREATE TABLE statements that the Schema module
generates for the selected database engine for each table defined by a
module.  It is for debugging purposes.</p>
<textarea style="width:100%" rows="30">{<span class="php-variable">$sql</span>}</textarea>
EOT;
  return $output;
}
function schema_show() {
  $schema = drupal_get_schema(NULL, TRUE);
  $show = print_r($schema, 1);
  $output = <<<EOT
<p>This page displays the Drupal database schema data structure.  It is for
debugging purposes.</p>

<textarea style="width:100%" rows="30">{<span class="php-variable">$show</span>}</textarea>
EOT;
  return $output;
}