<?php
/**
+ * @defgroup Database Database
+ *
+ * @file
+ * @ingroup Database
* This file deals with MySQL interface functions
* and query specifics/optimisations
*/
/**
* Database abstraction object
- * @addtogroup Database
+ * @ingroup Database
*/
class Database {
return $this->$name;
}
+ function getWikiID() {
+ if( $this->mTablePrefix ) {
+ return "{$this->mDBname}-{$this->mTablePrefix}";
+ } else {
+ return $this->mDBname;
+ }
+ }
+
#------------------------------------------------------------------------------
# Other functions
#------------------------------------------------------------------------------
* @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
*/
- function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array() )
+ function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() )
{
+ $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
+ return $this->query( $sql, $fname );
+ }
+
+ /**
+ * 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 string, the SQL text
+ */
+ function selectSQLText( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() ) {
if( is_array( $vars ) ) {
$vars = implode( ',', $vars );
}
$options = array( $options );
}
if( is_array( $table ) ) {
- if ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
- $from = ' FROM ' . $this->tableNamesWithUseIndex( $table, $options['USE INDEX'] );
+ if ( !empty($join_conds) || is_array( @$options['USE INDEX'] ) )
+ $from = ' FROM ' . $this->tableNamesWithUseIndexOrJOIN( $table, @$options['USE INDEX'], $join_conds );
else
$from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
} elseif ($table!='') {
if (isset($options['EXPLAIN'])) {
$sql = 'EXPLAIN ' . $sql;
}
- return $this->query( $sql, $fname );
+ return $sql;
}
/**
*
* @todo migrate documentation to phpdocumentor format
*/
- function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array() ) {
+ function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array(), $join_conds = array() ) {
$options['LIMIT'] = 1;
- $res = $this->select( $table, $vars, $conds, $fname, $options );
+ $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
if ( $res === false )
return false;
if ( !$this->numRows($res) ) {
/**
* Format a table name ready for use in constructing an SQL query
*
- * This does two important things: it quotes table names which as necessary,
- * and it adds a table prefix if there is one.
+ * This does two important things: it quotes the table names to clean them up,
+ * and it adds a table prefix if only given a table name with no quotes.
*
* All functions of this object which require a table name call this function
* themselves. Pass the canonical name to such functions. This is only needed
* when calling query() directly.
*
* @param string $name database table name
+ * @return string full database name
*/
function tableName( $name ) {
- global $wgSharedDB;
- # Skip quoted literals
- if ( $name{0} != '`' ) {
- if ( $this->mTablePrefix !== '' && strpos( $name, '.' ) === false ) {
- $name = "{$this->mTablePrefix}$name";
- }
- if ( isset( $wgSharedDB ) && "{$this->mTablePrefix}user" == $name ) {
- $name = "`$wgSharedDB`.`$name`";
- } else {
- # Standard quoting
- $name = "`$name`";
- }
+ global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
+ # Skip the entire process when we have a string quoted on both ends.
+ # Note that we check the end so that we will still quote any use of
+ # use of `database`.table. But won't break things if someone wants
+ # to query a database table with a dot in the name.
+ if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) return $name;
+
+ # Lets test for any bits of text that should never show up in a table
+ # name. Basically anything like JOIN or ON which are actually part of
+ # SQL queries, but may end up inside of the table value to combine
+ # sql. Such as how the API is doing.
+ # Note that we use a whitespace test rather than a \b test to avoid
+ # any remote case where a word like on may be inside of a table name
+ # surrounded by symbols which may be considered word breaks.
+ if( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) return $name;
+
+ # Split database and table into proper variables.
+ # We reverse the explode so that database.table and table both output
+ # the correct table.
+ @list( $table, $database ) = array_reverse( explode( '.', $name, 2 ) );
+ $prefix = $this->mTablePrefix; # Default prefix
+
+ # A database name has been specified in input. Quote the table name
+ # because we don't want any prefixes added.
+ if( isset($database) ) $table = ( $table[0] == '`' ? $table : "`{$table}`" );
+
+ # Note that we use the long format because php will complain in in_array if
+ # the input is not an array, and will complain in is_array if it is not set.
+ if( !isset( $database ) # Don't use shared database if pre selected.
+ && isset( $wgSharedDB ) # We have a shared database
+ && $table[0] != '`' # Paranoia check to prevent shared tables listing '`table`'
+ && isset( $wgSharedTables )
+ && is_array( $wgSharedTables )
+ && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
+ $database = $wgSharedDB;
+ $prefix = isset( $wgSharedprefix ) ? $wgSharedprefix : $prefix;
}
- return $name;
+
+ # Quote the $database and $table and apply the prefix if not quoted.
+ if( isset($database) ) $database = ( $database[0] == '`' ? $database : "`{$database}`" );
+ $table = ( $table[0] == '`' ? $table : "`{$prefix}{$table}`" );
+
+ # Merge our database and table into our final table name.
+ $tableName = ( isset($database) ? "{$database}.{$table}" : "{$table}" );
+
+ # We're finished, return.
+ return $tableName;
}
/**
/**
* @private
*/
- function tableNamesWithUseIndex( $tables, $use_index ) {
+ function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
$ret = array();
-
- foreach ( $tables as $table )
- if ( @$use_index[$table] !== null )
- $ret[] = $this->tableName( $table ) . ' ' . $this->useIndexClause( implode( ',', (array)$use_index[$table] ) );
- else
- $ret[] = $this->tableName( $table );
-
- return implode( ',', $ret );
+ $retJOIN = array();
+ $use_index_safe = is_array($use_index) ? $use_index : array();
+ $join_conds_safe = is_array($join_conds) ? $join_conds : array();
+ foreach ( $tables as $table ) {
+ // Is there a JOIN and INDEX clause for this table?
+ if ( isset($join_conds_safe[$table]) && isset($use_index_safe[$table]) ) {
+ $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+ $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
+ $retJOIN[] = $tableClause;
+ // Is there an INDEX clause?
+ } else if ( isset($use_index_safe[$table]) ) {
+ $tableClause = $this->tableName( $table );
+ $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
+ $ret[] = $tableClause;
+ // Is there a JOIN clause?
+ } else if ( isset($join_conds_safe[$table]) ) {
+ $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
+ $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
+ $retJOIN[] = $tableClause;
+ } else {
+ $tableClause = $this->tableName( $table );
+ $ret[] = $tableClause;
+ }
+ }
+ // We can't separate explicit JOIN clauses with ',', use ' ' for those
+ $straightJoins = !empty($ret) ? implode( ',', $ret ) : "";
+ $otherJoins = !empty($retJOIN) ? implode( ' ', $retJOIN ) : "";
+ // Compile our final table clause
+ return implode(' ',array($straightJoins,$otherJoins) );
}
/**
if( !is_numeric($limit) ) {
throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
}
- return " $sql LIMIT "
+ return "$sql LIMIT "
. ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" )
. "{$limit} ";
}
* Database abstraction object for mySQL
* Inherit all methods and properties of Database::Database()
*
- * @addtogroup Database
+ * @ingroup Database
* @see Database
*/
class DatabaseMysql extends Database {
/**
* Utility class.
- * @addtogroup Database
+ * @ingroup Database
*/
class DBObject {
public $mData;
/**
* Utility class
- * @addtogroup Database
+ * @ingroup Database
*
* This allows us to distinguish a blob from a normal string and an array of strings
*/
/**
* Utility class.
- * @addtogroup Database
+ * @ingroup Database
*/
class MySQLField {
private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_key, $type;
+ $is_pk, $is_unique, $is_multiple, $is_key, $type;
function __construct ($info) {
$this->name = $info->name;
$this->tablename = $info->table;
/**
* Database error base class
- * @addtogroup Database
+ * @ingroup Database
*/
class DBError extends MWException {
public $db;
}
/**
- * @addtogroup Database
+ * @ingroup Database
*/
class DBConnectionError extends DBError {
public $error;
}
/**
- * @addtogroup Database
+ * @ingroup Database
*/
class DBQueryError extends DBError {
public $error, $errno, $sql, $fname;
}
/**
- * @addtogroup Database
+ * @ingroup Database
*/
class DBUnexpectedError extends DBError {}
/**
* Result wrapper for grabbing data queried by someone else
- * @addtogroup Database
+ * @ingroup Database
*/
class ResultWrapper implements Iterator {
var $db, $result, $pos = 0, $currentRow = null;