summaryrefslogtreecommitdiff
path: root/includes/db
diff options
context:
space:
mode:
Diffstat (limited to 'includes/db')
-rw-r--r--includes/db/Database.php1654
-rw-r--r--includes/db/DatabaseIbm_db2.php1341
-rw-r--r--includes/db/DatabaseMssql.php1684
-rw-r--r--includes/db/DatabaseMysql.php240
-rw-r--r--includes/db/DatabaseOracle.php664
-rw-r--r--includes/db/DatabasePostgres.php922
-rw-r--r--includes/db/DatabaseSqlite.php247
-rw-r--r--includes/db/LBFactory.php76
-rw-r--r--includes/db/LBFactory_Multi.php6
-rw-r--r--includes/db/LBFactory_Single.php57
-rw-r--r--includes/db/LoadBalancer.php123
-rw-r--r--includes/db/LoadMonitor.php22
12 files changed, 3779 insertions, 3257 deletions
diff --git a/includes/db/Database.php b/includes/db/Database.php
index ea5d77da..5acb67fa 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -4,7 +4,7 @@
*
* @file
* @ingroup Database
- * This file deals with MySQL interface functions
+ * This file deals with database interface functions
* and query specifics/optimisations
*/
@@ -16,14 +16,200 @@ define( 'DEADLOCK_DELAY_MIN', 500000 );
define( 'DEADLOCK_DELAY_MAX', 1500000 );
/**
+ * Base interface for all DBMS-specific code. At a bare minimum, all of the
+ * following must be implemented to support MediaWiki
+ *
+ * @file
+ * @ingroup Database
+ */
+interface DatabaseType {
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ *
+ * @return string
+ */
+ public function getType();
+
+ /**
+ * Open a connection to the database. Usually aborts on failure
+ *
+ * @param $server String: database server host
+ * @param $user String: database user name
+ * @param $password String: database user password
+ * @param $dbName String: database name
+ * @return bool
+ * @throws DBConnectionError
+ */
+ public function open( $server, $user, $password, $dbName );
+
+ /**
+ * The DBMS-dependent part of query()
+ * @todo Fixme: Make this private someday
+ *
+ * @param $sql String: SQL query.
+ * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
+ * @private
+ */
+ /*private*/ function doQuery( $sql );
+
+ /**
+ * Fetch the next row from the given result object, in object form.
+ * Fields can be retrieved with $row->fieldname, with fields acting like
+ * member variables.
+ *
+ * @param $res SQL result object as returned from DatabaseBase::query(), etc.
+ * @return Row object
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ public function fetchObject( $res );
+
+ /**
+ * Fetch the next row from the given result object, in associative array
+ * form. Fields are retrieved with $row['fieldname'].
+ *
+ * @param $res SQL result object as returned from DatabaseBase::query(), etc.
+ * @return Row object
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ public function fetchRow( $res );
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @param $res Mixed: A SQL result
+ * @return int
+ */
+ public function numRows( $res );
+
+ /**
+ * Get the number of fields in a result object
+ * @see http://www.php.net/mysql_num_fields
+ *
+ * @param $res Mixed: A SQL result
+ * @return int
+ */
+ public function numFields( $res );
+
+ /**
+ * Get a field name in a result object
+ * @see http://www.php.net/mysql_field_name
+ *
+ * @param $res Mixed: A SQL result
+ * @param $n Integer
+ * @return string
+ */
+ public function fieldName( $res, $n );
+
+ /**
+ * Get the inserted value of an auto-increment row
+ *
+ * The value inserted should be fetched from nextSequenceValue()
+ *
+ * Example:
+ * $id = $dbw->nextSequenceValue('page_page_id_seq');
+ * $dbw->insert('page',array('page_id' => $id));
+ * $id = $dbw->insertId();
+ *
+ * @return int
+ */
+ public function insertId();
+
+ /**
+ * Change the position of the cursor in a result object
+ * @see http://www.php.net/mysql_data_seek
+ *
+ * @param $res Mixed: A SQL result
+ * @param $row Mixed: Either MySQL row or ResultWrapper
+ */
+ public function dataSeek( $res, $row );
+
+ /**
+ * Get the last error number
+ * @see http://www.php.net/mysql_errno
+ *
+ * @return int
+ */
+ public function lastErrno();
+
+ /**
+ * Get a description of the last error
+ * @see http://www.php.net/mysql_error
+ *
+ * @return string
+ */
+ public function lastError();
+
+ /**
+ * mysql_fetch_field() wrapper
+ * Returns false if the field doesn't exist
+ *
+ * @param $table string: table name
+ * @param $field string: field name
+ */
+ public function fieldInfo( $table, $field );
+
+ /**
+ * Get information about an index into an object
+ * @param $table string: Table name
+ * @param $index string: Index name
+ * @param $fname string: Calling function name
+ * @return Mixed: Database-specific index description class or false if the index does not exist
+ */
+ function indexInfo( $table, $index, $fname = 'Database::indexInfo' );
+
+ /**
+ * Get the number of rows affected by the last write query
+ * @see http://www.php.net/mysql_affected_rows
+ *
+ * @return int
+ */
+ public function affectedRows();
+
+ /**
+ * Wrapper for addslashes()
+ *
+ * @param $s string: to be slashed.
+ * @return string: slashed string.
+ */
+ public function strencode( $s );
+
+ /**
+ * Returns a wikitext link to the DB's website, e.g.,
+ * return "[http://www.mysql.com/ MySQL]";
+ * Should at least contain plain text, if for some reason
+ * your database has no website.
+ *
+ * @return string: wikitext of a link to the server software's web site
+ */
+ public static function getSoftwareLink();
+
+ /**
+ * A string describing the current software version, like from
+ * mysql_get_server_info().
+ *
+ * @return string: Version information from the database server.
+ */
+ public function getServerVersion();
+
+ /**
+ * A string describing the current software version, and possibly
+ * other details in a user-friendly way. Will be listed on Special:Version, etc.
+ * Use getServerVersion() to get machine-friendly information.
+ *
+ * @return string: Version information from the database server
+ */
+ public function getServerInfo();
+}
+
+/**
* Database abstraction object
* @ingroup Database
*/
-abstract class DatabaseBase {
+abstract class DatabaseBase implements DatabaseType {
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Variables
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
protected $mLastQuery = '';
protected $mDoneWrites = false;
@@ -32,7 +218,6 @@ abstract class DatabaseBase {
protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
protected $mOpened = false;
- protected $mFailFunction;
protected $mTablePrefix;
protected $mFlags;
protected $mTrxLevel = 0;
@@ -40,26 +225,22 @@ abstract class DatabaseBase {
protected $mLBInfo = array();
protected $mFakeSlaveLag = null, $mFakeMaster = false;
protected $mDefaultBigSelects = null;
+ protected $mSchemaVars = false;
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Accessors
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# These optionally set a variable and return the previous state
/**
- * Fail function, takes a Database as a parameter
- * Set to false for default, 1 for ignore errors
- */
- function failFunction( $function = null ) {
- return wfSetVar( $this->mFailFunction, $function );
- }
-
- /**
- * Output page, used for reporting errors
- * FALSE means discard output
+ * A string describing the current software version, and possibly
+ * other details in a user-friendly way. Will be listed on Special:Version, etc.
+ * Use getServerVersion() to get machine-friendly information.
+ *
+ * @return string: Version information from the database server
*/
- function setOutputPage( $out ) {
- wfDeprecated( __METHOD__ );
+ public function getServerInfo() {
+ return $this->getServerVersion();
}
/**
@@ -193,8 +374,8 @@ abstract class DatabaseBase {
}
/**
- * Returns true if this database requires that SELECT DISTINCT queries require that all
- ORDER BY expressions occur in the SELECT list per the SQL92 standard
+ * Returns true if this database requires that SELECT DISTINCT queries require that all
+ ORDER BY expressions occur in the SELECT list per the SQL92 standard
*/
function standardSelectDistinct() {
return true;
@@ -216,7 +397,7 @@ abstract class DatabaseBase {
}
/**
- * Return the last query that went through Database::query()
+ * Return the last query that went through DatabaseBase::query()
* @return String
*/
function lastQuery() { return $this->mLastQuery; }
@@ -244,7 +425,7 @@ abstract class DatabaseBase {
* - DBO_TRX: automatically start transactions
* - DBO_DEFAULT: automatically sets DBO_TRX if not in command line mode
* and removes it in command line mode
- * - DBO_PERSISTENT: use persistant database connection
+ * - DBO_PERSISTENT: use persistant database connection
*/
function setFlag( $flag ) {
$this->mFlags |= $flag;
@@ -266,7 +447,7 @@ abstract class DatabaseBase {
* @return Boolean
*/
function getFlag( $flag ) {
- return !!($this->mFlags & $flag);
+ return !!( $this->mFlags & $flag );
}
/**
@@ -277,7 +458,7 @@ abstract class DatabaseBase {
}
function getWikiID() {
- if( $this->mTablePrefix ) {
+ if ( $this->mTablePrefix ) {
return "{$this->mDBname}-{$this->mTablePrefix}";
} else {
return $this->mDBname;
@@ -285,13 +466,20 @@ abstract class DatabaseBase {
}
/**
- * Get the type of the DBMS, as it appears in $wgDBtype.
+ * Return a path to the DBMS-specific schema, otherwise default to tables.sql
*/
- abstract function getType();
+ public function getSchema() {
+ global $IP;
+ if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
+ return "$IP/maintenance/" . $this->getType() . "/tables.sql";
+ } else {
+ return "$IP/maintenance/tables.sql";
+ }
+ }
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# Other functions
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
/**
* Constructor.
@@ -299,20 +487,18 @@ abstract class DatabaseBase {
* @param $user String: database user name
* @param $password String: database user password
* @param $dbName String: database name
- * @param $failFunction
* @param $flags
* @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php
*/
function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) {
-
+ $flags = 0, $tablePrefix = 'get from global'
+ ) {
global $wgOut, $wgDBprefix, $wgCommandLineMode;
+
# Can't get a reference if it hasn't been set yet
if ( !isset( $wgOut ) ) {
$wgOut = null;
}
-
- $this->mFailFunction = $failFunction;
$this->mFlags = $flags;
if ( $this->mFlags & DBO_DEFAULT ) {
@@ -344,27 +530,53 @@ abstract class DatabaseBase {
/**
* Same as new DatabaseMysql( ... ), kept for backward compatibility
- * @param $server String: database server host
- * @param $user String: database user name
- * @param $password String: database user password
- * @param $dbName String: database name
- * @param failFunction
- * @param $flags
+ * @deprecated
*/
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
- {
- return new DatabaseMysql( $server, $user, $password, $dbName, $failFunction, $flags );
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
+ wfDeprecated( __METHOD__ );
+ return new DatabaseMysql( $server, $user, $password, $dbName, $flags );
}
/**
- * Usually aborts on failure
- * If the failFunction is set to a non-zero integer, returns success
- * @param $server String: database server host
- * @param $user String: database user name
- * @param $password String: database user password
- * @param $dbName String: database name
- */
- abstract function open( $server, $user, $password, $dbName );
+ * Given a DB type, construct the name of the appropriate child class of
+ * DatabaseBase. This is designed to replace all of the manual stuff like:
+ * $class = 'Database' . ucfirst( strtolower( $type ) );
+ * as well as validate against the canonical list of DB types we have
+ *
+ * This factory function is mostly useful for when you need to connect to a
+ * database other than the MediaWiki default (such as for external auth,
+ * an extension, et cetera). Do not use this to connect to the MediaWiki
+ * database. Example uses in core:
+ * @see LoadBalancer::reallyOpenConnection()
+ * @see ExternalUser_MediaWiki::initFromCond()
+ * @see ForeignDBRepo::getMasterDB()
+ * @see WebInstaller_DBConnect::execute()
+ *
+ * @param $dbType String A possible DB type
+ * @param $p Array An array of options to pass to the constructor.
+ * Valid options are: host, user, password, dbname, flags, tableprefix
+ * @return DatabaseBase subclass or null
+ */
+ public final static function newFromType( $dbType, $p = array() ) {
+ $canonicalDBTypes = array(
+ 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
+ );
+ $dbType = strtolower( $dbType );
+
+ if( in_array( $dbType, $canonicalDBTypes ) ) {
+ $class = 'Database' . ucfirst( $dbType );
+ return new $class(
+ isset( $p['host'] ) ? $p['host'] : false,
+ isset( $p['user'] ) ? $p['user'] : false,
+ isset( $p['password'] ) ? $p['password'] : false,
+ isset( $p['dbname'] ) ? $p['dbname'] : false,
+ isset( $p['flags'] ) ? $p['flags'] : 0,
+ isset( $p['tableprefix'] ) ? $p['tableprefix'] : 'get from global'
+ );
+ } else {
+ return null;
+ }
+ }
protected function installErrorHandler() {
$this->mPHPError = false;
@@ -402,7 +614,7 @@ abstract class DatabaseBase {
}
/**
- * @param $error String: fallback error message, used if none is given by MySQL
+ * @param $error String: fallback error message, used if none is given by DB
*/
function reportConnectionError( $error = 'Unknown error' ) {
$myError = $this->lastError();
@@ -410,16 +622,8 @@ abstract class DatabaseBase {
$error = $myError;
}
- if ( $this->mFailFunction ) {
- # Legacy error handling method
- if ( !is_int( $this->mFailFunction ) ) {
- $ff = $this->mFailFunction;
- $ff( $this, $error );
- }
- } else {
- # New method
- throw new DBConnectionError( $this, $error );
- }
+ # New method
+ throw new DBConnectionError( $this, $error );
}
/**
@@ -434,11 +638,11 @@ abstract class DatabaseBase {
* Usually aborts on failure. If errors are explicitly ignored, returns success.
*
* @param $sql String: SQL query
- * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST
+ * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST
* comment (you can use __METHOD__ or add some extra info)
- * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
+ * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
* maybe best to catch the exception instead?
- * @return true for a successful write query, ResultWrapper object for a successful read query,
+ * @return boolean or ResultWrapper. true for a successful write query, ResultWrapper object for a successful read query,
* or false on failure if $tempIgnore set
* @throws DBQueryError Thrown when the database returns an error of any kind
*/
@@ -451,15 +655,16 @@ abstract class DatabaseBase {
# logging size most of the time. The substr is really just a sanity check.
# Who's been wasting my precious column space? -- TS
- #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
+ # $profName = 'query: ' . $fname . ' ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
if ( $isMaster ) {
- $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'Database::query-master';
+ $queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query-master';
} else {
- $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
- $totalProf = 'Database::query';
+ $queryProf = 'query: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
+ $totalProf = 'DatabaseBase::query';
}
+
wfProfileIn( $totalProf );
wfProfileIn( $queryProf );
}
@@ -467,14 +672,14 @@ abstract class DatabaseBase {
$this->mLastQuery = $sql;
if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
// Set a flag indicating that writes have been done
- wfDebug( __METHOD__.": Writes done: $sql\n" );
+ wfDebug( __METHOD__ . ": Writes done: $sql\n" );
$this->mDoneWrites = true;
}
# Add a comment for easy SHOW PROCESSLIST interpretation
- #if ( $fname ) {
+ # if ( $fname ) {
global $wgUser;
- if ( is_object( $wgUser ) && !($wgUser instanceof StubObject) ) {
+ if ( is_object( $wgUser ) && $wgUser->mDataLoaded ) {
$userName = $wgUser->getName();
if ( mb_strlen( $userName ) > 15 ) {
$userName = mb_substr( $userName, 0, 15 ) . '...';
@@ -483,29 +688,33 @@ abstract class DatabaseBase {
} else {
$userName = '';
}
- $commentedSql = preg_replace('/\s/', " /* $fname $userName */ ", $sql, 1);
- #} else {
+ $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
+ # } else {
# $commentedSql = $sql;
- #}
+ # }
# If DBO_TRX is set, start a transaction
- if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
- $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') {
+ if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
+ $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK' ) {
// avoid establishing transactions for SHOW and SET statements too -
- // that would delay transaction initializations to once connection
+ // that would delay transaction initializations to once connection
// is really used by application
- $sqlstart = substr($sql,0,10); // very much worth it, benchmark certified(tm)
- if (strpos($sqlstart,"SHOW ")!==0 and strpos($sqlstart,"SET ")!==0)
- $this->begin();
+ $sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
+ if ( strpos( $sqlstart, "SHOW " ) !== 0 and strpos( $sqlstart, "SET " ) !== 0 )
+ $this->begin();
}
if ( $this->debug() ) {
+ static $cnt = 0;
+
+ $cnt++;
$sqlx = substr( $commentedSql, 0, 500 );
$sqlx = strtr( $sqlx, "\t\n", ' ' );
+
if ( $isMaster ) {
- wfDebug( "SQL-master: $sqlx\n" );
+ wfDebug( "Query $cnt (master): $sqlx\n" );
} else {
- wfDebug( "SQL: $sqlx\n" );
+ wfDebug( "Query $cnt (slave): $sqlx\n" );
}
}
@@ -521,13 +730,17 @@ abstract class DatabaseBase {
# Transaction is gone, like it or not
$this->mTrxLevel = 0;
wfDebug( "Connection lost, reconnecting...\n" );
+
if ( $this->ping() ) {
wfDebug( "Reconnected\n" );
$sqlx = substr( $commentedSql, 0, 500 );
$sqlx = strtr( $sqlx, "\t\n", ' ' );
global $wgRequestTime;
- $elapsed = round( microtime(true) - $wgRequestTime, 3 );
- wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
+ $elapsed = round( microtime( true ) - $wgRequestTime, 3 );
+ if ( $elapsed < 300 ) {
+ # Not a database error to lose a transaction after a minute or two
+ wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
+ }
$ret = $this->doQuery( $commentedSql );
} else {
wfDebug( "Failed\n" );
@@ -542,18 +755,11 @@ abstract class DatabaseBase {
wfProfileOut( $queryProf );
wfProfileOut( $totalProf );
}
+
return $this->resultObject( $ret );
}
/**
- * The DBMS-dependent part of query()
- * @param $sql String: SQL query.
- * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
- * @private
- */
- /*private*/ abstract function doQuery( $sql );
-
- /**
* @param $error String
* @param $errno Integer
* @param $sql String
@@ -561,18 +767,17 @@ abstract class DatabaseBase {
* @param $tempIgnore Boolean
*/
function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- global $wgCommandLineMode;
# Ignore errors during error handling to avoid infinite recursion
$ignore = $this->ignoreErrors( true );
++$this->mErrorCount;
- if( $ignore || $tempIgnore ) {
- wfDebug("SQL ERROR (ignored): $error\n");
+ if ( $ignore || $tempIgnore ) {
+ wfDebug( "SQL ERROR (ignored): $error\n" );
$this->ignoreErrors( $ignore );
} else {
$sql1line = str_replace( "\n", "\\n", $sql );
- wfLogDBError("$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n");
- wfDebug("SQL ERROR: " . $error . "\n");
+ wfLogDBError( "$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n" );
+ wfDebug( "SQL ERROR: " . $error . "\n" );
throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
}
@@ -587,7 +792,7 @@ abstract class DatabaseBase {
* & = filename; reads the file and inserts as a blob
* (we don't use this though...)
*/
- function prepare( $sql, $func = 'Database::prepare' ) {
+ function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
/* MySQL doesn't support prepared statements (yet), so just
pack up the query for reference. We'll manually replace
the bits later. */
@@ -595,7 +800,7 @@ abstract class DatabaseBase {
}
function freePrepared( $prepared ) {
- /* No-op for MySQL */
+ /* No-op by default */
}
/**
@@ -604,12 +809,14 @@ abstract class DatabaseBase {
* @param $args Mixed: Either an array here, or put scalars as varargs
*/
function execute( $prepared, $args = null ) {
- if( !is_array( $args ) ) {
+ if ( !is_array( $args ) ) {
# Pull the var args
$args = func_get_args();
array_shift( $args );
}
+
$sql = $this->fillPrepared( $prepared['query'], $args );
+
return $this->query( $sql, $prepared['func'] );
}
@@ -620,14 +827,17 @@ abstract class DatabaseBase {
* @param $args ...
*/
function safeQuery( $query, $args = null ) {
- $prepared = $this->prepare( $query, 'Database::safeQuery' );
- if( !is_array( $args ) ) {
+ $prepared = $this->prepare( $query, 'DatabaseBase::safeQuery' );
+
+ if ( !is_array( $args ) ) {
# Pull the var args
$args = func_get_args();
array_shift( $args );
}
+
$retval = $this->execute( $prepared, $args );
$this->freePrepared( $prepared );
+
return $retval;
}
@@ -641,6 +851,7 @@ abstract class DatabaseBase {
function fillPrepared( $preparedQuery, $args ) {
reset( $args );
$this->preparedArgs =& $args;
+
return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
array( &$this, 'fillPreparedArg' ), $preparedQuery );
}
@@ -660,7 +871,9 @@ abstract class DatabaseBase {
case '\\!': return '!';
case '\\&': return '&';
}
+
list( /* $n */ , $arg ) = each( $this->preparedArgs );
+
switch( $matches[1] ) {
case '?': return $this->addQuotes( $arg );
case '!': return $arg;
@@ -682,98 +895,18 @@ abstract class DatabaseBase {
}
/**
- * Fetch the next row from the given result object, in object form.
- * Fields can be retrieved with $row->fieldname, with fields acting like
- * member variables.
- *
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- abstract function fetchObject( $res );
-
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
- *
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- abstract function fetchRow( $res );
-
- /**
- * Get the number of rows in a result object
- * @param $res Mixed: A SQL result
- */
- abstract function numRows( $res );
-
- /**
- * Get the number of fields in a result object
- * See documentation for mysql_num_fields()
- * @param $res Mixed: A SQL result
- */
- abstract function numFields( $res );
-
- /**
- * Get a field name in a result object
- * See documentation for mysql_field_name():
- * http://www.php.net/mysql_field_name
- * @param $res Mixed: A SQL result
- * @param $n Integer
- */
- abstract function fieldName( $res, $n );
-
- /**
- * Get the inserted value of an auto-increment row
- *
- * The value inserted should be fetched from nextSequenceValue()
- *
- * Example:
- * $id = $dbw->nextSequenceValue('page_page_id_seq');
- * $dbw->insert('page',array('page_id' => $id));
- * $id = $dbw->insertId();
- */
- abstract function insertId();
-
- /**
- * Change the position of the cursor in a result object
- * See mysql_data_seek()
- * @param $res Mixed: A SQL result
- * @param $row Mixed: Either MySQL row or ResultWrapper
- */
- abstract function dataSeek( $res, $row );
-
- /**
- * Get the last error number
- * See mysql_errno()
- */
- abstract function lastErrno();
-
- /**
- * Get a description of the last error
- * See mysql_error() for more details
- */
- abstract function lastError();
-
- /**
- * Get the number of rows affected by the last write query
- * See mysql_affected_rows() for more details
- */
- abstract function affectedRows();
-
- /**
* Simple UPDATE wrapper
* Usually aborts on failure
* If errors are explicitly ignored, returns success
*
- * This function exists for historical reasons, Database::update() has a more standard
+ * This function exists for historical reasons, DatabaseBase::update() has a more standard
* calling convention and feature set
*/
- function set( $table, $var, $value, $cond, $fname = 'Database::set' ) {
+ function set( $table, $var, $value, $cond, $fname = 'DatabaseBase::set' ) {
$table = $this->tableName( $table );
$sql = "UPDATE $table SET $var = '" .
$this->strencode( $value ) . "' WHERE ($cond)";
+
return (bool)$this->query( $sql, $fname );
}
@@ -782,19 +915,22 @@ abstract class DatabaseBase {
* Usually aborts on failure
* If errors are explicitly ignored, returns FALSE on failure
*/
- function selectField( $table, $var, $cond='', $fname = 'Database::selectField', $options = array() ) {
+ function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField', $options = array() ) {
if ( !is_array( $options ) ) {
$options = array( $options );
}
+
$options['LIMIT'] = 1;
$res = $this->select( $table, $var, $cond, $fname, $options );
+
if ( $res === false || !$this->numRows( $res ) ) {
return false;
}
+
$row = $this->fetchRow( $res );
+
if ( $row !== false ) {
- $this->freeResult( $res );
return reset( $row );
} else {
return false;
@@ -816,42 +952,82 @@ abstract class DatabaseBase {
$startOpts = '';
$noKeyOptions = array();
+
foreach ( $options as $key => $option ) {
if ( is_numeric( $key ) ) {
$noKeyOptions[$option] = true;
}
}
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
- if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
-
- //if (isset($options['LIMIT'])) {
+ if ( isset( $options['GROUP BY'] ) ) {
+ $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
+ }
+
+ if ( isset( $options['HAVING'] ) ) {
+ $preLimitTail .= " HAVING {$options['HAVING']}";
+ }
+
+ if ( isset( $options['ORDER BY'] ) ) {
+ $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
+ }
+
+ // if (isset($options['LIMIT'])) {
// $tailOpts .= $this->limitResult('', $options['LIMIT'],
- // isset($options['OFFSET']) ? $options['OFFSET']
+ // isset($options['OFFSET']) ? $options['OFFSET']
// : false);
- //}
+ // }
+
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+ $postLimitTail .= ' FOR UPDATE';
+ }
+
+ if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
+ $postLimitTail .= ' LOCK IN SHARE MODE';
+ }
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
+ if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+ $startOpts .= 'DISTINCT';
+ }
# Various MySQL extensions
- if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */';
- if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
- if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
- if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
- if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT';
- if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS';
- if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE';
- if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE';
+ if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) {
+ $startOpts .= ' /*! STRAIGHT_JOIN */';
+ }
+
+ if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) {
+ $startOpts .= ' HIGH_PRIORITY';
+ }
+
+ if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) {
+ $startOpts .= ' SQL_BIG_RESULT';
+ }
+
+ if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) {
+ $startOpts .= ' SQL_BUFFER_RESULT';
+ }
+
+ if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) {
+ $startOpts .= ' SQL_SMALL_RESULT';
+ }
+
+ if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) {
+ $startOpts .= ' SQL_CALC_FOUND_ROWS';
+ }
+
+ if ( isset( $noKeyOptions['SQL_CACHE'] ) ) {
+ $startOpts .= ' SQL_CACHE';
+ }
+
+ if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) {
+ $startOpts .= ' SQL_NO_CACHE';
+ }
if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
$useIndex = $this->useIndexClause( $options['USE INDEX'] );
} else {
$useIndex = '';
}
-
+
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
@@ -863,44 +1039,47 @@ abstract class DatabaseBase {
* @param $conds Mixed: Array or string, condition(s) for WHERE
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
* @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
+ * see DatabaseBase::makeSelectOptions code for list of supported stuff
* @param $join_conds Array: Associative array of table join conditions (optional)
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * @return mixed Database result resource (feed to DatabaseBase::fetchObject or whatever), or false on failure
*/
- function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() )
- {
+ function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', $options = array(), $join_conds = array() ) {
$sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+
return $this->query( $sql, $fname );
}
-
+
/**
* SELECT wrapper
*
- * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
+ * @param $table Mixed: Array or string, table name(s) (prefix auto-added). Array keys are table aliases (optional)
* @param $vars Mixed: Array or string, field name(s) to be retrieved
* @param $conds Mixed: Array or string, condition(s) for WHERE
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
* @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
+ * see DatabaseBase::makeSelectOptions code for list of supported stuff
* @param $join_conds Array: Associative array of table join conditions (optional)
* (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
* @return string, the SQL text
*/
- function selectSQLText( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() ) {
- if( is_array( $vars ) ) {
+ function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', $options = array(), $join_conds = array() ) {
+ if ( is_array( $vars ) ) {
$vars = implode( ',', $vars );
}
- if( !is_array( $options ) ) {
+
+ if ( !is_array( $options ) ) {
$options = array( $options );
}
- if( is_array( $table ) ) {
- if ( !empty($join_conds) || ( isset( $options['USE INDEX'] ) && is_array( @$options['USE INDEX'] ) ) )
+
+ if ( is_array( $table ) ) {
+ if ( !empty( $join_conds ) || ( isset( $options['USE INDEX'] ) && is_array( @$options['USE INDEX'] ) ) ) {
$from = ' FROM ' . $this->tableNamesWithUseIndexOrJOIN( $table, @$options['USE INDEX'], $join_conds );
- else
- $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
- } elseif ($table!='') {
- if ($table{0}==' ') {
+ } else {
+ $from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
+ }
+ } elseif ( $table != '' ) {
+ if ( $table { 0 } == ' ' ) {
$from = ' FROM ' . $table;
} else {
$from = ' FROM ' . $this->tableName( $table );
@@ -911,7 +1090,7 @@ abstract class DatabaseBase {
list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
- if( !empty( $conds ) ) {
+ if ( !empty( $conds ) ) {
if ( is_array( $conds ) ) {
$conds = $this->makeList( $conds, LIST_AND );
}
@@ -920,14 +1099,15 @@ abstract class DatabaseBase {
$sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
}
- if (isset($options['LIMIT']))
- $sql = $this->limitResult($sql, $options['LIMIT'],
- isset($options['OFFSET']) ? $options['OFFSET'] : false);
+ if ( isset( $options['LIMIT'] ) )
+ $sql = $this->limitResult( $sql, $options['LIMIT'],
+ isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
$sql = "$sql $postLimitTail";
-
- if (isset($options['EXPLAIN'])) {
+
+ if ( isset( $options['EXPLAIN'] ) ) {
$sql = 'EXPLAIN ' . $sql;
}
+
return $sql;
}
@@ -949,42 +1129,45 @@ abstract class DatabaseBase {
*
* @todo migrate documentation to phpdocumentor format
*/
- function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array(), $join_conds = array() ) {
+ function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::selectRow', $options = array(), $join_conds = array() ) {
$options['LIMIT'] = 1;
$res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
- if ( $res === false )
+
+ if ( $res === false ) {
return false;
- if ( !$this->numRows($res) ) {
- $this->freeResult($res);
+ }
+
+ if ( !$this->numRows( $res ) ) {
return false;
}
+
$obj = $this->fetchObject( $res );
- $this->freeResult( $res );
- return $obj;
+ return $obj;
}
-
+
/**
* Estimate rows in dataset
* Returns estimated count - not necessarily an accurate estimate across different databases,
* so use sparingly
- * Takes same arguments as Database::select()
+ * Takes same arguments as DatabaseBase::select()
*
- * @param string $table table name
- * @param array $vars unused
- * @param array $conds filters on the table
- * @param string $fname function name for profiling
- * @param array $options options for select
- * @return int row count
+ * @param $table String: table name
+ * @param $vars Array: unused
+ * @param $conds Array: filters on the table
+ * @param $fname String: function name for profiling
+ * @param $options Array: options for select
+ * @return Integer: row count
*/
- public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
+ public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseBase::estimateRowCount', $options = array() ) {
$rows = 0;
$res = $this->select ( $table, 'COUNT(*) AS rowcount', $conds, $fname, $options );
+
if ( $res ) {
$row = $this->fetchRow( $res );
$rows = ( isset( $row['rowcount'] ) ) ? $row['rowcount'] : 0;
}
- $this->freeResult( $res );
+
return $rows;
}
@@ -999,42 +1182,33 @@ abstract class DatabaseBase {
# as to avoid crashing php on some large strings.
# $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
- $sql = str_replace ( "\\\\", '', $sql);
- $sql = str_replace ( "\\'", '', $sql);
- $sql = str_replace ( "\\\"", '', $sql);
- $sql = preg_replace ("/'.*'/s", "'X'", $sql);
- $sql = preg_replace ('/".*"/s', "'X'", $sql);
+ $sql = str_replace ( "\\\\", '', $sql );
+ $sql = str_replace ( "\\'", '', $sql );
+ $sql = str_replace ( "\\\"", '', $sql );
+ $sql = preg_replace ( "/'.*'/s", "'X'", $sql );
+ $sql = preg_replace ( '/".*"/s', "'X'", $sql );
# All newlines, tabs, etc replaced by single space
- $sql = preg_replace ( '/\s+/', ' ', $sql);
+ $sql = preg_replace ( '/\s+/', ' ', $sql );
# All numbers => N
- $sql = preg_replace ('/-?[0-9]+/s', 'N', $sql);
+ $sql = preg_replace ( '/-?[0-9]+/s', 'N', $sql );
return $sql;
}
/**
* Determines whether a field exists in a table
- * Usually aborts on failure
- * If errors are explicitly ignored, returns NULL on failure
+ *
+ * @param $table String: table name
+ * @param $field String: filed to check on that table
+ * @param $fname String: calling function name (optional)
+ * @return Boolean: whether $table has filed $field
*/
- function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
- $table = $this->tableName( $table );
- $res = $this->query( 'DESCRIBE '.$table, $fname );
- if ( !$res ) {
- return null;
- }
-
- $found = false;
+ function fieldExists( $table, $field, $fname = 'DatabaseBase::fieldExists' ) {
+ $info = $this->fieldInfo( $table, $field );
- while ( $row = $this->fetchObject( $res ) ) {
- if ( $row->Field == $field ) {
- $found = true;
- break;
- }
- }
- return $found;
+ return (bool)$info;
}
/**
@@ -1042,7 +1216,7 @@ abstract class DatabaseBase {
* Usually aborts on failure
* If errors are explicitly ignored, returns NULL on failure
*/
- function indexExists( $table, $index, $fname = 'Database::indexExists' ) {
+ function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
$info = $this->indexInfo( $table, $index, $fname );
if ( is_null( $info ) ) {
return null;
@@ -1051,58 +1225,17 @@ abstract class DatabaseBase {
}
}
-
- /**
- * Get information about an index into an object
- * Returns false if the index does not exist
- */
- function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
- # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
- # SHOW INDEX should work for 3.x and up:
- # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
- $table = $this->tableName( $table );
- $index = $this->indexName( $index );
- $sql = 'SHOW INDEX FROM '.$table;
- $res = $this->query( $sql, $fname );
- if ( !$res ) {
- return null;
- }
-
- $result = array();
- while ( $row = $this->fetchObject( $res ) ) {
- if ( $row->Key_name == $index ) {
- $result[] = $row;
- }
- }
- $this->freeResult($res);
-
- return empty($result) ? false : $result;
- }
-
/**
* Query whether a given table exists
*/
function tableExists( $table ) {
$table = $this->tableName( $table );
$old = $this->ignoreErrors( true );
- $res = $this->query( "SELECT 1 FROM $table LIMIT 1" );
+ $res = $this->query( "SELECT 1 FROM $table LIMIT 1", __METHOD__ );
$this->ignoreErrors( $old );
- if( $res ) {
- $this->freeResult( $res );
- return true;
- } else {
- return false;
- }
- }
- /**
- * mysql_fetch_field() wrapper
- * Returns false if the field doesn't exist
- *
- * @param $table
- * @param $field
- */
- abstract function fieldInfo( $table, $field );
+ return (bool)$res;
+ }
/**
* mysql_field_type() wrapper
@@ -1111,6 +1244,7 @@ abstract class DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
return mysql_field_type( $res, $index );
}
@@ -1119,9 +1253,11 @@ abstract class DatabaseBase {
*/
function indexUnique( $table, $index ) {
$indexInfo = $this->indexInfo( $table, $index );
+
if ( !$indexInfo ) {
return null;
}
+
return !$indexInfo[0]->Non_unique;
}
@@ -1133,17 +1269,26 @@ abstract class DatabaseBase {
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
+ *
+ * @param $table String: table name (prefix auto-added)
+ * @param $a Array: Array of rows to insert
+ * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
+ * @param $options Mixed: Associative array of options
+ *
+ * @return bool
*/
- function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
+ function insert( $table, $a, $fname = 'DatabaseBase::insert', $options = array() ) {
# No rows to insert, easy just return now
if ( !count( $a ) ) {
return true;
}
$table = $this->tableName( $table );
+
if ( !is_array( $options ) ) {
$options = array( $options );
}
+
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
$multi = true;
$keys = array_keys( $a[0] );
@@ -1168,26 +1313,33 @@ abstract class DatabaseBase {
} else {
$sql .= '(' . $this->makeList( $a ) . ')';
}
+
return (bool)$this->query( $sql, $fname );
}
/**
- * Make UPDATE options for the Database::update function
+ * Make UPDATE options for the DatabaseBase::update function
*
* @private
- * @param $options Array: The options passed to Database::update
+ * @param $options Array: The options passed to DatabaseBase::update
* @return string
*/
function makeUpdateOptions( $options ) {
- if( !is_array( $options ) ) {
+ if ( !is_array( $options ) ) {
$options = array( $options );
}
+
$opts = array();
- if ( in_array( 'LOW_PRIORITY', $options ) )
+
+ if ( in_array( 'LOW_PRIORITY', $options ) ) {
$opts[] = $this->lowPriorityOption();
- if ( in_array( 'IGNORE', $options ) )
+ }
+
+ if ( in_array( 'IGNORE', $options ) ) {
$opts[] = 'IGNORE';
- return implode(' ', $opts);
+ }
+
+ return implode( ' ', $opts );
}
/**
@@ -1202,13 +1354,15 @@ abstract class DatabaseBase {
* more of IGNORE, LOW_PRIORITY
* @return Boolean
*/
- function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
+ function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
$table = $this->tableName( $table );
$opts = $this->makeUpdateOptions( $options );
$sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
+
if ( $conds != '*' ) {
$sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
}
+
return $this->query( $sql, $fname );
}
@@ -1223,16 +1377,17 @@ abstract class DatabaseBase {
*/
function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
+ throw new DBUnexpectedError( $this, 'DatabaseBase::makeList called with incorrect parameters' );
}
$first = true;
$list = '';
+
foreach ( $a as $field => $value ) {
if ( !$first ) {
if ( $mode == LIST_AND ) {
$list .= ' AND ';
- } elseif($mode == LIST_OR) {
+ } elseif ( $mode == LIST_OR ) {
$list .= ' OR ';
} else {
$list .= ',';
@@ -1240,23 +1395,24 @@ abstract class DatabaseBase {
} else {
$first = false;
}
- if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
+
+ if ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_numeric( $field ) ) {
$list .= "($value)";
- } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
+ } elseif ( ( $mode == LIST_SET ) && is_numeric( $field ) ) {
$list .= "$value";
- } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
- if( count( $value ) == 0 ) {
- throw new MWException( __METHOD__.': empty input' );
- } elseif( count( $value ) == 1 ) {
+ } elseif ( ( $mode == LIST_AND || $mode == LIST_OR ) && is_array( $value ) ) {
+ if ( count( $value ) == 0 ) {
+ throw new MWException( __METHOD__ . ': empty input' );
+ } elseif ( count( $value ) == 1 ) {
// Special-case single values, as IN isn't terribly efficient
// Don't necessarily assume the single key is 0; we don't
// enforce linear numeric ordering on other arrays here.
$value = array_values( $value );
- $list .= $field." = ".$this->addQuotes( $value[0] );
+ $list .= $field . " = " . $this->addQuotes( $value[0] );
} else {
- $list .= $field." IN (".$this->makeList($value).") ";
+ $list .= $field . " IN (" . $this->makeList( $value ) . ") ";
}
- } elseif( $value === null ) {
+ } elseif ( $value === null ) {
if ( $mode == LIST_AND || $mode == LIST_OR ) {
$list .= "$field IS ";
} elseif ( $mode == LIST_SET ) {
@@ -1270,35 +1426,64 @@ abstract class DatabaseBase {
$list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
}
}
+
return $list;
}
/**
+ * Build a partial where clause from a 2-d array such as used for LinkBatch.
+ * The keys on each level may be either integers or strings.
+ *
+ * @param $data Array: organized as 2-d array(baseKeyVal => array(subKeyVal => <ignored>, ...), ...)
+ * @param $baseKey String: field name to match the base-level keys to (eg 'pl_namespace')
+ * @param $subKey String: field name to match the sub-level keys to (eg 'pl_title')
+ * @return Mixed: string SQL fragment, or false if no items in array.
+ */
+ function makeWhereFrom2d( $data, $baseKey, $subKey ) {
+ $conds = array();
+
+ foreach ( $data as $base => $sub ) {
+ if ( count( $sub ) ) {
+ $conds[] = $this->makeList(
+ array( $baseKey => $base, $subKey => array_keys( $sub ) ),
+ LIST_AND );
+ }
+ }
+
+ if ( $conds ) {
+ return $this->makeList( $conds, LIST_OR );
+ } else {
+ // Nothing to search for...
+ return false;
+ }
+ }
+
+ /**
* Bitwise operations
*/
- function bitNot($field) {
- return "(~$bitField)";
+ function bitNot( $field ) {
+ return "(~$field)";
}
- function bitAnd($fieldLeft, $fieldRight) {
+ function bitAnd( $fieldLeft, $fieldRight ) {
return "($fieldLeft & $fieldRight)";
}
- function bitOr($fieldLeft, $fieldRight) {
+ function bitOr( $fieldLeft, $fieldRight ) {
return "($fieldLeft | $fieldRight)";
}
/**
* Change the current database
*
+ * @todo Explain what exactly will fail if this is not overridden.
* @return bool Success or failure
*/
function selectDB( $db ) {
# Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
- # databases you may as well. TODO: explain what exactly will fail if
- # this is not overridden.
+ # databases you may as well.
return true;
}
@@ -1335,8 +1520,10 @@ abstract class DatabaseBase {
# Note that we check the end so that we will still quote any use of
# use of `database`.table. But won't break things if someone wants
# to query a database table with a dot in the name.
- if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) return $name;
-
+ if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) {
+ return $name;
+ }
+
# Lets test for any bits of text that should never show up in a table
# name. Basically anything like JOIN or ON which are actually part of
# SQL queries, but may end up inside of the table value to combine
@@ -1344,23 +1531,30 @@ abstract class DatabaseBase {
# Note that we use a whitespace test rather than a \b test to avoid
# any remote case where a word like on may be inside of a table name
# surrounded by symbols which may be considered word breaks.
- if( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) return $name;
-
+ if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
+ return $name;
+ }
+
# Split database and table into proper variables.
# We reverse the explode so that database.table and table both output
# the correct table.
$dbDetails = array_reverse( explode( '.', $name, 2 ) );
- if( isset( $dbDetails[1] ) ) @list( $table, $database ) = $dbDetails;
- else @list( $table ) = $dbDetails;
+ if ( isset( $dbDetails[1] ) ) {
+ @list( $table, $database ) = $dbDetails;
+ } else {
+ @list( $table ) = $dbDetails;
+ }
$prefix = $this->mTablePrefix; # Default prefix
-
+
# A database name has been specified in input. Quote the table name
# because we don't want any prefixes added.
- if( isset($database) ) $table = ( $table[0] == '`' ? $table : "`{$table}`" );
-
+ if ( isset( $database ) ) {
+ $table = ( $table[0] == '`' ? $table : "`{$table}`" );
+ }
+
# Note that we use the long format because php will complain in in_array if
# the input is not an array, and will complain in is_array if it is not set.
- if( !isset( $database ) # Don't use shared database if pre selected.
+ if ( !isset( $database ) # Don't use shared database if pre selected.
&& isset( $wgSharedDB ) # We have a shared database
&& $table[0] != '`' # Paranoia check to prevent shared tables listing '`table`'
&& isset( $wgSharedTables )
@@ -1369,15 +1563,16 @@ abstract class DatabaseBase {
$database = $wgSharedDB;
$prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
}
-
+
# Quote the $database and $table and apply the prefix if not quoted.
- if( isset($database) ) $database = ( $database[0] == '`' ? $database : "`{$database}`" );
+ if ( isset( $database ) ) {
+ $database = ( $database[0] == '`' ? $database : "`{$database}`" );
+ }
$table = ( $table[0] == '`' ? $table : "`{$prefix}{$table}`" );
-
+
# Merge our database and table into our final table name.
- $tableName = ( isset($database) ? "{$database}.{$table}" : "{$table}" );
-
- # We're finished, return.
+ $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
+
return $tableName;
}
@@ -1393,12 +1588,14 @@ abstract class DatabaseBase {
public function tableNames() {
$inArray = func_get_args();
$retVal = array();
+
foreach ( $inArray as $name ) {
$retVal[$name] = $this->tableName( $name );
}
+
return $retVal;
}
-
+
/**
* Fetch a number of table names into an zero-indexed numerical array
* This is handy when you need to construct SQL for joins
@@ -1411,47 +1608,97 @@ abstract class DatabaseBase {
public function tableNamesN() {
$inArray = func_get_args();
$retVal = array();
+
foreach ( $inArray as $name ) {
$retVal[] = $this->tableName( $name );
}
+
return $retVal;
}
/**
+ * Get an aliased table name
+ * e.g. tableName AS newTableName
+ *
+ * @param $name string Table name, see tableName()
+ * @param $alias string Alias (optional)
+ * @return string SQL name for aliased table. Will not alias a table to its own name
+ */
+ public function tableNameWithAlias( $name, $alias = false ) {
+ if ( !$alias || $alias == $name ) {
+ return $this->tableName( $name );
+ } else {
+ return $this->tableName( $name ) . ' ' . $this->addIdentifierQuotes( $alias );
+ }
+ }
+
+ /**
+ * Gets an array of aliased table names
+ *
+ * @param $tables array( [alias] => table )
+ * @return array of strings, see tableNameWithAlias()
+ */
+ public function tableNamesWithAlias( $tables ) {
+ $retval = array();
+ foreach ( $tables as $alias => $table ) {
+ if ( is_numeric( $alias ) ) {
+ $alias = $table;
+ }
+ $retval[] = $this->tableNameWithAlias( $table, $alias );
+ }
+ return $retval;
+ }
+
+ /**
* @private
*/
function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
$ret = array();
$retJOIN = array();
- $use_index_safe = is_array($use_index) ? $use_index : array();
- $join_conds_safe = is_array($join_conds) ? $join_conds : array();
- foreach ( $tables as $table ) {
+ $use_index_safe = is_array( $use_index ) ? $use_index : array();
+ $join_conds_safe = is_array( $join_conds ) ? $join_conds : array();
+
+ foreach ( $tables as $alias => $table ) {
+ if ( !is_string( $alias ) ) {
+ // No alias? Set it equal to the table name
+ $alias = $table;
+ }
// Is there a JOIN and INDEX clause for this table?
- if ( isset($join_conds_safe[$table]) && isset($use_index_safe[$table]) ) {
- $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
- $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
- $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
+ if ( isset( $join_conds_safe[$alias] ) && isset( $use_index_safe[$alias] ) ) {
+ $tableClause = $join_conds_safe[$alias][0] . ' ' . $this->tableNameWithAlias( $table, $alias );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$alias] ) );
+ $on = $this->makeList( (array)$join_conds_safe[$alias][1], LIST_AND );
+ if ( $on != '' ) {
+ $tableClause .= ' ON (' . $on . ')';
+ }
+
$retJOIN[] = $tableClause;
// Is there an INDEX clause?
- } else if ( isset($use_index_safe[$table]) ) {
- $tableClause = $this->tableName( $table );
- $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+ } else if ( isset( $use_index_safe[$alias] ) ) {
+ $tableClause = $this->tableNameWithAlias( $table, $alias );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$alias] ) );
$ret[] = $tableClause;
// Is there a JOIN clause?
- } else if ( isset($join_conds_safe[$table]) ) {
- $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
- $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
+ } else if ( isset( $join_conds_safe[$alias] ) ) {
+ $tableClause = $join_conds_safe[$alias][0] . ' ' . $this->tableNameWithAlias( $table, $alias );
+ $on = $this->makeList( (array)$join_conds_safe[$alias][1], LIST_AND );
+ if ( $on != '' ) {
+ $tableClause .= ' ON (' . $on . ')';
+ }
+
$retJOIN[] = $tableClause;
} else {
- $tableClause = $this->tableName( $table );
+ $tableClause = $this->tableNameWithAlias( $table, $alias );
$ret[] = $tableClause;
}
}
+
// We can't separate explicit JOIN clauses with ',', use ' ' for those
- $straightJoins = !empty($ret) ? implode( ',', $ret ) : "";
- $otherJoins = !empty($retJOIN) ? implode( ' ', $retJOIN ) : "";
+ $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
+ $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
+
// Compile our final table clause
- return implode(' ',array($straightJoins,$otherJoins) );
+ return implode( ' ', array( $straightJoins, $otherJoins ) );
}
/**
@@ -1464,7 +1711,8 @@ abstract class DatabaseBase {
'un_user_id' => 'user_id',
'un_user_ip' => 'user_ip',
);
- if( isset( $renamed[$index] ) ) {
+
+ if ( isset( $renamed[$index] ) ) {
return $renamed[$index];
} else {
return $index;
@@ -1472,13 +1720,6 @@ abstract class DatabaseBase {
}
/**
- * Wrapper for addslashes()
- * @param $s String: to be slashed.
- * @return String: slashed string.
- */
- abstract function strencode( $s );
-
- /**
* If it's a string, adds quotes and backslashes
* Otherwise returns as-is
*/
@@ -1495,14 +1736,42 @@ abstract class DatabaseBase {
}
/**
+ * Quotes an identifier using `backticks` or "double quotes" depending on the database type.
+ * MySQL uses `backticks` while basically everything else uses double quotes.
+ * Since MySQL is the odd one out here the double quotes are our generic
+ * and we implement backticks in DatabaseMysql.
+ */
+ public function addIdentifierQuotes( $s ) {
+ return '"' . str_replace( '"', '""', $s ) . '"';
+ }
+
+ /**
+ * Backwards compatibility, identifier quoting originated in DatabasePostgres
+ * which used quote_ident which does not follow our naming conventions
+ * was renamed to addIdentifierQuotes.
+ * @deprecated use addIdentifierQuotes
+ */
+ function quote_ident( $s ) {
+ wfDeprecated( __METHOD__ );
+ return $this->addIdentifierQuotes( $s );
+ }
+
+ /**
* Escape string for safe LIKE usage.
* WARNING: you should almost never use this function directly,
* instead use buildLike() that escapes everything automatically
+ * Deprecated in 1.17, warnings in 1.17, removed in ???
*/
- function escapeLike( $s ) {
+ public function escapeLike( $s ) {
+ wfDeprecated( __METHOD__ );
+ return $this->escapeLikeInternal( $s );
+ }
+
+ protected function escapeLikeInternal( $s ) {
$s = str_replace( '\\', '\\\\', $s );
$s = $this->strencode( $s );
$s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
+
return $s;
}
@@ -1510,27 +1779,31 @@ abstract class DatabaseBase {
* LIKE statement wrapper, receives a variable-length argument list with parts of pattern to match
* containing either string literals that will be escaped or tokens returned by anyChar() or anyString().
* Alternatively, the function could be provided with an array of aforementioned parameters.
- *
+ *
* Example: $dbr->buildLike( 'My_page_title/', $dbr->anyString() ) returns a LIKE clause that searches
* for subpages of 'My page title'.
* Alternatively: $pattern = array( 'My_page_title/', $dbr->anyString() ); $query .= $dbr->buildLike( $pattern );
*
- * @ return String: fully built LIKE statement
+ * @since 1.16
+ * @return String: fully built LIKE statement
*/
function buildLike() {
$params = func_get_args();
- if (count($params) > 0 && is_array($params[0])) {
+
+ if ( count( $params ) > 0 && is_array( $params[0] ) ) {
$params = $params[0];
}
$s = '';
- foreach( $params as $value) {
- if( $value instanceof LikeMatch ) {
+
+ foreach ( $params as $value ) {
+ if ( $value instanceof LikeMatch ) {
$s .= $value->toString();
} else {
- $s .= $this->escapeLike( $value );
+ $s .= $this->escapeLikeInternal( $value );
}
}
+
return " LIKE '" . $s . "' ";
}
@@ -1580,9 +1853,12 @@ abstract class DatabaseBase {
* However if you do this, you run the risk of encountering errors which wouldn't have
* occurred in MySQL
*
- * @todo migrate comment to phodocumentor format
+ * @param $table String: The table to replace the row(s) in.
+ * @param $uniqueIndexes Array: An associative array of indexes
+ * @param $rows Array: Array of rows to replace
+ * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
- function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
+ function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
$table = $this->tableName( $table );
# Single row case
@@ -1590,16 +1866,19 @@ abstract class DatabaseBase {
$rows = array( $rows );
}
- $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES ';
+ $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) . ') VALUES ';
$first = true;
+
foreach ( $rows as $row ) {
if ( $first ) {
$first = false;
} else {
$sql .= ',';
}
+
$sql .= '(' . $this->makeList( $row ) . ')';
}
+
return $this->query( $sql, $fname );
}
@@ -1619,14 +1898,15 @@ abstract class DatabaseBase {
* @param $conds Array: Condition array of field names mapped to variables, ANDed together in the WHERE clause
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) {
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
$joinTable = $this->tableName( $joinTable );
$sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
+
if ( $conds != '*' ) {
$sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
}
@@ -1640,16 +1920,17 @@ abstract class DatabaseBase {
function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
$sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
- $res = $this->query( $sql, 'Database::textFieldSize' );
+ $res = $this->query( $sql, 'DatabaseBase::textFieldSize' );
$row = $this->fetchObject( $res );
- $this->freeResult( $res );
$m = array();
+
if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
$size = $m[1];
} else {
$size = -1;
}
+
return $size;
}
@@ -1669,48 +1950,59 @@ abstract class DatabaseBase {
*
* Use $conds == "*" to delete all rows
*/
- function delete( $table, $conds, $fname = 'Database::delete' ) {
+ function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
+ throw new DBUnexpectedError( $this, 'DatabaseBase::delete() called with no conditions' );
}
+
$table = $this->tableName( $table );
$sql = "DELETE FROM $table";
+
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
+
return $this->query( $sql, $fname );
}
/**
* INSERT SELECT wrapper
* $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
+ * Source items may be literals rather than field names, but strings should be quoted with DatabaseBase::addQuotes()
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect',
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseBase::insertSelect',
$insertOptions = array(), $selectOptions = array() )
{
$destTable = $this->tableName( $destTable );
+
if ( is_array( $insertOptions ) ) {
$insertOptions = implode( ' ', $insertOptions );
}
- if( !is_array( $selectOptions ) ) {
+
+ if ( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
}
+
list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
- if( is_array( $srcTable ) ) {
+
+ if ( is_array( $srcTable ) ) {
$srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
} else {
$srcTable = $this->tableName( $srcTable );
}
+
$sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
" FROM $srcTable $useIndex ";
+
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
+
$sql .= " $tailOpts";
+
return $this->query( $sql, $fname );
}
@@ -1732,20 +2024,22 @@ abstract class DatabaseBase {
* @param $limit Integer: the SQL limit
* @param $offset Integer the SQL offset (default false)
*/
- function limitResult( $sql, $limit, $offset=false ) {
- if( !is_numeric( $limit ) ) {
+ function limitResult( $sql, $limit, $offset = false ) {
+ if ( !is_numeric( $limit ) ) {
throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
}
+
return "$sql LIMIT "
- . ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" )
+ . ( ( is_numeric( $offset ) && $offset != 0 ) ? "{$offset}," : "" )
. "{$limit} ";
}
+
function limitResultForUpdate( $sql, $num ) {
return $this->limitResult( $sql, $num, 0 );
}
/**
- * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
+ * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
* within the UNION construct.
* @return Boolean
*/
@@ -1761,9 +2055,9 @@ abstract class DatabaseBase {
* @param $all Boolean: use UNION ALL
* @return String: SQL fragment
*/
- function unionQueries($sqls, $all) {
+ function unionQueries( $sqls, $all ) {
$glue = $all ? ') UNION ALL (' : ') UNION (';
- return '('.implode( $glue, $sqls ) . ')';
+ return '(' . implode( $glue, $sqls ) . ')';
}
/**
@@ -1792,6 +2086,16 @@ abstract class DatabaseBase {
}
/**
+ * Convert a field to an unix timestamp
+ *
+ * @param $field String: field name
+ * @return String: SQL statement
+ */
+ public function unixTimestamp( $field ) {
+ return "EXTRACT(epoch FROM $field)";
+ }
+
+ /**
* Determines if the last failure was due to a deadlock
* STUB
*/
@@ -1800,7 +2104,7 @@ abstract class DatabaseBase {
}
/**
- * Determines if the last query error was something that should be dealt
+ * Determines if the last query error was something that should be dealt
* with by pinging the connection and reissuing the query.
* STUB
*/
@@ -1833,18 +2137,20 @@ abstract class DatabaseBase {
* reached.
*/
function deadlockLoop() {
- $myFname = 'Database::deadlockLoop';
+ $myFname = 'DatabaseBase::deadlockLoop';
$this->begin();
$args = func_get_args();
$function = array_shift( $args );
$oldIgnore = $this->ignoreErrors( true );
$tries = DEADLOCK_TRIES;
+
if ( is_array( $function ) ) {
$fname = $function[0];
} else {
$fname = $function;
}
+
do {
$retVal = call_user_func_array( $function, $args );
$error = $this->lastError();
@@ -1859,14 +2165,16 @@ abstract class DatabaseBase {
$this->reportQueryError( $error, $errno, $sql, $fname );
}
}
- } while( $this->wasDeadlock() && --$tries > 0 );
+ } while ( $this->wasDeadlock() && --$tries > 0 );
+
$this->ignoreErrors( $oldIgnore );
+
if ( $tries <= 0 ) {
- $this->query( 'ROLLBACK', $myFname );
+ $this->rollback( $myFname );
$this->reportQueryError( $error, $errno, $sql, $fname );
return false;
} else {
- $this->query( 'COMMIT', $myFname );
+ $this->commit( $myFname );
return $retVal;
}
}
@@ -1878,7 +2186,7 @@ abstract class DatabaseBase {
* @param $timeout Integer: the maximum number of seconds to wait for synchronisation
*/
function masterPosWait( MySQLMasterPos $pos, $timeout ) {
- $fname = 'Database::masterPosWait';
+ $fname = 'DatabaseBase::masterPosWait';
wfProfileIn( $fname );
# Commit any open transactions
@@ -1887,7 +2195,8 @@ abstract class DatabaseBase {
}
if ( !is_null( $this->mFakeSlaveLag ) ) {
- $wait = intval( ( $pos->pos - microtime(true) + $this->mFakeSlaveLag ) * 1e6 );
+ $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
+
if ( $wait > $timeout * 1e6 ) {
wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
wfProfileOut( $fname );
@@ -1909,8 +2218,8 @@ abstract class DatabaseBase {
$encPos = intval( $pos->pos );
$sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
$res = $this->doQuery( $sql );
+
if ( $res && $row = $this->fetchRow( $res ) ) {
- $this->freeResult( $res );
wfProfileOut( $fname );
return $row[0];
} else {
@@ -1924,14 +2233,16 @@ abstract class DatabaseBase {
*/
function getSlavePos() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
- $pos = new MySQLMasterPos( 'fake', microtime(true) - $this->mFakeSlaveLag );
- wfDebug( __METHOD__.": fake slave pos = $pos\n" );
+ $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
+ wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
return $pos;
}
- $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' );
+
+ $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
$row = $this->fetchObject( $res );
+
if ( $row ) {
- $pos = isset($row->Exec_master_log_pos) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
+ $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
} else {
return false;
@@ -1945,8 +2256,10 @@ abstract class DatabaseBase {
if ( $this->mFakeMaster ) {
return new MySQLMasterPos( 'fake', microtime( true ) );
}
- $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' );
+
+ $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
$row = $this->fetchObject( $res );
+
if ( $row ) {
return new MySQLMasterPos( $row->File, $row->Position );
} else {
@@ -1957,7 +2270,7 @@ abstract class DatabaseBase {
/**
* Begin a transaction, committing any previously open transaction
*/
- function begin( $fname = 'Database::begin' ) {
+ function begin( $fname = 'DatabaseBase::begin' ) {
$this->query( 'BEGIN', $fname );
$this->mTrxLevel = 1;
}
@@ -1965,25 +2278,30 @@ abstract class DatabaseBase {
/**
* End a transaction
*/
- function commit( $fname = 'Database::commit' ) {
- $this->query( 'COMMIT', $fname );
- $this->mTrxLevel = 0;
+ function commit( $fname = 'DatabaseBase::commit' ) {
+ if ( $this->mTrxLevel ) {
+ $this->query( 'COMMIT', $fname );
+ $this->mTrxLevel = 0;
+ }
}
/**
* Rollback a transaction.
* No-op on non-transactional databases.
*/
- function rollback( $fname = 'Database::rollback' ) {
- $this->query( 'ROLLBACK', $fname, true );
- $this->mTrxLevel = 0;
+ function rollback( $fname = 'DatabaseBase::rollback' ) {
+ if ( $this->mTrxLevel ) {
+ $this->query( 'ROLLBACK', $fname, true );
+ $this->mTrxLevel = 0;
+ }
}
/**
* Begin a transaction, committing any previously open transaction
* @deprecated use begin()
*/
- function immediateBegin( $fname = 'Database::immediateBegin' ) {
+ function immediateBegin( $fname = 'DatabaseBase::immediateBegin' ) {
+ wfDeprecated( __METHOD__ );
$this->begin();
}
@@ -1991,7 +2309,8 @@ abstract class DatabaseBase {
* Commit transaction, if one is open
* @deprecated use commit()
*/
- function immediateCommit( $fname = 'Database::immediateCommit' ) {
+ function immediateCommit( $fname = 'DatabaseBase::immediateCommit' ) {
+ wfDeprecated( __METHOD__ );
$this->commit();
}
@@ -2004,24 +2323,25 @@ abstract class DatabaseBase {
* @param $oldName String: name of table whose structure should be copied
* @param $newName String: name of table to be created
* @param $temporary Boolean: whether the new table should be temporary
+ * @param $fname String: calling function name
* @return Boolean: true if operation was successful
*/
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'Database::duplicateTableStructure' ) {
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseBase::duplicateTableStructure' ) {
throw new MWException( 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
}
/**
* Return MW-style timestamp used for MySQL schema
*/
- function timestamp( $ts=0 ) {
- return wfTimestamp(TS_MW,$ts);
+ function timestamp( $ts = 0 ) {
+ return wfTimestamp( TS_MW, $ts );
}
/**
* Local database timestamp format or null
*/
function timestampOrNull( $ts = null ) {
- if( is_null( $ts ) ) {
+ if ( is_null( $ts ) ) {
return null;
} else {
return $this->timestamp( $ts );
@@ -2032,7 +2352,7 @@ abstract class DatabaseBase {
* @todo document
*/
function resultObject( $result ) {
- if( empty( $result ) ) {
+ if ( empty( $result ) ) {
return false;
} elseif ( $result instanceof ResultWrapper ) {
return $result;
@@ -2047,29 +2367,11 @@ abstract class DatabaseBase {
/**
* Return aggregated value alias
*/
- function aggregateValue ($valuedata,$valuename='value') {
+ function aggregateValue ( $valuedata, $valuename = 'value' ) {
return $valuename;
}
/**
- * Returns a wikitext link to the DB's website, e.g.,
- * return "[http://www.mysql.com/ MySQL]";
- * Should at least contain plain text, if for some reason
- * your database has no website.
- *
- * @return String: wikitext of a link to the server software's web site
- */
- abstract function getSoftwareLink();
-
- /**
- * A string describing the current software version, like from
- * mysql_get_server_info(). Will be listed on Special:Version, etc.
- *
- * @return String: Version information from the database
- */
- abstract function getServerVersion();
-
- /**
* Ping the server and try to reconnect if it there is no connection
*
* @return bool Success or failure
@@ -2081,52 +2383,24 @@ abstract class DatabaseBase {
/**
* Get slave lag.
- * At the moment, this will only work if the DB user has the PROCESS privilege
+ * Currently supported only by MySQL
+ * @return Database replication lag in seconds
*/
function getLag() {
- if ( !is_null( $this->mFakeSlaveLag ) ) {
- wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
- return $this->mFakeSlaveLag;
- }
- $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
- # Find slave SQL thread
- while ( $row = $this->fetchObject( $res ) ) {
- /* This should work for most situations - when default db
- * for thread is not specified, it had no events executed,
- * and therefore it doesn't know yet how lagged it is.
- *
- * Relay log I/O thread does not select databases.
- */
- if ( $row->User == 'system user' &&
- $row->State != 'Waiting for master to send event' &&
- $row->State != 'Connecting to master' &&
- $row->State != 'Queueing master event to the relay log' &&
- $row->State != 'Waiting for master update' &&
- $row->State != 'Requesting binlog dump' &&
- $row->State != 'Waiting to reconnect after a failed master event read' &&
- $row->State != 'Reconnecting after a failed master event read' &&
- $row->State != 'Registering slave on master'
- ) {
- # This is it, return the time (except -ve)
- if ( $row->Time > 0x7fffffff ) {
- return false;
- } else {
- return $row->Time;
- }
- }
- }
- return false;
+ return intval( $this->mFakeSlaveLag );
}
/**
* Get status information from SHOW STATUS in an associative array
*/
- function getStatus($which="%") {
+ function getStatus( $which = "%" ) {
$res = $this->query( "SHOW STATUS LIKE '{$which}'" );
$status = array();
- while ( $row = $this->fetchObject( $res ) ) {
+
+ foreach ( $res as $row ) {
$status[$row->Variable_name] = $row->Value;
}
+
return $status;
}
@@ -2137,11 +2411,11 @@ abstract class DatabaseBase {
return 0;
}
- function encodeBlob($b) {
+ function encodeBlob( $b ) {
return $b;
}
- function decodeBlob($b) {
+ function decodeBlob( $b ) {
return $b;
}
@@ -2161,56 +2435,79 @@ abstract class DatabaseBase {
* @param $filename String: File name to open
* @param $lineCallback Callback: Optional function called before reading each line
* @param $resultCallback Callback: Optional function called for each MySQL result
+ * @param $fname String: Calling function name or false if name should be generated dynamically
+ * using $filename
*/
- function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
+ function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false ) {
$fp = fopen( $filename, 'r' );
+
if ( false === $fp ) {
- if (!defined("MEDIAWIKI_INSTALL"))
+ if ( !defined( "MEDIAWIKI_INSTALL" ) )
throw new MWException( "Could not open \"{$filename}\".\n" );
else
return "Could not open \"{$filename}\".\n";
}
+
+ if ( !$fname ) {
+ $fname = __METHOD__ . "( $filename )";
+ }
+
try {
- $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
+ $error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname );
}
- catch( MWException $e ) {
- if ( defined("MEDIAWIKI_INSTALL") ) {
+ catch ( MWException $e ) {
+ if ( defined( "MEDIAWIKI_INSTALL" ) ) {
$error = $e->getMessage();
} else {
fclose( $fp );
throw $e;
}
}
-
+
fclose( $fp );
+
return $error;
}
/**
* Get the full path of a patch file. Originally based on archive()
- * from updaters.inc. Keep in mind this always returns a patch, as
+ * from updaters.inc. Keep in mind this always returns a patch, as
* it fails back to MySQL if no DB-specific patch can be found
*
* @param $patch String The name of the patch, like patch-something.sql
* @return String Full path to patch file
*/
- public static function patchPath( $patch ) {
- global $wgDBtype, $IP;
- if ( file_exists( "$IP/maintenance/$wgDBtype/archives/$patch" ) ) {
- return "$IP/maintenance/$wgDBtype/archives/$patch";
+ public function patchPath( $patch ) {
+ global $IP;
+
+ $dbType = $this->getType();
+ if ( file_exists( "$IP/maintenance/$dbType/archives/$patch" ) ) {
+ return "$IP/maintenance/$dbType/archives/$patch";
} else {
return "$IP/maintenance/archives/$patch";
}
}
/**
+ * Set variables to be used in sourceFile/sourceStream, in preference to the
+ * ones in $GLOBALS. If an array is set here, $GLOBALS will not be used at
+ * all. If it's set to false, $GLOBALS will be used.
+ *
+ * @param $vars False, or array mapping variable name to value.
+ */
+ function setSchemaVars( $vars ) {
+ $this->mSchemaVars = $vars;
+ }
+
+ /**
* Read and execute commands from an open file handle
* Returns true on success, error string or exception on failure (depending on object's error ignore settings)
* @param $fp String: File handle
* @param $lineCallback Callback: Optional function called before reading each line
* @param $resultCallback Callback: Optional function called for each MySQL result
+ * @param $fname String: Calling function name
*/
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false, $fname = 'DatabaseBase::sourceStream' ) {
$cmd = "";
$done = false;
$dollarquote = false;
@@ -2219,15 +2516,21 @@ abstract class DatabaseBase {
if ( $lineCallback ) {
call_user_func( $lineCallback );
}
+
$line = trim( fgets( $fp, 1024 ) );
$sl = strlen( $line ) - 1;
- if ( $sl < 0 ) { continue; }
- if ( '-' == $line{0} && '-' == $line{1} ) { continue; }
+ if ( $sl < 0 ) {
+ continue;
+ }
+
+ if ( '-' == $line { 0 } && '-' == $line { 1 } ) {
+ continue;
+ }
- ## Allow dollar quoting for function declarations
- if (substr($line,0,4) == '$mw$') {
- if ($dollarquote) {
+ # # Allow dollar quoting for function declarations
+ if ( substr( $line, 0, 4 ) == '$mw$' ) {
+ if ( $dollarquote ) {
$dollarquote = false;
$done = true;
}
@@ -2235,20 +2538,24 @@ abstract class DatabaseBase {
$dollarquote = true;
}
}
- else if (!$dollarquote) {
- if ( ';' == $line{$sl} && ($sl < 2 || ';' != $line{$sl - 1})) {
+ else if ( !$dollarquote ) {
+ if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) {
$done = true;
$line = substr( $line, 0, $sl );
}
}
- if ( $cmd != '' ) { $cmd .= ' '; }
+ if ( $cmd != '' ) {
+ $cmd .= ' ';
+ }
+
$cmd .= "$line\n";
if ( $done ) {
- $cmd = str_replace(';;', ";", $cmd);
+ $cmd = str_replace( ';;', ";", $cmd );
$cmd = $this->replaceVars( $cmd );
- $res = $this->query( $cmd, __METHOD__ );
+ $res = $this->query( $cmd, $fname );
+
if ( $resultCallback ) {
call_user_func( $resultCallback, $res, $this );
}
@@ -2262,41 +2569,76 @@ abstract class DatabaseBase {
$done = false;
}
}
+
return true;
}
+ /**
+ * Database independent variable replacement, replaces a set of variables
+ * in a sql statement with their contents as given by $this->getSchemaVars().
+ * Supports '{$var}' `{$var}` and / *$var* / (without the spaces) style variables
+ *
+ * '{$var}' should be used for text and is passed through the database's addQuotes method
+ * `{$var}` should be used for identifiers (eg: table and database names), it is passed through
+ * the database's addIdentifierQuotes method which can be overridden if the database
+ * uses something other than backticks.
+ * / *$var* / is just encoded, besides traditional dbprefix and tableoptions it's use should be avoided
+ *
+ * @param $ins String: SQL statement to replace variables in
+ * @return String The new SQL statement with variables replaced
+ */
+ protected function replaceSchemaVars( $ins ) {
+ $vars = $this->getSchemaVars();
+ foreach ( $vars as $var => $value ) {
+ // replace '{$var}'
+ $ins = str_replace( '\'{$' . $var . '}\'', $this->addQuotes( $value ), $ins );
+ // replace `{$var}`
+ $ins = str_replace( '`{$' . $var . '}`', $this->addIdentifierQuotes( $value ), $ins );
+ // replace /*$var*/
+ $ins = str_replace( '/*$' . $var . '*/', $this->strencode( $value ) , $ins );
+ }
+ return $ins;
+ }
/**
* Replace variables in sourced SQL
*/
protected function replaceVars( $ins ) {
- $varnames = array(
- 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser',
- 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword',
- 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions',
- );
-
- // Ordinary variables
- foreach ( $varnames as $var ) {
- if( isset( $GLOBALS[$var] ) ) {
- $val = addslashes( $GLOBALS[$var] ); // FIXME: safety check?
- $ins = str_replace( '{$' . $var . '}', $val, $ins );
- $ins = str_replace( '/*$' . $var . '*/`', '`' . $val, $ins );
- $ins = str_replace( '/*$' . $var . '*/', $val, $ins );
- }
- }
+ $ins = $this->replaceSchemaVars( $ins );
// Table prefixes
$ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
array( $this, 'tableNameCallback' ), $ins );
// Index names
- $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
+ $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
array( $this, 'indexNameCallback' ), $ins );
+
return $ins;
}
/**
+ * Get schema variables. If none have been set via setSchemaVars(), then
+ * use some defaults from the current object.
+ */
+ protected function getSchemaVars() {
+ if ( $this->mSchemaVars ) {
+ return $this->mSchemaVars;
+ } else {
+ return $this->getDefaultSchemaVars();
+ }
+ }
+
+ /**
+ * Get schema variables to use if none have been set via setSchemaVars().
+ * Override this in derived classes to provide variables for tables.sql
+ * and SQL patch files.
+ */
+ protected function getDefaultSchemaVars() {
+ return array();
+ }
+
+ /**
* Table name callback
* @private
*/
@@ -2319,16 +2661,17 @@ abstract class DatabaseBase {
function buildConcat( $stringList ) {
return 'CONCAT(' . implode( ',', $stringList ) . ')';
}
-
+
/**
* Acquire a named lock
- *
+ *
* Abstracted from Filestore::lock() so child classes can implement for
* their own needs.
- *
- * @param $lockName String: Name of lock to aquire
- * @param $method String: Name of method calling us
- * @return bool
+ *
+ * @param $lockName String: name of lock to aquire
+ * @param $method String: name of method calling us
+ * @param $timeout Integer: timeout
+ * @return Boolean
*/
public function lock( $lockName, $method, $timeout = 5 ) {
return true;
@@ -2336,13 +2679,12 @@ abstract class DatabaseBase {
/**
* Release a lock.
- *
+ *
* @param $lockName String: Name of lock to release
* @param $method String: Name of method calling us
*
- * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
* @return Returns 1 if the lock was released, 0 if the lock was not established
- * by this thread (in which case the lock is not released), and NULL if the named
+ * by this thread (in which case the lock is not released), and NULL if the named
* lock did not exist
*/
public function unlock( $lockName, $method ) {
@@ -2360,7 +2702,7 @@ abstract class DatabaseBase {
public function lockTables( $read, $write, $method, $lowPriority = true ) {
return true;
}
-
+
/**
* Unlock specific tables
*
@@ -2369,31 +2711,30 @@ abstract class DatabaseBase {
public function unlockTables( $method ) {
return true;
}
-
+
/**
- * Get search engine class. All subclasses of this
- * need to implement this if they wish to use searching.
- *
+ * Get search engine class. All subclasses of this need to implement this
+ * if they wish to use searching.
+ *
* @return String
*/
public function getSearchEngine() {
- return "SearchMySQL";
+ return 'SearchEngineDummy';
}
/**
- * Allow or deny "big selects" for this session only. This is done by setting
+ * Allow or deny "big selects" for this session only. This is done by setting
* the sql_big_selects session variable.
*
- * This is a MySQL-specific feature.
+ * This is a MySQL-specific feature.
*
- * @param mixed $value true for allow, false for deny, or "default" to restore the initial value
+ * @param $value Mixed: true for allow, false for deny, or "default" to restore the initial value
*/
public function setBigSelects( $value = true ) {
// no-op
}
}
-
/******************************************************************************
* Utility classes
*****************************************************************************/
@@ -2405,7 +2746,7 @@ abstract class DatabaseBase {
class DBObject {
public $mData;
- function DBObject($data) {
+ function __construct( $data ) {
$this->mData = $data;
}
@@ -2426,65 +2767,44 @@ class DBObject {
*/
class Blob {
private $mData;
- function __construct($data) {
+
+ function __construct( $data ) {
$this->mData = $data;
}
+
function fetch() {
return $this->mData;
}
}
/**
- * Utility class.
+ * Base for all database-specific classes representing information about database fields
* @ingroup Database
*/
-class MySQLField {
- private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type;
- function __construct ($info) {
- $this->name = $info->name;
- $this->tablename = $info->table;
- $this->default = $info->def;
- $this->max_length = $info->max_length;
- $this->nullable = !$info->not_null;
- $this->is_pk = $info->primary_key;
- $this->is_unique = $info->unique_key;
- $this->is_multiple = $info->multiple_key;
- $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
- $this->type = $info->type;
- }
-
- function name() {
- return $this->name;
- }
-
- function tableName() {
- return $this->tableName;
- }
-
- function defaultValue() {
- return $this->default;
- }
-
- function maxLength() {
- return $this->max_length;
- }
-
- function nullable() {
- return $this->nullable;
- }
+interface Field {
+ /**
+ * Field name
+ * @return string
+ */
+ function name();
- function isKey() {
- return $this->is_key;
- }
+ /**
+ * Name of table this field belongs to
+ * @return string
+ */
+ function tableName();
- function isMultipleKey() {
- return $this->is_multiple;
- }
+ /**
+ * Database type
+ * @return string
+ */
+ function type();
- function type() {
- return $this->type;
- }
+ /**
+ * Whether this field can store NULL values
+ * @return bool
+ */
+ function isNullable();
}
/******************************************************************************
@@ -2510,10 +2830,13 @@ class DBError extends MWException {
function getText() {
global $wgShowDBErrorBacktrace;
+
$s = $this->getMessage() . "\n";
+
if ( $wgShowDBErrorBacktrace ) {
$s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
}
+
return $s;
}
}
@@ -2523,13 +2846,16 @@ class DBError extends MWException {
*/
class DBConnectionError extends DBError {
public $error;
-
+
function __construct( DatabaseBase &$db, $error = 'unknown error' ) {
$msg = 'DB connection error';
+
if ( trim( $error ) != '' ) {
$msg .= ": $error";
}
+
$this->error = $error;
+
parent::__construct( $db, $msg );
}
@@ -2542,7 +2868,7 @@ class DBConnectionError extends DBError {
// Not likely to work
return false;
}
-
+
function getLogMessage() {
# Don't send to the exception log
return false;
@@ -2550,11 +2876,13 @@ class DBConnectionError extends DBError {
function getPageTitle() {
global $wgSitename, $wgLang;
+
$header = "$wgSitename has a problem";
+
if ( $wgLang instanceof Language ) {
$header = htmlspecialchars( $wgLang->getMessage( 'dberr-header' ) );
}
-
+
return $header;
}
@@ -2577,7 +2905,7 @@ class DBConnectionError extends DBError {
}
if ( trim( $this->error ) == '' ) {
- $this->error = $this->db->getProperty('mServer');
+ $this->error = $this->db->getProperty( 'mServer' );
}
$noconnect = "<p><strong>$sorry</strong><br />$again</p><p><small>$info</small></p>";
@@ -2589,33 +2917,38 @@ class DBConnectionError extends DBError {
$extra = $this->searchForm();
- if( $wgUseFileCache ) {
+ if ( $wgUseFileCache ) {
try {
$cache = $this->fileCachedPage();
# Cached version on file system?
- if( $cache !== null ) {
+ if ( $cache !== null ) {
# Hack: extend the body for error messages
- $cache = str_replace( array('</html>','</body>'), '', $cache );
+ $cache = str_replace( array( '</html>', '</body>' ), '', $cache );
# Add cache notice...
$cachederror = "This is a cached copy of the requested page, and may not be up to date. ";
+
# Localize it if possible...
- if( $wgLang instanceof Language ) {
+ if ( $wgLang instanceof Language ) {
$cachederror = htmlspecialchars( $wgLang->getMessage( 'dberr-cachederror' ) );
}
+
$warning = "<div style='color:red;font-size:150%;font-weight:bold;'>$cachederror</div>";
+
# Output cached page with notices on bottom and re-close body
return "{$cache}{$warning}<hr />$text<hr />$extra</body></html>";
}
- } catch( MWException $e ) {
+ } catch ( MWException $e ) {
// Do nothing, just use the default page
}
}
+
# Headers needed here - output is just the error message
- return $this->htmlHeader()."$text<hr />$extra".$this->htmlFooter();
+ return $this->htmlHeader() . "$text<hr />$extra" . $this->htmlFooter();
}
function searchForm() {
- global $wgSitename, $wgServer, $wgLang, $wgInputEncoding;
+ global $wgSitename, $wgServer, $wgLang;
+
$usegoogle = "You can try searching via Google in the meantime.";
$outofdate = "Note that their indexes of our content may be out of date.";
$googlesearch = "Search";
@@ -2626,23 +2959,26 @@ class DBConnectionError extends DBError {
$googlesearch = htmlspecialchars( $wgLang->getMessage( 'searchbutton' ) );
}
- $search = htmlspecialchars(@$_REQUEST['search']);
+ $search = htmlspecialchars( @$_REQUEST['search'] );
+
+ $server = htmlspecialchars( $wgServer );
+ $sitename = htmlspecialchars( $wgSitename );
$trygoogle = <<<EOT
<div style="margin: 1.5em">$usegoogle<br />
<small>$outofdate</small></div>
<!-- SiteSearch Google -->
<form method="get" action="http://www.google.com/search" id="googlesearch">
- <input type="hidden" name="domains" value="$wgServer" />
- <input type="hidden" name="num" value="50" />
- <input type="hidden" name="ie" value="$wgInputEncoding" />
- <input type="hidden" name="oe" value="$wgInputEncoding" />
+ <input type="hidden" name="domains" value="$server" />
+ <input type="hidden" name="num" value="50" />
+ <input type="hidden" name="ie" value="UTF-8" />
+ <input type="hidden" name="oe" value="UTF-8" />
- <input type="text" name="q" size="31" maxlength="255" value="$search" />
- <input type="submit" name="btnG" value="$googlesearch" />
+ <input type="text" name="q" size="31" maxlength="255" value="$search" />
+ <input type="submit" name="btnG" value="$googlesearch" />
<div>
- <input type="radio" name="sitesearch" id="gwiki" value="$wgServer" checked="checked" /><label for="gwiki">$wgSitename</label>
- <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
+ <input type="radio" name="sitesearch" id="gwiki" value="$server" checked="checked" /><label for="gwiki">$sitename</label>
+ <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
</div>
</form>
<!-- SiteSearch Google -->
@@ -2651,33 +2987,35 @@ EOT;
}
function fileCachedPage() {
- global $wgTitle, $title, $wgLang, $wgOut;
- if( $wgOut->isDisabled() ) return; // Done already?
+ global $wgTitle, $wgLang, $wgOut;
+
+ if ( $wgOut->isDisabled() ) {
+ return; // Done already?
+ }
+
$mainpage = 'Main Page';
+
if ( $wgLang instanceof Language ) {
$mainpage = htmlspecialchars( $wgLang->getMessage( 'mainpage' ) );
}
- if( $wgTitle ) {
+ if ( $wgTitle ) {
$t =& $wgTitle;
- } elseif( $title ) {
- $t = Title::newFromURL( $title );
} else {
$t = Title::newFromText( $mainpage );
}
$cache = new HTMLFileCache( $t );
- if( $cache->isFileCached() ) {
+ if ( $cache->isFileCached() ) {
return $cache->fetchPageText();
} else {
return '';
}
}
-
+
function htmlBodyOnly() {
return true;
}
-
}
/**
@@ -2685,14 +3023,15 @@ EOT;
*/
class DBQueryError extends DBError {
public $error, $errno, $sql, $fname;
-
+
function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) {
- $message = "A database error has occurred\n" .
+ $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
"Query: $sql\n" .
"Function: $fname\n" .
"Error: $errno $error\n";
parent::__construct( $db, $message );
+
$this->error = $error;
$this->errno = $errno;
$this->sql = $sql;
@@ -2701,27 +3040,31 @@ class DBQueryError extends DBError {
function getText() {
global $wgShowDBErrorBacktrace;
+
if ( $this->useMessageCache() ) {
$s = wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
+
if ( $wgShowDBErrorBacktrace ) {
$s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
}
+
return $s;
} else {
return parent::getText();
}
}
-
+
function getSQL() {
global $wgShowSQLErrors;
- if( !$wgShowSQLErrors ) {
+
+ if ( !$wgShowSQLErrors ) {
return $this->msg( 'sqlhidden', 'SQL hidden' );
} else {
return $this->sql;
}
}
-
+
function getLogMessage() {
# Don't send to the exception log
return false;
@@ -2733,15 +3076,18 @@ class DBQueryError extends DBError {
function getHTML() {
global $wgShowDBErrorBacktrace;
+
if ( $this->useMessageCache() ) {
$s = wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) );
} else {
$s = nl2br( htmlspecialchars( $this->getMessage() ) );
}
+
if ( $wgShowDBErrorBacktrace ) {
$s .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
}
+
return $s;
}
}
@@ -2762,8 +3108,9 @@ class ResultWrapper implements Iterator {
/**
* Create a new result object from a result resource and a Database object
*/
- function ResultWrapper( $database, $result ) {
+ function __construct( $database, $result ) {
$this->db = $database;
+
if ( $result instanceof ResultWrapper ) {
$this->result = $result->result;
} else {
@@ -2783,7 +3130,6 @@ class ResultWrapper implements Iterator {
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
*
- * @param $res SQL result object as returned from Database::query(), etc.
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
@@ -2795,7 +3141,6 @@ class ResultWrapper implements Iterator {
* Fetch the next row from the given result object, in associative array
* form. Fields are retrieved with $row['fieldname'].
*
- * @param $res SQL result object as returned from Database::query(), etc.
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
@@ -2827,8 +3172,8 @@ class ResultWrapper implements Iterator {
*/
function rewind() {
- if ($this->numRows()) {
- $this->db->dataSeek($this, 0);
+ if ( $this->numRows() ) {
+ $this->db->dataSeek( $this, 0 );
}
$this->pos = 0;
$this->currentRow = null;
@@ -2857,8 +3202,49 @@ class ResultWrapper implements Iterator {
}
/**
+ * Overloads the relevant methods of the real ResultsWrapper so it
+ * doesn't go anywhere near an actual database.
+ */
+class FakeResultWrapper extends ResultWrapper {
+ var $result = array();
+ var $db = null; // And it's going to stay that way :D
+ var $pos = 0;
+ var $currentRow = null;
+
+ function __construct( $array ) {
+ $this->result = $array;
+ }
+
+ function numRows() {
+ return count( $this->result );
+ }
+
+ function fetchRow() {
+ $this->currentRow = $this->result[$this->pos++];
+ return $this->currentRow;
+ }
+
+ function seek( $row ) {
+ $this->pos = $row;
+ }
+
+ function free() {}
+
+ // Callers want to be able to access fields with $this->fieldName
+ function fetchObject() {
+ $this->currentRow = $this->result[$this->pos++];
+ return (object)$this->currentRow;
+ }
+
+ function rewind() {
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
+}
+
+/**
* Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE clauses
- * and thus need no escaping. Don't instantiate it manually, use Database::anyChar() and anyString() instead.
+ * and thus need no escaping. Don't instantiate it manually, use DatabaseBase::anyChar() and anyString() instead.
*/
class LikeMatch {
private $str;
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
index 9b62af82..becca11e 100644
--- a/includes/db/DatabaseIbm_db2.php
+++ b/includes/db/DatabaseIbm_db2.php
@@ -1,10 +1,11 @@
<?php
/**
- * This script is the IBM DB2 database abstraction layer
+ * This is the IBM DB2 database abstraction layer.
+ * See maintenance/ibm_db2/README for development notes
+ * and other specific information
*
- * See maintenance/ibm_db2/README for development notes and other specific information
- * @ingroup Database
* @file
+ * @ingroup Database
* @author leo.petr+mediawiki@gmail.com
*/
@@ -12,7 +13,7 @@
* This represents a column in a DB2 database
* @ingroup Database
*/
-class IBM_DB2Field {
+class IBM_DB2Field implements Field {
private $name = '';
private $tablename = '';
private $type = '';
@@ -20,32 +21,36 @@ class IBM_DB2Field {
private $max_length = 0;
/**
- * Builder method for the class
+ * Builder method for the class
* @param $db DatabaseIbm_db2: Database interface
* @param $table String: table name
* @param $field String: column name
* @return IBM_DB2Field
*/
- static function fromText($db, $table, $field) {
+ static function fromText( $db, $table, $field ) {
global $wgDBmwschema;
$q = <<<SQL
SELECT
-lcase(coltype) AS typname,
+lcase( coltype ) AS typname,
nulls AS attnotnull, length AS attlen
FROM sysibm.syscolumns
WHERE tbcreator=%s AND tbname=%s AND name=%s;
SQL;
- $res = $db->query(sprintf($q,
- $db->addQuotes($wgDBmwschema),
- $db->addQuotes($table),
- $db->addQuotes($field)));
- $row = $db->fetchObject($res);
- if (!$row)
+ $res = $db->query(
+ sprintf( $q,
+ $db->addQuotes( $wgDBmwschema ),
+ $db->addQuotes( $table ),
+ $db->addQuotes( $field )
+ )
+ );
+ $row = $db->fetchObject( $res );
+ if ( !$row ) {
return null;
+ }
$n = new IBM_DB2Field;
$n->type = $row->typname;
- $n->nullable = ($row->attnotnull == 'N');
+ $n->nullable = ( $row->attnotnull == 'N' );
$n->name = $field;
$n->tablename = $table;
$n->max_length = $row->attlen;
@@ -70,7 +75,7 @@ SQL;
* Can column be null?
* @return bool true or false
*/
- function nullable() { return $this->nullable; }
+ function isNullable() { return $this->nullable; }
/**
* How much can you fit in the column per row?
* @return int length
@@ -85,18 +90,17 @@ SQL;
class IBM_DB2Blob {
private $mData;
- public function __construct($data) {
+ public function __construct( $data ) {
$this->mData = $data;
}
public function getData() {
return $this->mData;
}
-
- public function __toString()
- {
- return $this->mData;
- }
+
+ public function __toString() {
+ return $this->mData;
+ }
}
/**
@@ -112,7 +116,6 @@ class DatabaseIbm_db2 extends DatabaseBase {
protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
protected $mOut, $mOpened = false;
- protected $mFailFunction;
protected $mTablePrefix;
protected $mFlags;
protected $mTrxLevel = 0;
@@ -121,207 +124,50 @@ class DatabaseIbm_db2 extends DatabaseBase {
protected $mFakeSlaveLag = null, $mFakeMaster = false;
*
*/
-
- /// Server port for uncataloged connections
+
+ /** Database server port */
protected $mPort = null;
- /// Whether connection is cataloged
- protected $mCataloged = null;
- /// Schema for tables, stored procedures, triggers
+ /** Schema for tables, stored procedures, triggers */
protected $mSchema = null;
- /// Whether the schema has been applied in this session
+ /** Whether the schema has been applied in this session */
protected $mSchemaSet = false;
- /// Result of last query
+ /** Result of last query */
protected $mLastResult = null;
- /// Number of rows affected by last INSERT/UPDATE/DELETE
+ /** Number of rows affected by last INSERT/UPDATE/DELETE */
protected $mAffectedRows = null;
- /// Number of rows returned by last SELECT
+ /** Number of rows returned by last SELECT */
protected $mNumRows = null;
-
- /// Connection config options - see constructor
+
+ /** Connection config options - see constructor */
public $mConnOptions = array();
- /// Statement config options -- see constructor
+ /** Statement config options -- see constructor */
public $mStmtOptions = array();
-
-
- const CATALOGED = "cataloged";
- const UNCATALOGED = "uncataloged";
- const USE_GLOBAL = "get from global";
-
+
+ /** Default schema */
+ const USE_GLOBAL = 'mediawiki';
+
+ /** Option that applies to nothing */
const NONE_OPTION = 0x00;
+ /** Option that applies to connection objects */
const CONN_OPTION = 0x01;
+ /** Option that applies to statement objects */
const STMT_OPTION = 0x02;
-
+
+ /** Regular operation mode -- minimal debug messages */
const REGULAR_MODE = 'regular';
+ /** Installation mode -- lots of debug messages */
const INSTALL_MODE = 'install';
-
- // Whether this is regular operation or the initial installation
+
+ /** Controls the level of debug message output */
protected $mMode = self::REGULAR_MODE;
-
- /// Last sequence value used for a primary key
+
+ /** Last sequence value used for a primary key */
protected $mInsertId = null;
-
- /*
- * These can be safely inherited
- *
- * Getter/Setter: (18)
- * failFunction
- * setOutputPage
- * bufferResults
- * ignoreErrors
- * trxLevel
- * errorCount
- * getLBInfo
- * setLBInfo
- * lastQuery
- * isOpen
- * setFlag
- * clearFlag
- * getFlag
- * getProperty
- * getDBname
- * getServer
- * tableNameCallback
- * tablePrefix
- *
- * Administrative: (8)
- * debug
- * installErrorHandler
- * restoreErrorHandler
- * connectionErrorHandler
- * reportConnectionError
- * sourceFile
- * sourceStream
- * replaceVars
- *
- * Database: (5)
- * query
- * set
- * selectField
- * generalizeSQL
- * update
- * strreplace
- * deadlockLoop
- *
- * Prepared Statement: 6
- * prepare
- * freePrepared
- * execute
- * safeQuery
- * fillPrepared
- * fillPreparedArg
- *
- * Slave/Master: (4)
- * masterPosWait
- * getSlavePos
- * getMasterPos
- * getLag
- *
- * Generation: (9)
- * tableNames
- * tableNamesN
- * tableNamesWithUseIndexOrJOIN
- * escapeLike
- * delete
- * insertSelect
- * timestampOrNull
- * resultObject
- * aggregateValue
- * selectSQLText
- * selectRow
- * makeUpdateOptions
- *
- * Reflection: (1)
- * indexExists
- */
-
- /*
- * These have been implemented
- *
- * Administrative: 7 / 7
- * constructor [Done]
- * open [Done]
- * openCataloged [Done]
- * close [Done]
- * newFromParams [Done]
- * openUncataloged [Done]
- * setup_database [Done]
- *
- * Getter/Setter: 13 / 13
- * cascadingDeletes [Done]
- * cleanupTriggers [Done]
- * strictIPs [Done]
- * realTimestamps [Done]
- * impliciGroupby [Done]
- * implicitOrderby [Done]
- * searchableIPs [Done]
- * functionalIndexes [Done]
- * getWikiID [Done]
- * isOpen [Done]
- * getServerVersion [Done]
- * getSoftwareLink [Done]
- * getSearchEngine [Done]
- *
- * Database driver wrapper: 23 / 23
- * lastError [Done]
- * lastErrno [Done]
- * doQuery [Done]
- * tableExists [Done]
- * fetchObject [Done]
- * fetchRow [Done]
- * freeResult [Done]
- * numRows [Done]
- * numFields [Done]
- * fieldName [Done]
- * insertId [Done]
- * dataSeek [Done]
- * affectedRows [Done]
- * selectDB [Done]
- * strencode [Done]
- * conditional [Done]
- * wasDeadlock [Done]
- * ping [Done]
- * getStatus [Done]
- * setTimeout [Done]
- * lock [Done]
- * unlock [Done]
- * insert [Done]
- * select [Done]
- *
- * Slave/master: 2 / 2
- * setFakeSlaveLag [Done]
- * setFakeMaster [Done]
- *
- * Reflection: 6 / 6
- * fieldExists [Done]
- * indexInfo [Done]
- * fieldInfo [Done]
- * fieldType [Done]
- * indexUnique [Done]
- * textFieldSize [Done]
- *
- * Generation: 16 / 16
- * tableName [Done]
- * addQuotes [Done]
- * makeList [Done]
- * makeSelectOptions [Done]
- * estimateRowCount [Done]
- * nextSequenceValue [Done]
- * useIndexClause [Done]
- * replace [Done]
- * deleteJoin [Done]
- * lowPriorityOption [Done]
- * limitResult [Done]
- * limitResultForUpdate [Done]
- * timestamp [Done]
- * encodeBlob [Done]
- * decodeBlob [Done]
- * buildConcat [Done]
- */
-
+
######################################
# Getters and Setters
######################################
-
+
/**
* Returns true if this database supports (and uses) cascading deletes
*/
@@ -330,20 +176,22 @@ class DatabaseIbm_db2 extends DatabaseBase {
}
/**
- * Returns true if this database supports (and uses) triggers (e.g. on the page table)
+ * Returns true if this database supports (and uses) triggers (e.g. on the
+ * page table)
*/
function cleanupTriggers() {
return true;
}
/**
- * Returns true if this database is strict about what can be put into an IP field.
+ * Returns true if this database is strict about what can be put into an
+ * IP field.
* Specifically, it uses a NULL value instead of an empty string.
*/
function strictIPs() {
return true;
}
-
+
/**
* Returns true if this database uses timestamps rather than integers
*/
@@ -359,7 +207,8 @@ class DatabaseIbm_db2 extends DatabaseBase {
}
/**
- * Returns true if this database does an implicit order by when the column has an index
+ * Returns true if this database does an implicit order by when the column
+ * has an index
* For example: SELECT page_title FROM page LIMIT 1
*/
function implicitOrderby() {
@@ -380,7 +229,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
function functionalIndexes() {
return true;
}
-
+
/**
* Returns a unique string representing the wiki on the server
*/
@@ -395,153 +244,148 @@ class DatabaseIbm_db2 extends DatabaseBase {
function getType() {
return 'ibm_db2';
}
-
- ######################################
- # Setup
- ######################################
-
-
+
/**
- *
+ *
* @param $server String: hostname of database server
* @param $user String: username
* @param $password String: password
* @param $dbName String: database name on the server
- * @param $failFunction Callback (optional)
* @param $flags Integer: database behaviour flags (optional, unused)
* @param $schema String
*/
- public function DatabaseIbm_db2($server = false, $user = false, $password = false,
- $dbName = false, $failFunction = false, $flags = 0,
+ public function __construct( $server = false, $user = false,
+ $password = false,
+ $dbName = false, $flags = 0,
$schema = self::USE_GLOBAL )
{
+ global $wgDBmwschema;
- global $wgOut, $wgDBmwschema;
- # Can't get a reference if it hasn't been set yet
- if ( !isset( $wgOut ) ) {
- $wgOut = null;
- }
- $this->mOut =& $wgOut;
- $this->mFailFunction = $failFunction;
- $this->mFlags = DBO_TRX | $flags;
-
if ( $schema == self::USE_GLOBAL ) {
$this->mSchema = $wgDBmwschema;
- }
- else {
+ } else {
$this->mSchema = $schema;
}
-
+
// configure the connection and statement objects
- $this->setDB2Option('db2_attr_case', 'DB2_CASE_LOWER', self::CONN_OPTION | self::STMT_OPTION);
- $this->setDB2Option('deferred_prepare', 'DB2_DEFERRED_PREPARE_ON', self::STMT_OPTION);
- $this->setDB2Option('rowcount', 'DB2_ROWCOUNT_PREFETCH_ON', self::STMT_OPTION);
-
- $this->open( $server, $user, $password, $dbName);
+ $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
+ self::CONN_OPTION | self::STMT_OPTION );
+ $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
+ self::STMT_OPTION );
+ $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
+ self::STMT_OPTION );
+
+ parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
}
-
+
/**
* Enables options only if the ibm_db2 extension version supports them
* @param $name String: name of the option in the options array
* @param $const String: name of the constant holding the right option value
* @param $type Integer: whether this is a Connection or Statement otion
*/
- private function setDB2Option($name, $const, $type) {
- if (defined($const)) {
- if ($type & self::CONN_OPTION) $this->mConnOptions[$name] = constant($const);
- if ($type & self::STMT_OPTION) $this->mStmtOptions[$name] = constant($const);
- }
- else {
- $this->installPrint("$const is not defined. ibm_db2 version is likely too low.");
+ private function setDB2Option( $name, $const, $type ) {
+ if ( defined( $const ) ) {
+ if ( $type & self::CONN_OPTION ) {
+ $this->mConnOptions[$name] = constant( $const );
+ }
+ if ( $type & self::STMT_OPTION ) {
+ $this->mStmtOptions[$name] = constant( $const );
+ }
+ } else {
+ $this->installPrint(
+ "$const is not defined. ibm_db2 version is likely too low." );
}
}
-
+
/**
* Outputs debug information in the appropriate place
* @param $string String: the relevant debug message
*/
- private function installPrint($string) {
- wfDebug("$string");
- if ($this->mMode == self::INSTALL_MODE) {
- print "<li>$string</li>";
+ private function installPrint( $string ) {
+ wfDebug( "$string\n" );
+ if ( $this->mMode == self::INSTALL_MODE ) {
+ print "<li><pre>$string</pre></li>";
flush();
- }
+ }
}
-
+
/**
* Opens a database connection and returns it
* Closes any existing connection
- * @return a fresh connection
+ *
* @param $server String: hostname
* @param $user String
* @param $password String
* @param $dbName String: database name
+ * @return a fresh connection
*/
- public function open( $server, $user, $password, $dbName )
- {
+ public function open( $server, $user, $password, $dbName ) {
// Load the port number
- global $wgDBport_db2, $wgDBcataloged;
+ global $wgDBport;
wfProfileIn( __METHOD__ );
-
+
// Load IBM DB2 driver if missing
- if (!@extension_loaded('ibm_db2')) {
- @dl('ibm_db2.so');
- }
+ wfDl( 'ibm_db2' );
+
// Test for IBM DB2 support, to avoid suppressed fatal error
if ( !function_exists( 'db2_connect' ) ) {
- $error = "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?\n";
- $this->installPrint($error);
- $this->reportConnectionError($error);
+ $error = <<<ERROR
+DB2 functions missing, have you enabled the ibm_db2 extension for PHP?
+
+ERROR;
+ $this->installPrint( $error );
+ $this->reportConnectionError( $error );
}
- if (!strlen($user)) { // Copied from Postgres
+ if ( strlen( $user ) < 1 ) {
+ wfProfileOut( __METHOD__ );
return null;
}
-
+
// Close existing connection
$this->close();
// Cache conn info
$this->mServer = $server;
- $this->mPort = $port = $wgDBport_db2;
+ $this->mPort = $port = $wgDBport;
$this->mUser = $user;
$this->mPassword = $password;
$this->mDBname = $dbName;
- $this->mCataloged = $cataloged = $wgDBcataloged;
-
- if ( $cataloged == self::CATALOGED ) {
- $this->openCataloged($dbName, $user, $password);
- }
- elseif ( $cataloged == self::UNCATALOGED ) {
- $this->openUncataloged($dbName, $user, $password, $server, $port);
- }
+
+ $this->openUncataloged( $dbName, $user, $password, $server, $port );
+
// Apply connection config
- db2_set_option($this->mConn, $this->mConnOptions, 1);
- // Not all MediaWiki code is transactional
- // Rather, turn autocommit off in the begin function and turn on after a commit
- db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
+ db2_set_option( $this->mConn, $this->mConnOptions, 1 );
+ // Some MediaWiki code is still transaction-less (?).
+ // The strategy is to keep AutoCommit on for that code
+ // but switch it off whenever a transaction is begun.
+ db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
- if ( $this->mConn == false ) {
+ if ( !$this->mConn ) {
$this->installPrint( "DB connection error\n" );
- $this->installPrint( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- $this->installPrint( $this->lastError()."\n" );
+ $this->installPrint(
+ "Server: $server, Database: $dbName, User: $user, Password: "
+ . substr( $password, 0, 3 ) . "...\n" );
+ $this->installPrint( $this->lastError() . "\n" );
+
+ wfProfileOut( __METHOD__ );
return null;
}
$this->mOpened = true;
$this->applySchema();
-
+
wfProfileOut( __METHOD__ );
return $this->mConn;
}
-
+
/**
* Opens a cataloged database connection, sets mConn
*/
- protected function openCataloged( $dbName, $user, $password )
- {
- @$this->mConn = db2_connect($dbName, $user, $password);
+ protected function openCataloged( $dbName, $user, $password ) {
+ @$this->mConn = db2_pconnect( $dbName, $user, $password );
}
-
+
/**
* Opens an uncataloged database connection, sets mConn
*/
@@ -550,14 +394,15 @@ class DatabaseIbm_db2 extends DatabaseBase {
$str = "DRIVER={IBM DB2 ODBC DRIVER};";
$str .= "DATABASE=$dbName;";
$str .= "HOSTNAME=$server;";
- if ($port) $str .= "PORT=$port;";
+ // port was formerly validated to not be 0
+ $str .= "PORT=$port;";
$str .= "PROTOCOL=TCPIP;";
$str .= "UID=$user;";
$str .= "PWD=$password;";
-
- @$this->mConn = db2_connect($str, $user, $password);
+
+ @$this->mConn = db2_pconnect( $str, $user, $password );
}
-
+
/**
* Closes a database connection, if it is open
* Returns success, true if already closed
@@ -565,16 +410,15 @@ class DatabaseIbm_db2 extends DatabaseBase {
public function close() {
$this->mOpened = false;
if ( $this->mConn ) {
- if ($this->trxLevel() > 0) {
+ if ( $this->trxLevel() > 0 ) {
$this->commit();
}
return db2_close( $this->mConn );
- }
- else {
+ } else {
return true;
}
}
-
+
/**
* Returns a fresh instance of this class
*
@@ -582,34 +426,35 @@ class DatabaseIbm_db2 extends DatabaseBase {
* @param $user String: username
* @param $password String
* @param $dbName String: database name on the server
- * @param $failFunction Callback (optional)
* @param $flags Integer: database behaviour flags (optional, unused)
* @return DatabaseIbm_db2 object
*/
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0)
+ static function newFromParams( $server, $user, $password, $dbName,
+ $flags = 0 )
{
- return new DatabaseIbm_db2( $server, $user, $password, $dbName, $failFunction, $flags );
+ return new DatabaseIbm_db2( $server, $user, $password, $dbName,
+ $flags );
}
-
+
/**
* Retrieves the most current database error
* Forces a database rollback
*/
public function lastError() {
$connerr = db2_conn_errormsg();
- if ($connerr) {
+ if ( $connerr ) {
//$this->rollback();
return $connerr;
}
$stmterr = db2_stmt_errormsg();
- if ($stmterr) {
+ if ( $stmterr ) {
//$this->rollback();
return $stmterr;
}
-
+
return false;
}
-
+
/**
* Get the last error number
* Return 0 if no error
@@ -617,43 +462,45 @@ class DatabaseIbm_db2 extends DatabaseBase {
*/
public function lastErrno() {
$connerr = db2_conn_error();
- if ($connerr) return $connerr;
+ if ( $connerr ) {
+ return $connerr;
+ }
$stmterr = db2_stmt_error();
- if ($stmterr) return $stmterr;
+ if ( $stmterr ) {
+ return $stmterr;
+ }
return 0;
}
-
+
/**
* Is a database connection open?
- * @return
+ * @return
*/
public function isOpen() { return $this->mOpened; }
-
+
/**
* The DBMS-dependent part of query()
* @param $sql String: SQL query.
- * @return object Result object to feed to fetchObject, fetchRow, ...; or false on failure
+ * @return object Result object for fetch functions or false on failure
* @access private
*/
/*private*/
public function doQuery( $sql ) {
- //print "<li><pre>$sql</pre></li>";
- // Switch into the correct namespace
$this->applySchema();
-
+
$ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
- if( !$ret ) {
- print "<br><pre>";
- print $sql;
- print "</pre><br>";
+ if( $ret == false ) {
$error = db2_stmt_errormsg();
- throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( $error ) );
+ $this->installPrint( "<pre>$sql</pre>" );
+ $this->installPrint( $error );
+ throw new DBUnexpectedError( $this, 'SQL error: '
+ . htmlspecialchars( $error ) );
}
$this->mLastResult = $ret;
- $this->mAffectedRows = null; // Not calculated until asked for
+ $this->mAffectedRows = null; // Not calculated until asked for
return $ret;
}
-
+
/**
* @return string Version information from the database
*/
@@ -661,7 +508,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
$info = db2_server_info( $this->mConn );
return $info->DBMS_VER;
}
-
+
/**
* Queries whether a given table exists
* @return boolean
@@ -669,22 +516,24 @@ class DatabaseIbm_db2 extends DatabaseBase {
public function tableExists( $table ) {
$schema = $this->mSchema;
$sql = <<< EOF
-SELECT COUNT(*) FROM SYSIBM.SYSTABLES ST
+SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST
WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema'
EOF;
$res = $this->query( $sql );
- if (!$res) return false;
-
+ if ( !$res ) {
+ return false;
+ }
+
// If the table exists, there should be one of it
- @$row = $this->fetchRow($res);
+ @$row = $this->fetchRow( $res );
$count = $row[0];
- if ($count == '1' or $count == 1) {
+ if ( $count == '1' || $count == 1 ) {
return true;
}
-
+
return false;
}
-
+
/**
* Fetch the next row from the given result object, in object form.
* Fields can be retrieved with $row->fieldname, with fields acting like
@@ -700,14 +549,15 @@ EOF;
}
@$row = db2_fetch_object( $res );
if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
+ throw new DBUnexpectedError( $this, 'Error in fetchObject(): '
+ . htmlspecialchars( $this->lastError() ) );
}
return $row;
}
/**
* Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
+ * form. Fields are retrieved with $row['fieldname'].
*
* @param $res SQL result object as returned from Database::query(), etc.
* @return DB2 row object
@@ -719,55 +569,47 @@ EOF;
}
@$row = db2_fetch_array( $res );
if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
+ throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
+ . htmlspecialchars( $this->lastError() ) );
}
return $row;
}
-
- /**
- * Override if introduced to base Database class
- */
- public function initial_setup() {
- // do nothing
- }
-
+
/**
* Create tables, stored procedures, and so on
*/
public function setup_database() {
- // Timeout was being changed earlier due to mysterious crashes
- // Changing it now may cause more problems than not changing it
- //set_time_limit(240);
try {
// TODO: switch to root login if available
-
+
// Switch into the correct namespace
$this->applySchema();
$this->begin();
-
+
$res = $this->sourceFile( "../maintenance/ibm_db2/tables.sql" );
- if ($res !== true) {
- print " <b>FAILED</b>: " . htmlspecialchars( $res ) . "</li>";
+ if ( $res !== true ) {
+ print ' <b>FAILED</b>: ' . htmlspecialchars( $res ) . '</li>';
} else {
- print " done</li>";
+ print ' done</li>';
}
- $res = null;
-
- // TODO: update mediawiki_version table
-
+ $res = $this->sourceFile( "../maintenance/ibm_db2/foreignkeys.sql" );
+ if ( $res !== true ) {
+ print ' <b>FAILED</b>: ' . htmlspecialchars( $res ) . '</li>';
+ } else {
+ print '<li>Foreign keys done</li>';
+ }
+
// TODO: populate interwiki links
-
- if ($this->lastError()) {
- print "<li>Errors encountered during table creation -- rolled back</li>\n";
- print "<li>Please install again</li>\n";
+
+ if ( $this->lastError() ) {
+ $this->installPrint(
+ 'Errors encountered during table creation -- rolled back' );
+ $this->installPrint( 'Please install again' );
$this->rollback();
- }
- else {
+ } else {
$this->commit();
}
- }
- catch (MWException $mwe)
- {
+ } catch ( MWException $mwe ) {
print "<br><pre>$mwe</pre><br>";
}
}
@@ -775,47 +617,48 @@ EOF;
/**
* Escapes strings
* Doesn't escape numbers
+ *
* @param $s String: string to escape
* @return escaped string
*/
public function addQuotes( $s ) {
- //$this->installPrint("DB2::addQuotes($s)\n");
+ //$this->installPrint( "DB2::addQuotes( $s )\n" );
if ( is_null( $s ) ) {
- return "NULL";
- } else if ($s instanceof Blob) {
- return "'".$s->fetch($s)."'";
- } else if ($s instanceof IBM_DB2Blob) {
- return "'".$this->decodeBlob($s)."'";
- }
- $s = $this->strencode($s);
- if ( is_numeric($s) ) {
+ return 'NULL';
+ } elseif ( $s instanceof Blob ) {
+ return "'" . $s->fetch( $s ) . "'";
+ } elseif ( $s instanceof IBM_DB2Blob ) {
+ return "'" . $this->decodeBlob( $s ) . "'";
+ }
+ $s = $this->strencode( $s );
+ if ( is_numeric( $s ) ) {
return $s;
- }
- else {
+ } else {
return "'$s'";
}
}
-
+
/**
* Verifies that a DB2 column/field type is numeric
- * @return bool true if numeric
+ *
* @param $type String: DB2 column type
+ * @return Boolean: true if numeric
*/
public function is_numeric_type( $type ) {
- switch (strtoupper($type)) {
- case 'SMALLINT':
- case 'INTEGER':
- case 'INT':
- case 'BIGINT':
- case 'DECIMAL':
- case 'REAL':
- case 'DOUBLE':
- case 'DECFLOAT':
- return true;
+ switch ( strtoupper( $type ) ) {
+ case 'SMALLINT':
+ case 'INTEGER':
+ case 'INT':
+ case 'BIGINT':
+ case 'DECIMAL':
+ case 'REAL':
+ case 'DOUBLE':
+ case 'DECFLOAT':
+ return true;
}
return false;
}
-
+
/**
* Alias for addQuotes()
* @param $s String: string to escape
@@ -823,178 +666,153 @@ EOF;
*/
public function strencode( $s ) {
// Bloody useless function
- // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
+ // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
// But also necessary
- $s = db2_escape_string($s);
+ $s = db2_escape_string( $s );
// Wide characters are evil -- some of them look like '
- $s = utf8_encode($s);
+ $s = utf8_encode( $s );
// Fix its stupidity
- $from = array("\\\\", "\\'", '\\n', '\\t', '\\"', '\\r');
- $to = array("\\", "''", "\n", "\t", '"', "\r");
- $s = str_replace($from, $to, $s); // DB2 expects '', not \' escaping
+ $from = array( "\\\\", "\\'", '\\n', '\\t', '\\"', '\\r' );
+ $to = array( "\\", "''", "\n", "\t", '"', "\r" );
+ $s = str_replace( $from, $to, $s ); // DB2 expects '', not \' escaping
return $s;
}
-
+
/**
* Switch into the database schema
*/
protected function applySchema() {
- if ( !($this->mSchemaSet) ) {
+ if ( !( $this->mSchemaSet ) ) {
$this->mSchemaSet = true;
$this->begin();
- $this->doQuery("SET SCHEMA = $this->mSchema");
+ $this->doQuery( "SET SCHEMA = $this->mSchema" );
$this->commit();
- }
+ }
}
-
+
/**
* Start a transaction (mandatory)
*/
public function begin( $fname = 'DatabaseIbm_db2::begin' ) {
- // turn off auto-commit
- db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
+ // BEGIN is implicit for DB2
+ // However, it requires that AutoCommit be off.
+
+ // Some MediaWiki code is still transaction-less (?).
+ // The strategy is to keep AutoCommit on for that code
+ // but switch it off whenever a transaction is begun.
+ db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_OFF );
+
$this->mTrxLevel = 1;
}
-
+
/**
* End a transaction
* Must have a preceding begin()
*/
public function commit( $fname = 'DatabaseIbm_db2::commit' ) {
- db2_commit($this->mConn);
- // turn auto-commit back on
- db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
+ db2_commit( $this->mConn );
+
+ // Some MediaWiki code is still transaction-less (?).
+ // The strategy is to keep AutoCommit on for that code
+ // but switch it off whenever a transaction is begun.
+ db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
+
$this->mTrxLevel = 0;
}
-
+
/**
* Cancel a transaction
*/
public function rollback( $fname = 'DatabaseIbm_db2::rollback' ) {
- db2_rollback($this->mConn);
+ db2_rollback( $this->mConn );
// turn auto-commit back on
// not sure if this is appropriate
- db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
+ db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
$this->mTrxLevel = 0;
}
-
+
/**
* Makes an encoded list of strings from an array
* $mode:
- * LIST_COMMA - comma separated, no field names
- * LIST_AND - ANDed WHERE clause (without the WHERE)
- * LIST_OR - ORed WHERE clause (without the WHERE)
- * LIST_SET - comma separated with field names, like a SET clause
- * LIST_NAMES - comma separated field names
- */
- public function makeList( $a, $mode = LIST_COMMA ) {
+ * LIST_COMMA - comma separated, no field names
+ * LIST_AND - ANDed WHERE clause (without the WHERE)
+ * LIST_OR - ORed WHERE clause (without the WHERE)
+ * LIST_SET - comma separated with field names, like a SET clause
+ * LIST_NAMES - comma separated field names
+ * LIST_SET_PREPARED - like LIST_SET, except with ? tokens as values
+ */
+ function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
+ throw new DBUnexpectedError( $this,
+ 'DatabaseIbm_db2::makeList called with incorrect parameters' );
}
- $first = true;
- $list = '';
- foreach ( $a as $field => $value ) {
- if ( !$first ) {
- if ( $mode == LIST_AND ) {
- $list .= ' AND ';
- } elseif($mode == LIST_OR) {
- $list .= ' OR ';
+ // if this is for a prepared UPDATE statement
+ // (this should be promoted to the parent class
+ // once other databases use prepared statements)
+ if ( $mode == LIST_SET_PREPARED ) {
+ $first = true;
+ $list = '';
+ foreach ( $a as $field => $value ) {
+ if ( !$first ) {
+ $list .= ", $field = ?";
} else {
- $list .= ',';
- }
- } else {
- $first = false;
- }
- if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
- $list .= "($value)";
- } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
- $list .= "$value";
- } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
- if( count( $value ) == 0 ) {
- throw new MWException( __METHOD__.': empty input' );
- } elseif( count( $value ) == 1 ) {
- // Special-case single values, as IN isn't terribly efficient
- // Don't necessarily assume the single key is 0; we don't
- // enforce linear numeric ordering on other arrays here.
- $value = array_values( $value );
- $list .= $field." = ".$this->addQuotes( $value[0] );
- } else {
- $list .= $field." IN (".$this->makeList($value).") ";
- }
- } elseif( is_null($value) ) {
- if ( $mode == LIST_AND || $mode == LIST_OR ) {
- $list .= "$field IS ";
- } elseif ( $mode == LIST_SET ) {
- $list .= "$field = ";
- }
- $list .= 'NULL';
- } else {
- if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
- $list .= "$field = ";
- }
- if ( $mode == LIST_NAMES ) {
- $list .= $value;
- }
- // Leo: Can't insert quoted numbers into numeric columns
- // (?) Might cause other problems. May have to check column type before insertion.
- else if ( is_numeric($value) ) {
- $list .= $value;
- }
- else {
- $list .= $this->addQuotes( $value );
+ $list .= "$field = ?";
+ $first = false;
}
}
+ $list .= '';
+
+ return $list;
}
- return $list;
+
+ // otherwise, call the usual function
+ return parent::makeList( $a, $mode );
}
-
+
/**
* Construct a LIMIT query with optional offset
* This is used for query pages
+ *
* @param $sql string SQL query we will append the limit too
* @param $limit integer the SQL limit
* @param $offset integer the SQL offset (default false)
*/
- public function limitResult($sql, $limit, $offset=false) {
- if( !is_numeric($limit) ) {
- throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
+ public function limitResult( $sql, $limit, $offset=false ) {
+ if( !is_numeric( $limit ) ) {
+ throw new DBUnexpectedError( $this,
+ "Invalid non-numeric limit passed to limitResult()\n" );
}
if( $offset ) {
- $this->installPrint("Offset parameter not supported in limitResult()\n");
+ if ( stripos( $sql, 'where' ) === false ) {
+ return "$sql AND ( ROWNUM BETWEEN $offset AND $offset+$limit )";
+ } else {
+ return "$sql WHERE ( ROWNUM BETWEEN $offset AND $offset+$limit )";
+ }
}
- // TODO implement proper offset handling
- // idea: get all the rows between 0 and offset, advance cursor to offset
return "$sql FETCH FIRST $limit ROWS ONLY ";
}
-
+
/**
* Handle reserved keyword replacement in table names
- * @return
+ *
* @param $name Object
+ * @return String
*/
public function tableName( $name ) {
- # Replace reserved words with better ones
-// switch( $name ) {
-// case 'user':
-// return 'mwuser';
-// case 'text':
-// return 'pagecontent';
-// default:
-// return $name;
-// }
// we want maximum compatibility with MySQL schema
return $name;
}
-
+
/**
* Generates a timestamp in an insertable format
- * @return string timestamp value
+ *
* @param $ts timestamp
+ * @return String: timestamp value
*/
- public function timestamp( $ts=0 ) {
+ public function timestamp( $ts = 0 ) {
// TS_MW cannot be easily distinguished from an integer
- return wfTimestamp(TS_DB2,$ts);
+ return wfTimestamp( TS_DB2, $ts );
}
/**
@@ -1003,19 +821,20 @@ EOF;
* @return next value in that sequence
*/
public function nextSequenceValue( $seqName ) {
- // Not using sequences in the primary schema to allow for easy third-party migration scripts
- // Emulating MySQL behaviour of using NULL to signal that sequences aren't used
+ // Not using sequences in the primary schema to allow for easier migration
+ // from MySQL
+ // Emulating MySQL behaviour of using NULL to signal that sequences
+ // aren't used
/*
$safeseq = preg_replace( "/'/", "''", $seqName );
$res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
- $this->freeResult( $res );
return $this->mInsertId;
*/
return null;
}
-
+
/**
* This must be called after nextSequenceVal
* @return Last sequence value used as a primary key
@@ -1023,26 +842,27 @@ EOF;
public function insertId() {
return $this->mInsertId;
}
-
+
/**
- * Updates the mInsertId property with the value of the last insert into a generated column
+ * Updates the mInsertId property with the value of the last insert
+ * into a generated column
+ *
* @param $table String: sanitized table name
- * @param $primaryKey Mixed: string name of the primary key or a bool if this call is a do-nothing
+ * @param $primaryKey Mixed: string name of the primary key
* @param $stmt Resource: prepared statement resource
* of the SELECT primary_key FROM FINAL TABLE ( INSERT ... ) form
*/
- private function calcInsertId($table, $primaryKey, $stmt) {
- if ($primaryKey) {
- $id_row = $this->fetchRow($stmt);
- $this->mInsertId = $id_row[0];
+ private function calcInsertId( $table, $primaryKey, $stmt ) {
+ if ( $primaryKey ) {
+ $this->mInsertId = db2_last_insert_id( $this->mConn );
}
}
-
+
/**
* INSERT wrapper, inserts an array into a table
*
- * $args may be a single associative array, or an array of these with numeric keys,
- * for multi-row insert
+ * $args may be a single associative array, or an array of arrays
+ * with numeric keys, for multi-row insert
*
* @param $table String: Name of the table to insert to.
* @param $args Array: Items to insert into the table.
@@ -1051,30 +871,33 @@ EOF;
*
* @return bool Success of insert operation. IGNORE always returns true.
*/
- public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) {
+ public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert',
+ $options = array() )
+ {
if ( !count( $args ) ) {
return true;
}
// get database-specific table name (not used)
$table = $this->tableName( $table );
// format options as an array
- if ( !is_array( $options ) ) $options = array( $options );
+ $options = IBM_DB2Helper::makeArray( $options );
// format args as an array of arrays
if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
- $args = array($args);
+ $args = array( $args );
}
+
// prevent insertion of NULL into primary key columns
- list($args, $primaryKeys) = $this->removeNullPrimaryKeys($table, $args);
+ list( $args, $primaryKeys ) = $this->removeNullPrimaryKeys( $table, $args );
// if there's only one primary key
// we'll be able to read its value after insertion
$primaryKey = false;
- if (count($primaryKeys) == 1) {
+ if ( count( $primaryKeys ) == 1 ) {
$primaryKey = $primaryKeys[0];
}
-
+
// get column names
$keys = array_keys( $args[0] );
- $key_count = count($keys);
+ $key_count = count( $keys );
// If IGNORE is set, we use savepoints to emulate mysql's behavior
$ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
@@ -1082,144 +905,169 @@ EOF;
// assume success
$res = true;
// If we are not in a transaction, we need to be for savepoint trickery
- $didbegin = 0;
- if (! $this->mTrxLevel) {
+ if ( !$this->mTrxLevel ) {
$this->begin();
- $didbegin = 1;
}
- $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
- switch($key_count) {
- //case 0 impossible
- case 1:
- $sql .= '(?)';
- break;
- default:
- $sql .= '(?' . str_repeat(',?', $key_count-1) . ')';
- }
- // add logic to read back the new primary key value
- if ($primaryKey) {
- $sql = "SELECT $primaryKey FROM FINAL TABLE($sql)";
+ $sql = "INSERT INTO $table ( " . implode( ',', $keys ) . ' ) VALUES ';
+ if ( $key_count == 1 ) {
+ $sql .= '( ? )';
+ } else {
+ $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
}
- $stmt = $this->prepare($sql);
-
+ //$this->installPrint( "Preparing the following SQL:" );
+ //$this->installPrint( "$sql" );
+ //$this->installPrint( print_r( $args, true ));
+ $stmt = $this->prepare( $sql );
+
// start a transaction/enter transaction mode
$this->begin();
if ( !$ignore ) {
- $first = true;
+ //$first = true;
foreach ( $args as $row ) {
+ //$this->installPrint( "Inserting " . print_r( $row, true ));
// insert each row into the database
- $res = $res & $this->execute($stmt, $row);
+ $res = $res & $this->execute( $stmt, $row );
+ if ( !$res ) {
+ $this->installPrint( 'Last error:' );
+ $this->installPrint( $this->lastError() );
+ }
// get the last inserted value into a generated column
- $this->calcInsertId($table, $primaryKey, $stmt);
+ $this->calcInsertId( $table, $primaryKey, $stmt );
}
- }
- else {
+ } else {
$olde = error_reporting( 0 );
// For future use, we may want to track the number of actual inserts
// Right now, insert (all writes) simply return true/false
$numrowsinserted = 0;
-
+
// always return true
$res = true;
-
+
foreach ( $args as $row ) {
$overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
- db2_exec($this->mConn, $overhead, $this->mStmtOptions);
-
- $res2 = $this->execute($stmt, $row);
+ db2_exec( $this->mConn, $overhead, $this->mStmtOptions );
+
+ $res2 = $this->execute( $stmt, $row );
+
+ if ( !$res2 ) {
+ $this->installPrint( 'Last error:' );
+ $this->installPrint( $this->lastError() );
+ }
// get the last inserted value into a generated column
- $this->calcInsertId($table, $primaryKey, $stmt);
-
+ $this->calcInsertId( $table, $primaryKey, $stmt );
+
$errNum = $this->lastErrno();
- if ($errNum) {
- db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore", $this->mStmtOptions );
- }
- else {
- db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore", $this->mStmtOptions );
+ if ( $errNum ) {
+ db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore",
+ $this->mStmtOptions );
+ } else {
+ db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore",
+ $this->mStmtOptions );
$numrowsinserted++;
}
}
-
+
$olde = error_reporting( $olde );
// Set the affected row count for the whole operation
$this->mAffectedRows = $numrowsinserted;
}
// commit either way
$this->commit();
-
+ $this->freePrepared( $stmt );
+
return $res;
}
-
+
/**
* Given a table name and a hash of columns with values
* Removes primary key columns from the hash where the value is NULL
- *
+ *
* @param $table String: name of the table
* @param $args Array of hashes of column names with values
- * @return Array: tuple containing filtered array of columns, array of primary keys
+ * @return Array: tuple( filtered array of columns, array of primary keys )
*/
- private function removeNullPrimaryKeys($table, $args) {
+ private function removeNullPrimaryKeys( $table, $args ) {
$schema = $this->mSchema;
// find out the primary keys
- $keyres = db2_primary_keys($this->mConn, null, strtoupper($schema), strtoupper($table));
+ $keyres = db2_primary_keys( $this->mConn, null, strtoupper( $schema ),
+ strtoupper( $table )
+ );
$keys = array();
- for ($row = $this->fetchObject($keyres); $row != null; $row = $this->fetchRow($keyres)) {
- $keys[] = strtolower($row->column_name);
+ for (
+ $row = $this->fetchObject( $keyres );
+ $row != null;
+ $row = $this->fetchObject( $keyres )
+ )
+ {
+ $keys[] = strtolower( $row->column_name );
}
// remove primary keys
- foreach ($args as $ai => $row) {
- foreach ($keys as $ki => $key) {
- if ($row[$key] == null) {
- unset($row[$key]);
+ foreach ( $args as $ai => $row ) {
+ foreach ( $keys as $key ) {
+ if ( $row[$key] == null ) {
+ unset( $row[$key] );
}
}
$args[$ai] = $row;
}
// return modified hash
- return array($args, $keys);
+ return array( $args, $keys );
}
-
+
/**
* UPDATE wrapper, takes a condition array and a SET array
*
* @param $table String: The table to UPDATE
* @param $values An array of values to SET
- * @param $conds An array of conditions (WHERE). Use '*' to update all rows.
+ * @param $conds An array of conditions ( WHERE ). Use '*' to update all rows.
* @param $fname String: The Class::Function calling this function
- * (for the log)
+ * ( for the log )
* @param $options An array of UPDATE options, can be one or
* more of IGNORE, LOW_PRIORITY
* @return Boolean
*/
- public function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
+ public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update',
+ $options = array() )
+ {
$table = $this->tableName( $table );
$opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
+ $sql = "UPDATE $opts $table SET "
+ . $this->makeList( $values, LIST_SET_PREPARED );
if ( $conds != '*' ) {
$sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
}
- return $this->query( $sql, $fname );
+ $stmt = $this->prepare( $sql );
+ $this->installPrint( 'UPDATE: ' . print_r( $values, true ) );
+ // assuming for now that an array with string keys will work
+ // if not, convert to simple array first
+ $result = $this->execute( $stmt, $values );
+ $this->freePrepared( $stmt );
+
+ return $result;
}
-
+
/**
* DELETE query wrapper
*
* Use $conds == "*" to delete all rows
*/
- public function delete( $table, $conds, $fname = 'Database::delete' ) {
+ public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
+ throw new DBUnexpectedError( $this,
+ 'DatabaseIbm_db2::delete() called with no conditions' );
}
$table = $this->tableName( $table );
$sql = "DELETE FROM $table";
if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
- return $this->query( $sql, $fname );
+ $result = $this->query( $sql, $fname );
+
+ return $result;
}
-
+
/**
* Returns the number of rows affected by the last query or 0
* @return Integer: the number of rows affected by the last query
@@ -1229,11 +1077,12 @@ EOF;
// Forced result for simulated queries
return $this->mAffectedRows;
}
- if( empty( $this->mLastResult ) )
+ if( empty( $this->mLastResult ) ) {
return 0;
+ }
return db2_num_rows( $this->mLastResult );
}
-
+
/**
* Simulates REPLACE with a DELETE followed by INSERT
* @param $table Object
@@ -1242,10 +1091,12 @@ EOF;
* @param $fname String: name of the function for profiling
* @return nothing
*/
- function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) {
+ function replace( $table, $uniqueIndexes, $rows,
+ $fname = 'DatabaseIbm_db2::replace' )
+ {
$table = $this->tableName( $table );
- if (count($rows)==0) {
+ if ( count( $rows )==0 ) {
return;
}
@@ -1262,9 +1113,9 @@ EOF;
foreach ( $uniqueIndexes as $index ) {
if ( $first ) {
$first = false;
- $sql .= "(";
+ $sql .= '( ';
} else {
- $sql .= ') OR (';
+ $sql .= ' ) OR ( ';
}
if ( is_array( $index ) ) {
$first2 = true;
@@ -1274,23 +1125,24 @@ EOF;
} else {
$sql .= ' AND ';
}
- $sql .= $col.'=' . $this->addQuotes( $row[$col] );
+ $sql .= $col . '=' . $this->addQuotes( $row[$col] );
}
} else {
- $sql .= $index.'=' . $this->addQuotes( $row[$index] );
+ $sql .= $index . '=' . $this->addQuotes( $row[$index] );
}
}
- $sql .= ')';
+ $sql .= ' )';
$this->query( $sql, $fname );
}
# Now insert the row
- $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
- $this->makeList( $row, LIST_COMMA ) . ')';
+ $sql = "INSERT INTO $table ( "
+ . $this->makeList( array_keys( $row ), LIST_NAMES )
+ .' ) VALUES ( ' . $this->makeList( $row, LIST_COMMA ) . ' )';
$this->query( $sql, $fname );
}
}
-
+
/**
* Returns the number of rows in the result set
* Has to be called right after the corresponding select query
@@ -1303,12 +1155,11 @@ EOF;
}
if ( $this->mNumRows ) {
return $this->mNumRows;
- }
- else {
+ } else {
return 0;
}
}
-
+
/**
* Moves the row pointer of the result set
* @param $res Object: result set
@@ -1321,11 +1172,11 @@ EOF;
}
return db2_fetch_row( $res, $row );
}
-
+
###
- # Fix notices in Block.php
+ # Fix notices in Block.php
###
-
+
/**
* Frees memory associated with a statement resource
* @param $res Object: statement resource to free
@@ -1336,10 +1187,10 @@ EOF;
$res = $res->result;
}
if ( !@db2_free_result( $res ) ) {
- throw new DBUnexpectedError($this, "Unable to free DB2 result\n" );
+ throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
}
}
-
+
/**
* Returns the number of columns in a resource
* @param $res Object: statement resource
@@ -1351,7 +1202,7 @@ EOF;
}
return db2_num_fields( $res );
}
-
+
/**
* Returns the nth column name
* @param $res Object: statement resource
@@ -1364,57 +1215,65 @@ EOF;
}
return db2_field_name( $res, $n );
}
-
+
/**
* SELECT wrapper
*
* @param $table Array or string, table name(s) (prefix auto-added)
* @param $vars Array or string, field name(s) to be retrieved
* @param $conds Array or string, condition(s) for WHERE
- * @param $fname String: calling function name (use __METHOD__) for logs/profiling
- * @param $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
+ * @param $fname String: calling function name (use __METHOD__)
+ * for logs/profiling
+ * @param $options Associative array of options
+ * (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of
+ * supported stuff
* @param $join_conds Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * (e.g. array( 'page' => array('LEFT JOIN',
+ * 'page_latest=rev_id') )
+ * @return Mixed: database result resource for fetch functions or false
+ * on failure
*/
- public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
+ public function select( $table, $vars, $conds = '', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
{
- $res = parent::select( $table, $vars, $conds, $fname, $options, $join_conds );
-
+ $res = parent::select( $table, $vars, $conds, $fname, $options,
+ $join_conds );
+
// We must adjust for offset
- if ( isset( $options['LIMIT'] ) ) {
- if ( isset ($options['OFFSET'] ) ) {
- $limit = $options['LIMIT'];
- $offset = $options['OFFSET'];
- }
+ if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
+ $limit = $options['LIMIT'];
+ $offset = $options['OFFSET'];
}
-
-
- // DB2 does not have a proper num_rows() function yet, so we must emulate it
- // DB2 9.5.3/9.5.4 and the corresponding ibm_db2 driver will introduce a working one
- // Yay!
-
+
+ // DB2 does not have a proper num_rows() function yet, so we must emulate
+ // DB2 9.5.4 and the corresponding ibm_db2 driver will introduce
+ // a working one
+ // TODO: Yay!
+
// we want the count
- $vars2 = array('count(*) as num_rows');
+ $vars2 = array( 'count( * ) as num_rows' );
// respecting just the limit option
$options2 = array();
- if ( isset( $options['LIMIT'] ) ) $options2['LIMIT'] = $options['LIMIT'];
+ if ( isset( $options['LIMIT'] ) ) {
+ $options2['LIMIT'] = $options['LIMIT'];
+ }
// but don't try to emulate for GROUP BY
- if ( isset( $options['GROUP BY'] ) ) return $res;
-
- $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds );
- $obj = $this->fetchObject($res2);
+ if ( isset( $options['GROUP BY'] ) ) {
+ return $res;
+ }
+
+ $res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
+ $join_conds );
+ $obj = $this->fetchObject( $res2 );
$this->mNumRows = $obj->num_rows;
-
-
+
return $res;
}
-
+
/**
* Handles ordering, grouping, and having options ('GROUP BY' => colname)
* Has limited support for per-column options (colnum => 'DISTINCT')
- *
+ *
* @private
*
* @param $options Associative array of options to be turned into
@@ -1432,31 +1291,41 @@ EOF;
}
}
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
- if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
-
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
-
+ if ( isset( $options['GROUP BY'] ) ) {
+ $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
+ }
+ if ( isset( $options['HAVING'] ) ) {
+ $preLimitTail .= " HAVING {$options['HAVING']}";
+ }
+ if ( isset( $options['ORDER BY'] ) ) {
+ $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
+ }
+
+ if ( isset( $noKeyOptions['DISTINCT'] )
+ || isset( $noKeyOptions['DISTINCTROW'] ) )
+ {
+ $startOpts .= 'DISTINCT';
+ }
+
return array( $startOpts, '', $preLimitTail, $postLimitTail );
}
-
+
/**
* Returns link to IBM DB2 free download
- * @return string wikitext of a link to the server software's web site
+ * @return String: wikitext of a link to the server software's web site
*/
- public function getSoftwareLink() {
- return "[http://www.ibm.com/software/data/db2/express/?s_cmp=ECDDWW01&s_tact=MediaWiki IBM DB2]";
+ public static function getSoftwareLink() {
+ return '[http://www.ibm.com/db2/express/ IBM DB2]';
}
-
+
/**
* Get search engine class. All subclasses of this
* need to implement this if they wish to use searching.
- *
+ *
* @return String
*/
public function getSearchEngine() {
- return "SearchIBM_DB2";
+ return 'SearchIBM_DB2';
}
/**
@@ -1466,16 +1335,17 @@ EOF;
public function wasDeadlock() {
// get SQLSTATE
$err = $this->lastErrno();
- switch($err) {
+ switch( $err ) {
+ // This is literal port of the MySQL logic and may be wrong for DB2
case '40001': // sql0911n, Deadlock or timeout, rollback
case '57011': // sql0904n, Resource unavailable, no rollback
case '57033': // sql0913n, Deadlock or timeout, no rollback
- $this->installPrint("In a deadlock because of SQLSTATE $err");
+ $this->installPrint( "In a deadlock because of SQLSTATE $err" );
return true;
}
return false;
}
-
+
/**
* Ping the server and try to reconnect if it there is no connection
* The connection may be closed and reopened while this happens
@@ -1485,15 +1355,9 @@ EOF;
// db2_ping() doesn't exist
// Emulate
$this->close();
- if ($this->mCataloged == null) {
- return false;
- }
- else if ($this->mCataloged) {
- $this->mConn = $this->openCataloged($this->mDBName, $this->mUser, $this->mPassword);
- }
- else if (!$this->mCataloged) {
- $this->mConn = $this->openUncataloged($this->mDBName, $this->mUser, $this->mPassword, $this->mServer, $this->mPort);
- }
+ $this->mConn = $this->openUncataloged( $this->mDBName, $this->mUser,
+ $this->mPassword, $this->mServer, $this->mPort );
+
return false;
}
######################################
@@ -1502,65 +1366,33 @@ EOF;
/**
* Not implemented
* @return string ''
- * @deprecated
*/
- public function getStatus( $which="%" ) { $this->installPrint('Not implemented for DB2: getStatus()'); return ''; }
- /**
- * Not implemented
- * TODO
- * @return bool true
- */
- /**
- * Not implemented
- * @deprecated
- */
- public function setFakeSlaveLag( $lag ) { $this->installPrint('Not implemented for DB2: setFakeSlaveLag()'); }
- /**
- * Not implemented
- * @deprecated
- */
- public function setFakeMaster( $enabled = true ) { $this->installPrint('Not implemented for DB2: setFakeMaster()'); }
+ public function getStatus( $which = '%' ) {
+ $this->installPrint( 'Not implemented for DB2: getStatus()' );
+ return '';
+ }
/**
* Not implemented
* @return string $sql
- * @deprecated
- */
- public function limitResultForUpdate($sql, $num) { $this->installPrint('Not implemented for DB2: limitResultForUpdate()'); return $sql; }
-
+ */
+ public function limitResultForUpdate( $sql, $num ) {
+ $this->installPrint( 'Not implemented for DB2: limitResultForUpdate()' );
+ return $sql;
+ }
+
/**
* Only useful with fake prepare like in base Database class
* @return string
*/
- public function fillPreparedArg( $matches ) { $this->installPrint('Not useful for DB2: fillPreparedArg()'); return ''; }
-
+ public function fillPreparedArg( $matches ) {
+ $this->installPrint( 'Not useful for DB2: fillPreparedArg()' );
+ return '';
+ }
+
######################################
# Reflection
######################################
-
- /**
- * Query whether a given column exists in the mediawiki schema
- * @param $table String: name of the table
- * @param $field String: name of the column
- * @param $fname String: function name for logging and profiling
- */
- public function fieldExists( $table, $field, $fname = 'DatabaseIbm_db2::fieldExists' ) {
- $table = $this->tableName( $table );
- $schema = $this->mSchema;
- $etable = preg_replace("/'/", "''", $table);
- $eschema = preg_replace("/'/", "''", $schema);
- $ecol = preg_replace("/'/", "''", $field);
- $sql = <<<SQL
-SELECT 1 as fieldexists
-FROM sysibm.syscolumns sc
-WHERE sc.name='$ecol' AND sc.tbname='$etable' AND sc.tbcreator='$eschema'
-SQL;
- $res = $this->query( $sql, $fname );
- $count = $res ? $this->numRows($res) : 0;
- if ($res)
- $this->freeResult( $res );
- return $count;
- }
-
+
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
@@ -1569,22 +1401,28 @@ SQL;
* @param $fname String: function name for logging and profiling
* @return Object query row in object form
*/
- public function indexInfo( $table, $index, $fname = 'DatabaseIbm_db2::indexExists' ) {
+ public function indexInfo( $table, $index,
+ $fname = 'DatabaseIbm_db2::indexExists' )
+ {
$table = $this->tableName( $table );
$sql = <<<SQL
SELECT name as indexname
FROM sysibm.sysindexes si
-WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
+WHERE si.name='$index' AND si.tbname='$table'
+AND sc.tbcreator='$this->mSchema'
SQL;
$res = $this->query( $sql, $fname );
if ( !$res ) {
return null;
}
$row = $this->fetchObject( $res );
- if ($row != null) return $row;
- else return false;
+ if ( $row != null ) {
+ return $row;
+ } else {
+ return false;
+ }
}
-
+
/**
* Returns an information object on a table column
* @param $table String: table name
@@ -1592,9 +1430,9 @@ SQL;
* @return IBM_DB2Field
*/
public function fieldInfo( $table, $field ) {
- return IBM_DB2Field::fromText($this, $table, $field);
+ return IBM_DB2Field::fromText( $this, $table, $field );
}
-
+
/**
* db2_field_type() wrapper
* @param $res Object: result of executed statement
@@ -1607,7 +1445,7 @@ SQL;
}
return db2_field_type( $res, $index );
}
-
+
/**
* Verifies that an index was created as unique
* @param $table String: table name
@@ -1615,25 +1453,28 @@ SQL;
* @param $fname function name for profiling
* @return Bool
*/
- public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
+ public function indexUnique ( $table, $index,
+ $fname = 'DatabaseIbm_db2::indexUnique' )
+ {
$table = $this->tableName( $table );
$sql = <<<SQL
SELECT si.name as indexname
FROM sysibm.sysindexes si
-WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
-AND si.uniquerule IN ('U', 'P')
+WHERE si.name='$index' AND si.tbname='$table'
+AND sc.tbcreator='$this->mSchema'
+AND si.uniquerule IN ( 'U', 'P' )
SQL;
$res = $this->query( $sql, $fname );
if ( !$res ) {
return null;
}
- if ($this->fetchObject( $res )) {
+ if ( $this->fetchObject( $res ) ) {
return true;
}
return false;
}
-
+
/**
* Returns the size of a text field, or -1 for "unlimited"
* @param $table String: table name
@@ -1645,15 +1486,15 @@ SQL;
$sql = <<<SQL
SELECT length as size
FROM sysibm.syscolumns sc
-WHERE sc.name='$field' AND sc.tbname='$table' AND sc.tbcreator='$this->mSchema'
+WHERE sc.name='$field' AND sc.tbname='$table'
+AND sc.tbcreator='$this->mSchema'
SQL;
- $res = $this->query($sql);
- $row = $this->fetchObject($res);
+ $res = $this->query( $sql );
+ $row = $this->fetchObject( $res );
$size = $row->size;
- $this->freeResult( $res );
return $size;
}
-
+
/**
* DELETE where the condition is a join
* @param $delTable String: deleting from this table
@@ -1663,18 +1504,26 @@ SQL;
* @param $conds Array: conditionals for join table
* @param $fname String: function name for profiling
*/
- public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) {
+ public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar,
+ $conds, $fname = "DatabaseIbm_db2::deleteJoin" )
+ {
if ( !$conds ) {
- throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this,
+ 'DatabaseIbm_db2::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
$joinTable = $this->tableName( $joinTable );
- $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
+ $sql = <<<SQL
+DELETE FROM $delTable
+WHERE $delVar IN (
+ SELECT $joinVar FROM $joinTable
+
+SQL;
if ( $conds != '*' ) {
$sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
}
- $sql .= ')';
+ $sql .= ' )';
$this->query( $sql, $fname );
}
@@ -1684,22 +1533,23 @@ SQL;
* @param $b Mixed: data to be encoded
* @return IBM_DB2Blob
*/
- public function encodeBlob($b) {
- return new IBM_DB2Blob($b);
+ public function encodeBlob( $b ) {
+ return new IBM_DB2Blob( $b );
}
-
+
/**
* Description is left as an exercise for the reader
* @param $b IBM_DB2Blob: data to be decoded
* @return mixed
*/
- public function decodeBlob($b) {
- return $b->getData();
+ public function decodeBlob( $b ) {
+ return "$b";
}
-
+
/**
* Convert into a list of string being concatenated
- * @param $stringList Array: strings that need to be joined together by the SQL engine
+ * @param $stringList Array: strings that need to be joined together
+ * by the SQL engine
* @return String: joined by the concatenation operator
*/
public function buildConcat( $stringList ) {
@@ -1707,7 +1557,7 @@ SQL;
// Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
return implode( ' || ', $stringList );
}
-
+
/**
* Generates the SQL required to convert a DB2 timestamp into a Unix epoch
* @param $column String: name of timestamp column
@@ -1717,11 +1567,11 @@ SQL;
// TODO
// see SpecialAncientpages
}
-
+
######################################
# Prepared statements
######################################
-
+
/**
* Intended to be compatible with the PEAR::DB wrapper functions.
* http://pear.php.net/manual/en/package.database.db.intro-execute.php
@@ -1735,7 +1585,7 @@ SQL;
* @return resource a prepared DB2 SQL statement
*/
public function prepare( $sql, $func = 'DB2::prepare' ) {
- $stmt = db2_prepare($this->mConn, $sql, $this->mStmtOptions);
+ $stmt = db2_prepare( $this->mConn, $sql, $this->mStmtOptions );
return $stmt;
}
@@ -1744,7 +1594,7 @@ SQL;
* @return Boolean success or failure
*/
public function freePrepared( $prepared ) {
- return db2_free_stmt($prepared);
+ return db2_free_stmt( $prepared );
}
/**
@@ -1759,7 +1609,10 @@ SQL;
$args = func_get_args();
array_shift( $args );
}
- $res = db2_execute($prepared, $args);
+ $res = db2_execute( $prepared, $args );
+ if ( !$res ) {
+ $this->installPrint( db2_stmt_errormsg() );
+ }
return $res;
}
@@ -1792,32 +1645,32 @@ SQL;
public function fillPrepared( $preparedQuery, $args ) {
reset( $args );
$this->preparedArgs =& $args;
-
- foreach ($args as $i => $arg) {
- db2_bind_param($preparedQuery, $i+1, $args[$i]);
+
+ foreach ( $args as $i => $arg ) {
+ db2_bind_param( $preparedQuery, $i+1, $args[$i] );
}
-
+
return $preparedQuery;
}
-
+
/**
* Switches module between regular and install modes
*/
- public function setMode($mode) {
- $old = $this->mMode;
+ public function setMode( $mode ) {
+ $old = $this->mMode;
$this->mMode = $mode;
return $old;
}
-
+
/**
* Bitwise negation of a column or value in SQL
* Same as (~field) in C
* @param $field String
* @return String
*/
- function bitNot($field) {
- //expecting bit-fields smaller than 4bytes
- return 'BITNOT('.$bitField.')';
+ function bitNot( $field ) {
+ // expecting bit-fields smaller than 4bytes
+ return "BITNOT( $field )";
}
/**
@@ -1827,8 +1680,8 @@ SQL;
* @param $fieldRight String
* @return String
*/
- function bitAnd($fieldLeft, $fieldRight) {
- return 'BITAND('.$fieldLeft.', '.$fieldRight.')';
+ function bitAnd( $fieldLeft, $fieldRight ) {
+ return "BITAND( $fieldLeft, $fieldRight )";
}
/**
@@ -1838,7 +1691,17 @@ SQL;
* @param $fieldRight String
* @return String
*/
- function bitOr($fieldLeft, $fieldRight) {
- return 'BITOR('.$fieldLeft.', '.$fieldRight.')';
+ function bitOr( $fieldLeft, $fieldRight ) {
+ return "BITOR( $fieldLeft, $fieldRight )";
+ }
+}
+
+class IBM_DB2Helper {
+ public static function makeArray( $maybeArray ) {
+ if ( !is_array( $maybeArray ) ) {
+ return array( $maybeArray );
+ }
+
+ return $maybeArray;
}
}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 6b1206b0..41ba2d08 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -1,968 +1,1226 @@
<?php
/**
- * This script is the MSSQL Server database abstraction layer
+ * This is the MS SQL Server Native database abstraction layer.
*
- * See maintenance/mssql/README for development notes and other specific information
- * @ingroup Database
* @file
+ * @ingroup Database
+ * @author Joel Penner <a-joelpe at microsoft dot com>
+ * @author Chris Pucci <a-cpucci at microsoft dot com>
+ * @author Ryan Biesemeyer <v-ryanbi at microsoft dot com>
*/
/**
* @ingroup Database
*/
class DatabaseMssql extends DatabaseBase {
+ var $mInsertId = NULL;
+ var $mLastResult = NULL;
+ var $mAffectedRows = NULL;
- var $mAffectedRows;
- var $mLastResult;
- var $mLastError;
- var $mLastErrorNo;
- var $mDatabaseFile;
-
- /**
- * Constructor
- */
- function __construct($server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0, $tablePrefix = 'get from global') {
-
- global $wgOut, $wgDBprefix, $wgCommandLineMode;
- if (!isset($wgOut)) $wgOut = null; # Can't get a reference if it hasn't been set yet
- $this->mOut =& $wgOut;
- $this->mFailFunction = $failFunction;
- $this->mFlags = $flags;
-
- if ( $this->mFlags & DBO_DEFAULT ) {
- if ( $wgCommandLineMode ) {
- $this->mFlags &= ~DBO_TRX;
- } else {
- $this->mFlags |= DBO_TRX;
- }
- }
-
- /** Get the default table prefix*/
- $this->mTablePrefix = $tablePrefix == 'get from global' ? $wgDBprefix : $tablePrefix;
-
- if ($server) $this->open($server, $user, $password, $dbName);
-
+ function cascadingDeletes() {
+ return true;
}
-
- function getType() {
- return 'mssql';
+ function cleanupTriggers() {
+ return true;
+ }
+ function strictIPs() {
+ return true;
+ }
+ function realTimestamps() {
+ return true;
+ }
+ function implicitGroupby() {
+ return false;
+ }
+ function implicitOrderby() {
+ return false;
+ }
+ function functionalIndexes() {
+ return true;
+ }
+ function unionSupportsOrderAndLimit() {
+ return false;
}
- /**
- * todo: check if these should be true like parent class
- */
- function implicitGroupby() { return false; }
- function implicitOrderby() { return false; }
-
- static function newFromParams($server, $user, $password, $dbName, $failFunction = false, $flags = 0) {
- return new DatabaseMssql($server, $user, $password, $dbName, $failFunction, $flags);
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
+ return new DatabaseMssql( $server, $user, $password, $dbName, $flags );
}
- /** Open an MSSQL database and return a resource handle to it
- * NOTE: only $dbName is used, the other parameters are irrelevant for MSSQL databases
+ /**
+ * Usually aborts on failure
*/
- function open($server,$user,$password,$dbName) {
- wfProfileIn(__METHOD__);
-
- # Test for missing mysql.so
- # First try to load it
- if (!@extension_loaded('mssql')) {
- @dl('mssql.so');
+ function open( $server, $user, $password, $dbName ) {
+ # Test for driver support, to avoid suppressed fatal error
+ if ( !function_exists( 'sqlsrv_connect' ) ) {
+ throw new DBConnectionError( $this, "MS Sql Server Native (sqlsrv) functions missing. You can download the driver from: http://go.microsoft.com/fwlink/?LinkId=123470\n" );
}
- # Fail now
- # Otherwise we get a suppressed fatal error, which is very hard to track down
- if (!function_exists( 'mssql_connect')) {
- throw new DBConnectionError( $this, "MSSQL functions missing, have you compiled PHP with the --with-mssql option?\n" );
+ global $wgDBport;
+
+ if ( !strlen( $user ) ) { # e.g. the class is being loaded
+ return;
}
$this->close();
- $this->mServer = $server;
- $this->mUser = $user;
+ $this->mServer = $server;
+ $this->mPort = $wgDBport;
+ $this->mUser = $user;
$this->mPassword = $password;
- $this->mDBname = $dbName;
-
- wfProfileIn("dbconnect-$server");
-
- # Try to connect up to three times
- # The kernel's default SYN retransmission period is far too slow for us,
- # so we use a short timeout plus a manual retry.
- $this->mConn = false;
- $max = 3;
- for ( $i = 0; $i < $max && !$this->mConn; $i++ ) {
- if ( $i > 1 ) {
- usleep( 1000 );
- }
- if ($this->mFlags & DBO_PERSISTENT) {
- @/**/$this->mConn = mssql_pconnect($server, $user, $password);
- } else {
- # Create a new connection...
- @/**/$this->mConn = mssql_connect($server, $user, $password, true);
- }
+ $this->mDBname = $dbName;
+
+ $connectionInfo = array();
+
+ if( $dbName ) {
+ $connectionInfo['Database'] = $dbName;
}
-
- wfProfileOut("dbconnect-$server");
-
- if ($dbName != '') {
- if ($this->mConn !== false) {
- $success = @/**/mssql_select_db($dbName, $this->mConn);
- if (!$success) {
- $error = "Error selecting database $dbName on server {$this->mServer} " .
- "from client host " . wfHostname() . "\n";
- wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
- wfDebug( $error );
- }
- } else {
- wfDebug("DB connection error\n");
- wfDebug("Server: $server, User: $user, Password: ".substr($password, 0, 3)."...\n");
- $success = false;
- }
+
+ // Start NT Auth Hack
+ // Quick and dirty work around to provide NT Auth designation support.
+ // Current solution requires installer to know to input 'ntauth' for both username and password
+ // to trigger connection via NT Auth. - ugly, ugly, ugly
+ // TO-DO: Make this better and add NT Auth choice to MW installer when SQL Server option is chosen.
+ $ntAuthUserTest = strtolower( $user );
+ $ntAuthPassTest = strtolower( $password );
+
+ // Decide which auth scenerio to use
+ if( ( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) ){
+ // Don't add credentials to $connectionInfo
} else {
- # Delay USE query
- $success = (bool)$this->mConn;
+ $connectionInfo['UID'] = $user;
+ $connectionInfo['PWD'] = $password;
+ }
+ // End NT Auth Hack
+
+ $this->mConn = @sqlsrv_connect( $server, $connectionInfo );
+
+ if ( $this->mConn === false ) {
+ wfDebug( "DB connection error\n" );
+ wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
+ wfDebug( $this->lastError() . "\n" );
+ return false;
}
- if (!$success) $this->reportConnectionError();
- $this->mOpened = $success;
- wfProfileOut(__METHOD__);
- return $success;
+ $this->mOpened = true;
+ return $this->mConn;
}
/**
- * Close an MSSQL database
+ * Closes a database connection, if it is open
+ * Returns success, true if already closed
*/
function close() {
$this->mOpened = false;
- if ($this->mConn) {
- if ($this->trxLevel()) $this->commit();
- return mssql_close($this->mConn);
- } else return true;
+ if ( $this->mConn ) {
+ return sqlsrv_close( $this->mConn );
+ } else {
+ return true;
+ }
}
- /**
- * - MSSQL doesn't seem to do buffered results
- * - the trasnaction syntax is modified here to avoid having to replicate
- * Database::query which uses BEGIN, COMMIT, ROLLBACK
- */
- function doQuery($sql) {
- if ($sql == 'BEGIN' || $sql == 'COMMIT' || $sql == 'ROLLBACK') return true; # $sql .= ' TRANSACTION';
- $sql = preg_replace('|[^\x07-\x7e]|','?',$sql); # TODO: need to fix unicode - just removing it here while testing
- $ret = mssql_query($sql, $this->mConn);
- if ($ret === false) {
- $err = mssql_get_last_message();
- if ($err) $this->mlastError = $err;
- $row = mssql_fetch_row(mssql_query('select @@ERROR'));
- if ($row[0]) $this->mlastErrorNo = $row[0];
- } else $this->mlastErrorNo = false;
- return $ret;
+ function doQuery( $sql ) {
+ wfDebug( "SQL: [$sql]\n" );
+ $this->offset = 0;
+
+ // several extensions seem to think that all databases support limits via LIMIT N after the WHERE clause
+ // well, MSSQL uses SELECT TOP N, so to catch any of those extensions we'll do a quick check for a LIMIT
+ // clause and pass $sql through $this->LimitToTopN() which parses the limit clause and passes the result to
+ // $this->limitResult();
+ if ( preg_match( '/\bLIMIT\s*/i', $sql ) ) {
+ // massage LIMIT -> TopN
+ $sql = $this->LimitToTopN( $sql ) ;
+ }
+
+ // MSSQL doesn't have EXTRACT(epoch FROM XXX)
+ if ( preg_match('#\bEXTRACT\s*?\(\s*?EPOCH\s+FROM\b#i', $sql, $matches ) ) {
+ // This is same as UNIX_TIMESTAMP, we need to calc # of seconds from 1970
+ $sql = str_replace( $matches[0], "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),", $sql );
+ }
+
+ // perform query
+ $stmt = sqlsrv_query( $this->mConn, $sql );
+ if ( $stmt == false ) {
+ $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
+ "Query: " . htmlentities( $sql ) . "\n" .
+ "Function: " . __METHOD__ . "\n";
+ // process each error (our driver will give us an array of errors unlike other providers)
+ foreach ( sqlsrv_errors() as $error ) {
+ $message .= $message . "ERROR[" . $error['code'] . "] " . $error['message'] . "\n";
+ }
+
+ throw new DBUnexpectedError( $this, $message );
+ }
+ // remember number of rows affected
+ $this->mAffectedRows = sqlsrv_rows_affected( $stmt );
+
+ // if it is a SELECT statement, or an insert with a request to output something we want to return a row.
+ if ( ( preg_match( '#\bSELECT\s#i', $sql ) ) ||
+ ( preg_match( '#\bINSERT\s#i', $sql ) && preg_match( '#\bOUTPUT\s+INSERTED\b#i', $sql ) ) ) {
+ // this is essentially a rowset, but Mediawiki calls these 'result'
+ // the rowset owns freeing the statement
+ $res = new MssqlResult( $stmt );
+ } else {
+ // otherwise we simply return it was successful, failure throws an exception
+ $res = true;
+ }
+ return $res;
}
- /**
- * Free a result object
- */
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- if ( !@/**/mssql_free_result( $res ) ) {
- throw new DBUnexpectedError( $this, "Unable to free MSSQL result" );
- }
+ $res->free();
}
- /**
- * Fetch the next row from the given result object, in object form.
- * Fields can be retrieved with $row->fieldname, with fields acting like
- * member variables.
- *
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$row = mssql_fetch_object( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
- }
+ $row = $res->fetch( 'OBJECT' );
return $row;
}
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
- *
- * @param $res SQL result object as returned from Database::query(), etc.
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchRow( $res ) {
+ function getErrors() {
+ $strRet = '';
+ $retErrors = sqlsrv_errors( SQLSRV_ERR_ALL );
+ if ( $retErrors != null ) {
+ foreach ( $retErrors as $arrError ) {
+ $strRet .= "SQLState: " . $arrError[ 'SQLSTATE'] . "\n";
+ $strRet .= "Error Code: " . $arrError[ 'code'] . "\n";
+ $strRet .= "Message: " . $arrError[ 'message'] . "\n";
+ }
+ } else {
+ $strRet = "No errors found";
+ }
+ return $strRet;
+ }
+
+ function fetchRow( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$row = mssql_fetch_array( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
- }
+ $row = $res->fetch( SQLSRV_FETCH_BOTH );
return $row;
}
- /**
- * Get the number of rows in a result object
- */
function numRows( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$n = mssql_num_rows( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $n;
+ return ( $res ) ? $res->numrows() : 0;
}
- /**
- * Get the number of fields in a result object
- * See documentation for mysql_num_fields()
- * @param $res SQL result object as returned from Database::query(), etc.
- */
function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return mssql_num_fields( $res );
+ return ( $res ) ? $res->numfields() : 0;
}
- /**
- * Get a field name in a result object
- * See documentation for mysql_field_name():
- * http://www.php.net/mysql_field_name
- * @param $res SQL result object as returned from Database::query(), etc.
- * @param $n Int
- */
function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return mssql_field_name( $res, $n );
+ return ( $res ) ? $res->fieldname( $n ) : 0;
}
/**
- * Get the inserted value of an auto-increment row
- *
- * The value inserted should be fetched from nextSequenceValue()
- *
- * Example:
- * $id = $dbw->nextSequenceValue('page_page_id_seq');
- * $dbw->insert('page',array('page_id' => $id));
- * $id = $dbw->insertId();
+ * This must be called after nextSequenceVal
*/
function insertId() {
- $row = mssql_fetch_row(mssql_query('select @@IDENTITY'));
- return $row[0];
+ return $this->mInsertId;
}
- /**
- * Change the position of the cursor in a result object
- * See mysql_data_seek()
- * @param $res SQL result object as returned from Database::query(), etc.
- * @param $row Database row
- */
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- return mssql_data_seek( $res, $row );
+ return ( $res ) ? $res->seek( $row ) : false;
}
- /**
- * Get the last error number
- */
- function lastErrno() {
- return $this->mlastErrorNo;
+ function lastError() {
+ if ( $this->mConn ) {
+ return $this->getErrors();
+ }
+ else {
+ return "No database connection";
+ }
}
- /**
- * Get a description of the last error
- */
- function lastError() {
- return $this->mlastError;
+ function lastErrno() {
+ $err = sqlsrv_errors( SQLSRV_ERR_ALL );
+ if ( $err[0] ) return $err[0]['code'];
+ else return 0;
}
- /**
- * Get the number of rows affected by the last write query
- */
function affectedRows() {
- return mssql_rows_affected( $this->mConn );
+ return $this->mAffectedRows;
}
/**
- * Simple UPDATE wrapper
- * Usually aborts on failure
- * If errors are explicitly ignored, returns success
+ * SELECT wrapper
*
- * This function exists for historical reasons, Database::update() has a more standard
- * calling convention and feature set
+ * @param $table Mixed: array or string, table name(s) (prefix auto-added)
+ * @param $vars Mixed: array or string, field name(s) to be retrieved
+ * @param $conds Mixed: array or string, condition(s) for WHERE
+ * @param $fname String: calling function name (use __METHOD__) for logs/profiling
+ * @param $options Array: associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of supported stuff
+ * @param $join_conds Array: Associative array of table join conditions (optional)
+ * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure
*/
- function set( $table, $var, $value, $cond, $fname = 'Database::set' )
+ function select( $table, $vars, $conds = '', $fname = 'DatabaseMssql::select', $options = array(), $join_conds = array() )
{
- if ($value == "NULL") $value = "''"; # see comments in makeListWithoutNulls()
- $table = $this->tableName( $table );
- $sql = "UPDATE $table SET $var = '" .
- $this->strencode( $value ) . "' WHERE ($cond)";
- return (bool)$this->query( $sql, $fname );
- }
-
- /**
- * Simple SELECT wrapper, returns a single field, input must be encoded
- * Usually aborts on failure
- * If errors are explicitly ignored, returns FALSE on failure
- */
- function selectField( $table, $var, $cond='', $fname = 'Database::selectField', $options = array() ) {
- if ( !is_array( $options ) ) {
- $options = array( $options );
- }
- $options['LIMIT'] = 1;
-
- $res = $this->select( $table, $var, $cond, $fname, $options );
- if ( $res === false || !$this->numRows( $res ) ) {
- return false;
- }
- $row = $this->fetchRow( $res );
- if ( $row !== false ) {
- $this->freeResult( $res );
- return $row[0];
- } else {
- return false;
- }
- }
-
- /**
- * Returns an optional USE INDEX clause to go after the table, and a
- * string to go at the end of the query
- *
- * @private
- *
- * @param $options Array: an associative array of options to be turned into
- * an SQL query, valid keys are listed in the function.
- * @return array
- */
- function makeSelectOptions( $options ) {
- $preLimitTail = $postLimitTail = '';
- $startOpts = '';
-
- $noKeyOptions = array();
- foreach ( $options as $key => $option ) {
- if ( is_numeric( $key ) ) {
- $noKeyOptions[$option] = true;
- }
- }
-
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
- if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
-
- //if (isset($options['LIMIT'])) {
- // $tailOpts .= $this->limitResult('', $options['LIMIT'],
- // isset($options['OFFSET']) ? $options['OFFSET']
- // : false);
- //}
-
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
-
- # Various MySQL extensions
- if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */';
- if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
- if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
- if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
- if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT';
- if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS';
- if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE';
- if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE';
-
- if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
- $useIndex = $this->useIndexClause( $options['USE INDEX'] );
- } else {
- $useIndex = '';
+ $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+ if ( isset( $options['EXPLAIN'] ) ) {
+ sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL ON;" );
+ $ret = $this->query( $sql, $fname );
+ sqlsrv_query( $this->mConn, "SET SHOWPLAN_ALL OFF;" );
+ return $ret;
}
-
- return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
+ return $this->query( $sql, $fname );
}
/**
* SELECT wrapper
*
- * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
- * @param $vars Mixed: Array or string, field name(s) to be retrieved
- * @param $conds Mixed: Array or string, condition(s) for WHERE
+ * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
+ * @param $vars Mixed: Array or string, field name(s) to be retrieved
+ * @param $conds Mixed: Array or string, condition(s) for WHERE
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
- * @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of supported stuff
+ * @param $join_conds Array: Associative array of table join conditions (optional)
+ * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return string, the SQL text
*/
- function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array() )
- {
- if( is_array( $vars ) ) {
- $vars = implode( ',', $vars );
- }
- if( !is_array( $options ) ) {
- $options = array( $options );
- }
- if( is_array( $table ) ) {
- if ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
- $from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] );
- else
- $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
- } elseif ($table!='') {
- if ($table{0}==' ') {
- $from = ' FROM ' . $table;
- } else {
- $from = ' FROM ' . $this->tableName( $table );
- }
- } else {
- $from = '';
- }
-
- list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
-
- if( !empty( $conds ) ) {
- if ( is_array( $conds ) ) {
- $conds = $this->makeList( $conds, LIST_AND );
- }
- $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
- } else {
- $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
- }
-
- if (isset($options['LIMIT']))
- $sql = $this->limitResult($sql, $options['LIMIT'],
- isset($options['OFFSET']) ? $options['OFFSET'] : false);
- $sql = "$sql $postLimitTail";
-
- if (isset($options['EXPLAIN'])) {
- $sql = 'EXPLAIN ' . $sql;
+ function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseMssql::select', $options = array(), $join_conds = array() ) {
+ if ( isset( $options['EXPLAIN'] ) ) {
+ unset( $options['EXPLAIN'] );
}
- return $this->query( $sql, $fname );
+ return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
}
/**
- * Determines whether a field exists in a table
- * Usually aborts on failure
- * If errors are explicitly ignored, returns NULL on failure
+ * Estimate rows in dataset
+ * Returns estimated count, based on SHOWPLAN_ALL output
+ * This is not necessarily an accurate estimate, so use sparingly
+ * Returns -1 if count cannot be found
+ * Takes same arguments as Database::select()
*/
- function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
- $table = $this->tableName( $table );
- $sql = "SELECT TOP 1 * FROM $table";
- $res = $this->query( $sql, 'Database::fieldExists' );
-
- $found = false;
- while ( $row = $this->fetchArray( $res ) ) {
- if ( isset($row[$field]) ) {
- $found = true;
- break;
- }
+ function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseMssql::estimateRowCount', $options = array() ) {
+ $options['EXPLAIN'] = true;// http://msdn2.microsoft.com/en-us/library/aa259203.aspx
+ $res = $this->select( $table, $vars, $conds, $fname, $options );
+
+ $rows = -1;
+ if ( $res ) {
+ $row = $this->fetchRow( $res );
+ if ( isset( $row['EstimateRows'] ) ) $rows = $row['EstimateRows'];
}
-
- $this->freeResult( $res );
- return $found;
+ return $rows;
}
+
/**
- * Get information about an index into an object
- * Returns false if the index does not exist
+ * Returns information about an index
+ * If errors are explicitly ignored, returns NULL on failure
*/
- function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
-
- throw new DBUnexpectedError( $this, 'Database::indexInfo called which is not supported yet' );
- return null;
-
- $table = $this->tableName( $table );
- $sql = 'SHOW INDEX FROM '.$table;
+ function indexInfo( $table, $index, $fname = 'DatabaseMssql::indexExists' ) {
+ # This does not return the same info as MYSQL would, but that's OK because MediaWiki never uses the
+ # returned value except to check for the existance of indexes.
+ $sql = "sp_helpindex '" . $table . "'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return null;
+ return NULL;
}
$result = array();
- while ( $row = $this->fetchObject( $res ) ) {
- if ( $row->Key_name == $index ) {
- $result[] = $row;
- }
- }
- $this->freeResult($res);
-
- return empty($result) ? false : $result;
- }
-
- /**
- * Query whether a given table exists
- */
- function tableExists( $table ) {
- $table = $this->tableName( $table );
- $res = $this->query( "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '$table'" );
- $exist = ($res->numRows() > 0);
- $this->freeResult($res);
- return $exist;
- }
-
- /**
- * mysql_fetch_field() wrapper
- * Returns false if the field doesn't exist
- *
- * @param $table
- * @param $field
- */
- function fieldInfo( $table, $field ) {
- $table = $this->tableName( $table );
- $res = $this->query( "SELECT TOP 1 * FROM $table" );
- $n = mssql_num_fields( $res->result );
- for( $i = 0; $i < $n; $i++ ) {
- $meta = mssql_fetch_field( $res->result, $i );
- if( $field == $meta->name ) {
- return new MSSQLField($meta);
+ foreach ( $res as $row ) {
+ if ( $row->index_name == $index ) {
+ $row->Non_unique = !stristr( $row->index_description, "unique" );
+ $cols = explode( ", ", $row->index_keys );
+ foreach ( $cols as $col ) {
+ $row->Column_name = trim( $col );
+ $result[] = clone $row;
+ }
+ } else if ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
+ $row->Non_unique = 0;
+ $cols = explode( ", ", $row->index_keys );
+ foreach ( $cols as $col ) {
+ $row->Column_name = trim( $col );
+ $result[] = clone $row;
+ }
}
}
- return false;
- }
-
- /**
- * mysql_field_type() wrapper
- */
- function fieldType( $res, $index ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mssql_field_type( $res, $index );
+ return empty( $result ) ? false : $result;
}
/**
* INSERT wrapper, inserts an array into a table
*
- * $a may be a single associative array, or an array of these with numeric keys, for
+ * $arrToInsert may be a single associative array, or an array of these with numeric keys, for
* multi-row insert.
*
* Usually aborts on failure
* If errors are explicitly ignored, returns success
- *
- * Same as parent class implementation except that it removes primary key from column lists
- * because MSSQL doesn't support writing nulls to IDENTITY (AUTO_INCREMENT) columns
*/
- function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
+ function insert( $table, $arrToInsert, $fname = 'DatabaseMssql::insert', $options = array() ) {
# No rows to insert, easy just return now
- if ( !count( $a ) ) {
+ if ( !count( $arrToInsert ) ) {
return true;
}
- $table = $this->tableName( $table );
+
if ( !is_array( $options ) ) {
$options = array( $options );
}
-
- # todo: need to record primary keys at table create time, and remove NULL assignments to them
- if ( isset( $a[0] ) && is_array( $a[0] ) ) {
- $multi = true;
- $keys = array_keys( $a[0] );
-# if (ereg('_id$',$keys[0])) {
- foreach ($a as $i) {
- if (is_null($i[$keys[0]])) unset($i[$keys[0]]); # remove primary-key column from multiple insert lists if empty value
- }
-# }
- $keys = array_keys( $a[0] );
- } else {
- $multi = false;
- $keys = array_keys( $a );
-# if (ereg('_id$',$keys[0]) && empty($a[$keys[0]])) unset($a[$keys[0]]); # remove primary-key column from insert list if empty value
- if (is_null($a[$keys[0]])) unset($a[$keys[0]]); # remove primary-key column from insert list if empty value
- $keys = array_keys( $a );
+
+ $table = $this->tableName( $table );
+
+ if ( !( isset( $arrToInsert[0] ) && is_array( $arrToInsert[0] ) ) ) {// Not multi row
+ $arrToInsert = array( 0 => $arrToInsert );// make everything multi row compatible
}
- # handle IGNORE option
- # example:
- # MySQL: INSERT IGNORE INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
- # MSSQL: IF NOT EXISTS (SELECT * FROM user_groups WHERE ug_user = '1') INSERT INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
- $ignore = in_array('IGNORE',$options);
-
- # remove IGNORE from options list
- if ($ignore) {
- $oldoptions = $options;
- $options = array();
- foreach ($oldoptions as $o) if ($o != 'IGNORE') $options[] = $o;
- }
-
- $keylist = implode(',', $keys);
- $sql = 'INSERT '.implode(' ', $options)." INTO $table (".implode(',', $keys).') VALUES ';
- if ($multi) {
- if ($ignore) {
- # If multiple and ignore, then do each row as a separate conditional insert
- foreach ($a as $row) {
- $prival = $row[$keys[0]];
- $sql = "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival') $sql";
- if (!$this->query("$sql (".$this->makeListWithoutNulls($row).')', $fname)) return false;
+ $allOk = true;
+
+
+ // We know the table we're inserting into, get its identity column
+ $identity = null;
+ $tableRaw = preg_replace( '#\[([^\]]*)\]#', '$1', $table ); // strip matching square brackets from table name
+ $res = $this->doQuery( "SELECT NAME AS idColumn FROM SYS.IDENTITY_COLUMNS WHERE OBJECT_NAME(OBJECT_ID)='{$tableRaw}'" );
+ if( $res && $res->numrows() ){
+ // There is an identity for this table.
+ $identity = array_pop( $res->fetch( SQLSRV_FETCH_ASSOC ) );
+ }
+ unset( $res );
+
+ foreach ( $arrToInsert as $a ) {
+ // start out with empty identity column, this is so we can return it as a result of the insert logic
+ $sqlPre = '';
+ $sqlPost = '';
+ $identityClause = '';
+
+ // if we have an identity column
+ if( $identity ) {
+ // iterate through
+ foreach ($a as $k => $v ) {
+ if ( $k == $identity ) {
+ if( !is_null($v) ){
+ // there is a value being passed to us, we need to turn on and off inserted identity
+ $sqlPre = "SET IDENTITY_INSERT $table ON;" ;
+ $sqlPost = ";SET IDENTITY_INSERT $table OFF;";
+
+ } else {
+ // we can't insert NULL into an identity column, so remove the column from the insert.
+ unset( $a[$k] );
+ }
+ }
}
- return true;
- } else {
- $first = true;
- foreach ($a as $row) {
- if ($first) $first = false; else $sql .= ',';
- $sql .= '('.$this->makeListWithoutNulls($row).')';
+ $identityClause = "OUTPUT INSERTED.$identity "; // we want to output an identity column as result
+ }
+
+ $keys = array_keys( $a );
+
+
+ // INSERT IGNORE is not supported by SQL Server
+ // remove IGNORE from options list and set ignore flag to true
+ $ignoreClause = false;
+ foreach ( $options as $k => $v ) {
+ if ( strtoupper( $v ) == "IGNORE" ) {
+ unset( $options[$k] );
+ $ignoreClause = true;
}
}
- } else {
- if ($ignore) {
+
+ // translate MySQL INSERT IGNORE to something SQL Server can use
+ // example:
+ // MySQL: INSERT IGNORE INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
+ // MSSQL: IF NOT EXISTS (SELECT * FROM user_groups WHERE ug_user = '1') INSERT INTO user_groups (ug_user,ug_group) VALUES ('1','sysop')
+ if ( $ignoreClause == true ) {
$prival = $a[$keys[0]];
- $sql = "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival') $sql";
+ $sqlPre .= "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival')";
+ }
+
+ // Build the actual query
+ $sql = $sqlPre . 'INSERT ' . implode( ' ', $options ) .
+ " INTO $table (" . implode( ',', $keys ) . ") $identityClause VALUES (";
+
+ $first = true;
+ foreach ( $a as $value ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $sql .= ',';
+ }
+ if ( is_string( $value ) ) {
+ $sql .= $this->addIdentifierQuotes( $value );
+ } elseif ( is_null( $value ) ) {
+ $sql .= 'null';
+ } elseif ( is_array( $value ) || is_object( $value ) ) {
+ if ( is_object( $value ) && strtolower( get_class( $value ) ) == 'blob' ) {
+ $sql .= $this->addIdentifierQuotes( $value->fetch() );
+ } else {
+ $sql .= $this->addIdentifierQuotes( serialize( $value ) );
+ }
+ } else {
+ $sql .= $value;
+ }
}
- $sql .= '('.$this->makeListWithoutNulls($a).')';
+ $sql .= ')' . $sqlPost;
+
+ // Run the query
+ $ret = sqlsrv_query( $this->mConn, $sql );
+
+ if ( $ret === false ) {
+ throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname );
+ } elseif ( $ret != NULL ) {
+ // remember number of rows affected
+ $this->mAffectedRows = sqlsrv_rows_affected( $ret );
+ if ( !is_null($identity) ) {
+ // then we want to get the identity column value we were assigned and save it off
+ $row = sqlsrv_fetch_object( $ret );
+ $this->mInsertId = $row->$identity;
+ }
+ sqlsrv_free_stmt( $ret );
+ continue;
+ }
+ $allOk = false;
}
- return (bool)$this->query( $sql, $fname );
+ return $allOk;
}
/**
- * MSSQL doesn't allow implicit casting of NULL's into non-null values for NOT NULL columns
- * for now I've just converted the NULL's in the lists for updates and inserts into empty strings
- * which get implicitly casted to 0 for numeric columns
- * NOTE: the set() method above converts NULL to empty string as well but not via this method
+ * INSERT SELECT wrapper
+ * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
+ * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
+ * $conds may be "*" to copy the whole table
+ * srcTable may be an array of tables.
*/
- function makeListWithoutNulls($a, $mode = LIST_COMMA) {
- return str_replace("NULL","''",$this->makeList($a,$mode));
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
+ $insertOptions = array(), $selectOptions = array() )
+ {
+ $ret = parent::insertSelect( $destTable, $srcTable, $varMap, $conds, $fname, $insertOptions, $selectOptions );
+
+ if ( $ret === false ) {
+ throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), $sql, $fname );
+ } elseif ( $ret != NULL ) {
+ // remember number of rows affected
+ $this->mAffectedRows = sqlsrv_rows_affected( $ret );
+ return $ret;
+ }
+ return NULL;
}
/**
- * UPDATE wrapper, takes a condition array and a SET array
+ * Format a table name ready for use in constructing an SQL query
+ *
+ * This does two important things: it brackets table names which as necessary,
+ * and it adds a table prefix if there is one.
*
- * @param $table String: The table to UPDATE
- * @param $values Array: An array of values to SET
- * @param $conds Array: An array of conditions (WHERE). Use '*' to update all rows.
- * @param $fname String: The Class::Function calling this function
- * (for the log)
- * @param $options Array: An array of UPDATE options, can be one or
- * more of IGNORE, LOW_PRIORITY
- * @return bool
+ * All functions of this object which require a table name call this function
+ * themselves. Pass the canonical name to such functions. This is only needed
+ * when calling query() directly.
+ *
+ * @param $name String: database table name
*/
- function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
- $table = $this->tableName( $table );
- $opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET " . $this->makeListWithoutNulls( $values, LIST_SET );
- if ( $conds != '*' ) {
- $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
+ function tableName( $name ) {
+ global $wgSharedDB;
+ # Skip quoted literals
+ if ( $name != '' && $name { 0 } != '[' ) {
+ if ( $this->mTablePrefix !== '' && strpos( '.', $name ) === false ) {
+ $name = "{$this->mTablePrefix}$name";
+ }
+ if ( isset( $wgSharedDB ) && "{$this->mTablePrefix}user" == $name ) {
+ $name = "[$wgSharedDB].[$name]";
+ } else {
+ # Standard quoting
+ if ( $name != '' ) $name = "[$name]";
+ }
}
- return $this->query( $sql, $fname );
+ return $name;
}
/**
- * Make UPDATE options for the Database::update function
- *
- * @private
- * @param $options Array: The options passed to Database::update
- * @return string
+ * Return the next in a sequence, save the value for retrieval via insertId()
*/
- function makeUpdateOptions( $options ) {
- if( !is_array( $options ) ) {
- $options = array( $options );
+ function nextSequenceValue( $seqName ) {
+ if ( !$this->tableExists( 'sequence_' . $seqName ) ) {
+ sqlsrv_query( $this->mConn, "CREATE TABLE [sequence_$seqName] (id INT NOT NULL IDENTITY PRIMARY KEY, junk varchar(10) NULL)" );
}
- $opts = array();
- if ( in_array( 'LOW_PRIORITY', $options ) )
- $opts[] = $this->lowPriorityOption();
- if ( in_array( 'IGNORE', $options ) )
- $opts[] = 'IGNORE';
- return implode(' ', $opts);
- }
+ sqlsrv_query( $this->mConn, "INSERT INTO [sequence_$seqName] (junk) VALUES ('')" );
+ $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
+ $row = sqlsrv_fetch_array( $ret, SQLSRV_FETCH_ASSOC );// KEEP ASSOC THERE, weird weird bug dealing with the return value if you don't
- /**
- * Change the current database
- */
- function selectDB( $db ) {
- $this->mDBname = $db;
- return mssql_select_db( $db, $this->mConn );
+ sqlsrv_free_stmt( $ret );
+ $this->mInsertId = $row['id'];
+ return $row['id'];
}
/**
- * MSSQL has a problem with the backtick quoting, so all this does is ensure the prefix is added exactly once
+ * Return the current value of a sequence. Assumes it has ben nextval'ed in this session.
*/
- function tableName($name) {
- return strpos($name, $this->mTablePrefix) === 0 ? $name : "{$this->mTablePrefix}$name";
+ function currentSequenceValue( $seqName ) {
+ $ret = sqlsrv_query( $this->mConn, "SELECT TOP 1 id FROM [sequence_$seqName] ORDER BY id DESC" );
+ if ( $ret !== false ) {
+ $row = sqlsrv_fetch_array( $ret );
+ sqlsrv_free_stmt( $ret );
+ return $row['id'];
+ } else {
+ return $this->nextSequenceValue( $seqName );
+ }
}
- /**
- * MSSQL doubles quotes instead of escaping them
- * @param $s String to be slashed.
- * @return string slashed string.
- */
- function strencode($s) {
- return str_replace("'","''",$s);
- }
- /**
- * REPLACE query wrapper
- * PostgreSQL simulates this with a DELETE followed by INSERT
- * $row is the row to insert, an associative array
- * $uniqueIndexes is an array of indexes. Each element may be either a
- * field name or an array of field names
- *
- * It may be more efficient to leave off unique indexes which are unlikely to collide.
- * However if you do this, you run the risk of encountering errors which wouldn't have
- * occurred in MySQL
- *
- * @todo migrate comment to phodocumentor format
- */
- function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
+ # REPLACE query wrapper
+ # MSSQL simulates this with a DELETE followed by INSERT
+ # $row is the row to insert, an associative array
+ # $uniqueIndexes is an array of indexes. Each element may be either a
+ # field name or an array of field names
+ #
+ # It may be more efficient to leave off unique indexes which are unlikely to collide.
+ # However if you do this, you run the risk of encountering errors which wouldn't have
+ # occurred in MySQL
+ function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMssql::replace' ) {
$table = $this->tableName( $table );
+ if ( count( $rows ) == 0 ) {
+ return;
+ }
+
# Single row case
if ( !is_array( reset( $rows ) ) ) {
$rows = array( $rows );
}
- $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES ';
- $first = true;
foreach ( $rows as $row ) {
- if ( $first ) {
- $first = false;
- } else {
- $sql .= ',';
+ # Delete rows which collide
+ if ( $uniqueIndexes ) {
+ $sql = "DELETE FROM $table WHERE ";
+ $first = true;
+ foreach ( $uniqueIndexes as $index ) {
+ if ( $first ) {
+ $first = false;
+ $sql .= "(";
+ } else {
+ $sql .= ') OR (';
+ }
+ if ( is_array( $index ) ) {
+ $first2 = true;
+ foreach ( $index as $col ) {
+ if ( $first2 ) {
+ $first2 = false;
+ } else {
+ $sql .= ' AND ';
+ }
+ $sql .= $col . '=' . $this->addQuotes( $row[$col] );
+ }
+ } else {
+ $sql .= $index . '=' . $this->addQuotes( $row[$index] );
+ }
+ }
+ $sql .= ')';
+ $this->query( $sql, $fname );
}
- $sql .= '(' . $this->makeList( $row ) . ')';
+
+ # Now insert the row
+ $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) . ') VALUES (' .
+ $this->makeList( $row, LIST_COMMA ) . ')';
+ $this->query( $sql, $fname );
}
- return $this->query( $sql, $fname );
}
- /**
- * DELETE where the condition is a join
- * MySQL does this with a multi-table DELETE syntax, PostgreSQL does it with sub-selects
- *
- * For safety, an empty $conds will not delete everything. If you want to delete all rows where the
- * join condition matches, set $conds='*'
- *
- * DO NOT put the join condition in $conds
- *
- * @param $delTable String: The table to delete from.
- * @param $joinTable String: The other table.
- * @param $delVar String: The variable to join on, in the first table.
- * @param $joinVar String: The variable to join on, in the second table.
- * @param $conds Array: Condition array of field names mapped to variables, ANDed together in the WHERE clause
- * @param $fname String: Calling function name
- */
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) {
+ # DELETE where the condition is a join
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseMssql::deleteJoin" ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabaseMssql::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
$joinTable = $this->tableName( $joinTable );
- $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
+ $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
if ( $conds != '*' ) {
- $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
+ $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
}
+ $sql .= ')';
- return $this->query( $sql, $fname );
+ $this->query( $sql, $fname );
}
- /**
- * Returns the size of a text field, or -1 for "unlimited"
- */
+ # Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
- $sql = "SELECT TOP 1 * FROM $table;";
- $res = $this->query( $sql, 'Database::textFieldSize' );
- $row = $this->fetchObject( $res );
- $this->freeResult( $res );
-
- $m = array();
- if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
- $size = $m[1];
+ $sql = "SELECT CHARACTER_MAXIMUM_LENGTH,DATA_TYPE FROM INFORMATION_SCHEMA.Columns
+ WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'";
+ $res = $this->query( $sql );
+ $row = $this->fetchRow( $res );
+ $size = -1;
+ if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) $size = $row['CHARACTER_MAXIMUM_LENGTH'];
+ return $size;
+ }
+
+ /**
+ * Construct a LIMIT query with optional offset
+ * This is used for query pages
+ * $sql string SQL query we will append the limit too
+ * $limit integer the SQL limit
+ * $offset integer the SQL offset (default false)
+ */
+ function limitResult( $sql, $limit, $offset = false ) {
+ if ( $offset === false || $offset == 0 ) {
+ if ( strpos( $sql, "SELECT" ) === false ) {
+ return "TOP {$limit} " . $sql;
+ } else {
+ return preg_replace( '/\bSELECT(\s*DISTINCT)?\b/Dsi', 'SELECT$1 TOP ' . $limit, $sql, 1 );
+ }
} else {
- $size = -1;
+ $sql = '
+ SELECT * FROM (
+ SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 FROM (
+ SELECT 1 AS line2, sub1.* FROM (' . $sql . ') AS sub1
+ ) as sub2
+ ) AS sub3
+ WHERE line3 BETWEEN ' . ( $offset + 1 ) . ' AND ' . ( $offset + $limit );
+ return $sql;
}
- return $size;
+ }
+
+ // If there is a limit clause, parse it, strip it, and pass the remaining sql through limitResult()
+ // with the appropriate parameters. Not the prettiest solution, but better than building a whole new parser.
+ // This exists becase there are still too many extensions that don't use dynamic sql generation.
+ function LimitToTopN( $sql ) {
+ // Matches: LIMIT {[offset,] row_count | row_count OFFSET offset}
+ $pattern = '/\bLIMIT\s+((([0-9]+)\s*,\s*)?([0-9]+)(\s+OFFSET\s+([0-9]+))?)/i';
+ if ( preg_match( $pattern, $sql, $matches ) ) {
+ // row_count = $matches[4]
+ $row_count = $matches[4];
+ // offset = $matches[3] OR $matches[6]
+ $offset = $matches[3] or
+ $offset = $matches[6] or
+ $offset = false;
+
+ // strip the matching LIMIT clause out
+ $sql = str_replace( $matches[0], '', $sql );
+ return $this->limitResult( $sql, $row_count, $offset );
+ }
+ return $sql;
+ }
+
+ // MSSQL does support this, but documentation is too thin to make a generalized
+ // function for this. Apparently UPDATE TOP (N) works, but the sort order
+ // may not be what we're expecting so the top n results may be a random selection.
+ // TODO: Implement properly.
+ function limitResultForUpdate( $sql, $num ) {
+ return $sql;
+ }
+
+
+ function timestamp( $ts = 0 ) {
+ return wfTimestamp( TS_ISO_8601, $ts );
}
/**
- * @return string Returns the text of the low priority option if it is supported, or a blank string otherwise
+ * @return string wikitext of a link to the server software's web site
*/
- function lowPriorityOption() {
- return 'LOW_PRIORITY';
+ public static function getSoftwareLink() {
+ return "[http://www.microsoft.com/sql/ MS SQL Server]";
}
/**
- * INSERT SELECT wrapper
- * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
- * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
- * $conds may be "*" to copy the whole table
- * srcTable may be an array of tables.
+ * @return string Version information from the database
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect',
- $insertOptions = array(), $selectOptions = array() )
- {
- $destTable = $this->tableName( $destTable );
- if ( is_array( $insertOptions ) ) {
- $insertOptions = implode( ' ', $insertOptions );
- }
- if( !is_array( $selectOptions ) ) {
- $selectOptions = array( $selectOptions );
- }
- list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
- if( is_array( $srcTable ) ) {
- $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
- } else {
- $srcTable = $this->tableName( $srcTable );
- }
- $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
- " SELECT $startOpts " . implode( ',', $varMap ) .
- " FROM $srcTable $useIndex ";
- if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ function getServerVersion() {
+ $server_info = sqlsrv_server_info( $this->mConn );
+ $version = 'Error';
+ if ( isset( $server_info['SQLServerVersion'] ) ) $version = $server_info['SQLServerVersion'];
+ return $version;
+ }
+
+ function tableExists ( $table, $schema = false ) {
+ $res = sqlsrv_query( $this->mConn, "SELECT * FROM information_schema.tables
+ WHERE table_type='BASE TABLE' AND table_name = '$table'" );
+ if ( $res === false ) {
+ print( "Error in tableExists query: " . $this->getErrors() );
+ return false;
}
- $sql .= " $tailOpts";
- return $this->query( $sql, $fname );
+ if ( sqlsrv_fetch( $res ) )
+ return true;
+ else
+ return false;
}
/**
- * Construct a LIMIT query with optional offset
- * This is used for query pages
- * $sql string SQL query we will append the limit to
- * $limit integer the SQL limit
- * $offset integer the SQL offset (default false)
+ * Query whether a given column exists in the mediawiki schema
*/
- function limitResult($sql, $limit, $offset=false) {
- if( !is_numeric($limit) ) {
- throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
+ function fieldExists( $table, $field, $fname = 'DatabaseMssql::fieldExists' ) {
+ $table = $this->tableName( $table );
+ $res = sqlsrv_query( $this->mConn, "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.Columns
+ WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+ if ( $res === false ) {
+ print( "Error in fieldExists query: " . $this->getErrors() );
+ return false;
}
- if ($offset) {
- throw new DBUnexpectedError( $this, 'Database::limitResult called with non-zero offset which is not supported yet' );
- } else {
- $sql = ereg_replace("^SELECT", "SELECT TOP $limit", $sql);
+ if ( sqlsrv_fetch( $res ) )
+ return true;
+ else
+ return false;
+ }
+
+ function fieldInfo( $table, $field ) {
+ $table = $this->tableName( $table );
+ $res = sqlsrv_query( $this->mConn, "SELECT * FROM INFORMATION_SCHEMA.Columns
+ WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" );
+ if ( $res === false ) {
+ print( "Error in fieldInfo query: " . $this->getErrors() );
+ return false;
}
- return $sql;
+ $meta = $this->fetchRow( $res );
+ if ( $meta ) {
+ return new MssqlField( $meta );
+ }
+ return false;
+ }
+
+ public function unixTimestamp( $field ) {
+ return "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),$field)";
}
/**
- * Should determine if the last failure was due to a deadlock
- * @return bool
+ * Begin a transaction, committing any previously open transaction
*/
- function wasDeadlock() {
- return $this->lastErrno() == 1205;
+ function begin( $fname = 'DatabaseMssql::begin' ) {
+ sqlsrv_begin_transaction( $this->mConn );
+ $this->mTrxLevel = 1;
}
/**
- * Return MW-style timestamp used for MySQL schema
+ * End a transaction
*/
- function timestamp( $ts=0 ) {
- return wfTimestamp(TS_MW,$ts);
+ function commit( $fname = 'DatabaseMssql::commit' ) {
+ sqlsrv_commit( $this->mConn );
+ $this->mTrxLevel = 0;
}
/**
- * Local database timestamp format or null
+ * Rollback a transaction.
+ * No-op on non-transactional databases.
*/
- function timestampOrNull( $ts = null ) {
- if( is_null( $ts ) ) {
- return null;
- } else {
- return $this->timestamp( $ts );
+ function rollback( $fname = 'DatabaseMssql::rollback' ) {
+ sqlsrv_rollback( $this->mConn );
+ $this->mTrxLevel = 0;
+ }
+
+ function setup_database() {
+ global $wgDBuser;
+
+ // Make sure that we can write to the correct schema
+ $ctest = "mediawiki_test_table";
+ if ( $this->tableExists( $ctest ) ) {
+ $this->doQuery( "DROP TABLE $ctest" );
+ }
+ $SQL = "CREATE TABLE $ctest (a int)";
+ $res = $this->doQuery( $SQL );
+ if ( !$res ) {
+ print "<b>FAILED</b>. Make sure that the user " . htmlspecialchars( $wgDBuser ) . " can write to the database</li>\n";
+ dieout( );
}
+ $this->doQuery( "DROP TABLE $ctest" );
+
+ $res = $this->sourceFile( "../maintenance/mssql/tables.sql" );
+ if ( $res !== true ) {
+ echo " <b>FAILED</b></li>";
+ dieout( htmlspecialchars( $res ) );
+ }
+
+ # Avoid the non-standard "REPLACE INTO" syntax
+ $f = fopen( "../maintenance/interwiki.sql", 'r' );
+ if ( $f == false ) {
+ dieout( "<li>Could not find the interwiki.sql file" );
+ }
+ # We simply assume it is already empty as we have just created it
+ $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
+ while ( ! feof( $f ) ) {
+ $line = fgets( $f, 1024 );
+ $matches = array();
+ if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) {
+ continue;
+ }
+ $this->query( "$SQL $matches[1],$matches[2])" );
+ }
+ print " (table interwiki successfully populated)...\n";
+
+ $this->commit();
}
/**
- * @return string wikitext of a link to the server software's web site
+ * Escapes a identifier for use inm SQL.
+ * Throws an exception if it is invalid.
+ * Reference: http://msdn.microsoft.com/en-us/library/aa224033%28v=SQL.80%29.aspx
*/
- function getSoftwareLink() {
- return "[http://www.microsoft.com/sql/default.mspx Microsoft SQL Server 2005 Home]";
+ private function escapeIdentifier( $identifier ) {
+ if ( strlen( $identifier ) == 0 ) {
+ throw new MWException( "An identifier must not be empty" );
+ }
+ if ( strlen( $identifier ) > 128 ) {
+ throw new MWException( "The identifier '$identifier' is too long (max. 128)" );
+ }
+ if ( ( strpos( $identifier, '[' ) !== false ) || ( strpos( $identifier, ']' ) !== false ) ) {
+ // It may be allowed if you quoted with double quotation marks, but that would break if QUOTED_IDENTIFIER is OFF
+ throw new MWException( "You can't use square brackers in the identifier '$identifier'" );
+ }
+ return "[$identifier]";
}
/**
- * @return string Version information from the database
+ * Initial setup.
+ * Precondition: This object is connected as the superuser.
+ * Creates the database, schema, user and login.
*/
- function getServerVersion() {
- $row = mssql_fetch_row(mssql_query('select @@VERSION'));
- return ereg("^(.+[0-9]+\\.[0-9]+\\.[0-9]+) ",$row[0],$m) ? $m[1] : $row[0];
+ function initial_setup( $dbName, $newUser, $loginPassword ) {
+ $dbName = $this->escapeIdentifier( $dbName );
+
+ // It is not clear what can be used as a login,
+ // From http://msdn.microsoft.com/en-us/library/ms173463.aspx
+ // a sysname may be the same as an identifier.
+ $newUser = $this->escapeIdentifier( $newUser );
+ $loginPassword = $this->addQuotes( $loginPassword );
+
+ $this->doQuery("CREATE DATABASE $dbName;");
+ $this->doQuery("USE $dbName;");
+ $this->doQuery("CREATE SCHEMA $dbName;");
+ $this->doQuery("
+ CREATE
+ LOGIN $newUser
+ WITH
+ PASSWORD=$loginPassword
+ ;
+ ");
+ $this->doQuery("
+ CREATE
+ USER $newUser
+ FOR
+ LOGIN $newUser
+ WITH
+ DEFAULT_SCHEMA=$dbName
+ ;
+ ");
+ $this->doQuery("
+ GRANT
+ BACKUP DATABASE,
+ BACKUP LOG,
+ CREATE DEFAULT,
+ CREATE FUNCTION,
+ CREATE PROCEDURE,
+ CREATE RULE,
+ CREATE TABLE,
+ CREATE VIEW,
+ CREATE FULLTEXT CATALOG
+ ON
+ DATABASE::$dbName
+ TO $newUser
+ ;
+ ");
+ $this->doQuery("
+ GRANT
+ CONTROL
+ ON
+ SCHEMA::$dbName
+ TO $newUser
+ ;
+ ");
+
+
}
- function limitResultForUpdate($sql, $num) {
- return $sql;
+ function encodeBlob( $b ) {
+ // we can't have zero's and such, this is a simple encoding to make sure we don't barf
+ return base64_encode( $b );
+ }
+
+ function decodeBlob( $b ) {
+ // we can't have zero's and such, this is a simple encoding to make sure we don't barf
+ return base64_decode( $b );
}
/**
- * How lagged is this slave?
+ * @private
*/
- public function getLag() {
- return 0;
+ function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
+ $ret = array();
+ $retJOIN = array();
+ $use_index_safe = is_array( $use_index ) ? $use_index : array();
+ $join_conds_safe = is_array( $join_conds ) ? $join_conds : array();
+ foreach ( $tables as $table ) {
+ // Is there a JOIN and INDEX clause for this table?
+ if ( isset( $join_conds_safe[$table] ) && isset( $use_index_safe[$table] ) ) {
+ $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+ $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
+ $retJOIN[] = $tableClause;
+ // Is there an INDEX clause?
+ } else if ( isset( $use_index_safe[$table] ) ) {
+ $tableClause = $this->tableName( $table );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+ $ret[] = $tableClause;
+ // Is there a JOIN clause?
+ } else if ( isset( $join_conds_safe[$table] ) ) {
+ $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
+ $tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
+ $retJOIN[] = $tableClause;
+ } else {
+ $tableClause = $this->tableName( $table );
+ $ret[] = $tableClause;
+ }
+ }
+ // We can't separate explicit JOIN clauses with ',', use ' ' for those
+ $straightJoins = !empty( $ret ) ? implode( ',', $ret ) : "";
+ $otherJoins = !empty( $retJOIN ) ? implode( ' ', $retJOIN ) : "";
+ // Compile our final table clause
+ return implode( ' ', array( $straightJoins, $otherJoins ) );
+ }
+
+ function strencode( $s ) { # Should not be called by us
+ return str_replace( "'", "''", $s );
+ }
+
+ function addQuotes( $s ) {
+ if ( $s instanceof Blob ) {
+ return "'" . $s->fetch( $s ) . "'";
+ } else {
+ return parent::addQuotes( $s );
+ }
+ }
+
+ function selectDB( $db ) {
+ return ( $this->query( "SET DATABASE $db" ) !== false );
}
/**
- * Called by the installer script
- * - this is the same way as DatabasePostgresql.php, MySQL reads in tables.sql and interwiki.sql using dbsource (which calls db->sourceFile)
+ * @private
+ *
+ * @param $options Array: an associative array of options to be turned into
+ * an SQL query, valid keys are listed in the function.
+ * @return Array
*/
- public function setup_database() {
- global $IP,$wgDBTableOptions;
- $wgDBTableOptions = '';
- $mysql_tmpl = "$IP/maintenance/tables.sql";
- $mysql_iw = "$IP/maintenance/interwiki.sql";
- $mssql_tmpl = "$IP/maintenance/mssql/tables.sql";
-
- # Make an MSSQL template file if it doesn't exist (based on the same one MySQL uses to create a new wiki db)
- if (!file_exists($mssql_tmpl)) { # todo: make this conditional again
- $sql = file_get_contents($mysql_tmpl);
- $sql = preg_replace('/^\s*--.*?$/m','',$sql); # strip comments
- $sql = preg_replace('/^\s*(UNIQUE )?(INDEX|KEY|FULLTEXT).+?$/m', '', $sql); # These indexes should be created with a CREATE INDEX query
- $sql = preg_replace('/(\sKEY) [^\(]+\(/is', '$1 (', $sql); # "KEY foo (foo)" should just be "KEY (foo)"
- $sql = preg_replace('/(varchar\([0-9]+\))\s+binary/i', '$1', $sql); # "varchar(n) binary" cannot be followed by "binary"
- $sql = preg_replace('/(var)?binary\(([0-9]+)\)/ie', '"varchar(".strlen(pow(2,$2)).")"', $sql); # use varchar(chars) not binary(bits)
- $sql = preg_replace('/ (var)?binary/i', ' varchar', $sql); # use varchar not binary
- $sql = preg_replace('/(varchar\([0-9]+\)(?! N))/', '$1 NULL', $sql); # MSSQL complains if NULL is put into a varchar
- #$sql = preg_replace('/ binary/i',' varchar',$sql); # MSSQL binary's can't be assigned with strings, so use varchar's instead
- #$sql = preg_replace('/(binary\([0-9]+\) (NOT NULL )?default) [\'"].*?[\'"]/i','$1 0',$sql); # binary default cannot be string
- $sql = preg_replace('/[a-z]*(blob|text)([ ,])/i', 'text$2', $sql); # no BLOB types in MSSQL
- $sql = preg_replace('/\).+?;/',');', $sql); # remove all table options
- $sql = preg_replace('/ (un)?signed/i', '', $sql);
- $sql = preg_replace('/ENUM\(.+?\)/','TEXT',$sql); # Make ENUM's into TEXT's
- $sql = str_replace(' bool ', ' bit ', $sql);
- $sql = str_replace('auto_increment', 'IDENTITY(1,1)', $sql);
- #$sql = preg_replace('/NOT NULL(?! IDENTITY)/', 'NULL', $sql); # Allow NULL's for non IDENTITY columns
-
- # Tidy up and write file
- $sql = preg_replace('/,\s*\)/s', "\n)", $sql); # Remove spurious commas left after INDEX removals
- $sql = preg_replace('/^\s*^/m', '', $sql); # Remove empty lines
- $sql = preg_replace('/;$/m', ";\n", $sql); # Separate each statement with an empty line
- file_put_contents($mssql_tmpl, $sql);
- }
-
- # Parse the MSSQL template replacing inline variables such as /*$wgDBprefix*/
- $err = $this->sourceFile($mssql_tmpl);
- if ($err !== true) $this->reportQueryError($err,0,$sql,__FUNCTION__);
-
- # Use DatabasePostgres's code to populate interwiki from MySQL template
- $f = fopen($mysql_iw,'r');
- if ($f == false) dieout("<li>Could not find the interwiki.sql file");
- $sql = "INSERT INTO {$this->mTablePrefix}interwiki(iw_prefix,iw_url,iw_local) VALUES ";
- while (!feof($f)) {
- $line = fgets($f,1024);
- $matches = array();
- if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) continue;
- $this->query("$sql $matches[1],$matches[2])");
+ function makeSelectOptions( $options ) {
+ $tailOpts = '';
+ $startOpts = '';
+
+ $noKeyOptions = array();
+ foreach ( $options as $key => $option ) {
+ if ( is_numeric( $key ) ) {
+ $noKeyOptions[$option] = true;
+ }
}
+
+ if ( isset( $options['GROUP BY'] ) ) $tailOpts .= " GROUP BY {$options['GROUP BY']}";
+ if ( isset( $options['HAVING'] ) ) $tailOpts .= " HAVING {$options['GROUP BY']}";
+ if ( isset( $options['ORDER BY'] ) ) $tailOpts .= " ORDER BY {$options['ORDER BY']}";
+
+ if ( isset( $noKeyOptions['DISTINCT'] ) && isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
+
+ // we want this to be compatible with the output of parent::makeSelectOptions()
+ return array( $startOpts, '' , $tailOpts, '' );
+ }
+
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ */
+ function getType(){
+ return 'mssql';
}
-
+
+ function buildConcat( $stringList ) {
+ return implode( ' + ', $stringList );
+ }
+
public function getSearchEngine() {
- return "SearchEngineDummy";
+ return "SearchMssql";
+ }
+
+} // end DatabaseMssql class
+
+/**
+ * Utility class.
+ *
+ * @ingroup Database
+ */
+class MssqlField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable, $type;
+ function __construct ( $info ) {
+ $this->name = $info['COLUMN_NAME'];
+ $this->tablename = $info['TABLE_NAME'];
+ $this->default = $info['COLUMN_DEFAULT'];
+ $this->max_length = $info['CHARACTER_MAXIMUM_LENGTH'];
+ $this->nullable = ( strtolower( $info['IS_NULLABLE'] ) == 'no' ) ? false:true;
+ $this->type = $info['DATA_TYPE'];
+ }
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function maxLength() {
+ return $this->max_length;
+ }
+
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function type() {
+ return $this->type;
}
}
/**
+ * The MSSQL PHP driver doesn't support sqlsrv_num_rows, so we recall all rows into an array and maintain our
+ * own cursor index into that array...This is similar to the way the Oracle driver handles this same issue
+ *
* @ingroup Database
*/
-class MSSQLField extends MySQLField {
+class MssqlResult {
+
+ public function __construct( $queryresult = false ) {
+ $this->mCursor = 0;
+ $this->mRows = array();
+ $this->mNumFields = sqlsrv_num_fields( $queryresult );
+ $this->mFieldMeta = sqlsrv_field_metadata( $queryresult );
+ while ( $row = sqlsrv_fetch_array( $queryresult, SQLSRV_FETCH_ASSOC ) ) {
+ if ( $row !== null ) {
+ foreach ( $row as $k => $v ) {
+ if ( is_object( $v ) && method_exists( $v, 'format' ) ) {// DateTime Object
+ $row[$k] = $v->format( "Y-m-d\TH:i:s\Z" );
+ }
+ }
+ $this->mRows[] = $row;// read results into memory, cursors are not supported
+ }
+ }
+ $this->mRowCount = count( $this->mRows );
+ sqlsrv_free_stmt( $queryresult );
+ }
+
+ private function array_to_obj( $array, &$obj ) {
+ foreach ( $array as $key => $value ) {
+ if ( is_array( $value ) ) {
+ $obj->$key = new stdClass();
+ $this->array_to_obj( $value, $obj->$key );
+ } else {
+ if ( !empty( $key ) ) {
+ $obj->$key = $value;
+ }
+ }
+ }
+ return $obj;
+ }
- function __construct() {
+ public function fetch( $mode = SQLSRV_FETCH_BOTH, $object_class = 'stdClass' ) {
+ if ( $this->mCursor >= $this->mRowCount || $this->mRowCount == 0 ) {
+ return false;
+ }
+ $arrNum = array();
+ if ( $mode == SQLSRV_FETCH_NUMERIC || $mode == SQLSRV_FETCH_BOTH ) {
+ foreach ( $this->mRows[$this->mCursor] as $value ) {
+ $arrNum[] = $value;
+ }
+ }
+ switch( $mode ) {
+ case SQLSRV_FETCH_ASSOC:
+ $ret = $this->mRows[$this->mCursor];
+ break;
+ case SQLSRV_FETCH_NUMERIC:
+ $ret = $arrNum;
+ break;
+ case 'OBJECT':
+ $o = new $object_class;
+ $ret = $this->array_to_obj( $this->mRows[$this->mCursor], $o );
+ break;
+ case SQLSRV_FETCH_BOTH:
+ default:
+ $ret = $this->mRows[$this->mCursor] + $arrNum;
+ break;
}
- static function fromText($db, $table, $field) {
- $n = new MSSQLField;
- $n->name = $field;
- $n->tablename = $table;
- return $n;
+ $this->mCursor++;
+ return $ret;
+ }
+
+ public function get( $pos, $fld ) {
+ return $this->mRows[$pos][$fld];
+ }
+
+ public function numrows() {
+ return $this->mRowCount;
+ }
+
+ public function seek( $iRow ) {
+ $this->mCursor = min( $iRow, $this->mRowCount );
+ }
+
+ public function numfields() {
+ return $this->mNumFields;
+ }
+
+ public function fieldname( $nr ) {
+ $arrKeys = array_keys( $this->mRows[0] );
+ return $arrKeys[$nr];
+ }
+
+ public function fieldtype( $nr ) {
+ $i = 0;
+ $intType = -1;
+ foreach ( $this->mFieldMeta as $meta ) {
+ if ( $nr == $i ) {
+ $intType = $meta['Type'];
+ break;
+ }
+ $i++;
}
+ // http://msdn.microsoft.com/en-us/library/cc296183.aspx contains type table
+ switch( $intType ) {
+ case SQLSRV_SQLTYPE_BIGINT: $strType = 'bigint'; break;
+ case SQLSRV_SQLTYPE_BINARY: $strType = 'binary'; break;
+ case SQLSRV_SQLTYPE_BIT: $strType = 'bit'; break;
+ case SQLSRV_SQLTYPE_CHAR: $strType = 'char'; break;
+ case SQLSRV_SQLTYPE_DATETIME: $strType = 'datetime'; break;
+ case SQLSRV_SQLTYPE_DECIMAL/*($precision, $scale)*/: $strType = 'decimal'; break;
+ case SQLSRV_SQLTYPE_FLOAT: $strType = 'float'; break;
+ case SQLSRV_SQLTYPE_IMAGE: $strType = 'image'; break;
+ case SQLSRV_SQLTYPE_INT: $strType = 'int'; break;
+ case SQLSRV_SQLTYPE_MONEY: $strType = 'money'; break;
+ case SQLSRV_SQLTYPE_NCHAR/*($charCount)*/: $strType = 'nchar'; break;
+ case SQLSRV_SQLTYPE_NUMERIC/*($precision, $scale)*/: $strType = 'numeric'; break;
+ case SQLSRV_SQLTYPE_NVARCHAR/*($charCount)*/: $strType = 'nvarchar'; break;
+ // case SQLSRV_SQLTYPE_NVARCHAR('max'): $strType = 'nvarchar(MAX)'; break;
+ case SQLSRV_SQLTYPE_NTEXT: $strType = 'ntext'; break;
+ case SQLSRV_SQLTYPE_REAL: $strType = 'real'; break;
+ case SQLSRV_SQLTYPE_SMALLDATETIME: $strType = 'smalldatetime'; break;
+ case SQLSRV_SQLTYPE_SMALLINT: $strType = 'smallint'; break;
+ case SQLSRV_SQLTYPE_SMALLMONEY: $strType = 'smallmoney'; break;
+ case SQLSRV_SQLTYPE_TEXT: $strType = 'text'; break;
+ case SQLSRV_SQLTYPE_TIMESTAMP: $strType = 'timestamp'; break;
+ case SQLSRV_SQLTYPE_TINYINT: $strType = 'tinyint'; break;
+ case SQLSRV_SQLTYPE_UNIQUEIDENTIFIER: $strType = 'uniqueidentifier'; break;
+ case SQLSRV_SQLTYPE_UDT: $strType = 'UDT'; break;
+ case SQLSRV_SQLTYPE_VARBINARY/*($byteCount)*/: $strType = 'varbinary'; break;
+ // case SQLSRV_SQLTYPE_VARBINARY('max'): $strType = 'varbinary(MAX)'; break;
+ case SQLSRV_SQLTYPE_VARCHAR/*($charCount)*/: $strType = 'varchar'; break;
+ // case SQLSRV_SQLTYPE_VARCHAR('max'): $strType = 'varchar(MAX)'; break;
+ case SQLSRV_SQLTYPE_XML: $strType = 'xml'; break;
+ default: $strType = $intType;
+ }
+ return $strType;
+ }
-} // end DatabaseMssql class
+ public function free() {
+ unset( $this->mRows );
+ return;
+ }
+}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index ea7ef5b9..ed276ec5 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -1,5 +1,12 @@
<?php
/**
+ * This is the MySQL database abstraction layer.
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
* Database abstraction object for mySQL
* Inherit all methods and properties of Database::Database()
*
@@ -24,11 +31,8 @@ class DatabaseMysql extends DatabaseBase {
global $wgAllDBsAreLocalhost;
wfProfileIn( __METHOD__ );
- # Test for missing mysql.so
- # First try to load it
- if (!@extension_loaded('mysql')) {
- @dl('mysql.so');
- }
+ # Load mysql.so if we don't have it
+ wfDl( 'mysql' );
# Fail now
# Otherwise we get a suppressed fatal error, which is very hard to track down
@@ -48,8 +52,6 @@ class DatabaseMysql extends DatabaseBase {
$this->mPassword = $password;
$this->mDBname = $dbName;
- $success = false;
-
wfProfileIn("dbconnect-$server");
# The kernel's default SYN retransmission period is far too slow for us,
@@ -72,10 +74,10 @@ class DatabaseMysql extends DatabaseBase {
# Create a new connection...
$this->mConn = mysql_connect( $realServer, $user, $password, true );
}
- if ($this->mConn === false) {
+ #if ( $this->mConn === false ) {
#$iplus = $i + 1;
- #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
- }
+ #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
+ #}
}
$phpError = $this->restoreErrorHandler();
# Always log connection errors
@@ -88,9 +90,8 @@ class DatabaseMysql extends DatabaseBase {
wfDebug( "DB connection error\n" );
wfDebug( "Server: $server, User: $user, Password: " .
substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" );
- $success = false;
}
-
+
wfProfileOut("dbconnect-$server");
if ( $dbName != '' && $this->mConn !== false ) {
@@ -114,9 +115,15 @@ class DatabaseMysql extends DatabaseBase {
global $wgDBmysql5;
if( $wgDBmysql5 ) {
$this->query( 'SET NAMES utf8', __METHOD__ );
+ } else {
+ $this->query( 'SET NAMES binary', __METHOD__ );
+ }
+ // Set SQL mode, default is turning them all off, can be overridden or skipped with null
+ global $wgSQLMode;
+ if ( is_string( $wgSQLMode ) ) {
+ $mode = $this->addQuotes( $wgSQLMode );
+ $this->query( "SET sql_mode = $mode", __METHOD__ );
}
- // Turn off strict mode
- $this->query( "SET sql_mode = ''", __METHOD__ );
}
// Turn off strict mode if it is on
@@ -233,34 +240,35 @@ class DatabaseMysql extends DatabaseBase {
}
function affectedRows() { return mysql_affected_rows( $this->mConn ); }
-
+
/**
* Estimate rows in dataset
* Returns estimated count, based on EXPLAIN output
* Takes same arguments as Database::select()
*/
- public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
+ public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
- if ( $res === false )
+ if ( $res === false ) {
return false;
+ }
if ( !$this->numRows( $res ) ) {
- $this->freeResult($res);
return 0;
}
$rows = 1;
- while( $plan = $this->fetchObject( $res ) ) {
+ foreach ( $res as $plan ) {
$rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
}
-
- $this->freeResult($res);
- return $rows;
+ return $rows;
}
function fieldInfo( $table, $field ) {
$table = $this->tableName( $table );
- $res = $this->query( "SELECT * FROM $table LIMIT 1" );
+ $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
+ if ( !$res ) {
+ return false;
+ }
$n = mysql_num_fields( $res->result );
for( $i = 0; $i < $n; $i++ ) {
$meta = mysql_fetch_field( $res->result, $i );
@@ -271,32 +279,108 @@ class DatabaseMysql extends DatabaseBase {
return false;
}
+ /**
+ * Get information about an index into an object
+ * Returns false if the index does not exist
+ */
+ function indexInfo( $table, $index, $fname = 'DatabaseMysql::indexInfo' ) {
+ # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
+ # SHOW INDEX should work for 3.x and up:
+ # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
+ $table = $this->tableName( $table );
+ $index = $this->indexName( $index );
+ $sql = 'SHOW INDEX FROM ' . $table;
+ $res = $this->query( $sql, $fname );
+
+ if ( !$res ) {
+ return null;
+ }
+
+ $result = array();
+
+ foreach ( $res as $row ) {
+ if ( $row->Key_name == $index ) {
+ $result[] = $row;
+ }
+ }
+
+ return empty( $result ) ? false : $result;
+ }
+
function selectDB( $db ) {
$this->mDBname = $db;
return mysql_select_db( $db, $this->mConn );
}
function strencode( $s ) {
- return mysql_real_escape_string( $s, $this->mConn );
+ $sQuoted = mysql_real_escape_string( $s, $this->mConn );
+
+ if($sQuoted === false) {
+ $this->ping();
+ $sQuoted = mysql_real_escape_string( $s, $this->mConn );
+ }
+ return $sQuoted;
+ }
+
+ /**
+ * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
+ */
+ public function addIdentifierQuotes( $s ) {
+ return "`" . $this->strencode( $s ) . "`";
}
function ping() {
- if( !function_exists( 'mysql_ping' ) ) {
- wfDebug( "Tried to call mysql_ping but this is ancient PHP version. Faking it!\n" );
- return true;
- }
$ping = mysql_ping( $this->mConn );
if ( $ping ) {
return true;
}
- // Need to reconnect manually in MySQL client 5.0.13+
- if ( version_compare( mysql_get_client_info(), '5.0.13', '>=' ) ) {
- mysql_close( $this->mConn );
- $this->mOpened = false;
- $this->mConn = false;
- $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
- return true;
+ mysql_close( $this->mConn );
+ $this->mOpened = false;
+ $this->mConn = false;
+ $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
+ return true;
+ }
+
+ /**
+ * Returns slave lag.
+ * At the moment, this will only work if the DB user has the PROCESS privilege
+ * @result int
+ */
+ function getLag() {
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
+ return $this->mFakeSlaveLag;
+ }
+ $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
+ if( !$res ) {
+ return false;
+ }
+ # Find slave SQL thread
+ foreach( $res as $row ) {
+ /* This should work for most situations - when default db
+ * for thread is not specified, it had no events executed,
+ * and therefore it doesn't know yet how lagged it is.
+ *
+ * Relay log I/O thread does not select databases.
+ */
+ if ( $row->User == 'system user' &&
+ $row->State != 'Waiting for master to send event' &&
+ $row->State != 'Connecting to master' &&
+ $row->State != 'Queueing master event to the relay log' &&
+ $row->State != 'Waiting for master update' &&
+ $row->State != 'Requesting binlog dump' &&
+ $row->State != 'Waiting to reconnect after a failed master event read' &&
+ $row->State != 'Reconnecting after a failed master event read' &&
+ $row->State != 'Registering slave on master'
+ ) {
+ # This is it, return the time (except -ve)
+ if ( $row->Time > 0x7fffffff ) {
+ return false;
+ } else {
+ return $row->Time;
+ }
+ }
}
return false;
}
@@ -313,7 +397,7 @@ class DatabaseMysql extends DatabaseBase {
return 'LOW_PRIORITY';
}
- function getSoftwareLink() {
+ public static function getSoftwareLink() {
return '[http://www.mysql.com/ MySQL]';
}
@@ -330,7 +414,6 @@ class DatabaseMysql extends DatabaseBase {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
$row = $this->fetchObject( $result );
- $this->freeResult( $result );
if( $row->lockstatus == 1 ) {
return true;
@@ -340,6 +423,9 @@ class DatabaseMysql extends DatabaseBase {
}
}
+ /**
+ * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
+ */
public function unlock( $lockName, $method ) {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
@@ -351,8 +437,8 @@ class DatabaseMysql extends DatabaseBase {
$items = array();
foreach( $write as $table ) {
- $tbl = $this->tableName( $table ) .
- ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
+ $tbl = $this->tableName( $table ) .
+ ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
' WRITE';
$items[] = $tbl;
}
@@ -367,6 +453,16 @@ class DatabaseMysql extends DatabaseBase {
$this->query( "UNLOCK TABLES", $method );
}
+ /**
+ * Get search engine class. All subclasses of this
+ * need to implement this if they wish to use searching.
+ *
+ * @return String
+ */
+ public function getSearchEngine() {
+ return 'SearchMySQL';
+ }
+
public function setBigSelects( $value = true ) {
if ( $value === 'default' ) {
if ( $this->mDefaultBigSelects === null ) {
@@ -382,7 +478,10 @@ class DatabaseMysql extends DatabaseBase {
$this->query( "SET sql_big_selects=$encValue", __METHOD__ );
}
-
+ public function unixTimestamp( $field ) {
+ return "UNIX_TIMESTAMP($field)";
+ }
+
/**
* Determines if the last failure was due to a deadlock
*/
@@ -391,7 +490,7 @@ class DatabaseMysql extends DatabaseBase {
}
/**
- * Determines if the last query error was something that should be dealt
+ * Determines if the last query error was something that should be dealt
* with by pinging the connection and reissuing the query
*/
function wasErrorReissuable() {
@@ -402,7 +501,7 @@ class DatabaseMysql extends DatabaseBase {
* Determines if the last failure was due to the database being read-only.
*/
function wasReadOnlyError() {
- return $this->lastErrno() == 1223 ||
+ return $this->lastErrno() == 1223 ||
( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
}
@@ -420,7 +519,7 @@ class DatabaseMysql extends DatabaseBase {
$res = $this->query( "SHOW CREATE TABLE $oldName" );
$row = $this->fetchRow( $res );
$oldQuery = $row[1];
- $query = preg_replace( '/CREATE TABLE `(.*?)`/',
+ $query = preg_replace( '/CREATE TABLE `(.*?)`/',
"CREATE $tmp TABLE `$newName`", $oldQuery );
if ($oldQuery === $query) {
# Couldn't do replacement
@@ -432,6 +531,11 @@ class DatabaseMysql extends DatabaseBase {
$this->query( $query, $fname );
}
+ protected function getDefaultSchemaVars() {
+ $vars = parent::getDefaultSchemaVars();
+ $vars['wgDBTableOptions'] = $GLOBALS['wgDBTableOptions'];
+ return $vars;
+ }
}
/**
@@ -439,6 +543,56 @@ class DatabaseMysql extends DatabaseBase {
*/
class Database extends DatabaseMysql {}
+/**
+ * Utility class.
+ * @ingroup Database
+ */
+class MySQLField implements Field {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_multiple, $is_key, $type;
+
+ function __construct ( $info ) {
+ $this->name = $info->name;
+ $this->tablename = $info->table;
+ $this->default = $info->def;
+ $this->max_length = $info->max_length;
+ $this->nullable = !$info->not_null;
+ $this->is_pk = $info->primary_key;
+ $this->is_unique = $info->unique_key;
+ $this->is_multiple = $info->multiple_key;
+ $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+ $this->type = $info->type;
+ }
+
+ function name() {
+ return $this->name;
+ }
+
+ function tableName() {
+ return $this->tableName;
+ }
+
+ function type() {
+ return $this->type;
+ }
+
+ function isNullable() {
+ return $this->nullable;
+ }
+
+ function defaultValue() {
+ return $this->default;
+ }
+
+ function isKey() {
+ return $this->is_key;
+ }
+
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+}
+
class MySQLMasterPos {
var $file, $pos;
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index bd60bbf8..4fe3e980 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -1,24 +1,10 @@
<?php
/**
- * @ingroup Database
- * @file
- */
-
-/**
* This is the Oracle database abstraction layer.
+ *
+ * @file
* @ingroup Database
*/
-class ORABlob {
- var $mData;
-
- function __construct( $data ) {
- $this->mData = $data;
- }
-
- function getData() {
- return $this->mData;
- }
-}
/**
* The oci8 extension is fairly weak and doesn't support oci_num_rows, among
@@ -29,15 +15,15 @@ class ORABlob {
class ORAResult {
private $rows;
private $cursor;
- private $stmt;
private $nrows;
+
+ private $columns = array();
- private $unique;
private function array_unique_md( $array_in ) {
$array_out = array();
$array_hashes = array();
- foreach ( $array_in as $key => $item ) {
+ foreach ( $array_in as $item ) {
$hash = md5( serialize( $item ) );
if ( !isset( $array_hashes[$hash] ) ) {
$array_hashes[$hash] = $hash;
@@ -53,7 +39,8 @@ class ORAResult {
if ( ( $this->nrows = oci_fetch_all( $stmt, $this->rows, 0, - 1, OCI_FETCHSTATEMENT_BY_ROW | OCI_NUM ) ) === false ) {
$e = oci_error( $stmt );
- $db->reportQueryError( $e['message'], $e['code'], '', __FUNCTION__ );
+ $db->reportQueryError( $e['message'], $e['code'], '', __METHOD__ );
+ $this->free();
return;
}
@@ -62,12 +49,18 @@ class ORAResult {
$this->nrows = count( $this->rows );
}
+ if ($this->nrows > 0) {
+ foreach ( $this->rows[0] as $k => $v ) {
+ $this->columns[$k] = strtolower( oci_field_name( $stmt, $k + 1 ) );
+ }
+ }
+
$this->cursor = 0;
- $this->stmt = $stmt;
+ oci_free_statement( $stmt );
}
public function free() {
- oci_free_statement( $this->stmt );
+ unset($this->db);
}
public function seek( $row ) {
@@ -79,7 +72,7 @@ class ORAResult {
}
public function numFields() {
- return oci_num_fields( $this->stmt );
+ return count($this->columns);
}
public function fetchObject() {
@@ -89,7 +82,7 @@ class ORAResult {
$row = $this->rows[$this->cursor++];
$ret = new stdClass();
foreach ( $row as $k => $v ) {
- $lc = strtolower( oci_field_name( $this->stmt, $k + 1 ) );
+ $lc = $this->columns[$k];
$ret->$lc = $v;
}
@@ -104,7 +97,7 @@ class ORAResult {
$row = $this->rows[$this->cursor++];
$ret = array();
foreach ( $row as $k => $v ) {
- $lc = strtolower( oci_field_name( $this->stmt, $k + 1 ) );
+ $lc = $this->columns[$k];
$ret[$lc] = $v;
$ret[$k] = $v;
}
@@ -116,7 +109,7 @@ class ORAResult {
* Utility class.
* @ingroup Database
*/
-class ORAField {
+class ORAField implements Field {
private $name, $tablename, $default, $max_length, $nullable,
$is_pk, $is_unique, $is_multiple, $is_key, $type;
@@ -149,7 +142,7 @@ class ORAField {
return $this->max_length;
}
- function nullable() {
+ function isNullable() {
return $this->nullable;
}
@@ -185,11 +178,19 @@ class DatabaseOracle extends DatabaseBase {
var $mFieldInfoCache = array();
function __construct( $server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0, $tablePrefix = 'get from global' )
+ $flags = 0, $tablePrefix = 'get from global' )
{
$tablePrefix = $tablePrefix == 'get from global' ? $tablePrefix : strtoupper( $tablePrefix );
- parent::__construct( $server, $user, $password, $dbName, $failFunction, $flags, $tablePrefix );
- wfRunHooks( 'DatabaseOraclePostInit', array( &$this ) );
+ parent::__construct( $server, $user, $password, $dbName, $flags, $tablePrefix );
+ wfRunHooks( 'DatabaseOraclePostInit', array( $this ) );
+ }
+
+ function __destruct() {
+ if ($this->mOpened) {
+ wfSuppressWarnings();
+ $this->close();
+ wfRestoreWarnings();
+ }
}
function getType() {
@@ -218,25 +219,36 @@ class DatabaseOracle extends DatabaseBase {
return true;
}
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 )
{
- return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags );
+ return new DatabaseOracle( $server, $user, $password, $dbName, $flags );
}
/**
* Usually aborts on failure
- * If the failFunction is set to a non-zero integer, returns success
*/
function open( $server, $user, $password, $dbName ) {
if ( !function_exists( 'oci_connect' ) ) {
throw new DBConnectionError( $this, "Oracle functions missing, have you compiled PHP with the --with-oci8 option?\n (Note: if you recently installed PHP, you may need to restart your webserver and database)\n" );
}
- $this->close();
- $this->mServer = $server;
$this->mUser = $user;
$this->mPassword = $password;
- $this->mDBname = $dbName;
+ // changed internal variables functions
+ // mServer now holds the TNS endpoint
+ // mDBname is schema name if different from username
+ if ( !$server ) {
+ // backward compatibillity (server used to be null and TNS was supplied in dbname)
+ $this->mServer = $dbName;
+ $this->mDBname = $user;
+ } else {
+ $this->mServer = $server;
+ if ( !$dbName ) {
+ $this->mDBname = $user;
+ } else {
+ $this->mDBname = $dbName;
+ }
+ }
if ( !strlen( $user ) ) { # e.g. the class is being loaded
return;
@@ -244,16 +256,18 @@ class DatabaseOracle extends DatabaseBase {
$session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
if ( $this->mFlags & DBO_DEFAULT ) {
- $this->mConn = oci_new_connect( $user, $password, $dbName, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_new_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
} else {
- $this->mConn = oci_connect( $user, $password, $dbName, $this->defaultCharset, $session_mode );
+ $this->mConn = oci_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
}
- if ( $this->mConn == false ) {
- wfDebug( "DB connection error\n" );
- wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError() . "\n" );
- return false;
+ if ( $this->mUser != $this->mDBname ) {
+ //change current schema in session
+ $this->selectDB( $this->mDBname );
+ }
+
+ if ( !$this->mConn ) {
+ throw new DBConnectionError( $this, $this->lastError() );
}
$this->mOpened = true;
@@ -271,6 +285,9 @@ class DatabaseOracle extends DatabaseBase {
function close() {
$this->mOpened = false;
if ( $this->mConn ) {
+ if ( $this->mTrxLevel ) {
+ $this->commit();
+ }
return oci_close( $this->mConn );
} else {
return true;
@@ -289,9 +306,10 @@ class DatabaseOracle extends DatabaseBase {
// handle some oracle specifics
// remove AS column/table/subquery namings
- if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
+ if( !$this->getFlag( DBO_DDLMODE ) ) {
$sql = preg_replace( '/ as /i', ' ', $sql );
}
+
// Oracle has issues with UNION clause if the statement includes LOB fields
// So we do a UNION ALL and then filter the results array with array_unique
$union_unique = ( preg_match( '/\/\* UNION_UNIQUE \*\/ /', $sql ) != 0 );
@@ -301,23 +319,22 @@ class DatabaseOracle extends DatabaseBase {
$sql = preg_replace( '/^EXPLAIN /', 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR', $sql, 1, $explain_count );
-
wfSuppressWarnings();
if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
$e = oci_error( $this->mConn );
- $this->reportQueryError( $e['message'], $e['code'], $sql, __FUNCTION__ );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
- if ( oci_execute( $stmt, $this->execFlags() ) == false ) {
+ if ( !oci_execute( $stmt, $this->execFlags() ) ) {
$e = oci_error( $stmt );
if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
- $this->reportQueryError( $e['message'], $e['code'], $sql, __FUNCTION__ );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
}
-
+
wfRestoreWarnings();
if ( $explain_count > 0 ) {
@@ -335,43 +352,43 @@ class DatabaseOracle extends DatabaseBase {
}
function freeResult( $res ) {
- if ( $res instanceof ORAResult ) {
- $res->free();
- } else {
- $res->result->free();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ $res->free();
}
function fetchObject( $res ) {
- if ( $res instanceof ORAResult ) {
- return $res->numRows();
- } else {
- return $res->result->fetchObject();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ return $res->fetchObject();
}
function fetchRow( $res ) {
- if ( $res instanceof ORAResult ) {
- return $res->fetchRow();
- } else {
- return $res->result->fetchRow();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ return $res->fetchRow();
}
function numRows( $res ) {
- if ( $res instanceof ORAResult ) {
- return $res->numRows();
- } else {
- return $res->result->numRows();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ return $res->numRows();
}
function numFields( $res ) {
- if ( $res instanceof ORAResult ) {
- return $res->numFields();
- } else {
- return $res->result->numFields();
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
}
+
+ return $res->numFields();
}
function fieldName( $stmt, $n ) {
@@ -456,8 +473,38 @@ class DatabaseOracle extends DatabaseBase {
return $retVal;
}
+ private function fieldBindStatement ( $table, $col, &$val, $includeCol = false ) {
+ $col_info = $this->fieldInfoMulti( $table, $col );
+ $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
+
+ $bind = '';
+ if ( is_numeric( $col ) ) {
+ $bind = $val;
+ $val = null;
+ return $bind;
+ } else if ( $includeCol ) {
+ $bind = "$col = ";
+ }
+
+ if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
+ $val = null;
+ }
+
+ if ( $val === null ) {
+ if ( $col_info != false && $col_info->isNullable() == 0 && $col_info->defaultValue() != null ) {
+ $bind .= 'DEFAULT';
+ } else {
+ $bind .= 'NULL';
+ }
+ } else {
+ $bind .= ':' . $col;
+ }
+
+ return $bind;
+ }
+
private function insertOneRow( $table, $row, $fname ) {
- global $wgLang;
+ global $wgContLang;
$table = $this->tableName( $table );
// "INSERT INTO tables (a, b, c)"
@@ -466,18 +513,22 @@ class DatabaseOracle extends DatabaseBase {
// for each value, append ":key"
$first = true;
- foreach ( $row as $col => $val ) {
- if ( $first ) {
- $sql .= $val !== null ? ':' . $col : 'NULL';
+ foreach ( $row as $col => &$val ) {
+ if ( !$first ) {
+ $sql .= ', ';
} else {
- $sql .= $val !== null ? ', :' . $col : ', NULL';
+ $first = false;
}
-
- $first = false;
+
+ $sql .= $this->fieldBindStatement( $table, $col, $val );
}
$sql .= ')';
- $stmt = oci_parse( $this->mConn, $sql );
+ if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
+ $e = oci_error( $this->mConn );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ return false;
+ }
foreach ( $row as $col => &$val ) {
$col_info = $this->fieldInfoMulti( $table, $col );
$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
@@ -486,16 +537,17 @@ class DatabaseOracle extends DatabaseBase {
// do nothing ... null was inserted in statement creation
} elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
if ( is_object( $val ) ) {
- $val = $val->getData();
+ $val = $val->fetch();
}
if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
$val = '31-12-2030 12:00:00.000000';
}
- $val = ( $wgLang != null ) ? $wgLang->checkTitleEncoding( $val ) : $val;
+ $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
- $this->reportQueryError( $this->lastErrno(), $this->lastError(), $sql, __METHOD__ );
+ $e = oci_error( $stmt );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
}
} else {
@@ -504,11 +556,15 @@ class DatabaseOracle extends DatabaseBase {
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
}
- if ( $col_type == 'BLOB' ) { // is_object($val)) {
- $lob[$col]->writeTemporary( $val ); // ->getData());
- oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB );
+ if ( is_object( $val ) ) {
+ $val = $val->fetch();
+ }
+
+ if ( $col_type == 'BLOB' ) {
+ $lob[$col]->writeTemporary( $val, OCI_TEMP_BLOB );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_BLOB );
} else {
- $lob[$col]->writeTemporary( $val );
+ $lob[$col]->writeTemporary( $val, OCI_TEMP_CLOB );
oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
}
}
@@ -516,9 +572,8 @@ class DatabaseOracle extends DatabaseBase {
wfSuppressWarnings();
- if ( oci_execute( $stmt, OCI_DEFAULT ) === false ) {
+ if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
$e = oci_error( $stmt );
-
if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
@@ -532,7 +587,7 @@ class DatabaseOracle extends DatabaseBase {
wfRestoreWarnings();
if ( isset( $lob ) ) {
- foreach ( $lob as $lob_i => $lob_v ) {
+ foreach ( $lob as $lob_v ) {
$lob_v->free();
}
}
@@ -560,11 +615,13 @@ class DatabaseOracle extends DatabaseBase {
if ( ( $sequenceData = $this->getSequenceData( $destTable ) ) !== false &&
!isset( $varMap[$sequenceData['column']] ) )
+ {
$varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
+ }
// count-alias subselect fields to avoid abigious definition errors
$i = 0;
- foreach ( $varMap as $key => &$val ) {
+ foreach ( $varMap as &$val ) {
$val = $val . ' field' . ( $i++ );
}
@@ -640,13 +697,18 @@ class DatabaseOracle extends DatabaseBase {
if ( isset( $database ) ) {
$database = ( $database[0] == '"' ? $database : "\"{$database}\"" );
}
- $table = ( $table[0] == '"' ? $table : "\"{$prefix}{$table}\"" );
+ $table = ( $table[0] == '"') ? $table : "\"{$prefix}{$table}\"" ;
$tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
return strtoupper( $tableName );
}
+ function tableNameInternal( $name ) {
+ $name = $this->tableName( $name );
+ return preg_replace( '/.*\."(.*)"/', '$1', $name);
+ }
+
/**
* Return the next in a sequence, save the value for retrieval via insertId()
*/
@@ -654,7 +716,6 @@ class DatabaseOracle extends DatabaseBase {
$res = $this->query( "SELECT $seqName.nextval FROM dual" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
- $this->freeResult( $res );
return $this->mInsertId;
}
@@ -663,7 +724,7 @@ class DatabaseOracle extends DatabaseBase {
*/
private function getSequenceData( $table ) {
if ( $this->sequenceData == null ) {
- $result = $this->query( "SELECT lower(us.sequence_name), lower(utc.table_name), lower(utc.column_name) from user_sequences us, user_tab_columns utc where us.sequence_name = utc.table_name||'_'||utc.column_name||'_SEQ'" );
+ $result = $this->doQuery( 'SELECT lower(us.sequence_name), lower(utc.table_name), lower(utc.column_name) from user_sequences us, user_tab_columns utc where us.sequence_name = utc.table_name||\'_\'||utc.column_name||\'_SEQ\'' );
while ( ( $row = $result->fetchRow() ) !== false ) {
$this->sequenceData[$this->tableName( $row[1] )] = array(
@@ -676,15 +737,23 @@ class DatabaseOracle extends DatabaseBase {
return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
}
- # REPLACE query wrapper
- # Oracle simulates this with a DELETE followed by INSERT
- # $row is the row to insert, an associative array
- # $uniqueIndexes is an array of indexes. Each element may be either a
- # field name or an array of field names
- #
- # It may be more efficient to leave off unique indexes which are unlikely to collide.
- # However if you do this, you run the risk of encountering errors which wouldn't have
- # occurred in MySQL
+ /**
+ * REPLACE query wrapper
+ * Oracle simulates this with a DELETE followed by INSERT
+ * $row is the row to insert, an associative array
+ * $uniqueIndexes is an array of indexes. Each element may be either a
+ * field name or an array of field names
+ *
+ * It may be more efficient to leave off unique indexes which are unlikely to collide.
+ * However if you do this, you run the risk of encountering errors which wouldn't have
+ * occurred in MySQL.
+ *
+ * @param $table String: table name
+ * @param $uniqueIndexes Array: array of indexes. Each element may be
+ * either a field name or an array of field names
+ * @param $rows Array: rows to insert to $table
+ * @param $fname String: function name, you can use __METHOD__ here
+ */
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseOracle::replace' ) {
$table = $this->tableName( $table );
@@ -703,9 +772,10 @@ class DatabaseOracle extends DatabaseBase {
# Delete rows which collide
if ( $uniqueIndexes ) {
$condsDelete = array();
- foreach ( $uniqueIndexes as $index )
+ foreach ( $uniqueIndexes as $index ) {
$condsDelete[$index] = $row[$index];
- if (count($condsDelete) > 0) {
+ }
+ if ( count( $condsDelete ) > 0 ) {
$this->delete( $table, $condsDelete, $fname );
}
}
@@ -720,9 +790,9 @@ class DatabaseOracle extends DatabaseBase {
}
# DELETE where the condition is a join
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseOracle::deleteJoin" ) {
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseOracle::deleteJoin' ) {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseOracle::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabaseOracle::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
@@ -738,13 +808,8 @@ class DatabaseOracle extends DatabaseBase {
# Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
- $fieldInfoData = $this->fieldInfo( $table, $field);
- if ( $fieldInfoData->type == "varchar" ) {
- $size = $row->size - 4;
- } else {
- $size = $row->size;
- }
- return $size;
+ $fieldInfoData = $this->fieldInfo( $table, $field );
+ return $fieldInfoData->maxLength();
}
function limitResult( $sql, $limit, $offset = false ) {
@@ -754,40 +819,73 @@ class DatabaseOracle extends DatabaseBase {
return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
}
+ function encodeBlob( $b ) {
+ return new Blob( $b );
+ }
+
+ function decodeBlob( $b ) {
+ if ( $b instanceof Blob ) {
+ $b = $b->fetch();
+ }
+ return $b;
+ }
function unionQueries( $sqls, $all ) {
$glue = ' UNION ALL ';
return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')' ;
}
+ public function unixTimestamp( $field ) {
+ return "((trunc($field) - to_date('19700101','YYYYMMDD')) * 86400)";
+ }
+
function wasDeadlock() {
return $this->lastErrno() == 'OCI-00060';
}
-
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseOracle::duplicateTableStructure' ) {
- $temporary = $temporary ? 'TRUE' : 'FALSE';
- $oldName = trim(strtoupper($oldName), '"');
- $oldParts = explode('_', $oldName);
+ global $wgDBprefix;
- $newName = trim(strtoupper($newName), '"');
- $newParts = explode('_', $newName);
-
- $oldPrefix = '';
- $newPrefix = '';
- for ($i = count($oldParts)-1; $i >= 0; $i--) {
- if ($oldParts[$i] != $newParts[$i]) {
- $oldPrefix = implode('_', $oldParts).'_';
- $newPrefix = implode('_', $newParts).'_';
- break;
- }
- unset($oldParts[$i]);
- unset($newParts[$i]);
+ $temporary = $temporary ? 'TRUE' : 'FALSE';
+
+ $newName = trim( strtoupper( $newName ), '"');
+ $oldName = trim( strtoupper( $oldName ), '"');
+
+ $tabName = substr( $newName, strlen( $wgDBprefix ) );
+ $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
+
+ return $this->doQuery( 'BEGIN DUPLICATE_TABLE(\'' . $tabName . '\', \'' . $oldPrefix . '\', \'' . strtoupper( $wgDBprefix ) . '\', ' . $temporary . '); END;' );
+ }
+
+ function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) {
+ $listWhere = '';
+ if (!empty($prefix)) {
+ $listWhere = ' AND table_name LIKE \''.strtoupper($prefix).'%\'';
}
- $tabName = substr($oldName, strlen($oldPrefix));
+ $result = $this->doQuery( "SELECT table_name FROM user_tables WHERE table_name NOT LIKE '%!_IDX$_' ESCAPE '!' $listWhere" );
+
+ // dirty code ... i know
+ $endArray = array();
+ $endArray[] = $prefix.'MWUSER';
+ $endArray[] = $prefix.'PAGE';
+ $endArray[] = $prefix.'IMAGE';
+ $fixedOrderTabs = $endArray;
+ while (($row = $result->fetchRow()) !== false) {
+ if (!in_array($row['table_name'], $fixedOrderTabs))
+ $endArray[] = $row['table_name'];
+ }
+
+ return $endArray;
+ }
+
+ public function dropTable( $tableName, $fName = 'DatabaseOracle::dropTable' ) {
+ $tableName = $this->tableName($tableName);
+ if( !$this->tableExists( $tableName ) ) {
+ return false;
+ }
- return $this->query( 'BEGIN DUPLICATE_TABLE(\'' . $tabName . '\', \'' . $oldPrefix . '\', \''.$newPrefix.'\', ' . $temporary . '); END;', $fname );
+ return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
}
function timestamp( $ts = 0 ) {
@@ -818,7 +916,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* @return string wikitext of a link to the server software's web site
*/
- function getSoftwareLink() {
+ public static function getSoftwareLink() {
return '[http://www.oracle.com/ Oracle]';
}
@@ -826,14 +924,21 @@ class DatabaseOracle extends DatabaseBase {
* @return string Version information from the database
*/
function getServerVersion() {
- return oci_server_version( $this->mConn );
+ //better version number, fallback on driver
+ $rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' );
+ if ( !( $row = $rset->fetchRow() ) ) {
+ return oci_server_version( $this->mConn );
+ }
+ return $row['version'];
}
/**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
*/
function tableExists( $table ) {
- $SQL = "SELECT 1 FROM user_tables WHERE table_name='$table'";
+ $table = $this->addQuotes( trim( $this->tableName($table), '"' ) );
+ $owner = $this->addQuotes( strtoupper( $this->mDBname ) );
+ $SQL = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
$res = $this->doQuery( $SQL );
if ( $res ) {
$count = $res->numRows();
@@ -841,7 +946,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$count = 0;
}
- return $count;
+ return $count!=0;
}
/**
@@ -850,76 +955,78 @@ class DatabaseOracle extends DatabaseBase {
* For internal calls. Use fieldInfo for normal usage.
* Returns false if the field doesn't exist
*
- * @param Array $table
- * @param String $field
+ * @param $table Array
+ * @param $field String
*/
private function fieldInfoMulti( $table, $field ) {
- $tableWhere = '';
- $field = strtoupper($field);
- if (is_array($table)) {
- $table = array_map( array( &$this, 'tableName' ), $table );
+ $field = strtoupper( $field );
+ if ( is_array( $table ) ) {
+ $table = array_map( array( &$this, 'tableNameInternal' ), $table );
$tableWhere = 'IN (';
- foreach($table as &$singleTable) {
- $singleTable = strtoupper(trim( $singleTable, '"' ));
- if (isset($this->mFieldInfoCache["$singleTable.$field"])) {
+ foreach( $table as &$singleTable ) {
+ $singleTable = strtoupper( trim( $singleTable, '"' ) );
+ if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
return $this->mFieldInfoCache["$singleTable.$field"];
}
- $tableWhere .= '\''.$singleTable.'\',';
+ $tableWhere .= '\'' . $singleTable . '\',';
}
- $tableWhere = rtrim($tableWhere, ',').')';
+ $tableWhere = rtrim( $tableWhere, ',' ) . ')';
} else {
- $table = strtoupper(trim( $this->tableName($table), '"' ));
- if (isset($this->mFieldInfoCache["$table.$field"])) {
+ $table = strtoupper( trim( $this->tableNameInternal( $table ), '"' ) );
+ if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
return $this->mFieldInfoCache["$table.$field"];
}
$tableWhere = '= \''.$table.'\'';
}
$fieldInfoStmt = oci_parse( $this->mConn, 'SELECT * FROM wiki_field_info_full WHERE table_name '.$tableWhere.' and column_name = \''.$field.'\'' );
- if ( oci_execute( $fieldInfoStmt, OCI_DEFAULT ) === false ) {
+ if ( oci_execute( $fieldInfoStmt, $this->execFlags() ) === false ) {
$e = oci_error( $fieldInfoStmt );
$this->reportQueryError( $e['message'], $e['code'], 'fieldInfo QUERY', __METHOD__ );
return false;
}
$res = new ORAResult( $this, $fieldInfoStmt );
- if ($res->numRows() == 0 ) {
- if (is_array($table)) {
- foreach($table as &$singleTable) {
+ if ( $res->numRows() == 0 ) {
+ if ( is_array( $table ) ) {
+ foreach( $table as &$singleTable ) {
$this->mFieldInfoCache["$singleTable.$field"] = false;
}
} else {
$this->mFieldInfoCache["$table.$field"] = false;
}
+ $fieldInfoTemp = null;
} else {
$fieldInfoTemp = new ORAField( $res->fetchRow() );
$table = $fieldInfoTemp->tableName();
$this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
- return $fieldInfoTemp;
}
+ $res->free();
+ return $fieldInfoTemp;
}
function fieldInfo( $table, $field ) {
if ( is_array( $table ) ) {
- throw new DBUnexpectedError( $this, 'Database::fieldInfo called with table array!' );
+ throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
}
return $this->fieldInfoMulti ($table, $field);
}
- function fieldExists( $table, $field, $fname = 'DatabaseOracle::fieldExists' ) {
- return (bool)$this->fieldInfo( $table, $field, $fname );
- }
-
- function begin( $fname = '' ) {
+ function begin( $fname = 'DatabaseOracle::begin' ) {
$this->mTrxLevel = 1;
}
- function immediateCommit( $fname = '' ) {
- return true;
+ function commit( $fname = 'DatabaseOracle::commit' ) {
+ if ( $this->mTrxLevel ) {
+ oci_commit( $this->mConn );
+ $this->mTrxLevel = 0;
+ }
}
- function commit( $fname = '' ) {
- oci_commit( $this->mConn );
- $this->mTrxLevel = 0;
+ function rollback( $fname = 'DatabaseOracle::rollback' ) {
+ if ( $this->mTrxLevel ) {
+ oci_rollback( $this->mConn );
+ $this->mTrxLevel = 0;
+ }
}
/* Not even sure why this is used in the main codebase... */
@@ -928,7 +1035,7 @@ class DatabaseOracle extends DatabaseBase {
}
/* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false, $fname = 'DatabaseOracle::sourceStream' ) {
$cmd = '';
$done = false;
$dollarquote = false;
@@ -953,6 +1060,7 @@ class DatabaseOracle extends DatabaseBase {
if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
if ( $dollarquote ) {
$dollarquote = false;
+ $line = str_replace( '/*$mw$*/', '', $line ); // remove dollarquotes
$done = true;
} else {
$dollarquote = true;
@@ -977,11 +1085,11 @@ class DatabaseOracle extends DatabaseBase {
}
} else {
foreach ( $replacements as $mwVar => $scVar ) {
- $cmd = str_replace( '&' . $scVar . '.', '{$' . $mwVar . '}', $cmd );
+ $cmd = str_replace( '&' . $scVar . '.', '`{$' . $mwVar . '}`', $cmd );
}
$cmd = $this->replaceVars( $cmd );
- $res = $this->query( $cmd, __METHOD__ );
+ $res = $this->doQuery( $cmd );
if ( $resultCallback ) {
call_user_func( $resultCallback, $res, $this );
}
@@ -999,36 +1107,24 @@ class DatabaseOracle extends DatabaseBase {
return true;
}
- function setup_database() {
- global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport, $wgDBuser;
-
- $res = $this->sourceFile( "../maintenance/ora/tables.sql" );
- if ($res === true) {
- print " done.</li>\n";
- } else {
- print " <b>FAILED</b></li>\n";
- dieout( htmlspecialchars( $res ) );
- }
-
- // Avoid the non-standard "REPLACE INTO" syntax
- echo "<li>Populating interwiki table</li>\n";
- $f = fopen( "../maintenance/interwiki.sql", 'r' );
- if ( $f == false ) {
- dieout( "Could not find the interwiki.sql file" );
+ function selectDB( $db ) {
+ $this->mDBname = $db;
+ if ( $db == null || $db == $this->mUser ) {
+ return true;
}
-
- // do it like the postgres :D
- $SQL = "INSERT INTO ".$this->tableName('interwiki')." (iw_prefix,iw_url,iw_local) VALUES ";
- while ( !feof( $f ) ) {
- $line = fgets( $f, 1024 );
- $matches = array();
- if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) {
- continue;
+ $sql = 'ALTER SESSION SET CURRENT_SCHEMA=' . strtoupper($db);
+ $stmt = oci_parse( $this->mConn, $sql );
+ wfSuppressWarnings();
+ $success = oci_execute( $stmt );
+ wfRestoreWarnings();
+ if ( !$success ) {
+ $e = oci_error( $stmt );
+ if ( $e['code'] != '1435' ) {
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
}
- $this->query( "$SQL $matches[1],$matches[2])" );
+ return false;
}
-
- echo "<li>Table interwiki successfully populated</li>\n";
+ return true;
}
function strencode( $s ) {
@@ -1036,35 +1132,42 @@ class DatabaseOracle extends DatabaseBase {
}
function addQuotes( $s ) {
- global $wgLang;
- if ( isset( $wgLang->mLoaded ) && $wgLang->mLoaded ) {
- $s = $wgLang->checkTitleEncoding( $s );
+ global $wgContLang;
+ if ( isset( $wgContLang->mLoaded ) && $wgContLang->mLoaded ) {
+ $s = $wgContLang->checkTitleEncoding( $s );
}
return "'" . $this->strencode( $s ) . "'";
}
- function quote_ident( $s ) {
+ public function addIdentifierQuotes( $s ) {
+ if ( !$this->mFlags & DBO_DDLMODE ) {
+ $s = '"' . str_replace( '"', '""', $s ) . '"';
+ }
return $s;
}
function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) {
- global $wgLang;
+ global $wgContLang;
- $conds2 = array();
- $conds = ($conds != null && !is_array($conds)) ? array($conds) : $conds;
- foreach ( $conds as $col => $val ) {
- $col_info = $this->fieldInfoMulti( $table, $col );
- $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
- if ( $col_type == 'CLOB' ) {
- $conds2['TO_CHAR(' . $col . ')'] = $wgLang->checkTitleEncoding( $val );
- } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) {
- $conds2[$col] = $wgLang->checkTitleEncoding( $val );
- } else {
- $conds2[$col] = $val;
+ if ($conds != null) {
+ $conds2 = array();
+ $conds = ( !is_array( $conds ) ) ? array( $conds ) : $conds;
+ foreach ( $conds as $col => $val ) {
+ $col_info = $this->fieldInfoMulti( $table, $col );
+ $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
+ if ( $col_type == 'CLOB' ) {
+ $conds2['TO_CHAR(' . $col . ')'] = $wgContLang->checkTitleEncoding( $val );
+ } elseif ( $col_type == 'VARCHAR2' && !mb_check_encoding( $val ) ) {
+ $conds2[$col] = $wgContLang->checkTitleEncoding( $val );
+ } else {
+ $conds2[$col] = $val;
+ }
}
- }
- return parent::selectRow( $table, $vars, $conds2, $fname, $options, $join_conds );
+ return parent::selectRow( $table, $vars, $conds2, $fname, $options, $join_conds );
+ } else {
+ return parent::selectRow( $table, $vars, $conds, $fname, $options, $join_conds );
+ }
}
/**
@@ -1111,24 +1214,24 @@ class DatabaseOracle extends DatabaseBase {
}
public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) {
- global $wgLang;
+ global $wgContLang;
- if ( $wgLang != null ) {
+ if ( $wgContLang != null && $conds != null && $conds != '*' ) {
$conds2 = array();
- $conds = ($conds != null && !is_array($conds)) ? array($conds) : $conds;
+ $conds = ( !is_array( $conds ) ) ? array( $conds ) : $conds;
foreach ( $conds as $col => $val ) {
$col_info = $this->fieldInfoMulti( $table, $col );
$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
if ( $col_type == 'CLOB' ) {
- $conds2['TO_CHAR(' . $col . ')'] = $wgLang->checkTitleEncoding( $val );
+ $conds2['TO_CHAR(' . $col . ')'] = $wgContLang->checkTitleEncoding( $val );
} else {
if ( is_array( $val ) ) {
$conds2[$col] = $val;
foreach ( $conds2[$col] as &$val2 ) {
- $val2 = $wgLang->checkTitleEncoding( $val2 );
+ $val2 = $wgContLang->checkTitleEncoding( $val2 );
}
} else {
- $conds2[$col] = $wgLang->checkTitleEncoding( $val );
+ $conds2[$col] = $wgContLang->checkTitleEncoding( $val );
}
}
}
@@ -1139,9 +1242,103 @@ class DatabaseOracle extends DatabaseBase {
}
}
+ function update( $table, $values, $conds, $fname = 'DatabaseOracle::update', $options = array() ) {
+ global $wgContLang;
+
+ $table = $this->tableName( $table );
+ $opts = $this->makeUpdateOptions( $options );
+ $sql = "UPDATE $opts $table SET ";
+
+ $first = true;
+ foreach ( $values as $col => &$val ) {
+ $sqlSet = $this->fieldBindStatement( $table, $col, $val, true );
+
+ if ( !$first ) {
+ $sqlSet = ', ' . $sqlSet;
+ } else {
+ $first = false;
+ }
+ $sql .= $sqlSet;
+ }
+
+ if ( $conds != '*' ) {
+ $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ }
+
+ if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
+ $e = oci_error( $this->mConn );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ return false;
+ }
+ foreach ( $values as $col => &$val ) {
+ $col_info = $this->fieldInfoMulti( $table, $col );
+ $col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
+
+ if ( $val === null ) {
+ // do nothing ... null was inserted in statement creation
+ } elseif ( $col_type != 'BLOB' && $col_type != 'CLOB' ) {
+ if ( is_object( $val ) ) {
+ $val = $val->getData();
+ }
+
+ if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
+ $val = '31-12-2030 12:00:00.000000';
+ }
+
+ $val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
+ if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
+ $e = oci_error( $stmt );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ return false;
+ }
+ } else {
+ if ( ( $lob[$col] = oci_new_descriptor( $this->mConn, OCI_D_LOB ) ) === false ) {
+ $e = oci_error( $stmt );
+ throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
+ }
+
+ if ( $col_type == 'BLOB' ) {
+ $lob[$col]->writeTemporary( $val );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB );
+ } else {
+ $lob[$col]->writeTemporary( $val );
+ oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, OCI_B_CLOB );
+ }
+ }
+ }
+
+ wfSuppressWarnings();
+
+ if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
+ $e = oci_error( $stmt );
+ if ( !$this->ignore_DUP_VAL_ON_INDEX || $e['code'] != '1' ) {
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+ return false;
+ } else {
+ $this->mAffectedRows = oci_num_rows( $stmt );
+ }
+ } else {
+ $this->mAffectedRows = oci_num_rows( $stmt );
+ }
+
+ wfRestoreWarnings();
+
+ if ( isset( $lob ) ) {
+ foreach ( $lob as $lob_v ) {
+ $lob_v->free();
+ }
+ }
+
+ if ( !$this->mTrxLevel ) {
+ oci_commit( $this->mConn );
+ }
+
+ oci_free_statement( $stmt );
+ }
+
function bitNot( $field ) {
// expecting bit-fields smaller than 4bytes
- return 'BITNOT(' . $bitField . ')';
+ return 'BITNOT(' . $field . ')';
}
function bitAnd( $fieldLeft, $fieldRight ) {
@@ -1152,17 +1349,6 @@ class DatabaseOracle extends DatabaseBase {
return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
}
- /**
- * How lagged is this slave?
- *
- * @return int
- */
- public function getLag() {
- # Not implemented for Oracle
- return 0;
- }
-
- function setFakeSlaveLag( $lag ) { }
function setFakeMaster( $enabled = true ) { }
function getDBname() {
@@ -1173,26 +1359,6 @@ class DatabaseOracle extends DatabaseBase {
return $this->mServer;
}
- public function replaceVars( $ins ) {
- $varnames = array( 'wgDBprefix' );
- if ( $this->mFlags & DBO_SYSDBA ) {
- $varnames[] = 'wgDBOracleDefTS';
- $varnames[] = 'wgDBOracleTempTS';
- }
-
- // Ordinary variables
- foreach ( $varnames as $var ) {
- if ( isset( $GLOBALS[$var] ) ) {
- $val = addslashes( $GLOBALS[$var] ); // FIXME: safety check?
- $ins = str_replace( '{$' . $var . '}', $val, $ins );
- $ins = str_replace( '/*$' . $var . '*/`', '`' . $val, $ins );
- $ins = str_replace( '/*$' . $var . '*/', $val, $ins );
- }
- }
-
- return parent::replaceVars( $ins );
- }
-
public function getSearchEngine() {
return 'SearchOracle';
}
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index 9072a5b2..bc71a9a5 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -1,46 +1,59 @@
<?php
/**
- * @ingroup Database
- * @file
* This is the Postgres database abstraction layer.
*
+ * @file
+ * @ingroup Database
*/
-class PostgresField {
- private $name, $tablename, $type, $nullable, $max_length;
+
+class PostgresField implements Field {
+ private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname;
static function fromText($db, $table, $field) {
global $wgDBmwschema;
$q = <<<SQL
SELECT
-CASE WHEN typname = 'int2' THEN 'smallint'
-WHEN typname = 'int4' THEN 'integer'
-WHEN typname = 'int8' THEN 'bigint'
-WHEN typname = 'bpchar' THEN 'char'
-ELSE typname END AS typname,
-attnotnull, attlen
-FROM pg_class, pg_namespace, pg_attribute, pg_type
-WHERE relnamespace=pg_namespace.oid
-AND relkind='r'
-AND attrelid=pg_class.oid
-AND atttypid=pg_type.oid
+ attnotnull, attlen, COALESCE(conname, '') AS conname,
+ COALESCE(condeferred, 'f') AS deferred,
+ COALESCE(condeferrable, 'f') AS deferrable,
+ CASE WHEN typname = 'int2' THEN 'smallint'
+ WHEN typname = 'int4' THEN 'integer'
+ WHEN typname = 'int8' THEN 'bigint'
+ WHEN typname = 'bpchar' THEN 'char'
+ ELSE typname END AS typname
+FROM pg_class c
+JOIN pg_namespace n ON (n.oid = c.relnamespace)
+JOIN pg_attribute a ON (a.attrelid = c.oid)
+JOIN pg_type t ON (t.oid = a.atttypid)
+LEFT JOIN pg_constraint o ON (o.conrelid = c.oid AND a.attnum = ANY(o.conkey) AND o.contype = 'f')
+WHERE relkind = 'r'
AND nspname=%s
AND relname=%s
AND attname=%s;
SQL;
- $res = $db->query(sprintf($q,
- $db->addQuotes($wgDBmwschema),
- $db->addQuotes($table),
- $db->addQuotes($field)));
- $row = $db->fetchObject($res);
- if (!$row)
+
+ $table = $db->tableName( $table );
+ $res = $db->query(
+ sprintf( $q,
+ $db->addQuotes( $wgDBmwschema ),
+ $db->addQuotes( $table ),
+ $db->addQuotes( $field )
+ )
+ );
+ $row = $db->fetchObject( $res );
+ if ( !$row ) {
return null;
+ }
$n = new PostgresField;
$n->type = $row->typname;
- $n->nullable = ($row->attnotnull == 'f');
+ $n->nullable = ( $row->attnotnull == 'f' );
$n->name = $field;
$n->tablename = $table;
$n->max_length = $row->attlen;
+ $n->deferrable = ( $row->deferrable == 't' );
+ $n->deferred = ( $row->deferred == 't' );
+ $n->conname = $row->conname;
return $n;
}
@@ -56,13 +69,26 @@ SQL;
return $this->type;
}
- function nullable() {
+ function isNullable() {
return $this->nullable;
}
function maxLength() {
return $this->max_length;
}
+
+ function is_deferrable() {
+ return $this->deferrable;
+ }
+
+ function is_deferred() {
+ return $this->deferred;
+ }
+
+ function conname() {
+ return $this->conname;
+ }
+
}
/**
@@ -74,16 +100,6 @@ class DatabasePostgres extends DatabaseBase {
var $numeric_version = null;
var $mAffectedRows = null;
- function DatabasePostgres($server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0 )
- {
-
- $this->mFailFunction = $failFunction;
- $this->mFlags = $flags;
- $this->open( $server, $user, $password, $dbName);
-
- }
-
function getType() {
return 'postgres';
}
@@ -115,18 +131,18 @@ class DatabasePostgres extends DatabaseBase {
function hasConstraint( $name ) {
global $wgDBmwschema;
- $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" . pg_escape_string( $name ) . "' AND n.nspname = '" . pg_escape_string($wgDBmwschema) ."'";
- return $this->numRows($res = $this->doQuery($SQL));
+ $SQL = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n WHERE c.connamespace = n.oid AND conname = '" .
+ pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" . pg_escape_string( $this->mConn, $wgDBmwschema ) ."'";
+ $res = $this->doQuery( $SQL );
+ return $this->numRows( $res );
}
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0)
- {
- return new DatabasePostgres( $server, $user, $password, $dbName, $failFunction, $flags );
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
+ return new DatabasePostgres( $server, $user, $password, $dbName, $flags );
}
/**
* Usually aborts on failure
- * If the failFunction is set to a non-zero integer, returns success
*/
function open( $server, $user, $password, $dbName ) {
# Test for Postgres support, to avoid suppressed fatal error
@@ -136,7 +152,7 @@ class DatabasePostgres extends DatabaseBase {
global $wgDBport;
- if (!strlen($user)) { ## e.g. the class is being loaded
+ if ( !strlen( $user ) ) { # e.g. the class is being loaded
return;
}
$this->close();
@@ -149,11 +165,12 @@ class DatabasePostgres extends DatabaseBase {
$connectVars = array(
'dbname' => $dbName,
'user' => $user,
- 'password' => $password );
- if ($server!=false && $server!="") {
+ 'password' => $password
+ );
+ if ( $server != false && $server != '' ) {
$connectVars['host'] = $server;
}
- if ($port!=false && $port!="") {
+ if ( $port != false && $port != '' ) {
$connectVars['port'] = $port;
}
$connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
@@ -162,35 +179,30 @@ class DatabasePostgres extends DatabaseBase {
$this->mConn = pg_connect( $connectString );
$phpError = $this->restoreErrorHandler();
- if ( $this->mConn == false ) {
+ if ( !$this->mConn ) {
wfDebug( "DB connection error\n" );
wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
- wfDebug( $this->lastError()."\n" );
- if ( !$this->mFailFunction ) {
- throw new DBConnectionError( $this, $phpError );
- } else {
- return false;
- }
+ wfDebug( $this->lastError() . "\n" );
+ throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
}
$this->mOpened = true;
global $wgCommandLineMode;
- ## If called from the command-line (e.g. importDump), only show errors
- if ($wgCommandLineMode) {
+ # If called from the command-line (e.g. importDump), only show errors
+ if ( $wgCommandLineMode ) {
$this->doQuery( "SET client_min_messages = 'ERROR'" );
}
$this->doQuery( "SET client_encoding='UTF8'" );
global $wgDBmwschema, $wgDBts2schema;
- if (isset( $wgDBmwschema ) && isset( $wgDBts2schema )
+ if ( isset( $wgDBmwschema ) && isset( $wgDBts2schema )
&& $wgDBmwschema !== 'mediawiki'
&& preg_match( '/^\w+$/', $wgDBmwschema )
&& preg_match( '/^\w+$/', $wgDBts2schema )
) {
- $safeschema = $this->quote_ident($wgDBmwschema);
- $safeschema2 = $this->quote_ident($wgDBts2schema);
+ $safeschema = $this->addIdentifierQuotes( $wgDBmwschema );
$this->doQuery( "SET search_path = $safeschema, $wgDBts2schema, public" );
}
@@ -205,365 +217,6 @@ class DatabasePostgres extends DatabaseBase {
return $s;
}
-
- function initial_setup($password, $dbName) {
- // If this is the initial connection, setup the schema stuff and possibly create the user
- global $wgDBname, $wgDBuser, $wgDBpassword, $wgDBsuperuser, $wgDBmwschema, $wgDBts2schema;
-
- print "<li>Checking the version of Postgres...";
- $version = $this->getServerVersion();
- $PGMINVER = '8.1';
- if ($version < $PGMINVER) {
- print "<b>FAILED</b>. Required version is $PGMINVER. You have " . htmlspecialchars( $version ) . "</li>\n";
- dieout("</ul>");
- }
- print "version " . htmlspecialchars( $this->numeric_version ) . " is OK.</li>\n";
-
- $safeuser = $this->quote_ident($wgDBuser);
- // Are we connecting as a superuser for the first time?
- if ($wgDBsuperuser) {
- // Are we really a superuser? Check out our rights
- $SQL = "SELECT
- CASE WHEN usesuper IS TRUE THEN
- CASE WHEN usecreatedb IS TRUE THEN 3 ELSE 1 END
- ELSE CASE WHEN usecreatedb IS TRUE THEN 2 ELSE 0 END
- END AS rights
- FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBsuperuser);
- $rows = $this->numRows($res = $this->doQuery($SQL));
- if (!$rows) {
- print "<li>ERROR: Could not read permissions for user \"" . htmlspecialchars( $wgDBsuperuser ) . "\"</li>\n";
- dieout('</ul>');
- }
- $perms = pg_fetch_result($res, 0, 0);
-
- $SQL = "SELECT 1 FROM pg_catalog.pg_user WHERE usename = " . $this->addQuotes($wgDBuser);
- $rows = $this->numRows($this->doQuery($SQL));
- if ($rows) {
- print "<li>User \"" . htmlspecialchars( $wgDBuser ) . "\" already exists, skipping account creation.</li>";
- }
- else {
- if ($perms != 1 and $perms != 3) {
- print "<li>ERROR: the user \"" . htmlspecialchars( $wgDBsuperuser ) . "\" cannot create other users. ";
- print 'Please use a different Postgres user.</li>';
- dieout('</ul>');
- }
- print "<li>Creating user <b>" . htmlspecialchars( $wgDBuser ) . "</b>...";
- $safepass = $this->addQuotes($wgDBpassword);
- $SQL = "CREATE USER $safeuser NOCREATEDB PASSWORD $safepass";
- $this->doQuery($SQL);
- print "OK</li>\n";
- }
- // User now exists, check out the database
- if ($dbName != $wgDBname) {
- $SQL = "SELECT 1 FROM pg_catalog.pg_database WHERE datname = " . $this->addQuotes($wgDBname);
- $rows = $this->numRows($this->doQuery($SQL));
- if ($rows) {
- print "<li>Database \"" . htmlspecialchars( $wgDBname ) . "\" already exists, skipping database creation.</li>";
- }
- else {
- if ($perms < 1) {
- print "<li>ERROR: the user \"" . htmlspecialchars( $wgDBsuperuser ) . "\" cannot create databases. ";
- print 'Please use a different Postgres user.</li>';
- dieout('</ul>');
- }
- print "<li>Creating database <b>" . htmlspecialchars( $wgDBname ) . "</b>...";
- $safename = $this->quote_ident($wgDBname);
- $SQL = "CREATE DATABASE $safename OWNER $safeuser ";
- $this->doQuery($SQL);
- print "OK</li>\n";
- // Hopefully tsearch2 and plpgsql are in template1...
- }
-
- // Reconnect to check out tsearch2 rights for this user
- print "<li>Connecting to \"" . htmlspecialchars( $wgDBname ) . "\" as superuser \"" .
- htmlspecialchars( $wgDBsuperuser ) . "\" to check rights...";
-
- $connectVars = array();
- if ($this->mServer!=false && $this->mServer!="") {
- $connectVars['host'] = $this->mServer;
- }
- if ($this->mPort!=false && $this->mPort!="") {
- $connectVars['port'] = $this->mPort;
- }
- $connectVars['dbname'] = $wgDBname;
- $connectVars['user'] = $wgDBsuperuser;
- $connectVars['password'] = $password;
-
- @$this->mConn = pg_connect( $this->makeConnectionString( $connectVars ) );
- if ( $this->mConn == false ) {
- print "<b>FAILED TO CONNECT!</b></li>";
- dieout("</ul>");
- }
- print "OK</li>\n";
- }
-
- if ($this->numeric_version < 8.3) {
- // Tsearch2 checks
- print "<li>Checking that tsearch2 is installed in the database \"" .
- htmlspecialchars( $wgDBname ) . "\"...";
- if (! $this->tableExists("pg_ts_cfg", $wgDBts2schema)) {
- print "<b>FAILED</b>. tsearch2 must be installed in the database \"" .
- htmlspecialchars( $wgDBname ) . "\".";
- print "Please see <a href='http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>";
- print " for instructions or ask on #postgresql on irc.freenode.net</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- print "<li>Ensuring that user \"" . htmlspecialchars( $wgDBuser ) .
- "\" has select rights on the tsearch2 tables...";
- foreach (array('cfg','cfgmap','dict','parser') as $table) {
- $SQL = "GRANT SELECT ON pg_ts_$table TO $safeuser";
- $this->doQuery($SQL);
- }
- print "OK</li>\n";
- }
-
- // Setup the schema for this user if needed
- $result = $this->schemaExists($wgDBmwschema);
- $safeschema = $this->quote_ident($wgDBmwschema);
- if (!$result) {
- print "<li>Creating schema <b>" . htmlspecialchars( $wgDBmwschema ) . "</b> ...";
- $result = $this->doQuery("CREATE SCHEMA $safeschema AUTHORIZATION $safeuser");
- if (!$result) {
- print "<b>FAILED</b>.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- }
- else {
- print "<li>Schema already exists, explicitly granting rights...\n";
- $safeschema2 = $this->addQuotes($wgDBmwschema);
- $SQL = "SELECT 'GRANT ALL ON '||pg_catalog.quote_ident(relname)||' TO $safeuser;'\n".
- "FROM pg_catalog.pg_class p, pg_catalog.pg_namespace n\n".
- "WHERE relnamespace = n.oid AND n.nspname = $safeschema2\n".
- "AND p.relkind IN ('r','S','v')\n";
- $SQL .= "UNION\n";
- $SQL .= "SELECT 'GRANT ALL ON FUNCTION '||pg_catalog.quote_ident(proname)||'('||\n".
- "pg_catalog.oidvectortypes(p.proargtypes)||') TO $safeuser;'\n".
- "FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n\n".
- "WHERE p.pronamespace = n.oid AND n.nspname = $safeschema2";
- $res = $this->doQuery($SQL);
- if (!$res) {
- print "<b>FAILED</b>. Could not set rights for the user.</li>\n";
- dieout("</ul>");
- }
- $this->doQuery("SET search_path = $safeschema");
- $rows = $this->numRows($res);
- while ($rows) {
- $rows--;
- $this->doQuery(pg_fetch_result($res, $rows, 0));
- }
- print "OK</li>";
- }
-
- // Install plpgsql if needed
- $this->setup_plpgsql();
-
- $wgDBsuperuser = '';
- return true; // Reconnect as regular user
-
- } // end superuser
-
- if (!defined('POSTGRES_SEARCHPATH')) {
-
- if ($this->numeric_version < 8.3) {
- // Do we have the basic tsearch2 table?
- print "<li>Checking for tsearch2 in the schema \"" . htmlspecialchars( $wgDBts2schema ) . "\"...";
- if (! $this->tableExists("pg_ts_dict", $wgDBts2schema)) {
- print "<b>FAILED</b>. Make sure tsearch2 is installed. See <a href=";
- print "'http://www.devx.com/opensource/Article/21674/0/page/2'>this article</a>";
- print " for instructions.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
-
- // Does this user have the rights to the tsearch2 tables?
- $ctype = pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0);
- print "<li>Checking tsearch2 permissions...";
- // Let's check all four, just to be safe
- error_reporting( 0 );
- $ts2tables = array('cfg','cfgmap','dict','parser');
- $safetsschema = $this->quote_ident($wgDBts2schema);
- foreach ( $ts2tables AS $tname ) {
- $SQL = "SELECT count(*) FROM $safetsschema.pg_ts_$tname";
- $res = $this->doQuery($SQL);
- if (!$res) {
- print "<b>FAILED</b> to access " . htmlspecialchars( "pg_ts_$tname" ) .
- ". Make sure that the user \"". htmlspecialchars( $wgDBuser ) .
- "\" has SELECT access to all four tsearch2 tables</li>\n";
- dieout("</ul>");
- }
- }
- $SQL = "SELECT ts_name FROM $safetsschema.pg_ts_cfg WHERE locale = " . $this->addQuotes( $ctype ) ;
- $SQL .= " ORDER BY CASE WHEN ts_name <> 'default' THEN 1 ELSE 0 END";
- $res = $this->doQuery($SQL);
- error_reporting( E_ALL );
- if (!$res) {
- print "<b>FAILED</b>. Could not determine the tsearch2 locale information</li>\n";
- dieout("</ul>");
- }
- print "OK</li>";
-
- // Will the current locale work? Can we force it to?
- print "<li>Verifying tsearch2 locale with " . htmlspecialchars( $ctype ) . "...";
- $rows = $this->numRows($res);
- $resetlocale = 0;
- if (!$rows) {
- print "<b>not found</b></li>\n";
- print "<li>Attempting to set default tsearch2 locale to \"" . htmlspecialchars( $ctype ) . "\"...";
- $resetlocale = 1;
- }
- else {
- $tsname = pg_fetch_result($res, 0, 0);
- if ($tsname != 'default') {
- print "<b>not set to default (" . htmlspecialchars( $tsname ) . ")</b>";
- print "<li>Attempting to change tsearch2 default locale to \"" .
- htmlspecialchars( $ctype ) . "\"...";
- $resetlocale = 1;
- }
- }
- if ($resetlocale) {
- $SQL = "UPDATE $safetsschema.pg_ts_cfg SET locale = " . $this->addQuotes( $ctype ) . " WHERE ts_name = 'default'";
- $res = $this->doQuery($SQL);
- if (!$res) {
- print "<b>FAILED</b>. ";
- print "Please make sure that the locale in pg_ts_cfg for \"default\" is set to \"" .
- htmlspecialchars( $ctype ) . "\"</li>\n";
- dieout("</ul>");
- }
- print "OK</li>";
- }
-
- // Final test: try out a simple tsearch2 query
- $SQL = "SELECT $safetsschema.to_tsvector('default','MediaWiki tsearch2 testing')";
- $res = $this->doQuery($SQL);
- if (!$res) {
- print "<b>FAILED</b>. Specifically, \"" . htmlspecialchars( $SQL ) . "\" did not work.</li>";
- dieout("</ul>");
- }
- print "OK</li>";
- }
-
- // Install plpgsql if needed
- $this->setup_plpgsql();
-
- // Does the schema already exist? Who owns it?
- $result = $this->schemaExists($wgDBmwschema);
- if (!$result) {
- print "<li>Creating schema <b>" . htmlspecialchars( $wgDBmwschema ) . "</b> ...";
- error_reporting( 0 );
- $safeschema = $this->quote_ident($wgDBmwschema);
- $result = $this->doQuery("CREATE SCHEMA $safeschema");
- error_reporting( E_ALL );
- if (!$result) {
- print "<b>FAILED</b>. The user \"" . htmlspecialchars( $wgDBuser ) .
- "\" must be able to access the schema. ".
- "You can try making them the owner of the database, or try creating the schema with a ".
- "different user, and then grant access to the \"" .
- htmlspecialchars( $wgDBuser ) . "\" user.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- }
- else if ($result != $wgDBuser) {
- print "<li>Schema \"" . htmlspecialchars( $wgDBmwschema ) . "\" exists but is not owned by \"" .
- htmlspecialchars( $wgDBuser ) . "\". Not ideal.</li>\n";
- }
- else {
- print "<li>Schema \"" . htmlspecialchars( $wgDBmwschema ) . "\" exists and is owned by \"" .
- htmlspecialchars( $wgDBuser ) . "\". Excellent.</li>\n";
- }
-
- // Always return GMT time to accomodate the existing integer-based timestamp assumption
- print "<li>Setting the timezone to GMT for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
- $SQL = "ALTER USER $safeuser SET timezone = 'GMT'";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<b>FAILED</b>.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- // Set for the rest of this session
- $SQL = "SET timezone = 'GMT'";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<li>Failed to set timezone</li>\n";
- dieout("</ul>");
- }
-
- print "<li>Setting the datestyle to ISO, YMD for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
- $SQL = "ALTER USER $safeuser SET datestyle = 'ISO, YMD'";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<b>FAILED</b>.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- // Set for the rest of this session
- $SQL = "SET datestyle = 'ISO, YMD'";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<li>Failed to set datestyle</li>\n";
- dieout("</ul>");
- }
-
- // Fix up the search paths if needed
- print "<li>Setting the search path for user \"" . htmlspecialchars( $wgDBuser ) . "\" ...";
- $path = $this->quote_ident($wgDBmwschema);
- if ($wgDBts2schema !== $wgDBmwschema)
- $path .= ", ". $this->quote_ident($wgDBts2schema);
- if ($wgDBmwschema !== 'public' and $wgDBts2schema !== 'public')
- $path .= ", public";
- $SQL = "ALTER USER $safeuser SET search_path = $path";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<b>FAILED</b>.</li>\n";
- dieout("</ul>");
- }
- print "OK</li>\n";
- // Set for the rest of this session
- $SQL = "SET search_path = $path";
- $result = pg_query($this->mConn, $SQL);
- if (!$result) {
- print "<li>Failed to set search_path</li>\n";
- dieout("</ul>");
- }
- define( "POSTGRES_SEARCHPATH", $path );
- }
- }
-
-
- function setup_plpgsql() {
- print "<li>Checking for Pl/Pgsql ...";
- $SQL = "SELECT 1 FROM pg_catalog.pg_language WHERE lanname = 'plpgsql'";
- $rows = $this->numRows($this->doQuery($SQL));
- if ($rows < 1) {
- // plpgsql is not installed, but if we have a pg_pltemplate table, we should be able to create it
- print "not installed. Attempting to install Pl/Pgsql ...";
- $SQL = "SELECT 1 FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) ".
- "WHERE relname = 'pg_pltemplate' AND nspname='pg_catalog'";
- $rows = $this->numRows($this->doQuery($SQL));
- if ($rows >= 1) {
- $olde = error_reporting(0);
- error_reporting($olde - E_WARNING);
- $result = $this->doQuery("CREATE LANGUAGE plpgsql");
- error_reporting($olde);
- if (!$result) {
- print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>" .
- htmlspecialchars( $wgDBname ) . "</tt></li>";
- dieout("</ul>");
- }
- }
- else {
- print "<b>FAILED</b>. You need to install the language plpgsql in the database <tt>" .
- htmlspecialchars( $wgDBname ) . "</tt></li>";
- dieout("</ul>");
- }
- }
- print "OK</li>\n";
- }
-
-
/**
* Closes a database connection, if it is open
* Returns success, true if already closed
@@ -578,15 +231,15 @@ class DatabasePostgres extends DatabaseBase {
}
function doQuery( $sql ) {
- if (function_exists('mb_convert_encoding')) {
- $sql = mb_convert_encoding($sql,'UTF-8');
+ if ( function_exists( 'mb_convert_encoding' ) ) {
+ $sql = mb_convert_encoding( $sql, 'UTF-8' );
}
- $this->mLastResult = pg_query( $this->mConn, $sql);
+ $this->mLastResult = pg_query( $this->mConn, $sql );
$this->mAffectedRows = null; // use pg_affected_rows(mLastResult)
return $this->mLastResult;
}
- function queryIgnore( $sql, $fname = '' ) {
+ function queryIgnore( $sql, $fname = 'DatabasePostgres::queryIgnore' ) {
return $this->query( $sql, $fname, true );
}
@@ -595,7 +248,7 @@ class DatabasePostgres extends DatabaseBase {
$res = $res->result;
}
if ( !@pg_free_result( $res ) ) {
- throw new DBUnexpectedError($this, "Unable to free Postgres result\n" );
+ throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
}
}
@@ -609,8 +262,8 @@ class DatabasePostgres extends DatabaseBase {
# TODO:
# hashar : not sure if the following test really trigger if the object
# fetching failed.
- if( pg_last_error($this->mConn) ) {
- throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
+ if( pg_last_error( $this->mConn ) ) {
+ throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $row;
}
@@ -620,8 +273,8 @@ class DatabasePostgres extends DatabaseBase {
$res = $res->result;
}
@$row = pg_fetch_array( $res );
- if( pg_last_error($this->mConn) ) {
- throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
+ if( pg_last_error( $this->mConn ) ) {
+ throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $row;
}
@@ -631,17 +284,19 @@ class DatabasePostgres extends DatabaseBase {
$res = $res->result;
}
@$n = pg_num_rows( $res );
- if( pg_last_error($this->mConn) ) {
- throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( pg_last_error($this->mConn) ) );
+ if( pg_last_error( $this->mConn ) ) {
+ throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
return $n;
}
+
function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
return pg_num_fields( $res );
}
+
function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -666,9 +321,8 @@ class DatabasePostgres extends DatabaseBase {
function lastError() {
if ( $this->mConn ) {
return pg_last_error();
- }
- else {
- return "No database connection";
+ } else {
+ return 'No database connection';
}
}
function lastErrno() {
@@ -680,8 +334,9 @@ class DatabasePostgres extends DatabaseBase {
// Forced result for simulated queries
return $this->mAffectedRows;
}
- if( empty( $this->mLastResult ) )
+ if( empty( $this->mLastResult ) ) {
return 0;
+ }
return pg_affected_rows( $this->mLastResult );
}
@@ -692,8 +347,7 @@ class DatabasePostgres extends DatabaseBase {
* Returns -1 if count cannot be found
* Takes same arguments as Database::select()
*/
-
- function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
+ function estimateRowCount( $table, $vars = '*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
$res = $this->select( $table, $vars, $conds, $fname, $options );
$rows = -1;
@@ -703,12 +357,10 @@ class DatabasePostgres extends DatabaseBase {
if( preg_match( '/rows=(\d+)/', $row[0], $count ) ) {
$rows = $count[1];
}
- $this->freeResult($res);
}
return $rows;
}
-
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
@@ -719,7 +371,7 @@ class DatabasePostgres extends DatabaseBase {
if ( !$res ) {
return null;
}
- while ( $row = $this->fetchObject( $res ) ) {
+ foreach ( $res as $row ) {
if ( $row->indexname == $this->indexName( $index ) ) {
return $row;
}
@@ -727,18 +379,19 @@ class DatabasePostgres extends DatabaseBase {
return false;
}
- function indexUnique ($table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
+ function indexUnique( $table, $index, $fname = 'DatabasePostgres::indexUnique' ) {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'".
- " AND indexdef LIKE 'CREATE UNIQUE%(" .
+ " AND indexdef LIKE 'CREATE UNIQUE%(" .
$this->strencode( $this->indexName( $index ) ) .
")'";
$res = $this->query( $sql, $fname );
- if ( !$res )
+ if ( !$res ) {
return null;
- while ($row = $this->fetchObject( $res ))
+ }
+ foreach ( $res as $row ) {
return true;
+ }
return false;
-
}
/**
@@ -755,25 +408,23 @@ class DatabasePostgres extends DatabaseBase {
* @return bool Success of insert operation. IGNORE always returns true.
*/
function insert( $table, $args, $fname = 'DatabasePostgres::insert', $options = array() ) {
- global $wgDBversion;
-
if ( !count( $args ) ) {
return true;
}
$table = $this->tableName( $table );
- if (! isset( $wgDBversion ) ) {
- $wgDBversion = $this->getServerVersion();
+ if (! isset( $this->numeric_version ) ) {
+ $this->getServerVersion();
}
- if ( !is_array( $options ) )
+ if ( !is_array( $options ) ) {
$options = array( $options );
+ }
if ( isset( $args[0] ) && is_array( $args[0] ) ) {
$multi = true;
$keys = array_keys( $args[0] );
- }
- else {
+ } else {
$multi = false;
$keys = array_keys( $args );
}
@@ -784,7 +435,7 @@ class DatabasePostgres extends DatabaseBase {
// If we are not in a transaction, we need to be for savepoint trickery
$didbegin = 0;
if ( $ignore ) {
- if (! $this->mTrxLevel) {
+ if ( !$this->mTrxLevel ) {
$this->begin();
$didbegin = 1;
}
@@ -797,7 +448,7 @@ class DatabasePostgres extends DatabaseBase {
$sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
if ( $multi ) {
- if ( $wgDBversion >= 8.2 && !$ignore ) {
+ if ( $this->numeric_version >= 8.2 && !$ignore ) {
$first = true;
foreach ( $args as $row ) {
if ( $first ) {
@@ -808,8 +459,7 @@ class DatabasePostgres extends DatabaseBase {
$sql .= '(' . $this->makeList( $row ) . ')';
}
$res = (bool)$this->query( $sql, $fname, $ignore );
- }
- else {
+ } else {
$res = true;
$origsql = $sql;
foreach ( $args as $row ) {
@@ -817,17 +467,16 @@ class DatabasePostgres extends DatabaseBase {
$tempsql .= '(' . $this->makeList( $row ) . ')';
if ( $ignore ) {
- pg_query($this->mConn, "SAVEPOINT $ignore");
+ pg_query( $this->mConn, "SAVEPOINT $ignore" );
}
$tempres = (bool)$this->query( $tempsql, $fname, $ignore );
if ( $ignore ) {
$bar = pg_last_error();
- if ($bar != false) {
+ if ( $bar != false ) {
pg_query( $this->mConn, "ROLLBACK TO $ignore" );
- }
- else {
+ } else {
pg_query( $this->mConn, "RELEASE $ignore" );
$numrowsinserted++;
}
@@ -835,12 +484,12 @@ class DatabasePostgres extends DatabaseBase {
// If any of them fail, we fail overall for this function call
// Note that this will be ignored if IGNORE is set
- if (! $tempres)
+ if ( !$tempres ) {
$res = false;
+ }
}
}
- }
- else {
+ } else {
// Not multi, just a lone insert
if ( $ignore ) {
pg_query($this->mConn, "SAVEPOINT $ignore");
@@ -850,10 +499,9 @@ class DatabasePostgres extends DatabaseBase {
$res = (bool)$this->query( $sql, $fname, $ignore );
if ( $ignore ) {
$bar = pg_last_error();
- if ($bar != false) {
+ if ( $bar != false ) {
pg_query( $this->mConn, "ROLLBACK TO $ignore" );
- }
- else {
+ } else {
pg_query( $this->mConn, "RELEASE $ignore" );
$numrowsinserted++;
}
@@ -861,7 +509,7 @@ class DatabasePostgres extends DatabaseBase {
}
if ( $ignore ) {
$olde = error_reporting( $olde );
- if ($didbegin) {
+ if ( $didbegin ) {
$this->commit();
}
@@ -872,9 +520,7 @@ class DatabasePostgres extends DatabaseBase {
return true;
}
-
return $res;
-
}
/**
@@ -884,7 +530,7 @@ class DatabasePostgres extends DatabaseBase {
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
* @todo FIXME: implement this a little better (seperate select/insert)?
- */
+ */
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect',
$insertOptions = array(), $selectOptions = array() )
{
@@ -922,7 +568,7 @@ class DatabasePostgres extends DatabaseBase {
" SELECT $startOpts " . implode( ',', $varMap ) .
" FROM $srcTable $useIndex";
- if ( $conds != '*') {
+ if ( $conds != '*' ) {
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
@@ -951,7 +597,7 @@ class DatabasePostgres extends DatabaseBase {
return $res;
}
-
+
function tableName( $name ) {
# Replace reserved words with better ones
switch( $name ) {
@@ -968,11 +614,10 @@ class DatabasePostgres extends DatabaseBase {
* Return the next in a sequence, save the value for retrieval via insertId()
*/
function nextSequenceValue( $seqName ) {
- $safeseq = preg_replace( "/'/", "''", $seqName );
+ $safeseq = str_replace( "'", "''", $seqName );
$res = $this->query( "SELECT nextval('$safeseq')" );
$row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
- $this->freeResult( $res );
return $this->mInsertId;
}
@@ -980,27 +625,28 @@ class DatabasePostgres extends DatabaseBase {
* Return the current value of a sequence. Assumes it has been nextval'ed in this session.
*/
function currentSequenceValue( $seqName ) {
- $safeseq = preg_replace( "/'/", "''", $seqName );
+ $safeseq = str_replace( "'", "''", $seqName );
$res = $this->query( "SELECT currval('$safeseq')" );
$row = $this->fetchRow( $res );
$currval = $row[0];
- $this->freeResult( $res );
return $currval;
}
- # REPLACE query wrapper
- # Postgres simulates this with a DELETE followed by INSERT
- # $row is the row to insert, an associative array
- # $uniqueIndexes is an array of indexes. Each element may be either a
- # field name or an array of field names
- #
- # It may be more efficient to leave off unique indexes which are unlikely to collide.
- # However if you do this, you run the risk of encountering errors which wouldn't have
- # occurred in MySQL
+ /**
+ * REPLACE query wrapper
+ * Postgres simulates this with a DELETE followed by INSERT
+ * $row is the row to insert, an associative array
+ * $uniqueIndexes is an array of indexes. Each element may be either a
+ * field name or an array of field names
+ *
+ * It may be more efficient to leave off unique indexes which are unlikely to collide.
+ * However if you do this, you run the risk of encountering errors which wouldn't have
+ * occurred in MySQL
+ */
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabasePostgres::replace' ) {
$table = $this->tableName( $table );
- if (count($rows)==0) {
+ if ( count( $rows ) == 0 ) {
return;
}
@@ -1017,7 +663,7 @@ class DatabasePostgres extends DatabaseBase {
foreach ( $uniqueIndexes as $index ) {
if ( $first ) {
$first = false;
- $sql .= "(";
+ $sql .= '(';
} else {
$sql .= ') OR (';
}
@@ -1049,7 +695,7 @@ class DatabasePostgres extends DatabaseBase {
# DELETE where the condition is a join
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabasePostgres::deleteJoin' ) {
if ( !$conds ) {
- throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabasePostgres::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
@@ -1070,19 +716,18 @@ class DatabasePostgres extends DatabaseBase {
FROM pg_class c, pg_attribute a, pg_type t
WHERE relname='$table' AND a.attrelid=c.oid AND
a.atttypid=t.oid and a.attname='$field'";
- $res =$this->query($sql);
- $row=$this->fetchObject($res);
- if ($row->ftype=="varchar") {
- $size=$row->size-4;
+ $res =$this->query( $sql );
+ $row = $this->fetchObject( $res );
+ if ( $row->ftype == 'varchar' ) {
+ $size = $row->size - 4;
} else {
- $size=$row->size;
+ $size = $row->size;
}
- $this->freeResult( $res );
return $size;
}
- function limitResult($sql, $limit, $offset=false) {
- return "$sql LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":"");
+ function limitResult( $sql, $limit, $offset = false ) {
+ return "$sql LIMIT $limit " . ( is_numeric( $offset ) ? " OFFSET {$offset} " : '' );
}
function wasDeadlock() {
@@ -1093,57 +738,40 @@ class DatabasePostgres extends DatabaseBase {
return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
}
- function timestamp( $ts=0 ) {
- return wfTimestamp(TS_POSTGRES,$ts);
+ function timestamp( $ts = 0 ) {
+ return wfTimestamp( TS_POSTGRES, $ts );
}
/**
* Return aggregated value function call
*/
- function aggregateValue ($valuedata,$valuename='value') {
+ function aggregateValue( $valuedata, $valuename = 'value' ) {
return $valuedata;
}
-
- function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
- // Ignore errors during error handling to avoid infinite recursion
- $ignore = $this->ignoreErrors( true );
- $this->mErrorCount++;
-
- if ($ignore || $tempIgnore) {
- wfDebug("SQL ERROR (ignored): $error\n");
- $this->ignoreErrors( $ignore );
- }
- else {
- $message = "A database error has occurred\n" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
- throw new DBUnexpectedError($this, $message);
- }
- }
-
/**
* @return string wikitext of a link to the server software's web site
*/
- function getSoftwareLink() {
- return "[http://www.postgresql.org/ PostgreSQL]";
+ public static function getSoftwareLink() {
+ return '[http://www.postgresql.org/ PostgreSQL]';
}
/**
* @return string Version information from the database
*/
function getServerVersion() {
- $versionInfo = pg_version( $this->mConn );
- if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
- // Old client, abort install
- $this->numeric_version = '7.3 or earlier';
- } elseif ( isset( $versionInfo['server'] ) ) {
- // Normal client
- $this->numeric_version = $versionInfo['server'];
- } else {
- // Bug 16937: broken pgsql extension from PHP<5.3
- $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
+ if ( !isset( $this->numeric_version ) ) {
+ $versionInfo = pg_version( $this->mConn );
+ if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
+ // Old client, abort install
+ $this->numeric_version = '7.3 or earlier';
+ } elseif ( isset( $versionInfo['server'] ) ) {
+ // Normal client
+ $this->numeric_version = $versionInfo['server'];
+ } else {
+ // Bug 16937: broken pgsql extension from PHP<5.3
+ $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' );
+ }
}
return $this->numeric_version;
}
@@ -1154,23 +782,23 @@ class DatabasePostgres extends DatabaseBase {
*/
function relationExists( $table, $types, $schema = false ) {
global $wgDBmwschema;
- if ( !is_array( $types ) )
+ if ( !is_array( $types ) ) {
$types = array( $types );
- if ( !$schema )
+ }
+ if ( !$schema ) {
$schema = $wgDBmwschema;
+ }
$etable = $this->addQuotes( $table );
$eschema = $this->addQuotes( $schema );
$SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
. "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
- . "AND c.relkind IN ('" . implode("','", $types) . "')";
+ . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
$res = $this->query( $SQL );
$count = $res ? $res->numRows() : 0;
- if ($res)
- $this->freeResult( $res );
- return $count ? true : false;
+ return (bool)$count;
}
- /*
+ /**
* For backward compatibility, this function checks both tables and
* views.
*/
@@ -1191,82 +819,71 @@ class DatabasePostgres extends DatabaseBase {
AND tgrelid=pg_class.oid
AND nspname=%s AND relname=%s AND tgname=%s
SQL;
- $res = $this->query(sprintf($q,
- $this->addQuotes($wgDBmwschema),
- $this->addQuotes($table),
- $this->addQuotes($trigger)));
- if (!$res)
+ $res = $this->query(
+ sprintf(
+ $q,
+ $this->addQuotes( $wgDBmwschema ),
+ $this->addQuotes( $table ),
+ $this->addQuotes( $trigger )
+ )
+ );
+ if ( !$res ) {
return null;
+ }
$rows = $res->numRows();
- $this->freeResult( $res );
return $rows;
}
function ruleExists( $table, $rule ) {
global $wgDBmwschema;
- $exists = $this->selectField("pg_rules", "rulename",
- array( "rulename" => $rule,
- "tablename" => $table,
- "schemaname" => $wgDBmwschema ) );
+ $exists = $this->selectField( 'pg_rules', 'rulename',
+ array(
+ 'rulename' => $rule,
+ 'tablename' => $table,
+ 'schemaname' => $wgDBmwschema
+ )
+ );
return $exists === $rule;
}
function constraintExists( $table, $constraint ) {
global $wgDBmwschema;
- $SQL = sprintf("SELECT 1 FROM information_schema.table_constraints ".
+ $SQL = sprintf( "SELECT 1 FROM information_schema.table_constraints ".
"WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
- $this->addQuotes($wgDBmwschema),
- $this->addQuotes($table),
- $this->addQuotes($constraint));
- $res = $this->query($SQL);
- if (!$res)
+ $this->addQuotes( $wgDBmwschema ),
+ $this->addQuotes( $table ),
+ $this->addQuotes( $constraint )
+ );
+ $res = $this->query( $SQL );
+ if ( !$res ) {
return null;
+ }
$rows = $res->numRows();
- $this->freeResult($res);
return $rows;
}
/**
- * Query whether a given schema exists. Returns the name of the owner
+ * Query whether a given schema exists. Returns true if it does, false if it doesn't.
*/
function schemaExists( $schema ) {
- $eschema = preg_replace("/'/", "''", $schema);
- $SQL = "SELECT rolname FROM pg_catalog.pg_namespace n, pg_catalog.pg_roles r "
- ."WHERE n.nspowner=r.oid AND n.nspname = '$eschema'";
- $res = $this->query( $SQL );
- if ( $res && $res->numRows() ) {
- $row = $res->fetchObject();
- $owner = $row->rolname;
- } else {
- $owner = false;
- }
- if ($res)
- $this->freeResult($res);
- return $owner;
+ $exists = $this->selectField( '"pg_catalog"."pg_namespace"', 1,
+ array( 'nspname' => $schema ), __METHOD__ );
+ return (bool)$exists;
}
/**
- * Query whether a given column exists in the mediawiki schema
+ * Returns true if a given role (i.e. user) exists, false otherwise.
*/
- function fieldExists( $table, $field, $fname = 'DatabasePostgres::fieldExists' ) {
- global $wgDBmwschema;
- $etable = preg_replace("/'/", "''", $table);
- $eschema = preg_replace("/'/", "''", $wgDBmwschema);
- $ecol = preg_replace("/'/", "''", $field);
- $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a "
- . "WHERE c.relnamespace = n.oid AND c.relname = '$etable' AND n.nspname = '$eschema' "
- . "AND a.attrelid = c.oid AND a.attname = '$ecol'";
- $res = $this->query( $SQL, $fname );
- $count = $res ? $res->numRows() : 0;
- if ($res)
- $this->freeResult( $res );
- return $count;
+ function roleExists( $roleName ) {
+ $exists = $this->selectField( '"pg_catalog"."pg_roles"', 1,
+ array( 'rolname' => $roleName ), __METHOD__ );
+ return (bool)$exists;
}
function fieldInfo( $table, $field ) {
- return PostgresField::fromText($this, $table, $field);
+ return PostgresField::fromText( $this, $table, $field );
}
-
+
/**
* pg_field_type() wrapper
*/
@@ -1277,119 +894,35 @@ SQL;
return pg_field_type( $res, $index );
}
- function begin( $fname = 'DatabasePostgres::begin' ) {
- $this->query( 'BEGIN', $fname );
- $this->mTrxLevel = 1;
- }
- function immediateCommit( $fname = 'DatabasePostgres::immediateCommit' ) {
- return true;
- }
- function commit( $fname = 'DatabasePostgres::commit' ) {
- $this->query( 'COMMIT', $fname );
- $this->mTrxLevel = 0;
- }
-
/* Not even sure why this is used in the main codebase... */
function limitResultForUpdate( $sql, $num ) {
return $sql;
}
- function setup_database() {
- global $wgVersion, $wgDBmwschema, $wgDBts2schema, $wgDBport, $wgDBuser;
-
- // Make sure that we can write to the correct schema
- // If not, Postgres will happily and silently go to the next search_path item
- $ctest = "mediawiki_test_table";
- $safeschema = $this->quote_ident($wgDBmwschema);
- if ($this->tableExists($ctest, $wgDBmwschema)) {
- $this->doQuery("DROP TABLE $safeschema.$ctest");
- }
- $SQL = "CREATE TABLE $safeschema.$ctest(a int)";
- $olde = error_reporting( 0 );
- $res = $this->doQuery($SQL);
- error_reporting( $olde );
- if (!$res) {
- print "<b>FAILED</b>. Make sure that the user \"" . htmlspecialchars( $wgDBuser ) .
- "\" can write to the schema \"" . htmlspecialchars( $wgDBmwschema ) . "\"</li>\n";
- dieout(""); # Will close the main list <ul> and finish the page.
- }
- $this->doQuery("DROP TABLE $safeschema.$ctest");
-
- $res = $this->sourceFile( "../maintenance/postgres/tables.sql" );
- if ($res === true) {
- print " done.</li>\n";
- } else {
- print " <b>FAILED</b></li>\n";
- dieout( htmlspecialchars( $res ) );
- }
-
- ## Update version information
- $mwv = $this->addQuotes($wgVersion);
- $pgv = $this->addQuotes($this->getServerVersion());
- $pgu = $this->addQuotes($this->mUser);
- $mws = $this->addQuotes($wgDBmwschema);
- $tss = $this->addQuotes($wgDBts2schema);
- $pgp = $this->addQuotes($wgDBport);
- $dbn = $this->addQuotes($this->mDBname);
- $ctype = $this->addQuotes( pg_fetch_result($this->doQuery("SHOW lc_ctype"),0,0) );
-
- $SQL = "UPDATE mediawiki_version SET mw_version=$mwv, pg_version=$pgv, pg_user=$pgu, ".
- "mw_schema = $mws, ts2_schema = $tss, pg_port=$pgp, pg_dbname=$dbn, ".
- "ctype = $ctype ".
- "WHERE type = 'Creation'";
- $this->query($SQL);
-
- echo "<li>Populating interwiki table... ";
-
- ## Avoid the non-standard "REPLACE INTO" syntax
- $f = fopen( "../maintenance/interwiki.sql", 'r' );
- if ($f == false ) {
- print "<b>FAILED</b></li>";
- dieout( "Could not find the interwiki.sql file" );
- }
- ## We simply assume it is already empty as we have just created it
- $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
- while ( ! feof( $f ) ) {
- $line = fgets($f,1024);
- $matches = array();
- if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) {
- continue;
- }
- $this->query("$SQL $matches[1],$matches[2])");
- }
- print " successfully populated.</li>\n";
-
- $this->doQuery("COMMIT");
- }
-
function encodeBlob( $b ) {
- return new Blob ( pg_escape_bytea( $b ) ) ;
+ return new Blob( pg_escape_bytea( $this->mConn, $b ) );
}
function decodeBlob( $b ) {
- if ($b instanceof Blob) {
+ if ( $b instanceof Blob ) {
$b = $b->fetch();
}
return pg_unescape_bytea( $b );
}
- function strencode( $s ) { ## Should not be called by us
- return pg_escape_string( $s );
+ function strencode( $s ) { # Should not be called by us
+ return pg_escape_string( $this->mConn, $s );
}
function addQuotes( $s ) {
if ( is_null( $s ) ) {
return 'NULL';
- } else if ( is_bool( $s ) ) {
+ } elseif ( is_bool( $s ) ) {
return intval( $s );
- } else if ($s instanceof Blob) {
- return "'".$s->fetch($s)."'";
+ } elseif ( $s instanceof Blob ) {
+ return "'" . $s->fetch( $s ) . "'";
}
- return "'" . pg_escape_string($s) . "'";
- }
-
- function quote_ident( $s ) {
- return '"' . preg_replace( '/"/', '""', $s) . '"';
+ return "'" . pg_escape_string( $this->mConn, $s ) . "'";
}
/**
@@ -1403,15 +936,14 @@ SQL;
* @return string SQL string
*/
protected function replaceVars( $ins ) {
-
$ins = parent::replaceVars( $ins );
- if ($this->numeric_version >= 8.3) {
+ if ( $this->numeric_version >= 8.3 ) {
// Thanks for not providing backwards-compatibility, 8.3
$ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
}
- if ($this->numeric_version <= 8.1) { // Our minimum version
+ if ( $this->numeric_version <= 8.1 ) { // Our minimum version
$ins = str_replace( 'USING gin', 'USING gist', $ins );
}
@@ -1438,33 +970,35 @@ SQL;
}
}
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY " . $options['GROUP BY'];
- if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
- if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY " . $options['ORDER BY'];
+ if ( isset( $options['GROUP BY'] ) ) {
+ $preLimitTail .= ' GROUP BY ' . $options['GROUP BY'];
+ }
+ if ( isset( $options['HAVING'] ) ) {
+ $preLimitTail .= " HAVING {$options['HAVING']}";
+ }
+ if ( isset( $options['ORDER BY'] ) ) {
+ $preLimitTail .= ' ORDER BY ' . $options['ORDER BY'];
+ }
- //if (isset($options['LIMIT'])) {
- // $tailOpts .= $this->limitResult('', $options['LIMIT'],
- // isset($options['OFFSET']) ? $options['OFFSET']
- // : false);
+ //if ( isset( $options['LIMIT'] ) ) {
+ // $tailOpts .= $this->limitResult( '', $options['LIMIT'],
+ // isset( $options['OFFSET'] ) ? $options['OFFSET']
+ // : false );
//}
- if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
- if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
+ if ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
+ $postLimitTail .= ' FOR UPDATE';
+ }
+ if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) {
+ $postLimitTail .= ' LOCK IN SHARE MODE';
+ }
+ if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+ $startOpts .= 'DISTINCT';
+ }
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
- /**
- * How lagged is this slave?
- *
- */
- public function getLag() {
- # Not implemented for PostgreSQL
- return false;
- }
-
- function setFakeSlaveLag( $lag ) {}
function setFakeMaster( $enabled = true ) {}
function getDBname() {
@@ -1480,6 +1014,6 @@ SQL;
}
public function getSearchEngine() {
- return "SearchPostgres";
+ return 'SearchPostgres';
}
} // end DatabasePostgres class
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index c149cf04..503ebdf6 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -1,10 +1,10 @@
<?php
/**
- * This script is the SQLite database abstraction layer
- *
+ * This is the SQLite database abstraction layer.
* See maintenance/sqlite/README for development notes and other specific information
- * @ingroup Database
+ *
* @file
+ * @ingroup Database
*/
/**
@@ -12,6 +12,8 @@
*/
class DatabaseSqlite extends DatabaseBase {
+ private static $fulltextEnabled = null;
+
var $mAffectedRows;
var $mLastResult;
var $mDatabaseFile;
@@ -21,11 +23,16 @@ class DatabaseSqlite extends DatabaseBase {
* Constructor.
* Parameters $server, $user and $password are not used.
*/
- function __construct( $server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0 ) {
- $this->mFailFunction = $failFunction;
- $this->mFlags = $flags;
+ function __construct( $server = false, $user = false, $password = false, $dbName = false, $flags = 0 ) {
$this->mName = $dbName;
- $this->open( $server, $user, $password, $dbName );
+ parent::__construct( $server, $user, $password, $dbName, $flags );
+ // parent doesn't open when $user is false, but we can work with $dbName
+ if( !$user && $dbName ) {
+ global $wgSharedDB;
+ if( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) {
+ $this->attachDatabase( $wgSharedDB );
+ }
+ }
}
function getType() {
@@ -37,8 +44,8 @@ class DatabaseSqlite extends DatabaseBase {
*/
function implicitGroupby() { return false; }
- static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) {
- return new DatabaseSqlite( $server, $user, $password, $dbName, $failFunction, $flags );
+ static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
+ return new DatabaseSqlite( $server, $user, $password, $dbName, $flags );
}
/** Open an SQLite database and return a resource handle to it
@@ -49,7 +56,8 @@ class DatabaseSqlite extends DatabaseBase {
$fileName = self::generateFileName( $wgSQLiteDataDir, $dbName );
if ( !is_readable( $fileName ) ) {
- throw new DBConnectionError( $this, "SQLite database not accessible" ); $this->mConn = false;
+ $this->mConn = false;
+ throw new DBConnectionError( $this, "SQLite database not accessible" );
}
$this->openFile( $fileName );
return $this->mConn;
@@ -71,14 +79,9 @@ class DatabaseSqlite extends DatabaseBase {
} catch ( PDOException $e ) {
$err = $e->getMessage();
}
- if ( $this->mConn === false ) {
+ if ( !$this->mConn ) {
wfDebug( "DB connection error: $err\n" );
- if ( !$this->mFailFunction ) {
- throw new DBConnectionError( $this, $err );
- } else {
- return false;
- }
-
+ throw new DBConnectionError( $this, $err );
}
$this->mOpened = !!$this->mConn;
# set error codes only, don't raise exceptions
@@ -111,17 +114,64 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
+ * Check if the searchindext table is FTS enabled.
+ * @returns false if not enabled.
+ */
+ function checkForEnabledSearch() {
+ if ( self::$fulltextEnabled === null ) {
+ self::$fulltextEnabled = false;
+ $table = $this->tableName( 'searchindex' );
+ $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
+ if ( $res ) {
+ $row = $res->fetchRow();
+ self::$fulltextEnabled = stristr($row['sql'], 'fts' ) !== false;
+ }
+ }
+ return self::$fulltextEnabled;
+ }
+
+ /**
* Returns version of currently supported SQLite fulltext search module or false if none present.
* @return String
*/
- function getFulltextSearchModule() {
+ static function getFulltextSearchModule() {
+ static $cachedResult = null;
+ if ( $cachedResult !== null ) {
+ return $cachedResult;
+ }
+ $cachedResult = false;
$table = 'dummy_search_test';
- $this->query( "DROP TABLE IF EXISTS $table", __METHOD__ );
- if ( $this->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
- $this->query( "DROP TABLE IF EXISTS $table", __METHOD__ );
- return 'FTS3';
+
+ $db = new DatabaseSqliteStandalone( ':memory:' );
+
+ if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
+ $cachedResult = 'FTS3';
}
- return false;
+ $db->close();
+ return $cachedResult;
+ }
+
+ /**
+ * Attaches external database to our connection, see http://sqlite.org/lang_attach.html
+ * for details.
+ * @param $name String: database name to be used in queries like SELECT foo FROM dbname.table
+ * @param $file String: database file name. If omitted, will be generated using $name and $wgSQLiteDataDir
+ * @param $fname String: calling function name
+ */
+ function attachDatabase( $name, $file = false, $fname = 'DatabaseSqlite::attachDatabase' ) {
+ global $wgSQLiteDataDir;
+ if ( !$file ) {
+ $file = self::generateFileName( $wgSQLiteDataDir, $name );
+ }
+ $file = $this->addQuotes( $file );
+ return $this->query( "ATTACH DATABASE $file AS $name", $fname );
+ }
+
+ /**
+ * @see DatabaseBase::isWriteQuery()
+ */
+ function isWriteQuery( $sql ) {
+ return parent::isWriteQuery( $sql ) && !preg_match( '/^ATTACH\b/i', $sql );
}
/**
@@ -140,25 +190,29 @@ class DatabaseSqlite extends DatabaseBase {
}
function freeResult( $res ) {
- if ( $res instanceof ResultWrapper )
+ if ( $res instanceof ResultWrapper ) {
$res->result = null;
- else
+ } else {
$res = null;
+ }
}
function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper )
+ if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
- else
+ } else {
$r =& $res;
+ }
$cur = current( $r );
if ( is_array( $cur ) ) {
next( $r );
$obj = new stdClass;
- foreach ( $cur as $k => $v )
- if ( !is_numeric( $k ) )
+ foreach ( $cur as $k => $v ) {
+ if ( !is_numeric( $k ) ) {
$obj->$k = $v;
+ }
+ }
return $obj;
}
@@ -166,11 +220,11 @@ class DatabaseSqlite extends DatabaseBase {
}
function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper )
+ if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
- else
+ } else {
$r =& $res;
-
+ }
$cur = current( $r );
if ( is_array( $cur ) ) {
next( $r );
@@ -205,6 +259,8 @@ class DatabaseSqlite extends DatabaseBase {
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
*/
function tableName( $name ) {
+ // table names starting with sqlite_ are reserved
+ if ( strpos( $name, 'sqlite_' ) === 0 ) return $name;
return str_replace( '`', '', parent::tableName( $name ) );
}
@@ -223,19 +279,23 @@ class DatabaseSqlite extends DatabaseBase {
}
function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper )
+ if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
- else
+ } else {
$r =& $res;
+ }
reset( $r );
- if ( $row > 0 )
- for ( $i = 0; $i < $row; $i++ )
+ if ( $row > 0 ) {
+ for ( $i = 0; $i < $row; $i++ ) {
next( $r );
+ }
+ }
}
function lastError() {
- if ( !is_object( $this->mConn ) )
+ if ( !is_object( $this->mConn ) ) {
return "Cannot return last error, no db connection";
+ }
$e = $this->mConn->errorInfo();
return isset( $e[2] ) ? $e[2] : '';
}
@@ -298,9 +358,11 @@ class DatabaseSqlite extends DatabaseBase {
* Filter the options used in SELECT statements
*/
function makeSelectOptions( $options ) {
- foreach ( $options as $k => $v )
- if ( is_numeric( $k ) && $v == 'FOR UPDATE' )
+ foreach ( $options as $k => $v ) {
+ if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) {
$options[$k] = '';
+ }
+ }
return parent::makeSelectOptions( $options );
}
@@ -308,20 +370,28 @@ class DatabaseSqlite extends DatabaseBase {
* Based on generic method (parent) with some prior SQLite-sepcific adjustments
*/
function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) {
- if ( !count( $a ) ) return true;
- if ( !is_array( $options ) ) $options = array( $options );
+ if ( !count( $a ) ) {
+ return true;
+ }
+ if ( !is_array( $options ) ) {
+ $options = array( $options );
+ }
# SQLite uses OR IGNORE not just IGNORE
- foreach ( $options as $k => $v )
- if ( $v == 'IGNORE' )
+ foreach ( $options as $k => $v ) {
+ if ( $v == 'IGNORE' ) {
$options[$k] = 'OR IGNORE';
+ }
+ }
# SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
$ret = true;
- foreach ( $a as $k => $v )
- if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) )
+ foreach ( $a as $v ) {
+ if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) {
$ret = false;
+ }
+ }
} else {
$ret = parent::insert( $table, $a, "$fname/single-row", $options );
}
@@ -331,13 +401,15 @@ class DatabaseSqlite extends DatabaseBase {
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseSqlite::replace' ) {
if ( !count( $rows ) ) return true;
-
+
# SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
$ret = true;
- foreach ( $rows as $k => $v )
- if ( !parent::replace( $table, $uniqueIndexes, $v, "$fname/multi-row" ) )
+ foreach ( $rows as $v ) {
+ if ( !parent::replace( $table, $uniqueIndexes, $v, "$fname/multi-row" ) ) {
$ret = false;
+ }
+ }
} else {
$ret = parent::replace( $table, $uniqueIndexes, $rows, "$fname/single-row" );
}
@@ -362,6 +434,10 @@ class DatabaseSqlite extends DatabaseBase {
return implode( $glue, $sqls );
}
+ public function unixTimestamp( $field ) {
+ return $field;
+ }
+
function wasDeadlock() {
return $this->lastErrno() == 5; // SQLITE_BUSY
}
@@ -377,7 +453,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @return string wikitext of a link to the server software's web site
*/
- function getSoftwareLink() {
+ public static function getSoftwareLink() {
return "[http://sqlite.org/ SQLite]";
}
@@ -390,11 +466,10 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * Query whether a given column exists in the mediawiki schema
+ * @return string User-friendly database information
*/
- function fieldExists( $table, $field, $fname = '' ) {
- $info = $this->fieldInfo( $table, $field );
- return (bool)$info;
+ public function getServerInfo() {
+ return wfMsg( self::getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() );
}
/**
@@ -458,10 +533,6 @@ class DatabaseSqlite extends DatabaseBase {
}
}
- function quote_ident( $s ) {
- return $s;
- }
-
function buildLike() {
$params = func_get_args();
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
@@ -470,43 +541,6 @@ class DatabaseSqlite extends DatabaseBase {
return parent::buildLike( $params ) . "ESCAPE '\' ";
}
- /**
- * How lagged is this slave?
- */
- public function getLag() {
- return 0;
- }
-
- /**
- * Called by the installer script (when modified according to the MediaWikiLite installation instructions)
- * - this is the same way PostgreSQL works, MySQL reads in tables.sql and interwiki.sql using dbsource (which calls db->sourceFile)
- */
- public function setup_database() {
- global $IP;
-
- # Process common MySQL/SQLite table definitions
- $err = $this->sourceFile( "$IP/maintenance/tables.sql" );
- if ( $err !== true ) {
- echo " <b>FAILED</b></li>";
- dieout( htmlspecialchars( $err ) );
- }
- echo " done.</li>";
-
- # Use DatabasePostgres's code to populate interwiki from MySQL template
- $f = fopen( "$IP/maintenance/interwiki.sql", 'r' );
- if ( $f == false ) {
- dieout( "Could not find the interwiki.sql file." );
- }
-
- $sql = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
- while ( !feof( $f ) ) {
- $line = fgets( $f, 1024 );
- $matches = array();
- if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) continue;
- $this->query( "$sql $matches[1],$matches[2])" );
- }
- }
-
public function getSearchEngine() {
return "SearchSqlite";
}
@@ -530,9 +564,11 @@ class DatabaseSqlite extends DatabaseBase {
// no such thing as unsigned
$s = preg_replace( '/\b(un)?signed\b/i', '', $s );
// INT -> INTEGER
- $s = preg_replace( '/\b(tiny|small|medium|big|)int(\([\s\d]*\)|\b)/i', 'INTEGER', $s );
+ $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
+ // floating point types -> REAL
+ $s = preg_replace( '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i', 'REAL', $s );
// varchar -> TEXT
- $s = preg_replace( '/\bvarchar\(\d+\)/i', 'TEXT', $s );
+ $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
// TEXT normalization
$s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
// BLOB normalization
@@ -542,13 +578,15 @@ class DatabaseSqlite extends DatabaseBase {
// DATETIME -> TEXT
$s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
// No ENUM type
- $s = preg_replace( '/enum\([^)]*\)/i', 'BLOB', $s );
+ $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
// binary collation type -> nothing
$s = preg_replace( '/\bbinary\b/i', '', $s );
// auto_increment -> autoincrement
$s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
// No explicit options
$s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
+ // AUTOINCREMENT should immedidately follow PRIMARY KEY
+ $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
} elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
// No truncated indexes
$s = preg_replace( '/\(\d+\)/', '', $s );
@@ -585,6 +623,7 @@ class DatabaseSqlite extends DatabaseBase {
class DatabaseSqliteStandalone extends DatabaseSqlite {
public function __construct( $fileName, $flags = 0 ) {
$this->mFlags = $flags;
+ $this->tablePrefix( null );
$this->openFile( $fileName );
}
}
@@ -592,7 +631,7 @@ class DatabaseSqliteStandalone extends DatabaseSqlite {
/**
* @ingroup Database
*/
-class SQLiteField {
+class SQLiteField implements Field {
private $info, $tableName;
function __construct( $info, $tableName ) {
$this->info = $info;
@@ -617,18 +656,10 @@ class SQLiteField {
return $this->info->dflt_value;
}
- function maxLength() {
- return -1;
+ function isNullable() {
+ return !$this->info->notnull;
}
- function nullable() {
- // SQLite dynamic types are always nullable
- return true;
- }
-
- # isKey(), isMultipleKey() not implemented, MySQL-specific concept.
- # Suggest removal from base class [TS]
-
function type() {
return $this->info->type;
}
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index 10c87133..f84a70e5 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Generator of database load balancing objects
+ *
* @file
* @ingroup Database
*/
@@ -12,6 +14,23 @@ abstract class LBFactory {
static $instance;
/**
+ * Disables all access to the load balancer, will cause all database access
+ * to throw a DBAccessError
+ */
+ public static function disableBackend() {
+ global $wgLBFactoryConf;
+ self::$instance = new LBFactory_Fake( $wgLBFactoryConf );
+ }
+
+ /**
+ * Resets the singleton for use if it's been disabled. Does nothing otherwise
+ */
+ public static function enableBackend() {
+ if( self::$instance instanceof LBFactory_Fake )
+ self::$instance = null;
+ }
+
+ /**
* Get an LBFactory instance
*/
static function &singleton() {
@@ -36,6 +55,14 @@ abstract class LBFactory {
}
/**
+ * Set the instance to be the given object
+ */
+ static function setInstance( $instance ) {
+ self::destroyInstance();
+ self::$instance = $instance;
+ }
+
+ /**
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
*/
abstract function __construct( $conf );
@@ -44,7 +71,7 @@ abstract class LBFactory {
* Create a new load balancer object. The resulting object will be untracked,
* not chronology-protected, and the caller is responsible for cleaning it up.
*
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $wiki String: wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function newMainLB( $wiki = false );
@@ -52,7 +79,7 @@ abstract class LBFactory {
/**
* Get a cached (tracked) load balancer object.
*
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $wiki String: wiki ID, or false for the current wiki
* @return LoadBalancer
*/
abstract function getMainLB( $wiki = false );
@@ -62,16 +89,16 @@ abstract class LBFactory {
* untracked, not chronology-protected, and the caller is responsible for
* cleaning it up.
*
- * @param string $cluster External storage cluster, or false for core
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $cluster String: external storage cluster, or false for core
+ * @param $wiki String: wiki ID, or false for the current wiki
*/
abstract function newExternalLB( $cluster, $wiki = false );
/*
* Get a cached (tracked) load balancer for external storage
*
- * @param string $cluster External storage cluster, or false for core
- * @param string $wiki Wiki ID, or false for the current wiki
+ * @param $cluster String: external storage cluster, or false for core
+ * @param $wiki String: wiki ID, or false for the current wiki
*/
abstract function &getExternalLB( $cluster, $wiki = false );
@@ -198,6 +225,39 @@ class LBFactory_Simple extends LBFactory {
}
/**
+ * LBFactory class that throws an error on any attempt to use it.
+ * This will typically be done via wfGetDB().
+ * Call LBFactory::disableBackend() to start using this, and
+ * LBFactory::enableBackend() to return to normal behavior
+ */
+class LBFactory_Fake extends LBFactory {
+ function __construct( $conf ) {}
+
+ function newMainLB( $wiki = false) {
+ throw new DBAccessError;
+ }
+ function getMainLB( $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function newExternalLB( $cluster, $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function &getExternalLB( $cluster, $wiki = false ) {
+ throw new DBAccessError;
+ }
+ function forEachLB( $callback, $params = array() ) {}
+}
+
+/**
+ * Exception class for attempted DB access
+ */
+class DBAccessError extends MWException {
+ function __construct() {
+ parent::__construct( "Mediawiki tried to access the database via wfGetDB(). This is not allowed." );
+ }
+}
+
+/**
* Class for ensuring a consistent ordering of events as seen by the user, despite replication.
* Kind of like Hawking's [[Chronology Protection Agency]].
*/
@@ -208,7 +268,7 @@ class ChronologyProtector {
/**
* Initialise a LoadBalancer to give it appropriate chronology protection.
*
- * @param LoadBalancer $lb
+ * @param $lb LoadBalancer
*/
function initLB( $lb ) {
if ( $this->startupPos === null ) {
@@ -233,7 +293,7 @@ class ChronologyProtector {
* Notify the ChronologyProtector that the LoadBalancer is about to shut
* down. Saves replication positions.
*
- * @param LoadBalancer $lb
+ * @param $lb LoadBalancer
*/
function shutdownLB( $lb ) {
// Don't start a session, don't bother with non-replicated setups
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactory_Multi.php
index 820aa2ea..0d411ec6 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactory_Multi.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Advanced generator of database load balancing objects for wiki farms
+ *
* @file
* @ingroup Database
*/
@@ -85,7 +87,7 @@ class LBFactory_Multi extends LBFactory {
if ( $this->lastWiki === $wiki ) {
return $this->lastSection;
}
- list( $dbName, $prefix ) = $this->getDBNameAndPrefix( $wiki );
+ list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
if ( isset( $this->sectionsByDB[$dbName] ) ) {
$section = $this->sectionsByDB[$dbName];
} else {
@@ -97,7 +99,7 @@ class LBFactory_Multi extends LBFactory {
}
function newMainLB( $wiki = false ) {
- list( $dbName, $prefix ) = $this->getDBNameAndPrefix( $wiki );
+ list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
$section = $this->getSectionForWiki( $wiki );
$groupLoads = array();
if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactory_Single.php
new file mode 100644
index 00000000..25acdc5b
--- /dev/null
+++ b/includes/db/LBFactory_Single.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * An LBFactory class that always returns a single database object.
+ */
+class LBFactory_Single extends LBFactory {
+ protected $lb;
+
+ /**
+ * @param $conf An associative array with one member:
+ * - connection: The DatabaseBase connection object
+ */
+ function __construct( $conf ) {
+ $this->lb = new LoadBalancer_Single( $conf );
+ }
+
+ function newMainLB( $wiki = false ) {
+ return $this->lb;
+ }
+
+ function getMainLB( $wiki = false ) {
+ return $this->lb;
+ }
+
+ function newExternalLB( $cluster, $wiki = false ) {
+ return $this->lb;
+ }
+
+ function &getExternalLB( $cluster, $wiki = false ) {
+ return $this->lb;
+ }
+
+ function forEachLB( $callback, $params = array() ) {
+ call_user_func_array( $callback, array_merge( array( $this->lb ), $params ) );
+ }
+}
+
+/**
+ * Helper class for LBFactory_Single.
+ */
+class LoadBalancer_Single extends LoadBalancer {
+ var $db;
+
+ function __construct( $params ) {
+ $this->db = $params['connection'];
+ parent::__construct( array( 'servers' => array( array(
+ 'type' => $this->db->getType(),
+ 'host' => $this->db->getServer(),
+ 'dbname' => $this->db->getDBname(),
+ 'load' => 1,
+ ) ) ) );
+ }
+
+ function reallyOpenConnection( $server, $dbNameOverride = false ) {
+ return $this->db;
+ }
+}
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index 083b70b3..d899ce07 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -1,5 +1,7 @@
<?php
/**
+ * Database load balancing
+ *
* @file
* @ingroup Database
*/
@@ -12,7 +14,7 @@
*/
class LoadBalancer {
/* private */ var $mServers, $mConns, $mLoads, $mGroupLoads;
- /* private */ var $mFailFunction, $mErrorConnection;
+ /* private */ var $mErrorConnection;
/* private */ var $mReadIndex, $mAllowLagged;
/* private */ var $mWaitForPos, $mWaitTimeout;
/* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error';
@@ -20,9 +22,8 @@ class LoadBalancer {
/* private */ var $mLoadMonitorClass, $mLoadMonitor;
/**
- * @param array $params Array with keys:
+ * @param $params Array with keys:
* servers Required. Array of server info structures.
- * failFunction Deprecated, use exceptions instead.
* masterWaitTimeout Replication lag wait timeout
* loadMonitor Name of a class used to fetch server lag and load.
*/
@@ -33,11 +34,6 @@ class LoadBalancer {
}
$this->mServers = $params['servers'];
- if ( isset( $params['failFunction'] ) ) {
- $this->mFailFunction = $params['failFunction'];
- } else {
- $this->mFailFunction = false;
- }
if ( isset( $params['waitTimeout'] ) ) {
$this->mWaitTimeout = $params['waitTimeout'];
} else {
@@ -54,7 +50,7 @@ class LoadBalancer {
$this->mWaitForPos = false;
$this->mLaggedSlaveMode = false;
$this->mErrorConnection = false;
- $this->mAllowLag = false;
+ $this->mAllowLagged = false;
$this->mLoadMonitorClass = isset( $params['loadMonitor'] )
? $params['loadMonitor'] : 'LoadMonitor_MySQL';
@@ -71,11 +67,6 @@ class LoadBalancer {
}
}
- static function newFromParams( $servers, $failFunction = false, $waitTimeout = 10 )
- {
- return new LoadBalancer( $servers, $failFunction, $waitTimeout );
- }
-
/**
* Get a LoadMonitor instance
*/
@@ -129,11 +120,11 @@ class LoadBalancer {
# Unset excessively lagged servers
$lags = $this->getLagTimes( $wiki );
foreach ( $lags as $i => $lag ) {
- if ( $i != 0 && isset( $this->mServers[$i]['max lag'] ) ) {
+ if ( $i != 0 ) {
if ( $lag === false ) {
wfDebug( "Server #$i is not replicating\n" );
unset( $loads[$i] );
- } elseif ( $lag > $this->mServers[$i]['max lag'] ) {
+ } elseif ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
wfDebug( "Server #$i is excessively lagged ($lag seconds)\n" );
unset( $loads[$i] );
}
@@ -214,8 +205,6 @@ class LoadBalancer {
# Scale the configured load ratios according to the dynamic load (if the load monitor supports it)
$this->getLoadMonitor()->scaleLoads( $nonErrorLoads, $group, $wiki );
- $i = false;
- $found = false;
$laggedSlaveMode = false;
# First try quickly looking through the available servers for a server that
@@ -231,7 +220,8 @@ class LoadBalancer {
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
if ( $i === false && count( $currentLoads ) != 0 ) {
# All slaves lagged. Switch to read-only mode
- $wgReadOnly = wfMsgNoDBForContent( 'readonly_lag' );
+ $wgReadOnly = 'The database has been automatically locked ' .
+ 'while the slave database servers catch up to the master';
$i = $this->pickRandom( $currentLoads );
$laggedSlaveMode = true;
}
@@ -332,14 +322,6 @@ class LoadBalancer {
}
/**
- * Get a random server to use in a query group
- * @deprecated use getReaderIndex
- */
- function getGroupIndex( $group ) {
- return $this->getReaderIndex( $group );
- }
-
- /**
* Set the master wait position
* If a DB_SLAVE connection has been opened already, waits
* Otherwise sets a variable telling it to wait if such a connection is opened
@@ -357,13 +339,25 @@ class LoadBalancer {
}
wfProfileOut( __METHOD__ );
}
+
+ /**
+ * Set the master wait position and wait for ALL slaves to catch up to it
+ */
+ public function waitForAll( $pos ) {
+ wfProfileIn( __METHOD__ );
+ $this->mWaitForPos = $pos;
+ for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
+ $this->doWait( $i );
+ }
+ wfProfileOut( __METHOD__ );
+ }
/**
* Get any open connection to a given server index, local or foreign
* Returns false if there is no connection open
*/
function getAnyOpenConnection( $i ) {
- foreach ( $this->mConns as $type => $conns ) {
+ foreach ( $this->mConns as $conns ) {
if ( !empty( $conns[$i] ) ) {
return reset( $conns[$i] );
}
@@ -398,12 +392,14 @@ class LoadBalancer {
/**
* Get a connection by index
* This is the main entry point for this class.
- * @param int $i Database
- * @param array $groups Query groups
- * @param string $wiki Wiki ID
+ *
+ * @param $i Integer: server index
+ * @param $groups Array: query groups
+ * @param $wiki String: wiki ID
+ *
+ * @return DatabaseBase
*/
public function &getConnection( $i, $groups = array(), $wiki = false ) {
- global $wgDBtype;
wfProfileIn( __METHOD__ );
if ( $i == DB_LAST ) {
@@ -445,6 +441,7 @@ class LoadBalancer {
if ( $i === false ) {
$this->mLastError = 'No working slave server: ' . $this->mLastError;
$this->reportConnectionError( $this->mErrorConnection );
+ wfProfileOut( __METHOD__ );
return false;
}
}
@@ -509,9 +506,9 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param integer $i Server index
- * @param string $wiki Wiki ID to open
- * @return Database
+ * @param $i Integer: server index
+ * @param $wiki String: wiki ID to open
+ * @return DatabaseBase
*
* @access private
*/
@@ -554,9 +551,9 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param integer $i Server index
- * @param string $wiki Wiki ID to open
- * @return Database
+ * @param $i Integer: server index
+ * @param $wiki String: wiki ID to open
+ * @return DatabaseBase
*/
function openForeignConnection( $i, $wiki ) {
wfProfileIn(__METHOD__);
@@ -615,6 +612,8 @@ class LoadBalancer {
/**
* Test if the specified index represents an open connection
+ *
+ * @param $index Integer: server index
* @access private
*/
function isOpen( $index ) {
@@ -634,21 +633,26 @@ class LoadBalancer {
throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' );
}
- extract( $server );
+ $host = $server['host'];
+ $dbname = $server['dbname'];
+
if ( $dbNameOverride !== false ) {
- $dbname = $dbNameOverride;
+ $server['dbname'] = $dbname = $dbNameOverride;
}
- # Get class for this database type
- $class = 'Database' . ucfirst( $type );
-
# Create object
wfDebug( "Connecting to $host $dbname...\n" );
- $db = new $class( $host, $user, $password, $dbname, 1, $flags );
+ try {
+ $db = DatabaseBase::newFromType( $server['type'], $server );
+ } catch ( DBConnectionError $e ) {
+ // FIXME: This is probably the ugliest thing I have ever done to
+ // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS
+ $db = $e->db;
+ }
if ( $db->isOpen() ) {
- wfDebug( "Connected\n" );
+ wfDebug( "Connected to $host $dbname.\n" );
} else {
- wfDebug( "Failed\n" );
+ wfDebug( "Connection failed to $host $dbname.\n" );
}
$db->setLBInfo( $server );
if ( isset( $server['fakeSlaveLag'] ) ) {
@@ -667,19 +671,9 @@ class LoadBalancer {
// No last connection, probably due to all servers being too busy
wfLogDBError( "LB failure with no last connection\n" );
$conn = new Database;
- if ( $this->mFailFunction ) {
- $conn->failFunction( $this->mFailFunction );
- $conn->reportConnectionError( $this->mLastError );
- } else {
- // If all servers were busy, mLastError will contain something sensible
- throw new DBConnectionError( $conn, $this->mLastError );
- }
+ // If all servers were busy, mLastError will contain something sensible
+ throw new DBConnectionError( $conn, $this->mLastError );
} else {
- if ( $this->mFailFunction ) {
- $conn->failFunction( $this->mFailFunction );
- } else {
- $conn->failFunction( false );
- }
$server = $conn->getProperty( 'mServer' );
wfLogDBError( "Connection error: {$this->mLastError} ({$server})\n" );
$conn->reportConnectionError( "{$this->mLastError} ({$server})" );
@@ -779,11 +773,20 @@ class LoadBalancer {
}
/**
+ * Deprecated function, typo in function name
+ */
+ function closeConnecton( $conn ) {
+ $this->closeConnection( $conn );
+ }
+
+ /**
* Close a connection
* Using this function makes sure the LoadBalancer knows the connection is closed.
* If you use $conn->close() directly, the load balancer won't update its state.
+ * @param $conn
+ * @return void
*/
- function closeConnecton( $conn ) {
+ function closeConnection( $conn ) {
$done = false;
foreach ( $this->mConns as $i1 => $conns2 ) {
foreach ( $conns2 as $i2 => $conns3 ) {
@@ -819,7 +822,7 @@ class LoadBalancer {
function commitMasterChanges() {
// Always 0, but who knows.. :)
$masterIndex = $this->getWriterIndex();
- foreach ( $this->mConns as $type => $conns2 ) {
+ foreach ( $this->mConns as $conns2 ) {
if ( empty( $conns2[$masterIndex] ) ) {
continue;
}
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 929ab2b9..9b959728 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -1,9 +1,16 @@
<?php
+/**
+ * Database load monitoring
+ *
+ * @file
+ * @ingroup Database
+ */
/**
* An interface for database load monitoring
+ *
+ * @ingroup Database
*/
-
interface LoadMonitor {
/**
* Construct a new LoadMonitor with a given LoadBalancer parent
@@ -12,9 +19,9 @@ interface LoadMonitor {
/**
* Perform pre-connection load ratio adjustment.
- * @param array $loads
- * @param string $group The selected query group
- * @param string $wiki
+ * @param $loads Array
+ * @param $group String: the selected query group
+ * @param $wiki String
*/
function scaleLoads( &$loads, $group = false, $wiki = false );
@@ -31,8 +38,8 @@ interface LoadMonitor {
* to the running thread count. The threshold may be false, which indicates
* that the sysadmin has not configured this feature.
*
- * @param Database $conn
- * @param float $threshold
+ * @param $conn DatabaseBase
+ * @param $threshold Float
*/
function postConnectionBackoff( $conn, $threshold );
@@ -46,8 +53,9 @@ interface LoadMonitor {
/**
* Basic MySQL load monitor with no external dependencies
* Uses memcached to cache the replication lag for a short time
+ *
+ * @ingroup Database
*/
-
class LoadMonitor_MySQL implements LoadMonitor {
var $parent; // LoadBalancer