schema.module

Tracking 7.x-1.x branch
  1. drupal
    1. 7 contributions/schema/schema.module

The Schema module provides functionality built on the Schema API.

Functions & methods

NameDescription
schema_compare"Compare" menu callback. This function just massages the data returned by schema_compare_schemas() into HTML.
schema_compare_schemasCompares two complete schemas.
schema_compare_tableCompares a reference specification (such as one returned by a module's hook_schema) to an inspected specification from the database.
schema_dbobject
schema_describe"Describe" menu callback.
schema_engine_typeConverts a column's Schema type into an engine-specific data type.
schema_inspect"Inspect" menu callback.
schema_menuImplement of hook_menu(). Define menu items and page callbacks. admin/structure/schema calls local task(default): schema_compare() admin/structure/schema/compare calls local task: schema_compare() admin/structure/schema/describe calls…
schema_permissionImplement of hook_permission().
schema_phpprintBuilds a pretty ASCII-formatted version of a $schema array.
schema_phpprint_column
schema_phpprint_key
schema_phpprint_table
schema_schema_typeConvert an engine-specific data type into a Schema type.
schema_settings
schema_show"Show" menu callback. Displays drupal schema as php code, so you can reuse it as you need.
schema_sql"SQL" menu callback.
schema_unprefix_table
_schema_process_description

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * The Schema module provides functionality built on the Schema API.
  5. */
  6. //////////////////////////////////////////////////////////////////////
  7. // Schema print functions
  8. //////////////////////////////////////////////////////////////////////
  9. /**
  10. * Builds a pretty ASCII-formatted version of a $schema array.
  11. *
  12. * This is nothing more than a specialized variation of var_dump and
  13. * similar functions and is used only as a convenience to generate the
  14. * PHP for existing database tables (to bootstrap support for modules
  15. * that previously used CREATE TABLE explicitly) and for debugging.
  16. */
  17. function schema_phpprint($schema) {
  18. $out = '';
  19. foreach ($schema as $name => $table) {
  20. $out .= schema_phpprint_table($name, $table);
  21. }
  22. return $out;
  23. }
  24. function schema_phpprint_table($name, $table) {
  25. $cols = array();
  26. if (isset($table['fields'])) {
  27. foreach ($table['fields'] as $colname => $col) {
  28. $cols[] = "'$colname' => " . schema_phpprint_column($col, TRUE);
  29. }
  30. }
  31. $unique = $index = array();
  32. if (isset($table['unique keys'])) {
  33. foreach ($table['unique keys'] as $keyname => $key) {
  34. $unique[] = "'$keyname' => " . schema_phpprint_key($key);
  35. }
  36. }
  37. if (isset($table['indexes'])) {
  38. foreach ($table['indexes'] as $keyname => $key) {
  39. $index[] = "'$keyname' => " . schema_phpprint_key($key);
  40. }
  41. }
  42. if ($table['description']) {
  43. $description = $table['description'];
  44. }
  45. else {
  46. $description = t('TODO: please describe this table!');
  47. }
  48. $out = '';
  49. $out .= "\$schema['" . $name . "'] = array(\n";
  50. $out .= " 'description' => '$description',\n";
  51. $out .= " 'fields' => array(\n ";
  52. $out .= implode(",\n ", $cols);
  53. $out .= ",\n ),\n";
  54. if (isset($table['primary key'])) {
  55. $out .= " 'primary key' => array('" . implode("', '", $table['primary key']) . "'),\n";
  56. }
  57. if (count($unique) > 0) {
  58. $out .= " 'unique keys' => array(\n ";
  59. $out .= implode(",\n ", $unique);
  60. $out .= "\n ),\n";
  61. }
  62. if (count($index) > 0) {
  63. $out .= " 'indexes' => array(\n ";
  64. $out .= implode(",\n ", $index);
  65. $out .= ",\n ),\n";
  66. }
  67. $out .= ");\n";
  68. return $out;
  69. }
  70. function schema_phpprint_column($col, $multiline=FALSE) {
  71. $attrs = array();
  72. if (isset($col['description']) && $col['description']) {
  73. $description = $col['description'];
  74. }
  75. else {
  76. $description = t('TODO: please describe this field!');
  77. }
  78. unset($col['description']);
  79. $attrs[] = "'description' => '$description'";
  80. if ($col['type'] == 'varchar' || $col['size'] == 'normal') {
  81. unset($col['size']);
  82. }
  83. foreach (array('type', 'unsigned', 'size', 'length', 'not null', 'default') as $attr) {
  84. if (isset($col[$attr])) {
  85. if (is_string($col[$attr])) {
  86. $attrs[] = "'$attr' => '$col[$attr]'";
  87. }
  88. elseif (is_bool($col[$attr])) {
  89. $attrs[] = "'$attr' => " . ($col[$attr] ? 'TRUE' : 'FALSE');
  90. }
  91. else {
  92. $attrs[] = "'$attr' => $col[$attr]";
  93. }
  94. unset($col[$attr]);
  95. }
  96. }
  97. foreach (array_keys($col) as $attr) {
  98. if (is_string($col[$attr])) {
  99. $attrs[] = "'$attr' => '$col[$attr]'";
  100. }
  101. else {
  102. $attrs[] = "'$attr' => $col[$attr]";
  103. }
  104. }
  105. if ($multiline) {
  106. return "array(\n " . implode(",\n ", $attrs) . ",\n )";
  107. }
  108. return "array(" . implode(', ', $attrs) . ")";
  109. }
  110. function schema_phpprint_key($keys) {
  111. $ret = array();
  112. foreach ($keys as $key) {
  113. if (is_array($key)) {
  114. $ret[] = "array('$key[0]', $key[1])";
  115. }
  116. else {
  117. $ret[] = "'$key'";
  118. }
  119. }
  120. return "array(" . implode(", ", $ret) . ")";
  121. }
  122. //////////////////////////////////////////////////////////////////////
  123. // Schema comparison functions
  124. //////////////////////////////////////////////////////////////////////
  125. function schema_unprefix_table($name) {
  126. // TODO: Deal with D7 prefixing
  127. global $db_prefix;
  128. static $_db_prefix;
  129. if (is_array($db_prefix)) {
  130. if (!isset($_db_prefix)) {
  131. foreach ($db_prefix as $key => $val) {
  132. $_db_prefix[$val . $key] = $key;
  133. }
  134. }
  135. if (isset($_db_prefix[$name])) {
  136. return $_db_prefix[$name];
  137. }
  138. elseif (!empty($db_prefix['default']) && preg_match('@^' . $db_prefix['default'] . '(.*)@', $name, $m)) {
  139. return $m[1];
  140. }
  141. else {
  142. // On pgsql, key and index names are also prefixed
  143. // (e.g. 'prefix_blocks_roles_rid_idx').
  144. foreach ($db_prefix as $key => $val) {
  145. if (($key != 'default' && preg_match('@^' . $val . '(' . $key . '.*)@', $name, $m)) ||
  146. ($key == 'default' && preg_match('@^' . $val . '(.*)@', $name, $m))) {
  147. return $m[1];
  148. }
  149. }
  150. return $name;
  151. }
  152. }
  153. elseif (!empty($db_prefix) && preg_match('@^' . $db_prefix . '(.*)@', $name, $m)) {
  154. return $m[1];
  155. }
  156. elseif (empty($_db_prefix)) {
  157. $info = Database::getConnectionInfo($connection = 'default'); // FIXME: obtain the correct connection
  158. $_db_prefix = empty($info['default']['prefix'])
  159. ? ''
  160. : reset($info['default']['prefix']);
  161. $db_prefix = $_db_prefix;
  162. $name = drupal_substr($name, drupal_strlen($_db_prefix));
  163. }
  164. return $name;
  165. }
  166. function schema_dbobject() {
  167. static $engines = array();
  168. $class_name = 'SchemaDatabaseSchema_' . db_driver();
  169. if (!isset($engines[$class_name])) {
  170. $engines[$class_name] = new $class_name(Database::getConnection());
  171. }
  172. return $engines[$class_name];
  173. }
  174. /**
  175. * Converts a column's Schema type into an engine-specific data type.
  176. */
  177. function schema_engine_type($col, $table, $field, $engine = NULL) {
  178. $map = schema_dbobject()->getFieldTypeMap();
  179. $size = (isset($col['size']) ? $col['size'] : 'normal');
  180. $type = $col['type'] . ':' . $size;
  181. if (isset($map[$type])) {
  182. return $map[$type];
  183. }
  184. else {
  185. drupal_set_message(t('%table.%field: no %engine type for Schema type %type.',
  186. array('%engine' => $engine, '%type' => $type, '%table' => $table, '%field' => $field)),
  187. 'warning');
  188. return $col['type'];
  189. }
  190. }
  191. /**
  192. * Convert an engine-specific data type into a Schema type.
  193. */
  194. function schema_schema_type($type, $table, $field, $engine = NULL) {
  195. $map = schema_dbobject()->schema_type_map();
  196. $type = strtolower($type);
  197. if (isset($map[$type])) {
  198. return explode(':', $map[$type]);
  199. }
  200. else {
  201. if (!variable_get('schema_suppress_type_warnings', FALSE)) {
  202. drupal_set_message(t('Field @table.@field: no Schema type for @engine type @type.', array('@engine' => $engine, '@type' => $type, '@table' => $table, '@field' => $field)), 'warning');
  203. }
  204. return array($type, 'normal');
  205. }
  206. }
  207. /**
  208. * Compares two complete schemas.
  209. * @param $ref is considered the reference copy
  210. * @param $inspect is compared against it. If $inspect is NULL, a
  211. * schema for the active database is generated and used.
  212. */
  213. function schema_compare_schemas($ref, $inspect = NULL) {
  214. if (!isset($inspect)) {
  215. $inspect = schema_dbobject()->inspect(variable_get('schema_database_connection', 'default'));
  216. }
  217. $info = array();
  218. // Error checks to consider adding:
  219. // All type serial columns must be in an index or key.
  220. // All columns in a primary or unique key must be NOT NULL.
  221. // Error check: column type and default type must match
  222. foreach ($ref as $t_name => $table) {
  223. if (!isset($table['fields']) || !is_array($table['fields'])) {
  224. drupal_set_message(t('Table %table: Missing or invalid \'fields\' array.', array('%table' => $t_name)), 'warning');
  225. continue;
  226. }
  227. foreach ($table['fields'] as $c_name => $col) {
  228. switch ($col['type']) {
  229. case 'int':
  230. case 'float':
  231. case 'numeric':
  232. if (isset($col['default']) &&
  233. (! is_numeric($col['default']) || is_string($col['default']))) {
  234. $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'])));
  235. }
  236. break;
  237. default:
  238. if (isset($col['default']) && !is_string($col['default'])) {
  239. $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'])));
  240. }
  241. break;
  242. }
  243. }
  244. }
  245. // Error check: 'text' and 'blob' columns cannot have a default value
  246. foreach ($ref as $t_name => $table) {
  247. if (!isset($table['fields'])) {
  248. continue;
  249. }
  250. foreach ($table['fields'] as $c_name => $col) {
  251. switch ($col['type']) {
  252. case 'text':
  253. case 'blob':
  254. if (isset($col['default'])) {
  255. $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']));
  256. }
  257. break;
  258. }
  259. }
  260. }
  261. // Error check: primary keys must be 'not null'
  262. foreach ($ref as $t_name => $table) {
  263. if (isset($table['primary key'])) {
  264. $keys = db_field_names($table['primary key']);
  265. foreach ($keys as $key) {
  266. if (!isset($table['fields'][$key]['not null']) || $table['fields'][$key]['not null'] != TRUE) {
  267. $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));
  268. }
  269. }
  270. }
  271. }
  272. foreach ($ref as $name => $table) {
  273. if (isset($table['module'])) {
  274. $module = $table['module'];
  275. }
  276. else {
  277. $module = '';
  278. }
  279. if (!isset($inspect[$name])) {
  280. $info['missing'][$module][$name] = array('status' => 'missing');
  281. }
  282. else {
  283. $status = schema_compare_table($table, $inspect[$name]);
  284. $info[$status['status']][$module][$name] = $status;
  285. unset($inspect[$name]);
  286. }
  287. }
  288. foreach ($inspect as $name => $table) {
  289. $info['extra'][] = $name;
  290. }
  291. return $info;
  292. }
  293. /**
  294. * Compares a reference specification (such as one returned by a
  295. * module's hook_schema) to an inspected specification from the
  296. * database.
  297. * @param $inspect if not provided, the database is inspected.
  298. */
  299. function schema_compare_table($ref, $inspect = NULL) {
  300. $_db_type = db_driver();
  301. if (!isset($inspect)) {
  302. // TODO: Handle prefixing the D7 way
  303. // $ref_name = db_prefix_tables('{' . $ref['name'] . '}');
  304. $ref_name = $ref['name'];
  305. $inspect = schema_dbobject()->inspect(
  306. variable_get('schema_database_connection', 'default'), $ref_name);
  307. $inspect = $inspect[$ref['name']];
  308. }
  309. if (!isset($inspect)) {
  310. return array('status' => 'missing');
  311. }
  312. $reasons = $notes = array();
  313. $col_keys = array_flip(
  314. array('type', 'size', 'not null', 'length', 'unsigned', 'default', 'scale', 'precision'));
  315. foreach ($ref['fields'] as $colname => $col) {
  316. // Many Schema types can map to the same engine type (e.g. in
  317. // PostgresSQL, text:{small,medium,big} are all just text). When
  318. // we inspect the database, we see the common type, but the
  319. // reference we are comparing against can have a specific type.
  320. // We therefore run the reference's specific type through the
  321. // type conversion cycle to get its common type for comparison.
  322. //
  323. // Sadly, we need a special-case hack for 'serial'.
  324. $serial = ($col['type'] == 'serial' ? TRUE : FALSE);
  325. $name = isset($ref['name']) ? $ref['name'] : '';
  326. $dbtype = schema_engine_type($col, $name, $colname);
  327. list($col['type'], $col['size']) = schema_schema_type($dbtype, $name, $colname);
  328. if ($serial) {
  329. $col['type'] = 'serial';
  330. }
  331. // If an engine-specific type is specified, use it. XXX $inspect
  332. // will contain the schema type for the engine type, if one
  333. // exists, whereas dbtype_type contains the engine type.
  334. if (isset($col[$_db_type . '_type'])) {
  335. $col['type'] = $col[$_db_type . '_type'];
  336. }
  337. $col = array_intersect_key($col, $col_keys);
  338. if (!isset($inspect['fields'][$colname])) {
  339. $reasons[] = "$colname: not in database";
  340. continue;
  341. }
  342. // Account for schemas that contain unnecessary 'default' => NULL
  343. if (!isset($col['default']) ||
  344. (is_null($col['default']) && !isset($inspect['fields'][$colname]['default']))) {
  345. unset($col['default']);
  346. }
  347. $kdiffs = array();
  348. foreach ($col_keys as $key => $val) {
  349. if (!(
  350. // First part tests that item exists for both and has same value in both places
  351. (isset($col[$key]) && !is_null($col[$key]) && $col[$key] !== FALSE
  352. && isset($inspect['fields'][$colname][$key]) && $inspect['fields'][$colname][$key] !== FALSE
  353. && $col[$key] == $inspect['fields'][$colname][$key] )
  354. // Second test is that it does not exist or exists but is null in both places
  355. || ((!isset($col[$key]) || is_null($col[$key]) || $col[$key] === FALSE)
  356. && (!isset($inspect['fields'][$colname][$key]) || $inspect['fields'][$colname][$key] === FALSE) ) ) ) {
  357. // One way or another, difference between the two so note it to explicitly identify it later.
  358. $kdiffs[] = $key;
  359. }
  360. }
  361. if (count($kdiffs) != 0) {
  362. $reasons[] = "column $colname - difference" .
  363. ( count($kdiffs) > 1 ? 's' : '') . " on: " .
  364. implode(', ', $kdiffs) . "<br/>declared: " .
  365. schema_phpprint_column($col) . '<br/>actual: ' .
  366. schema_phpprint_column($inspect['fields'][$colname]);
  367. }
  368. unset($inspect['fields'][$colname]);
  369. }
  370. foreach ($inspect['fields'] as $colname => $col) {
  371. $reasons[] = "$colname: unexpected column in database";
  372. }
  373. if (isset($ref['primary key'])) {
  374. if (!isset($inspect['primary key'])) {
  375. $reasons[] = "primary key: missing in database";
  376. }
  377. elseif ($ref['primary key'] !== $inspect['primary key']) {
  378. $reasons[] = ("primary key:<br />declared: " .
  379. schema_phpprint_key($ref['primary key']) . '<br />actual: ' .
  380. schema_phpprint_key($inspect['primary key']));
  381. }
  382. }
  383. elseif (isset($inspect['primary key'])) {
  384. $reasons[] = "primary key: missing in schema";
  385. }
  386. foreach (array('unique keys', 'indexes') as $type) {
  387. if (isset($ref[$type])) {
  388. foreach ($ref[$type] as $keyname => $key) {
  389. if (!isset($inspect[$type][$keyname])) {
  390. $reasons[] = "$type $keyname: missing in database";
  391. continue;
  392. }
  393. // $key is column list
  394. if ($key !== $inspect[$type][$keyname]) {
  395. $reasons[] = ("$type $keyname:<br />declared: " .
  396. schema_phpprint_key($key) . '<br />actual: ' .
  397. schema_phpprint_key($inspect[$type][$keyname]));
  398. }
  399. unset($inspect[$type][$keyname]);
  400. }
  401. }
  402. if (isset($inspect[$type])) {
  403. foreach ($inspect[$type] as $keyname => $col) {
  404. // this is not an error, the dba might have added it on purpose
  405. $notes[] = "$type $keyname: unexpected (not an error)";
  406. }
  407. }
  408. }
  409. $status = (count($reasons) ? 'different' : 'same');
  410. return array('status' => $status, 'reasons' => $reasons,
  411. 'notes' => $notes);
  412. }
  413. //////////////////////////////////////////////////////////////////////
  414. // Schema administration and UI
  415. //////////////////////////////////////////////////////////////////////
  416. /**
  417. * Implement of hook_init().
  418. * Perform setup tasks.
  419. */
  420. /**
  421. * TODO: Remove this unless someone can explain its purpose. Is it a means for
  422. * other modules to add support for other engines? In that case, it is unnecessary -
  423. * defining a SchemaDatabaseSchema_<driver> class will do the trick.
  424. */
  425. /*
  426. function schema_init() {
  427. schema_require();
  428. }
  429. function schema_require() {
  430. static $done = 0;
  431. if ($done++) {
  432. return;
  433. }
  434. // Load all our module 'on behalfs' so they will be available for
  435. // any module (including this one) that needs them.
  436. // TODO: What precisely is the purpose of this? To allow other
  437. $path = drupal_get_path('module', 'schema');
  438. $files = drupal_system_listing('/schema_.*\.inc$/', $path . '/modules', 'name', 0);
  439. foreach ($files as $file) {
  440. // The filename format is very specific. It must be schema_MODULENAME.inc
  441. $module = substr_replace($file->name, '', 0, 7);
  442. require_once("./$file->filename");
  443. }
  444. }
  445. */
  446. /**
  447. * Implement of hook_permission().
  448. */
  449. function schema_permission() {
  450. return array(
  451. 'administer schema' => array(
  452. 'title' => t('Administer schema module'),
  453. ),
  454. );
  455. }
  456. /**
  457. * Implement of hook_menu().
  458. * Define menu items and page callbacks.
  459. * admin/structure/schema calls local task(default): schema_compare()
  460. * admin/structure/schema/compare calls local task: schema_compare()
  461. * admin/structure/schema/describe calls local task: schema_describe()
  462. * admin/structure/schema/inspect calls local task: schema_inspect()
  463. * admin/structure/schema/sql calls local task: schema_sql()
  464. * admin/structure/schema/show calls local task: schema_show()
  465. */
  466. function schema_menu() {
  467. $items['admin/structure/schema'] = array(
  468. 'title' => 'Schema',
  469. 'description' => 'Manage the database schema for this system.',
  470. 'page callback' => 'drupal_get_form',
  471. 'page arguments' => array('schema_compare'),
  472. 'access arguments' => array('administer schema'),
  473. );
  474. $items['admin/structure/schema/compare'] = array(
  475. 'title' => 'Compare',
  476. 'type' => MENU_DEFAULT_LOCAL_TASK,
  477. 'page callback' => 'drupal_get_form',
  478. 'page arguments' => array('schema_compare'),
  479. 'weight' => -10,
  480. 'access arguments' => array('administer schema'),
  481. );
  482. $items['admin/structure/schema/describe'] = array(
  483. 'title' => 'Describe',
  484. 'type' => MENU_LOCAL_TASK,
  485. 'page callback' => 'drupal_get_form',
  486. 'page arguments' => array('schema_describe'),
  487. 'weight' => -8,
  488. 'access arguments' => array('administer schema'),
  489. );
  490. $items['admin/structure/schema/inspect'] = array(
  491. 'title' => 'Inspect',
  492. 'type' => MENU_LOCAL_TASK,
  493. 'page callback' => 'drupal_get_form',
  494. 'page arguments' => array('schema_inspect'),
  495. 'weight' => -6,
  496. 'access arguments' => array('administer schema'),
  497. );
  498. $items['admin/structure/schema/sql'] = array(
  499. 'title' => 'SQL',
  500. 'type' => MENU_LOCAL_TASK,
  501. 'page callback' => 'schema_sql',
  502. 'weight' => -4,
  503. 'access arguments' => array('administer schema'),
  504. );
  505. // This can't work unless we rename the functions in database.*.inc.
  506. global $_schema_engines;
  507. if (FALSE && isset($_schema_engines) && is_array($_schema_engines)) {
  508. foreach ($_schema_engines as $engine) {
  509. $items['admin/structure/schema/sql/' . $engine] = array(
  510. 'title' => $engine,
  511. 'type' => ($engine == db_driver() ? MENU_DEFAULT_LOCAL_TASK :
  512. MENU_LOCAL_TASK),
  513. 'page callback' => 'schema_sql',
  514. 'callback arguments' => $engine,
  515. 'access arguments' => array('administer schema'),
  516. );
  517. }
  518. }
  519. $items['admin/structure/schema/show'] = array(
  520. 'title' => 'Show',
  521. 'type' => MENU_LOCAL_TASK,
  522. 'page callback' => 'schema_show',
  523. 'weight' => -2,
  524. 'access arguments' => array('administer schema'),
  525. );
  526. $items['admin/structure/schema/settings'] = array(
  527. 'title' => 'Settings',
  528. 'type' => MENU_LOCAL_TASK,
  529. 'page callback' => 'drupal_get_form',
  530. 'page arguments' => array('schema_settings'),
  531. 'weight' => 0,
  532. 'access arguments' => array('administer schema'),
  533. );
  534. return $items;
  535. }
  536. function _schema_process_description($desc) {
  537. return preg_replace('@{([a-z_]+)}@i', '<a href="#" onclick="Drupal.toggleFieldset($(\'#table-$1\')[0]); return false;">$1</a>', $desc);
  538. }
  539. /**
  540. * "Compare" menu callback.
  541. * This function just massages the data returned by
  542. * schema_compare_schemas() into HTML.
  543. */
  544. function schema_compare() {
  545. $form = array();
  546. $form['description'] = array(
  547. '#prefix' => '<div>',
  548. '#markup' => t('This page compares the live database as it currently exists against
  549. the combination of all schema information provided by all enabled modules'),
  550. '#suffix' => '</div>',
  551. );
  552. $states = array(
  553. 'same' => t('Match'),
  554. 'different' => t('Mismatch'),
  555. 'missing' => t('Missing'),
  556. );
  557. $descs = array(
  558. 'same' => 'Tables for which the schema and database agree.',
  559. 'different' => 'Tables for which the schema and database are different.',
  560. 'missing' => 'Tables in the schema that are not present in the database.',
  561. );
  562. $schema = drupal_get_schema(NULL, TRUE);
  563. $info = schema_compare_schemas($schema);
  564. // The info array is keyed by state (same/different/missing/extra/warn). For missing,
  565. // the value is a simple array of table names. For warn, it is a simple array of warnings.
  566. // Get those out of the way first
  567. if (isset($info['warn'])) {
  568. foreach ($info['warn'] as $message) {
  569. drupal_set_message($message, 'warning');
  570. }
  571. unset($info['warn']);
  572. }
  573. $form['extra'] = array(
  574. '#type' => 'fieldset',
  575. '#title' => t('Extra (@count)',
  576. array('@count' => isset($info['extra']) ? count($info['extra']) : 0)),
  577. '#description' => t('Tables in the database that are not present in the schema. This
  578. indicates previously installed modules that are disabled but not un-installed or
  579. modules that do not use the Schema API.'),
  580. '#collapsible' => TRUE,
  581. '#collapsed' => TRUE,
  582. '#weight' => 50, // This goes last
  583. );
  584. $form['extra']['tablelist'] = array(
  585. '#theme' => 'item_list',
  586. '#items' => isset($info['extra']) ? $info['extra'] : array(),
  587. );
  588. unset($info['extra']);
  589. // For the other states, the value is an array keyed by module name. Each value
  590. // in that array is an array keyed by tablename, and each of those values is an
  591. // array containing 'status' (same as the state), an array of reasons, and an array of notes.
  592. $weight = 0;
  593. foreach ($info as $state => $modules) {
  594. // We'll fill in the fieldset title below, once we have the counts
  595. $form[$state] = array(
  596. '#type' => 'fieldset',
  597. '#description' => t($descs[$state]),
  598. '#collapsible' => TRUE,
  599. '#collapsed' => TRUE,
  600. '#weight' => $weight++,
  601. );
  602. $counts[$state] = 0;
  603. foreach ($modules as $module => $tables) {
  604. $counts[$state] += count($tables);
  605. $form[$state][$module] = array(
  606. '#type' => 'fieldset',
  607. '#title' => $module,
  608. '#collapsible' => TRUE,
  609. '#collapsed' => TRUE,
  610. );
  611. switch ($state) {
  612. case 'same':
  613. case 'missing':
  614. $form[$state][$module]['tablelist'] = array(
  615. '#theme' => 'item_list',
  616. '#items' => array_keys($tables),
  617. );
  618. break;
  619. case 'different':
  620. $items = array();
  621. foreach ($tables as $name => $stuff) {
  622. $form[$state][$module][$name] = array(
  623. '#type' => 'fieldset',
  624. '#collapsible' => TRUE,
  625. '#collapsed' => TRUE,
  626. '#title' => $name,
  627. );
  628. $form[$state][$module][$name]['reasons'] = array(
  629. '#theme' => 'item_list',
  630. '#items' => array_merge($tables[$name]['reasons'], $tables[$name]['notes']),
  631. );
  632. }
  633. break;
  634. }
  635. }
  636. }
  637. // Fill in counts in titles
  638. foreach ($states as $state => $description) {
  639. $form[$state]['#title'] = t('@state (@count)',
  640. array('@state' => $states[$state], '@count' => isset($counts[$state]) ? $counts[$state] : 0));
  641. }
  642. return $form;
  643. }
  644. /**
  645. * "Describe" menu callback.
  646. */
  647. function schema_describe() {
  648. $schema = drupal_get_schema(NULL, TRUE);
  649. ksort($schema);
  650. $row_hdrs = array(t('Name'), t('Type[:Size]'), t('Null?'), t('Default'));
  651. $form = array();
  652. $form['description'] = array(
  653. '#prefix' => '<div>',
  654. '#markup' => "This page describes the Drupal database schema. Click on a table name
  655. to see that table's description and fields. Table names within a table or
  656. field description are hyperlinks to that table's description.",
  657. '#suffix' => '</div>',
  658. );
  659. $default_table_description = t('TODO: please describe this table!');
  660. $default_field_description = t('TODO: please describe this field!');
  661. foreach ($schema as $t_name => $t_spec) {
  662. $rows = array();
  663. foreach ($t_spec['fields'] as $c_name => $c_spec) {
  664. $row = array();
  665. $row[] = $c_name;
  666. $type = $c_spec['type'];
  667. if (!empty($c_spec['length'])) {
  668. $type .= '(' . $c_spec['length'] . ')';
  669. }
  670. if (!empty($c_spec['scale']) && !empty($c_spec['precision'])) {
  671. $type .= '(' . $c_spec['precision'] . ', ' . $c_spec['scale'] . ' )';
  672. }
  673. if (!empty($c_spec['size']) && $c_spec['size'] != 'normal') {
  674. $type .= ':' . $c_spec['size'];
  675. }
  676. if ($c_spec['type'] == 'int' && !empty($c_spec['unsigned'])) {
  677. $type .= ', unsigned';
  678. }
  679. $row[] = $type;
  680. $row[] = !empty($c_spec['not null']) ? 'NO' : 'YES';
  681. $row[] = isset($c_spec['default']) ? (is_string($c_spec['default']) ? '\'' . $c_spec['default'] . '\'' : $c_spec['default']) : '';
  682. $rows[] = $row;
  683. if (!empty($c_spec['description']) && $c_spec['description'] != $default_field_description) {
  684. $desc = _schema_process_description($c_spec['description']);
  685. $rows[] = array(array('colspan' => count($row_hdrs), 'data' => $desc));
  686. }
  687. else {
  688. drupal_set_message(_schema_process_description(t('Field {!table}.@field has no description.', array('!table' => $t_name, '@field' => $c_name))), 'warning');
  689. }
  690. }
  691. if (empty($t_spec['description']) || $t_spec['description'] == $default_table_description) {
  692. drupal_set_message(_schema_process_description(t('Table {!table} has no description.', array('!table' => $t_name))), 'warning');
  693. }
  694. $form[$t_name] = array(
  695. '#type' => 'fieldset',
  696. '#title' => t('@table (@module module)',
  697. array('@table' => $t_name, '@module' => isset($t_spec['module']) ? $t_spec['module'] : '')),
  698. '#description' => !empty($t_spec['description']) ? _schema_process_description($t_spec['description']) : '',
  699. '#collapsible' => TRUE,
  700. '#collapsed' => TRUE,
  701. '#attributes' => array('id' => 'table-' . $t_name),
  702. );
  703. $form[$t_name]['content'] = array(
  704. '#theme' => 'table',
  705. '#header' => $row_hdrs,
  706. '#rows' => $rows,
  707. );
  708. }
  709. return $form;
  710. }
  711. /**
  712. * "Inspect" menu callback.
  713. */
  714. function schema_inspect() {
  715. $form = array();
  716. $form['description'] = array(
  717. '#prefix' => '<div>',
  718. '#markup' => t("This page shows the live database schema as it currently
  719. exists on this system. Known tables are grouped by the module that
  720. defines them; unknown tables are all grouped together.</div>
  721. <div>To implement hook_schema() for a module that has existing tables, copy
  722. the schema structure for those tables directly into the module's
  723. hook_schema() and return \$schema."),
  724. '#suffix' => '</div>',
  725. );
  726. $mods = module_list();
  727. sort($mods);
  728. $mods = array_flip($mods);
  729. $schema = drupal_get_schema(NULL, TRUE);
  730. $inspect = schema_dbobject()->inspect(variable_get('schema_database_connection', 'default'));
  731. foreach ($inspect as $name => $table) {
  732. $module = isset($schema[$name]['module']) ? $schema[$name]['module'] : 'Unknown';
  733. if (!isset($form[$module])) {
  734. $form[$module] = array(
  735. '#type' => 'fieldset',
  736. '#access' => TRUE,
  737. '#title' => check_plain($module),
  738. '#collapsible' => TRUE,
  739. '#collapsed' => ($module != 'Unknown'),
  740. '#weight' => ($module == 'Unknown' ? 0 : $mods[$module]+1),
  741. );
  742. }
  743. $form[$module][$name] = array(
  744. '#type' => 'markup',
  745. '#markup' => '<textarea style="width:100%" rows="10">' .
  746. check_plain(schema_phpprint_table($name, $table)) . '</textarea>');
  747. }
  748. return $form;
  749. }
  750. /**
  751. * "SQL" menu callback.
  752. */
  753. function schema_sql($engine = NULL) {
  754. $schema = drupal_get_schema(NULL, TRUE);
  755. $connection = Database::getConnection();
  756. $sql = '';
  757. foreach ($schema as $name => $table) {
  758. if (substr($name, 0, 1) == '#') {
  759. continue;
  760. }
  761. if ($engine) {
  762. $stmts = call_user_func('schema_' . $engine . '_create_table_sql', $table);
  763. }
  764. else {
  765. $stmts = schema_dbobject()->getCreateTableSql($name, $table);
  766. }
  767. $sql .= implode(";\n", $stmts) . ";\n\n";
  768. }
  769. $output = t('<p>This page shows the CREATE TABLE statements that the Schema API
  770. generates for the selected database engine for each table defined by a
  771. module. It is for debugging purposes.</p>');
  772. $output .= "<textarea style=\"width:100%\" rows=\"30\">$sql</textarea>";
  773. return $output;
  774. }
  775. /**
  776. * "Show" menu callback.
  777. * Displays drupal schema as php code, so you can reuse it
  778. * as you need.
  779. */
  780. function schema_show() {
  781. $schema = drupal_get_schema(NULL, TRUE);
  782. $show = var_export($schema, 1);
  783. $output = t('<p>This page displays the Drupal database schema data structure. It is for
  784. debugging purposes.</p>');
  785. $output .= "<textarea style=\"width:100%\" rows=\"30\">$show</textarea>";
  786. return $output;
  787. }
  788. function schema_settings($form, &$form_state) {
  789. global $databases;
  790. if (count($databases) > 1) {
  791. $form['schema_database_connection'] = array(
  792. '#type' => 'select',
  793. '#title' => t('Database connection to use'),
  794. '#default_value' => variable_get('schema_database_connection', 'default'),
  795. '#options' => array_combine(array_keys($databases), array_keys($databases)),
  796. '#description' => t('If you use a secondary database other than the default
  797. Drupal database you can select it here and use schema\'s "compare" and
  798. "inspect" functions on that other database.'),
  799. );
  800. }
  801. $form['schema_status_report'] = array(
  802. '#type' => 'checkbox',
  803. '#title' => t('Include schema comparison reports in site status report'),
  804. '#default_value' => variable_get('schema_status_report', TRUE),
  805. '#description' => t('When checked, schema comparison reports are run on
  806. the Administer page, and included in the site status report.'),
  807. );
  808. $form['schema_suppress_type_warnings'] = array(
  809. '#type' => 'checkbox',
  810. '#title' => t('Suppress schema warnings.'),
  811. '#default_value' => variable_get('schema_suppress_type_warnings', FALSE),
  812. '#description' => t('When checked, missing schema type warnings will be suppressed.'),
  813. );
  814. return system_settings_form($form);
  815. }