Projet

Général

Profil

Paste
Télécharger (17 ko) Statistiques
| Branche: | Révision:

root / drupal7 / includes / database / mysql / database.inc @ 01dfd3b5

1
<?php
2

    
3
/**
4
 * @file
5
 * Database interface code for MySQL database servers.
6
 */
7

    
8
/**
9
 * The default character for quoting identifiers in MySQL.
10
 */
11
define('MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT', '`');
12

    
13
/**
14
 * @addtogroup database
15
 * @{
16
 */
17

    
18
class DatabaseConnection_mysql extends DatabaseConnection {
19

    
20
  /**
21
   * Flag to indicate if the cleanup function in __destruct() should run.
22
   *
23
   * @var boolean
24
   */
25
  protected $needsCleanup = FALSE;
26

    
27
  /**
28
   * The list of MySQL reserved key words.
29
   *
30
   * @link https://dev.mysql.com/doc/refman/8.0/en/keywords.html
31
   */
32
  private $reservedKeyWords = array(
33
    'accessible',
34
    'add',
35
    'admin',
36
    'all',
37
    'alter',
38
    'analyze',
39
    'and',
40
    'as',
41
    'asc',
42
    'asensitive',
43
    'before',
44
    'between',
45
    'bigint',
46
    'binary',
47
    'blob',
48
    'both',
49
    'by',
50
    'call',
51
    'cascade',
52
    'case',
53
    'change',
54
    'char',
55
    'character',
56
    'check',
57
    'collate',
58
    'column',
59
    'condition',
60
    'constraint',
61
    'continue',
62
    'convert',
63
    'create',
64
    'cross',
65
    'cube',
66
    'cume_dist',
67
    'current_date',
68
    'current_time',
69
    'current_timestamp',
70
    'current_user',
71
    'cursor',
72
    'database',
73
    'databases',
74
    'day_hour',
75
    'day_microsecond',
76
    'day_minute',
77
    'day_second',
78
    'dec',
79
    'decimal',
80
    'declare',
81
    'default',
82
    'delayed',
83
    'delete',
84
    'dense_rank',
85
    'desc',
86
    'describe',
87
    'deterministic',
88
    'distinct',
89
    'distinctrow',
90
    'div',
91
    'double',
92
    'drop',
93
    'dual',
94
    'each',
95
    'else',
96
    'elseif',
97
    'empty',
98
    'enclosed',
99
    'escaped',
100
    'except',
101
    'exists',
102
    'exit',
103
    'explain',
104
    'false',
105
    'fetch',
106
    'first_value',
107
    'float',
108
    'float4',
109
    'float8',
110
    'for',
111
    'force',
112
    'foreign',
113
    'from',
114
    'fulltext',
115
    'function',
116
    'generated',
117
    'get',
118
    'grant',
119
    'group',
120
    'grouping',
121
    'groups',
122
    'having',
123
    'high_priority',
124
    'hour_microsecond',
125
    'hour_minute',
126
    'hour_second',
127
    'if',
128
    'ignore',
129
    'in',
130
    'index',
131
    'infile',
132
    'inner',
133
    'inout',
134
    'insensitive',
135
    'insert',
136
    'int',
137
    'int1',
138
    'int2',
139
    'int3',
140
    'int4',
141
    'int8',
142
    'integer',
143
    'interval',
144
    'into',
145
    'io_after_gtids',
146
    'io_before_gtids',
147
    'is',
148
    'iterate',
149
    'join',
150
    'json_table',
151
    'key',
152
    'keys',
153
    'kill',
154
    'lag',
155
    'last_value',
156
    'lead',
157
    'leading',
158
    'leave',
159
    'left',
160
    'like',
161
    'limit',
162
    'linear',
163
    'lines',
164
    'load',
165
    'localtime',
166
    'localtimestamp',
167
    'lock',
168
    'long',
169
    'longblob',
170
    'longtext',
171
    'loop',
172
    'low_priority',
173
    'master_bind',
174
    'master_ssl_verify_server_cert',
175
    'match',
176
    'maxvalue',
177
    'mediumblob',
178
    'mediumint',
179
    'mediumtext',
180
    'middleint',
181
    'minute_microsecond',
182
    'minute_second',
183
    'mod',
184
    'modifies',
185
    'natural',
186
    'not',
187
    'no_write_to_binlog',
188
    'nth_value',
189
    'ntile',
190
    'null',
191
    'numeric',
192
    'of',
193
    'on',
194
    'optimize',
195
    'optimizer_costs',
196
    'option',
197
    'optionally',
198
    'or',
199
    'order',
200
    'out',
201
    'outer',
202
    'outfile',
203
    'over',
204
    'partition',
205
    'percent_rank',
206
    'persist',
207
    'persist_only',
208
    'precision',
209
    'primary',
210
    'procedure',
211
    'purge',
212
    'range',
213
    'rank',
214
    'read',
215
    'reads',
216
    'read_write',
217
    'real',
218
    'recursive',
219
    'references',
220
    'regexp',
221
    'release',
222
    'rename',
223
    'repeat',
224
    'replace',
225
    'require',
226
    'resignal',
227
    'restrict',
228
    'return',
229
    'revoke',
230
    'right',
231
    'rlike',
232
    'row',
233
    'rows',
234
    'row_number',
235
    'schema',
236
    'schemas',
237
    'second_microsecond',
238
    'select',
239
    'sensitive',
240
    'separator',
241
    'set',
242
    'show',
243
    'signal',
244
    'smallint',
245
    'spatial',
246
    'specific',
247
    'sql',
248
    'sqlexception',
249
    'sqlstate',
250
    'sqlwarning',
251
    'sql_big_result',
252
    'sql_calc_found_rows',
253
    'sql_small_result',
254
    'ssl',
255
    'starting',
256
    'stored',
257
    'straight_join',
258
    'system',
259
    'table',
260
    'terminated',
261
    'then',
262
    'tinyblob',
263
    'tinyint',
264
    'tinytext',
265
    'to',
266
    'trailing',
267
    'trigger',
268
    'true',
269
    'undo',
270
    'union',
271
    'unique',
272
    'unlock',
273
    'unsigned',
274
    'update',
275
    'usage',
276
    'use',
277
    'using',
278
    'utc_date',
279
    'utc_time',
280
    'utc_timestamp',
281
    'values',
282
    'varbinary',
283
    'varchar',
284
    'varcharacter',
285
    'varying',
286
    'virtual',
287
    'when',
288
    'where',
289
    'while',
290
    'window',
291
    'with',
292
    'write',
293
    'xor',
294
    'year_month',
295
    'zerofill',
296
  );
297

    
298
  public function __construct(array $connection_options = array()) {
299
    // This driver defaults to transaction support, except if explicitly passed FALSE.
300
    $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
301

    
302
    // MySQL never supports transactional DDL.
303
    $this->transactionalDDLSupport = FALSE;
304

    
305
    $this->connectionOptions = $connection_options;
306

    
307
    $charset = 'utf8';
308
    // Check if the charset is overridden to utf8mb4 in settings.php.
309
    if ($this->utf8mb4IsActive()) {
310
      $charset = 'utf8mb4';
311
    }
312

    
313
    // The DSN should use either a socket or a host/port.
314
    if (isset($connection_options['unix_socket'])) {
315
      $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
316
    }
317
    else {
318
      // Default to TCP connection on port 3306.
319
      $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
320
    }
321
    // Character set is added to dsn to ensure PDO uses the proper character
322
    // set when escaping. This has security implications. See
323
    // https://www.drupal.org/node/1201452 for further discussion.
324
    $dsn .= ';charset=' . $charset;
325
    $dsn .= ';dbname=' . $connection_options['database'];
326
    // Allow PDO options to be overridden.
327
    $connection_options += array(
328
      'pdo' => array(),
329
    );
330
    $connection_options['pdo'] += array(
331
      // So we don't have to mess around with cursors and unbuffered queries by default.
332
      PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
333
      // Because MySQL's prepared statements skip the query cache, because it's dumb.
334
      PDO::ATTR_EMULATE_PREPARES => TRUE,
335
    );
336
    if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
337
      // An added connection option in PHP 5.5.21+ to optionally limit SQL to a
338
      // single statement like mysqli.
339
      $connection_options['pdo'] += array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => FALSE);
340
    }
341

    
342
    parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
343

    
344
    // Force MySQL to use the UTF-8 character set. Also set the collation, if a
345
    // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
346
    // for UTF-8.
347
    if (!empty($connection_options['collation'])) {
348
      $this->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
349
    }
350
    else {
351
      $this->exec('SET NAMES ' . $charset);
352
    }
353

    
354
    // Set MySQL init_commands if not already defined.  Default Drupal's MySQL
355
    // behavior to conform more closely to SQL standards.  This allows Drupal
356
    // to run almost seamlessly on many different kinds of database systems.
357
    // These settings force MySQL to behave the same as postgresql, or sqlite
358
    // in regards to syntax interpretation and invalid data handling.  See
359
    // http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5
360
    // changed the meaning of TRADITIONAL we need to spell out the modes one by
361
    // one.
362
    $connection_options += array(
363
      'init_commands' => array(),
364
    );
365

    
366
    $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';
367
    // NO_AUTO_CREATE_USER was removed in MySQL 8.0.11
368
    // https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-11.html#mysqld-8-0-11-deprecation-removal
369
    if (version_compare($this->getAttribute(PDO::ATTR_SERVER_VERSION), '8.0.11', '<')) {
370
      $sql_mode .= ',NO_AUTO_CREATE_USER';
371
    }
372
    $connection_options['init_commands'] += array(
373
      'sql_mode' => "SET sql_mode = '$sql_mode'",
374
    );
375

    
376
    // Execute initial commands.
377
    foreach ($connection_options['init_commands'] as $sql) {
378
      $this->exec($sql);
379
    }
380
  }
381

    
382
  /**
383
   * {@inheritdoc}}
384
   */
385
  protected function setPrefix($prefix) {
386
    parent::setPrefix($prefix);
387
    // Successive versions of MySQL have become increasingly strict about the
388
    // use of reserved keywords as table names. Drupal 7 uses at least one such
389
    // table (system). Therefore we surround all table names with quotes.
390
    $quote_char = variable_get('mysql_identifier_quote_character', MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT);
391
    foreach ($this->prefixSearch as $i => $prefixSearch) {
392
      if (substr($prefixSearch, 0, 1) === '{') {
393
        // If the prefix already contains one or more quotes remove them.
394
        // This can happen when - for example - DrupalUnitTestCase sets up a
395
        // "temporary prefixed database". Also if there's a dot in the prefix,
396
        // wrap it in quotes to cater for schema names in prefixes.
397
        $search = array($quote_char, '.');
398
        $replace = array('', $quote_char . '.' . $quote_char);
399
        $this->prefixReplace[$i] = $quote_char . str_replace($search, $replace, $this->prefixReplace[$i]);
400
      }
401
      if (substr($prefixSearch, -1) === '}') {
402
        $this->prefixReplace[$i] .= $quote_char;
403
      }
404
    }
405
  }
406

    
407
  /**
408
   * {@inheritdoc}
409
   */
410
  public function escapeField($field) {
411
    $field = parent::escapeField($field);
412
    return $this->quoteIdentifier($field);
413
  }
414

    
415
  public function escapeFields(array $fields) {
416
    foreach ($fields as &$field) {
417
      $field = $this->escapeField($field);
418
    }
419
    return $fields;
420
  }
421

    
422
  /**
423
   * {@inheritdoc}
424
   */
425
  public function escapeAlias($field) {
426
    $field = parent::escapeAlias($field);
427
    return $this->quoteIdentifier($field);
428
  }
429

    
430
  /**
431
   * Quotes an identifier if it matches a MySQL reserved keyword.
432
   *
433
   * @param string $identifier
434
   *   The field to check.
435
   *
436
   * @return string
437
   *   The identifier, quoted if it matches a MySQL reserved keyword.
438
   */
439
  private function quoteIdentifier($identifier) {
440
    // Quote identifiers so that MySQL reserved words like 'function' can be
441
    // used as column names. Sometimes the 'table.column_name' format is passed
442
    // in. For example, menu_load_links() adds a condition on "ml.menu_name".
443
    if (strpos($identifier, '.') !== FALSE) {
444
      list($table, $identifier) = explode('.', $identifier, 2);
445
    }
446
    if (in_array(strtolower($identifier), $this->reservedKeyWords, TRUE)) {
447
      // Quote the string for MySQL reserved keywords.
448
      $quote_char = variable_get('mysql_identifier_quote_character', MYSQL_IDENTIFIER_QUOTE_CHARACTER_DEFAULT);
449
      $identifier = $quote_char . $identifier . $quote_char;
450
    }
451
    return isset($table) ? $table . '.' . $identifier : $identifier;
452
  }
453

    
454
  public function __destruct() {
455
    if ($this->needsCleanup) {
456
      $this->nextIdDelete();
457
    }
458
  }
459

    
460
  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
461
    return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
462
  }
463

    
464
  public function queryTemporary($query, array $args = array(), array $options = array()) {
465
    $tablename = $this->generateTemporaryTableName();
466
    $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY ' . $query, $args, $options);
467
    return $tablename;
468
  }
469

    
470
  public function driver() {
471
    return 'mysql';
472
  }
473

    
474
  public function databaseType() {
475
    return 'mysql';
476
  }
477

    
478
  public function mapConditionOperator($operator) {
479
    // We don't want to override any of the defaults.
480
    return NULL;
481
  }
482

    
483
  public function nextId($existing_id = 0) {
484
    $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
485
    // This should only happen after an import or similar event.
486
    if ($existing_id >= $new_id) {
487
      // If we INSERT a value manually into the sequences table, on the next
488
      // INSERT, MySQL will generate a larger value. However, there is no way
489
      // of knowing whether this value already exists in the table. MySQL
490
      // provides an INSERT IGNORE which would work, but that can mask problems
491
      // other than duplicate keys. Instead, we use INSERT ... ON DUPLICATE KEY
492
      // UPDATE in such a way that the UPDATE does not do anything. This way,
493
      // duplicate keys do not generate errors but everything else does.
494
      $this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id));
495
      $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
496
    }
497
    $this->needsCleanup = TRUE;
498
    return $new_id;
499
  }
500

    
501
  public function nextIdDelete() {
502
    // While we want to clean up the table to keep it up from occupying too
503
    // much storage and memory, we must keep the highest value in the table
504
    // because InnoDB  uses an in-memory auto-increment counter as long as the
505
    // server runs. When the server is stopped and restarted, InnoDB
506
    // reinitializes the counter for each table for the first INSERT to the
507
    // table based solely on values from the table so deleting all values would
508
    // be a problem in this case. Also, TRUNCATE resets the auto increment
509
    // counter.
510
    try {
511
      $max_id = $this->query('SELECT MAX(value) FROM {sequences}')->fetchField();
512
      // We know we are using MySQL here, no need for the slower db_delete().
513
      $this->query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id));
514
    }
515
    // During testing, this function is called from shutdown with the
516
    // simpletest prefix stored in $this->connection, and those tables are gone
517
    // by the time shutdown is called so we need to ignore the database
518
    // errors. There is no problem with completely ignoring errors here: if
519
    // these queries fail, the sequence will work just fine, just use a bit
520
    // more database storage and memory.
521
    catch (PDOException $e) {
522
    }
523
  }
524

    
525
  /**
526
   * Overridden to work around issues to MySQL not supporting transactional DDL.
527
   */
528
  protected function popCommittableTransactions() {
529
    // Commit all the committable layers.
530
    foreach (array_reverse($this->transactionLayers) as $name => $active) {
531
      // Stop once we found an active transaction.
532
      if ($active) {
533
        break;
534
      }
535

    
536
      // If there are no more layers left then we should commit.
537
      unset($this->transactionLayers[$name]);
538
      if (empty($this->transactionLayers)) {
539
        if (!PDO::commit()) {
540
          throw new DatabaseTransactionCommitFailedException();
541
        }
542
      }
543
      else {
544
        // Attempt to release this savepoint in the standard way.
545
        try {
546
          $this->query('RELEASE SAVEPOINT ' . $name);
547
        }
548
        catch (PDOException $e) {
549
          // However, in MySQL (InnoDB), savepoints are automatically committed
550
          // when tables are altered or created (DDL transactions are not
551
          // supported). This can cause exceptions due to trying to release
552
          // savepoints which no longer exist.
553
          //
554
          // To avoid exceptions when no actual error has occurred, we silently
555
          // succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
556
          if ($e->errorInfo[1] == '1305') {
557
            // If one SAVEPOINT was released automatically, then all were.
558
            // Therefore, clean the transaction stack.
559
            $this->transactionLayers = array();
560
            // We also have to explain to PDO that the transaction stack has
561
            // been cleaned-up.
562
            PDO::commit();
563
          }
564
          else {
565
            throw $e;
566
          }
567
        }
568
      }
569
    }
570
  }
571

    
572
  public function utf8mb4IsConfigurable() {
573
    return TRUE;
574
  }
575

    
576
  public function utf8mb4IsActive() {
577
    return isset($this->connectionOptions['charset']) && $this->connectionOptions['charset'] === 'utf8mb4';
578
  }
579

    
580
  public function utf8mb4IsSupported() {
581
    // Ensure that the MySQL driver supports utf8mb4 encoding.
582
    $version = $this->getAttribute(PDO::ATTR_CLIENT_VERSION);
583
    if (strpos($version, 'mysqlnd') !== FALSE) {
584
      // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
585
      $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
586
      if (version_compare($version, '5.0.9', '<')) {
587
        return FALSE;
588
      }
589
    }
590
    else {
591
      // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
592
      if (version_compare($version, '5.5.3', '<')) {
593
        return FALSE;
594
      }
595
    }
596

    
597
    // Ensure that the MySQL server supports large prefixes and utf8mb4.
598
    try {
599
      $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");
600
    }
601
    catch (Exception $e) {
602
      return FALSE;
603
    }
604
    $this->query("DROP TABLE {drupal_utf8mb4_test}");
605
    return TRUE;
606
  }
607
}
608

    
609

    
610
/**
611
 * @} End of "addtogroup database".
612
 */