084d2e82ce2b4b12387f75942f54c05ae57f56df
[moodle.git] / lib / ddl / mysql_sql_generator.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Mysql specific SQL code generator.
19  *
20  * @package    core_ddl
21  * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
22  *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 require_once($CFG->libdir.'/ddl/sql_generator.php');
30 /**
31  * This class generate SQL code to be used against MySQL
32  * It extends XMLDBgenerator so everything can be
33  * overridden as needed to generate correct SQL.
34  *
35  * @package    core_ddl
36  * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
37  *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
38  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39  */
40 class mysql_sql_generator extends sql_generator {
42     // Only set values that are different from the defaults present in XMLDBgenerator
44     /** @var string Used to quote names. */
45     public $quote_string = '`';
47     /** @var string To define the default to set for NOT NULLs CHARs without default (null=do nothing).*/
48     public $default_for_char = '';
50     /** @var bool To specify if the generator must use some DEFAULT clause to drop defaults.*/
51     public $drop_default_value_required = true;
53     /** @var string The DEFAULT clause required to drop defaults.*/
54     public $drop_default_value = null;
56     /** @var string To force primary key names to one string (null=no force).*/
57     public $primary_key_name = '';
59     /** @var string Template to drop PKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/
60     public $drop_primary_key = 'ALTER TABLE TABLENAME DROP PRIMARY KEY';
62     /** @var string Template to drop UKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/
63     public $drop_unique_key = 'ALTER TABLE TABLENAME DROP KEY KEYNAME';
65     /** @var string Template to drop FKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/
66     public $drop_foreign_key = 'ALTER TABLE TABLENAME DROP FOREIGN KEY KEYNAME';
68     /** @var bool True if the generator needs to add extra code to generate the sequence fields.*/
69     public $sequence_extra_code = false;
71     /** @var string The particular name for inline sequences in this generator.*/
72     public $sequence_name = 'auto_increment';
74     public $add_after_clause = true; // Does the generator need to add the after clause for fields
76     /** @var string Characters to be used as concatenation operator.*/
77     public $concat_character = null;
79     /** @var string The SQL template to alter columns where the 'TABLENAME' and 'COLUMNSPECS' keywords are dynamically replaced.*/
80     public $alter_column_sql = 'ALTER TABLE TABLENAME MODIFY COLUMN COLUMNSPECS';
82     /** @var string SQL sentence to drop one index where 'TABLENAME', 'INDEXNAME' keywords are dynamically replaced.*/
83     public $drop_index_sql = 'ALTER TABLE TABLENAME DROP INDEX INDEXNAME';
85     /** @var string SQL sentence to rename one index where 'TABLENAME', 'OLDINDEXNAME' and 'NEWINDEXNAME' are dynamically replaced.*/
86     public $rename_index_sql = null;
88     /** @var string SQL sentence to rename one key 'TABLENAME', 'OLDKEYNAME' and 'NEWKEYNAME' are dynamically replaced.*/
89     public $rename_key_sql = null;
91     /** Maximum size of InnoDB row in Antelope file format */
92     const ANTELOPE_MAX_ROW_SIZE = 8126;
94     /**
95      * Reset a sequence to the id field of a table.
96      *
97      * @param xmldb_table|string $table name of table or the table object.
98      * @return array of sql statements
99      */
100     public function getResetSequenceSQL($table) {
102         if ($table instanceof xmldb_table) {
103             $tablename = $table->getName();
104         } else {
105             $tablename = $table;
106         }
108         // From http://dev.mysql.com/doc/refman/5.0/en/alter-table.html
109         $value = (int)$this->mdb->get_field_sql('SELECT MAX(id) FROM {'.$tablename.'}');
110         $value++;
111         return array("ALTER TABLE $this->prefix$tablename AUTO_INCREMENT = $value");
112     }
114     /**
115      * Calculate proximate row size when using InnoDB
116      * tables in Antelope row format.
117      *
118      * Note: the returned value is a bit higher to compensate for
119      *       errors and changes of column data types.
120      *
121      * @deprecated since Moodle 2.9 MDL-49723 - please do not use this function any more.
122      */
123     public function guess_antolope_row_size(array $columns) {
124         throw new coding_exception('guess_antolope_row_size() can not be used any more, please use guess_antelope_row_size() instead.');
125     }
127     /**
128      * Calculate proximate row size when using InnoDB tables in Antelope row format.
129      *
130      * Note: the returned value is a bit higher to compensate for errors and changes of column data types.
131      *
132      * @param xmldb_field[]|database_column_info[] $columns
133      * @return int approximate row size in bytes
134      */
135     public function guess_antelope_row_size(array $columns) {
137         if (empty($columns)) {
138             return 0;
139         }
141         $size = 0;
142         $first = reset($columns);
144         if (count($columns) > 1) {
145             // Do not start with zero because we need to cover changes of field types and
146             // this calculation is most probably not be accurate.
147             $size += 1000;
148         }
150         if ($first instanceof xmldb_field) {
151             foreach ($columns as $field) {
152                 switch ($field->getType()) {
153                     case XMLDB_TYPE_TEXT:
154                         $size += 768;
155                         break;
156                     case XMLDB_TYPE_BINARY:
157                         $size += 768;
158                         break;
159                     case XMLDB_TYPE_CHAR:
160                         $bytes = $field->getLength() * 3;
161                         if ($bytes > 768) {
162                             $bytes = 768;
163                         }
164                         $size += $bytes;
165                         break;
166                     default:
167                         // Anything else is usually maximum 8 bytes.
168                         $size += 8;
169                 }
170             }
172         } else if ($first instanceof database_column_info) {
173             foreach ($columns as $column) {
174                 switch ($column->meta_type) {
175                     case 'X':
176                         $size += 768;
177                         break;
178                     case 'B':
179                         $size += 768;
180                         break;
181                     case 'C':
182                         $bytes = $column->max_length * 3;
183                         if ($bytes > 768) {
184                             $bytes = 768;
185                         }
186                         $size += $bytes;
187                         break;
188                     default:
189                         // Anything else is usually maximum 8 bytes.
190                         $size += 8;
191                 }
192             }
193         }
195         return $size;
196     }
198     /**
199      * Given one correct xmldb_table, returns the SQL statements
200      * to create it (inside one array).
201      *
202      * @param xmldb_table $xmldb_table An xmldb_table instance.
203      * @return array An array of SQL statements, starting with the table creation SQL followed
204      * by any of its comments, indexes and sequence creation SQL statements.
205      */
206     public function getCreateTableSQL($xmldb_table) {
207         // First find out if want some special db engine.
208         $engine = $this->mdb->get_dbengine();
209         // Do we know collation?
210         $collation = $this->mdb->get_dbcollation();
212         // Do we need to use compressed format for rows?
213         $rowformat = "";
214         $size = $this->guess_antelope_row_size($xmldb_table->getFields());
215         if ($size > self::ANTELOPE_MAX_ROW_SIZE) {
216             if ($this->mdb->is_compressed_row_format_supported()) {
217                 $rowformat = "\n ROW_FORMAT=Compressed";
218             }
219         }
221         $sqlarr = parent::getCreateTableSQL($xmldb_table);
223         // This is a very nasty hack that tries to use just one query per created table
224         // because MySQL is stupidly slow when modifying empty tables.
225         // Note: it is safer to inject everything on new lines because there might be some trailing -- comments.
226         $sqls = array();
227         $prevcreate = null;
228         $matches = null;
229         foreach ($sqlarr as $sql) {
230             if (preg_match('/^CREATE TABLE ([^ ]+)/', $sql, $matches)) {
231                 $prevcreate = $matches[1];
232                 $sql = preg_replace('/\s*\)\s*$/s', '/*keyblock*/)', $sql);
233                 // Let's inject the extra MySQL tweaks here.
234                 if ($engine) {
235                     $sql .= "\n ENGINE = $engine";
236                 }
237                 if ($collation) {
238                     if (strpos($collation, 'utf8_') === 0) {
239                         $sql .= "\n DEFAULT CHARACTER SET utf8";
240                     }
241                     $sql .= "\n DEFAULT COLLATE = $collation";
242                 }
243                 if ($rowformat) {
244                     $sql .= $rowformat;
245                 }
246                 $sqls[] = $sql;
247                 continue;
248             }
249             if ($prevcreate) {
250                 if (preg_match('/^ALTER TABLE '.$prevcreate.' COMMENT=(.*)$/s', $sql, $matches)) {
251                     $prev = array_pop($sqls);
252                     $prev .= "\n COMMENT=$matches[1]";
253                     $sqls[] = $prev;
254                     continue;
255                 }
256                 if (preg_match('/^CREATE INDEX ([^ ]+) ON '.$prevcreate.' (.*)$/s', $sql, $matches)) {
257                     $prev = array_pop($sqls);
258                     if (strpos($prev, '/*keyblock*/')) {
259                         $prev = str_replace('/*keyblock*/', "\n, KEY $matches[1] $matches[2]/*keyblock*/", $prev);
260                         $sqls[] = $prev;
261                         continue;
262                     } else {
263                         $sqls[] = $prev;
264                     }
265                 }
266                 if (preg_match('/^CREATE UNIQUE INDEX ([^ ]+) ON '.$prevcreate.' (.*)$/s', $sql, $matches)) {
267                     $prev = array_pop($sqls);
268                     if (strpos($prev, '/*keyblock*/')) {
269                         $prev = str_replace('/*keyblock*/', "\n, UNIQUE KEY $matches[1] $matches[2]/*keyblock*/", $prev);
270                         $sqls[] = $prev;
271                         continue;
272                     } else {
273                         $sqls[] = $prev;
274                     }
275                 }
276             }
277             $prevcreate = null;
278             $sqls[] = $sql;
279         }
281         foreach ($sqls as $key => $sql) {
282             $sqls[$key] = str_replace('/*keyblock*/', "\n", $sql);
283         }
285         return $sqls;
286     }
288     /**
289      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add the field to the table.
290      *
291      * @param xmldb_table $xmldb_table The table related to $xmldb_field.
292      * @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.
293      * @param string $skip_type_clause The type clause on alter columns, NULL by default.
294      * @param string $skip_default_clause The default clause on alter columns, NULL by default.
295      * @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default.
296      * @return array The SQL statement for adding a field to the table.
297      */
298     public function getAddFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
299         $sqls = parent::getAddFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause, $skip_default_clause, $skip_notnull_clause);
301         if ($this->table_exists($xmldb_table)) {
302             $tablename = $xmldb_table->getName();
304             $size = $this->guess_antelope_row_size($this->mdb->get_columns($tablename));
305             $size += $this->guess_antelope_row_size(array($xmldb_field));
307             if ($size > self::ANTELOPE_MAX_ROW_SIZE) {
308                 if ($this->mdb->is_compressed_row_format_supported()) {
309                     $format = strtolower($this->mdb->get_row_format($tablename));
310                     if ($format === 'compact' or $format === 'redundant') {
311                         // Change the format before conversion so that we do not run out of space.
312                         array_unshift($sqls, "ALTER TABLE {$this->prefix}$tablename ROW_FORMAT=Compressed");
313                     }
314                 }
315             }
316         }
318         return $sqls;
319     }
321     /**
322      * Given one correct xmldb_table, returns the SQL statements
323      * to create temporary table (inside one array).
324      *
325      * @param xmldb_table $xmldb_table The xmldb_table object instance.
326      * @return array of sql statements
327      */
328     public function getCreateTempTableSQL($xmldb_table) {
329         // Do we know collation?
330         $collation = $this->mdb->get_dbcollation();
331         $this->temptables->add_temptable($xmldb_table->getName());
333         $sqlarr = parent::getCreateTableSQL($xmldb_table);
335         // Let's inject the extra MySQL tweaks.
336         foreach ($sqlarr as $i=>$sql) {
337             if (strpos($sql, 'CREATE TABLE ') === 0) {
338                 // We do not want the engine hack included in create table SQL.
339                 $sqlarr[$i] = preg_replace('/^CREATE TABLE (.*)/s', 'CREATE TEMPORARY TABLE $1', $sql);
340                 if ($collation) {
341                     if (strpos($collation, 'utf8_') === 0) {
342                         $sqlarr[$i] .= " DEFAULT CHARACTER SET utf8";
343                     }
344                     $sqlarr[$i] .= " DEFAULT COLLATE $collation";
345                 }
346             }
347         }
349         return $sqlarr;
350     }
352     /**
353      * Given one correct xmldb_table, returns the SQL statements
354      * to drop it (inside one array).
355      *
356      * @param xmldb_table $xmldb_table The table to drop.
357      * @return array SQL statement(s) for dropping the specified table.
358      */
359     public function getDropTableSQL($xmldb_table) {
360         $sqlarr = parent::getDropTableSQL($xmldb_table);
361         if ($this->temptables->is_temptable($xmldb_table->getName())) {
362             $sqlarr = preg_replace('/^DROP TABLE/', "DROP TEMPORARY TABLE", $sqlarr);
363             $this->temptables->delete_temptable($xmldb_table->getName());
364         }
365         return $sqlarr;
366     }
368     /**
369      * Given one XMLDB Type, length and decimals, returns the DB proper SQL type.
370      *
371      * @param int $xmldb_type The xmldb_type defined constant. XMLDB_TYPE_INTEGER and other XMLDB_TYPE_* constants.
372      * @param int $xmldb_length The length of that data type.
373      * @param int $xmldb_decimals The decimal places of precision of the data type.
374      * @return string The DB defined data type.
375      */
376     public function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null) {
378         switch ($xmldb_type) {
379             case XMLDB_TYPE_INTEGER:    // From http://mysql.com/doc/refman/5.0/en/numeric-types.html!
380                 if (empty($xmldb_length)) {
381                     $xmldb_length = 10;
382                 }
383                 if ($xmldb_length > 9) {
384                     $dbtype = 'BIGINT';
385                 } else if ($xmldb_length > 6) {
386                     $dbtype = 'INT';
387                 } else if ($xmldb_length > 4) {
388                     $dbtype = 'MEDIUMINT';
389                 } else if ($xmldb_length > 2) {
390                     $dbtype = 'SMALLINT';
391                 } else {
392                     $dbtype = 'TINYINT';
393                 }
394                 $dbtype .= '(' . $xmldb_length . ')';
395                 break;
396             case XMLDB_TYPE_NUMBER:
397                 $dbtype = $this->number_type;
398                 if (!empty($xmldb_length)) {
399                     $dbtype .= '(' . $xmldb_length;
400                     if (!empty($xmldb_decimals)) {
401                         $dbtype .= ',' . $xmldb_decimals;
402                     }
403                     $dbtype .= ')';
404                 }
405                 break;
406             case XMLDB_TYPE_FLOAT:
407                 $dbtype = 'DOUBLE';
408                 if (!empty($xmldb_decimals)) {
409                     if ($xmldb_decimals < 6) {
410                         $dbtype = 'FLOAT';
411                     }
412                 }
413                 if (!empty($xmldb_length)) {
414                     $dbtype .= '(' . $xmldb_length;
415                     if (!empty($xmldb_decimals)) {
416                         $dbtype .= ',' . $xmldb_decimals;
417                     } else {
418                         $dbtype .= ', 0'; // In MySQL, if length is specified, decimals are mandatory for FLOATs
419                     }
420                     $dbtype .= ')';
421                 }
422                 break;
423             case XMLDB_TYPE_CHAR:
424                 $dbtype = 'VARCHAR';
425                 if (empty($xmldb_length)) {
426                     $xmldb_length='255';
427                 }
428                 $dbtype .= '(' . $xmldb_length . ')';
429                 if ($collation = $this->mdb->get_dbcollation()) {
430                     if (strpos($collation, 'utf8_') === 0) {
431                         $dbtype .= " CHARACTER SET utf8";
432                     }
433                     $dbtype .= " COLLATE $collation";
434                 }
435                 break;
436             case XMLDB_TYPE_TEXT:
437                 $dbtype = 'LONGTEXT';
438                 if ($collation = $this->mdb->get_dbcollation()) {
439                     if (strpos($collation, 'utf8_') === 0) {
440                         $dbtype .= " CHARACTER SET utf8";
441                     }
442                     $dbtype .= " COLLATE $collation";
443                 }
444                 break;
445             case XMLDB_TYPE_BINARY:
446                 $dbtype = 'LONGBLOB';
447                 break;
448             case XMLDB_TYPE_DATETIME:
449                 $dbtype = 'DATETIME';
450         }
451         return $dbtype;
452     }
454     /**
455      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add its default
456      * (usually invoked from getModifyDefaultSQL()
457      *
458      * @param xmldb_table $xmldb_table The xmldb_table object instance.
459      * @param xmldb_field $xmldb_field The xmldb_field object instance.
460      * @return array Array of SQL statements to create a field's default.
461      */
462     public function getCreateDefaultSQL($xmldb_table, $xmldb_field) {
463         // Just a wrapper over the getAlterFieldSQL() function for MySQL that
464         // is capable of handling defaults
465         return $this->getAlterFieldSQL($xmldb_table, $xmldb_field);
466     }
468     /**
469      * Given one correct xmldb_field and the new name, returns the SQL statements
470      * to rename it (inside one array).
471      *
472      * @param xmldb_table $xmldb_table The table related to $xmldb_field.
473      * @param xmldb_field $xmldb_field The instance of xmldb_field to get the renamed field from.
474      * @param string $newname The new name to rename the field to.
475      * @return array The SQL statements for renaming the field.
476      */
477     public function getRenameFieldSQL($xmldb_table, $xmldb_field, $newname) {
478         // NOTE: MySQL is pretty different from the standard to justify this overloading.
480         // Need a clone of xmldb_field to perform the change leaving original unmodified
481         $xmldb_field_clone = clone($xmldb_field);
483         // Change the name of the field to perform the change
484         $xmldb_field_clone->setName($newname);
486         $fieldsql = $this->getFieldSQL($xmldb_table, $xmldb_field_clone);
488         $sql = 'ALTER TABLE ' . $this->getTableName($xmldb_table) . ' CHANGE ' .
489                $xmldb_field->getName() . ' ' . $fieldsql;
491         return array($sql);
492     }
494     /**
495      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its default
496      * (usually invoked from getModifyDefaultSQL()
497      *
498      * Note that this method may be dropped in future.
499      *
500      * @param xmldb_table $xmldb_table The xmldb_table object instance.
501      * @param xmldb_field $xmldb_field The xmldb_field object instance.
502      * @return array Array of SQL statements to create a field's default.
503      *
504      * @todo MDL-31147 Moodle 2.1 - Drop getDropDefaultSQL()
505      */
506     public function getDropDefaultSQL($xmldb_table, $xmldb_field) {
507         // Just a wrapper over the getAlterFieldSQL() function for MySQL that
508         // is capable of handling defaults
509         return $this->getAlterFieldSQL($xmldb_table, $xmldb_field);
510     }
512     /**
513      * Returns the code (array of statements) needed to add one comment to the table.
514      *
515      * @param xmldb_table $xmldb_table The xmldb_table object instance.
516      * @return array Array of SQL statements to add one comment to the table.
517      */
518     function getCommentSQL ($xmldb_table) {
519         $comment = '';
521         if ($xmldb_table->getComment()) {
522             $comment .= 'ALTER TABLE ' . $this->getTableName($xmldb_table);
523             $comment .= " COMMENT='" . $this->addslashes(substr($xmldb_table->getComment(), 0, 60)) . "'";
524         }
525         return array($comment);
526     }
528     /**
529      * Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg).
530      *
531      * (MySQL requires the whole xmldb_table object to be specified, so we add it always)
532      *
533      * This is invoked from getNameForObject().
534      * Only some DB have this implemented.
535      *
536      * @param string $object_name The object's name to check for.
537      * @param string $type The object's type (pk, uk, fk, ck, ix, uix, seq, trg).
538      * @param string $table_name The table's name to check in
539      * @return bool If such name is currently in use (true) or no (false)
540      */
541     public function isNameInUse($object_name, $type, $table_name) {
543         switch($type) {
544             case 'ix':
545             case 'uix':
546                 // First of all, check table exists
547                 $metatables = $this->mdb->get_tables();
548                 if (isset($metatables[$table_name])) {
549                     // Fetch all the indexes in the table
550                     if ($indexes = $this->mdb->get_indexes($table_name)) {
551                         // Look for existing index in array
552                         if (isset($indexes[$object_name])) {
553                             return true;
554                         }
555                     }
556                 }
557                 break;
558         }
559         return false; //No name in use found
560     }
563     /**
564      * Returns an array of reserved words (lowercase) for this DB
565      * @return array An array of database specific reserved words
566      */
567     public static function getReservedWords() {
568         // This file contains the reserved words for MySQL databases
569         // from http://dev.mysql.com/doc/refman/6.0/en/reserved-words.html
570         $reserved_words = array (
571             'accessible', 'add', 'all', 'alter', 'analyze', 'and', 'as', 'asc',
572             'asensitive', 'before', 'between', 'bigint', 'binary',
573             'blob', 'both', 'by', 'call', 'cascade', 'case', 'change',
574             'char', 'character', 'check', 'collate', 'column',
575             'condition', 'connection', 'constraint', 'continue',
576             'convert', 'create', 'cross', 'current_date', 'current_time',
577             'current_timestamp', 'current_user', 'cursor', 'database',
578             'databases', 'day_hour', 'day_microsecond',
579             'day_minute', 'day_second', 'dec', 'decimal', 'declare',
580             'default', 'delayed', 'delete', 'desc', 'describe',
581             'deterministic', 'distinct', 'distinctrow', 'div', 'double',
582             'drop', 'dual', 'each', 'else', 'elseif', 'enclosed', 'escaped',
583             'exists', 'exit', 'explain', 'false', 'fetch', 'float', 'float4',
584             'float8', 'for', 'force', 'foreign', 'from', 'fulltext', 'grant',
585             'group', 'having', 'high_priority', 'hour_microsecond',
586             'hour_minute', 'hour_second', 'if', 'ignore', 'in', 'index',
587             'infile', 'inner', 'inout', 'insensitive', 'insert', 'int', 'int1',
588             'int2', 'int3', 'int4', 'int8', 'integer', 'interval', 'into', 'is',
589             'iterate', 'join', 'key', 'keys', 'kill', 'leading', 'leave', 'left',
590             'like', 'limit', 'linear', 'lines', 'load', 'localtime', 'localtimestamp',
591             'lock', 'long', 'longblob', 'longtext', 'loop', 'low_priority', 'master_heartbeat_period',
592             'master_ssl_verify_server_cert', 'match', 'mediumblob', 'mediumint', 'mediumtext',
593             'middleint', 'minute_microsecond', 'minute_second',
594             'mod', 'modifies', 'natural', 'not', 'no_write_to_binlog',
595             'null', 'numeric', 'on', 'optimize', 'option', 'optionally',
596             'or', 'order', 'out', 'outer', 'outfile', 'overwrite', 'precision', 'primary',
597             'procedure', 'purge', 'raid0', 'range', 'read', 'read_only', 'read_write', 'reads', 'real',
598             'references', 'regexp', 'release', 'rename', 'repeat', 'replace',
599             'require', 'restrict', 'return', 'revoke', 'right', 'rlike', 'schema',
600             'schemas', 'second_microsecond', 'select', 'sensitive',
601             'separator', 'set', 'show', 'smallint', 'soname', 'spatial',
602             'specific', 'sql', 'sqlexception', 'sqlstate', 'sqlwarning',
603             'sql_big_result', 'sql_calc_found_rows', 'sql_small_result',
604             'ssl', 'starting', 'straight_join', 'table', 'terminated', 'then',
605             'tinyblob', 'tinyint', 'tinytext', 'to', 'trailing', 'trigger', 'true',
606             'undo', 'union', 'unique', 'unlock', 'unsigned', 'update',
607             'upgrade', 'usage', 'use', 'using', 'utc_date', 'utc_time',
608             'utc_timestamp', 'values', 'varbinary', 'varchar', 'varcharacter',
609             'varying', 'when', 'where', 'while', 'with', 'write', 'x509',
610             'xor', 'year_month', 'zerofill'
611         );
612         return $reserved_words;
613     }