summaryrefslogtreecommitdiff
path: root/includes/db/DatabaseSqlite.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/db/DatabaseSqlite.php')
-rw-r--r--includes/db/DatabaseSqlite.php238
1 files changed, 173 insertions, 65 deletions
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index dfc506cc..7a595697 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -15,6 +15,7 @@ class DatabaseSqlite extends Database {
var $mAffectedRows;
var $mLastResult;
var $mDatabaseFile;
+ var $mName;
/**
* Constructor
@@ -26,6 +27,7 @@ class DatabaseSqlite extends Database {
$this->mFailFunction = $failFunction;
$this->mFlags = $flags;
$this->mDatabaseFile = "$wgSQLiteDataDir/$dbName.sqlite";
+ $this->mName = $dbName;
$this->open($server, $user, $password, $dbName);
}
@@ -89,8 +91,9 @@ class DatabaseSqlite extends Database {
*/
function doQuery($sql) {
$res = $this->mConn->query($sql);
- if ($res === false) $this->reportQueryError($this->lastError(),$this->lastErrno(),$sql,__FUNCTION__);
- else {
+ if ($res === false) {
+ return false;
+ } else {
$r = $res instanceof ResultWrapper ? $res->result : $res;
$this->mAffectedRows = $r->rowCount();
$res = new ResultWrapper($this,$r->fetchAll());
@@ -98,11 +101,11 @@ class DatabaseSqlite extends Database {
return $res;
}
- function freeResult(&$res) {
+ function freeResult($res) {
if ($res instanceof ResultWrapper) $res->result = NULL; else $res = NULL;
}
- function fetchObject(&$res) {
+ function fetchObject($res) {
if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res;
$cur = current($r);
if (is_array($cur)) {
@@ -114,7 +117,7 @@ class DatabaseSqlite extends Database {
return false;
}
- function fetchRow(&$res) {
+ function fetchRow($res) {
if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res;
$cur = current($r);
if (is_array($cur)) {
@@ -127,17 +130,17 @@ 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);
}
- function numFields(&$res) {
+ function numFields($res) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
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]);
@@ -154,13 +157,20 @@ class DatabaseSqlite extends Database {
}
/**
+ * Index names have DB scope
+ */
+ function indexName( $index ) {
+ return $index;
+ }
+
+ /**
* This must be called after nextSequenceVal
*/
function insertId() {
return $this->mConn->lastInsertId();
}
- function dataSeek(&$res,$row) {
+ 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);
@@ -173,8 +183,12 @@ class DatabaseSqlite extends Database {
}
function lastErrno() {
- if (!is_object($this->mConn)) return "Cannot return last error, no db connection";
- return $this->mConn->errorCode();
+ if (!is_object($this->mConn)) {
+ return "Cannot return last error, no db connection";
+ } else {
+ $info = $this->mConn->errorInfo();
+ return $info[1];
+ }
}
function affectedRows() {
@@ -183,14 +197,43 @@ class DatabaseSqlite extends Database {
/**
* Returns information about an index
+ * Returns false if the index does not exist
* - if errors are explicitly ignored, returns NULL on failure
*/
function indexInfo($table, $index, $fname = 'Database::indexExists') {
- return false;
+ $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
+ $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;
}
function indexUnique($table, $index, $fname = 'Database::indexUnique') {
- return false;
+ $row = $this->selectRow( 'sqlite_master', '*',
+ array(
+ 'type' => 'index',
+ 'name' => $this->indexName( $index ),
+ ), $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 );
}
/**
@@ -228,7 +271,10 @@ class DatabaseSqlite extends Database {
return '';
}
- # Returns the size of a text field, or -1 for "unlimited"
+ /**
+ * 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;
}
@@ -252,6 +298,10 @@ class DatabaseSqlite extends Database {
return $this->lastErrno() == SQLITE_BUSY;
}
+ function wasErrorReissuable() {
+ return $this->lastErrno() == SQLITE_SCHEMA;
+ }
+
/**
* @return string wikitext of a link to the server software's web site
*/
@@ -265,38 +315,53 @@ class DatabaseSqlite extends Database {
function getServerVersion() {
global $wgContLang;
$ver = $this->mConn->getAttribute(PDO::ATTR_SERVER_VERSION);
- $size = $wgContLang->formatSize(filesize($this->mDatabaseFile));
- $file = basename($this->mDatabaseFile);
- return $ver." ($file: $size)";
+ return $ver;
}
/**
* Query whether a given column exists in the mediawiki schema
*/
- function fieldExists($table, $field) { return true; }
+ function fieldExists($table, $field, $fname = '') {
+ $info = $this->fieldInfo( $table, $field );
+ return (bool)$info;
+ }
- function fieldInfo($table, $field) { return SQLiteField::fromText($this, $table, $field); }
+ /**
+ * Get information about a given field
+ * Returns false if the field does not exist.
+ */
+ function fieldInfo($table, $field) {
+ $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;
+ }
- function begin() {
+ function begin( $fname = '' ) {
if ($this->mTrxLevel == 1) $this->commit();
$this->mConn->beginTransaction();
$this->mTrxLevel = 1;
}
- function commit() {
+ function commit( $fname = '' ) {
if ($this->mTrxLevel == 0) return;
$this->mConn->commit();
$this->mTrxLevel = 0;
}
- function rollback() {
+ function rollback( $fname = '' ) {
if ($this->mTrxLevel == 0) return;
$this->mConn->rollBack();
$this->mTrxLevel = 0;
}
function limitResultForUpdate($sql, $num) {
- return $sql;
+ return $this->limitResult( $sql, $num );
}
function strencode($s) {
@@ -325,17 +390,25 @@ class DatabaseSqlite extends Database {
function quote_ident($s) { return $s; }
/**
- * For now, does nothing
+ * 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) { return true; }
+ function selectDB($db) {
+ if ( $db != $this->mName ) {
+ throw new MWException( 'selectDB is not implemented in SQLite' );
+ }
+ }
/**
* not done
*/
public function setTimeout($timeout) { return; }
+ /**
+ * No-op for a non-networked database
+ */
function ping() {
- wfDebug("Function ping() not written for SQLite yet");
return true;
}
@@ -353,39 +426,16 @@ class DatabaseSqlite extends Database {
public function setup_database() {
global $IP,$wgSQLiteDataDir,$wgDBTableOptions;
$wgDBTableOptions = '';
- $mysql_tmpl = "$IP/maintenance/tables.sql";
- $mysql_iw = "$IP/maintenance/interwiki.sql";
- $sqlite_tmpl = "$IP/maintenance/sqlite/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 (!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('/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 AUTOINCREMENT',$sql);
- $sql = str_replace(' unsigned','',$sql);
- $sql = str_replace(' int ',' INTEGER ',$sql);
- $sql = str_replace('NOT NULL','',$sql);
-
- # Tidy up and write file
- $sql = preg_replace('/^\s*^/m','',$sql); # Remove empty lines
- $sql = preg_replace('/;$/m',";\n",$sql); # Separate each statement with an empty line
- file_put_contents($sqlite_tmpl,$sql);
- }
- # Parse the SQLite template replacing inline variables such as /*$wgDBprefix*/
- $err = $this->sourceFile($sqlite_tmpl);
- if ($err !== true) $this->reportQueryError($err,0,$sql,__FUNCTION__);
+ # Process common MySQL/SQLite table definitions
+ $err = $this->sourceFile( "$IP/maintenance/tables.sql" );
+ if ($err !== true) {
+ $this->reportQueryError($err,0,$sql,__FUNCTION__);
+ exit( 1 );
+ }
# Use DatabasePostgres's code to populate interwiki from MySQL template
- $f = fopen($mysql_iw,'r');
+ $f = fopen("$IP/maintenance/interwiki.sql",'r');
if ($f == false) dieout("<li>Could not find the interwiki.sql file");
$sql = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES ";
while (!feof($f)) {
@@ -418,22 +468,80 @@ class DatabaseSqlite extends Database {
$function = array_shift( $args );
return call_user_func_array( $function, $args );
}
-}
+
+ 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
+
+ // 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;
+ }
+
+} // end DatabaseSqlite class
/**
* @ingroup Database
*/
-class SQLiteField extends MySQLField {
+class SQLiteField {
+ private $info, $tableName;
+ function __construct( $info, $tableName ) {
+ $this->info = $info;
+ $this->tableName = $tableName;
+ }
- function __construct() {
+ function name() {
+ return $this->info->name;
}
- static function fromText($db, $table, $field) {
- $n = new SQLiteField;
- $n->name = $field;
- $n->tablename = $table;
- return $n;
+ function tableName() {
+ return $this->tableName;
}
-} // end DatabaseSqlite class
+ 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]
+
+ function type() {
+ return $this->info->type;
+ }
+
+} // end SQLiteField