Schema.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  1. <?php
  2. namespace Drupal\Core\Database;
  3. use Drupal\Core\Database\Query\PlaceholderInterface;
  4. /**
  5. * Provides a base implementation for Database Schema.
  6. */
  7. abstract class Schema implements PlaceholderInterface {
  8. /**
  9. * The database connection.
  10. *
  11. * @var \Drupal\Core\Database\Connection
  12. */
  13. protected $connection;
  14. /**
  15. * The placeholder counter.
  16. *
  17. * @var int
  18. */
  19. protected $placeholder = 0;
  20. /**
  21. * Definition of prefixInfo array structure.
  22. *
  23. * Rather than redefining DatabaseSchema::getPrefixInfo() for each driver,
  24. * by defining the defaultSchema variable only MySQL has to re-write the
  25. * method.
  26. *
  27. * @see DatabaseSchema::getPrefixInfo()
  28. *
  29. * @var string
  30. */
  31. protected $defaultSchema = 'public';
  32. /**
  33. * A unique identifier for this query object.
  34. *
  35. * @var string
  36. */
  37. protected $uniqueIdentifier;
  38. public function __construct($connection) {
  39. $this->uniqueIdentifier = uniqid('', TRUE);
  40. $this->connection = $connection;
  41. }
  42. /**
  43. * Implements the magic __clone function.
  44. */
  45. public function __clone() {
  46. $this->uniqueIdentifier = uniqid('', TRUE);
  47. }
  48. /**
  49. * {@inheritdoc}
  50. */
  51. public function uniqueIdentifier() {
  52. return $this->uniqueIdentifier;
  53. }
  54. /**
  55. * {@inheritdoc}
  56. */
  57. public function nextPlaceholder() {
  58. return $this->placeholder++;
  59. }
  60. /**
  61. * Get information about the table name and schema from the prefix.
  62. *
  63. * @param
  64. * Name of table to look prefix up for. Defaults to 'default' because that's
  65. * default key for prefix.
  66. * @param $add_prefix
  67. * Boolean that indicates whether the given table name should be prefixed.
  68. *
  69. * @return
  70. * A keyed array with information about the schema, table name and prefix.
  71. */
  72. protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
  73. $info = [
  74. 'schema' => $this->defaultSchema,
  75. 'prefix' => $this->connection->tablePrefix($table),
  76. ];
  77. if ($add_prefix) {
  78. $table = $info['prefix'] . $table;
  79. }
  80. // If the prefix contains a period in it, then that means the prefix also
  81. // contains a schema reference in which case we will change the schema key
  82. // to the value before the period in the prefix. Everything after the dot
  83. // will be prefixed onto the front of the table.
  84. if (($pos = strpos($table, '.')) !== FALSE) {
  85. // Grab everything before the period.
  86. $info['schema'] = substr($table, 0, $pos);
  87. // Grab everything after the dot.
  88. $info['table'] = substr($table, ++$pos);
  89. }
  90. else {
  91. $info['table'] = $table;
  92. }
  93. return $info;
  94. }
  95. /**
  96. * Create names for indexes, primary keys and constraints.
  97. *
  98. * This prevents using {} around non-table names like indexes and keys.
  99. */
  100. public function prefixNonTable($table) {
  101. $args = func_get_args();
  102. $info = $this->getPrefixInfo($table);
  103. $args[0] = $info['table'];
  104. return implode('_', $args);
  105. }
  106. /**
  107. * Build a condition to match a table name against a standard information_schema.
  108. *
  109. * The information_schema is a SQL standard that provides information about the
  110. * database server and the databases, schemas, tables, columns and users within
  111. * it. This makes information_schema a useful tool to use across the drupal
  112. * database drivers and is used by a few different functions. The function below
  113. * describes the conditions to be meet when querying information_schema.tables
  114. * for drupal tables or information associated with drupal tables. Even though
  115. * this is the standard method, not all databases follow standards and so this
  116. * method should be overwritten by a database driver if the database provider
  117. * uses alternate methods. Because information_schema.tables is used in a few
  118. * different functions, a database driver will only need to override this function
  119. * to make all the others work. For example see
  120. * core/includes/databases/mysql/schema.inc.
  121. *
  122. * @param $table_name
  123. * The name of the table in question.
  124. * @param $operator
  125. * The operator to apply on the 'table' part of the condition.
  126. * @param $add_prefix
  127. * Boolean to indicate whether the table name needs to be prefixed.
  128. *
  129. * @return \Drupal\Core\Database\Query\Condition
  130. * A Condition object.
  131. */
  132. protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
  133. $info = $this->connection->getConnectionOptions();
  134. // Retrieve the table name and schema
  135. $table_info = $this->getPrefixInfo($table_name, $add_prefix);
  136. $condition = $this->connection->condition('AND');
  137. $condition->condition('table_catalog', $info['database']);
  138. $condition->condition('table_schema', $table_info['schema']);
  139. $condition->condition('table_name', $table_info['table'], $operator);
  140. return $condition;
  141. }
  142. /**
  143. * Check if a table exists.
  144. *
  145. * @param $table
  146. * The name of the table in drupal (no prefixing).
  147. *
  148. * @return
  149. * TRUE if the given table exists, otherwise FALSE.
  150. */
  151. public function tableExists($table) {
  152. $condition = $this->buildTableNameCondition($table);
  153. $condition->compile($this->connection, $this);
  154. // Normally, we would heartily discourage the use of string
  155. // concatenation for conditionals like this however, we
  156. // couldn't use \Drupal::database()->select() here because it would prefix
  157. // information_schema.tables and the query would fail.
  158. // Don't use {} around information_schema.tables table.
  159. return (bool) $this->connection->query("SELECT 1 FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
  160. }
  161. /**
  162. * Finds all tables that are like the specified base table name.
  163. *
  164. * @param string $table_expression
  165. * An SQL expression, for example "cache_%" (without the quotes).
  166. *
  167. * @return array
  168. * Both the keys and the values are the matching tables.
  169. */
  170. public function findTables($table_expression) {
  171. // Load all the tables up front in order to take into account per-table
  172. // prefixes. The actual matching is done at the bottom of the method.
  173. $condition = $this->buildTableNameCondition('%', 'LIKE');
  174. $condition->compile($this->connection, $this);
  175. $individually_prefixed_tables = $this->connection->getUnprefixedTablesMap();
  176. $default_prefix = $this->connection->tablePrefix();
  177. $default_prefix_length = strlen($default_prefix);
  178. $tables = [];
  179. // Normally, we would heartily discourage the use of string
  180. // concatenation for conditionals like this however, we
  181. // couldn't use \Drupal::database()->select() here because it would prefix
  182. // information_schema.tables and the query would fail.
  183. // Don't use {} around information_schema.tables table.
  184. $results = $this->connection->query("SELECT table_name AS table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments());
  185. foreach ($results as $table) {
  186. // Take into account tables that have an individual prefix.
  187. if (isset($individually_prefixed_tables[$table->table_name])) {
  188. $prefix_length = strlen($this->connection->tablePrefix($individually_prefixed_tables[$table->table_name]));
  189. }
  190. elseif ($default_prefix && substr($table->table_name, 0, $default_prefix_length) !== $default_prefix) {
  191. // This table name does not start the default prefix, which means that
  192. // it is not managed by Drupal so it should be excluded from the result.
  193. continue;
  194. }
  195. else {
  196. $prefix_length = $default_prefix_length;
  197. }
  198. // Remove the prefix from the returned tables.
  199. $unprefixed_table_name = substr($table->table_name, $prefix_length);
  200. // The pattern can match a table which is the same as the prefix. That
  201. // will become an empty string when we remove the prefix, which will
  202. // probably surprise the caller, besides not being a prefixed table. So
  203. // remove it.
  204. if (!empty($unprefixed_table_name)) {
  205. $tables[$unprefixed_table_name] = $unprefixed_table_name;
  206. }
  207. }
  208. // Convert the table expression from its SQL LIKE syntax to a regular
  209. // expression and escape the delimiter that will be used for matching.
  210. $table_expression = str_replace(['%', '_'], ['.*?', '.'], preg_quote($table_expression, '/'));
  211. $tables = preg_grep('/^' . $table_expression . '$/i', $tables);
  212. return $tables;
  213. }
  214. /**
  215. * Check if a column exists in the given table.
  216. *
  217. * @param $table
  218. * The name of the table in drupal (no prefixing).
  219. * @param $column
  220. * The name of the column.
  221. *
  222. * @return
  223. * TRUE if the given column exists, otherwise FALSE.
  224. */
  225. public function fieldExists($table, $column) {
  226. $condition = $this->buildTableNameCondition($table);
  227. $condition->condition('column_name', $column);
  228. $condition->compile($this->connection, $this);
  229. // Normally, we would heartily discourage the use of string
  230. // concatenation for conditionals like this however, we
  231. // couldn't use \Drupal::database()->select() here because it would prefix
  232. // information_schema.tables and the query would fail.
  233. // Don't use {} around information_schema.columns table.
  234. return (bool) $this->connection->query("SELECT 1 FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
  235. }
  236. /**
  237. * Returns a mapping of Drupal schema field names to DB-native field types.
  238. *
  239. * Because different field types do not map 1:1 between databases, Drupal has
  240. * its own normalized field type names. This function returns a driver-specific
  241. * mapping table from Drupal names to the native names for each database.
  242. *
  243. * @return array
  244. * An array of Schema API field types to driver-specific field types.
  245. */
  246. abstract public function getFieldTypeMap();
  247. /**
  248. * Rename a table.
  249. *
  250. * @param $table
  251. * The table to be renamed.
  252. * @param $new_name
  253. * The new name for the table.
  254. *
  255. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  256. * If the specified table doesn't exist.
  257. * @throws \Drupal\Core\Database\SchemaObjectExistsException
  258. * If a table with the specified new name already exists.
  259. */
  260. abstract public function renameTable($table, $new_name);
  261. /**
  262. * Drop a table.
  263. *
  264. * @param $table
  265. * The table to be dropped.
  266. *
  267. * @return
  268. * TRUE if the table was successfully dropped, FALSE if there was no table
  269. * by that name to begin with.
  270. */
  271. abstract public function dropTable($table);
  272. /**
  273. * Add a new field to a table.
  274. *
  275. * @param $table
  276. * Name of the table to be altered.
  277. * @param $field
  278. * Name of the field to be added.
  279. * @param $spec
  280. * The field specification array, as taken from a schema definition.
  281. * The specification may also contain the key 'initial', the newly
  282. * created field will be set to the value of the key in all rows.
  283. * This is most useful for creating NOT NULL columns with no default
  284. * value in existing tables.
  285. * Alternatively, the 'initial_form_field' key may be used, which will
  286. * auto-populate the new field with values from the specified field.
  287. * @param $keys_new
  288. * (optional) Keys and indexes specification to be created on the
  289. * table along with adding the field. The format is the same as a
  290. * table specification but without the 'fields' element. If you are
  291. * adding a type 'serial' field, you MUST specify at least one key
  292. * or index including it in this array. See ::changeField() for more
  293. * explanation why.
  294. *
  295. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  296. * If the specified table doesn't exist.
  297. * @throws \Drupal\Core\Database\SchemaObjectExistsException
  298. * If the specified table already has a field by that name.
  299. */
  300. abstract public function addField($table, $field, $spec, $keys_new = []);
  301. /**
  302. * Drop a field.
  303. *
  304. * @param $table
  305. * The table to be altered.
  306. * @param $field
  307. * The field to be dropped.
  308. *
  309. * @return
  310. * TRUE if the field was successfully dropped, FALSE if there was no field
  311. * by that name to begin with.
  312. */
  313. abstract public function dropField($table, $field);
  314. /**
  315. * Set the default value for a field.
  316. *
  317. * @param $table
  318. * The table to be altered.
  319. * @param $field
  320. * The field to be altered.
  321. * @param $default
  322. * Default value to be set. NULL for 'default NULL'.
  323. *
  324. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  325. * If the specified table or field doesn't exist.
  326. *
  327. * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. Instead,
  328. * call ::changeField() passing a full field specification.
  329. *
  330. * @see ::changeField()
  331. */
  332. abstract public function fieldSetDefault($table, $field, $default);
  333. /**
  334. * Set a field to have no default value.
  335. *
  336. * @param $table
  337. * The table to be altered.
  338. * @param $field
  339. * The field to be altered.
  340. *
  341. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  342. * If the specified table or field doesn't exist.
  343. *
  344. * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0. Instead,
  345. * call ::changeField() passing a full field specification.
  346. *
  347. * @see ::changeField()
  348. */
  349. abstract public function fieldSetNoDefault($table, $field);
  350. /**
  351. * Checks if an index exists in the given table.
  352. *
  353. * @param $table
  354. * The name of the table in drupal (no prefixing).
  355. * @param $name
  356. * The name of the index in drupal (no prefixing).
  357. *
  358. * @return
  359. * TRUE if the given index exists, otherwise FALSE.
  360. */
  361. abstract public function indexExists($table, $name);
  362. /**
  363. * Add a primary key.
  364. *
  365. * @param $table
  366. * The table to be altered.
  367. * @param $fields
  368. * Fields for the primary key.
  369. *
  370. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  371. * If the specified table doesn't exist.
  372. * @throws \Drupal\Core\Database\SchemaObjectExistsException
  373. * If the specified table already has a primary key.
  374. */
  375. abstract public function addPrimaryKey($table, $fields);
  376. /**
  377. * Drop the primary key.
  378. *
  379. * @param $table
  380. * The table to be altered.
  381. *
  382. * @return
  383. * TRUE if the primary key was successfully dropped, FALSE if there was no
  384. * primary key on this table to begin with.
  385. */
  386. abstract public function dropPrimaryKey($table);
  387. /**
  388. * Finds the primary key columns of a table, from the database.
  389. *
  390. * @param string $table
  391. * The name of the table.
  392. *
  393. * @return string[]|false
  394. * A simple array with the names of the columns composing the table's
  395. * primary key, or FALSE if the table does not exist.
  396. *
  397. * @throws \RuntimeException
  398. * If the driver does not override this method.
  399. */
  400. protected function findPrimaryKeyColumns($table) {
  401. if (!$this->tableExists($table)) {
  402. return FALSE;
  403. }
  404. throw new \RuntimeException("The '" . $this->connection->driver() . "' database driver does not implement " . __METHOD__);
  405. }
  406. /**
  407. * Add a unique key.
  408. *
  409. * @param $table
  410. * The table to be altered.
  411. * @param $name
  412. * The name of the key.
  413. * @param $fields
  414. * An array of field names.
  415. *
  416. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  417. * If the specified table doesn't exist.
  418. * @throws \Drupal\Core\Database\SchemaObjectExistsException
  419. * If the specified table already has a key by that name.
  420. */
  421. abstract public function addUniqueKey($table, $name, $fields);
  422. /**
  423. * Drop a unique key.
  424. *
  425. * @param $table
  426. * The table to be altered.
  427. * @param $name
  428. * The name of the key.
  429. *
  430. * @return
  431. * TRUE if the key was successfully dropped, FALSE if there was no key by
  432. * that name to begin with.
  433. */
  434. abstract public function dropUniqueKey($table, $name);
  435. /**
  436. * Add an index.
  437. *
  438. * @param $table
  439. * The table to be altered.
  440. * @param $name
  441. * The name of the index.
  442. * @param $fields
  443. * An array of field names or field information; if field information is
  444. * passed, it's an array whose first element is the field name and whose
  445. * second is the maximum length in the index. For example, the following
  446. * will use the full length of the `foo` field, but limit the `bar` field to
  447. * 4 characters:
  448. * @code
  449. * $fields = ['foo', ['bar', 4]];
  450. * @endcode
  451. * @param array $spec
  452. * The table specification for the table to be altered. This is used in
  453. * order to be able to ensure that the index length is not too long.
  454. * This schema definition can usually be obtained through hook_schema(), or
  455. * in case the table was created by the Entity API, through the schema
  456. * handler listed in the entity class definition. For reference, see
  457. * SqlContentEntityStorageSchema::getDedicatedTableSchema() and
  458. * SqlContentEntityStorageSchema::getSharedTableFieldSchema().
  459. *
  460. * In order to prevent human error, it is recommended to pass in the
  461. * complete table specification. However, in the edge case of the complete
  462. * table specification not being available, we can pass in a partial table
  463. * definition containing only the fields that apply to the index:
  464. * @code
  465. * $spec = [
  466. * // Example partial specification for a table:
  467. * 'fields' => [
  468. * 'example_field' => [
  469. * 'description' => 'An example field',
  470. * 'type' => 'varchar',
  471. * 'length' => 32,
  472. * 'not null' => TRUE,
  473. * 'default' => '',
  474. * ],
  475. * ],
  476. * 'indexes' => [
  477. * 'table_example_field' => ['example_field'],
  478. * ],
  479. * ];
  480. * @endcode
  481. * Note that the above is a partial table definition and that we would
  482. * usually pass a complete table definition as obtained through
  483. * hook_schema() instead.
  484. *
  485. * @see schemaapi
  486. * @see hook_schema()
  487. *
  488. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  489. * If the specified table doesn't exist.
  490. * @throws \Drupal\Core\Database\SchemaObjectExistsException
  491. * If the specified table already has an index by that name.
  492. *
  493. * @todo remove the $spec argument whenever schema introspection is added.
  494. */
  495. abstract public function addIndex($table, $name, $fields, array $spec);
  496. /**
  497. * Drop an index.
  498. *
  499. * @param $table
  500. * The table to be altered.
  501. * @param $name
  502. * The name of the index.
  503. *
  504. * @return
  505. * TRUE if the index was successfully dropped, FALSE if there was no index
  506. * by that name to begin with.
  507. */
  508. abstract public function dropIndex($table, $name);
  509. /**
  510. * Finds the columns for the primary key, unique keys and indexes of a table.
  511. *
  512. * @param string $table
  513. * The name of the table.
  514. *
  515. * @return array
  516. * A schema array with the following keys: 'primary key', 'unique keys' and
  517. * 'indexes', and values as arrays of database columns.
  518. *
  519. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  520. * If the specified table doesn't exist.
  521. * @throws \RuntimeException
  522. * If the driver does not implement this method.
  523. */
  524. protected function introspectIndexSchema($table) {
  525. if (!$this->tableExists($table)) {
  526. throw new SchemaObjectDoesNotExistException("The table $table doesn't exist.");
  527. }
  528. throw new \RuntimeException("The '{$this->connection->driver()}' database driver does not implement " . __METHOD__);
  529. }
  530. /**
  531. * Change a field definition.
  532. *
  533. * IMPORTANT NOTE: To maintain database portability, you have to explicitly
  534. * recreate all indices and primary keys that are using the changed field.
  535. *
  536. * That means that you have to drop all affected keys and indexes with
  537. * Schema::dropPrimaryKey(), Schema::dropUniqueKey(), or Schema::dropIndex()
  538. * before calling ::changeField().
  539. * To recreate the keys and indices, pass the key definitions as the
  540. * optional $keys_new argument directly to ::changeField().
  541. *
  542. * For example, suppose you have:
  543. * @code
  544. * $schema['foo'] = array(
  545. * 'fields' => array(
  546. * 'bar' => array('type' => 'int', 'not null' => TRUE)
  547. * ),
  548. * 'primary key' => array('bar')
  549. * );
  550. * @endcode
  551. * and you want to change foo.bar to be type serial, leaving it as the
  552. * primary key. The correct sequence is:
  553. * @code
  554. * $injected_database->schema()->dropPrimaryKey('foo');
  555. * $injected_database->schema()->changeField('foo', 'bar', 'bar',
  556. * array('type' => 'serial', 'not null' => TRUE),
  557. * array('primary key' => array('bar')));
  558. * @endcode
  559. *
  560. * The reasons for this are due to the different database engines:
  561. *
  562. * On PostgreSQL, changing a field definition involves adding a new field
  563. * and dropping an old one which* causes any indices, primary keys and
  564. * sequences (from serial-type fields) that use the changed field to be dropped.
  565. *
  566. * On MySQL, all type 'serial' fields must be part of at least one key
  567. * or index as soon as they are created. You cannot use
  568. * Schema::addPrimaryKey, Schema::addUniqueKey(), or Schema::addIndex()
  569. * for this purpose because the ALTER TABLE command will fail to add
  570. * the column without a key or index specification.
  571. * The solution is to use the optional $keys_new argument to create the key
  572. * or index at the same time as field.
  573. *
  574. * You could use Schema::addPrimaryKey, Schema::addUniqueKey(), or
  575. * Schema::addIndex() in all cases unless you are converting a field to
  576. * be type serial. You can use the $keys_new argument in all cases.
  577. *
  578. * @param $table
  579. * Name of the table.
  580. * @param $field
  581. * Name of the field to change.
  582. * @param $field_new
  583. * New name for the field (set to the same as $field if you don't want to change the name).
  584. * @param $spec
  585. * The field specification for the new field.
  586. * @param $keys_new
  587. * (optional) Keys and indexes specification to be created on the
  588. * table along with changing the field. The format is the same as a
  589. * table specification but without the 'fields' element.
  590. *
  591. * @throws \Drupal\Core\Database\SchemaObjectDoesNotExistException
  592. * If the specified table or source field doesn't exist.
  593. * @throws \Drupal\Core\Database\SchemaObjectExistsException
  594. * If the specified destination field already exists.
  595. */
  596. abstract public function changeField($table, $field, $field_new, $spec, $keys_new = []);
  597. /**
  598. * Create a new table from a Drupal table definition.
  599. *
  600. * @param $name
  601. * The name of the table to create.
  602. * @param $table
  603. * A Schema API table definition array.
  604. *
  605. * @throws \Drupal\Core\Database\SchemaObjectExistsException
  606. * If the specified table already exists.
  607. */
  608. public function createTable($name, $table) {
  609. if ($this->tableExists($name)) {
  610. throw new SchemaObjectExistsException("Table '$name' already exists.");
  611. }
  612. $statements = $this->createTableSql($name, $table);
  613. foreach ($statements as $statement) {
  614. $this->connection->query($statement);
  615. }
  616. }
  617. /**
  618. * Return an array of field names from an array of key/index column specifiers.
  619. *
  620. * This is usually an identity function but if a key/index uses a column prefix
  621. * specification, this function extracts just the name.
  622. *
  623. * @param $fields
  624. * An array of key/index column specifiers.
  625. *
  626. * @return
  627. * An array of field names.
  628. */
  629. public function fieldNames($fields) {
  630. $return = [];
  631. foreach ($fields as $field) {
  632. if (is_array($field)) {
  633. $return[] = $field[0];
  634. }
  635. else {
  636. $return[] = $field;
  637. }
  638. }
  639. return $return;
  640. }
  641. /**
  642. * Prepare a table or column comment for database query.
  643. *
  644. * @param $comment
  645. * The comment string to prepare.
  646. * @param $length
  647. * Optional upper limit on the returned string length.
  648. *
  649. * @return
  650. * The prepared comment.
  651. */
  652. public function prepareComment($comment, $length = NULL) {
  653. // Remove semicolons to avoid triggering multi-statement check.
  654. $comment = strtr($comment, [';' => '.']);
  655. return $this->connection->quote($comment);
  656. }
  657. /**
  658. * Return an escaped version of its parameter to be used as a default value
  659. * on a column.
  660. *
  661. * @param mixed $value
  662. * The value to be escaped (int, float, null or string).
  663. *
  664. * @return string|int|float
  665. * The escaped value.
  666. */
  667. protected function escapeDefaultValue($value) {
  668. if (is_null($value)) {
  669. return 'NULL';
  670. }
  671. return is_string($value) ? $this->connection->quote($value) : $value;
  672. }
  673. /**
  674. * Ensures that all the primary key fields are correctly defined.
  675. *
  676. * @param array $primary_key
  677. * An array containing the fields that will form the primary key of a table.
  678. * @param array $fields
  679. * An array containing the field specifications of the table, as per the
  680. * schema data structure format.
  681. *
  682. * @throws \Drupal\Core\Database\SchemaException
  683. * Thrown if any primary key field specification does not exist or if they
  684. * do not define 'not null' as TRUE.
  685. */
  686. protected function ensureNotNullPrimaryKey(array $primary_key, array $fields) {
  687. foreach (array_intersect($primary_key, array_keys($fields)) as $field_name) {
  688. if (!isset($fields[$field_name]['not null']) || $fields[$field_name]['not null'] !== TRUE) {
  689. throw new SchemaException("The '$field_name' field specification does not define 'not null' as TRUE.");
  690. }
  691. }
  692. }
  693. }