schema.inc

Schema API handling functions.

File

drupal/core/includes/schema.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Schema API handling functions.
  5. */
  6. use Drupal\Core\Cache\CacheBackendInterface;
  7. use Drupal\Core\Database\Database;
  8. use Drupal\Core\Utility\SchemaCache;
  9. /**
  10. * @addtogroup schemaapi
  11. * @{
  12. */
  13. /**
  14. * Indicates that a module has not been installed yet.
  15. */
  16. const SCHEMA_UNINSTALLED = -1;
  17. /**
  18. * Indicates that a module has been installed.
  19. */
  20. const SCHEMA_INSTALLED = 0;
  21. /**
  22. * Gets the schema definition of a table, or the whole database schema.
  23. *
  24. * The returned schema will include any modifications made by any
  25. * module that implements hook_schema_alter().
  26. *
  27. * @param string $table
  28. * The name of the table. If not given, the schema of all tables is returned.
  29. * @param bool $rebuild
  30. * If TRUE, the schema will be rebuilt instead of retrieved from the cache.
  31. */
  32. function drupal_get_schema($table = NULL, $rebuild = FALSE) {
  33. static $schema;
  34. if ($rebuild || !isset($table)) {
  35. $schema = drupal_get_complete_schema($rebuild);
  36. }
  37. elseif (!isset($schema)) {
  38. $schema = new SchemaCache();
  39. }
  40. if (!isset($table)) {
  41. return $schema;
  42. }
  43. if (isset($schema[$table])) {
  44. return $schema[$table];
  45. }
  46. else {
  47. return FALSE;
  48. }
  49. }
  50. /**
  51. * Gets the whole database schema.
  52. *
  53. * The returned schema will include any modifications made by any
  54. * module that implements hook_schema_alter().
  55. *
  56. * @param bool $rebuild
  57. * If TRUE, the schema will be rebuilt instead of retrieved from the cache.
  58. */
  59. function drupal_get_complete_schema($rebuild = FALSE) {
  60. static $schema;
  61. if (!isset($schema) || $rebuild) {
  62. // Try to load the schema from cache.
  63. if (!$rebuild && $cached = cache()->get('schema')) {
  64. $schema = $cached->data;
  65. }
  66. // Otherwise, rebuild the schema cache.
  67. else {
  68. $schema = array();
  69. // Load the .install files to get hook_schema.
  70. Drupal::moduleHandler()->loadAllIncludes('install');
  71. require_once __DIR__ . '/common.inc';
  72. // Invoke hook_schema for all modules.
  73. foreach (module_implements('schema') as $module) {
  74. // Cast the result of hook_schema() to an array, as a NULL return value
  75. // would cause array_merge() to set the $schema variable to NULL as well.
  76. // That would break modules which use $schema further down the line.
  77. $current = (array) module_invoke($module, 'schema');
  78. // Set 'module' and 'name' keys for each table, and remove descriptions,
  79. // as they needlessly slow down cache()->get() for every single request.
  80. _drupal_schema_initialize($current, $module);
  81. $schema = array_merge($schema, $current);
  82. }
  83. drupal_alter('schema', $schema);
  84. if ($rebuild) {
  85. cache()->deleteTags(array('schema' => TRUE));
  86. }
  87. // If the schema is empty, avoid saving it: some database engines require
  88. // the schema to perform queries, and this could lead to infinite loops.
  89. if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) {
  90. cache()->set('schema', $schema, CacheBackendInterface::CACHE_PERMANENT, array('schema' => TRUE));
  91. }
  92. }
  93. }
  94. return $schema;
  95. }
  96. /**
  97. * Returns an array of available schema versions for a module.
  98. *
  99. * @param string $module
  100. * A module name.
  101. *
  102. * @return array|bool
  103. * If the module has updates, an array of available updates sorted by
  104. * version. Otherwise, FALSE.
  105. */
  106. function drupal_get_schema_versions($module) {
  107. $updates = &drupal_static(__FUNCTION__, NULL);
  108. if (!isset($updates[$module])) {
  109. $updates = array();
  110. foreach (Drupal::moduleHandler()->getModuleList() as $loaded_module => $filename) {
  111. $updates[$loaded_module] = array();
  112. }
  113. // Prepare regular expression to match all possible defined hook_update_N().
  114. $regexp = '/^(?<module>.+)_update_(?<version>\d+)$/';
  115. $functions = get_defined_functions();
  116. // Narrow this down to functions ending with an integer, since all
  117. // hook_update_N() functions end this way, and there are other
  118. // possible functions which match '_update_'. We use preg_grep() here
  119. // instead of foreaching through all defined functions, since the loop
  120. // through all PHP functions can take significant page execution time
  121. // and this function is called on every administrative page via
  122. // system_requirements().
  123. foreach (preg_grep('/_\d+$/', $functions['user']) as $function) {
  124. // If this function is a module update function, add it to the list of
  125. // module updates.
  126. if (preg_match($regexp, $function, $matches)) {
  127. $updates[$matches['module']][] = $matches['version'];
  128. }
  129. }
  130. // Ensure that updates are applied in numerical order.
  131. foreach ($updates as &$module_updates) {
  132. sort($module_updates, SORT_NUMERIC);
  133. }
  134. }
  135. return empty($updates[$module]) ? FALSE : $updates[$module];
  136. }
  137. /**
  138. * Returns the currently installed schema version for a module.
  139. *
  140. * @param string $module
  141. * A module name.
  142. * @param bool $reset
  143. * Set to TRUE after installing or uninstalling an extension.
  144. * @param bool $array
  145. * Set to TRUE if you want to get information about all modules in the
  146. * system.
  147. *
  148. * @return string|int
  149. * The currently installed schema version, or SCHEMA_UNINSTALLED if the
  150. * module is not installed.
  151. */
  152. function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
  153. static $versions = array();
  154. if ($reset) {
  155. $versions = array();
  156. }
  157. if (!$versions) {
  158. if (!$versions = Drupal::keyValue('system.schema')->getAll()) {
  159. $versions = array();
  160. }
  161. }
  162. if ($array) {
  163. return $versions;
  164. }
  165. else {
  166. return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED;
  167. }
  168. }
  169. /**
  170. * Updates the installed version information for a module.
  171. *
  172. * @param string $module
  173. * A module name.
  174. * @param string $version
  175. * The new schema version.
  176. */
  177. function drupal_set_installed_schema_version($module, $version) {
  178. Drupal::keyValue('system.schema')->set($module, $version);
  179. // Reset the static cache of module schema versions.
  180. drupal_get_installed_schema_version(NULL, TRUE);
  181. }
  182. /**
  183. * Creates all tables defined in a module's hook_schema().
  184. *
  185. * Note: This function does not pass the module's schema through
  186. * hook_schema_alter(). The module's tables will be created exactly as the
  187. * module defines them.
  188. *
  189. * @param string $module
  190. * The module for which the tables will be created.
  191. */
  192. function drupal_install_schema($module) {
  193. $schema = drupal_get_schema_unprocessed($module);
  194. _drupal_schema_initialize($schema, $module, FALSE);
  195. foreach ($schema as $name => $table) {
  196. db_create_table($name, $table);
  197. }
  198. }
  199. /**
  200. * Removes all tables defined in a module's hook_schema().
  201. *
  202. * Note: This function does not pass the module's schema through
  203. * hook_schema_alter(). The module's tables will be created exactly as the
  204. * module defines them.
  205. *
  206. * @param string $module
  207. * The module for which the tables will be removed.
  208. *
  209. * @return array
  210. * An array of arrays with the following key/value pairs:
  211. * - success: a boolean indicating whether the query succeeded.
  212. * - query: the SQL query(s) executed, passed through check_plain().
  213. */
  214. function drupal_uninstall_schema($module) {
  215. $schema = drupal_get_schema_unprocessed($module);
  216. _drupal_schema_initialize($schema, $module, FALSE);
  217. foreach ($schema as $table) {
  218. if (db_table_exists($table['name'])) {
  219. db_drop_table($table['name']);
  220. }
  221. }
  222. }
  223. /**
  224. * Returns the unprocessed and unaltered version of a module's schema.
  225. *
  226. * Use this function only if you explicitly need the original
  227. * specification of a schema, as it was defined in a module's
  228. * hook_schema(). No additional default values will be set,
  229. * hook_schema_alter() is not invoked and these unprocessed
  230. * definitions won't be cached.
  231. *
  232. * This function can be used to retrieve a schema specification in
  233. * hook_schema(), so it allows you to derive your tables from existing
  234. * specifications.
  235. *
  236. * It is also used by drupal_install_schema() and
  237. * drupal_uninstall_schema() to ensure that a module's tables are
  238. * created exactly as specified without any changes introduced by a
  239. * module that implements hook_schema_alter().
  240. *
  241. * @param string $module
  242. * The module to which the table belongs.
  243. * @param string $table
  244. * The name of the table. If not given, the module's complete schema
  245. * is returned.
  246. */
  247. function drupal_get_schema_unprocessed($module, $table = NULL) {
  248. // Load the .install file to get hook_schema.
  249. module_load_install($module);
  250. $schema = module_invoke($module, 'schema');
  251. if (isset($table)) {
  252. if (isset($schema[$table])) {
  253. return $schema[$table];
  254. }
  255. return array();
  256. }
  257. elseif (!empty($schema)) {
  258. return $schema;
  259. }
  260. return array();
  261. }
  262. /**
  263. * Fills in required default values for table definitions from hook_schema().
  264. *
  265. * @param array $schema
  266. * The schema definition array as it was returned by the module's
  267. * hook_schema().
  268. * @param string $module
  269. * The module for which hook_schema() was invoked.
  270. * @param bool $remove_descriptions
  271. * (optional) Whether to additionally remove 'description' keys of all tables
  272. * and fields to improve performance of serialize() and unserialize().
  273. * Defaults to TRUE.
  274. */
  275. function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) {
  276. // Set the name and module key for all tables.
  277. foreach ($schema as $name => &$table) {
  278. if (empty($table['module'])) {
  279. $table['module'] = $module;
  280. }
  281. if (!isset($table['name'])) {
  282. $table['name'] = $name;
  283. }
  284. if ($remove_descriptions) {
  285. unset($table['description']);
  286. foreach ($table['fields'] as &$field) {
  287. unset($field['description']);
  288. }
  289. }
  290. }
  291. }
  292. /**
  293. * Retrieves a list of fields from a table schema.
  294. *
  295. * The returned list is suitable for use in an SQL query.
  296. *
  297. * @param string $table
  298. * The name of the table from which to retrieve fields.
  299. * @param string $prefix
  300. * An optional prefix to to all fields.
  301. *
  302. * @return array
  303. * An array of fields.
  304. */
  305. function drupal_schema_fields_sql($table, $prefix = NULL) {
  306. if (!$schema = drupal_get_schema($table)) {
  307. return array();
  308. }
  309. $fields = array_keys($schema['fields']);
  310. if ($prefix) {
  311. $columns = array();
  312. foreach ($fields as $field) {
  313. $columns[] = "$prefix.$field";
  314. }
  315. return $columns;
  316. }
  317. else {
  318. return $fields;
  319. }
  320. }
  321. /**
  322. * Saves (inserts or updates) a record to the database based upon the schema.
  323. *
  324. * Do not use drupal_write_record() within hook_update_N() functions, since the
  325. * database schema cannot be relied upon when a user is running a series of
  326. * updates. Instead, use db_insert() or db_update() to save the record.
  327. *
  328. * @param string $table
  329. * The name of the table; this must be defined by a hook_schema()
  330. * implementation.
  331. * @param object|array $record
  332. * An object or array representing the record to write, passed in by
  333. * reference. If inserting a new record, values not provided in $record will
  334. * be populated in $record and in the database with the default values from
  335. * the schema, as well as a single serial (auto-increment) field
  336. * (if present). If updating an existing record, only provided values are
  337. * updated in the database, and $record is not modified.
  338. * @param array $primary_keys
  339. * To indicate that this is a new record to be inserted, omit this argument.
  340. * If this is an update, this argument specifies the primary keys' field
  341. * names. If there is only 1 field in the key, you may pass in a string; if
  342. * there are multiple fields in the key, pass in an array.
  343. *
  344. * @return bool|int
  345. * If the record insert or update failed, returns FALSE. If it succeeded,
  346. * returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
  347. */
  348. function drupal_write_record($table, &$record, $primary_keys = array()) {
  349. // Standardize $primary_keys to an array.
  350. if (is_string($primary_keys)) {
  351. $primary_keys = array($primary_keys);
  352. }
  353. $schema = drupal_get_schema($table);
  354. if (empty($schema)) {
  355. return FALSE;
  356. }
  357. $object = (object) $record;
  358. $fields = array();
  359. $default_fields = array();
  360. // Go through the schema to determine fields to write.
  361. foreach ($schema['fields'] as $field => $info) {
  362. if ($info['type'] == 'serial') {
  363. // Skip serial types if we are updating.
  364. if (!empty($primary_keys)) {
  365. continue;
  366. }
  367. // Track serial field so we can helpfully populate them after the query.
  368. // NOTE: Each table should come with one serial field only.
  369. $serial = $field;
  370. }
  371. // Skip field if it is in $primary_keys as it is unnecessary to update a
  372. // field to the value it is already set to.
  373. if (in_array($field, $primary_keys)) {
  374. continue;
  375. }
  376. // Skip fields that are not provided, default values are already known
  377. // by the database. property_exists() allows to explicitly set a value to
  378. // NULL.
  379. if (!property_exists($object, $field)) {
  380. $default_fields[] = $field;
  381. continue;
  382. }
  383. // However, if $object is an entity class instance, then class properties
  384. // always exist, as they cannot be unset. Therefore, if $field is a serial
  385. // type and the value is NULL, skip it.
  386. // @see http://php.net/manual/en/function.property-exists.php
  387. if ($info['type'] == 'serial' && !isset($object->$field)) {
  388. $default_fields[] = $field;
  389. continue;
  390. }
  391. // Build array of fields to update or insert.
  392. if (empty($info['serialize'])) {
  393. $fields[$field] = $object->$field;
  394. }
  395. else {
  396. $fields[$field] = serialize($object->$field);
  397. }
  398. // Type cast to proper datatype, except when the value is NULL and the
  399. // column allows this.
  400. if (isset($object->$field) || !empty($info['not null'])) {
  401. $fields[$field] = drupal_schema_get_field_value($info, $fields[$field]);
  402. }
  403. }
  404. // Build the SQL.
  405. if (empty($primary_keys)) {
  406. // We are doing an insert.
  407. $options = array('return' => Database::RETURN_INSERT_ID);
  408. if (isset($serial) && isset($fields[$serial])) {
  409. // If the serial column has been explicitly set with an ID, then we don't
  410. // require the database to return the last insert id.
  411. if ($fields[$serial]) {
  412. $options['return'] = Database::RETURN_AFFECTED;
  413. }
  414. // If a serial column does exist with no value (i.e. 0) then remove it as
  415. // the database will insert the correct value for us.
  416. else {
  417. unset($fields[$serial]);
  418. }
  419. }
  420. // Create an INSERT query. useDefaults() is necessary for the SQL to be
  421. // valid when $fields is empty.
  422. $query = db_insert($table, $options)
  423. ->fields($fields)
  424. ->useDefaults($default_fields);
  425. $return = SAVED_NEW;
  426. }
  427. else {
  428. // Create an UPDATE query.
  429. $query = db_update($table)->fields($fields);
  430. foreach ($primary_keys as $key) {
  431. $query->condition($key, $object->$key);
  432. }
  433. $return = SAVED_UPDATED;
  434. }
  435. // Execute the SQL.
  436. if ($query_return = $query->execute()) {
  437. if (isset($serial)) {
  438. // If the database was not told to return the last insert id, it will be
  439. // because we already know it.
  440. if (isset($options) && $options['return'] != Database::RETURN_INSERT_ID) {
  441. $object->$serial = $fields[$serial];
  442. }
  443. else {
  444. $object->$serial = $query_return;
  445. }
  446. }
  447. }
  448. // If we have a single-field primary key but got no insert ID, the
  449. // query failed. Note that we explicitly check for FALSE, because
  450. // a valid update query which doesn't change any values will return
  451. // zero (0) affected rows.
  452. elseif ($query_return === FALSE && count($primary_keys) == 1) {
  453. $return = FALSE;
  454. }
  455. // If we are inserting, populate empty fields with default values.
  456. if (empty($primary_keys)) {
  457. foreach ($schema['fields'] as $field => $info) {
  458. if (isset($info['default']) && !property_exists($object, $field)) {
  459. $object->$field = $info['default'];
  460. }
  461. }
  462. }
  463. // If we began with an array, convert back.
  464. if (is_array($record)) {
  465. $record = (array) $object;
  466. }
  467. return $return;
  468. }
  469. /**
  470. * Typecasts values to proper datatypes.
  471. *
  472. * MySQL PDO silently casts, e.g. FALSE and '' to 0, when inserting the value
  473. * into an integer column, but PostgreSQL PDO does not. Look up the schema
  474. * information and use that to correctly typecast the value.
  475. *
  476. * @param array $info
  477. * An array describing the schema field info.
  478. * @param mixed $value
  479. * The value to be converted.
  480. *
  481. * @return mixed
  482. * The converted value.
  483. */
  484. function drupal_schema_get_field_value(array $info, $value) {
  485. if ($info['type'] == 'int' || $info['type'] == 'serial') {
  486. $value = (int) $value;
  487. }
  488. elseif ($info['type'] == 'float') {
  489. $value = (float) $value;
  490. }
  491. else {
  492. $value = (string) $value;
  493. }
  494. return $value;
  495. }
  496. /**
  497. * @} End of "addtogroup schemaapi".
  498. */

Functions

Namesort descending Description
drupal_get_complete_schema Gets the whole database schema.
drupal_get_installed_schema_version Returns the currently installed schema version for a module.
drupal_get_schema Gets the schema definition of a table, or the whole database schema.
drupal_get_schema_unprocessed Returns the unprocessed and unaltered version of a module's schema.
drupal_get_schema_versions Returns an array of available schema versions for a module.
drupal_install_schema Creates all tables defined in a module's hook_schema().
drupal_schema_fields_sql Retrieves a list of fields from a table schema.
drupal_schema_get_field_value Typecasts values to proper datatypes.
drupal_set_installed_schema_version Updates the installed version information for a module.
drupal_uninstall_schema Removes all tables defined in a module's hook_schema().
drupal_write_record Saves (inserts or updates) a record to the database based upon the schema.
_drupal_schema_initialize Fills in required default values for table definitions from hook_schema().

Constants

Namesort descending Description
SCHEMA_INSTALLED Indicates that a module has been installed.
SCHEMA_UNINSTALLED Indicates that a module has not been installed yet.