summaryrefslogtreecommitdiff
path: root/includes/db
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2010-07-28 11:52:48 +0200
committerPierre Schmitz <pierre@archlinux.de>2010-07-28 11:52:48 +0200
commit222b01f5169f1c7e69762e0e8904c24f78f71882 (patch)
tree8e932e12546bb991357ec48eb1638d1770be7a35 /includes/db
parent00ab76a6b686e98a914afc1975812d2b1aaa7016 (diff)
update to MediaWiki 1.16.0
Diffstat (limited to 'includes/db')
-rw-r--r--includes/db/Database.php812
-rw-r--r--includes/db/DatabaseIbm_db2.php908
-rw-r--r--includes/db/DatabaseMssql.php86
-rw-r--r--includes/db/DatabaseMysql.php453
-rw-r--r--includes/db/DatabaseOracle.php1049
-rw-r--r--includes/db/DatabasePostgres.php184
-rw-r--r--includes/db/DatabaseSqlite.php432
-rw-r--r--includes/db/LBFactory.php14
-rw-r--r--includes/db/LoadBalancer.php21
9 files changed, 2511 insertions, 1448 deletions
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 52a59c11..ea5d77da 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -19,7 +19,7 @@ define( 'DEADLOCK_DELAY_MAX', 1500000 );
* Database abstraction object
* @ingroup Database
*/
-class Database {
+abstract class DatabaseBase {
#------------------------------------------------------------------------------
# Variables
@@ -39,6 +39,7 @@ class Database {
protected $mErrorCount = 0;
protected $mLBInfo = array();
protected $mFakeSlaveLag = null, $mFakeMaster = false;
+ protected $mDefaultBigSelects = null;
#------------------------------------------------------------------------------
# Accessors
@@ -49,7 +50,7 @@ class Database {
* Fail function, takes a Database as a parameter
* Set to false for default, 1 for ignore errors
*/
- function failFunction( $function = NULL ) {
+ function failFunction( $function = null ) {
return wfSetVar( $this->mFailFunction, $function );
}
@@ -64,7 +65,7 @@ class Database {
/**
* Boolean, controls output of large amounts of debug information
*/
- function debug( $debug = NULL ) {
+ function debug( $debug = null ) {
return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
}
@@ -72,7 +73,7 @@ class Database {
* Turns buffering of SQL result sets on (true) or off (false).
* Default is "on" and it should not be changed without good reasons.
*/
- function bufferResults( $buffer = NULL ) {
+ function bufferResults( $buffer = null ) {
if ( is_null( $buffer ) ) {
return !(bool)( $this->mFlags & DBO_NOBUFFER );
} else {
@@ -87,7 +88,7 @@ class Database {
* code should use lastErrno() and lastError() to handle the
* situation as appropriate.
*/
- function ignoreErrors( $ignoreErrors = NULL ) {
+ function ignoreErrors( $ignoreErrors = null ) {
return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
}
@@ -95,14 +96,14 @@ class Database {
* The current depth of nested transactions
* @param $level Integer: , default NULL.
*/
- function trxLevel( $level = NULL ) {
+ function trxLevel( $level = null ) {
return wfSetVar( $this->mTrxLevel, $level );
}
/**
* Number of errors logged, only useful when errors are ignored
*/
- function errorCount( $count = NULL ) {
+ function errorCount( $count = null ) {
return wfSetVar( $this->mErrorCount, $count );
}
@@ -113,19 +114,19 @@ class Database {
/**
* Properties passed down from the server info array of the load balancer
*/
- function getLBInfo( $name = NULL ) {
+ function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
return $this->mLBInfo;
} else {
if ( array_key_exists( $name, $this->mLBInfo ) ) {
return $this->mLBInfo[$name];
} else {
- return NULL;
+ return null;
}
}
}
- function setLBInfo( $name, $value = NULL ) {
+ function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
$this->mLBInfo = $name;
} else {
@@ -192,6 +193,14 @@ class Database {
}
/**
+ * 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;
+ }
+
+ /**
* Returns true if this database can do a native search on IP columns
* e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
*/
@@ -225,14 +234,37 @@ class Database {
*/
function isOpen() { return $this->mOpened; }
+ /**
+ * Set a flag for this connection
+ *
+ * @param $flag Integer: DBO_* constants from Defines.php:
+ * - DBO_DEBUG: output some debug info (same as debug())
+ * - DBO_NOBUFFER: don't buffer results (inverse of bufferResults())
+ * - DBO_IGNORE: ignore errors (same as ignoreErrors())
+ * - 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
+ */
function setFlag( $flag ) {
$this->mFlags |= $flag;
}
+ /**
+ * Clear a flag for this connection
+ *
+ * @param $flag: same as setFlag()'s $flag param
+ */
function clearFlag( $flag ) {
$this->mFlags &= ~$flag;
}
+ /**
+ * Returns a boolean whether the flag $flag is set for this connection
+ *
+ * @param $flag: same as setFlag()'s $flag param
+ * @return Boolean
+ */
function getFlag( $flag ) {
return !!($this->mFlags & $flag);
}
@@ -252,6 +284,11 @@ class Database {
}
}
+ /**
+ * Get the type of the DBMS, as it appears in $wgDBtype.
+ */
+ abstract function getType();
+
#------------------------------------------------------------------------------
# Other functions
#------------------------------------------------------------------------------
@@ -272,7 +309,7 @@ class Database {
global $wgOut, $wgDBprefix, $wgCommandLineMode;
# Can't get a reference if it hasn't been set yet
if ( !isset( $wgOut ) ) {
- $wgOut = NULL;
+ $wgOut = null;
}
$this->mFailFunction = $failFunction;
@@ -306,7 +343,7 @@ class Database {
}
/**
- * Same as new Database( ... ), kept for backward compatibility
+ * 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
@@ -316,7 +353,7 @@ class Database {
*/
static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
{
- return new Database( $server, $user, $password, $dbName, $failFunction, $flags );
+ return new DatabaseMysql( $server, $user, $password, $dbName, $failFunction, $flags );
}
/**
@@ -327,114 +364,7 @@ class Database {
* @param $password String: database user password
* @param $dbName String: database name
*/
- function open( $server, $user, $password, $dbName ) {
- global $wgAllDBsAreLocalhost;
- wfProfileIn( __METHOD__ );
-
- # Test for missing mysql.so
- # First try to load it
- if (!@extension_loaded('mysql')) {
- @dl('mysql.so');
- }
-
- # Fail now
- # Otherwise we get a suppressed fatal error, which is very hard to track down
- if ( !function_exists( 'mysql_connect' ) ) {
- throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
- }
-
- # Debugging hack -- fake cluster
- if ( $wgAllDBsAreLocalhost ) {
- $realServer = 'localhost';
- } else {
- $realServer = $server;
- }
- $this->close();
- $this->mServer = $server;
- $this->mUser = $user;
- $this->mPassword = $password;
- $this->mDBname = $dbName;
-
- $success = false;
-
- wfProfileIn("dbconnect-$server");
-
- # The kernel's default SYN retransmission period is far too slow for us,
- # so we use a short timeout plus a manual retry. Retrying means that a small
- # but finite rate of SYN packet loss won't cause user-visible errors.
- $this->mConn = false;
- if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
- $numAttempts = 2;
- } else {
- $numAttempts = 1;
- }
- $this->installErrorHandler();
- for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) {
- if ( $i > 1 ) {
- usleep( 1000 );
- }
- if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = mysql_pconnect( $realServer, $user, $password );
- } else {
- # Create a new connection...
- $this->mConn = mysql_connect( $realServer, $user, $password, true );
- }
- if ($this->mConn === false) {
- #$iplus = $i + 1;
- #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
- }
- }
- $phpError = $this->restoreErrorHandler();
- # Always log connection errors
- if ( !$this->mConn ) {
- $error = $this->lastError();
- if ( !$error ) {
- $error = $phpError;
- }
- wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
- 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 ) {
- $success = @/**/mysql_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 {
- # Delay USE query
- $success = (bool)$this->mConn;
- }
-
- if ( $success ) {
- $version = $this->getServerVersion();
- if ( version_compare( $version, '4.1' ) >= 0 ) {
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- global $wgDBmysql5;
- if( $wgDBmysql5 ) {
- $this->query( 'SET NAMES utf8', __METHOD__ );
- }
- // Turn off strict mode
- $this->query( "SET sql_mode = ''", __METHOD__ );
- }
-
- // Turn off strict mode if it is on
- } else {
- $this->reportConnectionError( $phpError );
- }
-
- $this->mOpened = $success;
- wfProfileOut( __METHOD__ );
- return $success;
- }
+ abstract function open( $server, $user, $password, $dbName );
protected function installErrorHandler() {
$this->mPHPError = false;
@@ -466,17 +396,9 @@ class Database {
*
* @return Bool operation success. true if already closed.
*/
- function close()
- {
- $this->mOpened = false;
- if ( $this->mConn ) {
- if ( $this->trxLevel() ) {
- $this->immediateCommit();
- }
- return mysql_close( $this->mConn );
- } else {
- return true;
- }
+ function close() {
+ # Stub, should probably be overridden
+ return true;
}
/**
@@ -505,7 +427,7 @@ class Database {
* Should return true if unsure.
*/
function isWriteQuery( $sql ) {
- return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW)\b/i', $sql );
+ return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW|\(SELECT)\b/i', $sql );
}
/**
@@ -629,14 +551,7 @@ class Database {
* @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
* @private
*/
- /*private*/ function doQuery( $sql ) {
- if( $this->bufferResults() ) {
- $ret = mysql_query( $sql, $this->mConn );
- } else {
- $ret = mysql_unbuffered_query( $sql, $this->mConn );
- }
- return $ret;
- }
+ /*private*/ abstract function doQuery( $sql );
/**
* @param $error String
@@ -762,12 +677,8 @@ class Database {
* @param $res Mixed: A SQL result
*/
function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- if ( !@/**/mysql_free_result( $res ) ) {
- throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
- }
+ # Stub. Might not really need to be overridden, since results should
+ # be freed by PHP when the variable goes out of scope anyway.
}
/**
@@ -779,16 +690,7 @@ class Database {
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
- function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- @/**/$row = mysql_fetch_object( $res );
- if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
+ abstract function fetchObject( $res );
/**
* Fetch the next row from the given result object, in associative array
@@ -798,43 +700,20 @@ class Database {
* @return MySQL row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
- function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- @/**/$row = mysql_fetch_array( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
+ abstract function fetchRow( $res );
/**
* Get the number of rows in a result object
* @param $res Mixed: A SQL result
*/
- function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- @/**/$n = mysql_num_rows( $res );
- if( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
- }
- return $n;
- }
+ 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
*/
- function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_num_fields( $res );
- }
+ abstract function numFields( $res );
/**
* Get a field name in a result object
@@ -843,12 +722,7 @@ class Database {
* @param $res Mixed: A SQL result
* @param $n Integer
*/
- function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_field_name( $res, $n );
- }
+ abstract function fieldName( $res, $n );
/**
* Get the inserted value of an auto-increment row
@@ -860,7 +734,7 @@ class Database {
* $dbw->insert('page',array('page_id' => $id));
* $id = $dbw->insertId();
*/
- function insertId() { return mysql_insert_id( $this->mConn ); }
+ abstract function insertId();
/**
* Change the position of the cursor in a result object
@@ -868,51 +742,25 @@ class Database {
* @param $res Mixed: A SQL result
* @param $row Mixed: Either MySQL row or ResultWrapper
*/
- function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
- return mysql_data_seek( $res, $row );
- }
+ abstract function dataSeek( $res, $row );
/**
* Get the last error number
* See mysql_errno()
*/
- function lastErrno() {
- if ( $this->mConn ) {
- return mysql_errno( $this->mConn );
- } else {
- return mysql_errno();
- }
- }
+ abstract function lastErrno();
/**
* Get a description of the last error
* See mysql_error() for more details
*/
- function lastError() {
- if ( $this->mConn ) {
- # Even if it's non-zero, it can still be invalid
- wfSuppressWarnings();
- $error = mysql_error( $this->mConn );
- if ( !$error ) {
- $error = mysql_error();
- }
- wfRestoreWarnings();
- } else {
- $error = mysql_error();
- }
- if( $error ) {
- $error .= ' (' . $this->mServer . ')';
- }
- return $error;
- }
+ abstract function lastError();
+
/**
* Get the number of rows affected by the last write query
* See mysql_affected_rows() for more details
*/
- function affectedRows() { return mysql_affected_rows( $this->mConn ); }
+ abstract function affectedRows();
/**
* Simple UPDATE wrapper
@@ -1095,7 +943,7 @@ class Database {
* e.g: selectRow( "page", array( "page_id" ), array( "page_namespace" =>
* NS_MAIN, "page_title" => "Astronomy" ) ) would return an object where
* $obj- >page_id is the ID of the Astronomy article
- * @param $fname String: Calling functio name
+ * @param $fname String: Calling function name
* @param $options Array
* @param $join_conds Array
*
@@ -1118,30 +966,27 @@ class Database {
/**
* Estimate rows in dataset
- * Returns estimated count, based on EXPLAIN output
+ * Returns estimated count - not necessarily an accurate estimate across different databases,
+ * so use sparingly
* Takes same arguments as Database::select()
- */
-
- function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
- $options['EXPLAIN']=true;
- $res = $this->select ($table, $vars, $conds, $fname, $options );
- if ( $res === false )
- return false;
- if (!$this->numRows($res)) {
- $this->freeResult($res);
- return 0;
- }
-
- $rows=1;
-
- while( $plan = $this->fetchObject( $res ) ) {
- $rows *= ($plan->rows > 0)?$plan->rows:1; // avoid resetting to zero
+ *
+ * @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
+ */
+ public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::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;
+ $this->freeResult( $res );
+ return $rows;
}
-
/**
* Removes most variables from an SQL query and replaces them with X or N for numbers.
@@ -1178,7 +1023,7 @@ class Database {
$table = $this->tableName( $table );
$res = $this->query( 'DESCRIBE '.$table, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$found = false;
@@ -1200,7 +1045,7 @@ class Database {
function indexExists( $table, $index, $fname = 'Database::indexExists' ) {
$info = $this->indexInfo( $table, $index, $fname );
if ( is_null( $info ) ) {
- return NULL;
+ return null;
} else {
return $info !== false;
}
@@ -1220,7 +1065,7 @@ class Database {
$sql = 'SHOW INDEX FROM '.$table;
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$result = array();
@@ -1257,18 +1102,7 @@ class Database {
* @param $table
* @param $field
*/
- function fieldInfo( $table, $field ) {
- $table = $this->tableName( $table );
- $res = $this->query( "SELECT * FROM $table LIMIT 1" );
- $n = mysql_num_fields( $res->result );
- for( $i = 0; $i < $n; $i++ ) {
- $meta = mysql_fetch_field( $res->result, $i );
- if( $field == $meta->name ) {
- return new MySQLField($meta);
- }
- }
- return false;
- }
+ abstract function fieldInfo( $table, $field );
/**
* mysql_field_type() wrapper
@@ -1286,7 +1120,7 @@ class Database {
function indexUnique( $table, $index ) {
$indexInfo = $this->indexInfo( $table, $index );
if ( !$indexInfo ) {
- return NULL;
+ return null;
}
return !$indexInfo[0]->Non_unique;
}
@@ -1440,11 +1274,32 @@ class Database {
}
/**
+ * Bitwise operations
+ */
+
+ function bitNot($field) {
+ return "(~$bitField)";
+ }
+
+ function bitAnd($fieldLeft, $fieldRight) {
+ return "($fieldLeft & $fieldRight)";
+ }
+
+ function bitOr($fieldLeft, $fieldRight) {
+ return "($fieldLeft | $fieldRight)";
+ }
+
+ /**
* Change the current database
+ *
+ * @return bool Success or failure
*/
function selectDB( $db ) {
- $this->mDBname = $db;
- return mysql_select_db( $db, $this->mConn );
+ # 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.
+ return true;
}
/**
@@ -1621,9 +1476,7 @@ class Database {
* @param $s String: to be slashed.
* @return String: slashed string.
*/
- function strencode( $s ) {
- return mysql_real_escape_string( $s, $this->mConn );
- }
+ abstract function strencode( $s );
/**
* If it's a string, adds quotes and backslashes
@@ -1642,30 +1495,78 @@ class Database {
}
/**
- * Escape string for safe LIKE usage
+ * Escape string for safe LIKE usage.
+ * WARNING: you should almost never use this function directly,
+ * instead use buildLike() that escapes everything automatically
*/
function escapeLike( $s ) {
- $s=str_replace('\\','\\\\',$s);
- $s=$this->strencode( $s );
- $s=str_replace(array('%','_'),array('\%','\_'),$s);
+ $s = str_replace( '\\', '\\\\', $s );
+ $s = $this->strencode( $s );
+ $s = str_replace( array( '%', '_' ), array( '\%', '\_' ), $s );
return $s;
}
/**
+ * 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
+ */
+ function buildLike() {
+ $params = func_get_args();
+ if (count($params) > 0 && is_array($params[0])) {
+ $params = $params[0];
+ }
+
+ $s = '';
+ foreach( $params as $value) {
+ if( $value instanceof LikeMatch ) {
+ $s .= $value->toString();
+ } else {
+ $s .= $this->escapeLike( $value );
+ }
+ }
+ return " LIKE '" . $s . "' ";
+ }
+
+ /**
+ * Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
+ */
+ function anyChar() {
+ return new LikeMatch( '_' );
+ }
+
+ /**
+ * Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
+ */
+ function anyString() {
+ return new LikeMatch( '%' );
+ }
+
+ /**
* Returns an appropriately quoted sequence value for inserting a new row.
* MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
* subclass will return an integer, and save the value for insertId()
*/
function nextSequenceValue( $seqName ) {
- return NULL;
+ return null;
}
/**
- * USE INDEX clause
- * PostgreSQL doesn't have them and returns ""
+ * USE INDEX clause. Unlikely to be useful for anything but MySQL. This
+ * is only needed because a) MySQL must be as efficient as possible due to
+ * its use on Wikipedia, and b) MySQL 4.0 is kind of dumb sometimes about
+ * which index to pick. Anyway, other databases might have different
+ * indexes on a given table. So don't bother overriding this unless you're
+ * MySQL.
*/
function useIndexClause( $index ) {
- return "FORCE INDEX (" . $this->indexName( $index ) . ")";
+ return '';
}
/**
@@ -1753,10 +1654,14 @@ class Database {
}
/**
+ * A string to insert into queries to show that they're low-priority, like
+ * MySQL's LOW_PRIORITY. If no such feature exists, return an empty
+ * string and nothing bad should happen.
+ *
* @return string Returns the text of the low priority option if it is supported, or a blank string otherwise
*/
function lowPriorityOption() {
- return 'LOW_PRIORITY';
+ return '';
}
/**
@@ -1810,27 +1715,60 @@ class Database {
}
/**
- * Construct a LIMIT query with optional offset
- * This is used for query pages
+ * Construct a LIMIT query with optional offset. This is used for query
+ * pages. The SQL should be adjusted so that only the first $limit rows
+ * are returned. If $offset is provided as well, then the first $offset
+ * rows should be discarded, and the next $limit rows should be returned.
+ * If the result of the query is not ordered, then the rows to be returned
+ * are theoretically arbitrary.
+ *
+ * $sql is expected to be a SELECT, if that makes a difference. For
+ * UPDATE, limitResultForUpdate should be used.
+ *
+ * The version provided by default works in MySQL and SQLite. It will very
+ * likely need to be overridden for most other DBMSes.
+ *
* @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)
*/
- 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}," : "" )
. "{$limit} ";
}
- function limitResultForUpdate($sql, $num) {
- return $this->limitResult($sql, $num, 0);
+ function limitResultForUpdate( $sql, $num ) {
+ return $this->limitResult( $sql, $num, 0 );
}
/**
- * Returns an SQL expression for a simple conditional.
- * Uses IF on MySQL.
+ * Returns true if current database backend supports ORDER BY or LIMIT for separate subqueries
+ * within the UNION construct.
+ * @return Boolean
+ */
+ function unionSupportsOrderAndLimit() {
+ return true; // True for almost every DB supported
+ }
+
+ /**
+ * Construct a UNION query
+ * This is used for providing overload point for other DB abstractions
+ * not compatible with the MySQL syntax.
+ * @param $sqls Array: SQL statements to combine
+ * @param $all Boolean: use UNION ALL
+ * @return String: SQL fragment
+ */
+ function unionQueries($sqls, $all) {
+ $glue = $all ? ') UNION ALL (' : ') UNION (';
+ return '('.implode( $glue, $sqls ) . ')';
+ }
+
+ /**
+ * Returns an SQL expression for a simple conditional. This doesn't need
+ * to be overridden unless CASE isn't supported in your DBMS.
*
* @param $cond String: SQL expression which will result in a boolean value
* @param $trueVal String: SQL expression to return if true
@@ -1838,7 +1776,7 @@ class Database {
* @return String: SQL fragment
*/
function conditional( $cond, $trueVal, $falseVal ) {
- return " IF($cond, $trueVal, $falseVal) ";
+ return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
}
/**
@@ -1855,17 +1793,27 @@ class Database {
/**
* Determines if the last failure was due to a deadlock
+ * STUB
*/
function wasDeadlock() {
- return $this->lastErrno() == 1213;
+ return false;
}
/**
* Determines if the last query error was something that should be dealt
- * with by pinging the connection and reissuing the query
+ * with by pinging the connection and reissuing the query.
+ * STUB
*/
function wasErrorReissuable() {
- return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
+ return false;
+ }
+
+ /**
+ * Determines if the last failure was due to the database being read-only.
+ * STUB
+ */
+ function wasReadOnlyError() {
+ return false;
}
/**
@@ -1935,7 +1883,7 @@ class Database {
# Commit any open transactions
if ( $this->mTrxLevel ) {
- $this->immediateCommit();
+ $this->commit();
}
if ( !is_null( $this->mFakeSlaveLag ) ) {
@@ -2048,6 +1996,21 @@ class Database {
}
/**
+ * Creates a new table with structure copied from existing table
+ * Note that unlike most database abstraction functions, this function does not
+ * automatically append database prefix, because it works at a lower
+ * abstraction level.
+ *
+ * @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
+ * @return Boolean: true if operation was successful
+ */
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'Database::duplicateTableStructure' ) {
+ throw new MWException( 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
+ }
+
+ /**
* Return MW-style timestamp used for MySQL schema
*/
function timestamp( $ts=0 ) {
@@ -2089,41 +2052,31 @@ class Database {
}
/**
+ * 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
*/
- function getSoftwareLink() {
- return "[http://www.mysql.com/ MySQL]";
- }
+ 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
*/
- function getServerVersion() {
- return mysql_get_server_info( $this->mConn );
- }
+ abstract function getServerVersion();
/**
* Ping the server and try to reconnect if it there is no connection
+ *
+ * @return bool Success or failure
*/
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;
- }
- return false;
+ # Stub. Not essential to override.
+ return true;
}
/**
@@ -2135,7 +2088,7 @@ class Database {
wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
return $this->mFakeSlaveLag;
}
- $res = $this->query( 'SHOW PROCESSLIST' );
+ $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
# Find slave SQL thread
while ( $row = $this->fetchObject( $res ) ) {
/* This should work for most situations - when default db
@@ -2149,7 +2102,10 @@ class Database {
$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 != '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 ) {
@@ -2190,16 +2146,14 @@ class Database {
}
/**
- * Override database's default connection timeout.
- * May be useful for very long batch queries such as
- * full-wiki dumps, where a single query reads out
- * over hours or days.
+ * Override database's default connection timeout. May be useful for very
+ * long batch queries such as full-wiki dumps, where a single query reads
+ * out over hours or days. May or may not be necessary for non-MySQL
+ * databases. For most purposes, leaving it as a no-op should be fine.
+ *
* @param $timeout Integer in seconds
*/
- public function setTimeout( $timeout ) {
- $this->query( "SET net_read_timeout=$timeout" );
- $this->query( "SET net_write_timeout=$timeout" );
- }
+ public function setTimeout( $timeout ) {}
/**
* Read and execute SQL commands from a file.
@@ -2211,14 +2165,45 @@ class Database {
function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
$fp = fopen( $filename, 'r' );
if ( false === $fp ) {
- throw new MWException( "Could not open \"{$filename}\".\n" );
+ if (!defined("MEDIAWIKI_INSTALL"))
+ throw new MWException( "Could not open \"{$filename}\".\n" );
+ else
+ return "Could not open \"{$filename}\".\n";
+ }
+ try {
+ $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
}
- $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
+ 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
+ * 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";
+ } else {
+ return "$IP/maintenance/archives/$patch";
+ }
+ }
+
+ /**
* 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
@@ -2257,7 +2242,7 @@ class Database {
}
}
- if ( '' != $cmd ) { $cmd .= ' '; }
+ if ( $cmd != '' ) { $cmd .= ' '; }
$cmd .= "$line\n";
if ( $done ) {
@@ -2326,15 +2311,17 @@ class Database {
return $this->indexName( $matches[1] );
}
- /*
+ /**
* Build a concatenation list to feed into a SQL query
- */
+ * @param $stringList Array: list of raw SQL expressions; caller is responsible for any quoting
+ * @return String
+ */
function buildConcat( $stringList ) {
return 'CONCAT(' . implode( ',', $stringList ) . ')';
}
/**
- * Acquire a lock
+ * Acquire a named lock
*
* Abstracted from Filestore::lock() so child classes can implement for
* their own needs.
@@ -2343,32 +2330,44 @@ class Database {
* @param $method String: Name of method calling us
* @return bool
*/
- public function lock( $lockName, $method ) {
- $lockName = $this->addQuotes( $lockName );
- $result = $this->query( "SELECT GET_LOCK($lockName, 5) AS lockstatus", $method );
- $row = $this->fetchObject( $result );
- $this->freeResult( $result );
-
- if( $row->lockstatus == 1 ) {
- return true;
- } else {
- wfDebug( __METHOD__." failed to acquire lock\n" );
- return false;
- }
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ return true;
}
+
/**
* Release a lock.
*
- * @todo fixme - Figure out a way to return a bool
- * based on successful lock release.
- *
* @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
+ * lock did not exist
*/
public function unlock( $lockName, $method ) {
- $lockName = $this->addQuotes( $lockName );
- $result = $this->query( "SELECT RELEASE_LOCK($lockName)", $method );
- $this->freeResult( $result );
+ return true;
+ }
+
+ /**
+ * Lock specific tables
+ *
+ * @param $read Array of tables to lock for read access
+ * @param $write Array of tables to lock for write access
+ * @param $method String name of caller
+ * @param $lowPriority bool Whether to indicate writes to be LOW PRIORITY
+ */
+ public function lockTables( $read, $write, $method, $lowPriority = true ) {
+ return true;
+ }
+
+ /**
+ * Unlock specific tables
+ *
+ * @param $method String the caller
+ */
+ public function unlockTables( $method ) {
+ return true;
}
/**
@@ -2380,19 +2379,21 @@ class Database {
public function getSearchEngine() {
return "SearchMySQL";
}
-}
-/**
- * Database abstraction object for mySQL
- * Inherit all methods and properties of Database::Database()
- *
- * @ingroup Database
- * @see Database
- */
-class DatabaseMysql extends Database {
- # Inherit all
+ /**
+ * 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.
+ *
+ * @param mixed $value true for allow, false for deny, or "default" to restore the initial value
+ */
+ public function setBigSelects( $value = true ) {
+ // no-op
+ }
}
+
/******************************************************************************
* Utility classes
*****************************************************************************/
@@ -2502,10 +2503,19 @@ class DBError extends MWException {
* @param $db Database object which threw the error
* @param $error A simple error message to be used for debugging
*/
- function __construct( Database &$db, $error ) {
+ function __construct( DatabaseBase &$db, $error ) {
$this->db =& $db;
parent::__construct( $error );
}
+
+ function getText() {
+ global $wgShowDBErrorBacktrace;
+ $s = $this->getMessage() . "\n";
+ if ( $wgShowDBErrorBacktrace ) {
+ $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
+ }
+ return $s;
+ }
}
/**
@@ -2514,7 +2524,7 @@ class DBError extends MWException {
class DBConnectionError extends DBError {
public $error;
- function __construct( Database &$db, $error = 'unknown error' ) {
+ function __construct( DatabaseBase &$db, $error = 'unknown error' ) {
$msg = 'DB connection error';
if ( trim( $error ) != '' ) {
$msg .= ": $error";
@@ -2533,10 +2543,6 @@ class DBConnectionError extends DBError {
return false;
}
- function getText() {
- return $this->getMessage() . "\n";
- }
-
function getLogMessage() {
# Don't send to the exception log
return false;
@@ -2553,7 +2559,7 @@ class DBConnectionError extends DBError {
}
function getHTML() {
- global $wgLang, $wgMessageCache, $wgUseFileCache;
+ global $wgLang, $wgMessageCache, $wgUseFileCache, $wgShowDBErrorBacktrace;
$sorry = 'Sorry! This site is experiencing technical difficulties.';
$again = 'Try waiting a few minutes and reloading.';
@@ -2577,30 +2583,31 @@ class DBConnectionError extends DBError {
$noconnect = "<p><strong>$sorry</strong><br />$again</p><p><small>$info</small></p>";
$text = str_replace( '$1', $this->error, $noconnect );
- /*
- if ( $GLOBALS['wgShowExceptionDetails'] ) {
- $text .= '</p><p>Backtrace:</p><p>' .
- nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
- "</p>\n";
- }*/
+ if ( $wgShowDBErrorBacktrace ) {
+ $text .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
+ }
$extra = $this->searchForm();
if( $wgUseFileCache ) {
- $cache = $this->fileCachedPage();
- # Cached version on file system?
- if( $cache !== null ) {
- # Hack: extend the body for error messages
- $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 ) {
- $cachederror = htmlspecialchars( $wgLang->getMessage( 'dberr-cachederror' ) );
+ try {
+ $cache = $this->fileCachedPage();
+ # Cached version on file system?
+ if( $cache !== null ) {
+ # Hack: extend the body for error messages
+ $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 ) {
+ $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>";
}
- $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 ) {
+ // Do nothing, just use the default page
}
}
# Headers needed here - output is just the error message
@@ -2631,8 +2638,6 @@ class DBConnectionError extends DBError {
<input type="hidden" name="ie" value="$wgInputEncoding" />
<input type="hidden" name="oe" value="$wgInputEncoding" />
- <img src="http://www.google.com/logos/Logo_40wht.gif" alt="" style="float:left; margin-left: 1.5em; margin-right: 1.5em;" />
-
<input type="text" name="q" size="31" maxlength="255" value="$search" />
<input type="submit" name="btnG" value="$googlesearch" />
<div>
@@ -2653,9 +2658,9 @@ EOT;
$mainpage = htmlspecialchars( $wgLang->getMessage( 'mainpage' ) );
}
- if($wgTitle) {
+ if( $wgTitle ) {
$t =& $wgTitle;
- } elseif($title) {
+ } elseif( $title ) {
$t = Title::newFromURL( $title );
} else {
$t = Title::newFromText( $mainpage );
@@ -2681,7 +2686,7 @@ EOT;
class DBQueryError extends DBError {
public $error, $errno, $sql, $fname;
- function __construct( Database &$db, $error, $errno, $sql, $fname ) {
+ function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) {
$message = "A database error has occurred\n" .
"Query: $sql\n" .
"Function: $fname\n" .
@@ -2695,11 +2700,16 @@ class DBQueryError extends DBError {
}
function getText() {
+ global $wgShowDBErrorBacktrace;
if ( $this->useMessageCache() ) {
- return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
- htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
+ $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 $this->getMessage();
+ return parent::getText();
}
}
@@ -2722,12 +2732,17 @@ class DBQueryError extends DBError {
}
function getHTML() {
+ global $wgShowDBErrorBacktrace;
if ( $this->useMessageCache() ) {
- return wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
+ $s = wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) );
} else {
- return nl2br( htmlspecialchars( $this->getMessage() ) );
+ $s = nl2br( htmlspecialchars( $this->getMessage() ) );
}
+ if ( $wgShowDBErrorBacktrace ) {
+ $s .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
+ }
+ return $s;
}
}
@@ -2841,15 +2856,18 @@ class ResultWrapper implements Iterator {
}
}
-class MySQLMasterPos {
- var $file, $pos;
+/**
+ * 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.
+ */
+class LikeMatch {
+ private $str;
- function __construct( $file, $pos ) {
- $this->file = $file;
- $this->pos = $pos;
+ public function __construct( $s ) {
+ $this->str = $s;
}
- function __toString() {
- return "{$this->file}/{$this->pos}";
+ public function toString() {
+ return $this->str;
}
}
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
index fcd0bc2d..9b62af82 100644
--- a/includes/db/DatabaseIbm_db2.php
+++ b/includes/db/DatabaseIbm_db2.php
@@ -9,37 +9,33 @@
*/
/**
- * Utility class for generating blank objects
- * Intended as an equivalent to {} in Javascript
- * @ingroup Database
- */
-class BlankObject {
-}
-
-/**
* This represents a column in a DB2 database
* @ingroup Database
*/
class IBM_DB2Field {
- private $name, $tablename, $type, $nullable, $max_length;
+ private $name = '';
+ private $tablename = '';
+ private $type = '';
+ private $nullable = false;
+ private $max_length = 0;
/**
* Builder method for the class
- * @param Object $db Database interface
- * @param string $table table name
- * @param string $field column name
+ * @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) {
global $wgDBmwschema;
- $q = <<<END
+ $q = <<<SQL
SELECT
lcase(coltype) AS typname,
nulls AS attnotnull, length AS attlen
FROM sysibm.syscolumns
WHERE tbcreator=%s AND tbname=%s AND name=%s;
-END;
+SQL;
$res = $db->query(sprintf($q,
$db->addQuotes($wgDBmwschema),
$db->addQuotes($table),
@@ -89,20 +85,25 @@ END;
class IBM_DB2Blob {
private $mData;
- function __construct($data) {
+ public function __construct($data) {
$this->mData = $data;
}
- function getData() {
+ public function getData() {
return $this->mData;
}
+
+ public function __toString()
+ {
+ return $this->mData;
+ }
}
/**
* Primary database interface
* @ingroup Database
*/
-class DatabaseIbm_db2 extends Database {
+class DatabaseIbm_db2 extends DatabaseBase {
/*
* Inherited members
protected $mLastQuery = '';
@@ -122,27 +123,42 @@ class DatabaseIbm_db2 extends Database {
*/
/// Server port for uncataloged connections
- protected $mPort = NULL;
+ protected $mPort = null;
/// Whether connection is cataloged
- protected $mCataloged = NULL;
+ protected $mCataloged = null;
/// Schema for tables, stored procedures, triggers
- protected $mSchema = NULL;
+ protected $mSchema = null;
/// Whether the schema has been applied in this session
protected $mSchemaSet = false;
/// Result of last query
- protected $mLastResult = NULL;
+ protected $mLastResult = null;
/// Number of rows affected by last INSERT/UPDATE/DELETE
- protected $mAffectedRows = NULL;
+ protected $mAffectedRows = null;
/// Number of rows returned by last SELECT
- protected $mNumRows = NULL;
+ protected $mNumRows = null;
+
+ /// Connection config options - see constructor
+ public $mConnOptions = array();
+ /// Statement config options -- see constructor
+ public $mStmtOptions = array();
const CATALOGED = "cataloged";
const UNCATALOGED = "uncataloged";
const USE_GLOBAL = "get from global";
+ const NONE_OPTION = 0x00;
+ const CONN_OPTION = 0x01;
+ const STMT_OPTION = 0x02;
+
+ const REGULAR_MODE = 'regular';
+ const INSTALL_MODE = 'install';
+
+ // Whether this is regular operation or the initial installation
+ protected $mMode = self::REGULAR_MODE;
+
/// Last sequence value used for a primary key
- protected $mInsertId = NULL;
+ protected $mInsertId = null;
/*
* These can be safely inherited
@@ -219,7 +235,7 @@ class DatabaseIbm_db2 extends Database {
*/
/*
- * These need to be implemented TODO
+ * These have been implemented
*
* Administrative: 7 / 7
* constructor [Done]
@@ -375,7 +391,10 @@ class DatabaseIbm_db2 extends Database {
return $this->mDBname;
}
}
-
+
+ function getType() {
+ return 'ibm_db2';
+ }
######################################
# Setup
@@ -384,12 +403,13 @@ class DatabaseIbm_db2 extends Database {
/**
*
- * @param string $server hostname of database server
- * @param string $user username
- * @param string $password
- * @param string $dbName database name on the server
- * @param function $failFunction (optional)
- * @param integer $flags database behaviour flags (optional, unused)
+ * @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,
@@ -399,7 +419,7 @@ class DatabaseIbm_db2 extends Database {
global $wgOut, $wgDBmwschema;
# Can't get a reference if it hasn't been set yet
if ( !isset( $wgOut ) ) {
- $wgOut = NULL;
+ $wgOut = null;
}
$this->mOut =& $wgOut;
$this->mFailFunction = $failFunction;
@@ -412,17 +432,50 @@ class DatabaseIbm_db2 extends Database {
$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);
}
/**
+ * 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.");
+ }
+ }
+
+ /**
+ * 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>";
+ flush();
+ }
+ }
+
+ /**
* Opens a database connection and returns it
* Closes any existing connection
* @return a fresh connection
- * @param string $server hostname
- * @param string $user
- * @param string $password
- * @param string $dbName database name
+ * @param $server String: hostname
+ * @param $user String
+ * @param $password String
+ * @param $dbName String: database name
*/
public function open( $server, $user, $password, $dbName )
{
@@ -437,7 +490,7 @@ class DatabaseIbm_db2 extends Database {
// 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";
- wfDebug($error);
+ $this->installPrint($error);
$this->reportConnectionError($error);
}
@@ -461,16 +514,16 @@ class DatabaseIbm_db2 extends Database {
elseif ( $cataloged == self::UNCATALOGED ) {
$this->openUncataloged($dbName, $user, $password, $server, $port);
}
- // Don't do this
+ // Apply connection config
+ db2_set_option($this->mConn, $this->mConnOptions, 1);
// Not all MediaWiki code is transactional
- // Rather, turn it off in the begin function and turn on after a commit
- // db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
+ // Rather, turn autocommit off in the begin function and turn on after a commit
db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
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" );
+ $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" );
return null;
}
@@ -524,14 +577,14 @@ class DatabaseIbm_db2 extends Database {
/**
* Returns a fresh instance of this class
- * @static
- * @return
- * @param string $server hostname of database server
- * @param string $user username
- * @param string $password
- * @param string $dbName database name on the server
- * @param function $failFunction (optional)
- * @param integer $flags database behaviour flags (optional, unused)
+ *
+ * @param $server String: hostname of database server
+ * @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)
{
@@ -543,20 +596,16 @@ class DatabaseIbm_db2 extends Database {
* Forces a database rollback
*/
public function lastError() {
- if ($this->lastError2()) {
- $this->rollback();
- return true;
- }
- return false;
- }
-
- private function lastError2() {
$connerr = db2_conn_errormsg();
- if ($connerr) return $connerr;
+ if ($connerr) {
+ //$this->rollback();
+ return $connerr;
+ }
$stmterr = db2_stmt_errormsg();
- if ($stmterr) return $stmterr;
- if ($this->mConn) return "No open connection.";
- if ($this->mOpened) return "No open connection allegedly.";
+ if ($stmterr) {
+ //$this->rollback();
+ return $stmterr;
+ }
return false;
}
@@ -592,7 +641,7 @@ class DatabaseIbm_db2 extends Database {
// Switch into the correct namespace
$this->applySchema();
- $ret = db2_exec( $this->mConn, $sql );
+ $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
if( !$ret ) {
print "<br><pre>";
print $sql;
@@ -601,7 +650,7 @@ class DatabaseIbm_db2 extends Database {
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;
}
@@ -653,17 +702,6 @@ EOF;
if( $this->lastErrno() ) {
throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
}
- // Make field names lowercase for compatibility with MySQL
- if ($row)
- {
- $row2 = new BlankObject();
- foreach ($row as $key => $value)
- {
- $keyu = strtolower($key);
- $row2->$keyu = $value;
- }
- $row = $row2;
- }
return $row;
}
@@ -707,14 +745,26 @@ EOF;
$this->applySchema();
$this->begin();
- $res = dbsource( "../maintenance/ibm_db2/tables.sql", $this);
+ $res = $this->sourceFile( "../maintenance/ibm_db2/tables.sql" );
+ if ($res !== true) {
+ print " <b>FAILED</b>: " . htmlspecialchars( $res ) . "</li>";
+ } else {
+ print " done</li>";
+ }
$res = null;
// TODO: update mediawiki_version table
// TODO: populate interwiki links
- $this->commit();
+ if ($this->lastError()) {
+ print "<li>Errors encountered during table creation -- rolled back</li>\n";
+ print "<li>Please install again</li>\n";
+ $this->rollback();
+ }
+ else {
+ $this->commit();
+ }
}
catch (MWException $mwe)
{
@@ -725,15 +775,17 @@ EOF;
/**
* Escapes strings
* Doesn't escape numbers
- * @param string s string to escape
+ * @param $s String: string to escape
* @return escaped string
*/
public function addQuotes( $s ) {
- //wfDebug("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) ) {
@@ -745,41 +797,9 @@ EOF;
}
/**
- * Escapes strings
- * Only escapes numbers going into non-numeric fields
- * @param string s string to escape
- * @return escaped string
- */
- public function addQuotesSmart( $table, $field, $s ) {
- if ( is_null( $s ) ) {
- return "NULL";
- } else if ($s instanceof Blob) {
- return "'".$s->fetch($s)."'";
- }
- $s = $this->strencode($s);
- if ( is_numeric($s) ) {
- // Check with the database if the column is actually numeric
- // This allows for numbers in titles, etc
- $res = $this->doQuery("SELECT $field FROM $table FETCH FIRST 1 ROWS ONLY");
- $type = db2_field_type($res, strtoupper($field));
- if ( $this->is_numeric_type( $type ) ) {
- //wfDebug("DB2: Numeric value going in a numeric column: $s in $type $field in $table\n");
- return $s;
- }
- else {
- wfDebug("DB2: Numeric in non-numeric: '$s' in $type $field in $table\n");
- return "'$s'";
- }
- }
- else {
- return "'$s'";
- }
- }
-
- /**
* Verifies that a DB2 column/field type is numeric
* @return bool true if numeric
- * @param string $type DB2 column type
+ * @param $type String: DB2 column type
*/
public function is_numeric_type( $type ) {
switch (strtoupper($type)) {
@@ -798,7 +818,7 @@ EOF;
/**
* Alias for addQuotes()
- * @param string s string to escape
+ * @param $s String: string to escape
* @return escaped string
*/
public function strencode( $s ) {
@@ -830,7 +850,7 @@ EOF;
/**
* Start a transaction (mandatory)
*/
- public function begin() {
+ public function begin( $fname = 'DatabaseIbm_db2::begin' ) {
// turn off auto-commit
db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
$this->mTrxLevel = 1;
@@ -840,7 +860,7 @@ EOF;
* End a transaction
* Must have a preceding begin()
*/
- public function commit() {
+ public function commit( $fname = 'DatabaseIbm_db2::commit' ) {
db2_commit($this->mConn);
// turn auto-commit back on
db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
@@ -850,7 +870,7 @@ EOF;
/**
* Cancel a transaction
*/
- public function rollback() {
+ public function rollback( $fname = 'DatabaseIbm_db2::rollback' ) {
db2_rollback($this->mConn);
// turn auto-commit back on
// not sure if this is appropriate
@@ -868,7 +888,6 @@ EOF;
* LIST_NAMES - comma separated field names
*/
public function makeList( $a, $mode = LIST_COMMA ) {
- wfDebug("DB2::makeList()\n");
if ( !is_array( $a ) ) {
throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
}
@@ -931,88 +950,18 @@ EOF;
}
/**
- * Makes an encoded list of strings from an array
- * Quotes numeric values being inserted into non-numeric fields
- * @return string
- * @param string $table name of the table
- * @param array $a list of values
- * @param $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 makeListSmart( $table, $a, $mode = LIST_COMMA ) {
- if ( !is_array( $a ) ) {
- throw new DBUnexpectedError( $this, 'Database::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 ';
- } 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;
- }
- else {
- $list .= $this->addQuotesSmart( $table, $field, $value );
- }
- }
- }
- return $list;
- }
-
- /**
* 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)
+ * @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" );
}
if( $offset ) {
- wfDebug("Offset parameter not supported in limitResult()\n");
+ $this->installPrint("Offset parameter not supported in limitResult()\n");
}
// TODO implement proper offset handling
// idea: get all the rows between 0 and offset, advance cursor to offset
@@ -1026,20 +975,22 @@ EOF;
*/
public function tableName( $name ) {
# Replace reserved words with better ones
- switch( $name ) {
- case 'user':
- return 'mwuser';
- case 'text':
- return 'pagecontent';
- default:
- return $name;
- }
+// 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 timestamp $ts
+ * @param $ts timestamp
*/
public function timestamp( $ts=0 ) {
// TS_MW cannot be easily distinguished from an integer
@@ -1048,16 +999,21 @@ EOF;
/**
* Return the next in a sequence, save the value for retrieval via insertId()
- * @param string seqName Name of a defined sequence in the database
+ * @param $seqName String: name of a defined sequence in the database
* @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
+ /*
$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;
}
/**
@@ -1069,139 +1025,180 @@ EOF;
}
/**
+ * 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 $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];
+ }
+ }
+
+ /**
* 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
*
- * @param array $table String: Name of the table to insert to.
- * @param array $args Array: Items to insert into the table.
- * @param array $fname String: Name of the function, for profiling
- * @param mixed $options String or Array. Valid options: IGNORE
+ * @param $table String: Name of the table to insert to.
+ * @param $args Array: Items to insert into the table.
+ * @param $fname String: Name of the function, for profiling
+ * @param $options String or Array. Valid options: IGNORE
*
* @return bool Success of insert operation. IGNORE always returns true.
*/
public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) {
- wfDebug("DB2::insert($table)\n");
if ( !count( $args ) ) {
return true;
}
-
+ // get database-specific table name (not used)
$table = $this->tableName( $table );
-
- if ( !is_array( $options ) )
- $options = array( $options );
-
- if ( isset( $args[0] ) && is_array( $args[0] ) ) {
- }
- else {
+ // format options as an array
+ if ( !is_array( $options ) ) $options = array( $options );
+ // format args as an array of arrays
+ if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
$args = array($args);
}
+ // prevent insertion of NULL into primary key columns
+ 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) {
+ $primaryKey = $primaryKeys[0];
+ }
+
+ // get column names
$keys = array_keys( $args[0] );
+ $key_count = count($keys);
// If IGNORE is set, we use savepoints to emulate mysql's behavior
$ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
-
- // Cache autocommit value at the start
- $oldautocommit = db2_autocommit($this->mConn);
+ // assume success
+ $res = true;
// If we are not in a transaction, we need to be for savepoint trickery
$didbegin = 0;
if (! $this->mTrxLevel) {
$this->begin();
$didbegin = 1;
}
- if ( $ignore ) {
- $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;
- }
$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)";
+ }
+ $stmt = $this->prepare($sql);
+
+ // start a transaction/enter transaction mode
+ $this->begin();
if ( !$ignore ) {
$first = true;
foreach ( $args as $row ) {
- if ( $first ) {
- $first = false;
- } else {
- $sql .= ',';
- }
- $sql .= '(' . $this->makeListSmart( $table, $row ) . ')';
+ // insert each row into the database
+ $res = $res & $this->execute($stmt, $row);
+ // get the last inserted value into a generated column
+ $this->calcInsertId($table, $primaryKey, $stmt);
}
- $res = (bool)$this->query( $sql, $fname, $ignore );
}
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;
- $origsql = $sql;
+
foreach ( $args as $row ) {
- $tempsql = $origsql;
- $tempsql .= '(' . $this->makeListSmart( $table, $row ) . ')';
-
- if ( $ignore ) {
- db2_exec($this->mConn, "SAVEPOINT $ignore");
+ $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
+ db2_exec($this->mConn, $overhead, $this->mStmtOptions);
+
+ $res2 = $this->execute($stmt, $row);
+ // get the last inserted value into a generated column
+ $this->calcInsertId($table, $primaryKey, $stmt);
+
+ $errNum = $this->lastErrno();
+ if ($errNum) {
+ db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore", $this->mStmtOptions );
}
-
- $tempres = (bool)$this->query( $tempsql, $fname, $ignore );
-
- if ( $ignore ) {
- $bar = db2_stmt_error();
- if ($bar != false) {
- db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore" );
- }
- else {
- db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore" );
- $numrowsinserted++;
- }
+ else {
+ db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore", $this->mStmtOptions );
+ $numrowsinserted++;
}
-
- // If any of them fail, we fail overall for this function call
- // Note that this will be ignored if IGNORE is set
- if (! $tempres)
- $res = false;
}
- }
-
- if ($didbegin) {
- $this->commit();
- }
- // if autocommit used to be on, it's ok to commit everything
- else if ($oldautocommit)
- {
- $this->commit();
- }
-
- if ( $ignore ) {
+
$olde = error_reporting( $olde );
// Set the affected row count for the whole operation
$this->mAffectedRows = $numrowsinserted;
-
- // IGNORE always returns true
- return true;
}
+ // commit either way
+ $this->commit();
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
+ */
+ private function removeNullPrimaryKeys($table, $args) {
+ $schema = $this->mSchema;
+ // find out the primary keys
+ $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);
+ }
+ // remove primary keys
+ foreach ($args as $ai => $row) {
+ foreach ($keys as $ki => $key) {
+ if ($row[$key] == null) {
+ unset($row[$key]);
+ }
+ }
+ $args[$ai] = $row;
+ }
+ // return modified hash
+ return array($args, $keys);
+ }
+
+ /**
* UPDATE wrapper, takes a condition array and a SET array
*
- * @param string $table The table to UPDATE
- * @param array $values An array of values to SET
- * @param array $conds An array of conditions (WHERE). Use '*' to update all rows.
- * @param string $fname The Class::Function calling this function
- * (for the log)
- * @param array $options An array of UPDATE options, can be one or
- * more of IGNORE, LOW_PRIORITY
- * @return bool
- */
- function update( $table, $values, $conds, $fname = 'Database::update', $options = 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 $fname String: The Class::Function calling this function
+ * (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() ) {
$table = $this->tableName( $table );
$opts = $this->makeUpdateOptions( $options );
- $sql = "UPDATE $opts $table SET " . $this->makeListSmart( $table, $values, LIST_SET );
+ $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
if ( $conds != '*' ) {
- $sql .= " WHERE " . $this->makeListSmart( $table, $conds, LIST_AND );
+ $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
}
return $this->query( $sql, $fname );
}
@@ -1211,21 +1208,21 @@ EOF;
*
* Use $conds == "*" to delete all rows
*/
- function delete( $table, $conds, $fname = 'Database::delete' ) {
+ public function delete( $table, $conds, $fname = 'Database::delete' ) {
if ( !$conds ) {
throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
}
$table = $this->tableName( $table );
$sql = "DELETE FROM $table";
if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeListSmart( $table, $conds, LIST_AND );
+ $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
return $this->query( $sql, $fname );
}
/**
* Returns the number of rows affected by the last query or 0
- * @return int the number of rows affected by the last query
+ * @return Integer: the number of rows affected by the last query
*/
public function affectedRows() {
if ( !is_null( $this->mAffectedRows ) ) {
@@ -1238,20 +1235,11 @@ EOF;
}
/**
- * USE INDEX clause
- * DB2 doesn't have them and returns ""
- * @param sting $index
- */
- public function useIndexClause( $index ) {
- return "";
- }
-
- /**
* Simulates REPLACE with a DELETE followed by INSERT
* @param $table Object
- * @param array $uniqueIndexes array consisting of indexes and arrays of indexes
- * @param array $rows Rows to insert
- * @param string $fname Name of the function for profiling
+ * @param $uniqueIndexes Array consisting of indexes and arrays of indexes
+ * @param $rows Array: rows to insert
+ * @param $fname String: name of the function for profiling
* @return nothing
*/
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) {
@@ -1306,8 +1294,8 @@ EOF;
/**
* Returns the number of rows in the result set
* Has to be called right after the corresponding select query
- * @param Object $res result set
- * @return int number of rows
+ * @param $res Object result set
+ * @return Integer: number of rows
*/
public function numRows( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -1323,8 +1311,8 @@ EOF;
/**
* Moves the row pointer of the result set
- * @param Object $res result set
- * @param int $row row number
+ * @param $res Object: result set
+ * @param $row Integer: row number
* @return success or failure
*/
public function dataSeek( $res, $row ) {
@@ -1340,8 +1328,8 @@ EOF;
/**
* Frees memory associated with a statement resource
- * @param Object $res Statement resource to free
- * @return bool success or failure
+ * @param $res Object: statement resource to free
+ * @return Boolean success or failure
*/
public function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -1354,7 +1342,7 @@ EOF;
/**
* Returns the number of columns in a resource
- * @param Object $res Statement resource
+ * @param $res Object: statement resource
* @return Number of fields/columns in resource
*/
public function numFields( $res ) {
@@ -1366,9 +1354,9 @@ EOF;
/**
* Returns the nth column name
- * @param Object $res Statement resource
- * @param int $n Index of field or column
- * @return string name of nth column
+ * @param $res Object: statement resource
+ * @param $n Integer: Index of field or column
+ * @return String name of nth column
*/
public function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
@@ -1380,15 +1368,15 @@ EOF;
/**
* SELECT wrapper
*
- * @param mixed $table Array or string, table name(s) (prefix auto-added)
- * @param mixed $vars Array or string, field name(s) to be retrieved
- * @param mixed $conds Array or string, condition(s) for WHERE
- * @param string $fname Calling function name (use __METHOD__) for logs/profiling
- * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @param array $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
+ * @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 $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
*/
public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
{
@@ -1419,7 +1407,6 @@ EOF;
$obj = $this->fetchObject($res2);
$this->mNumRows = $obj->num_rows;
- wfDebug("DatabaseIbm_db2::select: There are $this->mNumRows rows.\n");
return $res;
}
@@ -1430,9 +1417,9 @@ EOF;
*
* @private
*
- * @param array $options an associative array of options to be turned into
+ * @param $options Associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
- * @return array
+ * @return Array
*/
function makeSelectOptions( $options ) {
$preLimitTail = $postLimitTail = '';
@@ -1463,46 +1450,18 @@ EOF;
}
/**
- * Does nothing
- * @param object $db
- * @return bool true
- */
- public function selectDB( $db ) {
- return true;
- }
-
- /**
- * Returns an SQL expression for a simple conditional.
- * Uses CASE on DB2
- *
- * @param string $cond SQL expression which will result in a boolean value
- * @param string $trueVal SQL expression to return if true
- * @param string $falseVal SQL expression to return if false
- * @return string SQL fragment
- */
- public function conditional( $cond, $trueVal, $falseVal ) {
- return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
- }
-
- ###
- # Fix search crash
- ###
- /**
* Get search engine class. All subclasses of this
* need to implement this if they wish to use searching.
*
- * @return string
+ * @return String
*/
public function getSearchEngine() {
return "SearchIBM_DB2";
}
-
- ###
- # Tuesday the 14th of October, 2008
- ###
+
/**
* Did the last database access fail because of deadlock?
- * @return bool
+ * @return Boolean
*/
public function wasDeadlock() {
// get SQLSTATE
@@ -1511,7 +1470,7 @@ EOF;
case '40001': // sql0911n, Deadlock or timeout, rollback
case '57011': // sql0904n, Resource unavailable, no rollback
case '57033': // sql0913n, Deadlock or timeout, no rollback
- wfDebug("In a deadlock because of SQLSTATE $err");
+ $this->installPrint("In a deadlock because of SQLSTATE $err");
return true;
}
return false;
@@ -1520,13 +1479,13 @@ EOF;
/**
* Ping the server and try to reconnect if it there is no connection
* The connection may be closed and reopened while this happens
- * @return bool whether the connection exists
+ * @return Boolean: whether the connection exists
*/
public function ping() {
// db2_ping() doesn't exist
// Emulate
$this->close();
- if ($this->mCataloged == NULL) {
+ if ($this->mCataloged == null) {
return false;
}
else if ($this->mCataloged) {
@@ -1545,46 +1504,34 @@ EOF;
* @return string ''
* @deprecated
*/
- public function getStatus( $which ) { wfDebug('Not implemented for DB2: getStatus()'); return ''; }
- /**
- * Not implemented
- * @deprecated
- */
- public function setTimeout( $timeout ) { wfDebug('Not implemented for DB2: setTimeout()'); }
+ public function getStatus( $which="%" ) { $this->installPrint('Not implemented for DB2: getStatus()'); return ''; }
/**
* Not implemented
* TODO
* @return bool true
*/
- public function lock( $lockName, $method ) { wfDebug('Not implemented for DB2: lock()'); return true; }
- /**
- * Not implemented
- * TODO
- * @return bool true
- */
- public function unlock( $lockName, $method ) { wfDebug('Not implemented for DB2: unlock()'); return true; }
/**
* Not implemented
* @deprecated
*/
- public function setFakeSlaveLag( $lag ) { wfDebug('Not implemented for DB2: setFakeSlaveLag()'); }
+ public function setFakeSlaveLag( $lag ) { $this->installPrint('Not implemented for DB2: setFakeSlaveLag()'); }
/**
* Not implemented
* @deprecated
*/
- public function setFakeMaster( $enabled ) { wfDebug('Not implemented for DB2: setFakeMaster()'); }
+ public function setFakeMaster( $enabled = true ) { $this->installPrint('Not implemented for DB2: setFakeMaster()'); }
/**
* Not implemented
* @return string $sql
* @deprecated
*/
- public function limitResultForUpdate($sql, $num) { return $sql; }
+ public function limitResultForUpdate($sql, $num) { $this->installPrint('Not implemented for DB2: limitResultForUpdate()'); return $sql; }
+
/**
- * No such option
- * @return string ''
- * @deprecated
+ * Only useful with fake prepare like in base Database class
+ * @return string
*/
- public function lowPriorityOption() { return ''; }
+ public function fillPreparedArg( $matches ) { $this->installPrint('Not useful for DB2: fillPreparedArg()'); return ''; }
######################################
# Reflection
@@ -1592,9 +1539,9 @@ EOF;
/**
* Query whether a given column exists in the mediawiki schema
- * @param string $table name of the table
- * @param string $field name of the column
- * @param string $fname function name for logging and profiling
+ * @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 );
@@ -1617,10 +1564,10 @@ SQL;
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
- * @param string $table table name
- * @param string $index index name
- * @param string
- * @return object query row in object form
+ * @param $table String: table name
+ * @param $index String: index name
+ * @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' ) {
$table = $this->tableName( $table );
@@ -1631,17 +1578,17 @@ WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
SQL;
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$row = $this->fetchObject( $res );
- if ($row != NULL) return $row;
+ if ($row != null) return $row;
else return false;
}
/**
* Returns an information object on a table column
- * @param string $table table name
- * @param string $field column name
+ * @param $table String: table name
+ * @param $field String: column name
* @return IBM_DB2Field
*/
public function fieldInfo( $table, $field ) {
@@ -1650,9 +1597,9 @@ SQL;
/**
* db2_field_type() wrapper
- * @param Object $res Result of executed statement
- * @param mixed $index number or name of the column
- * @return string column type
+ * @param $res Object: result of executed statement
+ * @param $index Mixed: number or name of the column
+ * @return String column type
*/
public function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
@@ -1663,10 +1610,10 @@ SQL;
/**
* Verifies that an index was created as unique
- * @param string $table table name
- * @param string $index index name
- * @param string $fnam function name for profiling
- * @return bool
+ * @param $table String: table name
+ * @param $index String: index name
+ * @param $fname function name for profiling
+ * @return Bool
*/
public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
$table = $this->tableName( $table );
@@ -1689,9 +1636,9 @@ SQL;
/**
* Returns the size of a text field, or -1 for "unlimited"
- * @param string $table table name
- * @param string $field column name
- * @return int length or -1 for unlimited
+ * @param $table String: table name
+ * @param $field String: column name
+ * @return Integer: length or -1 for unlimited
*/
public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
@@ -1709,12 +1656,12 @@ SQL;
/**
* DELETE where the condition is a join
- * @param string $delTable deleting from this table
- * @param string $joinTable using data from this table
- * @param string $delVar variable in deleteable table
- * @param string $joinVar variable in data table
- * @param array $conds conditionals for join table
- * @param string $fname function name for profiling
+ * @param $delTable String: deleting from this table
+ * @param $joinTable String: using data from this table
+ * @param $delVar String: variable in deleteable table
+ * @param $joinVar String: variable in data table
+ * @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" ) {
if ( !$conds ) {
@@ -1731,32 +1678,10 @@ SQL;
$this->query( $sql, $fname );
}
-
- /**
- * Estimate rows in dataset
- * Returns estimated count, based on COUNT(*) output
- * Takes same arguments as Database::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
- */
- public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
- $rows = 0;
- $res = $this->select ($table, 'COUNT(*) as mwrowcount', $conds, $fname, $options );
- if ($res) {
- $row = $this->fetchRow($res);
- $rows = (isset($row['mwrowcount'])) ? $row['mwrowcount'] : 0;
- }
- $this->freeResult($res);
- return $rows;
- }
-
+
/**
* Description is left as an exercise for the reader
- * @param mixed $b data to be encoded
+ * @param $b Mixed: data to be encoded
* @return IBM_DB2Blob
*/
public function encodeBlob($b) {
@@ -1765,7 +1690,7 @@ SQL;
/**
* Description is left as an exercise for the reader
- * @param IBM_DB2Blob $b data to be decoded
+ * @param $b IBM_DB2Blob: data to be decoded
* @return mixed
*/
public function decodeBlob($b) {
@@ -1774,8 +1699,8 @@ SQL;
/**
* Convert into a list of string being concatenated
- * @param array $stringList strings that need to be joined together by the SQL engine
- * @return string joined by the concatenation operator
+ * @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 ) {
// || is equivalent to CONCAT
@@ -1785,12 +1710,135 @@ SQL;
/**
* Generates the SQL required to convert a DB2 timestamp into a Unix epoch
- * @param string $column name of timestamp column
- * @return string SQL code
+ * @param $column String: name of timestamp column
+ * @return String: SQL code
*/
public function extractUnixEpoch( $column ) {
// 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
+ *
+ * ? = scalar value, quoted as necessary
+ * ! = raw SQL bit (a function for instance)
+ * & = filename; reads the file and inserts as a blob
+ * (we don't use this though...)
+ * @param $sql String: SQL statement with appropriate markers
+ * @param $func String: Name of the function, for profiling
+ * @return resource a prepared DB2 SQL statement
+ */
+ public function prepare( $sql, $func = 'DB2::prepare' ) {
+ $stmt = db2_prepare($this->mConn, $sql, $this->mStmtOptions);
+ return $stmt;
+ }
+
+ /**
+ * Frees resources associated with a prepared statement
+ * @return Boolean success or failure
+ */
+ public function freePrepared( $prepared ) {
+ return db2_free_stmt($prepared);
+ }
+
+ /**
+ * Execute a prepared query with the various arguments
+ * @param $prepared String: the prepared sql
+ * @param $args Mixed: either an array here, or put scalars as varargs
+ * @return Resource: results object
+ */
+ public function execute( $prepared, $args = null ) {
+ if( !is_array( $args ) ) {
+ # Pull the var args
+ $args = func_get_args();
+ array_shift( $args );
+ }
+ $res = db2_execute($prepared, $args);
+ return $res;
+ }
+
+ /**
+ * Prepare & execute an SQL statement, quoting and inserting arguments
+ * in the appropriate places.
+ * @param $query String
+ * @param $args ...
+ */
+ public function safeQuery( $query, $args = null ) {
+ // copied verbatim from Database.php
+ $prepared = $this->prepare( $query, 'DB2::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;
+ }
+
+ /**
+ * For faking prepared SQL statements on DBs that don't support
+ * it directly.
+ * @param $preparedQuery String: a 'preparable' SQL statement
+ * @param $args Array of arguments to fill it with
+ * @return String: executable statement
+ */
+ public function fillPrepared( $preparedQuery, $args ) {
+ reset( $args );
+ $this->preparedArgs =& $args;
+
+ 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;
+ $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.')';
+ }
+
+ /**
+ * Bitwise AND of two columns or values in SQL
+ * Same as (fieldLeft & fieldRight) in C
+ * @param $fieldLeft String
+ * @param $fieldRight String
+ * @return String
+ */
+ function bitAnd($fieldLeft, $fieldRight) {
+ return 'BITAND('.$fieldLeft.', '.$fieldRight.')';
+ }
+
+ /**
+ * Bitwise OR of two columns or values in SQL
+ * Same as (fieldLeft | fieldRight) in C
+ * @param $fieldLeft String
+ * @param $fieldRight String
+ * @return String
+ */
+ function bitOr($fieldLeft, $fieldRight) {
+ return 'BITOR('.$fieldLeft.', '.$fieldRight.')';
+ }
}
-?> \ No newline at end of file
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 28ccab2d..6b1206b0 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -10,7 +10,7 @@
/**
* @ingroup Database
*/
-class DatabaseMssql extends Database {
+class DatabaseMssql extends DatabaseBase {
var $mAffectedRows;
var $mLastResult;
@@ -25,7 +25,7 @@ class DatabaseMssql extends Database {
$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
+ 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;
@@ -45,6 +45,10 @@ class DatabaseMssql extends Database {
}
+ function getType() {
+ return 'mssql';
+ }
+
/**
* todo: check if these should be true like parent class
*/
@@ -131,7 +135,7 @@ class DatabaseMssql extends Database {
function close() {
$this->mOpened = false;
if ($this->mConn) {
- if ($this->trxLevel()) $this->immediateCommit();
+ if ($this->trxLevel()) $this->commit();
return mssql_close($this->mConn);
} else return true;
}
@@ -446,22 +450,6 @@ class DatabaseMssql extends Database {
}
/**
- * Estimate rows in dataset
- * Returns estimated count, based on EXPLAIN output
- * Takes same arguments as Database::select()
- */
- function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
- $rows = 0;
- $res = $this->select ($table, 'COUNT(*)', $conds, $fname, $options );
- if ($res) {
- $row = $this->fetchObject($res);
- $rows = $row[0];
- }
- $this->freeResult($res);
- return $rows;
- }
-
- /**
* Determines whether a field exists in a table
* Usually aborts on failure
* If errors are explicitly ignored, returns NULL on failure
@@ -490,13 +478,13 @@ class DatabaseMssql extends Database {
function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
throw new DBUnexpectedError( $this, 'Database::indexInfo called which is not supported yet' );
- return NULL;
+ return null;
$table = $this->tableName( $table );
$sql = 'SHOW INDEX FROM '.$table;
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$result = array();
@@ -708,13 +696,6 @@ class DatabaseMssql extends Database {
}
/**
- * USE INDEX clause
- */
- function useIndexClause( $index ) {
- return "";
- }
-
- /**
* REPLACE query wrapper
* PostgreSQL simulates this with a DELETE followed by INSERT
* $row is the row to insert, an associative array
@@ -858,18 +839,6 @@ class DatabaseMssql extends Database {
}
/**
- * Returns an SQL expression for a simple conditional.
- *
- * @param $cond String: SQL expression which will result in a boolean value
- * @param $trueVal String: SQL expression to return if true
- * @param $falseVal String: SQL expression to return if false
- * @return string SQL fragment
- */
- function conditional( $cond, $trueVal, $falseVal ) {
- return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
- }
-
- /**
* Should determine if the last failure was due to a deadlock
* @return bool
*/
@@ -878,22 +847,6 @@ class DatabaseMssql extends Database {
}
/**
- * Begin a transaction, committing any previously open transaction
- * @deprecated use begin()
- */
- function immediateBegin( $fname = 'Database::immediateBegin' ) {
- $this->begin();
- }
-
- /**
- * Commit transaction, if one is open
- * @deprecated use commit()
- */
- function immediateCommit( $fname = 'Database::immediateCommit' ) {
- $this->commit();
- }
-
- /**
* Return MW-style timestamp used for MySQL schema
*/
function timestamp( $ts=0 ) {
@@ -931,16 +884,6 @@ class DatabaseMssql extends Database {
}
/**
- * not done
- */
- public function setTimeout($timeout) { return; }
-
- function ping() {
- wfDebug("Function ping() not written for MSSQL yet");
- return true;
- }
-
- /**
* How lagged is this slave?
*/
public function getLag() {
@@ -1001,20 +944,9 @@ class DatabaseMssql extends Database {
}
}
- /**
- * No-op lock functions
- */
- public function lock( $lockName, $method ) {
- return true;
- }
- public function unlock( $lockName, $method ) {
- return true;
- }
-
public function getSearchEngine() {
return "SearchEngineDummy";
}
-
}
/**
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
new file mode 100644
index 00000000..ea7ef5b9
--- /dev/null
+++ b/includes/db/DatabaseMysql.php
@@ -0,0 +1,453 @@
+<?php
+/**
+ * Database abstraction object for mySQL
+ * Inherit all methods and properties of Database::Database()
+ *
+ * @ingroup Database
+ * @see Database
+ */
+class DatabaseMysql extends DatabaseBase {
+ function getType() {
+ return 'mysql';
+ }
+
+ /*private*/ function doQuery( $sql ) {
+ if( $this->bufferResults() ) {
+ $ret = mysql_query( $sql, $this->mConn );
+ } else {
+ $ret = mysql_unbuffered_query( $sql, $this->mConn );
+ }
+ return $ret;
+ }
+
+ function open( $server, $user, $password, $dbName ) {
+ global $wgAllDBsAreLocalhost;
+ wfProfileIn( __METHOD__ );
+
+ # Test for missing mysql.so
+ # First try to load it
+ if (!@extension_loaded('mysql')) {
+ @dl('mysql.so');
+ }
+
+ # Fail now
+ # Otherwise we get a suppressed fatal error, which is very hard to track down
+ if ( !function_exists( 'mysql_connect' ) ) {
+ throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
+ }
+
+ # Debugging hack -- fake cluster
+ if ( $wgAllDBsAreLocalhost ) {
+ $realServer = 'localhost';
+ } else {
+ $realServer = $server;
+ }
+ $this->close();
+ $this->mServer = $server;
+ $this->mUser = $user;
+ $this->mPassword = $password;
+ $this->mDBname = $dbName;
+
+ $success = false;
+
+ wfProfileIn("dbconnect-$server");
+
+ # The kernel's default SYN retransmission period is far too slow for us,
+ # so we use a short timeout plus a manual retry. Retrying means that a small
+ # but finite rate of SYN packet loss won't cause user-visible errors.
+ $this->mConn = false;
+ if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
+ $numAttempts = 2;
+ } else {
+ $numAttempts = 1;
+ }
+ $this->installErrorHandler();
+ for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) {
+ if ( $i > 1 ) {
+ usleep( 1000 );
+ }
+ if ( $this->mFlags & DBO_PERSISTENT ) {
+ $this->mConn = mysql_pconnect( $realServer, $user, $password );
+ } else {
+ # Create a new connection...
+ $this->mConn = mysql_connect( $realServer, $user, $password, true );
+ }
+ if ($this->mConn === false) {
+ #$iplus = $i + 1;
+ #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
+ }
+ }
+ $phpError = $this->restoreErrorHandler();
+ # Always log connection errors
+ if ( !$this->mConn ) {
+ $error = $this->lastError();
+ if ( !$error ) {
+ $error = $phpError;
+ }
+ wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
+ 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 ) {
+ $success = @/**/mysql_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 {
+ # Delay USE query
+ $success = (bool)$this->mConn;
+ }
+
+ if ( $success ) {
+ $version = $this->getServerVersion();
+ if ( version_compare( $version, '4.1' ) >= 0 ) {
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ global $wgDBmysql5;
+ if( $wgDBmysql5 ) {
+ $this->query( 'SET NAMES utf8', __METHOD__ );
+ }
+ // Turn off strict mode
+ $this->query( "SET sql_mode = ''", __METHOD__ );
+ }
+
+ // Turn off strict mode if it is on
+ } else {
+ $this->reportConnectionError( $phpError );
+ }
+
+ $this->mOpened = $success;
+ wfProfileOut( __METHOD__ );
+ return $success;
+ }
+
+ function close() {
+ $this->mOpened = false;
+ if ( $this->mConn ) {
+ if ( $this->trxLevel() ) {
+ $this->commit();
+ }
+ return mysql_close( $this->mConn );
+ } else {
+ return true;
+ }
+ }
+
+ function freeResult( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ if ( !@/**/mysql_free_result( $res ) ) {
+ throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
+ }
+ }
+
+ function fetchObject( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ @/**/$row = mysql_fetch_object( $res );
+ if( $this->lastErrno() ) {
+ throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
+ }
+ return $row;
+ }
+
+ function fetchRow( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ @/**/$row = mysql_fetch_array( $res );
+ if ( $this->lastErrno() ) {
+ throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
+ }
+ return $row;
+ }
+
+ function numRows( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ @/**/$n = mysql_num_rows( $res );
+ if( $this->lastErrno() ) {
+ throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
+ }
+ return $n;
+ }
+
+ function numFields( $res ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ return mysql_num_fields( $res );
+ }
+
+ function fieldName( $res, $n ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ return mysql_field_name( $res, $n );
+ }
+
+ function insertId() { return mysql_insert_id( $this->mConn ); }
+
+ function dataSeek( $res, $row ) {
+ if ( $res instanceof ResultWrapper ) {
+ $res = $res->result;
+ }
+ return mysql_data_seek( $res, $row );
+ }
+
+ function lastErrno() {
+ if ( $this->mConn ) {
+ return mysql_errno( $this->mConn );
+ } else {
+ return mysql_errno();
+ }
+ }
+
+ function lastError() {
+ if ( $this->mConn ) {
+ # Even if it's non-zero, it can still be invalid
+ wfSuppressWarnings();
+ $error = mysql_error( $this->mConn );
+ if ( !$error ) {
+ $error = mysql_error();
+ }
+ wfRestoreWarnings();
+ } else {
+ $error = mysql_error();
+ }
+ if( $error ) {
+ $error .= ' (' . $this->mServer . ')';
+ }
+ return $error;
+ }
+
+ 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() ) {
+ $options['EXPLAIN'] = true;
+ $res = $this->select( $table, $vars, $conds, $fname, $options );
+ if ( $res === false )
+ return false;
+ if ( !$this->numRows( $res ) ) {
+ $this->freeResult($res);
+ return 0;
+ }
+
+ $rows = 1;
+ while( $plan = $this->fetchObject( $res ) ) {
+ $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
+ }
+
+ $this->freeResult($res);
+ return $rows;
+ }
+
+ function fieldInfo( $table, $field ) {
+ $table = $this->tableName( $table );
+ $res = $this->query( "SELECT * FROM $table LIMIT 1" );
+ $n = mysql_num_fields( $res->result );
+ for( $i = 0; $i < $n; $i++ ) {
+ $meta = mysql_fetch_field( $res->result, $i );
+ if( $field == $meta->name ) {
+ return new MySQLField($meta);
+ }
+ }
+ return false;
+ }
+
+ function selectDB( $db ) {
+ $this->mDBname = $db;
+ return mysql_select_db( $db, $this->mConn );
+ }
+
+ function strencode( $s ) {
+ return mysql_real_escape_string( $s, $this->mConn );
+ }
+
+ 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;
+ }
+ return false;
+ }
+
+ function getServerVersion() {
+ return mysql_get_server_info( $this->mConn );
+ }
+
+ function useIndexClause( $index ) {
+ return "FORCE INDEX (" . $this->indexName( $index ) . ")";
+ }
+
+ function lowPriorityOption() {
+ return 'LOW_PRIORITY';
+ }
+
+ function getSoftwareLink() {
+ return '[http://www.mysql.com/ MySQL]';
+ }
+
+ function standardSelectDistinct() {
+ return false;
+ }
+
+ public function setTimeout( $timeout ) {
+ $this->query( "SET net_read_timeout=$timeout" );
+ $this->query( "SET net_write_timeout=$timeout" );
+ }
+
+ public function lock( $lockName, $method, $timeout = 5 ) {
+ $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;
+ } else {
+ wfDebug( __METHOD__." failed to acquire lock\n" );
+ return false;
+ }
+ }
+
+ public function unlock( $lockName, $method ) {
+ $lockName = $this->addQuotes( $lockName );
+ $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
+ $row = $this->fetchObject( $result );
+ return $row->lockstatus;
+ }
+
+ public function lockTables( $read, $write, $method, $lowPriority = true ) {
+ $items = array();
+
+ foreach( $write as $table ) {
+ $tbl = $this->tableName( $table ) .
+ ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
+ ' WRITE';
+ $items[] = $tbl;
+ }
+ foreach( $read as $table ) {
+ $items[] = $this->tableName( $table ) . ' READ';
+ }
+ $sql = "LOCK TABLES " . implode( ',', $items );
+ $this->query( $sql, $method );
+ }
+
+ public function unlockTables( $method ) {
+ $this->query( "UNLOCK TABLES", $method );
+ }
+
+ public function setBigSelects( $value = true ) {
+ if ( $value === 'default' ) {
+ if ( $this->mDefaultBigSelects === null ) {
+ # Function hasn't been called before so it must already be set to the default
+ return;
+ } else {
+ $value = $this->mDefaultBigSelects;
+ }
+ } elseif ( $this->mDefaultBigSelects === null ) {
+ $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
+ }
+ $encValue = $value ? '1' : '0';
+ $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
+ }
+
+
+ /**
+ * Determines if the last failure was due to a deadlock
+ */
+ function wasDeadlock() {
+ return $this->lastErrno() == 1213;
+ }
+
+ /**
+ * Determines if the last query error was something that should be dealt
+ * with by pinging the connection and reissuing the query
+ */
+ function wasErrorReissuable() {
+ return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
+ }
+
+ /**
+ * Determines if the last failure was due to the database being read-only.
+ */
+ function wasReadOnlyError() {
+ return $this->lastErrno() == 1223 ||
+ ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
+ }
+
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseMysql::duplicateTableStructure' ) {
+ $tmp = $temporary ? 'TEMPORARY ' : '';
+ if ( strcmp( $this->getServerVersion(), '4.1' ) < 0 ) {
+ # Hack for MySQL versions < 4.1, which don't support
+ # "CREATE TABLE ... LIKE". Note that
+ # "CREATE TEMPORARY TABLE ... SELECT * FROM ... LIMIT 0"
+ # would not create the indexes we need....
+ #
+ # Note that we don't bother changing around the prefixes here be-
+ # cause we know we're using MySQL anyway.
+
+ $res = $this->query( "SHOW CREATE TABLE $oldName" );
+ $row = $this->fetchRow( $res );
+ $oldQuery = $row[1];
+ $query = preg_replace( '/CREATE TABLE `(.*?)`/',
+ "CREATE $tmp TABLE `$newName`", $oldQuery );
+ if ($oldQuery === $query) {
+ # Couldn't do replacement
+ throw new MWException( "could not create temporary table $newName" );
+ }
+ } else {
+ $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
+ }
+ $this->query( $query, $fname );
+ }
+
+}
+
+/**
+ * Legacy support: Database == DatabaseMysql
+ */
+class Database extends DatabaseMysql {}
+
+class MySQLMasterPos {
+ var $file, $pos;
+
+ function __construct( $file, $pos ) {
+ $this->file = $file;
+ $this->pos = $pos;
+ }
+
+ function __toString() {
+ return "{$this->file}/{$this->pos}";
+ }
+}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 4c37a507..bd60bbf8 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -11,7 +11,7 @@
class ORABlob {
var $mData;
- function __construct($data) {
+ function __construct( $data ) {
$this->mData = $data;
}
@@ -31,58 +31,80 @@ class ORAResult {
private $cursor;
private $stmt;
private $nrows;
- private $db;
- function __construct(&$db, $stmt) {
+ private $unique;
+ private function array_unique_md( $array_in ) {
+ $array_out = array();
+ $array_hashes = array();
+
+ foreach ( $array_in as $key => $item ) {
+ $hash = md5( serialize( $item ) );
+ if ( !isset( $array_hashes[$hash] ) ) {
+ $array_hashes[$hash] = $hash;
+ $array_out[] = $item;
+ }
+ }
+
+ return $array_out;
+ }
+
+ function __construct( &$db, $stmt, $unique = false ) {
$this->db =& $db;
- 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__);
+
+ 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__ );
return;
}
+ if ( $unique ) {
+ $this->rows = $this->array_unique_md( $this->rows );
+ $this->nrows = count( $this->rows );
+ }
+
$this->cursor = 0;
$this->stmt = $stmt;
}
- function free() {
- oci_free_statement($this->stmt);
+ public function free() {
+ oci_free_statement( $this->stmt );
}
- function seek($row) {
- $this->cursor = min($row, $this->nrows);
+ public function seek( $row ) {
+ $this->cursor = min( $row, $this->nrows );
}
- function numRows() {
+ public function numRows() {
return $this->nrows;
}
- function numFields() {
- return oci_num_fields($this->stmt);
+ public function numFields() {
+ return oci_num_fields( $this->stmt );
}
- function fetchObject() {
- if ($this->cursor >= $this->nrows)
+ public function fetchObject() {
+ if ( $this->cursor >= $this->nrows ) {
return false;
-
+ }
$row = $this->rows[$this->cursor++];
$ret = new stdClass();
- foreach ($row as $k => $v) {
- $lc = strtolower(oci_field_name($this->stmt, $k + 1));
+ foreach ( $row as $k => $v ) {
+ $lc = strtolower( oci_field_name( $this->stmt, $k + 1 ) );
$ret->$lc = $v;
}
return $ret;
}
- function fetchAssoc() {
- if ($this->cursor >= $this->nrows)
+ public function fetchRow() {
+ if ( $this->cursor >= $this->nrows ) {
return false;
+ }
$row = $this->rows[$this->cursor++];
$ret = array();
- foreach ($row as $k => $v) {
- $lc = strtolower(oci_field_name($this->stmt, $k + 1));
+ foreach ( $row as $k => $v ) {
+ $lc = strtolower( oci_field_name( $this->stmt, $k + 1 ) );
$ret[$lc] = $v;
$ret[$k] = $v;
}
@@ -91,30 +113,87 @@ class ORAResult {
}
/**
+ * Utility class.
+ * @ingroup Database
+ */
+class ORAField {
+ private $name, $tablename, $default, $max_length, $nullable,
+ $is_pk, $is_unique, $is_multiple, $is_key, $type;
+
+ function __construct( $info ) {
+ $this->name = $info['column_name'];
+ $this->tablename = $info['table_name'];
+ $this->default = $info['data_default'];
+ $this->max_length = $info['data_length'];
+ $this->nullable = $info['not_null'];
+ $this->is_pk = isset( $info['prim'] ) && $info['prim'] == 1 ? 1 : 0;
+ $this->is_unique = isset( $info['uniq'] ) && $info['uniq'] == 1 ? 1 : 0;
+ $this->is_multiple = isset( $info['nonuniq'] ) && $info['nonuniq'] == 1 ? 1 : 0;
+ $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
+ $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 nullable() {
+ return $this->nullable;
+ }
+
+ function isKey() {
+ return $this->is_key;
+ }
+
+ function isMultipleKey() {
+ return $this->is_multiple;
+ }
+
+ function type() {
+ return $this->type;
+ }
+}
+
+/**
* @ingroup Database
*/
-class DatabaseOracle extends Database {
- var $mInsertId = NULL;
- var $mLastResult = NULL;
- var $numeric_version = NULL;
+class DatabaseOracle extends DatabaseBase {
+ var $mInsertId = null;
+ var $mLastResult = null;
+ var $numeric_version = null;
var $lastResult = null;
var $cursor = 0;
var $mAffectedRows;
- function DatabaseOracle($server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0 )
- {
+ var $ignore_DUP_VAL_ON_INDEX = false;
+ var $sequenceData = null;
- global $wgOut;
- # 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 = $flags;
- $this->open( $server, $user, $password, $dbName);
+ var $defaultCharset = 'AL32UTF8';
+
+ var $mFieldInfoCache = array();
+
+ function __construct( $server = false, $user = false, $password = false, $dbName = false,
+ $failFunction = false, $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 ) );
+ }
+ function getType() {
+ return 'oracle';
}
function cascadingDeletes() {
@@ -139,8 +218,7 @@ class DatabaseOracle extends Database {
return true;
}
- static function newFromParams( $server = false, $user = false, $password = false, $dbName = false,
- $failFunction = false, $flags = 0)
+ static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
{
return new DatabaseOracle( $server, $user, $password, $dbName, $failFunction, $flags );
}
@@ -154,30 +232,35 @@ class DatabaseOracle extends Database {
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" );
}
- # Needed for proper UTF-8 functionality
- putenv("NLS_LANG=AMERICAN_AMERICA.AL32UTF8");
-
$this->close();
$this->mServer = $server;
$this->mUser = $user;
$this->mPassword = $password;
$this->mDBname = $dbName;
- if (!strlen($user)) { ## e.g. the class is being loaded
+ if ( !strlen( $user ) ) { # e.g. the class is being loaded
return;
}
- error_reporting( E_ALL );
- $this->mConn = oci_connect($user, $password, $dbName);
+ $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 );
+ } else {
+ $this->mConn = oci_connect( $user, $password, $dbName, $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");
+ 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;
}
$this->mOpened = true;
+
+ # removed putenv calls because they interfere with the system globaly
+ $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
+ $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
return $this->mConn;
}
@@ -198,55 +281,101 @@ class DatabaseOracle extends Database {
return $this->mTrxLevel ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS;
}
- function doQuery($sql) {
- wfDebug("SQL: [$sql]\n");
- if (!mb_check_encoding($sql)) {
- throw new MWException("SQL encoding is invalid");
+ function doQuery( $sql ) {
+ wfDebug( "SQL: [$sql]\n" );
+ if ( !mb_check_encoding( $sql ) ) {
+ throw new MWException( "SQL encoding is invalid\n$sql" );
}
- if (($this->mLastResult = $stmt = oci_parse($this->mConn, $sql)) === false) {
- $e = oci_error($this->mConn);
- $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__);
+ // handle some oracle specifics
+ // remove AS column/table/subquery namings
+ if ( !defined( 'MEDIAWIKI_INSTALL' ) ) {
+ $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 );
+ // EXPLAIN syntax in Oracle is EXPLAIN PLAN FOR and it return nothing
+ // you have to select data from plan table after explain
+ $explain_id = date( 'dmYHis' );
+
+ $sql = preg_replace( '/^EXPLAIN /', 'EXPLAIN PLAN SET STATEMENT_ID = \'' . $explain_id . '\' FOR', $sql, 1, $explain_count );
+
- if (oci_execute($stmt, $this->execFlags()) == false) {
- $e = oci_error($stmt);
- $this->reportQueryError($e['message'], $e['code'], $sql, __FUNCTION__);
+ wfSuppressWarnings();
+
+ if ( ( $this->mLastResult = $stmt = oci_parse( $this->mConn, $sql ) ) === false ) {
+ $e = oci_error( $this->mConn );
+ $this->reportQueryError( $e['message'], $e['code'], $sql, __FUNCTION__ );
+ return 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, __FUNCTION__ );
+ return false;
+ }
}
- if (oci_statement_type($stmt) == "SELECT")
- return new ORAResult($this, $stmt);
- else {
- $this->mAffectedRows = oci_num_rows($stmt);
+
+ wfRestoreWarnings();
+
+ if ( $explain_count > 0 ) {
+ return $this->doQuery( 'SELECT id, cardinality "ROWS" FROM plan_table WHERE statement_id = \'' . $explain_id . '\'' );
+ } elseif ( oci_statement_type( $stmt ) == 'SELECT' ) {
+ return new ORAResult( $this, $stmt, $union_unique );
+ } else {
+ $this->mAffectedRows = oci_num_rows( $stmt );
return true;
}
}
- function queryIgnore($sql, $fname = '') {
- return $this->query($sql, $fname, true);
+ function queryIgnore( $sql, $fname = '' ) {
+ return $this->query( $sql, $fname, true );
}
- function freeResult($res) {
- $res->free();
+ function freeResult( $res ) {
+ if ( $res instanceof ORAResult ) {
+ $res->free();
+ } else {
+ $res->result->free();
+ }
}
- function fetchObject($res) {
- return $res->fetchObject();
+ function fetchObject( $res ) {
+ if ( $res instanceof ORAResult ) {
+ return $res->numRows();
+ } else {
+ return $res->result->fetchObject();
+ }
}
- function fetchRow($res) {
- return $res->fetchAssoc();
+ function fetchRow( $res ) {
+ if ( $res instanceof ORAResult ) {
+ return $res->fetchRow();
+ } else {
+ return $res->result->fetchRow();
+ }
}
- function numRows($res) {
- return $res->numRows();
+ function numRows( $res ) {
+ if ( $res instanceof ORAResult ) {
+ return $res->numRows();
+ } else {
+ return $res->result->numRows();
+ }
}
- function numFields($res) {
- return $res->numFields();
+ function numFields( $res ) {
+ if ( $res instanceof ORAResult ) {
+ return $res->numFields();
+ } else {
+ return $res->result->numFields();
+ }
}
- function fieldName($stmt, $n) {
- return pg_field_name($stmt, $n);
+ function fieldName( $stmt, $n ) {
+ return oci_field_name( $stmt, $n );
}
/**
@@ -256,23 +385,29 @@ class DatabaseOracle extends Database {
return $this->mInsertId;
}
- function dataSeek($res, $row) {
- $res->seek($row);
+ function dataSeek( $res, $row ) {
+ if ( $res instanceof ORAResult ) {
+ $res->seek( $row );
+ } else {
+ $res->result->seek( $row );
+ }
}
function lastError() {
- if ($this->mConn === false)
+ if ( $this->mConn === false ) {
$e = oci_error();
- else
- $e = oci_error($this->mConn);
+ } else {
+ $e = oci_error( $this->mConn );
+ }
return $e['message'];
}
function lastErrno() {
- if ($this->mConn === false)
+ if ( $this->mConn === false ) {
$e = oci_error();
- else
- $e = oci_error($this->mConn);
+ } else {
+ $e = oci_error( $this->mConn );
+ }
return $e['code'];
}
@@ -284,122 +419,261 @@ class DatabaseOracle extends Database {
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
*/
- function indexInfo( $table, $index, $fname = 'Database::indexExists' ) {
+ function indexInfo( $table, $index, $fname = 'DatabaseOracle::indexExists' ) {
return false;
}
- function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
+ function indexUnique( $table, $index, $fname = 'DatabaseOracle::indexUnique' ) {
return false;
}
- function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
- if (!is_array($options))
- $options = array($options);
+ function insert( $table, $a, $fname = 'DatabaseOracle::insert', $options = array() ) {
+ if ( !count( $a ) ) {
+ return true;
+ }
+
+ if ( !is_array( $options ) ) {
+ $options = array( $options );
+ }
- #if (in_array('IGNORE', $options))
- # $oldIgnore = $this->ignoreErrors(true);
+ if ( in_array( 'IGNORE', $options ) ) {
+ $this->ignore_DUP_VAL_ON_INDEX = true;
+ }
- # IGNORE is performed using single-row inserts, ignoring errors in each
- # FIXME: need some way to distiguish between key collision and other types of error
- //$oldIgnore = $this->ignoreErrors(true);
- if (!is_array(reset($a))) {
- $a = array($a);
+ if ( !is_array( reset( $a ) ) ) {
+ $a = array( $a );
}
- foreach ($a as $row) {
- $this->insertOneRow($table, $row, $fname);
+
+ foreach ( $a as &$row ) {
+ $this->insertOneRow( $table, $row, $fname );
}
- //$this->ignoreErrors($oldIgnore);
$retVal = true;
- //if (in_array('IGNORE', $options))
- // $this->ignoreErrors($oldIgnore);
+ if ( in_array( 'IGNORE', $options ) ) {
+ $this->ignore_DUP_VAL_ON_INDEX = false;
+ }
return $retVal;
}
- function insertOneRow($table, $row, $fname) {
+ private function insertOneRow( $table, $row, $fname ) {
+ global $wgLang;
+
+ $table = $this->tableName( $table );
// "INSERT INTO tables (a, b, c)"
- $sql = "INSERT INTO " . $this->tableName($table) . " (" . join(',', array_keys($row)) . ')';
+ $sql = "INSERT INTO " . $table . " (" . join( ',', array_keys( $row ) ) . ')';
$sql .= " VALUES (";
// for each value, append ":key"
$first = true;
- $returning = '';
- foreach ($row as $col => $val) {
- if (is_object($val)) {
- $what = "EMPTY_BLOB()";
- assert($returning === '');
- $returning = " RETURNING $col INTO :bval";
- $blobcol = $col;
- } else
- $what = ":$col";
-
- if ($first)
- $sql .= "$what";
- else
- $sql.= ", $what";
+ foreach ( $row as $col => $val ) {
+ if ( $first ) {
+ $sql .= $val !== null ? ':' . $col : 'NULL';
+ } else {
+ $sql .= $val !== null ? ', :' . $col : ', NULL';
+ }
+
$first = false;
}
- $sql .= ") $returning";
+ $sql .= ')';
- $stmt = oci_parse($this->mConn, $sql);
- foreach ($row as $col => $val) {
- if (!is_object($val)) {
- if (oci_bind_by_name($stmt, ":$col", $row[$col]) === false)
- $this->reportQueryError($this->lastErrno(), $this->lastError(), $sql, __METHOD__);
+ $stmt = oci_parse( $this->mConn, $sql );
+ foreach ( $row 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 = ( $wgLang != null ) ? $wgLang->checkTitleEncoding( $val ) : $val;
+ if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
+ $this->reportQueryError( $this->lastErrno(), $this->lastError(), $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' ) { // is_object($val)) {
+ $lob[$col]->writeTemporary( $val ); // ->getData());
+ 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 );
+ }
}
}
- if (($bval = oci_new_descriptor($this->mConn, OCI_D_LOB)) === false) {
- $e = oci_error($stmt);
- throw new DBUnexpectedError($this, "Cannot create LOB descriptor: " . $e['message']);
+ wfSuppressWarnings();
+
+ if ( oci_execute( $stmt, OCI_DEFAULT ) === 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_i => $lob_v ) {
+ $lob_v->free();
+ }
+ }
+
+ if ( !$this->mTrxLevel ) {
+ oci_commit( $this->mConn );
+ }
+
+ oci_free_statement( $stmt );
+ }
+
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseOracle::insertSelect',
+ $insertOptions = array(), $selectOptions = array() )
+ {
+ $destTable = $this->tableName( $destTable );
+ 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 );
}
- if (strlen($returning))
- oci_bind_by_name($stmt, ":bval", $bval, -1, SQLT_BLOB);
+ if ( ( $sequenceData = $this->getSequenceData( $destTable ) ) !== false &&
+ !isset( $varMap[$sequenceData['column']] ) )
+ $varMap[$sequenceData['column']] = 'GET_SEQUENCE_VALUE(\'' . $sequenceData['sequence'] . '\')';
- if (oci_execute($stmt, OCI_DEFAULT) === false) {
- $e = oci_error($stmt);
- $this->reportQueryError($e['message'], $e['code'], $sql, __METHOD__);
+ // count-alias subselect fields to avoid abigious definition errors
+ $i = 0;
+ foreach ( $varMap as $key => &$val ) {
+ $val = $val . ' field' . ( $i++ );
}
- if (strlen($returning)) {
- $bval->save($row[$blobcol]->getData());
- $bval->free();
+
+ $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
+ " SELECT $startOpts " . implode( ',', $varMap ) .
+ " FROM $srcTable $useIndex ";
+ if ( $conds != '*' ) {
+ $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ }
+ $sql .= " $tailOpts";
+
+ if ( in_array( 'IGNORE', $insertOptions ) ) {
+ $this->ignore_DUP_VAL_ON_INDEX = true;
}
- if (!$this->mTrxLevel)
- oci_commit($this->mConn);
- oci_free_statement($stmt);
+ $retval = $this->query( $sql, $fname );
+
+ if ( in_array( 'IGNORE', $insertOptions ) ) {
+ $this->ignore_DUP_VAL_ON_INDEX = false;
+ }
+
+ return $retval;
}
function tableName( $name ) {
- # Replace reserved words with better ones
+ global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
+ /*
+ Replace reserved words with better ones
+ Using uppercase because that's the only way Oracle can handle
+ quoted tablenames
+ */
switch( $name ) {
case 'user':
- return 'mwuser';
+ $name = 'MWUSER';
+ break;
case 'text':
- return 'pagecontent';
- default:
- return $name;
+ $name = 'PAGECONTENT';
+ break;
+ }
+
+ /*
+ The rest of procedure is equal to generic Databse class
+ except for the quoting style
+ */
+ if ( $name[0] == '"' && substr( $name, - 1, 1 ) == '"' ) {
+ return $name;
+ }
+ if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
+ return $name;
+ }
+ $dbDetails = array_reverse( explode( '.', $name, 2 ) );
+ if ( isset( $dbDetails[1] ) ) {
+ @list( $table, $database ) = $dbDetails;
+ } else {
+ @list( $table ) = $dbDetails;
+ }
+
+ $prefix = $this->mTablePrefix;
+
+ if ( isset( $database ) ) {
+ $table = ( $table[0] == '`' ? $table : "`{$table}`" );
}
+
+ if ( !isset( $database ) && isset( $wgSharedDB ) && $table[0] != '"'
+ && isset( $wgSharedTables )
+ && is_array( $wgSharedTables )
+ && in_array( $table, $wgSharedTables )
+ ) {
+ $database = $wgSharedDB;
+ $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
+ }
+
+ if ( isset( $database ) ) {
+ $database = ( $database[0] == '"' ? $database : "\"{$database}\"" );
+ }
+ $table = ( $table[0] == '"' ? $table : "\"{$prefix}{$table}\"" );
+
+ $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
+
+ return strtoupper( $tableName );
}
/**
* Return the next in a sequence, save the value for retrieval via insertId()
*/
- function nextSequenceValue($seqName) {
- $res = $this->query("SELECT $seqName.nextval FROM dual");
- $row = $this->fetchRow($res);
+ function nextSequenceValue( $seqName ) {
+ $res = $this->query( "SELECT $seqName.nextval FROM dual" );
+ $row = $this->fetchRow( $res );
$this->mInsertId = $row[0];
- $this->freeResult($res);
+ $this->freeResult( $res );
return $this->mInsertId;
}
/**
- * Oracle does not have a "USE INDEX" clause, so return an empty string
+ * Return sequence_name if table has a sequence
*/
- function useIndexClause($index) {
- return '';
+ 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'" );
+
+ while ( ( $row = $result->fetchRow() ) !== false ) {
+ $this->sequenceData[$this->tableName( $row[1] )] = array(
+ 'sequence' => $row[0],
+ 'column' => $row[2]
+ );
+ }
+ }
+
+ return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false;
}
# REPLACE query wrapper
@@ -411,59 +685,44 @@ class DatabaseOracle extends Database {
# 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 = 'Database::replace' ) {
- $table = $this->tableName($table);
+ function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseOracle::replace' ) {
+ $table = $this->tableName( $table );
- if (count($rows)==0) {
+ if ( count( $rows ) == 0 ) {
return;
}
# Single row case
- if (!is_array(reset($rows))) {
- $rows = array($rows);
+ if ( !is_array( reset( $rows ) ) ) {
+ $rows = array( $rows );
}
- foreach( $rows as $row ) {
+ $sequenceData = $this->getSequenceData( $table );
+
+ foreach ( $rows as $row ) {
# 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] );
- }
+ $condsDelete = array();
+ foreach ( $uniqueIndexes as $index )
+ $condsDelete[$index] = $row[$index];
+ if (count($condsDelete) > 0) {
+ $this->delete( $table, $condsDelete, $fname );
}
- $sql .= ')';
- $this->query( $sql, $fname );
+ }
+
+ if ( $sequenceData !== false && !isset( $row[$sequenceData['column']] ) ) {
+ $row[$sequenceData['column']] = $this->nextSequenceValue( $sequenceData['sequence'] );
}
# 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);
+ $this->insert( $table, $row, $fname );
}
}
# DELETE where the condition is a join
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) {
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseOracle::deleteJoin" ) {
if ( !$conds ) {
- throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' );
+ throw new DBUnexpectedError( $this, 'DatabaseOracle::deleteJoin() called with empty $conds' );
}
$delTable = $this->tableName( $delTable );
@@ -479,78 +738,80 @@ class DatabaseOracle extends Database {
# Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
- $table = $this->tableName( $table );
- $sql = "SELECT t.typname as ftype,a.atttypmod as size
- 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;
+ $fieldInfoData = $this->fieldInfo( $table, $field);
+ if ( $fieldInfoData->type == "varchar" ) {
+ $size = $row->size - 4;
} else {
- $size=$row->size;
+ $size = $row->size;
}
- $this->freeResult( $res );
return $size;
}
- function lowPriorityOption() {
- return '';
- }
-
- function limitResult($sql, $limit, $offset) {
- if ($offset === false)
+ function limitResult( $sql, $limit, $offset = false ) {
+ if ( $offset === false ) {
$offset = 0;
- return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < 1 + $limit + $offset";
+ }
+ return "SELECT * FROM ($sql) WHERE rownum >= (1 + $offset) AND rownum < (1 + $limit + $offset)";
}
- /**
- * Returns an SQL expression for a simple conditional.
- * Uses CASE on Oracle
- *
- * @param $cond String: SQL expression which will result in a boolean value
- * @param $trueVal String: SQL expression to return if true
- * @param $falseVal String: SQL expression to return if false
- * @return String: SQL fragment
- */
- function conditional( $cond, $trueVal, $falseVal ) {
- return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
+
+ function unionQueries( $sqls, $all ) {
+ $glue = ' UNION ALL ';
+ return 'SELECT * ' . ( $all ? '':'/* UNION_UNIQUE */ ' ) . 'FROM (' . implode( $glue, $sqls ) . ')' ;
}
function wasDeadlock() {
return $this->lastErrno() == 'OCI-00060';
}
- function timestamp($ts = 0) {
- return wfTimestamp(TS_ORACLE, $ts);
+
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseOracle::duplicateTableStructure' ) {
+ $temporary = $temporary ? 'TRUE' : 'FALSE';
+ $oldName = trim(strtoupper($oldName), '"');
+ $oldParts = explode('_', $oldName);
+
+ $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]);
+ }
+
+ $tabName = substr($oldName, strlen($oldPrefix));
+
+ return $this->query( 'BEGIN DUPLICATE_TABLE(\'' . $tabName . '\', \'' . $oldPrefix . '\', \''.$newPrefix.'\', ' . $temporary . '); END;', $fname );
+ }
+
+ function timestamp( $ts = 0 ) {
+ return wfTimestamp( TS_ORACLE, $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) {
+ function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
# Ignore errors during error handling to avoid infinite
# recursion
- $ignore = $this->ignoreErrors(true);
+ $ignore = $this->ignoreErrors( true );
++$this->mErrorCount;
- if ($ignore || $tempIgnore) {
-echo "error ignored! query = [$sql]\n";
- wfDebug("SQL ERROR (ignored): $error\n");
+ if ( $ignore || $tempIgnore ) {
+ wfDebug( "SQL ERROR (ignored): $error\n" );
$this->ignoreErrors( $ignore );
- }
- else {
-echo "error!\n";
- $message = "A database error has occurred\n" .
- "Query: $sql\n" .
- "Function: $fname\n" .
- "Error: $errno $error\n";
- throw new DBUnexpectedError($this, $message);
+ } else {
+ throw new DBQueryError( $this, $error, $errno, $sql, $fname );
}
}
@@ -558,80 +819,252 @@ echo "error!\n";
* @return string wikitext of a link to the server software's web site
*/
function getSoftwareLink() {
- return "[http://www.oracle.com/ Oracle]";
+ return '[http://www.oracle.com/ Oracle]';
}
/**
* @return string Version information from the database
*/
function getServerVersion() {
- return oci_server_version($this->mConn);
+ return oci_server_version( $this->mConn );
}
/**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
*/
- function tableExists($table) {
- $etable= $this->addQuotes($table);
- $SQL = "SELECT 1 FROM user_tables WHERE table_name='$etable'";
- $res = $this->query($SQL);
- $count = $res ? oci_num_rows($res) : 0;
- if ($res)
- $this->freeResult($res);
+ function tableExists( $table ) {
+ $SQL = "SELECT 1 FROM user_tables WHERE table_name='$table'";
+ $res = $this->doQuery( $SQL );
+ if ( $res ) {
+ $count = $res->numRows();
+ $res->free();
+ } else {
+ $count = 0;
+ }
return $count;
}
/**
- * Query whether a given column exists in the mediawiki schema
+ * Function translates mysql_fetch_field() functionality on ORACLE.
+ * Caching is present for reducing query time.
+ * For internal calls. Use fieldInfo for normal usage.
+ * Returns false if the field doesn't exist
+ *
+ * @param Array $table
+ * @param String $field
*/
- function fieldExists( $table, $field ) {
- return true; // XXX
+ private function fieldInfoMulti( $table, $field ) {
+ $tableWhere = '';
+ $field = strtoupper($field);
+ if (is_array($table)) {
+ $table = array_map( array( &$this, 'tableName' ), $table );
+ $tableWhere = 'IN (';
+ foreach($table as &$singleTable) {
+ $singleTable = strtoupper(trim( $singleTable, '"' ));
+ if (isset($this->mFieldInfoCache["$singleTable.$field"])) {
+ return $this->mFieldInfoCache["$singleTable.$field"];
+ }
+ $tableWhere .= '\''.$singleTable.'\',';
+ }
+ $tableWhere = rtrim($tableWhere, ',').')';
+ } else {
+ $table = strtoupper(trim( $this->tableName($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 ) {
+ $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) {
+ $this->mFieldInfoCache["$singleTable.$field"] = false;
+ }
+ } else {
+ $this->mFieldInfoCache["$table.$field"] = false;
+ }
+ } else {
+ $fieldInfoTemp = new ORAField( $res->fetchRow() );
+ $table = $fieldInfoTemp->tableName();
+ $this->mFieldInfoCache["$table.$field"] = $fieldInfoTemp;
+ return $fieldInfoTemp;
+ }
}
function fieldInfo( $table, $field ) {
- return false; // XXX
+ if ( is_array( $table ) ) {
+ throw new DBUnexpectedError( $this, 'Database::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 = '' ) {
$this->mTrxLevel = 1;
}
+
function immediateCommit( $fname = '' ) {
return true;
}
+
function commit( $fname = '' ) {
- oci_commit($this->mConn);
+ oci_commit( $this->mConn );
$this->mTrxLevel = 0;
}
/* Not even sure why this is used in the main codebase... */
- function limitResultForUpdate($sql, $num) {
+ function limitResultForUpdate( $sql, $num ) {
return $sql;
}
- function strencode($s) {
- return str_replace("'", "''", $s);
+ /* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
+ $cmd = '';
+ $done = false;
+ $dollarquote = false;
+
+ $replacements = array();
+
+ while ( ! feof( $fp ) ) {
+ 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;
+ }
+
+ // Allow dollar quoting for function declarations
+ if ( substr( $line, 0, 8 ) == '/*$mw$*/' ) {
+ if ( $dollarquote ) {
+ $dollarquote = false;
+ $done = true;
+ } else {
+ $dollarquote = true;
+ }
+ } elseif ( !$dollarquote ) {
+ if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) {
+ $done = true;
+ $line = substr( $line, 0, $sl );
+ }
+ }
+
+ if ( $cmd != '' ) {
+ $cmd .= ' ';
+ }
+ $cmd .= "$line\n";
+
+ if ( $done ) {
+ $cmd = str_replace( ';;', ";", $cmd );
+ if ( strtolower( substr( $cmd, 0, 6 ) ) == 'define' ) {
+ if ( preg_match( '/^define\s*([^\s=]*)\s*=\s*\'\{\$([^\}]*)\}\'/', $cmd, $defines ) ) {
+ $replacements[$defines[2]] = $defines[1];
+ }
+ } else {
+ foreach ( $replacements as $mwVar => $scVar ) {
+ $cmd = str_replace( '&' . $scVar . '.', '{$' . $mwVar . '}', $cmd );
+ }
+
+ $cmd = $this->replaceVars( $cmd );
+ $res = $this->query( $cmd, __METHOD__ );
+ if ( $resultCallback ) {
+ call_user_func( $resultCallback, $res, $this );
+ }
+
+ if ( false === $res ) {
+ $err = $this->lastError();
+ return "Query \"{$cmd}\" failed with error code \"$err\".\n";
+ }
+ }
+
+ $cmd = '';
+ $done = false;
+ }
+ }
+ return true;
}
- function encodeBlob($b) {
- return new ORABlob($b);
+ 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" );
+ }
+
+ // 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;
+ }
+ $this->query( "$SQL $matches[1],$matches[2])" );
+ }
+
+ echo "<li>Table interwiki successfully populated</li>\n";
}
- function decodeBlob($b) {
- return $b; //return $b->load();
+
+ function strencode( $s ) {
+ return str_replace( "'", "''", $s );
}
function addQuotes( $s ) {
- global $wgLang;
- $s = $wgLang->checkTitleEncoding($s);
- return "'" . $this->strencode($s) . "'";
+ global $wgLang;
+ if ( isset( $wgLang->mLoaded ) && $wgLang->mLoaded ) {
+ $s = $wgLang->checkTitleEncoding( $s );
+ }
+ return "'" . $this->strencode( $s ) . "'";
}
function quote_ident( $s ) {
return $s;
}
- /* For now, does nothing */
- function selectDB( $db ) {
- return true;
+ function selectRow( $table, $vars, $conds, $fname = 'DatabaseOracle::selectRow', $options = array(), $join_conds = array() ) {
+ global $wgLang;
+
+ $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;
+ }
+ }
+
+ return parent::selectRow( $table, $vars, $conds2, $fname, $options, $join_conds );
}
/**
@@ -655,18 +1088,18 @@ echo "error!\n";
}
}
- if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
- 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['GROUP BY'] ) ) {
+ $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
+ }
+ if ( isset( $options['ORDER BY'] ) ) {
+ $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
}
- #if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
- #if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
- if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
+ # if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $tailOpts .= ' FOR UPDATE';
+ # if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $tailOpts .= ' LOCK IN SHARE MODE';
+ if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) {
+ $startOpts .= 'DISTINCT';
+ }
if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
$useIndex = $this->useIndexClause( $options['USE INDEX'] );
@@ -677,13 +1110,46 @@ echo "error!\n";
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
- public function setTimeout( $timeout ) {
- // @todo fixme no-op
+ public function delete( $table, $conds, $fname = 'DatabaseOracle::delete' ) {
+ global $wgLang;
+
+ if ( $wgLang != null ) {
+ $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 );
+ } else {
+ if ( is_array( $val ) ) {
+ $conds2[$col] = $val;
+ foreach ( $conds2[$col] as &$val2 ) {
+ $val2 = $wgLang->checkTitleEncoding( $val2 );
+ }
+ } else {
+ $conds2[$col] = $wgLang->checkTitleEncoding( $val );
+ }
+ }
+ }
+
+ return parent::delete( $table, $conds2, $fname );
+ } else {
+ return parent::delete( $table, $conds, $fname );
+ }
}
- function ping() {
- wfDebug( "Function ping() not written for DatabaseOracle.php yet");
- return true;
+ function bitNot( $field ) {
+ // expecting bit-fields smaller than 4bytes
+ return 'BITNOT(' . $bitField . ')';
+ }
+
+ function bitAnd( $fieldLeft, $fieldRight ) {
+ return 'BITAND(' . $fieldLeft . ', ' . $fieldRight . ')';
+ }
+
+ function bitOr( $fieldLeft, $fieldRight ) {
+ return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
}
/**
@@ -696,8 +1162,8 @@ echo "error!\n";
return 0;
}
- function setFakeSlaveLag( $lag ) {}
- function setFakeMaster( $enabled = true ) {}
+ function setFakeSlaveLag( $lag ) { }
+ function setFakeMaster( $enabled = true ) { }
function getDBname() {
return $this->mDBname;
@@ -706,19 +1172,28 @@ echo "error!\n";
function getServer() {
return $this->mServer;
}
-
- /**
- * No-op lock functions
- */
- public function lock( $lockName, $method ) {
- return true;
- }
- public function unlock( $lockName, $method ) {
- return true;
+
+ 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";
+ return 'SearchOracle';
}
-
} // end DatabaseOracle class
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index c940ad09..9072a5b2 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -11,7 +11,7 @@ class PostgresField {
static function fromText($db, $table, $field) {
global $wgDBmwschema;
- $q = <<<END
+ $q = <<<SQL
SELECT
CASE WHEN typname = 'int2' THEN 'smallint'
WHEN typname = 'int4' THEN 'integer'
@@ -27,7 +27,7 @@ AND atttypid=pg_type.oid
AND nspname=%s
AND relname=%s
AND attname=%s;
-END;
+SQL;
$res = $db->query(sprintf($q,
$db->addQuotes($wgDBmwschema),
$db->addQuotes($table),
@@ -68,11 +68,11 @@ END;
/**
* @ingroup Database
*/
-class DatabasePostgres extends Database {
- var $mInsertId = NULL;
- var $mLastResult = NULL;
- var $numeric_version = NULL;
- var $mAffectedRows = NULL;
+class DatabasePostgres extends DatabaseBase {
+ var $mInsertId = null;
+ var $mLastResult = null;
+ var $numeric_version = null;
+ var $mAffectedRows = null;
function DatabasePostgres($server = false, $user = false, $password = false, $dbName = false,
$failFunction = false, $flags = 0 )
@@ -84,6 +84,10 @@ class DatabasePostgres extends Database {
}
+ function getType() {
+ return 'postgres';
+ }
+
function cascadingDeletes() {
return true;
}
@@ -132,8 +136,8 @@ class DatabasePostgres extends Database {
global $wgDBport;
- if (!strlen($user)) { ## e.g. the class is being loaded
- return;
+ if (!strlen($user)) { ## e.g. the class is being loaded
+ return;
}
$this->close();
$this->mServer = $server;
@@ -152,7 +156,7 @@ class DatabasePostgres extends Database {
if ($port!=false && $port!="") {
$connectVars['port'] = $port;
}
- $connectString = $this->makeConnectionString( $connectVars );
+ $connectString = $this->makeConnectionString( $connectVars, PGSQL_CONNECT_FORCE_NEW );
$this->installErrorHandler();
$this->mConn = pg_connect( $connectString );
@@ -578,7 +582,7 @@ class DatabasePostgres extends Database {
$sql = mb_convert_encoding($sql,'UTF-8');
}
$this->mLastResult = pg_query( $this->mConn, $sql);
- $this->mAffectedRows = NULL; // use pg_affected_rows(mLastResult)
+ $this->mAffectedRows = null; // use pg_affected_rows(mLastResult)
return $this->mLastResult;
}
@@ -713,7 +717,7 @@ class DatabasePostgres extends Database {
$sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'";
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
while ( $row = $this->fetchObject( $res ) ) {
if ( $row->indexname == $this->indexName( $index ) ) {
@@ -730,7 +734,7 @@ class DatabasePostgres extends Database {
")'";
$res = $this->query( $sql, $fname );
if ( !$res )
- return NULL;
+ return null;
while ($row = $this->fetchObject( $res ))
return true;
return false;
@@ -873,6 +877,81 @@ class DatabasePostgres extends Database {
}
+ /**
+ * INSERT SELECT wrapper
+ * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
+ * Source items may be literals rather then 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.
+ * @todo FIXME: implement this a little better (seperate select/insert)?
+ */
+ function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect',
+ $insertOptions = array(), $selectOptions = array() )
+ {
+ $destTable = $this->tableName( $destTable );
+
+ // If IGNORE is set, we use savepoints to emulate mysql's behavior
+ $ignore = in_array( 'IGNORE', $insertOptions ) ? 'mw' : '';
+
+ 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 );
+ }
+
+ // If we are not in a transaction, we need to be for savepoint trickery
+ $didbegin = 0;
+ if ( $ignore ) {
+ if( !$this->mTrxLevel ) {
+ $this->begin();
+ $didbegin = 1;
+ }
+ $olde = error_reporting( 0 );
+ $numrowsinserted = 0;
+ pg_query( $this->mConn, "SAVEPOINT $ignore");
+ }
+
+ $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
+ " SELECT $startOpts " . implode( ',', $varMap ) .
+ " FROM $srcTable $useIndex";
+
+ if ( $conds != '*') {
+ $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ }
+
+ $sql .= " $tailOpts";
+
+ $res = (bool)$this->query( $sql, $fname, $ignore );
+ if( $ignore ) {
+ $bar = pg_last_error();
+ if( $bar != false ) {
+ pg_query( $this->mConn, "ROLLBACK TO $ignore" );
+ } else {
+ pg_query( $this->mConn, "RELEASE $ignore" );
+ $numrowsinserted++;
+ }
+ $olde = error_reporting( $olde );
+ if( $didbegin ) {
+ $this->commit();
+ }
+
+ // Set the affected row count for the whole operation
+ $this->mAffectedRows = $numrowsinserted;
+
+ // IGNORE always returns true
+ return true;
+ }
+
+ return $res;
+ }
+
function tableName( $name ) {
# Replace reserved words with better ones
switch( $name ) {
@@ -898,7 +977,7 @@ class DatabasePostgres extends Database {
}
/**
- * Return the current value of a sequence. Assumes it has ben nextval'ed in this session.
+ * Return the current value of a sequence. Assumes it has been nextval'ed in this session.
*/
function currentSequenceValue( $seqName ) {
$safeseq = preg_replace( "/'/", "''", $seqName );
@@ -909,13 +988,6 @@ class DatabasePostgres extends Database {
return $currval;
}
- /**
- * Postgres does not have a "USE INDEX" clause, so return an empty string
- */
- function useIndexClause( $index ) {
- return '';
- }
-
# REPLACE query wrapper
# Postgres simulates this with a DELETE followed by INSERT
# $row is the row to insert, an associative array
@@ -1009,31 +1081,18 @@ class DatabasePostgres extends Database {
return $size;
}
- function lowPriorityOption() {
- return '';
- }
-
function limitResult($sql, $limit, $offset=false) {
return "$sql LIMIT $limit ".(is_numeric($offset)?" OFFSET {$offset} ":"");
}
- /**
- * Returns an SQL expression for a simple conditional.
- * Uses CASE on Postgres
- *
- * @param $cond String: SQL expression which will result in a boolean value
- * @param $trueVal String: SQL expression to return if true
- * @param $falseVal String: SQL expression to return if false
- * @return String: SQL fragment
- */
- function conditional( $cond, $trueVal, $falseVal ) {
- return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
- }
-
function wasDeadlock() {
return $this->lastErrno() == '40P01';
}
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabasePostgres::duplicateTableStructure' ) {
+ return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
+ }
+
function timestamp( $ts=0 ) {
return wfTimestamp(TS_POSTGRES,$ts);
}
@@ -1126,18 +1185,18 @@ class DatabasePostgres extends Database {
function triggerExists( $table, $trigger ) {
global $wgDBmwschema;
- $q = <<<END
+ $q = <<<SQL
SELECT 1 FROM pg_class, pg_namespace, pg_trigger
WHERE relnamespace=pg_namespace.oid AND relkind='r'
AND tgrelid=pg_class.oid
AND nspname=%s AND relname=%s AND tgname=%s
-END;
+SQL;
$res = $this->query(sprintf($q,
$this->addQuotes($wgDBmwschema),
$this->addQuotes($table),
$this->addQuotes($trigger)));
if (!$res)
- return NULL;
+ return null;
$rows = $res->numRows();
$this->freeResult( $res );
return $rows;
@@ -1161,7 +1220,7 @@ END;
$this->addQuotes($constraint));
$res = $this->query($SQL);
if (!$res)
- return NULL;
+ return null;
$rows = $res->numRows();
$this->freeResult($res);
return $rows;
@@ -1252,11 +1311,17 @@ END;
if (!$res) {
print "<b>FAILED</b>. Make sure that the user \"" . htmlspecialchars( $wgDBuser ) .
"\" can write to the schema \"" . htmlspecialchars( $wgDBmwschema ) . "\"</li>\n";
- dieout("</ul>");
+ dieout(""); # Will close the main list <ul> and finish the page.
}
$this->doQuery("DROP TABLE $safeschema.$ctest");
- $res = dbsource( "../maintenance/postgres/tables.sql", $this);
+ $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);
@@ -1274,10 +1339,13 @@ END;
"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 ) {
- dieout( "<li>Could not find the interwiki.sql file");
+ 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 ";
@@ -1289,7 +1357,7 @@ END;
}
$this->query("$SQL $matches[1],$matches[2])");
}
- print " (table interwiki successfully populated)...\n";
+ print " successfully populated.</li>\n";
$this->doQuery("COMMIT");
}
@@ -1324,11 +1392,6 @@ END;
return '"' . preg_replace( '/"/', '""', $s) . '"';
}
- /* For now, does nothing */
- function selectDB( $db ) {
- return true;
- }
-
/**
* Postgres specific version of replaceVars.
* Calls the parent version in Database.php
@@ -1392,15 +1455,6 @@ END;
return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
}
- public function setTimeout( $timeout ) {
- // @todo fixme no-op
- }
-
- function ping() {
- wfDebug( "Function ping() not written for DatabasePostgres.php yet");
- return true;
- }
-
/**
* How lagged is this slave?
*
@@ -1425,17 +1479,7 @@ END;
return implode( ' || ', $stringList );
}
- /* These are not used yet, but we know we don't want the default version */
-
- public function lock( $lockName, $method ) {
- return true;
- }
- public function unlock( $lockName, $method ) {
- return true;
- }
-
public function getSearchEngine() {
return "SearchPostgres";
}
-
} // end DatabasePostgres class
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index 455c0b48..c149cf04 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -10,7 +10,7 @@
/**
* @ingroup Database
*/
-class DatabaseSqlite extends Database {
+class DatabaseSqlite extends DatabaseBase {
var $mAffectedRows;
var $mLastResult;
@@ -18,110 +18,162 @@ class DatabaseSqlite extends Database {
var $mName;
/**
- * Constructor
+ * Constructor.
+ * Parameters $server, $user and $password are not used.
*/
- function __construct($server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0) {
- global $wgOut,$wgSQLiteDataDir, $wgSQLiteDataDirMode;
- if ("$wgSQLiteDataDir" == '') $wgSQLiteDataDir = dirname($_SERVER['DOCUMENT_ROOT']).'/data';
- if (!is_dir($wgSQLiteDataDir)) wfMkdirParents( $wgSQLiteDataDir, $wgSQLiteDataDirMode );
+ function __construct( $server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0 ) {
$this->mFailFunction = $failFunction;
$this->mFlags = $flags;
- $this->mDatabaseFile = "$wgSQLiteDataDir/$dbName.sqlite";
$this->mName = $dbName;
- $this->open($server, $user, $password, $dbName);
+ $this->open( $server, $user, $password, $dbName );
+ }
+
+ function getType() {
+ return 'sqlite';
}
/**
- * todo: check if these should be true like parent class
+ * @todo: check if it 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 DatabaseSqlite($server, $user, $password, $dbName, $failFunction, $flags);
+ static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) {
+ return new DatabaseSqlite( $server, $user, $password, $dbName, $failFunction, $flags );
}
/** Open an SQLite database and return a resource handle to it
* NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
*/
- function open($server,$user,$pass,$dbName) {
- $this->mConn = false;
- if ($dbName) {
- $file = $this->mDatabaseFile;
- try {
- if ( $this->mFlags & DBO_PERSISTENT ) {
- $this->mConn = new PDO( "sqlite:$file", $user, $pass,
- array( PDO::ATTR_PERSISTENT => true ) );
- } else {
- $this->mConn = new PDO( "sqlite:$file", $user, $pass );
- }
- } catch ( PDOException $e ) {
- $err = $e->getMessage();
- }
- if ( $this->mConn === false ) {
- wfDebug( "DB connection error: $err\n" );
- if ( !$this->mFailFunction ) {
- throw new DBConnectionError( $this, $err );
- } else {
- return false;
- }
+ function open( $server, $user, $pass, $dbName ) {
+ global $wgSQLiteDataDir;
- }
- $this->mOpened = $this->mConn;
- # set error codes only, don't raise exceptions
- $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
+ $fileName = self::generateFileName( $wgSQLiteDataDir, $dbName );
+ if ( !is_readable( $fileName ) ) {
+ throw new DBConnectionError( $this, "SQLite database not accessible" ); $this->mConn = false;
}
+ $this->openFile( $fileName );
return $this->mConn;
}
/**
+ * Opens a database file
+ * @return SQL connection or false if failed
+ */
+ function openFile( $fileName ) {
+ $this->mDatabaseFile = $fileName;
+ try {
+ if ( $this->mFlags & DBO_PERSISTENT ) {
+ $this->mConn = new PDO( "sqlite:$fileName", '', '',
+ array( PDO::ATTR_PERSISTENT => true ) );
+ } else {
+ $this->mConn = new PDO( "sqlite:$fileName", '', '' );
+ }
+ } catch ( PDOException $e ) {
+ $err = $e->getMessage();
+ }
+ if ( $this->mConn === false ) {
+ wfDebug( "DB connection error: $err\n" );
+ if ( !$this->mFailFunction ) {
+ throw new DBConnectionError( $this, $err );
+ } else {
+ return false;
+ }
+
+ }
+ $this->mOpened = !!$this->mConn;
+ # set error codes only, don't raise exceptions
+ if ( $this->mOpened ) {
+ $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
+ return true;
+ }
+ }
+
+ /**
* Close an SQLite database
*/
function close() {
$this->mOpened = false;
- if (is_object($this->mConn)) {
- if ($this->trxLevel()) $this->immediateCommit();
+ if ( is_object( $this->mConn ) ) {
+ if ( $this->trxLevel() ) $this->commit();
$this->mConn = null;
}
return true;
}
/**
+ * Generates a database file name. Explicitly public for installer.
+ * @param $dir String: Directory where database resides
+ * @param $dbName String: Database name
+ * @return String
+ */
+ public static function generateFileName( $dir, $dbName ) {
+ return "$dir/$dbName.sqlite";
+ }
+
+ /**
+ * Returns version of currently supported SQLite fulltext search module or false if none present.
+ * @return String
+ */
+ function getFulltextSearchModule() {
+ $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';
+ }
+ return false;
+ }
+
+ /**
* SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
*/
- function doQuery($sql) {
- $res = $this->mConn->query($sql);
- if ($res === false) {
+ function doQuery( $sql ) {
+ $res = $this->mConn->query( $sql );
+ if ( $res === false ) {
return false;
} else {
$r = $res instanceof ResultWrapper ? $res->result : $res;
$this->mAffectedRows = $r->rowCount();
- $res = new ResultWrapper($this,$r->fetchAll());
+ $res = new ResultWrapper( $this, $r->fetchAll() );
}
return $res;
}
- function freeResult($res) {
- if ($res instanceof ResultWrapper) $res->result = NULL; else $res = NULL;
+ function freeResult( $res ) {
+ if ( $res instanceof ResultWrapper )
+ $res->result = null;
+ else
+ $res = null;
}
- function fetchObject($res) {
- if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res;
- $cur = current($r);
- if (is_array($cur)) {
- next($r);
+ function fetchObject( $res ) {
+ if ( $res instanceof ResultWrapper )
+ $r =& $res->result;
+ else
+ $r =& $res;
+
+ $cur = current( $r );
+ if ( is_array( $cur ) ) {
+ next( $r );
$obj = new stdClass;
- foreach ($cur as $k => $v) if (!is_numeric($k)) $obj->$k = $v;
+ foreach ( $cur as $k => $v )
+ if ( !is_numeric( $k ) )
+ $obj->$k = $v;
+
return $obj;
}
return false;
}
- function fetchRow($res) {
- if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res;
- $cur = current($r);
- if (is_array($cur)) {
- next($r);
+ function fetchRow( $res ) {
+ if ( $res instanceof ResultWrapper )
+ $r =& $res->result;
+ else
+ $r =& $res;
+
+ $cur = current( $r );
+ if ( is_array( $cur ) ) {
+ next( $r );
return $cur;
}
return false;
@@ -130,20 +182,20 @@ class DatabaseSqlite extends Database {
/**
* The PDO::Statement class implements the array interface so count() will work
*/
- function numRows($res) {
+ function numRows( $res ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
- return count($r);
+ return count( $r );
}
- function numFields($res) {
+ function numFields( $res ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
- return is_array($r) ? count($r[0]) : 0;
+ return is_array( $r ) ? count( $r[0] ) : 0;
}
- function fieldName($res,$n) {
+ function fieldName( $res, $n ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
- if (is_array($r)) {
- $keys = array_keys($r[0]);
+ if ( is_array( $r ) ) {
+ $keys = array_keys( $r[0] );
return $keys[$n];
}
return false;
@@ -152,8 +204,8 @@ class DatabaseSqlite extends Database {
/**
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
*/
- function tableName($name) {
- return str_replace('`','',parent::tableName($name));
+ function tableName( $name ) {
+ return str_replace( '`', '', parent::tableName( $name ) );
}
/**
@@ -170,20 +222,26 @@ class DatabaseSqlite extends Database {
return $this->mConn->lastInsertId();
}
- function dataSeek($res,$row) {
- if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res;
- reset($r);
- if ($row > 0) for ($i = 0; $i < $row; $i++) next($r);
+ function dataSeek( $res, $row ) {
+ if ( $res instanceof ResultWrapper )
+ $r =& $res->result;
+ else
+ $r =& $res;
+ reset( $r );
+ if ( $row > 0 )
+ for ( $i = 0; $i < $row; $i++ )
+ next( $r );
}
function lastError() {
- if (!is_object($this->mConn)) return "Cannot return last error, no db connection";
+ if ( !is_object( $this->mConn ) )
+ return "Cannot return last error, no db connection";
$e = $this->mConn->errorInfo();
- return isset($e[2]) ? $e[2] : '';
+ return isset( $e[2] ) ? $e[2] : '';
}
function lastErrno() {
- if (!is_object($this->mConn)) {
+ if ( !is_object( $this->mConn ) ) {
return "Cannot return last error, no db connection";
} else {
$info = $this->mConn->errorInfo();
@@ -200,7 +258,7 @@ class DatabaseSqlite extends Database {
* Returns false if the index does not exist
* - if errors are explicitly ignored, returns NULL on failure
*/
- function indexInfo($table, $index, $fname = 'Database::indexExists') {
+ function indexInfo( $table, $index, $fname = 'DatabaseSqlite::indexExists' ) {
$sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
$res = $this->query( $sql, $fname );
if ( !$res ) {
@@ -216,8 +274,8 @@ class DatabaseSqlite extends Database {
return $info;
}
- function indexUnique($table, $index, $fname = 'Database::indexUnique') {
- $row = $this->selectRow( 'sqlite_master', '*',
+ function indexUnique( $table, $index, $fname = 'DatabaseSqlite::indexUnique' ) {
+ $row = $this->selectRow( 'sqlite_master', '*',
array(
'type' => 'index',
'name' => $this->indexName( $index ),
@@ -239,67 +297,81 @@ class DatabaseSqlite extends Database {
/**
* Filter the options used in SELECT statements
*/
- function makeSelectOptions($options) {
- foreach ($options as $k => $v) if (is_numeric($k) && $v == 'FOR UPDATE') $options[$k] = '';
- return parent::makeSelectOptions($options);
+ function makeSelectOptions( $options ) {
+ foreach ( $options as $k => $v )
+ if ( is_numeric( $k ) && $v == 'FOR UPDATE' )
+ $options[$k] = '';
+ return parent::makeSelectOptions( $options );
}
/**
- * Based on MySQL method (parent) with some prior SQLite-sepcific adjustments
+ * 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);
+ function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) {
+ 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') $options[$k] = 'OR 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])) {
+ if ( isset( $a[0] ) && is_array( $a[0] ) ) {
$ret = true;
- foreach ($a as $k => $v) if (!parent::insert($table,$v,"$fname/multi-row",$options)) $ret = false;
+ foreach ( $a as $k => $v )
+ if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) )
+ $ret = false;
+ } else {
+ $ret = parent::insert( $table, $a, "$fname/single-row", $options );
}
- else $ret = parent::insert($table,$a,"$fname/single-row",$options);
return $ret;
}
- /**
- * SQLite does not have a "USE INDEX" clause, so return an empty string
- */
- function useIndexClause($index) {
- return '';
+ 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" ) )
+ $ret = false;
+ } else {
+ $ret = parent::replace( $table, $uniqueIndexes, $rows, "$fname/single-row" );
+ }
+
+ return $ret;
}
/**
* Returns the size of a text field, or -1 for "unlimited"
* In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though.
*/
- function textFieldSize($table, $field) {
- return -1;
+ function textFieldSize( $table, $field ) {
+ return - 1;
}
- /**
- * No low priority option in SQLite
- */
- function lowPriorityOption() {
- return '';
+ function unionSupportsOrderAndLimit() {
+ return false;
}
- /**
- * Returns an SQL expression for a simple conditional.
- * - uses CASE on SQLite
- */
- function conditional($cond, $trueVal, $falseVal) {
- return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
+ function unionQueries( $sqls, $all ) {
+ $glue = $all ? ' UNION ALL ' : ' UNION ';
+ return implode( $glue, $sqls );
}
function wasDeadlock() {
- return $this->lastErrno() == SQLITE_BUSY;
+ return $this->lastErrno() == 5; // SQLITE_BUSY
}
function wasErrorReissuable() {
- return $this->lastErrno() == SQLITE_SCHEMA;
+ return $this->lastErrno() == 17; // SQLITE_SCHEMA;
+ }
+
+ function wasReadOnlyError() {
+ return $this->lastErrno() == 8; // SQLITE_READONLY;
}
/**
@@ -313,15 +385,14 @@ class DatabaseSqlite extends Database {
* @return string Version information from the database
*/
function getServerVersion() {
- global $wgContLang;
- $ver = $this->mConn->getAttribute(PDO::ATTR_SERVER_VERSION);
+ $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
return $ver;
}
/**
* Query whether a given column exists in the mediawiki schema
*/
- function fieldExists($table, $field, $fname = '') {
+ function fieldExists( $table, $field, $fname = '' ) {
$info = $this->fieldInfo( $table, $field );
return (bool)$info;
}
@@ -330,7 +401,7 @@ class DatabaseSqlite extends Database {
* Get information about a given field
* Returns false if the field does not exist.
*/
- function fieldInfo($table, $field) {
+ function fieldInfo( $table, $field ) {
$tableName = $this->tableName( $table );
$sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
$res = $this->query( $sql, __METHOD__ );
@@ -343,73 +414,60 @@ class DatabaseSqlite extends Database {
}
function begin( $fname = '' ) {
- if ($this->mTrxLevel == 1) $this->commit();
+ if ( $this->mTrxLevel == 1 ) $this->commit();
$this->mConn->beginTransaction();
$this->mTrxLevel = 1;
}
function commit( $fname = '' ) {
- if ($this->mTrxLevel == 0) return;
+ if ( $this->mTrxLevel == 0 ) return;
$this->mConn->commit();
$this->mTrxLevel = 0;
}
function rollback( $fname = '' ) {
- if ($this->mTrxLevel == 0) return;
+ if ( $this->mTrxLevel == 0 ) return;
$this->mConn->rollBack();
$this->mTrxLevel = 0;
}
- function limitResultForUpdate($sql, $num) {
+ function limitResultForUpdate( $sql, $num ) {
return $this->limitResult( $sql, $num );
}
- function strencode($s) {
- return substr($this->addQuotes($s),1,-1);
+ function strencode( $s ) {
+ return substr( $this->addQuotes( $s ), 1, - 1 );
}
- function encodeBlob($b) {
+ function encodeBlob( $b ) {
return new Blob( $b );
}
- function decodeBlob($b) {
- if ($b instanceof Blob) {
+ function decodeBlob( $b ) {
+ if ( $b instanceof Blob ) {
$b = $b->fetch();
}
return $b;
}
- function addQuotes($s) {
+ function addQuotes( $s ) {
if ( $s instanceof Blob ) {
return "x'" . bin2hex( $s->fetch() ) . "'";
} else {
- return $this->mConn->quote($s);
+ return $this->mConn->quote( $s );
}
}
- function quote_ident($s) { return $s; }
-
- /**
- * Not possible in SQLite
- * We have ATTACH_DATABASE but that requires database selectors before the
- * table names and in any case is really a different concept to MySQL's USE
- */
- function selectDB($db) {
- if ( $db != $this->mName ) {
- throw new MWException( 'selectDB is not implemented in SQLite' );
- }
+ function quote_ident( $s ) {
+ return $s;
}
- /**
- * not done
- */
- public function setTimeout($timeout) { return; }
-
- /**
- * No-op for a non-networked database
- */
- function ping() {
- return true;
+ function buildLike() {
+ $params = func_get_args();
+ if ( count( $params ) > 0 && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ return parent::buildLike( $params ) . "ESCAPE '\' ";
}
/**
@@ -424,40 +482,33 @@ class DatabaseSqlite extends Database {
* - 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,$wgSQLiteDataDir,$wgDBTableOptions;
- $wgDBTableOptions = '';
+ global $IP;
# Process common MySQL/SQLite table definitions
$err = $this->sourceFile( "$IP/maintenance/tables.sql" );
- if ($err !== true) {
- $this->reportQueryError($err,0,$sql,__FUNCTION__);
- exit( 1 );
+ 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("<li>Could not find the interwiki.sql file");
+ $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);
+ while ( !feof( $f ) ) {
+ $line = fgets( $f, 1024 );
$matches = array();
- if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) continue;
- $this->query("$sql $matches[1],$matches[2])");
+ if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) continue;
+ $this->query( "$sql $matches[1],$matches[2])" );
}
}
- /**
- * No-op lock functions
- */
- public function lock( $lockName, $method ) {
- return true;
- }
- public function unlock( $lockName, $method ) {
- return true;
- }
-
public function getSearchEngine() {
- return "SearchEngineDummy";
+ return "SearchSqlite";
}
/**
@@ -471,23 +522,33 @@ class DatabaseSqlite extends Database {
protected function replaceVars( $s ) {
$s = parent::replaceVars( $s );
- if ( preg_match( '/^\s*CREATE TABLE/i', $s ) ) {
+ if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
// CREATE TABLE hacks to allow schema file sharing with MySQL
-
+
// binary/varbinary column type -> blob
- $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'blob\1', $s );
+ $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s );
// no such thing as unsigned
- $s = preg_replace( '/\bunsigned\b/i', '', $s );
- // INT -> INTEGER for primary keys
- $s = preg_replacE( '/\bint\b/i', 'integer', $s );
+ $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 );
+ // varchar -> TEXT
+ $s = preg_replace( '/\bvarchar\(\d+\)/i', 'TEXT', $s );
+ // TEXT normalization
+ $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
+ // BLOB normalization
+ $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s );
+ // BOOL -> INTEGER
+ $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s );
+ // 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( '/enum\([^)]*\)/i', 'BLOB', $s );
// binary collation type -> nothing
$s = preg_replace( '/\bbinary\b/i', '', $s );
// auto_increment -> autoincrement
- $s = preg_replace( '/\bauto_increment\b/i', 'autoincrement', $s );
+ $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
// No explicit options
- $s = preg_replace( '/\)[^)]*$/', ')', $s );
+ $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
} elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
// No truncated indexes
$s = preg_replace( '/\(\d+\)/', '', $s );
@@ -504,9 +565,31 @@ class DatabaseSqlite extends Database {
return '(' . implode( ') || (', $stringList ) . ')';
}
+ function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseSqlite::duplicateTableStructure' ) {
+ $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name='$oldName' AND type='table'", $fname );
+ $obj = $this->fetchObject( $res );
+ if ( !$obj ) {
+ throw new MWException( "Couldn't retrieve structure for table $oldName" );
+ }
+ $sql = $obj->sql;
+ $sql = preg_replace( '/\b' . preg_quote( $oldName ) . '\b/', $newName, $sql, 1 );
+ return $this->query( $sql, $fname );
+ }
+
} // end DatabaseSqlite class
/**
+ * This class allows simple acccess to a SQLite database independently from main database settings
+ * @ingroup Database
+ */
+class DatabaseSqliteStandalone extends DatabaseSqlite {
+ public function __construct( $fileName, $flags = 0 ) {
+ $this->mFlags = $flags;
+ $this->openFile( $fileName );
+ }
+}
+
+/**
* @ingroup Database
*/
class SQLiteField {
@@ -545,10 +628,9 @@ class SQLiteField {
# isKey(), isMultipleKey() not implemented, MySQL-specific concept.
# Suggest removal from base class [TS]
-
+
function type() {
return $this->info->type;
}
} // end SQLiteField
-
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index 3876d71f..10c87133 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -25,7 +25,7 @@ abstract class LBFactory {
/**
* Shut down, close connections and destroy the cached instance.
- *
+ *
*/
static function destroyInstance() {
if ( self::$instance ) {
@@ -41,7 +41,7 @@ abstract class LBFactory {
abstract function __construct( $conf );
/**
- * Create a new load balancer object. The resulting object will be untracked,
+ * 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
@@ -58,8 +58,8 @@ abstract class LBFactory {
abstract function getMainLB( $wiki = false );
/*
- * Create a new load balancer for external storage. The resulting object will be
- * untracked, not chronology-protected, and the caller is responsible for
+ * Create a new load balancer for external storage. The resulting object will be
+ * untracked, not chronology-protected, and the caller is responsible for
* cleaning it up.
*
* @param string $cluster External storage cluster, or false for core
@@ -142,8 +142,8 @@ class LBFactory_Simple extends LBFactory {
}
return new LoadBalancer( array(
- 'servers' => $servers,
- 'masterWaitTimeout' => $wgMasterWaitTimeout
+ 'servers' => $servers,
+ 'masterWaitTimeout' => $wgMasterWaitTimeout
));
}
@@ -162,7 +162,7 @@ class LBFactory_Simple extends LBFactory {
throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
}
return new LoadBalancer( array(
- 'servers' => $wgExternalServers[$cluster]
+ 'servers' => $wgExternalServers[$cluster]
));
}
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index 0b8ef05a..083b70b3 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -809,7 +809,7 @@ class LoadBalancer {
foreach ( $this->mConns as $conns2 ) {
foreach ( $conns2 as $conns3 ) {
foreach ( $conns3 as $conn ) {
- $conn->immediateCommit();
+ $conn->commit();
}
}
}
@@ -831,7 +831,7 @@ class LoadBalancer {
}
}
- function waitTimeout( $value = NULL ) {
+ function waitTimeout( $value = null ) {
return wfSetVar( $this->mWaitTimeout, $value );
}
@@ -878,14 +878,18 @@ class LoadBalancer {
* Get the hostname and lag time of the most-lagged slave.
* This is useful for maintenance scripts that need to throttle their updates.
* May attempt to open connections to slaves on the default DB.
+ * @param $wiki string Wiki ID, or false for the default database
*/
- function getMaxLag() {
+ function getMaxLag( $wiki = false ) {
$maxLag = -1;
$host = '';
foreach ( $this->mServers as $i => $conn ) {
- $conn = $this->getAnyOpenConnection( $i );
+ $conn = false;
+ if ( $wiki === false ) {
+ $conn = $this->getAnyOpenConnection( $i );
+ }
if ( !$conn ) {
- $conn = $this->openConnection( $i );
+ $conn = $this->openConnection( $i, $wiki );
}
if ( !$conn ) {
continue;
@@ -912,4 +916,11 @@ class LoadBalancer {
$this->mLagTimes = $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
return $this->mLagTimes;
}
+
+ /**
+ * Clear the cache for getLagTimes
+ */
+ function clearLagTimeCache() {
+ $this->mLagTimes = null;
+ }
}