X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2Fdb%2FDatabaseSqlite.php;h=1313252329a39e0edbd966142ced9eebb0ad665e;hb=16cded8b32fc671125d89af1827f49665715c0ea;hp=1eb49108cdff0b5a347a72bbf29d3245ee5da224;hpb=05ddb6910957520494ddb56a0e6e67a6b397f054;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php index 1eb49108cd..1313252329 100644 --- a/includes/db/DatabaseSqlite.php +++ b/includes/db/DatabaseSqlite.php @@ -1,10 +1,10 @@ mFailFunction = $failFunction; + global $wgSharedDB; $this->mFlags = $flags; - $this->mDatabaseFile = "$wgSQLiteDataDir/$dbName.sqlite"; - if( !is_readable( $this->mDatabaseFile ) ) - throw new DBConnectionError( $this, "SQLite database not accessible" ); $this->mName = $dbName; - $this->open( $server, $user, $password, $dbName ); + + if ( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) { + $this->attachDatabase( $wgSharedDB ); + } + } + + function getType() { + return 'sqlite'; } /** - * todo: check if these should be true like parent class + * @todo: check if it should be true like parent class */ function implicitGroupby() { return false; } - function implicitOrderby() { return false; } static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 ) { return new DatabaseSqlite( $server, $user, $password, $dbName, $failFunction, $flags ); @@ -45,33 +50,43 @@ class DatabaseSqlite extends DatabaseBase { * NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases */ function open( $server, $user, $pass, $dbName ) { - $this->mConn = false; - if ( $dbName ) { - $file = $this->mDatabaseFile; - try { - if ( $this->mFlags & DBO_PERSISTENT ) { - $this->mConn = new PDO( "sqlite:$file", $user, $pass, - 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; - } + global $wgSQLiteDataDir; + $fileName = self::generateFileName( $wgSQLiteDataDir, $dbName ); + if ( !is_readable( $fileName ) ) { + $this->mConn = false; + throw new DBConnectionError( $this, "SQLite database not accessible" ); + } + $this->openFile( $fileName ); + return $this->mConn; + } + + /** + * Opens a database file + * @return SQL connection or false if failed + */ + function openFile( $fileName ) { + $this->mDatabaseFile = $fileName; + try { + if ( $this->mFlags & DBO_PERSISTENT ) { + $this->mConn = new PDO( "sqlite:$fileName", '', '', + array( PDO::ATTR_PERSISTENT => true ) ); + } else { + $this->mConn = new PDO( "sqlite:$fileName", '', '' ); } - $this->mOpened = $this->mConn; - # set error codes only, don't raise exceptions + } catch ( PDOException $e ) { + $err = $e->getMessage(); + } + if ( !$this->mConn ) { + wfDebug( "DB connection error: $err\n" ); + throw new DBConnectionError( $this, $err ); + } + $this->mOpened = !!$this->mConn; + # set error codes only, don't raise exceptions + if ( $this->mOpened ) { $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); + return true; } - return $this->mConn; } /** @@ -80,12 +95,81 @@ class DatabaseSqlite extends DatabaseBase { function close() { $this->mOpened = false; if ( is_object( $this->mConn ) ) { - if ( $this->trxLevel() ) $this->immediateCommit(); + if ( $this->trxLevel() ) $this->commit(); $this->mConn = null; } return true; } + /** + * 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"; + } + + /** + * Check if the searchindext table is FTS enabled. + * @returns false if not enabled. + */ + function checkForEnabledSearch() { + if ( self::$fulltextEnabled === null ) { + self::$fulltextEnabled = false; + $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = 'searchindex'", __METHOD__ ); + if ( $res ) { + $row = $res->fetchRow(); + self::$fulltextEnabled = stristr($row['sql'], 'fts' ) !== false; + } + } + return self::$fulltextEnabled; + } + + /** + * Returns version of currently supported SQLite fulltext search module or false if none present. + * @return String + */ + function getFulltextSearchModule() { + static $cachedResult = null; + if ( $cachedResult !== null ) { + return $cachedResult; + } + $cachedResult = false; + $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__ ); + $cachedResult = 'FTS3'; + } + return $cachedResult; + } + + /** + * Attaches external database to our connection, see http://sqlite.org/lang_attach.html + * for details. + * @param $name String: database name to be used in queries like SELECT foo FROM dbname.table + * @param $file String: database file name. If omitted, will be generated using $name and $wgSQLiteDataDir + * @param $fname String: calling function name + */ + function attachDatabase( $name, $file = false, $fname = 'DatabaseSqlite::attachDatabase' ) { + global $wgSQLiteDataDir; + if ( !$file ) { + $file = self::generateFileName( $wgSQLiteDataDir, $name ); + } + $file = $this->addQuotes( $file ); + return $this->query( "ATTACH DATABASE $file AS $name", $fname ); + } + + /** + * @see DatabaseBase::isWriteQuery() + */ + function isWriteQuery( $sql ) { + return parent::isWriteQuery( $sql ) && !preg_match( '/^ATTACH\b/i', $sql ); + } + /** * SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result */ @@ -102,40 +186,44 @@ class DatabaseSqlite extends DatabaseBase { } function freeResult( $res ) { - if ( $res instanceof ResultWrapper ) - $res->result = NULL; - else - $res = NULL; + if ( $res instanceof ResultWrapper ) { + $res->result = null; + } else { + $res = null; + } } - function fetchObject($res) { - if ($res instanceof ResultWrapper) + function fetchObject( $res ) { + if ( $res instanceof ResultWrapper ) { $r =& $res->result; - else + } else { $r =& $res; + } $cur = current( $r ); if ( is_array( $cur ) ) { next( $r ); $obj = new stdClass; - foreach ( $cur as $k => $v ) - if ( !is_numeric( $k ) ) + foreach ( $cur as $k => $v ) { + if ( !is_numeric( $k ) ) { $obj->$k = $v; + } + } return $obj; } return false; } - function fetchRow($res) { - if ( $res instanceof ResultWrapper ) + function fetchRow( $res ) { + if ( $res instanceof ResultWrapper ) { $r =& $res->result; - else + } else { $r =& $res; - - $cur = current($r); - if (is_array($cur)) { - next($r); + } + $cur = current( $r ); + if ( is_array( $cur ) ) { + next( $r ); return $cur; } return false; @@ -167,6 +255,8 @@ class DatabaseSqlite extends DatabaseBase { * Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks */ function tableName( $name ) { + // table names starting with sqlite_ are reserved + if ( strpos( $name, 'sqlite_' ) === 0 ) return $name; return str_replace( '`', '', parent::tableName( $name ) ); } @@ -185,19 +275,23 @@ class DatabaseSqlite extends DatabaseBase { } function dataSeek( $res, $row ) { - if ( $res instanceof ResultWrapper ) + if ( $res instanceof ResultWrapper ) { $r =& $res->result; - else + } else { $r =& $res; + } reset( $r ); - if ( $row > 0 ) - for ( $i = 0; $i < $row; $i++ ) + if ( $row > 0 ) { + for ( $i = 0; $i < $row; $i++ ) { next( $r ); + } + } } function lastError() { - if ( !is_object( $this->mConn ) ) + if ( !is_object( $this->mConn ) ) { return "Cannot return last error, no db connection"; + } $e = $this->mConn->errorInfo(); return isset( $e[2] ) ? $e[2] : ''; } @@ -260,30 +354,40 @@ class DatabaseSqlite extends DatabaseBase { * Filter the options used in SELECT statements */ function makeSelectOptions( $options ) { - foreach ( $options as $k => $v ) - if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) + foreach ( $options as $k => $v ) { + if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) { $options[$k] = ''; + } + } return parent::makeSelectOptions( $options ); } /** - * Based on MySQL method (parent) with some prior SQLite-sepcific adjustments + * Based on generic method (parent) with some prior SQLite-sepcific adjustments */ function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) { - if ( !count( $a ) ) return true; - if ( !is_array( $options ) ) $options = array( $options ); + if ( !count( $a ) ) { + return true; + } + if ( !is_array( $options ) ) { + $options = array( $options ); + } # SQLite uses OR IGNORE not just IGNORE - foreach ( $options as $k => $v ) - if ( $v == 'IGNORE' ) + foreach ( $options as $k => $v ) { + if ( $v == 'IGNORE' ) { $options[$k] = 'OR IGNORE'; + } + } # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts if ( isset( $a[0] ) && is_array( $a[0] ) ) { $ret = true; - foreach ( $a as $k => $v ) - if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) + foreach ( $a as $v ) { + if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) { $ret = false; + } + } } else { $ret = parent::insert( $table, $a, "$fname/single-row", $options ); } @@ -291,6 +395,24 @@ class DatabaseSqlite extends DatabaseBase { return $ret; } + function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseSqlite::replace' ) { + if ( !count( $rows ) ) return true; + + # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries + if ( isset( $rows[0] ) && is_array( $rows[0] ) ) { + $ret = true; + foreach ( $rows as $v ) { + if ( !parent::replace( $table, $uniqueIndexes, $v, "$fname/multi-row" ) ) { + $ret = false; + } + } + } else { + $ret = parent::replace( $table, $uniqueIndexes, $rows, "$fname/single-row" ); + } + + return $ret; + } + /** * 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. @@ -299,22 +421,31 @@ class DatabaseSqlite extends DatabaseBase { return - 1; } + function unionSupportsOrderAndLimit() { + return false; + } + + function unionQueries( $sqls, $all ) { + $glue = $all ? ' UNION ALL ' : ' UNION '; + return implode( $glue, $sqls ); + } + function wasDeadlock() { - return $this->lastErrno() == SQLITE_BUSY; + return $this->lastErrno() == 5; // SQLITE_BUSY } function wasErrorReissuable() { - return $this->lastErrno() == SQLITE_SCHEMA; + return $this->lastErrno() == 17; // SQLITE_SCHEMA; } function wasReadOnlyError() { - return $this->lastErrno() == SQLITE_READONLY; + return $this->lastErrno() == 8; // SQLITE_READONLY; } /** * @return string wikitext of a link to the server software's web site */ - function getSoftwareLink() { + public static function getSoftwareLink() { return "[http://sqlite.org/ SQLite]"; } @@ -327,11 +458,10 @@ class DatabaseSqlite extends DatabaseBase { } /** - * Query whether a given column exists in the mediawiki schema + * @return string User-friendly database information */ - function fieldExists( $table, $field, $fname = '' ) { - $info = $this->fieldInfo( $table, $field ); - return (bool)$info; + public function getServerInfo() { + return wfMsg( $this->getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() ); } /** @@ -399,42 +529,46 @@ class DatabaseSqlite extends DatabaseBase { return $s; } - /** - * How lagged is this slave? - */ - public function getLag() { - return 0; + function buildLike() { + $params = func_get_args(); + if ( count( $params ) > 0 && is_array( $params[0] ) ) { + $params = $params[0]; + } + return parent::buildLike( $params ) . "ESCAPE '\' "; } /** - * 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) + * Called by the installer script + * - this is the same way PostgreSQL works, MySQL reads in tables.sql and interwiki.sql using DatabaseBase::sourceFile() */ public function setup_database() { - global $IP, $wgSQLiteDataDir, $wgDBTableOptions; - $wgDBTableOptions = ''; + global $IP; # Process common MySQL/SQLite table definitions $err = $this->sourceFile( "$IP/maintenance/tables.sql" ); if ( $err !== true ) { - $this->reportQueryError( $err, 0, $sql, __FUNCTION__ ); - exit( 1 ); + echo " FAILED"; + dieout( htmlspecialchars( $err ) ); } + echo " done."; # Use DatabasePostgres's code to populate interwiki from MySQL template $f = fopen( "$IP/maintenance/interwiki.sql", 'r' ); - if ( $f == false ) dieout( "
  • Could not find the interwiki.sql file" ); - $sql = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES "; + if ( !$f ) { + dieout( "Could not find the interwiki.sql file." ); + } + + $sql = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local,iw_api,iw_wikiid) VALUES "; while ( !feof( $f ) ) { $line = fgets( $f, 1024 ); $matches = array(); if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) continue; - $this->query( "$sql $matches[1],$matches[2])" ); + $this->query( "$sql $matches[1],$matches[2],'','')" ); } } - + public function getSearchEngine() { - return "SearchEngineDummy"; + return "SearchSqlite"; } /** @@ -448,23 +582,35 @@ class DatabaseSqlite extends DatabaseBase { protected function replaceVars( $s ) { $s = parent::replaceVars( $s ); - if ( preg_match( '/^\s*CREATE TABLE/i', $s ) ) { + if ( preg_match( '/^\s*(CREATE|ALTER) 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 ); + $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $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 ); + $s = preg_replace( '/\b(un)?signed\b/i', '', $s ); + // INT -> INTEGER + $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s ); + // floating point types -> REAL + $s = preg_replace( '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i', 'REAL', $s ); + // varchar -> TEXT + $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s ); + // TEXT normalization + $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s ); + // BLOB normalization + $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s ); + // BOOL -> INTEGER + $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s ); + // DATETIME -> TEXT + $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s ); // No ENUM type - $s = preg_replace( '/enum\([^)]*\)/i', 'blob', $s ); + $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s ); // binary collation type -> nothing $s = preg_replace( '/\bbinary\b/i', '', $s ); // auto_increment -> autoincrement - $s = preg_replace( '/\bauto_increment\b/i', 'autoincrement', $s ); + $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s ); // No explicit options - $s = preg_replace( '/\)[^)]*$/', ')', $s ); + $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s ); } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) { // No truncated indexes $s = preg_replace( '/\(\d+\)/', '', $s ); @@ -481,8 +627,31 @@ class DatabaseSqlite extends DatabaseBase { return '(' . implode( ') || (', $stringList ) . ')'; } + function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseSqlite::duplicateTableStructure' ) { + $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name='$oldName' AND type='table'", $fname ); + $obj = $this->fetchObject( $res ); + if ( !$obj ) { + throw new MWException( "Couldn't retrieve structure for table $oldName" ); + } + $sql = $obj->sql; + $sql = preg_replace( '/\b' . preg_quote( $oldName ) . '\b/', $newName, $sql, 1 ); + return $this->query( $sql, $fname ); + } + } // end DatabaseSqlite class +/** + * This class allows simple acccess to a SQLite database independently from main database settings + * @ingroup Database + */ +class DatabaseSqliteStandalone extends DatabaseSqlite { + public function __construct( $fileName, $flags = 0 ) { + $this->mFlags = $flags; + $this->tablePrefix( null ); + $this->openFile( $fileName ); + } +} + /** * @ingroup Database */ @@ -520,7 +689,7 @@ class SQLiteField { return true; } - # isKey(), isMultipleKey() not implemented, MySQL-specific concept. + # isKey(), isMultipleKey() not implemented, MySQL-specific concept. # Suggest removal from base class [TS] function type() {