Difference between revisions of "Extension:MediaWikiLite.php"

From Organic Design wiki
(use base-class's makeSelectOptions method)
(setup_database method done - uses MySQL's SQL template and regexes it to SQLite's liking)
Line 5: Line 5:
  
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
if (!defined('SQLITE_OK')) die('SQLite is not installed!');
+
if (!defined('PDO::ATTR_SERVER_VERSION')) die('PDO::SQLite3 is not installed!');
  
 
define('MWLITE_VERSION','0.0.0, 2008-01-08');
 
define('MWLITE_VERSION','0.0.0, 2008-01-08');
  
$wgDBtype       = 'sqlite';
+
$wgDBtype         = 'sqlite';
$wgSQLiteDataDir = dirname(__FILE__);
+
$wgSQLiteDataDir = dirname(__FILE__);
$wgShowSQLErrors = true;
+
$wgShowSQLErrors = true;
  
 
$wgExtensionCredits['other'][] = array(
 
$wgExtensionCredits['other'][] = array(
Line 58: Line 58:
 
if ($this->mConn === false) wfDebug("DB connection error: $err\n");;
 
if ($this->mConn === false) wfDebug("DB connection error: $err\n");;
 
$this->mOpened = $this->mConn;
 
$this->mOpened = $this->mConn;
 +
$this->mConn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_SILENT); # set error codes only, dont raise exceptions
 
}
 
}
 
return $this->mConn;
 
return $this->mConn;
Line 65: Line 66:
 
function close() {
 
function close() {
 
$this->mOpened = false;
 
$this->mOpened = false;
if ($this->mConn) {
+
if (is_object($this->mConn)) {
 
if ($this->trxLevel()) $this->immediateCommit();
 
if ($this->trxLevel()) $this->immediateCommit();
 
$this->mConn = null;
 
$this->mConn = null;
Line 72: Line 73:
 
}
 
}
  
 +
# todo: handle buffered/unbuffered from $this->bufferResults()
 
function doQuery($sql) {
 
function doQuery($sql) {
if ($this->bufferResults()) @$ret = sqlite_query($this->mConn, $sql, SQLITE_BOTH, $err);
+
$ret = $this->mConn->query($sql);
else @$ret = sqlite_unbuffered_query($this->mConn, $sql, SQLITE_BOTH, $err);
+
if ($ret === false) $this->reportQueryError($this->lastError(),$this->lastErrno(),$sql,__FUNCTION__);
if ($err) $this->reportQueryError($err, 0, $sql, __FUNCTION__);
 
 
return $ret;
 
return $ret;
 
}
 
}
Line 84: Line 85:
  
 
function freeResult($res) {
 
function freeResult($res) {
 +
if ($res instanceof ResultWrapper) $res = $res->result;
 +
$res->closeCursor();
 
return;
 
return;
 
}
 
}
Line 89: Line 92:
 
function fetchObject($res) {
 
function fetchObject($res) {
 
if ($res instanceof ResultWrapper) $res = $res->result;
 
if ($res instanceof ResultWrapper) $res = $res->result;
@$row = sqlite_fetch_object($res);
+
@$row = $res->fetch(PDO::FETCH_OBJ);
 
if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in fetchObject(): '.htmlspecialchars($this->lastError()));
 
if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in fetchObject(): '.htmlspecialchars($this->lastError()));
 
return $row;
 
return $row;
Line 96: Line 99:
 
function fetchRow($res) {
 
function fetchRow($res) {
 
if ($res instanceof ResultWrapper) $res = $res->result;
 
if ($res instanceof ResultWrapper) $res = $res->result;
@$row = sqlite_fetch_array($res);
+
@$row = $res->fetch(PDO::FETCH_BOTH);
 
if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in fetchRow(): '.htmlspecialchars($this->lastError()));
 
if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in fetchRow(): '.htmlspecialchars($this->lastError()));
 
return $row;
 
return $row;
Line 103: Line 106:
 
function numRows($res) {
 
function numRows($res) {
 
if ($res instanceof ResultWrapper) $res = $res->result;
 
if ($res instanceof ResultWrapper) $res = $res->result;
@$n = sqlite_num_rows($res);
+
@$n = $res->rowCount();
 
if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in numRows(): '.htmlspecialchars($this->lastError()));
 
if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in numRows(): '.htmlspecialchars($this->lastError()));
 
return $n;
 
return $n;
Line 110: Line 113:
 
function numFields($res) {
 
function numFields($res) {
 
if ($res instanceof ResultWrapper) $res = $res->result;
 
if ($res instanceof ResultWrapper) $res = $res->result;
@$n = sqlite_num_fields($res);
+
@$n = $res->columnCount();
 
if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in numFields(): '.htmlspecialchars($this->lastError()));
 
if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in numFields(): '.htmlspecialchars($this->lastError()));
 
return $n;
 
return $n;
Line 116: Line 119:
  
 
function fieldName($res,$n) {
 
function fieldName($res,$n) {
return sqlite_field_name($res,$n);
+
return $res->fieldName($n);
 
}
 
}
  
Line 134: Line 137:
  
 
function lastError() {
 
function lastError() {
if ($this->mConn === false) return "Cannot return last error, no db connection";
+
if (!is_object($this->mConn)) return "Cannot return last error, no db connection";
return sqlite_error_string(sqlite_last_error($this->mConn));
+
$e = $this->mConn->errorInfo();
 +
return $e[2];
 
}
 
}
  
 
function lastErrno() {
 
function lastErrno() {
if ($this->mConn === false) return "Cannot return last error, no db connection";
+
if (!is_object($this->mConn)) return "Cannot return last error, no db connection";
return sqlite_last_error($this->mConn);
+
return $this->mConn->errorCode();
 
}
 
}
  
Line 207: Line 211:
 
# @return string Version information from the database
 
# @return string Version information from the database
 
function getServerVersion() {
 
function getServerVersion() {
return sqlite_libversion();
+
return $this->mConn->getAttribute(PDO::ATTR_SERVER_VERSION);
 
}
 
}
  
Line 246: Line 250:
  
 
function addQuotes($s) {
 
function addQuotes($s) {
global $wgLang;
+
return '"'.$s.'"';
$s = $wgLang->checkTitleEncoding($s);
 
return $this->strencode($s);
 
 
}
 
}
  
Line 270: Line 272:
 
# - this is the same way PostgreSQL works, MySQL reads in tables.sql and interwiki.sql using dbsource (which calls db->sourceFile)
 
# - 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() {
 
public function setup_database() {
global $IP,$wgSQLiteDataDir;
+
global $IP,$wgSQLiteDataDir,$wgDBTableOptions;
 +
$wgDBTableOptions = '';
 
$mysql_tmpl  = "$IP/maintenance/tables.sql";
 
$mysql_tmpl  = "$IP/maintenance/tables.sql";
 
$sqlite_tmpl = "$wgSQLiteDataDir/tables.sql";
 
$sqlite_tmpl = "$wgSQLiteDataDir/tables.sql";
  
 
# Make an SQLite template file if it doesn't exist (based on the same one MySQL uses to create a new wiki db)
 
# Make an SQLite template file if it doesn't exist (based on the same one MySQL uses to create a new wiki db)
if (!file_exists($sqlite_tmpl)) {
+
if (1 || !file_exists($sqlite_tmpl)) {
 
$sql = file_get_contents($mysql_tmpl);
 
$sql = file_get_contents($mysql_tmpl);
# todo: do a regexp to make the SQL compatible with SQLite3
+
$sql = preg_replace('/^\\s*--.*?$/m','',$sql); # strip comments
 +
$sql = preg_replace('/^\\s*(UNIQUE)?\\s*(PRIMARY)?\\s*KEY.+?$/m','',$sql);
 +
$sql = preg_replace('/^\\s*(UNIQUE )?INDEX.+?$/m','',$sql); # These indexes should be created with a CREATE INDEX query
 +
$sql = preg_replace('/^\\s*FULLTEXT.+?$/m','',$sql);        # Full text indexes
 +
$sql = preg_replace('/ENUM\\(.+?\\)/','TEXT',$sql); # Make ENUM's into TEXT's
 +
$sql = preg_replace('/ENUM\\(.+?\\)/','TEXT',$sql); # Make ENUM's into TEXT's
 +
$sql = preg_replace('/binary\\(\\d+\\)/','BLOB',$sql);
 +
$sql = preg_replace('/(TYPE|MAX_ROWS|AVG_ROW_LENGTH)=\\w+/','',$sql);
 +
$sql = preg_replace('/,\\s*\\)/s',')',$sql); # removing previous items may leave a trailing comma
 +
$sql = str_replace('binary','',$sql);
 +
$sql = str_replace('auto_increment','PRIMARY KEY',$sql);
 
file_put_contents($sqlite_tmpl,$sql);
 
file_put_contents($sqlite_tmpl,$sql);
 
}
 
}
  
 
# Parse the SQL template replacing inline variables such as /*$wgDBprefix*/
 
# Parse the SQL template replacing inline variables such as /*$wgDBprefix*/
$this->sourceFile($sqlite_tmpl);
+
$err = $this->sourceFile($sqlite_tmpl);
 +
if ($err !== true) $this->reportQueryError($err,0,$sql,__FUNCTION__);
 
}
 
}
  
 
}
 
}

Revision as of 09:13, 8 January 2008

<?php

  1. Extension:SQLite
Info.svg These are the MediaWiki extensions we're using and/or developing. Please refer to the information on the mediawiki.org wiki for installation and usage details. Extensions here which have no corresponding mediawiki article are either not ready for use or have been superseded. You can also browse our extension code in our local Subversion repository or our GitHub mirror.

Template:PhpCategory:Extensions in progress

  1. - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
  2. - Author: User:NadCategory:Extensions created with Template:Extension

if (!defined('MEDIAWIKI')) die('Not an entry point.'); if (!defined('PDO::ATTR_SERVER_VERSION')) die('PDO::SQLite3 is not installed!');

define('MWLITE_VERSION','0.0.0, 2008-01-08');

$wgDBtype = 'sqlite'; $wgSQLiteDataDir = dirname(__FILE__); $wgShowSQLErrors = true;

$wgExtensionCredits['other'][] = array( 'name' => 'MediaWikiLite', 'author' => 'User:Nad', 'description' => 'Allow MediaWiki installations to store content in an SQLite database instead of MySQL, and to run as a daemon independently of a web-server.', 'url' => 'http://www.organicdesign.co.nz/Extension:SQLite.php', 'version' => MWLITE_VERSION );

class DatabaseSqlite extends Database {

var $mAffectedRows;

function DatabaseSqlite($server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0) { 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); }

function cascadingDeletes() { return true; } function cleanupTriggers() { return true; } function strictIPs() { return true; } function realTimestamps() { return true; } function implicitGroupby() { return false; } function implicitOrderby() { return false; } function searchableIPs() { return true; } function functionalIndexes() { return true; }

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,$password,$dbName) { global $wgSQLiteDataDir; $this->mConn = false; if ($dbName) { if ($this->mFlags & DBO_PERSISTENT) @$this->mConn = new PDO("sqlite:$wgSQLiteDataDir/$dbName",$user,$pass,array(PDO::ATTR_PERSISTENT => true)); else @$this->mConn = @$this->mConn = new PDO("sqlite:$wgSQLiteDataDir/$dbName",$user,$pass); if ($this->mConn === false) wfDebug("DB connection error: $err\n");; $this->mOpened = $this->mConn; $this->mConn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_SILENT); # set error codes only, dont raise exceptions } return $this->mConn; }

# Close an SQLite database function close() { $this->mOpened = false; if (is_object($this->mConn)) { if ($this->trxLevel()) $this->immediateCommit(); $this->mConn = null; } return true; }

# todo: handle buffered/unbuffered from $this->bufferResults() function doQuery($sql) { $ret = $this->mConn->query($sql); if ($ret === false) $this->reportQueryError($this->lastError(),$this->lastErrno(),$sql,__FUNCTION__); return $ret; }

function queryIgnore($sql, $fname = ) { return $this->query($sql, $fname, true); }

function freeResult($res) { if ($res instanceof ResultWrapper) $res = $res->result; $res->closeCursor(); return; }

function fetchObject($res) { if ($res instanceof ResultWrapper) $res = $res->result; @$row = $res->fetch(PDO::FETCH_OBJ); 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 = $res->fetch(PDO::FETCH_BOTH); 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 = $res->rowCount(); 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; @$n = $res->columnCount(); if ($this->lastErrno()) throw new DBUnexpectedError($this,'Error in numFields(): '.htmlspecialchars($this->lastError())); return $n; }

function fieldName($res,$n) { return $res->fieldName($n); }

# SQLite doesn't like the back-tick for strings function tableName($name) { return ereg_replace('`','"',parent::tableName($name)); }

# This must be called after nextSequenceVal function insertId() { # todo: return $this->mInsertId; ? }

function dataSeek($res, $row) { $res->seek($row); }

function lastError() { if (!is_object($this->mConn)) return "Cannot return last error, no db connection"; $e = $this->mConn->errorInfo(); return $e[2]; }

function lastErrno() { if (!is_object($this->mConn)) return "Cannot return last error, no db connection"; return $this->mConn->errorCode(); }

function affectedRows() { return $this->mAffectedRows; }

# Returns information about an index # - if errors are explicitly ignored, returns NULL on failure function indexInfo($table, $index, $fname = 'Database::indexExists') { # todo }

function indexUnique($table, $index, $fname = 'Database::indexUnique') { # todo }

function insert($table, $a, $fname = 'Database::insert', $options = array()) { # todo }

function insertOneRow($table, $row, $fname) { # todo }

# SQLite does not have a "USE INDEX" clause, so return an empty string function useIndexClause($index) { return ; }

# REPLACE query wrapper function replace($table, $uniqueIndexes, $rows, $fname = 'Database::replace') { # todo }

# DELETE where the condition is a join function deleteJoin($delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin") { # todo }

# Returns the size of a text field, or -1 for "unlimited" function textFieldSize($table, $field) { # todo }

# No low priority option in SQLite function lowPriorityOption() { return ; }

# 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 wasDeadlock() { return $this->lastErrno() == SQLITE_BUSY; }

# @return string wikitext of a link to the server software's web site function getSoftwareLink() { return "SQLite"; }

# @return string Version information from the database function getServerVersion() { return $this->mConn->getAttribute(PDO::ATTR_SERVER_VERSION); }

# Query whether a given column exists in the mediawiki schema function fieldExists($table, $field) { return true; }

function fieldInfo($table, $field) { return false; }

function begin($fname = 'DatabasePostgres::begin') { $this->query('BEGIN', $fname); $this->mTrxLevel = 1; }

function immediateCommit($fname = 'DatabaseSqlite::immediateCommit') { return true; }

function commit($fname = 'DatabaseSqlite::commit') { $this->query('COMMIT', $fname); $this->mTrxLevel = 0; }

function limitResultForUpdate($sql, $num) { return $sql; }

function strencode($s) { return sqlite_escape_string($s); }

function encodeBlob($b) { return new SQLiteBlob($b); }

function decodeBlob($b) { return $b; //return $b->load(); }

function addQuotes($s) { return '"'.$s.'"'; }

function quote_ident($s) { return $s; }

# For now, does nothing function selectDB($db) { return true; }

# not done public function setTimeout($timeout) { return; }

function ping() { wfDebug("Function ping() not written for SQLite yet"); return true; }

# How lagged is this slave? public function getLag() { return 0; }

# Called by the installer script (when modified according to the MediaWikiLite installation instructions) # - this is the same way PostgreSQL works, MySQL reads in tables.sql and interwiki.sql using dbsource (which calls db->sourceFile) public function setup_database() { global $IP,$wgSQLiteDataDir,$wgDBTableOptions; $wgDBTableOptions = ; $mysql_tmpl = "$IP/maintenance/tables.sql"; $sqlite_tmpl = "$wgSQLiteDataDir/tables.sql";

# Make an SQLite template file if it doesn't exist (based on the same one MySQL uses to create a new wiki db) if (1 || !file_exists($sqlite_tmpl)) { $sql = file_get_contents($mysql_tmpl); $sql = preg_replace('/^\\s*--.*?$/m',,$sql); # strip comments $sql = preg_replace('/^\\s*(UNIQUE)?\\s*(PRIMARY)?\\s*KEY.+?$/m',,$sql); $sql = preg_replace('/^\\s*(UNIQUE )?INDEX.+?$/m',,$sql); # These indexes should be created with a CREATE INDEX query $sql = preg_replace('/^\\s*FULLTEXT.+?$/m',,$sql); # Full text indexes $sql = preg_replace('/ENUM\\(.+?\\)/','TEXT',$sql); # Make ENUM's into TEXT's $sql = preg_replace('/ENUM\\(.+?\\)/','TEXT',$sql); # Make ENUM's into TEXT's $sql = preg_replace('/binary\\(\\d+\\)/','BLOB',$sql); $sql = preg_replace('/(TYPE|MAX_ROWS|AVG_ROW_LENGTH)=\\w+/',,$sql); $sql = preg_replace('/,\\s*\\)/s',')',$sql); # removing previous items may leave a trailing comma $sql = str_replace('binary',,$sql); $sql = str_replace('auto_increment','PRIMARY KEY',$sql); file_put_contents($sqlite_tmpl,$sql); }

# Parse the SQL template replacing inline variables such as /*$wgDBprefix*/ $err = $this->sourceFile($sqlite_tmpl); if ($err !== true) $this->reportQueryError($err,0,$sql,__FUNCTION__); }

}