r34405 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r34404‎ | r34405 | r34406 >
Date:23:40, 7 May 2008
Author:nad
Status:old
Tags:
Comment:
Add SQLite database class
Modified paths:
  • /trunk/phase3/includes/DatabaseSqlite.php (added) (history)

Diff [purge]

Index: trunk/phase3/includes/DatabaseSqlite.php
@@ -0,0 +1,396 @@
 2+<?php
 3+/**
 4+ * This script is the SQLite database abstraction layer, see http://www.organicdesign.co.nz/Extension_talk:MediaWikiLite.php
 5+ * - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html){{php}}
 6+ * - Author: [http://www.organicdesign.co.nz/nad User:Nad]
 7+ * - Started: 2007-12-17
 8+ * - Last update: 2008-01-20
 9+ *
 10+ * {{php}}{{category:Extensions|DatabaseSqlite.php}}
 11+ */
 12+
 13+/**
 14+ * @addtogroup Database
 15+ */
 16+class DatabaseSqlite extends Database {
 17+
 18+ var $mAffectedRows;
 19+ var $mLastResult;
 20+ var $mDatabaseFile;
 21+
 22+ /**
 23+ * Constructor
 24+ */
 25+ function __construct($server = false, $user = false, $password = false, $dbName = false, $failFunction = false, $flags = 0) {
 26+ global $wgOut,$wgSQLiteDataDir;
 27+ if ("$wgSQLiteDataDir" == '') $wgSQLiteDataDir = dirname($_SERVER['DOCUMENT_ROOT']).'/data';
 28+ if (!is_dir($wgSQLiteDataDir)) mkdir($wgSQLiteDataDir,0700);
 29+ if (!isset($wgOut)) $wgOut = NULL; # Can't get a reference if it hasn't been set yet
 30+ $this->mOut =& $wgOut;
 31+ $this->mFailFunction = $failFunction;
 32+ $this->mFlags = $flags;
 33+ $this->mDatabaseFile = "$wgSQLiteDataDir/$dbName.sqlite";
 34+ $this->open($server, $user, $password, $dbName);
 35+ }
 36+
 37+ /**
 38+ * todo: check if these should be true like parent class
 39+ */
 40+ function implicitGroupby() { return false; }
 41+ function implicitOrderby() { return false; }
 42+
 43+ static function newFromParams($server, $user, $password, $dbName, $failFunction = false, $flags = 0) {
 44+ return new DatabaseSqlite($server, $user, $password, $dbName, $failFunction, $flags);
 45+ }
 46+
 47+ /** Open an SQLite database and return a resource handle to it
 48+ * NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
 49+ */
 50+ function open($server,$user,$pass,$dbName) {
 51+ $this->mConn = false;
 52+ if ($dbName) {
 53+ $file = $this->mDatabaseFile;
 54+ if ($this->mFlags & DBO_PERSISTENT) $this->mConn = new PDO("sqlite:$file",$user,$pass,array(PDO::ATTR_PERSISTENT => true));
 55+ else $this->mConn = new PDO("sqlite:$file",$user,$pass);
 56+ if ($this->mConn === false) wfDebug("DB connection error: $err\n");;
 57+ $this->mOpened = $this->mConn;
 58+ $this->mConn->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_SILENT); # set error codes only, dont raise exceptions
 59+ }
 60+ return $this->mConn;
 61+ }
 62+
 63+ /**
 64+ * Close an SQLite database
 65+ */
 66+ function close() {
 67+ $this->mOpened = false;
 68+ if (is_object($this->mConn)) {
 69+ if ($this->trxLevel()) $this->immediateCommit();
 70+ $this->mConn = null;
 71+ }
 72+ return true;
 73+ }
 74+
 75+ /**
 76+ * SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
 77+ */
 78+ function doQuery($sql) {
 79+ $res = $this->mConn->query($sql);
 80+ if ($res === false) $this->reportQueryError($this->lastError(),$this->lastErrno(),$sql,__FUNCTION__);
 81+ else {
 82+ $r = $res instanceof ResultWrapper ? $res->result : $res;
 83+ $this->mAffectedRows = $r->rowCount();
 84+ $res = new ResultWrapper($this,$r->fetchAll());
 85+ }
 86+ return $res;
 87+ }
 88+
 89+ function freeResult(&$res) {
 90+ if ($res instanceof ResultWrapper) $res->result = NULL; else $res = NULL;
 91+ }
 92+
 93+ function fetchObject(&$res) {
 94+ if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res;
 95+ $cur = current($r);
 96+ if (is_array($cur)) {
 97+ next($r);
 98+ $obj = new stdClass;
 99+ foreach ($cur as $k => $v) if (!is_numeric($k)) $obj->$k = $v;
 100+ return $obj;
 101+ }
 102+ return false;
 103+ }
 104+
 105+ function fetchRow(&$res) {
 106+ if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res;
 107+ $cur = current($r);
 108+ if (is_array($cur)) {
 109+ next($r);
 110+ return $cur;
 111+ }
 112+ return false;
 113+ }
 114+
 115+ /**
 116+ * The PDO::Statement class implements the array interface so count() will work
 117+ */
 118+ function numRows(&$res) {
 119+ $r = $res instanceof ResultWrapper ? $res->result : $res;
 120+ return count($r);
 121+ }
 122+
 123+ function numFields(&$res) {
 124+ $r = $res instanceof ResultWrapper ? $res->result : $res;
 125+ return is_array($r) ? count($r[0]) : 0;
 126+ }
 127+
 128+ function fieldName(&$res,$n) {
 129+ $r = $res instanceof ResultWrapper ? $res->result : $res;
 130+ if (is_array($r)) {
 131+ $keys = array_keys($r[0]);
 132+ return $keys[$n];
 133+ }
 134+ return false;
 135+ }
 136+
 137+ /**
 138+ * Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
 139+ */
 140+ function tableName($name) {
 141+ $t = parent::tableName($name);
 142+ if (!empty($t)) $t = substr($t,1,-1);
 143+ return $t;
 144+ }
 145+
 146+ /**
 147+ * This must be called after nextSequenceVal
 148+ */
 149+ function insertId() {
 150+ return $this->mConn->lastInsertId();
 151+ }
 152+
 153+ function dataSeek(&$res,$row) {
 154+ if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res;
 155+ reset($r);
 156+ if ($row > 0) for ($i = 0; $i < $row; $i++) next($r);
 157+ }
 158+
 159+ function lastError() {
 160+ if (!is_object($this->mConn)) return "Cannot return last error, no db connection";
 161+ $e = $this->mConn->errorInfo();
 162+ return isset($e[2]) ? $e[2] : '';
 163+ }
 164+
 165+ function lastErrno() {
 166+ if (!is_object($this->mConn)) return "Cannot return last error, no db connection";
 167+ return $this->mConn->errorCode();
 168+ }
 169+
 170+ function affectedRows() {
 171+ return $this->mAffectedRows;
 172+ }
 173+
 174+ /**
 175+ * Returns information about an index
 176+ * - if errors are explicitly ignored, returns NULL on failure
 177+ */
 178+ function indexInfo($table, $index, $fname = 'Database::indexExists') {
 179+ return false;
 180+ }
 181+
 182+ function indexUnique($table, $index, $fname = 'Database::indexUnique') {
 183+ return false;
 184+ }
 185+
 186+ /**
 187+ * Filter the options used in SELECT statements
 188+ */
 189+ function makeSelectOptions($options) {
 190+ foreach ($options as $k => $v) if (is_numeric($k) && $v == 'FOR UPDATE') $options[$k] = '';
 191+ return parent::makeSelectOptions($options);
 192+ }
 193+
 194+ /**
 195+ * Based on MySQL method (parent) with some prior SQLite-sepcific adjustments
 196+ */
 197+ function insert($table, $a, $fname = 'DatabaseSqlite::insert', $options = array()) {
 198+ if (!count($a)) return true;
 199+ if (!is_array($options)) $options = array($options);
 200+
 201+ # SQLite uses OR IGNORE not just IGNORE
 202+ foreach ($options as $k => $v) if ($v == 'IGNORE') $options[$k] = 'OR IGNORE';
 203+
 204+ # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
 205+ if (isset($a[0]) && is_array($a[0])) {
 206+ $ret = true;
 207+ foreach ($a as $k => $v) if (!parent::insert($table,$v,"$fname/multi-row",$options)) $ret = false;
 208+ }
 209+ else $ret = parent::insert($table,$a,"$fname/single-row",$options);
 210+
 211+ return $ret;
 212+ }
 213+
 214+ /**
 215+ * SQLite does not have a "USE INDEX" clause, so return an empty string
 216+ */
 217+ function useIndexClause($index) {
 218+ return '';
 219+ }
 220+
 221+ # Returns the size of a text field, or -1 for "unlimited"
 222+ function textFieldSize($table, $field) {
 223+ return -1;
 224+ }
 225+
 226+ /**
 227+ * No low priority option in SQLite
 228+ */
 229+ function lowPriorityOption() {
 230+ return '';
 231+ }
 232+
 233+ /**
 234+ * Returns an SQL expression for a simple conditional.
 235+ * - uses CASE on SQLite
 236+ */
 237+ function conditional($cond, $trueVal, $falseVal) {
 238+ return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
 239+ }
 240+
 241+ function wasDeadlock() {
 242+ return $this->lastErrno() == SQLITE_BUSY;
 243+ }
 244+
 245+ /**
 246+ * @return string wikitext of a link to the server software's web site
 247+ */
 248+ function getSoftwareLink() {
 249+ return "[http://sqlite.org/ SQLite]";
 250+ }
 251+
 252+ /**
 253+ * @return string Version information from the database
 254+ */
 255+ function getServerVersion() {
 256+ global $wgContLang;
 257+ $ver = $this->mConn->getAttribute(PDO::ATTR_SERVER_VERSION);
 258+ $size = $wgContLang->formatSize(filesize($this->mDatabaseFile));
 259+ $file = basename($this->mDatabaseFile);
 260+ return $ver." ($file: $size)";
 261+ }
 262+
 263+ /**
 264+ * Query whether a given column exists in the mediawiki schema
 265+ */
 266+ function fieldExists($table, $field) { return true; }
 267+
 268+ function fieldInfo($table, $field) { return SQLiteField::fromText($this, $table, $field); }
 269+
 270+ function begin() {
 271+ if ($this->mTrxLevel == 1) $this->commit();
 272+ $this->mConn->beginTransaction();
 273+ $this->mTrxLevel = 1;
 274+ }
 275+
 276+ function commit() {
 277+ if ($this->mTrxLevel == 0) return;
 278+ $this->mConn->commit();
 279+ $this->mTrxLevel = 0;
 280+ }
 281+
 282+ function rollback() {
 283+ if ($this->mTrxLevel == 0) return;
 284+ $this->mConn->rollBack();
 285+ $this->mTrxLevel = 0;
 286+ }
 287+
 288+ function limitResultForUpdate($sql, $num) {
 289+ return $sql;
 290+ }
 291+
 292+ function strencode($s) {
 293+ return substr($this->addQuotes($s),1,-1);
 294+ }
 295+
 296+ function encodeBlob($b) {
 297+ return $this->strencode($b);
 298+ }
 299+
 300+ function decodeBlob($b) {
 301+ return $b;
 302+ }
 303+
 304+ function addQuotes($s) {
 305+ return $this->mConn->quote($s);
 306+ }
 307+
 308+ function quote_ident($s) { return $s; }
 309+
 310+ /**
 311+ * For now, does nothing
 312+ */
 313+ function selectDB($db) { return true; }
 314+
 315+ /**
 316+ * not done
 317+ */
 318+ public function setTimeout($timeout) { return; }
 319+
 320+ function ping() {
 321+ wfDebug("Function ping() not written for SQLite yet");
 322+ return true;
 323+ }
 324+
 325+ /**
 326+ * How lagged is this slave?
 327+ */
 328+ public function getLag() {
 329+ return 0;
 330+ }
 331+
 332+ /**
 333+ * Called by the installer script (when modified according to the MediaWikiLite installation instructions)
 334+ * - this is the same way PostgreSQL works, MySQL reads in tables.sql and interwiki.sql using dbsource (which calls db->sourceFile)
 335+ */
 336+ public function setup_database() {
 337+ global $IP,$wgSQLiteDataDir,$wgDBTableOptions;
 338+ $wgDBTableOptions = '';
 339+ $mysql_tmpl = "$IP/maintenance/tables.sql";
 340+ $mysql_iw = "$IP/maintenance/interwiki.sql";
 341+ $sqlite_tmpl = "$wgSQLiteDataDir/tables.sql";
 342+
 343+ # Make an SQLite template file if it doesn't exist (based on the same one MySQL uses to create a new wiki db)
 344+ if (1 || !file_exists($sqlite_tmpl)) { # todo: make this conditional again
 345+ $sql = file_get_contents($mysql_tmpl);
 346+ $sql = preg_replace('/^\s*--.*?$/m','',$sql); # strip comments
 347+ $sql = preg_replace('/^\s*(UNIQUE)?\s*(PRIMARY)?\s*KEY.+?$/m','',$sql);
 348+ $sql = preg_replace('/^\s*(UNIQUE )?INDEX.+?$/m','',$sql); # These indexes should be created with a CREATE INDEX query
 349+ $sql = preg_replace('/^\s*FULLTEXT.+?$/m','',$sql); # Full text indexes
 350+ $sql = preg_replace('/ENUM\(.+?\)/','TEXT',$sql); # Make ENUM's into TEXT's
 351+ $sql = preg_replace('/ENUM\(.+?\)/','TEXT',$sql); # Make ENUM's into TEXT's
 352+ $sql = preg_replace('/binary\(\d+\)/','BLOB',$sql);
 353+ $sql = preg_replace('/(TYPE|MAX_ROWS|AVG_ROW_LENGTH)=\w+/','',$sql);
 354+ $sql = preg_replace('/,\s*\)/s',')',$sql); # removing previous items may leave a trailing comma
 355+ $sql = str_replace('binary','',$sql);
 356+ $sql = str_replace('auto_increment','PRIMARY KEY AUTOINCREMENT',$sql);
 357+ $sql = str_replace(' unsigned','',$sql);
 358+ $sql = str_replace(' int ',' INTEGER ',$sql);
 359+ $sql = str_replace('NOT NULL','',$sql);
 360+ file_put_contents($sqlite_tmpl,$sql);
 361+ }
 362+
 363+ # Parse the SQLite template replacing inline variables such as /*$wgDBprefix*/
 364+ $err = $this->sourceFile($sqlite_tmpl);
 365+ if ($err !== true) $this->reportQueryError($err,0,$sql,__FUNCTION__);
 366+
 367+ # Use DatabasePostgres's code to populate interwiki from MySQL template
 368+ $f = fopen($mysql_iw,'r');
 369+ if ($f == false) dieout("<li>Could not find the interwiki.sql file");
 370+ $sql = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
 371+ while (!feof($f)) {
 372+ $line = fgets($f,1024);
 373+ $matches = array();
 374+ if (!preg_match('/^\s*(\(.+?),(\d)\)/', $line, $matches)) continue;
 375+ $this->query("$sql $matches[1],$matches[2])");
 376+ }
 377+ }
 378+
 379+}
 380+
 381+/**
 382+ * @addtogroup Database
 383+ */
 384+class SQLiteField extends MySQLField {
 385+
 386+ function __construct() {
 387+ }
 388+
 389+ static function fromText($db, $table, $field) {
 390+ $n = new SQLiteField;
 391+ $n->name = $field;
 392+ $n->tablename = $table;
 393+ return $n;
 394+ }
 395+
 396+} // end DatabaseSqlite class
 397+
Property changes on: trunk/phase3/includes/DatabaseSqlite.php
___________________________________________________________________
Name: svn:eol-style
1398 + native

Status & tagging log