Pull merge.
[yaffs-website] / web / core / lib / Drupal / Core / Database / Driver / mysql / Schema.php
1 <?php
2
3 namespace Drupal\Core\Database\Driver\mysql;
4
5 use Drupal\Core\Database\Query\Condition;
6 use Drupal\Core\Database\SchemaException;
7 use Drupal\Core\Database\SchemaObjectExistsException;
8 use Drupal\Core\Database\SchemaObjectDoesNotExistException;
9 use Drupal\Core\Database\Schema as DatabaseSchema;
10 use Drupal\Component\Utility\Unicode;
11
12 /**
13  * @addtogroup schemaapi
14  * @{
15  */
16
17 /**
18  * MySQL implementation of \Drupal\Core\Database\Schema.
19  */
20 class Schema extends DatabaseSchema {
21
22   /**
23    * Maximum length of a table comment in MySQL.
24    */
25   const COMMENT_MAX_TABLE = 60;
26
27   /**
28    * Maximum length of a column comment in MySQL.
29    */
30   const COMMENT_MAX_COLUMN = 255;
31
32   /**
33    * @var array
34    *   List of MySQL string types.
35    */
36   protected $mysqlStringTypes = [
37     'VARCHAR',
38     'CHAR',
39     'TINYTEXT',
40     'MEDIUMTEXT',
41     'LONGTEXT',
42     'TEXT',
43   ];
44
45   /**
46    * Get information about the table and database name from the prefix.
47    *
48    * @return
49    *   A keyed array with information about the database, table name and prefix.
50    */
51   protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
52     $info = ['prefix' => $this->connection->tablePrefix($table)];
53     if ($add_prefix) {
54       $table = $info['prefix'] . $table;
55     }
56     if (($pos = strpos($table, '.')) !== FALSE) {
57       $info['database'] = substr($table, 0, $pos);
58       $info['table'] = substr($table, ++$pos);
59     }
60     else {
61       $info['database'] = $this->connection->getConnectionOptions()['database'];
62       $info['table'] = $table;
63     }
64     return $info;
65   }
66
67   /**
68    * Build a condition to match a table name against a standard information_schema.
69    *
70    * MySQL uses databases like schemas rather than catalogs so when we build
71    * a condition to query the information_schema.tables, we set the default
72    * database as the schema unless specified otherwise, and exclude table_catalog
73    * from the condition criteria.
74    */
75   protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
76     $table_info = $this->getPrefixInfo($table_name, $add_prefix);
77
78     $condition = new Condition('AND');
79     $condition->condition('table_schema', $table_info['database']);
80     $condition->condition('table_name', $table_info['table'], $operator);
81     return $condition;
82   }
83
84   /**
85    * Generate SQL to create a new table from a Drupal schema definition.
86    *
87    * @param $name
88    *   The name of the table to create.
89    * @param $table
90    *   A Schema API table definition array.
91    * @return
92    *   An array of SQL statements to create the table.
93    */
94   protected function createTableSql($name, $table) {
95     $info = $this->connection->getConnectionOptions();
96
97     // Provide defaults if needed.
98     $table += [
99       'mysql_engine' => 'InnoDB',
100       'mysql_character_set' => 'utf8mb4',
101     ];
102
103     $sql = "CREATE TABLE {" . $name . "} (\n";
104
105     // Add the SQL statement for each field.
106     foreach ($table['fields'] as $field_name => $field) {
107       $sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n";
108     }
109
110     // Process keys & indexes.
111     if (!empty($table['primary key']) && is_array($table['primary key'])) {
112       $this->ensureNotNullPrimaryKey($table['primary key'], $table['fields']);
113     }
114     $keys = $this->createKeysSql($table);
115     if (count($keys)) {
116       $sql .= implode(", \n", $keys) . ", \n";
117     }
118
119     // Remove the last comma and space.
120     $sql = substr($sql, 0, -3) . "\n) ";
121
122     $sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
123     // By default, MySQL uses the default collation for new tables, which is
124     // 'utf8mb4_general_ci' (MySQL 5) or 'utf8mb4_0900_ai_ci' (MySQL 8) for
125     // utf8mb4. If an alternate collation has been set, it needs to be
126     // explicitly specified.
127     // @see \Drupal\Core\Database\Driver\mysql\Schema
128     if (!empty($info['collation'])) {
129       $sql .= ' COLLATE ' . $info['collation'];
130     }
131
132     // Add table comment.
133     if (!empty($table['description'])) {
134       $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
135     }
136
137     return [$sql];
138   }
139
140   /**
141    * Create an SQL string for a field to be used in table creation or alteration.
142    *
143    * Before passing a field out of a schema definition into this function it has
144    * to be processed by _db_process_field().
145    *
146    * @param string $name
147    *   Name of the field.
148    * @param array $spec
149    *   The field specification, as per the schema data structure format.
150    */
151   protected function createFieldSql($name, $spec) {
152     $sql = "`" . $name . "` " . $spec['mysql_type'];
153
154     if (in_array($spec['mysql_type'], $this->mysqlStringTypes)) {
155       if (isset($spec['length'])) {
156         $sql .= '(' . $spec['length'] . ')';
157       }
158       if (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
159         $sql .= ' CHARACTER SET ascii';
160       }
161       if (!empty($spec['binary'])) {
162         $sql .= ' BINARY';
163       }
164       // Note we check for the "type" key here. "mysql_type" is VARCHAR:
165       elseif (isset($spec['type']) && $spec['type'] == 'varchar_ascii') {
166         $sql .= ' COLLATE ascii_general_ci';
167       }
168     }
169     elseif (isset($spec['precision']) && isset($spec['scale'])) {
170       $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
171     }
172
173     if (!empty($spec['unsigned'])) {
174       $sql .= ' unsigned';
175     }
176
177     if (isset($spec['not null'])) {
178       if ($spec['not null']) {
179         $sql .= ' NOT NULL';
180       }
181       else {
182         $sql .= ' NULL';
183       }
184     }
185
186     if (!empty($spec['auto_increment'])) {
187       $sql .= ' auto_increment';
188     }
189
190     // $spec['default'] can be NULL, so we explicitly check for the key here.
191     if (array_key_exists('default', $spec)) {
192       $sql .= ' DEFAULT ' . $this->escapeDefaultValue($spec['default']);
193     }
194
195     if (empty($spec['not null']) && !isset($spec['default'])) {
196       $sql .= ' DEFAULT NULL';
197     }
198
199     // Add column comment.
200     if (!empty($spec['description'])) {
201       $sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN);
202     }
203
204     return $sql;
205   }
206
207   /**
208    * Set database-engine specific properties for a field.
209    *
210    * @param $field
211    *   A field description array, as specified in the schema documentation.
212    */
213   protected function processField($field) {
214
215     if (!isset($field['size'])) {
216       $field['size'] = 'normal';
217     }
218
219     // Set the correct database-engine specific datatype.
220     // In case one is already provided, force it to uppercase.
221     if (isset($field['mysql_type'])) {
222       $field['mysql_type'] = mb_strtoupper($field['mysql_type']);
223     }
224     else {
225       $map = $this->getFieldTypeMap();
226       $field['mysql_type'] = $map[$field['type'] . ':' . $field['size']];
227     }
228
229     if (isset($field['type']) && $field['type'] == 'serial') {
230       $field['auto_increment'] = TRUE;
231     }
232
233     return $field;
234   }
235
236   /**
237    * {@inheritdoc}
238    */
239   public function getFieldTypeMap() {
240     // Put :normal last so it gets preserved by array_flip. This makes
241     // it much easier for modules (such as schema.module) to map
242     // database types back into schema types.
243     // $map does not use drupal_static as its value never changes.
244     static $map = [
245       'varchar_ascii:normal' => 'VARCHAR',
246
247       'varchar:normal'  => 'VARCHAR',
248       'char:normal'     => 'CHAR',
249
250       'text:tiny'       => 'TINYTEXT',
251       'text:small'      => 'TINYTEXT',
252       'text:medium'     => 'MEDIUMTEXT',
253       'text:big'        => 'LONGTEXT',
254       'text:normal'     => 'TEXT',
255
256       'serial:tiny'     => 'TINYINT',
257       'serial:small'    => 'SMALLINT',
258       'serial:medium'   => 'MEDIUMINT',
259       'serial:big'      => 'BIGINT',
260       'serial:normal'   => 'INT',
261
262       'int:tiny'        => 'TINYINT',
263       'int:small'       => 'SMALLINT',
264       'int:medium'      => 'MEDIUMINT',
265       'int:big'         => 'BIGINT',
266       'int:normal'      => 'INT',
267
268       'float:tiny'      => 'FLOAT',
269       'float:small'     => 'FLOAT',
270       'float:medium'    => 'FLOAT',
271       'float:big'       => 'DOUBLE',
272       'float:normal'    => 'FLOAT',
273
274       'numeric:normal'  => 'DECIMAL',
275
276       'blob:big'        => 'LONGBLOB',
277       'blob:normal'     => 'BLOB',
278     ];
279     return $map;
280   }
281
282   protected function createKeysSql($spec) {
283     $keys = [];
284
285     if (!empty($spec['primary key'])) {
286       $keys[] = 'PRIMARY KEY (' . $this->createKeySql($spec['primary key']) . ')';
287     }
288     if (!empty($spec['unique keys'])) {
289       foreach ($spec['unique keys'] as $key => $fields) {
290         $keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeySql($fields) . ')';
291       }
292     }
293     if (!empty($spec['indexes'])) {
294       $indexes = $this->getNormalizedIndexes($spec);
295       foreach ($indexes as $index => $fields) {
296         $keys[] = 'INDEX `' . $index . '` (' . $this->createKeySql($fields) . ')';
297       }
298     }
299
300     return $keys;
301   }
302
303   /**
304    * Gets normalized indexes from a table specification.
305    *
306    * Shortens indexes to 191 characters if they apply to utf8mb4-encoded
307    * fields, in order to comply with the InnoDB index limitation of 756 bytes.
308    *
309    * @param array $spec
310    *   The table specification.
311    *
312    * @return array
313    *   List of shortened indexes.
314    *
315    * @throws \Drupal\Core\Database\SchemaException
316    *   Thrown if field specification is missing.
317    */
318   protected function getNormalizedIndexes(array $spec) {
319     $indexes = isset($spec['indexes']) ? $spec['indexes'] : [];
320     foreach ($indexes as $index_name => $index_fields) {
321       foreach ($index_fields as $index_key => $index_field) {
322         // Get the name of the field from the index specification.
323         $field_name = is_array($index_field) ? $index_field[0] : $index_field;
324         // Check whether the field is defined in the table specification.
325         if (isset($spec['fields'][$field_name])) {
326           // Get the MySQL type from the processed field.
327           $mysql_field = $this->processField($spec['fields'][$field_name]);
328           if (in_array($mysql_field['mysql_type'], $this->mysqlStringTypes)) {
329             // Check whether we need to shorten the index.
330             if ((!isset($mysql_field['type']) || $mysql_field['type'] != 'varchar_ascii') && (!isset($mysql_field['length']) || $mysql_field['length'] > 191)) {
331               // Limit the index length to 191 characters.
332               $this->shortenIndex($indexes[$index_name][$index_key]);
333             }
334           }
335         }
336         else {
337           throw new SchemaException("MySQL needs the '$field_name' field specification in order to normalize the '$index_name' index");
338         }
339       }
340     }
341     return $indexes;
342   }
343
344   /**
345    * Helper function for normalizeIndexes().
346    *
347    * Shortens an index to 191 characters.
348    *
349    * @param array $index
350    *   The index array to be used in createKeySql.
351    *
352    * @see Drupal\Core\Database\Driver\mysql\Schema::createKeySql()
353    * @see Drupal\Core\Database\Driver\mysql\Schema::normalizeIndexes()
354    */
355   protected function shortenIndex(&$index) {
356     if (is_array($index)) {
357       if ($index[1] > 191) {
358         $index[1] = 191;
359       }
360     }
361     else {
362       $index = [$index, 191];
363     }
364   }
365
366   protected function createKeySql($fields) {
367     $return = [];
368     foreach ($fields as $field) {
369       if (is_array($field)) {
370         $return[] = '`' . $field[0] . '`(' . $field[1] . ')';
371       }
372       else {
373         $return[] = '`' . $field . '`';
374       }
375     }
376     return implode(', ', $return);
377   }
378
379   /**
380    * {@inheritdoc}
381    */
382   public function renameTable($table, $new_name) {
383     if (!$this->tableExists($table)) {
384       throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", ['@table' => $table, '@table_new' => $new_name]));
385     }
386     if ($this->tableExists($new_name)) {
387       throw new SchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", ['@table' => $table, '@table_new' => $new_name]));
388     }
389
390     $info = $this->getPrefixInfo($new_name);
391     return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`');
392   }
393
394   /**
395    * {@inheritdoc}
396    */
397   public function dropTable($table) {
398     if (!$this->tableExists($table)) {
399       return FALSE;
400     }
401
402     $this->connection->query('DROP TABLE {' . $table . '}');
403     return TRUE;
404   }
405
406   /**
407    * {@inheritdoc}
408    */
409   public function addField($table, $field, $spec, $keys_new = []) {
410     if (!$this->tableExists($table)) {
411       throw new SchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", ['@field' => $field, '@table' => $table]));
412     }
413     if ($this->fieldExists($table, $field)) {
414       throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", ['@field' => $field, '@table' => $table]));
415     }
416
417     // Fields that are part of a PRIMARY KEY must be added as NOT NULL.
418     $is_primary_key = isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE);
419     if ($is_primary_key) {
420       $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field => $spec]);
421     }
422
423     $fixnull = FALSE;
424     if (!empty($spec['not null']) && !isset($spec['default']) && !$is_primary_key) {
425       $fixnull = TRUE;
426       $spec['not null'] = FALSE;
427     }
428     $query = 'ALTER TABLE {' . $table . '} ADD ';
429     $query .= $this->createFieldSql($field, $this->processField($spec));
430     if ($keys_sql = $this->createKeysSql($keys_new)) {
431       // Make sure to drop the existing primary key before adding a new one.
432       // This is only needed when adding a field because this method, unlike
433       // changeField(), is supposed to handle primary keys automatically.
434       if (isset($keys_new['primary key']) && $this->indexExists($table, 'PRIMARY')) {
435         $query .= ', DROP PRIMARY KEY';
436       }
437
438       $query .= ', ADD ' . implode(', ADD ', $keys_sql);
439     }
440     $this->connection->query($query);
441     if (isset($spec['initial_from_field'])) {
442       if (isset($spec['initial'])) {
443         $expression = 'COALESCE(' . $spec['initial_from_field'] . ', :default_initial_value)';
444         $arguments = [':default_initial_value' => $spec['initial']];
445       }
446       else {
447         $expression = $spec['initial_from_field'];
448         $arguments = [];
449       }
450       $this->connection->update($table)
451         ->expression($field, $expression, $arguments)
452         ->execute();
453     }
454     elseif (isset($spec['initial'])) {
455       $this->connection->update($table)
456         ->fields([$field => $spec['initial']])
457         ->execute();
458     }
459     if ($fixnull) {
460       $spec['not null'] = TRUE;
461       $this->changeField($table, $field, $field, $spec);
462     }
463   }
464
465   /**
466    * {@inheritdoc}
467    */
468   public function dropField($table, $field) {
469     if (!$this->fieldExists($table, $field)) {
470       return FALSE;
471     }
472
473     // When dropping a field that is part of a composite primary key MySQL
474     // automatically removes the field from the primary key, which can leave the
475     // table in an invalid state. MariaDB 10.2.8 requires explicitly dropping
476     // the primary key first for this reason. We perform this deletion
477     // explicitly which also makes the behavior on both MySQL and MariaDB
478     // consistent with PostgreSQL.
479     // @see https://mariadb.com/kb/en/library/alter-table
480     $primary_key = $this->findPrimaryKeyColumns($table);
481     if ((count($primary_key) > 1) && in_array($field, $primary_key, TRUE)) {
482       $this->dropPrimaryKey($table);
483     }
484
485     $this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`');
486     return TRUE;
487   }
488
489   /**
490    * {@inheritdoc}
491    */
492   public function fieldSetDefault($table, $field, $default) {
493     if (!$this->fieldExists($table, $field)) {
494       throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", ['@table' => $table, '@field' => $field]));
495     }
496
497     $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $this->escapeDefaultValue($default));
498   }
499
500   /**
501    * {@inheritdoc}
502    */
503   public function fieldSetNoDefault($table, $field) {
504     if (!$this->fieldExists($table, $field)) {
505       throw new SchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", ['@table' => $table, '@field' => $field]));
506     }
507
508     $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT');
509   }
510
511   /**
512    * {@inheritdoc}
513    */
514   public function indexExists($table, $name) {
515     // Returns one row for each column in the index. Result is string or FALSE.
516     // Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
517     $row = $this->connection->query('SHOW INDEX FROM {' . $table . '} WHERE key_name = ' . $this->connection->quote($name))->fetchAssoc();
518     return isset($row['Key_name']);
519   }
520
521   /**
522    * {@inheritdoc}
523    */
524   public function addPrimaryKey($table, $fields) {
525     if (!$this->tableExists($table)) {
526       throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", ['@table' => $table]));
527     }
528     if ($this->indexExists($table, 'PRIMARY')) {
529       throw new SchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", ['@table' => $table]));
530     }
531
532     $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
533   }
534
535   /**
536    * {@inheritdoc}
537    */
538   public function dropPrimaryKey($table) {
539     if (!$this->indexExists($table, 'PRIMARY')) {
540       return FALSE;
541     }
542
543     $this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY');
544     return TRUE;
545   }
546
547   /**
548    * {@inheritdoc}
549    */
550   protected function findPrimaryKeyColumns($table) {
551     if (!$this->tableExists($table)) {
552       return FALSE;
553     }
554     $result = $this->connection->query("SHOW KEYS FROM {" . $table . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
555     return array_keys($result);
556   }
557
558   /**
559    * {@inheritdoc}
560    */
561   public function addUniqueKey($table, $name, $fields) {
562     if (!$this->tableExists($table)) {
563       throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", ['@table' => $table, '@name' => $name]));
564     }
565     if ($this->indexExists($table, $name)) {
566       throw new SchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", ['@table' => $table, '@name' => $name]));
567     }
568
569     $this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')');
570   }
571
572   /**
573    * {@inheritdoc}
574    */
575   public function dropUniqueKey($table, $name) {
576     if (!$this->indexExists($table, $name)) {
577       return FALSE;
578     }
579
580     $this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`');
581     return TRUE;
582   }
583
584   /**
585    * {@inheritdoc}
586    */
587   public function addIndex($table, $name, $fields, array $spec) {
588     if (!$this->tableExists($table)) {
589       throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", ['@table' => $table, '@name' => $name]));
590     }
591     if ($this->indexExists($table, $name)) {
592       throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", ['@table' => $table, '@name' => $name]));
593     }
594
595     $spec['indexes'][$name] = $fields;
596     $indexes = $this->getNormalizedIndexes($spec);
597
598     $this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($indexes[$name]) . ')');
599   }
600
601   /**
602    * {@inheritdoc}
603    */
604   public function dropIndex($table, $name) {
605     if (!$this->indexExists($table, $name)) {
606       return FALSE;
607     }
608
609     $this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`');
610     return TRUE;
611   }
612
613   /**
614    * {@inheritdoc}
615    */
616   public function changeField($table, $field, $field_new, $spec, $keys_new = []) {
617     if (!$this->fieldExists($table, $field)) {
618       throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", ['@table' => $table, '@name' => $field]));
619     }
620     if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
621       throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", ['@table' => $table, '@name' => $field, '@name_new' => $field_new]));
622     }
623     if (isset($keys_new['primary key']) && in_array($field_new, $keys_new['primary key'], TRUE)) {
624       $this->ensureNotNullPrimaryKey($keys_new['primary key'], [$field_new => $spec]);
625     }
626
627     $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
628     if ($keys_sql = $this->createKeysSql($keys_new)) {
629       $sql .= ', ADD ' . implode(', ADD ', $keys_sql);
630     }
631     $this->connection->query($sql);
632   }
633
634   /**
635    * {@inheritdoc}
636    */
637   public function prepareComment($comment, $length = NULL) {
638     // Truncate comment to maximum comment length.
639     if (isset($length)) {
640       // Add table prefixes before truncating.
641       $comment = Unicode::truncate($this->connection->prefixTables($comment), $length, TRUE, TRUE);
642     }
643     // Remove semicolons to avoid triggering multi-statement check.
644     $comment = strtr($comment, [';' => '.']);
645     return $this->connection->quote($comment);
646   }
647
648   /**
649    * Retrieve a table or column comment.
650    */
651   public function getComment($table, $column = NULL) {
652     $condition = $this->buildTableNameCondition($table);
653     if (isset($column)) {
654       $condition->condition('column_name', $column);
655       $condition->compile($this->connection, $this);
656       // Don't use {} around information_schema.columns table.
657       return $this->connection->query("SELECT column_comment as column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
658     }
659     $condition->compile($this->connection, $this);
660     // Don't use {} around information_schema.tables table.
661     $comment = $this->connection->query("SELECT table_comment as table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
662     // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
663     return preg_replace('/; InnoDB free:.*$/', '', $comment);
664   }
665
666   /**
667    * {@inheritdoc}
668    */
669   public function tableExists($table) {
670     // The information_schema table is very slow to query under MySQL 5.0.
671     // Instead, we try to select from the table in question.  If it fails,
672     // the most likely reason is that it does not exist. That is dramatically
673     // faster than using information_schema.
674     // @link http://bugs.mysql.com/bug.php?id=19588
675     // @todo This override should be removed once we require a version of MySQL
676     //   that has that bug fixed.
677     try {
678       $this->connection->queryRange("SELECT 1 FROM {" . $table . "}", 0, 1);
679       return TRUE;
680     }
681     catch (\Exception $e) {
682       return FALSE;
683     }
684   }
685
686   /**
687    * {@inheritdoc}
688    */
689   public function fieldExists($table, $column) {
690     // The information_schema table is very slow to query under MySQL 5.0.
691     // Instead, we try to select from the table and field in question. If it
692     // fails, the most likely reason is that it does not exist. That is
693     // dramatically faster than using information_schema.
694     // @link http://bugs.mysql.com/bug.php?id=19588
695     // @todo This override should be removed once we require a version of MySQL
696     //   that has that bug fixed.
697     try {
698       $this->connection->queryRange("SELECT $column FROM {" . $table . "}", 0, 1);
699       return TRUE;
700     }
701     catch (\Exception $e) {
702       return FALSE;
703     }
704   }
705
706 }
707
708 /**
709  * @} End of "addtogroup schemaapi".
710  */