Schema.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849
  1. <?php
  2. namespace Drupal\Core\Database\Driver\sqlite;
  3. use Drupal\Core\Database\SchemaObjectExistsException;
  4. use Drupal\Core\Database\SchemaObjectDoesNotExistException;
  5. use Drupal\Core\Database\Schema as DatabaseSchema;
  6. /**
  7. * @ingroup schemaapi
  8. * @{
  9. */
  10. /**
  11. * SQLite implementation of \Drupal\Core\Database\Schema.
  12. */
  13. class Schema extends DatabaseSchema {
  14. /**
  15. * Override DatabaseSchema::$defaultSchema
  16. *
  17. * @var string
  18. */
  19. protected $defaultSchema = 'main';
  20. /**
  21. * {@inheritdoc}
  22. */
  23. public function tableExists($table) {
  24. $info = $this->getPrefixInfo($table);
  25. // Don't use {} around sqlite_master table.
  26. return (bool) $this->connection->query('SELECT 1 FROM ' . $info['schema'] . '.sqlite_master WHERE type = :type AND name = :name', [':type' => 'table', ':name' => $info['table']])->fetchField();
  27. }
  28. /**
  29. * {@inheritdoc}
  30. */
  31. public function fieldExists($table, $column) {
  32. $schema = $this->introspectSchema($table);
  33. return !empty($schema['fields'][$column]);
  34. }
  35. /**
  36. * Generate SQL to create a new table from a Drupal schema definition.
  37. *
  38. * @param $name
  39. * The name of the table to create.
  40. * @param $table
  41. * A Schema API table definition array.
  42. *
  43. * @return
  44. * An array of SQL statements to create the table.
  45. */
  46. public function createTableSql($name, $table) {
  47. if (!empty($table['primary key']) && is_array($table['primary key'])) {
  48. $this->ensureNotNullPrimaryKey($table['primary key'], $table['fields']);
  49. }
  50. $sql = [];
  51. $sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumnsSql($name, $table) . "\n)\n";
  52. return array_merge($sql, $this->createIndexSql($name, $table));
  53. }
  54. /**
  55. * Build the SQL expression for indexes.
  56. */
  57. protected function createIndexSql($tablename, $schema) {
  58. $sql = [];
  59. $info = $this->getPrefixInfo($tablename);
  60. if (!empty($schema['unique keys'])) {
  61. foreach ($schema['unique keys'] as $key => $fields) {
  62. $sql[] = 'CREATE UNIQUE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . ")\n";
  63. }
  64. }
  65. if (!empty($schema['indexes'])) {
  66. foreach ($schema['indexes'] as $key => $fields) {
  67. $sql[] = 'CREATE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . ")\n";
  68. }
  69. }
  70. return $sql;
  71. }
  72. /**
  73. * Build the SQL expression for creating columns.
  74. */
  75. protected function createColumnsSql($tablename, $schema) {
  76. $sql_array = [];
  77. // Add the SQL statement for each field.
  78. foreach ($schema['fields'] as $name => $field) {
  79. if (isset($field['type']) && $field['type'] == 'serial') {
  80. if (isset($schema['primary key']) && ($key = array_search($name, $schema['primary key'])) !== FALSE) {
  81. unset($schema['primary key'][$key]);
  82. }
  83. }
  84. $sql_array[] = $this->createFieldSql($name, $this->processField($field));
  85. }
  86. // Process keys.
  87. if (!empty($schema['primary key'])) {
  88. $sql_array[] = " PRIMARY KEY (" . $this->createKeySql($schema['primary key']) . ")";
  89. }
  90. return implode(", \n", $sql_array);
  91. }
  92. /**
  93. * Build the SQL expression for keys.
  94. */
  95. protected function createKeySql($fields) {
  96. $return = [];
  97. foreach ($fields as $field) {
  98. if (is_array($field)) {
  99. $return[] = $field[0];
  100. }
  101. else {
  102. $return[] = $field;
  103. }
  104. }
  105. return implode(', ', $return);
  106. }
  107. /**
  108. * Set database-engine specific properties for a field.
  109. *
  110. * @param $field
  111. * A field description array, as specified in the schema documentation.
  112. */
  113. protected function processField($field) {
  114. if (!isset($field['size'])) {
  115. $field['size'] = 'normal';
  116. }
  117. // Set the correct database-engine specific datatype.
  118. // In case one is already provided, force it to uppercase.
  119. if (isset($field['sqlite_type'])) {
  120. $field['sqlite_type'] = mb_strtoupper($field['sqlite_type']);
  121. }
  122. else {
  123. $map = $this->getFieldTypeMap();
  124. $field['sqlite_type'] = $map[$field['type'] . ':' . $field['size']];
  125. // Numeric fields with a specified scale have to be stored as floats.
  126. if ($field['sqlite_type'] === 'NUMERIC' && isset($field['scale'])) {
  127. $field['sqlite_type'] = 'FLOAT';
  128. }
  129. }
  130. if (isset($field['type']) && $field['type'] == 'serial') {
  131. $field['auto_increment'] = TRUE;
  132. }
  133. return $field;
  134. }
  135. /**
  136. * Create an SQL string for a field to be used in table creation or alteration.
  137. *
  138. * Before passing a field out of a schema definition into this function it has
  139. * to be processed by self::processField().
  140. *
  141. * @param $name
  142. * Name of the field.
  143. * @param $spec
  144. * The field specification, as per the schema data structure format.
  145. */
  146. protected function createFieldSql($name, $spec) {
  147. if (!empty($spec['auto_increment'])) {
  148. $sql = $name . " INTEGER PRIMARY KEY AUTOINCREMENT";
  149. if (!empty($spec['unsigned'])) {
  150. $sql .= ' CHECK (' . $name . '>= 0)';
  151. }
  152. }
  153. else {
  154. $sql = $name . ' ' . $spec['sqlite_type'];
  155. if (in_array($spec['sqlite_type'], ['VARCHAR', 'TEXT'])) {
  156. if (isset($spec['length'])) {
  157. $sql .= '(' . $spec['length'] . ')';
  158. }
  159. if (isset($spec['binary']) && $spec['binary'] === FALSE) {
  160. $sql .= ' COLLATE NOCASE_UTF8';
  161. }
  162. }
  163. if (isset($spec['not null'])) {
  164. if ($spec['not null']) {
  165. $sql .= ' NOT NULL';
  166. }
  167. else {
  168. $sql .= ' NULL';
  169. }
  170. }
  171. if (!empty($spec['unsigned'])) {
  172. $sql .= ' CHECK (' . $name . '>= 0)';
  173. }
  174. if (isset($spec['default'])) {
  175. if (is_string($spec['default'])) {
  176. $spec['default'] = $this->connection->quote($spec['default']);
  177. }
  178. $sql .= ' DEFAULT ' . $spec['default'];
  179. }
  180. if (empty($spec['not null']) && !isset($spec['default'])) {
  181. $sql .= ' DEFAULT NULL';
  182. }
  183. }
  184. return $sql;
  185. }
  186. /**
  187. * {@inheritdoc}
  188. */
  189. public function getFieldTypeMap() {
  190. // Put :normal last so it gets preserved by array_flip. This makes
  191. // it much easier for modules (such as schema.module) to map
  192. // database types back into schema types.
  193. // $map does not use drupal_static as its value never changes.
  194. static $map = [
  195. 'varchar_ascii:normal' => 'VARCHAR',
  196. 'varchar:normal' => 'VARCHAR',
  197. 'char:normal' => 'CHAR',
  198. 'text:tiny' => 'TEXT',
  199. 'text:small' => 'TEXT',
  200. 'text:medium' => 'TEXT',
  201. 'text:big' => 'TEXT',
  202. 'text:normal' => 'TEXT',
  203. 'serial:tiny' => 'INTEGER',
  204. 'serial:small' => 'INTEGER',
  205. 'serial:medium' => 'INTEGER',
  206. 'serial:big' => 'INTEGER',
  207. 'serial:normal' => 'INTEGER',
  208. 'int:tiny' => 'INTEGER',
  209. 'int:small' => 'INTEGER',
  210. 'int:medium' => 'INTEGER',
  211. 'int:big' => 'INTEGER',
  212. 'int:normal' => 'INTEGER',
  213. 'float:tiny' => 'FLOAT',
  214. 'float:small' => 'FLOAT',
  215. 'float:medium' => 'FLOAT',
  216. 'float:big' => 'FLOAT',
  217. 'float:normal' => 'FLOAT',
  218. 'numeric:normal' => 'NUMERIC',
  219. 'blob:big' => 'BLOB',
  220. 'blob:normal' => 'BLOB',
  221. ];
  222. return $map;
  223. }
  224. /**
  225. * {@inheritdoc}
  226. */
  227. public function renameTable($table, $new_name) {
  228. if (!$this->tableExists($table)) {
  229. throw new SchemaObjectDoesNotExistException("Cannot rename '$table' to '$new_name': table '$table' doesn't exist.");
  230. }
  231. if ($this->tableExists($new_name)) {
  232. throw new SchemaObjectExistsException("Cannot rename '$table' to '$new_name': table '$new_name' already exists.");
  233. }
  234. $schema = $this->introspectSchema($table);
  235. // SQLite doesn't allow you to rename tables outside of the current
  236. // database. So the syntax '... RENAME TO database.table' would fail.
  237. // So we must determine the full table name here rather than surrounding
  238. // the table with curly braces in case the db_prefix contains a reference
  239. // to a database outside of our existing database.
  240. $info = $this->getPrefixInfo($new_name);
  241. $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']);
  242. // Drop the indexes, there is no RENAME INDEX command in SQLite.
  243. if (!empty($schema['unique keys'])) {
  244. foreach ($schema['unique keys'] as $key => $fields) {
  245. $this->dropIndex($table, $key);
  246. }
  247. }
  248. if (!empty($schema['indexes'])) {
  249. foreach ($schema['indexes'] as $index => $fields) {
  250. $this->dropIndex($table, $index);
  251. }
  252. }
  253. // Recreate the indexes.
  254. $statements = $this->createIndexSql($new_name, $schema);
  255. foreach ($statements as $statement) {
  256. $this->connection->query($statement);
  257. }
  258. }
  259. /**
  260. * {@inheritdoc}
  261. */
  262. public function dropTable($table) {
  263. if (!$this->tableExists($table)) {
  264. return FALSE;
  265. }
  266. $this->connection->tableDropped = TRUE;
  267. $this->connection->query('DROP TABLE {' . $table . '}');
  268. return TRUE;
  269. }
  270. /**
  271. * {@inheritdoc}
  272. */
  273. public function addField($table, $field, $specification, $keys_new = []) {
  274. if (!$this->tableExists($table)) {
  275. throw new SchemaObjectDoesNotExistException("Cannot add field '$table.$field': table doesn't exist.");
  276. }
  277. if ($this->fieldExists($table, $field)) {
  278. throw new SchemaObjectExistsException("Cannot add field '$table.$field': field already exists.");
  279. }
  280. if (isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE)) {
  281. $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field => $specification]);
  282. }
  283. // SQLite doesn't have a full-featured ALTER TABLE statement. It only
  284. // supports adding new fields to a table, in some simple cases. In most
  285. // cases, we have to create a new table and copy the data over.
  286. if (empty($keys_new) && (empty($specification['not null']) || isset($specification['default']))) {
  287. // When we don't have to create new keys and we are not creating a
  288. // NOT NULL column without a default value, we can use the quicker version.
  289. $query = 'ALTER TABLE {' . $table . '} ADD ' . $this->createFieldSql($field, $this->processField($specification));
  290. $this->connection->query($query);
  291. // Apply the initial value if set.
  292. if (isset($specification['initial_from_field'])) {
  293. if (isset($specification['initial'])) {
  294. $expression = 'COALESCE(' . $specification['initial_from_field'] . ', :default_initial_value)';
  295. $arguments = [':default_initial_value' => $specification['initial']];
  296. }
  297. else {
  298. $expression = $specification['initial_from_field'];
  299. $arguments = [];
  300. }
  301. $this->connection->update($table)
  302. ->expression($field, $expression, $arguments)
  303. ->execute();
  304. }
  305. elseif (isset($specification['initial'])) {
  306. $this->connection->update($table)
  307. ->fields([$field => $specification['initial']])
  308. ->execute();
  309. }
  310. }
  311. else {
  312. // We cannot add the field directly. Use the slower table alteration
  313. // method, starting from the old schema.
  314. $old_schema = $this->introspectSchema($table);
  315. $new_schema = $old_schema;
  316. // Add the new field.
  317. $new_schema['fields'][$field] = $specification;
  318. // Build the mapping between the old fields and the new fields.
  319. $mapping = [];
  320. if (isset($specification['initial_from_field'])) {
  321. // If we have a initial value, copy it over.
  322. if (isset($specification['initial'])) {
  323. $expression = 'COALESCE(' . $specification['initial_from_field'] . ', :default_initial_value)';
  324. $arguments = [':default_initial_value' => $specification['initial']];
  325. }
  326. else {
  327. $expression = $specification['initial_from_field'];
  328. $arguments = [];
  329. }
  330. $mapping[$field] = [
  331. 'expression' => $expression,
  332. 'arguments' => $arguments,
  333. ];
  334. }
  335. elseif (isset($specification['initial'])) {
  336. // If we have a initial value, copy it over.
  337. $mapping[$field] = [
  338. 'expression' => ':newfieldinitial',
  339. 'arguments' => [':newfieldinitial' => $specification['initial']],
  340. ];
  341. }
  342. else {
  343. // Else use the default of the field.
  344. $mapping[$field] = NULL;
  345. }
  346. // Add the new indexes.
  347. $new_schema = array_merge($new_schema, $keys_new);
  348. $this->alterTable($table, $old_schema, $new_schema, $mapping);
  349. }
  350. }
  351. /**
  352. * Create a table with a new schema containing the old content.
  353. *
  354. * As SQLite does not support ALTER TABLE (with a few exceptions) it is
  355. * necessary to create a new table and copy over the old content.
  356. *
  357. * @param $table
  358. * Name of the table to be altered.
  359. * @param $old_schema
  360. * The old schema array for the table.
  361. * @param $new_schema
  362. * The new schema array for the table.
  363. * @param $mapping
  364. * An optional mapping between the fields of the old specification and the
  365. * fields of the new specification. An associative array, whose keys are
  366. * the fields of the new table, and values can take two possible forms:
  367. * - a simple string, which is interpreted as the name of a field of the
  368. * old table,
  369. * - an associative array with two keys 'expression' and 'arguments',
  370. * that will be used as an expression field.
  371. */
  372. protected function alterTable($table, $old_schema, $new_schema, array $mapping = []) {
  373. $i = 0;
  374. do {
  375. $new_table = $table . '_' . $i++;
  376. } while ($this->tableExists($new_table));
  377. $this->createTable($new_table, $new_schema);
  378. // Build a SQL query to migrate the data from the old table to the new.
  379. $select = $this->connection->select($table);
  380. // Complete the mapping.
  381. $possible_keys = array_keys($new_schema['fields']);
  382. $mapping += array_combine($possible_keys, $possible_keys);
  383. // Now add the fields.
  384. foreach ($mapping as $field_alias => $field_source) {
  385. // Just ignore this field (ie. use its default value).
  386. if (!isset($field_source)) {
  387. continue;
  388. }
  389. if (is_array($field_source)) {
  390. $select->addExpression($field_source['expression'], $field_alias, $field_source['arguments']);
  391. }
  392. else {
  393. $select->addField($table, $field_source, $field_alias);
  394. }
  395. }
  396. // Execute the data migration query.
  397. $this->connection->insert($new_table)
  398. ->from($select)
  399. ->execute();
  400. $old_count = $this->connection->query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField();
  401. $new_count = $this->connection->query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField();
  402. if ($old_count == $new_count) {
  403. $this->dropTable($table);
  404. $this->renameTable($new_table, $table);
  405. }
  406. }
  407. /**
  408. * Find out the schema of a table.
  409. *
  410. * This function uses introspection methods provided by the database to
  411. * create a schema array. This is useful, for example, during update when
  412. * the old schema is not available.
  413. *
  414. * @param $table
  415. * Name of the table.
  416. *
  417. * @return
  418. * An array representing the schema.
  419. *
  420. * @throws \Exception
  421. * If a column of the table could not be parsed.
  422. */
  423. protected function introspectSchema($table) {
  424. $mapped_fields = array_flip($this->getFieldTypeMap());
  425. $schema = [
  426. 'fields' => [],
  427. 'primary key' => [],
  428. 'unique keys' => [],
  429. 'indexes' => [],
  430. ];
  431. $info = $this->getPrefixInfo($table);
  432. $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.table_info(' . $info['table'] . ')');
  433. foreach ($result as $row) {
  434. if (preg_match('/^([^(]+)\((.*)\)$/', $row->type, $matches)) {
  435. $type = $matches[1];
  436. $length = $matches[2];
  437. }
  438. else {
  439. $type = $row->type;
  440. $length = NULL;
  441. }
  442. if (isset($mapped_fields[$type])) {
  443. list($type, $size) = explode(':', $mapped_fields[$type]);
  444. $schema['fields'][$row->name] = [
  445. 'type' => $type,
  446. 'size' => $size,
  447. 'not null' => !empty($row->notnull) || $row->pk !== "0",
  448. 'default' => trim($row->dflt_value, "'"),
  449. ];
  450. if ($length) {
  451. $schema['fields'][$row->name]['length'] = $length;
  452. }
  453. // $row->pk contains a number that reflects the primary key order. We
  454. // use that as the key and sort (by key) below to return the primary key
  455. // in the same order that it is stored in.
  456. if ($row->pk) {
  457. $schema['primary key'][$row->pk] = $row->name;
  458. }
  459. }
  460. else {
  461. throw new \Exception("Unable to parse the column type " . $row->type);
  462. }
  463. }
  464. ksort($schema['primary key']);
  465. // Re-key the array because $row->pk starts counting at 1.
  466. $schema['primary key'] = array_values($schema['primary key']);
  467. $indexes = [];
  468. $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_list(' . $info['table'] . ')');
  469. foreach ($result as $row) {
  470. if (strpos($row->name, 'sqlite_autoindex_') !== 0) {
  471. $indexes[] = [
  472. 'schema_key' => $row->unique ? 'unique keys' : 'indexes',
  473. 'name' => $row->name,
  474. ];
  475. }
  476. }
  477. foreach ($indexes as $index) {
  478. $name = $index['name'];
  479. // Get index name without prefix.
  480. $index_name = substr($name, strlen($info['table']) + 1);
  481. $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $name . ')');
  482. foreach ($result as $row) {
  483. $schema[$index['schema_key']][$index_name][] = $row->name;
  484. }
  485. }
  486. return $schema;
  487. }
  488. /**
  489. * {@inheritdoc}
  490. */
  491. public function dropField($table, $field) {
  492. if (!$this->fieldExists($table, $field)) {
  493. return FALSE;
  494. }
  495. $old_schema = $this->introspectSchema($table);
  496. $new_schema = $old_schema;
  497. unset($new_schema['fields'][$field]);
  498. // Drop the primary key if the field to drop is part of it. This is
  499. // consistent with the behavior on PostgreSQL.
  500. // @see \Drupal\Core\Database\Driver\mysql\Schema::dropField()
  501. if (isset($new_schema['primary key']) && in_array($field, $new_schema['primary key'], TRUE)) {
  502. unset($new_schema['primary key']);
  503. }
  504. // Handle possible index changes.
  505. foreach ($new_schema['indexes'] as $index => $fields) {
  506. foreach ($fields as $key => $field_name) {
  507. if ($field_name == $field) {
  508. unset($new_schema['indexes'][$index][$key]);
  509. }
  510. }
  511. // If this index has no more fields then remove it.
  512. if (empty($new_schema['indexes'][$index])) {
  513. unset($new_schema['indexes'][$index]);
  514. }
  515. }
  516. $this->alterTable($table, $old_schema, $new_schema);
  517. return TRUE;
  518. }
  519. /**
  520. * {@inheritdoc}
  521. */
  522. public function changeField($table, $field, $field_new, $spec, $keys_new = []) {
  523. if (!$this->fieldExists($table, $field)) {
  524. throw new SchemaObjectDoesNotExistException("Cannot change the definition of field '$table.$field': field doesn't exist.");
  525. }
  526. if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
  527. throw new SchemaObjectExistsException("Cannot rename field '$table.$field' to '$field_new': target field already exists.");
  528. }
  529. if (isset($keys_new['primary key']) && in_array($field_new, $keys_new['primary key'], TRUE)) {
  530. $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field_new => $spec]);
  531. }
  532. $old_schema = $this->introspectSchema($table);
  533. $new_schema = $old_schema;
  534. // Map the old field to the new field.
  535. if ($field != $field_new) {
  536. $mapping[$field_new] = $field;
  537. }
  538. else {
  539. $mapping = [];
  540. }
  541. // Remove the previous definition and swap in the new one.
  542. unset($new_schema['fields'][$field]);
  543. $new_schema['fields'][$field_new] = $spec;
  544. // Map the former indexes to the new column name.
  545. $new_schema['primary key'] = $this->mapKeyDefinition($new_schema['primary key'], $mapping);
  546. foreach (['unique keys', 'indexes'] as $k) {
  547. foreach ($new_schema[$k] as &$key_definition) {
  548. $key_definition = $this->mapKeyDefinition($key_definition, $mapping);
  549. }
  550. }
  551. // Add in the keys from $keys_new.
  552. if (isset($keys_new['primary key'])) {
  553. $new_schema['primary key'] = $keys_new['primary key'];
  554. }
  555. foreach (['unique keys', 'indexes'] as $k) {
  556. if (!empty($keys_new[$k])) {
  557. $new_schema[$k] = $keys_new[$k] + $new_schema[$k];
  558. }
  559. }
  560. $this->alterTable($table, $old_schema, $new_schema, $mapping);
  561. }
  562. /**
  563. * Utility method: rename columns in an index definition according to a new mapping.
  564. *
  565. * @param $key_definition
  566. * The key definition.
  567. * @param $mapping
  568. * The new mapping.
  569. */
  570. protected function mapKeyDefinition(array $key_definition, array $mapping) {
  571. foreach ($key_definition as &$field) {
  572. // The key definition can be an array($field, $length).
  573. if (is_array($field)) {
  574. $field = &$field[0];
  575. }
  576. $mapped_field = array_search($field, $mapping, TRUE);
  577. if ($mapped_field !== FALSE) {
  578. $field = $mapped_field;
  579. }
  580. }
  581. return $key_definition;
  582. }
  583. /**
  584. * {@inheritdoc}
  585. */
  586. public function addIndex($table, $name, $fields, array $spec) {
  587. if (!$this->tableExists($table)) {
  588. throw new SchemaObjectDoesNotExistException("Cannot add index '$name' to table '$table': table doesn't exist.");
  589. }
  590. if ($this->indexExists($table, $name)) {
  591. throw new SchemaObjectExistsException("Cannot add index '$name' to table '$table': index already exists.");
  592. }
  593. $schema['indexes'][$name] = $fields;
  594. $statements = $this->createIndexSql($table, $schema);
  595. foreach ($statements as $statement) {
  596. $this->connection->query($statement);
  597. }
  598. }
  599. /**
  600. * {@inheritdoc}
  601. */
  602. public function indexExists($table, $name) {
  603. $info = $this->getPrefixInfo($table);
  604. return $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $info['table'] . '_' . $name . ')')->fetchField() != '';
  605. }
  606. /**
  607. * {@inheritdoc}
  608. */
  609. public function dropIndex($table, $name) {
  610. if (!$this->indexExists($table, $name)) {
  611. return FALSE;
  612. }
  613. $info = $this->getPrefixInfo($table);
  614. $this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
  615. return TRUE;
  616. }
  617. /**
  618. * {@inheritdoc}
  619. */
  620. public function addUniqueKey($table, $name, $fields) {
  621. if (!$this->tableExists($table)) {
  622. throw new SchemaObjectDoesNotExistException("Cannot add unique key '$name' to table '$table': table doesn't exist.");
  623. }
  624. if ($this->indexExists($table, $name)) {
  625. throw new SchemaObjectExistsException("Cannot add unique key '$name' to table '$table': unique key already exists.");
  626. }
  627. $schema['unique keys'][$name] = $fields;
  628. $statements = $this->createIndexSql($table, $schema);
  629. foreach ($statements as $statement) {
  630. $this->connection->query($statement);
  631. }
  632. }
  633. /**
  634. * {@inheritdoc}
  635. */
  636. public function dropUniqueKey($table, $name) {
  637. if (!$this->indexExists($table, $name)) {
  638. return FALSE;
  639. }
  640. $info = $this->getPrefixInfo($table);
  641. $this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
  642. return TRUE;
  643. }
  644. /**
  645. * {@inheritdoc}
  646. */
  647. public function addPrimaryKey($table, $fields) {
  648. if (!$this->tableExists($table)) {
  649. throw new SchemaObjectDoesNotExistException("Cannot add primary key to table '$table': table doesn't exist.");
  650. }
  651. $old_schema = $this->introspectSchema($table);
  652. $new_schema = $old_schema;
  653. if (!empty($new_schema['primary key'])) {
  654. throw new SchemaObjectExistsException("Cannot add primary key to table '$table': primary key already exists.");
  655. }
  656. $new_schema['primary key'] = $fields;
  657. $this->ensureNotNullPrimaryKey($new_schema['primary key'], $new_schema['fields']);
  658. $this->alterTable($table, $old_schema, $new_schema);
  659. }
  660. /**
  661. * {@inheritdoc}
  662. */
  663. public function dropPrimaryKey($table) {
  664. $old_schema = $this->introspectSchema($table);
  665. $new_schema = $old_schema;
  666. if (empty($new_schema['primary key'])) {
  667. return FALSE;
  668. }
  669. unset($new_schema['primary key']);
  670. $this->alterTable($table, $old_schema, $new_schema);
  671. return TRUE;
  672. }
  673. /**
  674. * {@inheritdoc}
  675. */
  676. protected function findPrimaryKeyColumns($table) {
  677. if (!$this->tableExists($table)) {
  678. return FALSE;
  679. }
  680. $schema = $this->introspectSchema($table);
  681. return $schema['primary key'];
  682. }
  683. /**
  684. * {@inheritdoc}
  685. */
  686. protected function introspectIndexSchema($table) {
  687. if (!$this->tableExists($table)) {
  688. throw new SchemaObjectDoesNotExistException("The table $table doesn't exist.");
  689. }
  690. $schema = $this->introspectSchema($table);
  691. unset($schema['fields']);
  692. return $schema;
  693. }
  694. /**
  695. * {@inheritdoc}
  696. */
  697. public function fieldSetDefault($table, $field, $default) {
  698. @trigger_error('fieldSetDefault() is deprecated in drupal:8.7.0 and will be removed before drupal:9.0.0. Instead, call ::changeField() passing a full field specification. See https://www.drupal.org/node/2999035', E_USER_DEPRECATED);
  699. if (!$this->fieldExists($table, $field)) {
  700. throw new SchemaObjectDoesNotExistException("Cannot set default value of field '$table.$field': field doesn't exist.");
  701. }
  702. $old_schema = $this->introspectSchema($table);
  703. $new_schema = $old_schema;
  704. $new_schema['fields'][$field]['default'] = $default;
  705. $this->alterTable($table, $old_schema, $new_schema);
  706. }
  707. /**
  708. * {@inheritdoc}
  709. */
  710. public function fieldSetNoDefault($table, $field) {
  711. @trigger_error('fieldSetNoDefault() is deprecated in drupal:8.7.0 and will be removed before drupal:9.0.0. Instead, call ::changeField() passing a full field specification. See https://www.drupal.org/node/2999035', E_USER_DEPRECATED);
  712. if (!$this->fieldExists($table, $field)) {
  713. throw new SchemaObjectDoesNotExistException("Cannot remove default value of field '$table.$field': field doesn't exist.");
  714. }
  715. $old_schema = $this->introspectSchema($table);
  716. $new_schema = $old_schema;
  717. unset($new_schema['fields'][$field]['default']);
  718. $this->alterTable($table, $old_schema, $new_schema);
  719. }
  720. /**
  721. * {@inheritdoc}
  722. */
  723. public function findTables($table_expression) {
  724. $tables = [];
  725. // The SQLite implementation doesn't need to use the same filtering strategy
  726. // as the parent one because individually prefixed tables live in their own
  727. // schema (database), which means that neither the main database nor any
  728. // attached one will contain a prefixed table name, so we just need to loop
  729. // over all known schemas and filter by the user-supplied table expression.
  730. $attached_dbs = $this->connection->getAttachedDatabases();
  731. foreach ($attached_dbs as $schema) {
  732. // Can't use query placeholders for the schema because the query would
  733. // have to be :prefixsqlite_master, which does not work. We also need to
  734. // ignore the internal SQLite tables.
  735. $result = $this->connection->query("SELECT name FROM " . $schema . ".sqlite_master WHERE type = :type AND name LIKE :table_name AND name NOT LIKE :pattern", [
  736. ':type' => 'table',
  737. ':table_name' => $table_expression,
  738. ':pattern' => 'sqlite_%',
  739. ]);
  740. $tables += $result->fetchAllKeyed(0, 0);
  741. }
  742. return $tables;
  743. }
  744. }