PageRenderTime 41ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/ddl/postgres_sql_generator.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 535 lines | 291 code | 57 blank | 187 comment | 83 complexity | 917c3367dc0c2f4f30d67da6e3d5b0c1 MD5 | raw file
  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/>.
  16. /**
  17. * PostgreSQL specific SQL code generator.
  18. *
  19. * @package core_ddl
  20. * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
  21. * 2001-3001 Eloy Lafuente (stronk7) http://contiento.com
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. require_once($CFG->libdir.'/ddl/sql_generator.php');
  26. /**
  27. * This class generate SQL code to be used against PostgreSQL
  28. * It extends XMLDBgenerator so everything can be
  29. * overridden as needed to generate correct SQL.
  30. *
  31. * @package core_ddl
  32. * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
  33. * 2001-3001 Eloy Lafuente (stronk7) http://contiento.com
  34. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35. */
  36. class postgres_sql_generator extends sql_generator {
  37. // Only set values that are different from the defaults present in XMLDBgenerator
  38. /** @var string Proper type for NUMBER(x) in this DB. */
  39. public $number_type = 'NUMERIC';
  40. /** @var string To define the default to set for NOT NULLs CHARs without default (null=do nothing).*/
  41. public $default_for_char = '';
  42. /** @var bool True if the generator needs to add extra code to generate the sequence fields.*/
  43. public $sequence_extra_code = false;
  44. /** @var string The particular name for inline sequences in this generator.*/
  45. public $sequence_name = 'BIGSERIAL';
  46. /** @var string The particular name for inline sequences in this generator.*/
  47. public $sequence_name_small = 'SERIAL';
  48. /** @var bool To avoid outputting the rest of the field specs, leaving only the name and the sequence_name returned.*/
  49. public $sequence_only = true;
  50. /** @var string SQL sentence to rename one index where 'TABLENAME', 'OLDINDEXNAME' and 'NEWINDEXNAME' are dynamically replaced.*/
  51. public $rename_index_sql = 'ALTER TABLE OLDINDEXNAME RENAME TO NEWINDEXNAME';
  52. /** @var string SQL sentence to rename one key 'TABLENAME', 'OLDKEYNAME' and 'NEWKEYNAME' are dynamically replaced.*/
  53. public $rename_key_sql = null;
  54. /** @var string type of string quoting used - '' or \' quotes*/
  55. protected $std_strings = null;
  56. /**
  57. * Reset a sequence to the id field of a table.
  58. *
  59. * @param xmldb_table|string $table name of table or the table object.
  60. * @return array of sql statements
  61. */
  62. public function getResetSequenceSQL($table) {
  63. if ($table instanceof xmldb_table) {
  64. $tablename = $table->getName();
  65. } else {
  66. $tablename = $table;
  67. }
  68. // From http://www.postgresql.org/docs/7.4/static/sql-altersequence.html
  69. $value = (int)$this->mdb->get_field_sql('SELECT MAX(id) FROM {'.$tablename.'}');
  70. $value++;
  71. return array("ALTER SEQUENCE $this->prefix{$tablename}_id_seq RESTART WITH $value");
  72. }
  73. /**
  74. * Given one correct xmldb_table, returns the SQL statements
  75. * to create temporary table (inside one array).
  76. *
  77. * @param xmldb_table $xmldb_table The xmldb_table object instance.
  78. * @return array of sql statements
  79. */
  80. public function getCreateTempTableSQL($xmldb_table) {
  81. $this->temptables->add_temptable($xmldb_table->getName());
  82. $sqlarr = $this->getCreateTableSQL($xmldb_table);
  83. $sqlarr = preg_replace('/^CREATE TABLE/', "CREATE TEMPORARY TABLE", $sqlarr);
  84. return $sqlarr;
  85. }
  86. /**
  87. * Given one correct xmldb_table, returns the SQL statements
  88. * to drop it (inside one array).
  89. *
  90. * @param xmldb_table $xmldb_table The table to drop.
  91. * @return array SQL statement(s) for dropping the specified table.
  92. */
  93. public function getDropTableSQL($xmldb_table) {
  94. $sqlarr = parent::getDropTableSQL($xmldb_table);
  95. if ($this->temptables->is_temptable($xmldb_table->getName())) {
  96. $this->temptables->delete_temptable($xmldb_table->getName());
  97. }
  98. return $sqlarr;
  99. }
  100. /**
  101. * Given one correct xmldb_index, returns the SQL statements
  102. * needed to create it (in array).
  103. *
  104. * @param xmldb_table $xmldb_table The xmldb_table instance to create the index on.
  105. * @param xmldb_index $xmldb_index The xmldb_index to create.
  106. * @return array An array of SQL statements to create the index.
  107. * @throws coding_exception Thrown if the xmldb_index does not validate with the xmldb_table.
  108. */
  109. public function getCreateIndexSQL($xmldb_table, $xmldb_index) {
  110. $sqls = parent::getCreateIndexSQL($xmldb_table, $xmldb_index);
  111. $hints = $xmldb_index->getHints();
  112. $fields = $xmldb_index->getFields();
  113. if (in_array('varchar_pattern_ops', $hints) and count($fields) == 1) {
  114. // Add the pattern index and keep the normal one, keep unique only the standard index to improve perf.
  115. foreach ($sqls as $sql) {
  116. $field = reset($fields);
  117. $count = 0;
  118. $newindex = preg_replace("/^CREATE( UNIQUE)? INDEX ([a-z0-9_]+) ON ([a-z0-9_]+) \($field\)$/", "CREATE INDEX \\2_pattern ON \\3 USING btree ($field varchar_pattern_ops)", $sql, -1, $count);
  119. if ($count != 1) {
  120. debugging('Unexpected getCreateIndexSQL() structure.');
  121. continue;
  122. }
  123. $sqls[] = $newindex;
  124. }
  125. }
  126. return $sqls;
  127. }
  128. /**
  129. * Given one XMLDB Type, length and decimals, returns the DB proper SQL type.
  130. *
  131. * @param int $xmldb_type The xmldb_type defined constant. XMLDB_TYPE_INTEGER and other XMLDB_TYPE_* constants.
  132. * @param int $xmldb_length The length of that data type.
  133. * @param int $xmldb_decimals The decimal places of precision of the data type.
  134. * @return string The DB defined data type.
  135. */
  136. public function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null) {
  137. switch ($xmldb_type) {
  138. case XMLDB_TYPE_INTEGER: // From http://www.postgresql.org/docs/7.4/interactive/datatype.html
  139. if (empty($xmldb_length)) {
  140. $xmldb_length = 10;
  141. }
  142. if ($xmldb_length > 9) {
  143. $dbtype = 'BIGINT';
  144. } else if ($xmldb_length > 4) {
  145. $dbtype = 'INTEGER';
  146. } else {
  147. $dbtype = 'SMALLINT';
  148. }
  149. break;
  150. case XMLDB_TYPE_NUMBER:
  151. $dbtype = $this->number_type;
  152. if (!empty($xmldb_length)) {
  153. $dbtype .= '(' . $xmldb_length;
  154. if (!empty($xmldb_decimals)) {
  155. $dbtype .= ',' . $xmldb_decimals;
  156. }
  157. $dbtype .= ')';
  158. }
  159. break;
  160. case XMLDB_TYPE_FLOAT:
  161. $dbtype = 'DOUBLE PRECISION';
  162. if (!empty($xmldb_decimals)) {
  163. if ($xmldb_decimals < 6) {
  164. $dbtype = 'REAL';
  165. }
  166. }
  167. break;
  168. case XMLDB_TYPE_CHAR:
  169. $dbtype = 'VARCHAR';
  170. if (empty($xmldb_length)) {
  171. $xmldb_length='255';
  172. }
  173. $dbtype .= '(' . $xmldb_length . ')';
  174. break;
  175. case XMLDB_TYPE_TEXT:
  176. $dbtype = 'TEXT';
  177. break;
  178. case XMLDB_TYPE_BINARY:
  179. $dbtype = 'BYTEA';
  180. break;
  181. case XMLDB_TYPE_DATETIME:
  182. $dbtype = 'TIMESTAMP';
  183. break;
  184. }
  185. return $dbtype;
  186. }
  187. /**
  188. * Returns the code (array of statements) needed to add one comment to the table.
  189. *
  190. * @param xmldb_table $xmldb_table The xmldb_table object instance.
  191. * @return array Array of SQL statements to add one comment to the table.
  192. */
  193. function getCommentSQL ($xmldb_table) {
  194. $comment = "COMMENT ON TABLE " . $this->getTableName($xmldb_table);
  195. $comment.= " IS '" . $this->addslashes(substr($xmldb_table->getComment(), 0, 250)) . "'";
  196. return array($comment);
  197. }
  198. /**
  199. * Returns the code (array of statements) needed to execute extra statements on table rename.
  200. *
  201. * @param xmldb_table $xmldb_table The xmldb_table object instance.
  202. * @param string $newname The new name for the table.
  203. * @return array Array of extra SQL statements to rename a table.
  204. */
  205. public function getRenameTableExtraSQL($xmldb_table, $newname) {
  206. $results = array();
  207. $newt = new xmldb_table($newname);
  208. $xmldb_field = new xmldb_field('id'); // Fields having sequences should be exclusively, id.
  209. $oldseqname = $this->getTableName($xmldb_table) . '_' . $xmldb_field->getName() . '_seq';
  210. $newseqname = $this->getTableName($newt) . '_' . $xmldb_field->getName() . '_seq';
  211. // Rename de sequence
  212. $results[] = 'ALTER TABLE ' . $oldseqname . ' RENAME TO ' . $newseqname;
  213. return $results;
  214. }
  215. /**
  216. * Given one xmldb_table and one xmldb_field, return the SQL statements needed to alter the field in the table.
  217. *
  218. * PostgreSQL has some severe limits:
  219. * - Any change of type or precision requires a new temporary column to be created, values to
  220. * be transfered potentially casting them, to apply defaults if the column is not null and
  221. * finally, to rename it
  222. * - Changes in null/not null require the SET/DROP NOT NULL clause
  223. * - Changes in default require the SET/DROP DEFAULT clause
  224. *
  225. * @param xmldb_table $xmldb_table The table related to $xmldb_field.
  226. * @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.
  227. * @param string $skip_type_clause The type clause on alter columns, NULL by default.
  228. * @param string $skip_default_clause The default clause on alter columns, NULL by default.
  229. * @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default.
  230. * @return string The field altering SQL statement.
  231. */
  232. public function getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
  233. $results = array(); // To store all the needed SQL commands
  234. // Get the normal names of the table and field
  235. $tablename = $xmldb_table->getName();
  236. $fieldname = $xmldb_field->getName();
  237. // Take a look to field metadata
  238. $meta = $this->mdb->get_columns($tablename);
  239. $metac = $meta[$xmldb_field->getName()];
  240. $oldmetatype = $metac->meta_type;
  241. $oldlength = $metac->max_length;
  242. $olddecimals = empty($metac->scale) ? null : $metac->scale;
  243. $oldnotnull = empty($metac->not_null) ? false : $metac->not_null;
  244. $olddefault = empty($metac->has_default) ? null : $metac->default_value;
  245. $typechanged = true; //By default, assume that the column type has changed
  246. $precisionchanged = true; //By default, assume that the column precision has changed
  247. $decimalchanged = true; //By default, assume that the column decimal has changed
  248. $defaultchanged = true; //By default, assume that the column default has changed
  249. $notnullchanged = true; //By default, assume that the column notnull has changed
  250. // Detect if we are changing the type of the column
  251. if (($xmldb_field->getType() == XMLDB_TYPE_INTEGER && $oldmetatype == 'I') ||
  252. ($xmldb_field->getType() == XMLDB_TYPE_NUMBER && $oldmetatype == 'N') ||
  253. ($xmldb_field->getType() == XMLDB_TYPE_FLOAT && $oldmetatype == 'F') ||
  254. ($xmldb_field->getType() == XMLDB_TYPE_CHAR && $oldmetatype == 'C') ||
  255. ($xmldb_field->getType() == XMLDB_TYPE_TEXT && $oldmetatype == 'X') ||
  256. ($xmldb_field->getType() == XMLDB_TYPE_BINARY && $oldmetatype == 'B')) {
  257. $typechanged = false;
  258. }
  259. // Detect if we are changing the precision
  260. if (($xmldb_field->getType() == XMLDB_TYPE_TEXT) ||
  261. ($xmldb_field->getType() == XMLDB_TYPE_BINARY) ||
  262. ($oldlength == -1) ||
  263. ($xmldb_field->getLength() == $oldlength)) {
  264. $precisionchanged = false;
  265. }
  266. // Detect if we are changing the decimals
  267. if (($xmldb_field->getType() == XMLDB_TYPE_INTEGER) ||
  268. ($xmldb_field->getType() == XMLDB_TYPE_CHAR) ||
  269. ($xmldb_field->getType() == XMLDB_TYPE_TEXT) ||
  270. ($xmldb_field->getType() == XMLDB_TYPE_BINARY) ||
  271. (!$xmldb_field->getDecimals()) ||
  272. (!$olddecimals) ||
  273. ($xmldb_field->getDecimals() == $olddecimals)) {
  274. $decimalchanged = false;
  275. }
  276. // Detect if we are changing the default
  277. if (($xmldb_field->getDefault() === null && $olddefault === null) ||
  278. ($xmldb_field->getDefault() === $olddefault)) {
  279. $defaultchanged = false;
  280. }
  281. // Detect if we are changing the nullability
  282. if (($xmldb_field->getNotnull() === $oldnotnull)) {
  283. $notnullchanged = false;
  284. }
  285. // Get the quoted name of the table and field
  286. $tablename = $this->getTableName($xmldb_table);
  287. $fieldname = $this->getEncQuoted($xmldb_field->getName());
  288. // Decide if we have changed the column specs (type/precision/decimals)
  289. $specschanged = $typechanged || $precisionchanged || $decimalchanged;
  290. // if specs have changed, need to alter column
  291. if ($specschanged) {
  292. // Always drop any exiting default before alter column (some type changes can cause casting error in default for column)
  293. if ($olddefault !== null) {
  294. $results[] = 'ALTER TABLE ' . $tablename . ' ALTER COLUMN ' . $fieldname . ' DROP DEFAULT'; // Drop default clause
  295. }
  296. $alterstmt = 'ALTER TABLE ' . $tablename . ' ALTER COLUMN ' . $this->getEncQuoted($xmldb_field->getName()) .
  297. ' TYPE' . $this->getFieldSQL($xmldb_table, $xmldb_field, null, true, true, null, false);
  298. // Some castings must be performed explicitly (mainly from text|char to numeric|integer)
  299. if (($oldmetatype == 'C' || $oldmetatype == 'X') &&
  300. ($xmldb_field->getType() == XMLDB_TYPE_NUMBER || $xmldb_field->getType() == XMLDB_TYPE_FLOAT)) {
  301. $alterstmt .= ' USING CAST('.$fieldname.' AS NUMERIC)'; // from char or text to number or float
  302. } else if (($oldmetatype == 'C' || $oldmetatype == 'X') &&
  303. $xmldb_field->getType() == XMLDB_TYPE_INTEGER) {
  304. $alterstmt .= ' USING CAST(CAST('.$fieldname.' AS NUMERIC) AS INTEGER)'; // From char to integer
  305. }
  306. $results[] = $alterstmt;
  307. }
  308. // If the default has changed or we have performed one change in specs
  309. if ($defaultchanged || $specschanged) {
  310. $default_clause = $this->getDefaultClause($xmldb_field);
  311. if ($default_clause) {
  312. $sql = 'ALTER TABLE ' . $tablename . ' ALTER COLUMN ' . $fieldname . ' SET' . $default_clause; // Add default clause
  313. $results[] = $sql;
  314. } else {
  315. if (!$specschanged) { // Only drop default if we haven't performed one specs change
  316. $results[] = 'ALTER TABLE ' . $tablename . ' ALTER COLUMN ' . $fieldname . ' DROP DEFAULT'; // Drop default clause
  317. }
  318. }
  319. }
  320. // If the not null has changed
  321. if ($notnullchanged) {
  322. if ($xmldb_field->getNotnull()) {
  323. $results[] = 'ALTER TABLE ' . $tablename . ' ALTER COLUMN ' . $fieldname . ' SET NOT NULL';
  324. } else {
  325. $results[] = 'ALTER TABLE ' . $tablename . ' ALTER COLUMN ' . $fieldname . ' DROP NOT NULL';
  326. }
  327. }
  328. // Return the results
  329. return $results;
  330. }
  331. /**
  332. * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add its default
  333. * (usually invoked from getModifyDefaultSQL()
  334. *
  335. * @param xmldb_table $xmldb_table The xmldb_table object instance.
  336. * @param xmldb_field $xmldb_field The xmldb_field object instance.
  337. * @return array Array of SQL statements to create a field's default.
  338. */
  339. public function getCreateDefaultSQL($xmldb_table, $xmldb_field) {
  340. // Just a wrapper over the getAlterFieldSQL() function for PostgreSQL that
  341. // is capable of handling defaults
  342. return $this->getAlterFieldSQL($xmldb_table, $xmldb_field);
  343. }
  344. /**
  345. * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its default
  346. * (usually invoked from getModifyDefaultSQL()
  347. *
  348. * Note that this method may be dropped in future.
  349. *
  350. * @param xmldb_table $xmldb_table The xmldb_table object instance.
  351. * @param xmldb_field $xmldb_field The xmldb_field object instance.
  352. * @return array Array of SQL statements to create a field's default.
  353. *
  354. * @todo MDL-31147 Moodle 2.1 - Drop getDropDefaultSQL()
  355. */
  356. public function getDropDefaultSQL($xmldb_table, $xmldb_field) {
  357. // Just a wrapper over the getAlterFieldSQL() function for PostgreSQL that
  358. // is capable of handling defaults
  359. return $this->getAlterFieldSQL($xmldb_table, $xmldb_field);
  360. }
  361. /**
  362. * Adds slashes to string.
  363. * @param string $s
  364. * @return string The escaped string.
  365. */
  366. public function addslashes($s) {
  367. // Postgres is gradually switching to ANSI quotes, we need to check what is expected
  368. if (!isset($this->std_strings)) {
  369. $this->std_strings = ($this->mdb->get_field_sql("select setting from pg_settings where name = 'standard_conforming_strings'") === 'on');
  370. }
  371. if ($this->std_strings) {
  372. $s = str_replace("'", "''", $s);
  373. } else {
  374. // do not use php addslashes() because it depends on PHP quote settings!
  375. $s = str_replace('\\','\\\\',$s);
  376. $s = str_replace("\0","\\\0", $s);
  377. $s = str_replace("'", "\\'", $s);
  378. }
  379. return $s;
  380. }
  381. /**
  382. * Given one xmldb_table returns one string with the sequence of the table
  383. * in the table (fetched from DB)
  384. * The sequence name for Postgres has one standard name convention:
  385. * tablename_fieldname_seq
  386. * so we just calculate it and confirm it's present in pg_class
  387. *
  388. * @param xmldb_table $xmldb_table The xmldb_table object instance.
  389. * @return string|bool If no sequence is found, returns false
  390. */
  391. function getSequenceFromDB($xmldb_table) {
  392. $tablename = $this->getTableName($xmldb_table);
  393. $sequencename = $tablename . '_id_seq';
  394. if (!$this->mdb->get_record_sql("SELECT c.*
  395. FROM pg_catalog.pg_class c
  396. JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.relnamespace
  397. WHERE c.relname = ? AND c.relkind = 'S'
  398. AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())",
  399. array($sequencename))) {
  400. $sequencename = false;
  401. }
  402. return $sequencename;
  403. }
  404. /**
  405. * Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg).
  406. *
  407. * (MySQL requires the whole xmldb_table object to be specified, so we add it always)
  408. *
  409. * This is invoked from getNameForObject().
  410. * Only some DB have this implemented.
  411. *
  412. * @param string $object_name The object's name to check for.
  413. * @param string $type The object's type (pk, uk, fk, ck, ix, uix, seq, trg).
  414. * @param string $table_name The table's name to check in
  415. * @return bool If such name is currently in use (true) or no (false)
  416. */
  417. public function isNameInUse($object_name, $type, $table_name) {
  418. switch($type) {
  419. case 'ix':
  420. case 'uix':
  421. case 'seq':
  422. if ($check = $this->mdb->get_records_sql("SELECT c.relname
  423. FROM pg_class c
  424. JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.relnamespace
  425. WHERE lower(c.relname) = ?
  426. AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())", array(strtolower($object_name)))) {
  427. return true;
  428. }
  429. break;
  430. case 'pk':
  431. case 'uk':
  432. case 'fk':
  433. case 'ck':
  434. if ($check = $this->mdb->get_records_sql("SELECT c.conname
  435. FROM pg_constraint c
  436. JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.connamespace
  437. WHERE lower(c.conname) = ?
  438. AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())", array(strtolower($object_name)))) {
  439. return true;
  440. }
  441. break;
  442. case 'trg':
  443. if ($check = $this->mdb->get_records_sql("SELECT tgname
  444. FROM pg_trigger
  445. WHERE lower(tgname) = ?", array(strtolower($object_name)))) {
  446. return true;
  447. }
  448. break;
  449. }
  450. return false; //No name in use found
  451. }
  452. /**
  453. * Returns an array of reserved words (lowercase) for this DB
  454. * @return array An array of database specific reserved words
  455. */
  456. public static function getReservedWords() {
  457. // This file contains the reserved words for PostgreSQL databases
  458. // This file contains the reserved words for PostgreSQL databases
  459. // http://www.postgresql.org/docs/current/static/sql-keywords-appendix.html
  460. $reserved_words = array (
  461. 'all', 'analyse', 'analyze', 'and', 'any', 'array', 'as', 'asc',
  462. 'asymmetric', 'authorization', 'between', 'binary', 'both', 'case',
  463. 'cast', 'check', 'collate', 'column', 'constraint', 'create', 'cross',
  464. 'current_date', 'current_role', 'current_time', 'current_timestamp',
  465. 'current_user', 'default', 'deferrable', 'desc', 'distinct', 'do',
  466. 'else', 'end', 'except', 'false', 'for', 'foreign', 'freeze', 'from',
  467. 'full', 'grant', 'group', 'having', 'ilike', 'in', 'initially', 'inner',
  468. 'intersect', 'into', 'is', 'isnull', 'join', 'leading', 'left', 'like',
  469. 'limit', 'localtime', 'localtimestamp', 'natural', 'new', 'not',
  470. 'notnull', 'null', 'off', 'offset', 'old', 'on', 'only', 'or', 'order',
  471. 'outer', 'overlaps', 'placing', 'primary', 'references', 'returning', 'right', 'select',
  472. 'session_user', 'similar', 'some', 'symmetric', 'table', 'then', 'to',
  473. 'trailing', 'true', 'union', 'unique', 'user', 'using', 'verbose',
  474. 'when', 'where', 'with'
  475. );
  476. return $reserved_words;
  477. }
  478. }