Upgraded drupal core with security updates
[yaffs-website] / web / core / lib / Drupal / Core / Database / Schema.php
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 }