comparison core/lib/Drupal/Core/Database/Schema.php @ 0:4c8ae668cc8c

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