MediaWiki
REL1_20
|
00001 <?php 00028 class DatabaseSqlite extends DatabaseBase { 00029 00030 private static $fulltextEnabled = null; 00031 00032 var $mAffectedRows; 00033 var $mLastResult; 00034 var $mDatabaseFile; 00035 var $mName; 00036 00040 protected $mConn; 00041 00051 function __construct( $server = false, $user = false, $password = false, $dbName = false, $flags = 0 ) { 00052 $this->mName = $dbName; 00053 parent::__construct( $server, $user, $password, $dbName, $flags ); 00054 // parent doesn't open when $user is false, but we can work with $dbName 00055 if( $dbName ) { 00056 global $wgSharedDB; 00057 if( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) { 00058 $this->attachDatabase( $wgSharedDB ); 00059 } 00060 } 00061 } 00062 00066 function getType() { 00067 return 'sqlite'; 00068 } 00069 00075 function implicitGroupby() { 00076 return false; 00077 } 00078 00089 function open( $server, $user, $pass, $dbName ) { 00090 global $wgSQLiteDataDir; 00091 00092 $fileName = self::generateFileName( $wgSQLiteDataDir, $dbName ); 00093 if ( !is_readable( $fileName ) ) { 00094 $this->mConn = false; 00095 throw new DBConnectionError( $this, "SQLite database not accessible" ); 00096 } 00097 $this->openFile( $fileName ); 00098 return $this->mConn; 00099 } 00100 00108 function openFile( $fileName ) { 00109 $this->mDatabaseFile = $fileName; 00110 try { 00111 if ( $this->mFlags & DBO_PERSISTENT ) { 00112 $this->mConn = new PDO( "sqlite:$fileName", '', '', 00113 array( PDO::ATTR_PERSISTENT => true ) ); 00114 } else { 00115 $this->mConn = new PDO( "sqlite:$fileName", '', '' ); 00116 } 00117 } catch ( PDOException $e ) { 00118 $err = $e->getMessage(); 00119 } 00120 if ( !$this->mConn ) { 00121 wfDebug( "DB connection error: $err\n" ); 00122 throw new DBConnectionError( $this, $err ); 00123 } 00124 $this->mOpened = !!$this->mConn; 00125 # set error codes only, don't raise exceptions 00126 if ( $this->mOpened ) { 00127 $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); 00128 return true; 00129 } 00130 } 00131 00136 protected function closeConnection() { 00137 $this->mConn = null; 00138 return true; 00139 } 00140 00147 public static function generateFileName( $dir, $dbName ) { 00148 return "$dir/$dbName.sqlite"; 00149 } 00150 00155 function checkForEnabledSearch() { 00156 if ( self::$fulltextEnabled === null ) { 00157 self::$fulltextEnabled = false; 00158 $table = $this->tableName( 'searchindex' ); 00159 $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ ); 00160 if ( $res ) { 00161 $row = $res->fetchRow(); 00162 self::$fulltextEnabled = stristr($row['sql'], 'fts' ) !== false; 00163 } 00164 } 00165 return self::$fulltextEnabled; 00166 } 00167 00172 static function getFulltextSearchModule() { 00173 static $cachedResult = null; 00174 if ( $cachedResult !== null ) { 00175 return $cachedResult; 00176 } 00177 $cachedResult = false; 00178 $table = 'dummy_search_test'; 00179 00180 $db = new DatabaseSqliteStandalone( ':memory:' ); 00181 00182 if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) { 00183 $cachedResult = 'FTS3'; 00184 } 00185 $db->close(); 00186 return $cachedResult; 00187 } 00188 00199 function attachDatabase( $name, $file = false, $fname = 'DatabaseSqlite::attachDatabase' ) { 00200 global $wgSQLiteDataDir; 00201 if ( !$file ) { 00202 $file = self::generateFileName( $wgSQLiteDataDir, $name ); 00203 } 00204 $file = $this->addQuotes( $file ); 00205 return $this->query( "ATTACH DATABASE $file AS $name", $fname ); 00206 } 00207 00215 function isWriteQuery( $sql ) { 00216 return parent::isWriteQuery( $sql ) && !preg_match( '/^ATTACH\b/i', $sql ); 00217 } 00218 00226 protected function doQuery( $sql ) { 00227 $res = $this->mConn->query( $sql ); 00228 if ( $res === false ) { 00229 return false; 00230 } else { 00231 $r = $res instanceof ResultWrapper ? $res->result : $res; 00232 $this->mAffectedRows = $r->rowCount(); 00233 $res = new ResultWrapper( $this, $r->fetchAll() ); 00234 } 00235 return $res; 00236 } 00237 00241 function freeResult( $res ) { 00242 if ( $res instanceof ResultWrapper ) { 00243 $res->result = null; 00244 } else { 00245 $res = null; 00246 } 00247 } 00248 00253 function fetchObject( $res ) { 00254 if ( $res instanceof ResultWrapper ) { 00255 $r =& $res->result; 00256 } else { 00257 $r =& $res; 00258 } 00259 00260 $cur = current( $r ); 00261 if ( is_array( $cur ) ) { 00262 next( $r ); 00263 $obj = new stdClass; 00264 foreach ( $cur as $k => $v ) { 00265 if ( !is_numeric( $k ) ) { 00266 $obj->$k = $v; 00267 } 00268 } 00269 00270 return $obj; 00271 } 00272 return false; 00273 } 00274 00279 function fetchRow( $res ) { 00280 if ( $res instanceof ResultWrapper ) { 00281 $r =& $res->result; 00282 } else { 00283 $r =& $res; 00284 } 00285 $cur = current( $r ); 00286 if ( is_array( $cur ) ) { 00287 next( $r ); 00288 return $cur; 00289 } 00290 return false; 00291 } 00292 00300 function numRows( $res ) { 00301 $r = $res instanceof ResultWrapper ? $res->result : $res; 00302 return count( $r ); 00303 } 00304 00309 function numFields( $res ) { 00310 $r = $res instanceof ResultWrapper ? $res->result : $res; 00311 return is_array( $r ) ? count( $r[0] ) : 0; 00312 } 00313 00319 function fieldName( $res, $n ) { 00320 $r = $res instanceof ResultWrapper ? $res->result : $res; 00321 if ( is_array( $r ) ) { 00322 $keys = array_keys( $r[0] ); 00323 return $keys[$n]; 00324 } 00325 return false; 00326 } 00327 00335 function tableName( $name, $format = 'quoted' ) { 00336 // table names starting with sqlite_ are reserved 00337 if ( strpos( $name, 'sqlite_' ) === 0 ) { 00338 return $name; 00339 } 00340 return str_replace( '"', '', parent::tableName( $name, $format ) ); 00341 } 00342 00350 function indexName( $index ) { 00351 return $index; 00352 } 00353 00359 function insertId() { 00360 // PDO::lastInsertId yields a string :( 00361 return intval( $this->mConn->lastInsertId() ); 00362 } 00363 00368 function dataSeek( $res, $row ) { 00369 if ( $res instanceof ResultWrapper ) { 00370 $r =& $res->result; 00371 } else { 00372 $r =& $res; 00373 } 00374 reset( $r ); 00375 if ( $row > 0 ) { 00376 for ( $i = 0; $i < $row; $i++ ) { 00377 next( $r ); 00378 } 00379 } 00380 } 00381 00385 function lastError() { 00386 if ( !is_object( $this->mConn ) ) { 00387 return "Cannot return last error, no db connection"; 00388 } 00389 $e = $this->mConn->errorInfo(); 00390 return isset( $e[2] ) ? $e[2] : ''; 00391 } 00392 00396 function lastErrno() { 00397 if ( !is_object( $this->mConn ) ) { 00398 return "Cannot return last error, no db connection"; 00399 } else { 00400 $info = $this->mConn->errorInfo(); 00401 return $info[1]; 00402 } 00403 } 00404 00408 function affectedRows() { 00409 return $this->mAffectedRows; 00410 } 00411 00419 function indexInfo( $table, $index, $fname = 'DatabaseSqlite::indexExists' ) { 00420 $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')'; 00421 $res = $this->query( $sql, $fname ); 00422 if ( !$res ) { 00423 return null; 00424 } 00425 if ( $res->numRows() == 0 ) { 00426 return false; 00427 } 00428 $info = array(); 00429 foreach ( $res as $row ) { 00430 $info[] = $row->name; 00431 } 00432 return $info; 00433 } 00434 00441 function indexUnique( $table, $index, $fname = 'DatabaseSqlite::indexUnique' ) { 00442 $row = $this->selectRow( 'sqlite_master', '*', 00443 array( 00444 'type' => 'index', 00445 'name' => $this->indexName( $index ), 00446 ), $fname ); 00447 if ( !$row || !isset( $row->sql ) ) { 00448 return null; 00449 } 00450 00451 // $row->sql will be of the form CREATE [UNIQUE] INDEX ... 00452 $indexPos = strpos( $row->sql, 'INDEX' ); 00453 if ( $indexPos === false ) { 00454 return null; 00455 } 00456 $firstPart = substr( $row->sql, 0, $indexPos ); 00457 $options = explode( ' ', $firstPart ); 00458 return in_array( 'UNIQUE', $options ); 00459 } 00460 00468 function makeSelectOptions( $options ) { 00469 foreach ( $options as $k => $v ) { 00470 if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) { 00471 $options[$k] = ''; 00472 } 00473 } 00474 return parent::makeSelectOptions( $options ); 00475 } 00476 00481 function makeUpdateOptions( $options ) { 00482 $options = self::fixIgnore( $options ); 00483 return parent::makeUpdateOptions( $options ); 00484 } 00485 00490 static function fixIgnore( $options ) { 00491 # SQLite uses OR IGNORE not just IGNORE 00492 foreach ( $options as $k => $v ) { 00493 if ( $v == 'IGNORE' ) { 00494 $options[$k] = 'OR IGNORE'; 00495 } 00496 } 00497 return $options; 00498 } 00499 00504 function makeInsertOptions( $options ) { 00505 $options = self::fixIgnore( $options ); 00506 return parent::makeInsertOptions( $options ); 00507 } 00508 00513 function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) { 00514 if ( !count( $a ) ) { 00515 return true; 00516 } 00517 00518 # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts 00519 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 00520 $ret = true; 00521 foreach ( $a as $v ) { 00522 if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) { 00523 $ret = false; 00524 } 00525 } 00526 } else { 00527 $ret = parent::insert( $table, $a, "$fname/single-row", $options ); 00528 } 00529 00530 return $ret; 00531 } 00532 00540 function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseSqlite::replace' ) { 00541 if ( !count( $rows ) ) return true; 00542 00543 # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries 00544 if ( isset( $rows[0] ) && is_array( $rows[0] ) ) { 00545 $ret = true; 00546 foreach ( $rows as $v ) { 00547 if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) { 00548 $ret = false; 00549 } 00550 } 00551 } else { 00552 $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" ); 00553 } 00554 00555 return $ret; 00556 } 00557 00564 function textFieldSize( $table, $field ) { 00565 return -1; 00566 } 00567 00571 function unionSupportsOrderAndLimit() { 00572 return false; 00573 } 00574 00580 function unionQueries( $sqls, $all ) { 00581 $glue = $all ? ' UNION ALL ' : ' UNION '; 00582 return implode( $glue, $sqls ); 00583 } 00584 00588 function wasDeadlock() { 00589 return $this->lastErrno() == 5; // SQLITE_BUSY 00590 } 00591 00595 function wasErrorReissuable() { 00596 return $this->lastErrno() == 17; // SQLITE_SCHEMA; 00597 } 00598 00602 function wasReadOnlyError() { 00603 return $this->lastErrno() == 8; // SQLITE_READONLY; 00604 } 00605 00609 public static function getSoftwareLink() { 00610 return "[http://sqlite.org/ SQLite]"; 00611 } 00612 00616 function getServerVersion() { 00617 $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION ); 00618 return $ver; 00619 } 00620 00624 public function getServerInfo() { 00625 return wfMessage( self::getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() )->text(); 00626 } 00627 00636 function fieldInfo( $table, $field ) { 00637 $tableName = $this->tableName( $table ); 00638 $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')'; 00639 $res = $this->query( $sql, __METHOD__ ); 00640 foreach ( $res as $row ) { 00641 if ( $row->name == $field ) { 00642 return new SQLiteField( $row, $tableName ); 00643 } 00644 } 00645 return false; 00646 } 00647 00648 protected function doBegin( $fname = '' ) { 00649 if ( $this->mTrxLevel == 1 ) { 00650 $this->commit( __METHOD__ ); 00651 } 00652 $this->mConn->beginTransaction(); 00653 $this->mTrxLevel = 1; 00654 } 00655 00656 protected function doCommit( $fname = '' ) { 00657 if ( $this->mTrxLevel == 0 ) { 00658 return; 00659 } 00660 $this->mConn->commit(); 00661 $this->mTrxLevel = 0; 00662 } 00663 00664 protected function doRollback( $fname = '' ) { 00665 if ( $this->mTrxLevel == 0 ) { 00666 return; 00667 } 00668 $this->mConn->rollBack(); 00669 $this->mTrxLevel = 0; 00670 } 00671 00676 function strencode( $s ) { 00677 return substr( $this->addQuotes( $s ), 1, - 1 ); 00678 } 00679 00684 function encodeBlob( $b ) { 00685 return new Blob( $b ); 00686 } 00687 00692 function decodeBlob( $b ) { 00693 if ( $b instanceof Blob ) { 00694 $b = $b->fetch(); 00695 } 00696 return $b; 00697 } 00698 00703 function addQuotes( $s ) { 00704 if ( $s instanceof Blob ) { 00705 return "x'" . bin2hex( $s->fetch() ) . "'"; 00706 } else { 00707 return $this->mConn->quote( $s ); 00708 } 00709 } 00710 00714 function buildLike() { 00715 $params = func_get_args(); 00716 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 00717 $params = $params[0]; 00718 } 00719 return parent::buildLike( $params ) . "ESCAPE '\' "; 00720 } 00721 00725 public function getSearchEngine() { 00726 return "SearchSqlite"; 00727 } 00728 00733 public function deadlockLoop( /*...*/ ) { 00734 $args = func_get_args(); 00735 $function = array_shift( $args ); 00736 return call_user_func_array( $function, $args ); 00737 } 00738 00743 protected function replaceVars( $s ) { 00744 $s = parent::replaceVars( $s ); 00745 if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) { 00746 // CREATE TABLE hacks to allow schema file sharing with MySQL 00747 00748 // binary/varbinary column type -> blob 00749 $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s ); 00750 // no such thing as unsigned 00751 $s = preg_replace( '/\b(un)?signed\b/i', '', $s ); 00752 // INT -> INTEGER 00753 $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s ); 00754 // floating point types -> REAL 00755 $s = preg_replace( '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i', 'REAL', $s ); 00756 // varchar -> TEXT 00757 $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s ); 00758 // TEXT normalization 00759 $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s ); 00760 // BLOB normalization 00761 $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s ); 00762 // BOOL -> INTEGER 00763 $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s ); 00764 // DATETIME -> TEXT 00765 $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s ); 00766 // No ENUM type 00767 $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s ); 00768 // binary collation type -> nothing 00769 $s = preg_replace( '/\bbinary\b/i', '', $s ); 00770 // auto_increment -> autoincrement 00771 $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s ); 00772 // No explicit options 00773 $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s ); 00774 // AUTOINCREMENT should immedidately follow PRIMARY KEY 00775 $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s ); 00776 } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) { 00777 // No truncated indexes 00778 $s = preg_replace( '/\(\d+\)/', '', $s ); 00779 // No FULLTEXT 00780 $s = preg_replace( '/\bfulltext\b/i', '', $s ); 00781 } 00782 return $s; 00783 } 00784 00792 function buildConcat( $stringList ) { 00793 return '(' . implode( ') || (', $stringList ) . ')'; 00794 } 00795 00804 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseSqlite::duplicateTableStructure' ) { 00805 $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" . $this->addQuotes( $oldName ) . " AND type='table'", $fname ); 00806 $obj = $this->fetchObject( $res ); 00807 if ( !$obj ) { 00808 throw new MWException( "Couldn't retrieve structure for table $oldName" ); 00809 } 00810 $sql = $obj->sql; 00811 $sql = preg_replace( '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/', $this->addIdentifierQuotes( $newName ), $sql, 1 ); 00812 if ( $temporary ) { 00813 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) { 00814 wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" ); 00815 } else { 00816 $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql ); 00817 } 00818 } 00819 return $this->query( $sql, $fname ); 00820 } 00821 00822 00831 function listTables( $prefix = null, $fname = 'DatabaseSqlite::listTables' ) { 00832 $result = $this->select( 00833 'sqlite_master', 00834 'name', 00835 "type='table'" 00836 ); 00837 00838 $endArray = array(); 00839 00840 foreach( $result as $table ) { 00841 $vars = get_object_vars($table); 00842 $table = array_pop( $vars ); 00843 00844 if( !$prefix || strpos( $table, $prefix ) === 0 ) { 00845 if ( strpos( $table, 'sqlite_' ) !== 0 ) { 00846 $endArray[] = $table; 00847 } 00848 00849 } 00850 } 00851 00852 return $endArray; 00853 } 00854 00855 } // end DatabaseSqlite class 00856 00861 class DatabaseSqliteStandalone extends DatabaseSqlite { 00862 public function __construct( $fileName, $flags = 0 ) { 00863 $this->mFlags = $flags; 00864 $this->tablePrefix( null ); 00865 $this->openFile( $fileName ); 00866 } 00867 } 00868 00872 class SQLiteField implements Field { 00873 private $info, $tableName; 00874 function __construct( $info, $tableName ) { 00875 $this->info = $info; 00876 $this->tableName = $tableName; 00877 } 00878 00879 function name() { 00880 return $this->info->name; 00881 } 00882 00883 function tableName() { 00884 return $this->tableName; 00885 } 00886 00887 function defaultValue() { 00888 if ( is_string( $this->info->dflt_value ) ) { 00889 // Typically quoted 00890 if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) { 00891 return str_replace( "''", "'", $this->info->dflt_value ); 00892 } 00893 } 00894 return $this->info->dflt_value; 00895 } 00896 00900 function isNullable() { 00901 return !$this->info->notnull; 00902 } 00903 00904 function type() { 00905 return $this->info->type; 00906 } 00907 00908 } // end SQLiteField