2008-05-07 23:40:14 +00:00
|
|
|
<?php
|
|
|
|
|
/**
|
2008-05-13 00:01:00 +00:00
|
|
|
* This script is the SQLite database abstraction layer
|
2008-05-07 23:40:14 +00:00
|
|
|
*
|
2008-05-13 00:01:00 +00:00
|
|
|
* See maintenance/sqlite/README for development notes and other specific information
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Database
|
|
|
|
|
* @file
|
2008-05-07 23:40:14 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Database
|
2008-05-07 23:40:14 +00:00
|
|
|
*/
|
2009-06-12 17:59:04 +00:00
|
|
|
class DatabaseSqlite extends DatabaseBase {
|
2008-05-07 23:40:14 +00:00
|
|
|
|
|
|
|
|
var $mAffectedRows;
|
|
|
|
|
var $mLastResult;
|
|
|
|
|
var $mDatabaseFile;
|
2009-01-15 06:56:58 +00:00
|
|
|
var $mName;
|
2008-05-07 23:40:14 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function __construct( $server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0 ) {
|
2009-08-29 12:35:40 +00:00
|
|
|
global $wgSQLiteDataDir;
|
2008-05-07 23:40:14 +00:00
|
|
|
$this->mFailFunction = $failFunction;
|
|
|
|
|
$this->mFlags = $flags;
|
2009-10-20 20:23:14 +00:00
|
|
|
$this->mDatabaseFile = self::generateFileName( $wgSQLiteDataDir, $dbName );
|
2009-08-29 12:35:40 +00:00
|
|
|
if( !is_readable( $this->mDatabaseFile ) )
|
|
|
|
|
throw new DBConnectionError( $this, "SQLite database not accessible" );
|
2009-01-15 06:56:58 +00:00
|
|
|
$this->mName = $dbName;
|
2009-09-15 21:05:30 +00:00
|
|
|
$this->open( $server, $user, $password, $dbName );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* todo: check if these should be true like parent class
|
|
|
|
|
*/
|
|
|
|
|
function implicitGroupby() { return false; }
|
|
|
|
|
function implicitOrderby() { return false; }
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) {
|
|
|
|
|
return new DatabaseSqlite( $server, $user, $password, $dbName, $failFunction, $flags );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Open an SQLite database and return a resource handle to it
|
|
|
|
|
* NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function open( $server, $user, $pass, $dbName ) {
|
2008-05-07 23:40:14 +00:00
|
|
|
$this->mConn = false;
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( $dbName ) {
|
2008-05-07 23:40:14 +00:00
|
|
|
$file = $this->mDatabaseFile;
|
2008-10-06 00:45:18 +00:00
|
|
|
try {
|
|
|
|
|
if ( $this->mFlags & DBO_PERSISTENT ) {
|
2009-09-15 21:05:30 +00:00
|
|
|
$this->mConn = new PDO( "sqlite:$file", $user, $pass,
|
2008-10-06 00:45:18 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
$this->mOpened = $this->mConn;
|
2008-10-06 00:45:18 +00:00
|
|
|
# set error codes only, don't raise exceptions
|
2009-09-15 21:05:30 +00:00
|
|
|
$this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
return $this->mConn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close an SQLite database
|
|
|
|
|
*/
|
|
|
|
|
function close() {
|
|
|
|
|
$this->mOpened = false;
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( is_object( $this->mConn ) ) {
|
|
|
|
|
if ( $this->trxLevel() ) $this->immediateCommit();
|
2008-05-07 23:40:14 +00:00
|
|
|
$this->mConn = null;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2009-10-20 20:23:14 +00:00
|
|
|
/**
|
|
|
|
|
* 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";
|
|
|
|
|
}
|
|
|
|
|
|
2009-10-29 16:19:35 +00:00
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2008-05-07 23:40:14 +00:00
|
|
|
/**
|
|
|
|
|
* SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function doQuery( $sql ) {
|
|
|
|
|
$res = $this->mConn->query( $sql );
|
|
|
|
|
if ( $res === false ) {
|
2009-01-15 06:56:58 +00:00
|
|
|
return false;
|
|
|
|
|
} else {
|
2008-05-07 23:40:14 +00:00
|
|
|
$r = $res instanceof ResultWrapper ? $res->result : $res;
|
|
|
|
|
$this->mAffectedRows = $r->rowCount();
|
2009-09-15 21:05:30 +00:00
|
|
|
$res = new ResultWrapper( $this, $r->fetchAll() );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
return $res;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function freeResult( $res ) {
|
|
|
|
|
if ( $res instanceof ResultWrapper )
|
|
|
|
|
$res->result = NULL;
|
|
|
|
|
else
|
|
|
|
|
$res = NULL;
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-01-15 14:20:28 +00:00
|
|
|
function fetchObject($res) {
|
2009-09-15 21:05:30 +00:00
|
|
|
if ($res instanceof ResultWrapper)
|
|
|
|
|
$r =& $res->result;
|
|
|
|
|
else
|
|
|
|
|
$r =& $res;
|
|
|
|
|
|
|
|
|
|
$cur = current( $r );
|
|
|
|
|
if ( is_array( $cur ) ) {
|
|
|
|
|
next( $r );
|
2008-05-07 23:40:14 +00:00
|
|
|
$obj = new stdClass;
|
2009-09-15 21:05:30 +00:00
|
|
|
foreach ( $cur as $k => $v )
|
|
|
|
|
if ( !is_numeric( $k ) )
|
|
|
|
|
$obj->$k = $v;
|
|
|
|
|
|
2008-05-07 23:40:14 +00:00
|
|
|
return $obj;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2009-01-15 14:20:28 +00:00
|
|
|
function fetchRow($res) {
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( $res instanceof ResultWrapper )
|
|
|
|
|
$r =& $res->result;
|
|
|
|
|
else
|
|
|
|
|
$r =& $res;
|
|
|
|
|
|
2008-05-07 23:40:14 +00:00
|
|
|
$cur = current($r);
|
|
|
|
|
if (is_array($cur)) {
|
|
|
|
|
next($r);
|
|
|
|
|
return $cur;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The PDO::Statement class implements the array interface so count() will work
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function numRows( $res ) {
|
2008-05-07 23:40:14 +00:00
|
|
|
$r = $res instanceof ResultWrapper ? $res->result : $res;
|
2009-09-15 21:05:30 +00:00
|
|
|
return count( $r );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function numFields( $res ) {
|
2008-05-07 23:40:14 +00:00
|
|
|
$r = $res instanceof ResultWrapper ? $res->result : $res;
|
2009-09-15 21:05:30 +00:00
|
|
|
return is_array( $r ) ? count( $r[0] ) : 0;
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function fieldName( $res, $n ) {
|
2008-05-07 23:40:14 +00:00
|
|
|
$r = $res instanceof ResultWrapper ? $res->result : $res;
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( is_array( $r ) ) {
|
|
|
|
|
$keys = array_keys( $r[0] );
|
2008-05-07 23:40:14 +00:00
|
|
|
return $keys[$n];
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function tableName( $name ) {
|
|
|
|
|
return str_replace( '`', '', parent::tableName( $name ) );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-01-19 13:56:08 +00:00
|
|
|
/**
|
|
|
|
|
* Index names have DB scope
|
|
|
|
|
*/
|
|
|
|
|
function indexName( $index ) {
|
|
|
|
|
return $index;
|
|
|
|
|
}
|
|
|
|
|
|
2008-05-07 23:40:14 +00:00
|
|
|
/**
|
|
|
|
|
* This must be called after nextSequenceVal
|
|
|
|
|
*/
|
|
|
|
|
function insertId() {
|
|
|
|
|
return $this->mConn->lastInsertId();
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
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 );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lastError() {
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( !is_object( $this->mConn ) )
|
|
|
|
|
return "Cannot return last error, no db connection";
|
2008-05-07 23:40:14 +00:00
|
|
|
$e = $this->mConn->errorInfo();
|
2009-09-15 21:05:30 +00:00
|
|
|
return isset( $e[2] ) ? $e[2] : '';
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function lastErrno() {
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( !is_object( $this->mConn ) ) {
|
2009-01-15 06:56:58 +00:00
|
|
|
return "Cannot return last error, no db connection";
|
|
|
|
|
} else {
|
|
|
|
|
$info = $this->mConn->errorInfo();
|
|
|
|
|
return $info[1];
|
|
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function affectedRows() {
|
|
|
|
|
return $this->mAffectedRows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns information about an index
|
2009-01-15 06:56:58 +00:00
|
|
|
* Returns false if the index does not exist
|
2008-05-07 23:40:14 +00:00
|
|
|
* - if errors are explicitly ignored, returns NULL on failure
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function indexInfo( $table, $index, $fname = 'DatabaseSqlite::indexExists' ) {
|
2009-01-19 13:56:08 +00:00
|
|
|
$sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
|
2009-01-15 06:56:58 +00:00
|
|
|
$res = $this->query( $sql, $fname );
|
|
|
|
|
if ( !$res ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if ( $res->numRows() == 0 ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$info = array();
|
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
|
$info[] = $row->name;
|
|
|
|
|
}
|
|
|
|
|
return $info;
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function indexUnique( $table, $index, $fname = 'DatabaseSqlite::indexUnique' ) {
|
|
|
|
|
$row = $this->selectRow( 'sqlite_master', '*',
|
2009-01-15 06:56:58 +00:00
|
|
|
array(
|
|
|
|
|
'type' => 'index',
|
2009-01-19 13:56:08 +00:00
|
|
|
'name' => $this->indexName( $index ),
|
2009-01-15 06:56:58 +00:00
|
|
|
), $fname );
|
|
|
|
|
if ( !$row || !isset( $row->sql ) ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// $row->sql will be of the form CREATE [UNIQUE] INDEX ...
|
|
|
|
|
$indexPos = strpos( $row->sql, 'INDEX' );
|
|
|
|
|
if ( $indexPos === false ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
$firstPart = substr( $row->sql, 0, $indexPos );
|
|
|
|
|
$options = explode( ' ', $firstPart );
|
|
|
|
|
return in_array( 'UNIQUE', $options );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Filter the options used in SELECT statements
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function makeSelectOptions( $options ) {
|
|
|
|
|
foreach ( $options as $k => $v )
|
|
|
|
|
if ( is_numeric( $k ) && $v == 'FOR UPDATE' )
|
|
|
|
|
$options[$k] = '';
|
|
|
|
|
return parent::makeSelectOptions( $options );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Based on MySQL method (parent) with some prior SQLite-sepcific adjustments
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) {
|
|
|
|
|
if ( !count( $a ) ) return true;
|
|
|
|
|
if ( !is_array( $options ) ) $options = array( $options );
|
2008-05-07 23:40:14 +00:00
|
|
|
|
|
|
|
|
# SQLite uses OR IGNORE not just IGNORE
|
2009-09-15 21:05:30 +00:00
|
|
|
foreach ( $options as $k => $v )
|
|
|
|
|
if ( $v == 'IGNORE' )
|
|
|
|
|
$options[$k] = 'OR IGNORE';
|
2008-05-07 23:40:14 +00:00
|
|
|
|
|
|
|
|
# SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
|
2008-05-07 23:40:14 +00:00
|
|
|
$ret = true;
|
2009-09-15 21:05:30 +00:00
|
|
|
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 );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $ret;
|
|
|
|
|
}
|
|
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function textFieldSize( $table, $field ) {
|
|
|
|
|
return - 1;
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-10-17 12:23:23 +00:00
|
|
|
function unionSupportsOrderAndLimit() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function unionQueries( $sqls, $all ) {
|
|
|
|
|
$glue = $all ? ' UNION ALL ' : ' UNION ';
|
|
|
|
|
return implode( $glue, $sqls );
|
|
|
|
|
}
|
|
|
|
|
|
2008-05-07 23:40:14 +00:00
|
|
|
function wasDeadlock() {
|
2009-10-29 16:19:35 +00:00
|
|
|
return $this->lastErrno() == 5; // SQLITE_BUSY
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
function wasErrorReissuable() {
|
2009-10-29 16:19:35 +00:00
|
|
|
return $this->lastErrno() == 17; // SQLITE_SCHEMA;
|
2009-01-15 06:56:58 +00:00
|
|
|
}
|
|
|
|
|
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
function wasReadOnlyError() {
|
2009-10-29 16:19:35 +00:00
|
|
|
return $this->lastErrno() == 8; // SQLITE_READONLY;
|
* Converted BagOStuff.php from the style of memcached-client.php to the standard MediaWiki style, including camel case, using protected visibility instead of initial underscore, abstract functions instead of stubs, stylize.php.
* In SqlBagOStuff, ignore errors due to a read-only database, per my comments on CR r42796. Same for LocalisationCache.
* Merged SqlBagOStuff and MediaWikiBagOStuff, that proved to be an awkward and unnecessary generalisation. Use the standard quoting wrapper functions instead of $db->query().
* Implemented atomic incr() and decr() functions for SqlBagOStuff.
* Made incr() and decr() generally work roughly the same as it does in memcached, respecting negative steps instead of ignoring such operations. This allows decr() to be implemented in terms of incr().
* Per bug 11533, in MessageCache.php, don't retry 20 times on a cache failure, that's really memcached-specific and won't be useful for other cache types. It's not really very useful for memcached either.
* Moved MySQL-specific implementations of wasDeadlock() and wasErrorReissuable() to DatabaseMysql.
* Briefly tested page views with $wgReadOnly=read_only=1, fixed an error from Article::viewUpdates(). A CentralAuth fix will be in a subsequent commit.
2009-08-15 03:45:19 +00:00
|
|
|
}
|
|
|
|
|
|
2008-05-07 23:40:14 +00:00
|
|
|
/**
|
|
|
|
|
* @return string wikitext of a link to the server software's web site
|
|
|
|
|
*/
|
|
|
|
|
function getSoftwareLink() {
|
|
|
|
|
return "[http://sqlite.org/ SQLite]";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return string Version information from the database
|
|
|
|
|
*/
|
|
|
|
|
function getServerVersion() {
|
2009-09-15 21:05:30 +00:00
|
|
|
$ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
|
2009-01-15 06:56:58 +00:00
|
|
|
return $ver;
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Query whether a given column exists in the mediawiki schema
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function fieldExists( $table, $field, $fname = '' ) {
|
2009-01-15 06:56:58 +00:00
|
|
|
$info = $this->fieldInfo( $table, $field );
|
|
|
|
|
return (bool)$info;
|
|
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
/**
|
|
|
|
|
* Get information about a given field
|
|
|
|
|
* Returns false if the field does not exist.
|
|
|
|
|
*/
|
2009-09-15 21:05:30 +00:00
|
|
|
function fieldInfo( $table, $field ) {
|
2009-01-15 06:56:58 +00:00
|
|
|
$tableName = $this->tableName( $table );
|
|
|
|
|
$sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
|
|
|
|
|
$res = $this->query( $sql, __METHOD__ );
|
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
|
if ( $row->name == $field ) {
|
|
|
|
|
return new SQLiteField( $row, $tableName );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
|
2009-01-15 14:20:28 +00:00
|
|
|
function begin( $fname = '' ) {
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( $this->mTrxLevel == 1 ) $this->commit();
|
2008-05-07 23:40:14 +00:00
|
|
|
$this->mConn->beginTransaction();
|
|
|
|
|
$this->mTrxLevel = 1;
|
|
|
|
|
}
|
|
|
|
|
|
2009-01-15 14:20:28 +00:00
|
|
|
function commit( $fname = '' ) {
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( $this->mTrxLevel == 0 ) return;
|
2008-05-07 23:40:14 +00:00
|
|
|
$this->mConn->commit();
|
|
|
|
|
$this->mTrxLevel = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2009-01-15 14:20:28 +00:00
|
|
|
function rollback( $fname = '' ) {
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( $this->mTrxLevel == 0 ) return;
|
2008-05-07 23:40:14 +00:00
|
|
|
$this->mConn->rollBack();
|
|
|
|
|
$this->mTrxLevel = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function limitResultForUpdate( $sql, $num ) {
|
2009-01-15 06:56:58 +00:00
|
|
|
return $this->limitResult( $sql, $num );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function strencode( $s ) {
|
|
|
|
|
return substr( $this->addQuotes( $s ), 1, - 1 );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function encodeBlob( $b ) {
|
2008-09-06 13:56:59 +00:00
|
|
|
return new Blob( $b );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function decodeBlob( $b ) {
|
|
|
|
|
if ( $b instanceof Blob ) {
|
2008-09-06 13:56:59 +00:00
|
|
|
$b = $b->fetch();
|
|
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
return $b;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function addQuotes( $s ) {
|
2008-09-06 13:56:59 +00:00
|
|
|
if ( $s instanceof Blob ) {
|
|
|
|
|
return "x'" . bin2hex( $s->fetch() ) . "'";
|
|
|
|
|
} else {
|
2009-09-15 21:05:30 +00:00
|
|
|
return $this->mConn->quote( $s );
|
2008-09-06 13:56:59 +00:00
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-09-15 21:05:30 +00:00
|
|
|
function quote_ident( $s ) {
|
|
|
|
|
return $s;
|
|
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
|
2009-10-21 19:53:03 +00:00
|
|
|
function buildLike() {
|
|
|
|
|
$params = func_get_args();
|
|
|
|
|
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
|
|
|
|
|
$params = $params[0];
|
|
|
|
|
}
|
|
|
|
|
return parent::buildLike( $params ) . "ESCAPE '\' ";
|
|
|
|
|
}
|
|
|
|
|
|
2008-05-07 23:40:14 +00:00
|
|
|
/**
|
|
|
|
|
* How lagged is this slave?
|
|
|
|
|
*/
|
|
|
|
|
public function getLag() {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Called by the installer script (when modified according to the MediaWikiLite installation instructions)
|
|
|
|
|
* - this is the same way PostgreSQL works, MySQL reads in tables.sql and interwiki.sql using dbsource (which calls db->sourceFile)
|
|
|
|
|
*/
|
|
|
|
|
public function setup_database() {
|
2009-10-20 20:23:14 +00:00
|
|
|
global $IP;
|
2008-05-07 23:40:14 +00:00
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
# Process common MySQL/SQLite table definitions
|
|
|
|
|
$err = $this->sourceFile( "$IP/maintenance/tables.sql" );
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( $err !== true ) {
|
|
|
|
|
$this->reportQueryError( $err, 0, $sql, __FUNCTION__ );
|
2009-01-15 06:56:58 +00:00
|
|
|
exit( 1 );
|
|
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
|
|
|
|
|
# Use DatabasePostgres's code to populate interwiki from MySQL template
|
2009-09-15 21:05:30 +00:00
|
|
|
$f = fopen( "$IP/maintenance/interwiki.sql", 'r' );
|
|
|
|
|
if ( $f == false ) dieout( "<li>Could not find the interwiki.sql file" );
|
2008-05-07 23:40:14 +00:00
|
|
|
$sql = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
|
2009-09-15 21:05:30 +00:00
|
|
|
while ( !feof( $f ) ) {
|
|
|
|
|
$line = fgets( $f, 1024 );
|
2008-05-07 23:40:14 +00:00
|
|
|
$matches = array();
|
2009-09-15 21:05:30 +00:00
|
|
|
if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) continue;
|
|
|
|
|
$this->query( "$sql $matches[1],$matches[2])" );
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
}
|
2008-07-02 18:22:38 +00:00
|
|
|
|
2008-08-18 15:36:53 +00:00
|
|
|
public function getSearchEngine() {
|
2009-10-29 16:19:35 +00:00
|
|
|
return "SearchSqlite";
|
2008-08-18 15:36:53 +00:00
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
|
2009-01-09 03:53:28 +00:00
|
|
|
/**
|
|
|
|
|
* No-op version of deadlockLoop
|
|
|
|
|
*/
|
|
|
|
|
public function deadlockLoop( /*...*/ ) {
|
|
|
|
|
$args = func_get_args();
|
|
|
|
|
$function = array_shift( $args );
|
|
|
|
|
return call_user_func_array( $function, $args );
|
|
|
|
|
}
|
2009-01-15 06:56:58 +00:00
|
|
|
|
|
|
|
|
protected function replaceVars( $s ) {
|
|
|
|
|
$s = parent::replaceVars( $s );
|
|
|
|
|
if ( preg_match( '/^\s*CREATE TABLE/i', $s ) ) {
|
|
|
|
|
// CREATE TABLE hacks to allow schema file sharing with MySQL
|
2009-09-15 21:05:30 +00:00
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
// binary/varbinary column type -> blob
|
|
|
|
|
$s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'blob\1', $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 );
|
|
|
|
|
// No ENUM type
|
|
|
|
|
$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 );
|
|
|
|
|
// No explicit options
|
|
|
|
|
$s = preg_replace( '/\)[^)]*$/', ')', $s );
|
|
|
|
|
} elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
|
|
|
|
|
// No truncated indexes
|
|
|
|
|
$s = preg_replace( '/\(\d+\)/', '', $s );
|
|
|
|
|
// No FULLTEXT
|
|
|
|
|
$s = preg_replace( '/\bfulltext\b/i', '', $s );
|
|
|
|
|
}
|
|
|
|
|
return $s;
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-13 21:56:24 +00:00
|
|
|
/*
|
|
|
|
|
* Build a concatenation list to feed into a SQL query
|
|
|
|
|
*/
|
|
|
|
|
function buildConcat( $stringList ) {
|
2009-07-14 00:55:17 +00:00
|
|
|
return '(' . implode( ') || (', $stringList ) . ')';
|
2009-07-13 21:56:24 +00:00
|
|
|
}
|
2009-06-26 15:38:43 +00:00
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
} // end DatabaseSqlite class
|
2008-05-07 23:40:14 +00:00
|
|
|
|
|
|
|
|
/**
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Database
|
2008-05-07 23:40:14 +00:00
|
|
|
*/
|
2009-01-15 06:56:58 +00:00
|
|
|
class SQLiteField {
|
|
|
|
|
private $info, $tableName;
|
|
|
|
|
function __construct( $info, $tableName ) {
|
|
|
|
|
$this->info = $info;
|
|
|
|
|
$this->tableName = $tableName;
|
|
|
|
|
}
|
2008-05-07 23:40:14 +00:00
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
function name() {
|
|
|
|
|
return $this->info->name;
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
function tableName() {
|
|
|
|
|
return $this->tableName;
|
2008-05-07 23:40:14 +00:00
|
|
|
}
|
|
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
function defaultValue() {
|
|
|
|
|
if ( is_string( $this->info->dflt_value ) ) {
|
|
|
|
|
// Typically quoted
|
|
|
|
|
if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) {
|
|
|
|
|
return str_replace( "''", "'", $this->info->dflt_value );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $this->info->dflt_value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function maxLength() {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function nullable() {
|
|
|
|
|
// SQLite dynamic types are always nullable
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# isKey(), isMultipleKey() not implemented, MySQL-specific concept.
|
|
|
|
|
# Suggest removal from base class [TS]
|
2009-09-15 21:05:30 +00:00
|
|
|
|
2009-01-15 06:56:58 +00:00
|
|
|
function type() {
|
|
|
|
|
return $this->info->type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // end SQLiteField
|