MediaWiki
REL1_19
|
00001 <?php 00013 class DatabaseSqlite extends DatabaseBase { 00014 00015 private static $fulltextEnabled = null; 00016 00017 var $mAffectedRows; 00018 var $mLastResult; 00019 var $mDatabaseFile; 00020 var $mName; 00021 00025 protected $mConn; 00026 00036 function __construct( $server = false, $user = false, $password = false, $dbName = false, $flags = 0 ) { 00037 $this->mName = $dbName; 00038 parent::__construct( $server, $user, $password, $dbName, $flags ); 00039 // parent doesn't open when $user is false, but we can work with $dbName 00040 if( $dbName ) { 00041 global $wgSharedDB; 00042 if( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) { 00043 $this->attachDatabase( $wgSharedDB ); 00044 } 00045 } 00046 } 00047 00051 function getType() { 00052 return 'sqlite'; 00053 } 00054 00060 function implicitGroupby() { 00061 return false; 00062 } 00063 00074 function open( $server, $user, $pass, $dbName ) { 00075 global $wgSQLiteDataDir; 00076 00077 $fileName = self::generateFileName( $wgSQLiteDataDir, $dbName ); 00078 if ( !is_readable( $fileName ) ) { 00079 $this->mConn = false; 00080 throw new DBConnectionError( $this, "SQLite database not accessible" ); 00081 } 00082 $this->openFile( $fileName ); 00083 return $this->mConn; 00084 } 00085 00093 function openFile( $fileName ) { 00094 $this->mDatabaseFile = $fileName; 00095 try { 00096 if ( $this->mFlags & DBO_PERSISTENT ) { 00097 $this->mConn = new PDO( "sqlite:$fileName", '', '', 00098 array( PDO::ATTR_PERSISTENT => true ) ); 00099 } else { 00100 $this->mConn = new PDO( "sqlite:$fileName", '', '' ); 00101 } 00102 } catch ( PDOException $e ) { 00103 $err = $e->getMessage(); 00104 } 00105 if ( !$this->mConn ) { 00106 wfDebug( "DB connection error: $err\n" ); 00107 throw new DBConnectionError( $this, $err ); 00108 } 00109 $this->mOpened = !!$this->mConn; 00110 # set error codes only, don't raise exceptions 00111 if ( $this->mOpened ) { 00112 $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); 00113 return true; 00114 } 00115 } 00116 00122 function close() { 00123 $this->mOpened = false; 00124 if ( is_object( $this->mConn ) ) { 00125 if ( $this->trxLevel() ) $this->commit(); 00126 $this->mConn = null; 00127 } 00128 return true; 00129 } 00130 00137 public static function generateFileName( $dir, $dbName ) { 00138 return "$dir/$dbName.sqlite"; 00139 } 00140 00145 function checkForEnabledSearch() { 00146 if ( self::$fulltextEnabled === null ) { 00147 self::$fulltextEnabled = false; 00148 $table = $this->tableName( 'searchindex' ); 00149 $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ ); 00150 if ( $res ) { 00151 $row = $res->fetchRow(); 00152 self::$fulltextEnabled = stristr($row['sql'], 'fts' ) !== false; 00153 } 00154 } 00155 return self::$fulltextEnabled; 00156 } 00157 00162 static function getFulltextSearchModule() { 00163 static $cachedResult = null; 00164 if ( $cachedResult !== null ) { 00165 return $cachedResult; 00166 } 00167 $cachedResult = false; 00168 $table = 'dummy_search_test'; 00169 00170 $db = new DatabaseSqliteStandalone( ':memory:' ); 00171 00172 if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) { 00173 $cachedResult = 'FTS3'; 00174 } 00175 $db->close(); 00176 return $cachedResult; 00177 } 00178 00189 function attachDatabase( $name, $file = false, $fname = 'DatabaseSqlite::attachDatabase' ) { 00190 global $wgSQLiteDataDir; 00191 if ( !$file ) { 00192 $file = self::generateFileName( $wgSQLiteDataDir, $name ); 00193 } 00194 $file = $this->addQuotes( $file ); 00195 return $this->query( "ATTACH DATABASE $file AS $name", $fname ); 00196 } 00197 00205 function isWriteQuery( $sql ) { 00206 return parent::isWriteQuery( $sql ) && !preg_match( '/^ATTACH\b/i', $sql ); 00207 } 00208 00216 protected function doQuery( $sql ) { 00217 $res = $this->mConn->query( $sql ); 00218 if ( $res === false ) { 00219 return false; 00220 } else { 00221 $r = $res instanceof ResultWrapper ? $res->result : $res; 00222 $this->mAffectedRows = $r->rowCount(); 00223 $res = new ResultWrapper( $this, $r->fetchAll() ); 00224 } 00225 return $res; 00226 } 00227 00231 function freeResult( $res ) { 00232 if ( $res instanceof ResultWrapper ) { 00233 $res->result = null; 00234 } else { 00235 $res = null; 00236 } 00237 } 00238 00243 function fetchObject( $res ) { 00244 if ( $res instanceof ResultWrapper ) { 00245 $r =& $res->result; 00246 } else { 00247 $r =& $res; 00248 } 00249 00250 $cur = current( $r ); 00251 if ( is_array( $cur ) ) { 00252 next( $r ); 00253 $obj = new stdClass; 00254 foreach ( $cur as $k => $v ) { 00255 if ( !is_numeric( $k ) ) { 00256 $obj->$k = $v; 00257 } 00258 } 00259 00260 return $obj; 00261 } 00262 return false; 00263 } 00264 00269 function fetchRow( $res ) { 00270 if ( $res instanceof ResultWrapper ) { 00271 $r =& $res->result; 00272 } else { 00273 $r =& $res; 00274 } 00275 $cur = current( $r ); 00276 if ( is_array( $cur ) ) { 00277 next( $r ); 00278 return $cur; 00279 } 00280 return false; 00281 } 00282 00290 function numRows( $res ) { 00291 $r = $res instanceof ResultWrapper ? $res->result : $res; 00292 return count( $r ); 00293 } 00294 00299 function numFields( $res ) { 00300 $r = $res instanceof ResultWrapper ? $res->result : $res; 00301 return is_array( $r ) ? count( $r[0] ) : 0; 00302 } 00303 00309 function fieldName( $res, $n ) { 00310 $r = $res instanceof ResultWrapper ? $res->result : $res; 00311 if ( is_array( $r ) ) { 00312 $keys = array_keys( $r[0] ); 00313 return $keys[$n]; 00314 } 00315 return false; 00316 } 00317 00325 function tableName( $name, $format = 'quoted' ) { 00326 // table names starting with sqlite_ are reserved 00327 if ( strpos( $name, 'sqlite_' ) === 0 ) { 00328 return $name; 00329 } 00330 return str_replace( '"', '', parent::tableName( $name, $format ) ); 00331 } 00332 00340 function indexName( $index ) { 00341 return $index; 00342 } 00343 00349 function insertId() { 00350 return $this->mConn->lastInsertId(); 00351 } 00352 00357 function dataSeek( $res, $row ) { 00358 if ( $res instanceof ResultWrapper ) { 00359 $r =& $res->result; 00360 } else { 00361 $r =& $res; 00362 } 00363 reset( $r ); 00364 if ( $row > 0 ) { 00365 for ( $i = 0; $i < $row; $i++ ) { 00366 next( $r ); 00367 } 00368 } 00369 } 00370 00374 function lastError() { 00375 if ( !is_object( $this->mConn ) ) { 00376 return "Cannot return last error, no db connection"; 00377 } 00378 $e = $this->mConn->errorInfo(); 00379 return isset( $e[2] ) ? $e[2] : ''; 00380 } 00381 00385 function lastErrno() { 00386 if ( !is_object( $this->mConn ) ) { 00387 return "Cannot return last error, no db connection"; 00388 } else { 00389 $info = $this->mConn->errorInfo(); 00390 return $info[1]; 00391 } 00392 } 00393 00397 function affectedRows() { 00398 return $this->mAffectedRows; 00399 } 00400 00408 function indexInfo( $table, $index, $fname = 'DatabaseSqlite::indexExists' ) { 00409 $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')'; 00410 $res = $this->query( $sql, $fname ); 00411 if ( !$res ) { 00412 return null; 00413 } 00414 if ( $res->numRows() == 0 ) { 00415 return false; 00416 } 00417 $info = array(); 00418 foreach ( $res as $row ) { 00419 $info[] = $row->name; 00420 } 00421 return $info; 00422 } 00423 00430 function indexUnique( $table, $index, $fname = 'DatabaseSqlite::indexUnique' ) { 00431 $row = $this->selectRow( 'sqlite_master', '*', 00432 array( 00433 'type' => 'index', 00434 'name' => $this->indexName( $index ), 00435 ), $fname ); 00436 if ( !$row || !isset( $row->sql ) ) { 00437 return null; 00438 } 00439 00440 // $row->sql will be of the form CREATE [UNIQUE] INDEX ... 00441 $indexPos = strpos( $row->sql, 'INDEX' ); 00442 if ( $indexPos === false ) { 00443 return null; 00444 } 00445 $firstPart = substr( $row->sql, 0, $indexPos ); 00446 $options = explode( ' ', $firstPart ); 00447 return in_array( 'UNIQUE', $options ); 00448 } 00449 00457 function makeSelectOptions( $options ) { 00458 foreach ( $options as $k => $v ) { 00459 if ( is_numeric( $k ) && $v == 'FOR UPDATE' ) { 00460 $options[$k] = ''; 00461 } 00462 } 00463 return parent::makeSelectOptions( $options ); 00464 } 00465 00470 function makeUpdateOptions( $options ) { 00471 $options = self::fixIgnore( $options ); 00472 return parent::makeUpdateOptions( $options ); 00473 } 00474 00479 static function fixIgnore( $options ) { 00480 # SQLite uses OR IGNORE not just IGNORE 00481 foreach ( $options as $k => $v ) { 00482 if ( $v == 'IGNORE' ) { 00483 $options[$k] = 'OR IGNORE'; 00484 } 00485 } 00486 return $options; 00487 } 00488 00493 function makeInsertOptions( $options ) { 00494 $options = self::fixIgnore( $options ); 00495 return parent::makeInsertOptions( $options ); 00496 } 00497 00501 function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) { 00502 if ( !count( $a ) ) { 00503 return true; 00504 } 00505 00506 # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts 00507 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 00508 $ret = true; 00509 foreach ( $a as $v ) { 00510 if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) { 00511 $ret = false; 00512 } 00513 } 00514 } else { 00515 $ret = parent::insert( $table, $a, "$fname/single-row", $options ); 00516 } 00517 00518 return $ret; 00519 } 00520 00528 function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseSqlite::replace' ) { 00529 if ( !count( $rows ) ) return true; 00530 00531 # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries 00532 if ( isset( $rows[0] ) && is_array( $rows[0] ) ) { 00533 $ret = true; 00534 foreach ( $rows as $v ) { 00535 if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) { 00536 $ret = false; 00537 } 00538 } 00539 } else { 00540 $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" ); 00541 } 00542 00543 return $ret; 00544 } 00545 00552 function textFieldSize( $table, $field ) { 00553 return -1; 00554 } 00555 00559 function unionSupportsOrderAndLimit() { 00560 return false; 00561 } 00562 00568 function unionQueries( $sqls, $all ) { 00569 $glue = $all ? ' UNION ALL ' : ' UNION '; 00570 return implode( $glue, $sqls ); 00571 } 00572 00576 function wasDeadlock() { 00577 return $this->lastErrno() == 5; // SQLITE_BUSY 00578 } 00579 00583 function wasErrorReissuable() { 00584 return $this->lastErrno() == 17; // SQLITE_SCHEMA; 00585 } 00586 00590 function wasReadOnlyError() { 00591 return $this->lastErrno() == 8; // SQLITE_READONLY; 00592 } 00593 00597 public static function getSoftwareLink() { 00598 return "[http://sqlite.org/ SQLite]"; 00599 } 00600 00604 function getServerVersion() { 00605 $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION ); 00606 return $ver; 00607 } 00608 00612 public function getServerInfo() { 00613 return wfMsg( self::getFulltextSearchModule() ? 'sqlite-has-fts' : 'sqlite-no-fts', $this->getServerVersion() ); 00614 } 00615 00622 function fieldInfo( $table, $field ) { 00623 $tableName = $this->tableName( $table ); 00624 $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')'; 00625 $res = $this->query( $sql, __METHOD__ ); 00626 foreach ( $res as $row ) { 00627 if ( $row->name == $field ) { 00628 return new SQLiteField( $row, $tableName ); 00629 } 00630 } 00631 return false; 00632 } 00633 00634 function begin( $fname = '' ) { 00635 if ( $this->mTrxLevel == 1 ) { 00636 $this->commit(); 00637 } 00638 $this->mConn->beginTransaction(); 00639 $this->mTrxLevel = 1; 00640 } 00641 00642 function commit( $fname = '' ) { 00643 if ( $this->mTrxLevel == 0 ) { 00644 return; 00645 } 00646 $this->mConn->commit(); 00647 $this->mTrxLevel = 0; 00648 } 00649 00650 function rollback( $fname = '' ) { 00651 if ( $this->mTrxLevel == 0 ) { 00652 return; 00653 } 00654 $this->mConn->rollBack(); 00655 $this->mTrxLevel = 0; 00656 } 00657 00663 function limitResultForUpdate( $sql, $num ) { 00664 return $this->limitResult( $sql, $num ); 00665 } 00666 00671 function strencode( $s ) { 00672 return substr( $this->addQuotes( $s ), 1, - 1 ); 00673 } 00674 00679 function encodeBlob( $b ) { 00680 return new Blob( $b ); 00681 } 00682 00687 function decodeBlob( $b ) { 00688 if ( $b instanceof Blob ) { 00689 $b = $b->fetch(); 00690 } 00691 return $b; 00692 } 00693 00698 function addQuotes( $s ) { 00699 if ( $s instanceof Blob ) { 00700 return "x'" . bin2hex( $s->fetch() ) . "'"; 00701 } else { 00702 return $this->mConn->quote( $s ); 00703 } 00704 } 00705 00709 function buildLike() { 00710 $params = func_get_args(); 00711 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 00712 $params = $params[0]; 00713 } 00714 return parent::buildLike( $params ) . "ESCAPE '\' "; 00715 } 00716 00720 public function getSearchEngine() { 00721 return "SearchSqlite"; 00722 } 00723 00727 public function deadlockLoop( /*...*/ ) { 00728 $args = func_get_args(); 00729 $function = array_shift( $args ); 00730 return call_user_func_array( $function, $args ); 00731 } 00732 00737 protected function replaceVars( $s ) { 00738 $s = parent::replaceVars( $s ); 00739 if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) { 00740 // CREATE TABLE hacks to allow schema file sharing with MySQL 00741 00742 // binary/varbinary column type -> blob 00743 $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s ); 00744 // no such thing as unsigned 00745 $s = preg_replace( '/\b(un)?signed\b/i', '', $s ); 00746 // INT -> INTEGER 00747 $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s ); 00748 // floating point types -> REAL 00749 $s = preg_replace( '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i', 'REAL', $s ); 00750 // varchar -> TEXT 00751 $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s ); 00752 // TEXT normalization 00753 $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s ); 00754 // BLOB normalization 00755 $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s ); 00756 // BOOL -> INTEGER 00757 $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s ); 00758 // DATETIME -> TEXT 00759 $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s ); 00760 // No ENUM type 00761 $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s ); 00762 // binary collation type -> nothing 00763 $s = preg_replace( '/\bbinary\b/i', '', $s ); 00764 // auto_increment -> autoincrement 00765 $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s ); 00766 // No explicit options 00767 $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s ); 00768 // AUTOINCREMENT should immedidately follow PRIMARY KEY 00769 $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s ); 00770 } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) { 00771 // No truncated indexes 00772 $s = preg_replace( '/\(\d+\)/', '', $s ); 00773 // No FULLTEXT 00774 $s = preg_replace( '/\bfulltext\b/i', '', $s ); 00775 } 00776 return $s; 00777 } 00778 00786 function buildConcat( $stringList ) { 00787 return '(' . implode( ') || (', $stringList ) . ')'; 00788 } 00789 00798 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseSqlite::duplicateTableStructure' ) { 00799 $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" . $this->addQuotes( $oldName ) . " AND type='table'", $fname ); 00800 $obj = $this->fetchObject( $res ); 00801 if ( !$obj ) { 00802 throw new MWException( "Couldn't retrieve structure for table $oldName" ); 00803 } 00804 $sql = $obj->sql; 00805 $sql = preg_replace( '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/', $this->addIdentifierQuotes( $newName ), $sql, 1 ); 00806 if ( $temporary ) { 00807 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) { 00808 wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" ); 00809 } else { 00810 $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql ); 00811 } 00812 } 00813 return $this->query( $sql, $fname ); 00814 } 00815 00816 00825 function listTables( $prefix = null, $fname = 'DatabaseSqlite::listTables' ) { 00826 $result = $this->select( 00827 'sqlite_master', 00828 'name', 00829 "type='table'" 00830 ); 00831 00832 $endArray = array(); 00833 00834 foreach( $result as $table ) { 00835 $vars = get_object_vars($table); 00836 $table = array_pop( $vars ); 00837 00838 if( !$prefix || strpos( $table, $prefix ) === 0 ) { 00839 if ( strpos( $table, 'sqlite_' ) !== 0 ) { 00840 $endArray[] = $table; 00841 } 00842 00843 } 00844 } 00845 00846 return $endArray; 00847 } 00848 00849 } // end DatabaseSqlite class 00850 00855 class DatabaseSqliteStandalone extends DatabaseSqlite { 00856 public function __construct( $fileName, $flags = 0 ) { 00857 $this->mFlags = $flags; 00858 $this->tablePrefix( null ); 00859 $this->openFile( $fileName ); 00860 } 00861 } 00862 00866 class SQLiteField implements Field { 00867 private $info, $tableName; 00868 function __construct( $info, $tableName ) { 00869 $this->info = $info; 00870 $this->tableName = $tableName; 00871 } 00872 00873 function name() { 00874 return $this->info->name; 00875 } 00876 00877 function tableName() { 00878 return $this->tableName; 00879 } 00880 00881 function defaultValue() { 00882 if ( is_string( $this->info->dflt_value ) ) { 00883 // Typically quoted 00884 if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) { 00885 return str_replace( "''", "'", $this->info->dflt_value ); 00886 } 00887 } 00888 return $this->info->dflt_value; 00889 } 00890 00894 function isNullable() { 00895 return !$this->info->notnull; 00896 } 00897 00898 function type() { 00899 return $this->info->type; 00900 } 00901 00902 } // end SQLiteField