database.inc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. <?php
  2. /**
  3. * @file
  4. * Database interface code for MySQL database servers.
  5. */
  6. /**
  7. * The default character for quoting identifiers in MySQL.
  8. */
  9. define('MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT', '`');
  10. /**
  11. * @addtogroup database
  12. * @{
  13. */
  14. class DatabaseConnection_mysql extends DatabaseConnection {
  15. /**
  16. * Flag to indicate if the cleanup function in __destruct() should run.
  17. *
  18. * @var boolean
  19. */
  20. protected $needsCleanup = FALSE;
  21. /**
  22. * The list of MySQL reserved key words.
  23. *
  24. * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html
  25. */
  26. private $reservedKeyWords = array(
  27. 'accessible',
  28. 'add',
  29. 'admin',
  30. 'all',
  31. 'alter',
  32. 'analyze',
  33. 'and',
  34. 'as',
  35. 'asc',
  36. 'asensitive',
  37. 'before',
  38. 'between',
  39. 'bigint',
  40. 'binary',
  41. 'blob',
  42. 'both',
  43. 'by',
  44. 'call',
  45. 'cascade',
  46. 'case',
  47. 'change',
  48. 'char',
  49. 'character',
  50. 'check',
  51. 'collate',
  52. 'column',
  53. 'condition',
  54. 'constraint',
  55. 'continue',
  56. 'convert',
  57. 'create',
  58. 'cross',
  59. 'cube',
  60. 'cume_dist',
  61. 'current_date',
  62. 'current_time',
  63. 'current_timestamp',
  64. 'current_user',
  65. 'cursor',
  66. 'database',
  67. 'databases',
  68. 'day_hour',
  69. 'day_microsecond',
  70. 'day_minute',
  71. 'day_second',
  72. 'dec',
  73. 'decimal',
  74. 'declare',
  75. 'default',
  76. 'delayed',
  77. 'delete',
  78. 'dense_rank',
  79. 'desc',
  80. 'describe',
  81. 'deterministic',
  82. 'distinct',
  83. 'distinctrow',
  84. 'div',
  85. 'double',
  86. 'drop',
  87. 'dual',
  88. 'each',
  89. 'else',
  90. 'elseif',
  91. 'empty',
  92. 'enclosed',
  93. 'escaped',
  94. 'except',
  95. 'exists',
  96. 'exit',
  97. 'explain',
  98. 'false',
  99. 'fetch',
  100. 'first_value',
  101. 'float',
  102. 'float4',
  103. 'float8',
  104. 'for',
  105. 'force',
  106. 'foreign',
  107. 'from',
  108. 'fulltext',
  109. 'function',
  110. 'generated',
  111. 'get',
  112. 'grant',
  113. 'group',
  114. 'grouping',
  115. 'groups',
  116. 'having',
  117. 'high_priority',
  118. 'hour_microsecond',
  119. 'hour_minute',
  120. 'hour_second',
  121. 'if',
  122. 'ignore',
  123. 'in',
  124. 'index',
  125. 'infile',
  126. 'inner',
  127. 'inout',
  128. 'insensitive',
  129. 'insert',
  130. 'int',
  131. 'int1',
  132. 'int2',
  133. 'int3',
  134. 'int4',
  135. 'int8',
  136. 'integer',
  137. 'interval',
  138. 'into',
  139. 'io_after_gtids',
  140. 'io_before_gtids',
  141. 'is',
  142. 'iterate',
  143. 'join',
  144. 'json_table',
  145. 'key',
  146. 'keys',
  147. 'kill',
  148. 'lag',
  149. 'last_value',
  150. 'lead',
  151. 'leading',
  152. 'leave',
  153. 'left',
  154. 'like',
  155. 'limit',
  156. 'linear',
  157. 'lines',
  158. 'load',
  159. 'localtime',
  160. 'localtimestamp',
  161. 'lock',
  162. 'long',
  163. 'longblob',
  164. 'longtext',
  165. 'loop',
  166. 'low_priority',
  167. 'master_bind',
  168. 'master_ssl_verify_server_cert',
  169. 'match',
  170. 'maxvalue',
  171. 'mediumblob',
  172. 'mediumint',
  173. 'mediumtext',
  174. 'middleint',
  175. 'minute_microsecond',
  176. 'minute_second',
  177. 'mod',
  178. 'modifies',
  179. 'natural',
  180. 'not',
  181. 'no_write_to_binlog',
  182. 'nth_value',
  183. 'ntile',
  184. 'null',
  185. 'numeric',
  186. 'of',
  187. 'on',
  188. 'optimize',
  189. 'optimizer_costs',
  190. 'option',
  191. 'optionally',
  192. 'or',
  193. 'order',
  194. 'out',
  195. 'outer',
  196. 'outfile',
  197. 'over',
  198. 'partition',
  199. 'percent_rank',
  200. 'persist',
  201. 'persist_only',
  202. 'precision',
  203. 'primary',
  204. 'procedure',
  205. 'purge',
  206. 'range',
  207. 'rank',
  208. 'read',
  209. 'reads',
  210. 'read_write',
  211. 'real',
  212. 'recursive',
  213. 'references',
  214. 'regexp',
  215. 'release',
  216. 'rename',
  217. 'repeat',
  218. 'replace',
  219. 'require',
  220. 'resignal',
  221. 'restrict',
  222. 'return',
  223. 'revoke',
  224. 'right',
  225. 'rlike',
  226. 'row',
  227. 'rows',
  228. 'row_number',
  229. 'schema',
  230. 'schemas',
  231. 'second_microsecond',
  232. 'select',
  233. 'sensitive',
  234. 'separator',
  235. 'set',
  236. 'show',
  237. 'signal',
  238. 'smallint',
  239. 'spatial',
  240. 'specific',
  241. 'sql',
  242. 'sqlexception',
  243. 'sqlstate',
  244. 'sqlwarning',
  245. 'sql_big_result',
  246. 'sql_calc_found_rows',
  247. 'sql_small_result',
  248. 'ssl',
  249. 'starting',
  250. 'stored',
  251. 'straight_join',
  252. 'system',
  253. 'table',
  254. 'terminated',
  255. 'then',
  256. 'tinyblob',
  257. 'tinyint',
  258. 'tinytext',
  259. 'to',
  260. 'trailing',
  261. 'trigger',
  262. 'true',
  263. 'undo',
  264. 'union',
  265. 'unique',
  266. 'unlock',
  267. 'unsigned',
  268. 'update',
  269. 'usage',
  270. 'use',
  271. 'using',
  272. 'utc_date',
  273. 'utc_time',
  274. 'utc_timestamp',
  275. 'values',
  276. 'varbinary',
  277. 'varchar',
  278. 'varcharacter',
  279. 'varying',
  280. 'virtual',
  281. 'when',
  282. 'where',
  283. 'while',
  284. 'window',
  285. 'with',
  286. 'write',
  287. 'xor',
  288. 'year_month',
  289. 'zerofill',
  290. );
  291. public function __construct(array $connection_options = array()) {
  292. // This driver defaults to transaction support, except if explicitly passed FALSE.
  293. $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
  294. // MySQL never supports transactional DDL.
  295. $this->transactionalDDLSupport = FALSE;
  296. $this->connectionOptions = $connection_options;
  297. $charset = 'utf8';
  298. // Check if the charset is overridden to utf8mb4 in settings.php.
  299. if ($this->utf8mb4IsActive()) {
  300. $charset = 'utf8mb4';
  301. }
  302. // The DSN should use either a socket or a host/port.
  303. if (isset($connection_options['unix_socket'])) {
  304. $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
  305. }
  306. else {
  307. // Default to TCP connection on port 3306.
  308. $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
  309. }
  310. // Character set is added to dsn to ensure PDO uses the proper character
  311. // set when escaping. This has security implications. See
  312. // https://www.drupal.org/node/1201452 for further discussion.
  313. $dsn .= ';charset=' . $charset;
  314. $dsn .= ';dbname=' . $connection_options['database'];
  315. // Allow PDO options to be overridden.
  316. $connection_options += array(
  317. 'pdo' => array(),
  318. );
  319. $connection_options['pdo'] += array(
  320. // So we don't have to mess around with cursors and unbuffered queries by default.
  321. PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
  322. // Because MySQL's prepared statements skip the query cache, because it's dumb.
  323. PDO::ATTR_EMULATE_PREPARES => TRUE,
  324. );
  325. if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
  326. // An added connection option in PHP 5.5.21+ to optionally limit SQL to a
  327. // single statement like mysqli.
  328. $connection_options['pdo'] += array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => FALSE);
  329. }
  330. parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
  331. // Force MySQL to use the UTF-8 character set. Also set the collation, if a
  332. // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
  333. // for UTF-8.
  334. if (!empty($connection_options['collation'])) {
  335. $this->connection->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
  336. }
  337. else {
  338. $this->connection->exec('SET NAMES ' . $charset);
  339. }
  340. // Set MySQL init_commands if not already defined. Default Drupal's MySQL
  341. // behavior to conform more closely to SQL standards. This allows Drupal
  342. // to run almost seamlessly on many different kinds of database systems.
  343. // These settings force MySQL to behave the same as postgresql, or sqlite
  344. // in regards to syntax interpretation and invalid data handling. See
  345. // http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5
  346. // changed the meaning of TRADITIONAL we need to spell out the modes one by
  347. // one.
  348. $connection_options += array(
  349. 'init_commands' => array(),
  350. );
  351. $sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO';
  352. // NO_AUTO_CREATE_USER was removed in MySQL 8.0.11
  353. // https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-11.html#mysqld-8-0-11-deprecation-removal
  354. if (version_compare($this->connection->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '<')) {
  355. $sql_mode .= ',NO_AUTO_CREATE_USER';
  356. }
  357. $connection_options['init_commands'] += array(
  358. 'sql_mode' => "SET sql_mode = '$sql_mode'",
  359. );
  360. // Execute initial commands.
  361. foreach ($connection_options['init_commands'] as $sql) {
  362. $this->connection->exec($sql);
  363. }
  364. }
  365. /**
  366. * {@inheritdoc}}
  367. */
  368. protected function setPrefix($prefix) {
  369. parent::setPrefix($prefix);
  370. // Successive versions of MySQL have become increasingly strict about the
  371. // use of reserved keywords as table names. Drupal 7 uses at least one such
  372. // table (system). Therefore we surround all table names with quotes.
  373. $quote_char = variable_get('mysql_identifier_quote_character', MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT);
  374. foreach ($this->prefixSearch as $i => $prefixSearch) {
  375. if (substr($prefixSearch, 0, 1) === '{') {
  376. // If the prefix already contains one or more quotes remove them.
  377. // This can happen when - for example - DrupalUnitTestCase sets up a
  378. // "temporary prefixed database". Also if there's a dot in the prefix,
  379. // wrap it in quotes to cater for schema names in prefixes.
  380. $search = array($quote_char, '.');
  381. $replace = array('', $quote_char . '.' . $quote_char);
  382. $this->prefixReplace[$i] = $quote_char . str_replace($search, $replace, $this->prefixReplace[$i]);
  383. }
  384. if (substr($prefixSearch, -1) === '}') {
  385. $this->prefixReplace[$i] .= $quote_char;
  386. }
  387. }
  388. }
  389. /**
  390. * {@inheritdoc}
  391. */
  392. public function escapeField($field) {
  393. $field = parent::escapeField($field);
  394. return $this->quoteIdentifier($field);
  395. }
  396. public function escapeFields(array $fields) {
  397. foreach ($fields as &$field) {
  398. $field = $this->escapeField($field);
  399. }
  400. return $fields;
  401. }
  402. /**
  403. * {@inheritdoc}
  404. */
  405. public function escapeAlias($field) {
  406. $field = parent::escapeAlias($field);
  407. return $this->quoteIdentifier($field);
  408. }
  409. /**
  410. * Quotes an identifier if it matches a MySQL reserved keyword.
  411. *
  412. * @param string $identifier
  413. * The field to check.
  414. *
  415. * @return string
  416. * The identifier, quoted if it matches a MySQL reserved keyword.
  417. */
  418. private function quoteIdentifier($identifier) {
  419. // Quote identifiers so that MySQL reserved words like 'function' can be
  420. // used as column names. Sometimes the 'table.column_name' format is passed
  421. // in. For example, menu_load_links() adds a condition on "ml.menu_name".
  422. if (strpos($identifier, '.') !== FALSE) {
  423. list($table, $identifier) = explode('.', $identifier, 2);
  424. }
  425. if (in_array(strtolower($identifier), $this->reservedKeyWords, TRUE)) {
  426. // Quote the string for MySQL reserved keywords.
  427. $quote_char = variable_get('mysql_identifier_quote_character', MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT);
  428. $identifier = $quote_char . $identifier . $quote_char;
  429. }
  430. return isset($table) ? $table . '.' . $identifier : $identifier;
  431. }
  432. public function __destruct() {
  433. if ($this->needsCleanup) {
  434. $this->nextIdDelete();
  435. }
  436. }
  437. public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
  438. return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
  439. }
  440. public function queryTemporary($query, array $args = array(), array $options = array()) {
  441. $tablename = $this->generateTemporaryTableName();
  442. $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY ' . $query, $args, $options);
  443. return $tablename;
  444. }
  445. public function driver() {
  446. return 'mysql';
  447. }
  448. public function databaseType() {
  449. return 'mysql';
  450. }
  451. public function mapConditionOperator($operator) {
  452. // We don't want to override any of the defaults.
  453. return NULL;
  454. }
  455. public function nextId($existing_id = 0) {
  456. $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
  457. // This should only happen after an import or similar event.
  458. if ($existing_id >= $new_id) {
  459. // If we INSERT a value manually into the sequences table, on the next
  460. // INSERT, MySQL will generate a larger value. However, there is no way
  461. // of knowing whether this value already exists in the table. MySQL
  462. // provides an INSERT IGNORE which would work, but that can mask problems
  463. // other than duplicate keys. Instead, we use INSERT ... ON DUPLICATE KEY
  464. // UPDATE in such a way that the UPDATE does not do anything. This way,
  465. // duplicate keys do not generate errors but everything else does.
  466. $this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id));
  467. $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
  468. }
  469. $this->needsCleanup = TRUE;
  470. return $new_id;
  471. }
  472. public function nextIdDelete() {
  473. // While we want to clean up the table to keep it up from occupying too
  474. // much storage and memory, we must keep the highest value in the table
  475. // because InnoDB uses an in-memory auto-increment counter as long as the
  476. // server runs. When the server is stopped and restarted, InnoDB
  477. // reinitializes the counter for each table for the first INSERT to the
  478. // table based solely on values from the table so deleting all values would
  479. // be a problem in this case. Also, TRUNCATE resets the auto increment
  480. // counter.
  481. try {
  482. $max_id = $this->query('SELECT MAX(value) FROM {sequences}')->fetchField();
  483. // We know we are using MySQL here, no need for the slower db_delete().
  484. $this->query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id));
  485. }
  486. // During testing, this function is called from shutdown with the
  487. // simpletest prefix stored in $this->connection, and those tables are gone
  488. // by the time shutdown is called so we need to ignore the database
  489. // errors. There is no problem with completely ignoring errors here: if
  490. // these queries fail, the sequence will work just fine, just use a bit
  491. // more database storage and memory.
  492. catch (PDOException $e) {
  493. }
  494. }
  495. /**
  496. * Overridden to work around issues to MySQL not supporting transactional DDL.
  497. */
  498. protected function popCommittableTransactions() {
  499. // Commit all the committable layers.
  500. foreach (array_reverse($this->transactionLayers) as $name => $active) {
  501. // Stop once we found an active transaction.
  502. if ($active) {
  503. break;
  504. }
  505. // If there are no more layers left then we should commit.
  506. unset($this->transactionLayers[$name]);
  507. if (empty($this->transactionLayers)) {
  508. if (!$this->doCommit()) {
  509. throw new DatabaseTransactionCommitFailedException();
  510. }
  511. }
  512. else {
  513. // Attempt to release this savepoint in the standard way.
  514. try {
  515. $this->query('RELEASE SAVEPOINT ' . $name);
  516. }
  517. catch (PDOException $e) {
  518. // However, in MySQL (InnoDB), savepoints are automatically committed
  519. // when tables are altered or created (DDL transactions are not
  520. // supported). This can cause exceptions due to trying to release
  521. // savepoints which no longer exist.
  522. //
  523. // To avoid exceptions when no actual error has occurred, we silently
  524. // succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
  525. if ($e->errorInfo[1] == '1305') {
  526. // If one SAVEPOINT was released automatically, then all were.
  527. // Therefore, clean the transaction stack.
  528. $this->transactionLayers = array();
  529. // We also have to explain to PDO that the transaction stack has
  530. // been cleaned-up.
  531. $this->doCommit();
  532. }
  533. else {
  534. throw $e;
  535. }
  536. }
  537. }
  538. }
  539. }
  540. /**
  541. * Do the actual commit, including a workaround for PHP 8 behaviour changes.
  542. *
  543. * @return bool
  544. * Success or otherwise of the commit.
  545. */
  546. protected function doCommit() {
  547. if ($this->connection->inTransaction()) {
  548. return $this->connection->commit();
  549. }
  550. else {
  551. // In PHP 8.0 a PDOException is thrown when a commit is attempted with no
  552. // transaction active. In previous PHP versions this failed silently.
  553. return TRUE;
  554. }
  555. }
  556. /**
  557. * {@inheritdoc}
  558. */
  559. public function rollback($savepoint_name = 'drupal_transaction') {
  560. // MySQL will automatically commit transactions when tables are altered or
  561. // created (DDL transactions are not supported). Prevent triggering an
  562. // exception to ensure that the error that has caused the rollback is
  563. // properly reported.
  564. if (!$this->connection->inTransaction()) {
  565. // Before PHP 8 $this->connection->inTransaction() will return TRUE and
  566. // $this->connection->rollback() does not throw an exception; the
  567. // following code is unreachable.
  568. // If \DatabaseConnection::rollback() would throw an
  569. // exception then continue to throw an exception.
  570. if (!$this->inTransaction()) {
  571. throw new DatabaseTransactionNoActiveException();
  572. }
  573. // A previous rollback to an earlier savepoint may mean that the savepoint
  574. // in question has already been accidentally committed.
  575. if (!isset($this->transactionLayers[$savepoint_name])) {
  576. throw new DatabaseTransactionNoActiveException();
  577. }
  578. trigger_error('Rollback attempted when there is no active transaction. This can cause data integrity issues.', E_USER_WARNING);
  579. return;
  580. }
  581. return parent::rollback($savepoint_name);
  582. }
  583. public function utf8mb4IsConfigurable() {
  584. return TRUE;
  585. }
  586. public function utf8mb4IsActive() {
  587. return isset($this->connectionOptions['charset']) && $this->connectionOptions['charset'] === 'utf8mb4';
  588. }
  589. public function utf8mb4IsSupported() {
  590. // Ensure that the MySQL driver supports utf8mb4 encoding.
  591. $version = $this->connection->getAttribute(PDO::ATTR_CLIENT_VERSION);
  592. if (strpos($version, 'mysqlnd') !== FALSE) {
  593. // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
  594. $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
  595. if (version_compare($version, '5.0.9', '<')) {
  596. return FALSE;
  597. }
  598. }
  599. else {
  600. // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
  601. if (version_compare($version, '5.5.3', '<')) {
  602. return FALSE;
  603. }
  604. }
  605. // Ensure that the MySQL server supports large prefixes and utf8mb4.
  606. try {
  607. $this->query("CREATE TABLE {drupal_utf8mb4_test} (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB");
  608. }
  609. catch (Exception $e) {
  610. return FALSE;
  611. }
  612. $this->query("DROP TABLE {drupal_utf8mb4_test}");
  613. return TRUE;
  614. }
  615. }
  616. /**
  617. * @} End of "addtogroup database".
  618. */