MediaWiki  master
DatabaseMysqlBase.php
Go to the documentation of this file.
1 <?php
20 namespace Wikimedia\Rdbms;
21 
22 use RuntimeException;
26 
41 abstract class DatabaseMysqlBase extends Database {
43  protected $sslKeyPath;
45  protected $sslCertPath;
47  protected $sslCAFile;
49  protected $sslCAPath;
55  protected $sslCiphers;
57  protected $utf8Mode;
59  protected $defaultBigSelects;
60 
62  protected $platform;
63 
66 
86  public function __construct( array $params ) {
87  foreach ( [ 'KeyPath', 'CertPath', 'CAFile', 'CAPath', 'Ciphers' ] as $name ) {
88  $var = "ssl{$name}";
89  if ( isset( $params[$var] ) ) {
90  $this->$var = $params[$var];
91  }
92  }
93  $this->utf8Mode = !empty( $params['utf8Mode'] );
94  parent::__construct( $params );
95  $this->platform = new MySQLPlatform(
96  $this,
97  $this->logger,
98  $this->currentDomain,
99  $this->errorLogger
100  );
101  $this->replicationReporter = new MysqlReplicationReporter(
102  $params['topologyRole'],
103  $this->logger,
104  $params['srvCache'],
105  $params['lagDetectionMethod'] ?? 'Seconds_Behind_Master',
106  $params['lagDetectionOptions'] ?? [],
107  !empty( $params['useGTIDs' ] )
108  );
109  }
110 
114  public function getType() {
115  return 'mysql';
116  }
117 
118  protected function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
119  $this->close( __METHOD__ );
120 
121  if ( $schema !== null ) {
122  throw $this->newExceptionAfterConnectError( "Got schema '$schema'; not supported." );
123  }
124 
125  $this->installErrorHandler();
126  try {
127  $this->conn = $this->mysqlConnect( $server, $user, $password, $db );
128  } catch ( RuntimeException $e ) {
129  $this->restoreErrorHandler();
130  throw $this->newExceptionAfterConnectError( $e->getMessage() );
131  }
132  $error = $this->restoreErrorHandler();
133 
134  if ( !$this->conn ) {
135  throw $this->newExceptionAfterConnectError( $error ?: $this->lastError() );
136  }
137 
138  try {
139  $this->currentDomain = new DatabaseDomain(
140  $db && strlen( $db ) ? $db : null,
141  null,
142  $tablePrefix
143  );
144  $this->platform->setPrefix( $tablePrefix );
145  // Abstract over any excessive MySQL defaults
146  $set = [ 'group_concat_max_len = 262144' ];
147  // Set any custom settings defined by site config
148  // https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html
149  foreach ( $this->connectionVariables as $var => $val ) {
150  // Escape strings but not numbers to avoid MySQL complaining
151  if ( !is_int( $val ) && !is_float( $val ) ) {
152  $val = $this->addQuotes( $val );
153  }
154  $set[] = $this->platform->addIdentifierQuotes( $var ) . ' = ' . $val;
155  }
156 
157  // @phan-suppress-next-next-line PhanRedundantCondition
158  // Safety check to avoid empty SET query
159  if ( $set ) {
160  $sql = 'SET ' . implode( ', ', $set );
161  $flags = self::QUERY_NO_RETRY | self::QUERY_CHANGE_TRX;
162  // Avoid using query() so that replaceLostConnection() does not throw
163  // errors if the transaction status is STATUS_TRX_ERROR
164  $qs = $this->executeQuery( $sql, __METHOD__, $flags, $sql );
165  if ( $qs->res === false ) {
166  $this->reportQueryError( $qs->message, $qs->code, $sql, __METHOD__ );
167  }
168  }
169  } catch ( RuntimeException $e ) {
170  throw $this->newExceptionAfterConnectError( $e->getMessage() );
171  }
172  }
173 
174  protected function doSelectDomain( DatabaseDomain $domain ) {
175  if ( $domain->getSchema() !== null ) {
176  throw new DBExpectedError(
177  $this,
178  __CLASS__ . ": domain '{$domain->getId()}' has a schema component"
179  );
180  }
181 
182  $database = $domain->getDatabase();
183  // A null database means "don't care" so leave it as is and update the table prefix
184  if ( $database === null ) {
185  $this->currentDomain = new DatabaseDomain(
186  $this->currentDomain->getDatabase(),
187  null,
188  $domain->getTablePrefix()
189  );
190  $this->platform->setPrefix( $domain->getTablePrefix() );
191 
192  return true;
193  }
194 
195  if ( $database !== $this->getDBname() ) {
196  $sql = 'USE ' . $this->addIdentifierQuotes( $database );
197  $qs = $this->executeQuery( $sql, __METHOD__, self::QUERY_IGNORE_DBO_TRX, $sql );
198  if ( $qs->res === false ) {
199  $this->reportQueryError( $qs->message, $qs->code, $sql, __METHOD__ );
200  return false; // unreachable
201  }
202  }
203 
204  // Update that domain fields on success (no exception thrown)
205  $this->currentDomain = $domain;
206  $this->platform->setPrefix( $domain->getTablePrefix() );
207 
208  return true;
209  }
210 
221  abstract protected function mysqlConnect( $server, $user, $password, $db );
222 
226  public function lastError() {
227  if ( $this->conn ) {
228  // Even if it's non-zero, it can still be invalid
229  $error = $this->mysqlError( $this->conn );
230  if ( !$error ) {
231  $error = $this->mysqlError();
232  }
233  } else {
234  $error = $this->mysqlError() ?: $this->lastConnectError;
235  }
236 
237  return $error;
238  }
239 
246  abstract protected function mysqlError( $conn = null );
247 
248  protected function isInsertSelectSafe( array $insertOptions, array $selectOptions ) {
249  $row = $this->replicationReporter->getReplicationSafetyInfo( $this );
250  // For row-based-replication, the resulting changes will be relayed, not the query
251  if ( $row->binlog_format === 'ROW' ) {
252  return true;
253  }
254  // LIMIT requires ORDER BY on a unique key or it is non-deterministic
255  if ( isset( $selectOptions['LIMIT'] ) ) {
256  return false;
257  }
258  // In MySQL, an INSERT SELECT is only replication safe with row-based
259  // replication or if innodb_autoinc_lock_mode is 0. When those
260  // conditions aren't met, use non-native mode.
261  // While we could try to determine if the insert is safe anyway by
262  // checking if the target table has an auto-increment column that
263  // isn't set in $varMap, that seems unlikely to be worth the extra
264  // complexity.
265  return (
266  in_array( 'NO_AUTO_COLUMNS', $insertOptions ) ||
267  (int)$row->innodb_autoinc_lock_mode === 0
268  );
269  }
270 
284  public function estimateRowCount(
285  $tables,
286  $var = '*',
287  $conds = '',
288  $fname = __METHOD__,
289  $options = [],
290  $join_conds = []
291  ) {
292  $conds = $this->platform->normalizeConditions( $conds, $fname );
293  $column = $this->platform->extractSingleFieldFromList( $var );
294  if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
295  $conds[] = "$column IS NOT NULL";
296  }
297 
298  $options['EXPLAIN'] = true;
299  $res = $this->select( $tables, $var, $conds, $fname, $options, $join_conds );
300  if ( $res === false ) {
301  return false;
302  }
303  if ( !$res->numRows() ) {
304  return 0;
305  }
306 
307  $rows = 1;
308  foreach ( $res as $plan ) {
309  $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
310  }
311 
312  return (int)$rows;
313  }
314 
315  public function tableExists( $table, $fname = __METHOD__ ) {
316  // Split database and table into proper variables as Database::tableName() returns
317  // shared tables prefixed with their database, which do not work in SHOW TABLES statements
318  [ $database, , $prefix, $table ] = $this->platform->qualifiedTableComponents( $table );
319  $tableName = "{$prefix}{$table}";
320 
321  if ( isset( $this->sessionTempTables[$tableName] ) ) {
322  return true; // already known to exist and won't show in SHOW TABLES anyway
323  }
324 
325  // We can't use buildLike() here, because it specifies an escape character
326  // other than the backslash, which is the only one supported by SHOW TABLES
327  // TODO: Avoid using platform's internal methods
328  $encLike = $this->platform->escapeLikeInternal( $tableName, '\\' );
329 
330  // If the database has been specified (such as for shared tables), use "FROM"
331  if ( $database !== '' ) {
332  $encDatabase = $this->platform->addIdentifierQuotes( $database );
333  $sql = "SHOW TABLES FROM $encDatabase LIKE '$encLike'";
334  } else {
335  $sql = "SHOW TABLES LIKE '$encLike'";
336  }
337 
338  $res = $this->query(
339  $sql,
340  $fname,
341  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
342  );
343 
344  return $res->numRows() > 0;
345  }
346 
352  public function fieldInfo( $table, $field ) {
353  $res = $this->query(
354  "SELECT * FROM " . $this->tableName( $table ) . " LIMIT 1",
355  __METHOD__,
356  self::QUERY_SILENCE_ERRORS | self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
357  );
358  if ( !$res ) {
359  return false;
360  }
362  '@phan-var MysqliResultWrapper $res';
363  return $res->getInternalFieldInfo( $field );
364  }
365 
375  public function indexInfo( $table, $index, $fname = __METHOD__ ) {
376  # https://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
377  $index = $this->indexName( $index );
378 
379  $res = $this->query(
380  'SHOW INDEX FROM ' . $this->tableName( $table ),
381  $fname,
382  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
383  );
384 
385  if ( !$res ) {
386  return null;
387  }
388 
389  $result = [];
390 
391  foreach ( $res as $row ) {
392  if ( $row->Key_name == $index ) {
393  $result[] = $row;
394  }
395  }
396 
397  return $result ?: false;
398  }
399 
404  public function strencode( $s ) {
405  return $this->mysqlRealEscapeString( $s );
406  }
407 
414  abstract protected function mysqlRealEscapeString( $s );
415 
416  public function serverIsReadOnly() {
417  // Avoid SHOW to avoid internal temporary tables
418  $flags = self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE;
419  $res = $this->query( "SELECT @@GLOBAL.read_only AS Value", __METHOD__, $flags );
420  $row = $res->fetchObject();
421 
422  return $row ? (bool)$row->Value : false;
423  }
424 
428  public function getSoftwareLink() {
429  [ $variant ] = $this->getMySqlServerVariant();
430  if ( $variant === 'MariaDB' ) {
431  return '[{{int:version-db-mariadb-url}} MariaDB]';
432  }
433 
434  return '[{{int:version-db-mysql-url}} MySQL]';
435  }
436 
440  protected function getMySqlServerVariant() {
441  $version = $this->getServerVersion();
442 
443  // MariaDB includes its name in its version string; this is how MariaDB's version of
444  // the mysql command-line client identifies MariaDB servers.
445  // https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_version
446  // https://mariadb.com/kb/en/version/
447  $parts = explode( '-', $version, 2 );
448  $number = $parts[0];
449  $suffix = $parts[1] ?? '';
450  if ( strpos( $suffix, 'MariaDB' ) !== false || strpos( $suffix, '-maria-' ) !== false ) {
451  $vendor = 'MariaDB';
452  } else {
453  $vendor = 'MySQL';
454  }
455 
456  return [ $vendor, $number ];
457  }
458 
462  public function getServerVersion() {
463  $cache = $this->srvCache;
464  $fname = __METHOD__;
465 
466  return $cache->getWithSetCallback(
467  $cache->makeGlobalKey( 'mysql-server-version', $this->getServerName() ),
468  $cache::TTL_HOUR,
469  function () use ( $fname ) {
470  // Not using mysql_get_server_info() or similar for consistency: in the handshake,
471  // MariaDB 10 adds the prefix "5.5.5-", and only some newer client libraries strip
472  // it off (see RPL_VERSION_HACK in include/mysql_com.h).
473  return $this->selectField( '', 'VERSION()', '', $fname );
474  }
475  );
476  }
477 
481  public function setSessionOptions( array $options ) {
482  $sqlAssignments = [];
483 
484  if ( isset( $options['connTimeout'] ) ) {
485  $encTimeout = (int)$options['connTimeout'];
486  $sqlAssignments[] = "net_read_timeout=$encTimeout";
487  $sqlAssignments[] = "net_write_timeout=$encTimeout";
488  }
489 
490  if ( $sqlAssignments ) {
491  $this->query(
492  'SET ' . implode( ', ', $sqlAssignments ),
493  __METHOD__,
494  self::QUERY_CHANGE_TRX | self::QUERY_CHANGE_NONE
495  );
496  }
497  }
498 
504  public function streamStatementEnd( &$sql, &$newLine ) {
505  if ( preg_match( '/^DELIMITER\s+(\S+)/i', $newLine, $m ) ) {
506  $this->delimiter = $m[1];
507  $newLine = '';
508  }
509 
510  return parent::streamStatementEnd( $sql, $newLine );
511  }
512 
513  public function doLockIsFree( string $lockName, string $method ) {
514  $res = $this->query(
515  $this->platform->lockIsFreeSQLText( $lockName ),
516  $method,
517  self::QUERY_CHANGE_LOCKS
518  );
519  $row = $res->fetchObject();
520 
521  return ( $row->unlocked == 1 );
522  }
523 
524  public function doLock( string $lockName, string $method, int $timeout ) {
525  $res = $this->query(
526  $this->platform->lockSQLText( $lockName, $timeout ),
527  $method,
528  self::QUERY_CHANGE_LOCKS
529  );
530  $row = $res->fetchObject();
531 
532  return ( $row->acquired !== null ) ? (float)$row->acquired : null;
533  }
534 
535  public function doUnlock( string $lockName, string $method ) {
536  $res = $this->query(
537  $this->platform->unlockSQLText( $lockName ),
538  $method,
539  self::QUERY_CHANGE_LOCKS
540  );
541  $row = $res->fetchObject();
542 
543  return ( $row->released == 1 );
544  }
545 
546  public function namedLocksEnqueue() {
547  return true;
548  }
549 
550  protected function doFlushSession( $fname ) {
551  $flags = self::QUERY_CHANGE_LOCKS | self::QUERY_NO_RETRY;
552  // Note that RELEASE_ALL_LOCKS() is not supported well enough to use here.
553  // https://mariadb.com/kb/en/release_all_locks/
554  $releaseLockFields = [];
555  foreach ( $this->sessionNamedLocks as $name => $info ) {
556  $encName = $this->addQuotes( $this->platform->makeLockName( $name ) );
557  $releaseLockFields[] = "RELEASE_LOCK($encName)";
558  }
559  if ( $releaseLockFields ) {
560  $sql = 'SELECT ' . implode( ',', $releaseLockFields );
561  $qs = $this->executeQuery( $sql, __METHOD__, $flags, $sql );
562  if ( $qs->res === false ) {
563  $this->reportQueryError( $qs->message, $qs->code, $sql, $fname, true );
564  }
565  }
566  }
567 
568  protected function doUpsert(
569  string $table,
570  array $rows,
571  array $identityKey,
572  array $set,
573  string $fname
574  ) {
575  $encTable = $this->tableName( $table );
576  [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
577  $sqlColumnAssignments = $this->makeList( $set, self::LIST_SET );
578  // No need to expose __NEW.* since buildExcludedValue() uses VALUES(column)
579 
580  // https://mariadb.com/kb/en/insert-on-duplicate-key-update/
581  // https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html
582  $sql =
583  "INSERT INTO $encTable " .
584  "($sqlColumns) VALUES $sqlTuples " .
585  "ON DUPLICATE KEY UPDATE $sqlColumnAssignments";
586 
587  $this->query( $sql, $fname, self::QUERY_CHANGE_ROWS );
588  }
589 
590  protected function doReplace( $table, array $identityKey, array $rows, $fname ) {
591  $encTable = $this->tableName( $table );
592  [ $sqlColumns, $sqlTuples ] = $this->platform->makeInsertLists( $rows );
593 
594  $sql = "REPLACE INTO $encTable ($sqlColumns) VALUES $sqlTuples";
595 
596  $this->query( $sql, $fname, self::QUERY_CHANGE_ROWS );
597  }
598 
604  public function wasDeadlock() {
605  return $this->lastErrno() == 1213;
606  }
607 
613  public function wasLockTimeout() {
614  return $this->lastErrno() == 1205;
615  }
616 
622  public function wasReadOnlyError() {
623  return $this->lastErrno() == 1223 ||
624  ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
625  }
626 
627  protected function isConnectionError( $errno ) {
628  // https://mariadb.com/kb/en/mariadb-error-codes/
629  // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html
630  // https://dev.mysql.com/doc/mysql-errors/8.0/en/client-error-reference.html
631  return in_array( $errno, [ 2013, 2006, 2003, 1927, 1053 ], true );
632  }
633 
634  protected function isQueryTimeoutError( $errno ) {
635  // https://mariadb.com/kb/en/mariadb-error-codes/
636  // https://dev.mysql.com/doc/refman/8.0/en/client-error-reference.html
637  // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html
638  return in_array( $errno, [ 3024, 2062, 1969, 1028 ], true );
639  }
640 
641  protected function isKnownStatementRollbackError( $errno ) {
642  // https://mariadb.com/kb/en/mariadb-error-codes/
643  // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html
644  return in_array(
645  $errno,
646  [ 3024, 1969, 1022, 1062, 1216, 1217, 1137, 1146, 1051, 1054 ],
647  true
648  );
649  }
650 
658  public function duplicateTableStructure(
659  $oldName, $newName, $temporary = false, $fname = __METHOD__
660  ) {
661  $tmp = $temporary ? 'TEMPORARY ' : '';
662  $newName = $this->addIdentifierQuotes( $newName );
663  $oldName = $this->addIdentifierQuotes( $oldName );
664 
665  return $this->query(
666  "CREATE $tmp TABLE $newName (LIKE $oldName)",
667  $fname,
668  self::QUERY_PSEUDO_PERMANENT | self::QUERY_CHANGE_SCHEMA
669  );
670  }
671 
679  public function listTables( $prefix = null, $fname = __METHOD__ ) {
680  $result = $this->query(
681  "SHOW TABLES",
682  $fname,
683  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
684  );
685 
686  $endArray = [];
687 
688  foreach ( $result as $table ) {
689  $vars = get_object_vars( $table );
690  $table = array_pop( $vars );
691 
692  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
693  $endArray[] = $table;
694  }
695  }
696 
697  return $endArray;
698  }
699 
709  public function listViews( $prefix = null, $fname = __METHOD__ ) {
710  // The name of the column containing the name of the VIEW
711  $propertyName = 'Tables_in_' . $this->getDBname();
712 
713  // Query for the VIEWS
714  $res = $this->query(
715  'SHOW FULL TABLES WHERE TABLE_TYPE = "VIEW"',
716  $fname,
717  self::QUERY_IGNORE_DBO_TRX | self::QUERY_CHANGE_NONE
718  );
719 
720  $allViews = [];
721  foreach ( $res as $row ) {
722  array_push( $allViews, $row->$propertyName );
723  }
724 
725  if ( $prefix === null || $prefix === '' ) {
726  return $allViews;
727  }
728 
729  $filteredViews = [];
730  foreach ( $allViews as $viewName ) {
731  // Does the name of this VIEW start with the table-prefix?
732  if ( strpos( $viewName, $prefix ) === 0 ) {
733  array_push( $filteredViews, $viewName );
734  }
735  }
736 
737  return $filteredViews;
738  }
739 
748  public function isView( $name, $prefix = null ) {
749  return in_array( $name, $this->listViews( $prefix, __METHOD__ ) );
750  }
751 
752  public function selectSQLText(
753  $table,
754  $vars,
755  $conds = '',
756  $fname = __METHOD__,
757  $options = [],
758  $join_conds = []
759  ) {
760  $sql = parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
761  // https://dev.mysql.com/doc/refman/5.7/en/optimizer-hints.html
762  // https://mariadb.com/kb/en/library/aborting-statements/
763  $timeoutMsec = intval( $options['MAX_EXECUTION_TIME'] ?? 0 );
764  if ( $timeoutMsec > 0 ) {
765  [ $vendor, $number ] = $this->getMySqlServerVariant();
766  if ( $vendor === 'MariaDB' && version_compare( $number, '10.1.2', '>=' ) ) {
767  $timeoutSec = $timeoutMsec / 1000;
768  $sql = "SET STATEMENT max_statement_time=$timeoutSec FOR $sql";
769  } elseif ( $vendor === 'MySQL' && version_compare( $number, '5.7.0', '>=' ) ) {
770  $sql = preg_replace(
771  '/^SELECT(?=\s)/',
772  "SELECT /*+ MAX_EXECUTION_TIME($timeoutMsec)*/",
773  $sql
774  );
775  }
776  }
777 
778  return $sql;
779  }
780 }
781 
785 class_alias( DatabaseMysqlBase::class, 'DatabaseMysqlBase' );
const LIST_SET
Definition: Defines.php:44
getWithSetCallback( $key, $exptime, $callback, $flags=0)
Get an item, regenerating and setting it if not found.
Definition: BagOStuff.php:212
Base class for the more common types of database errors.
Class to handle database/schema/prefix specifications for IDatabase.
MySQL database abstraction layer.
doSelectDomain(DatabaseDomain $domain)
__construct(array $params)
Additional $params include:
string null $sslCiphers
Open SSL cipher list string.
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
doLock(string $lockName, string $method, int $timeout)
namedLocksEnqueue()
Check to see if a named lock used by lock() use blocking queues.bool 1.26Stability: stableto override
isInsertSelectSafe(array $insertOptions, array $selectOptions)
doUnlock(string $lockName, string $method)
wasReadOnlyError()
Determines if the last failure was due to the database being read-only.
doUpsert(string $table, array $rows, array $identityKey, array $set, string $fname)
Perform an UPSERT query.
isConnectionError( $errno)
Do not use this method outside of Database/DBError classes.
doFlushSession( $fname)
Reset the server-side session state for named locks and table locks.
serverIsReadOnly()
bool Whether the DB server is marked as read-only server-side If an error occurs, {query} 1....
indexInfo( $table, $index, $fname=__METHOD__)
Get information about an index into an object Returns false if the index does not exist.
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Take the same arguments as IDatabase::select() and return the SQL it would use.
mysqlError( $conn=null)
Returns the text of the error message from previous MySQL operation.
open( $server, $user, $password, $db, $schema, $tablePrefix)
Open a new connection to the database (closing any existing one)
bool $utf8Mode
Use experimental UTF-8 transmission encoding.
MysqlReplicationReporter $replicationReporter
doLockIsFree(string $lockName, string $method)
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
mysqlRealEscapeString( $s)
Escape special characters in a string for use in an SQL statement.
wasLockTimeout()
Determines if the last failure was due to a lock timeout.
doReplace( $table, array $identityKey, array $rows, $fname)
estimateRowCount( $tables, $var=' *', $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Estimate rows in dataset Returns estimated count, based on EXPLAIN output Takes same arguments as Dat...
mysqlConnect( $server, $user, $password, $db)
Open a connection to a MySQL server.
isQueryTimeoutError( $errno)
Checks whether the cause of the error is detected to be a timeout.
listViews( $prefix=null, $fname=__METHOD__)
Lists VIEWs in the database.
isView( $name, $prefix=null)
Differentiates between a TABLE and a VIEW.
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.
wasDeadlock()
Determines if the last failure was due to a deadlock.
Relational database abstraction object.
Definition: Database.php:45
restoreErrorHandler()
Restore the previous error handler and return the last PHP error for this DB.
Definition: Database.php:544
object resource null $conn
Database connection.
Definition: Database.php:68
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.string
Definition: Database.php:1817
newExceptionAfterConnectError( $error)
Definition: Database.php:1542
string $lastConnectError
Last error during connection; empty string if none.
Definition: Database.php:106
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:1618
installErrorHandler()
Set a custom error handler for logging errors during database connection.
Definition: Database.php:533
tableName( $name, $format='quoted')
Format a table name ready for use in constructing an SQL query.
Definition: Database.php:3683
addIdentifierQuotes( $s)
Escape a SQL identifier (e.g.
Definition: Database.php:3699
close( $fname=__METHOD__)
Close the database connection.
Definition: Database.php:596
reportQueryError( $error, $errno, $sql, $fname, $ignore=false)
Report a query error.
Definition: Database.php:1485
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
Definition: Database.php:3635
BagOStuff $srvCache
APC cache.
Definition: Database.php:47
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query statement and return the result.
Definition: Database.php:848
executeQuery( $sqls, $fname, $flags, $summarySql)
Execute a set of queries without enforcing public (non-Database) caller restrictions.
Definition: Database.php:928
getDBname()
Get the current database name; null if there isn't one.
Definition: Database.php:1801
selectField( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a single field from a single result row.
Definition: Database.php:1570
lastErrno()
Get the RDBMS-specific error code from the last attempted query statement.
Interface for query language.
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s