[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * This is the SQLite database abstraction layer. 4 * See maintenance/sqlite/README for development notes and other specific information 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License along 17 * with this program; if not, write to the Free Software Foundation, Inc., 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 * http://www.gnu.org/copyleft/gpl.html 20 * 21 * @file 22 * @ingroup Database 23 */ 24 25 /** 26 * @ingroup Database 27 */ 28 class DatabaseSqlite extends DatabaseBase { 29 /** @var bool Whether full text is enabled */ 30 private static $fulltextEnabled = null; 31 32 /** @var string File name for SQLite database file */ 33 public $mDatabaseFile; 34 35 /** @var int The number of rows affected as an integer */ 36 protected $mAffectedRows; 37 38 /** @var resource */ 39 protected $mLastResult; 40 41 /** @var PDO */ 42 protected $mConn; 43 44 /** @var FSLockManager (hopefully on the same server as the DB) */ 45 protected $lockMgr; 46 47 function __construct( $p = null ) { 48 global $wgSharedDB, $wgSQLiteDataDir; 49 50 if ( !is_array( $p ) ) { // legacy calling pattern 51 wfDeprecated( __METHOD__ . " method called without parameter array.", "1.22" ); 52 $args = func_get_args(); 53 $p = array( 54 'host' => isset( $args[0] ) ? $args[0] : false, 55 'user' => isset( $args[1] ) ? $args[1] : false, 56 'password' => isset( $args[2] ) ? $args[2] : false, 57 'dbname' => isset( $args[3] ) ? $args[3] : false, 58 'flags' => isset( $args[4] ) ? $args[4] : 0, 59 'tablePrefix' => isset( $args[5] ) ? $args[5] : 'get from global', 60 'schema' => 'get from global', 61 'foreign' => isset( $args[6] ) ? $args[6] : false 62 ); 63 } 64 $this->mDBname = $p['dbname']; 65 parent::__construct( $p ); 66 // parent doesn't open when $user is false, but we can work with $dbName 67 if ( $p['dbname'] && !$this->isOpen() ) { 68 if ( $this->open( $p['host'], $p['user'], $p['password'], $p['dbname'] ) ) { 69 if ( $wgSharedDB ) { 70 $this->attachDatabase( $wgSharedDB ); 71 } 72 } 73 } 74 75 $this->lockMgr = new FSLockManager( array( 'lockDirectory' => "$wgSQLiteDataDir/locks" ) ); 76 } 77 78 /** 79 * @return string 80 */ 81 function getType() { 82 return 'sqlite'; 83 } 84 85 /** 86 * @todo Check if it should be true like parent class 87 * 88 * @return bool 89 */ 90 function implicitGroupby() { 91 return false; 92 } 93 94 /** Open an SQLite database and return a resource handle to it 95 * NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases 96 * 97 * @param string $server 98 * @param string $user 99 * @param string $pass 100 * @param string $dbName 101 * 102 * @throws DBConnectionError 103 * @return PDO 104 */ 105 function open( $server, $user, $pass, $dbName ) { 106 global $wgSQLiteDataDir; 107 108 $this->close(); 109 $fileName = self::generateFileName( $wgSQLiteDataDir, $dbName ); 110 if ( !is_readable( $fileName ) ) { 111 $this->mConn = false; 112 throw new DBConnectionError( $this, "SQLite database not accessible" ); 113 } 114 $this->openFile( $fileName ); 115 116 return $this->mConn; 117 } 118 119 /** 120 * Opens a database file 121 * 122 * @param string $fileName 123 * @throws DBConnectionError 124 * @return PDO|bool SQL connection or false if failed 125 */ 126 function openFile( $fileName ) { 127 $err = false; 128 129 $this->mDatabaseFile = $fileName; 130 try { 131 if ( $this->mFlags & DBO_PERSISTENT ) { 132 $this->mConn = new PDO( "sqlite:$fileName", '', '', 133 array( PDO::ATTR_PERSISTENT => true ) ); 134 } else { 135 $this->mConn = new PDO( "sqlite:$fileName", '', '' ); 136 } 137 } catch ( PDOException $e ) { 138 $err = $e->getMessage(); 139 } 140 141 if ( !$this->mConn ) { 142 wfDebug( "DB connection error: $err\n" ); 143 throw new DBConnectionError( $this, $err ); 144 } 145 146 $this->mOpened = !!$this->mConn; 147 # set error codes only, don't raise exceptions 148 if ( $this->mOpened ) { 149 $this->mConn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); 150 # Enforce LIKE to be case sensitive, just like MySQL 151 $this->query( 'PRAGMA case_sensitive_like = 1' ); 152 153 return $this->mConn; 154 } 155 156 return false; 157 } 158 159 /** 160 * Does not actually close the connection, just destroys the reference for GC to do its work 161 * @return bool 162 */ 163 protected function closeConnection() { 164 $this->mConn = null; 165 166 return true; 167 } 168 169 /** 170 * Generates a database file name. Explicitly public for installer. 171 * @param string $dir Directory where database resides 172 * @param string $dbName Database name 173 * @return string 174 */ 175 public static function generateFileName( $dir, $dbName ) { 176 return "$dir/$dbName.sqlite"; 177 } 178 179 /** 180 * Check if the searchindext table is FTS enabled. 181 * @return bool False if not enabled. 182 */ 183 function checkForEnabledSearch() { 184 if ( self::$fulltextEnabled === null ) { 185 self::$fulltextEnabled = false; 186 $table = $this->tableName( 'searchindex' ); 187 $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ ); 188 if ( $res ) { 189 $row = $res->fetchRow(); 190 self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false; 191 } 192 } 193 194 return self::$fulltextEnabled; 195 } 196 197 /** 198 * Returns version of currently supported SQLite fulltext search module or false if none present. 199 * @return string 200 */ 201 static function getFulltextSearchModule() { 202 static $cachedResult = null; 203 if ( $cachedResult !== null ) { 204 return $cachedResult; 205 } 206 $cachedResult = false; 207 $table = 'dummy_search_test'; 208 209 $db = new DatabaseSqliteStandalone( ':memory:' ); 210 211 if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) { 212 $cachedResult = 'FTS3'; 213 } 214 $db->close(); 215 216 return $cachedResult; 217 } 218 219 /** 220 * Attaches external database to our connection, see http://sqlite.org/lang_attach.html 221 * for details. 222 * 223 * @param string $name Database name to be used in queries like 224 * SELECT foo FROM dbname.table 225 * @param bool|string $file Database file name. If omitted, will be generated 226 * using $name and $wgSQLiteDataDir 227 * @param string $fname Calling function name 228 * @return ResultWrapper 229 */ 230 function attachDatabase( $name, $file = false, $fname = __METHOD__ ) { 231 global $wgSQLiteDataDir; 232 if ( !$file ) { 233 $file = self::generateFileName( $wgSQLiteDataDir, $name ); 234 } 235 $file = $this->addQuotes( $file ); 236 237 return $this->query( "ATTACH DATABASE $file AS $name", $fname ); 238 } 239 240 /** 241 * @see DatabaseBase::isWriteQuery() 242 * 243 * @param string $sql 244 * @return bool 245 */ 246 function isWriteQuery( $sql ) { 247 return parent::isWriteQuery( $sql ) && !preg_match( '/^ATTACH\b/i', $sql ); 248 } 249 250 /** 251 * SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result 252 * 253 * @param string $sql 254 * @return bool|ResultWrapper 255 */ 256 protected function doQuery( $sql ) { 257 $res = $this->mConn->query( $sql ); 258 if ( $res === false ) { 259 return false; 260 } else { 261 $r = $res instanceof ResultWrapper ? $res->result : $res; 262 $this->mAffectedRows = $r->rowCount(); 263 $res = new ResultWrapper( $this, $r->fetchAll() ); 264 } 265 266 return $res; 267 } 268 269 /** 270 * @param ResultWrapper|mixed $res 271 */ 272 function freeResult( $res ) { 273 if ( $res instanceof ResultWrapper ) { 274 $res->result = null; 275 } else { 276 $res = null; 277 } 278 } 279 280 /** 281 * @param ResultWrapper|array $res 282 * @return stdClass|bool 283 */ 284 function fetchObject( $res ) { 285 if ( $res instanceof ResultWrapper ) { 286 $r =& $res->result; 287 } else { 288 $r =& $res; 289 } 290 291 $cur = current( $r ); 292 if ( is_array( $cur ) ) { 293 next( $r ); 294 $obj = new stdClass; 295 foreach ( $cur as $k => $v ) { 296 if ( !is_numeric( $k ) ) { 297 $obj->$k = $v; 298 } 299 } 300 301 return $obj; 302 } 303 304 return false; 305 } 306 307 /** 308 * @param ResultWrapper|mixed $res 309 * @return array|bool 310 */ 311 function fetchRow( $res ) { 312 if ( $res instanceof ResultWrapper ) { 313 $r =& $res->result; 314 } else { 315 $r =& $res; 316 } 317 $cur = current( $r ); 318 if ( is_array( $cur ) ) { 319 next( $r ); 320 321 return $cur; 322 } 323 324 return false; 325 } 326 327 /** 328 * The PDO::Statement class implements the array interface so count() will work 329 * 330 * @param ResultWrapper|array $res 331 * @return int 332 */ 333 function numRows( $res ) { 334 $r = $res instanceof ResultWrapper ? $res->result : $res; 335 336 return count( $r ); 337 } 338 339 /** 340 * @param ResultWrapper $res 341 * @return int 342 */ 343 function numFields( $res ) { 344 $r = $res instanceof ResultWrapper ? $res->result : $res; 345 if ( is_array( $r ) && count( $r ) > 0 ) { 346 // The size of the result array is twice the number of fields. (Bug: 65578) 347 return count( $r[0] ) / 2; 348 } else { 349 // If the result is empty return 0 350 return 0; 351 } 352 } 353 354 /** 355 * @param ResultWrapper $res 356 * @param int $n 357 * @return bool 358 */ 359 function fieldName( $res, $n ) { 360 $r = $res instanceof ResultWrapper ? $res->result : $res; 361 if ( is_array( $r ) ) { 362 $keys = array_keys( $r[0] ); 363 364 return $keys[$n]; 365 } 366 367 return false; 368 } 369 370 /** 371 * Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks 372 * 373 * @param string $name 374 * @param string $format 375 * @return string 376 */ 377 function tableName( $name, $format = 'quoted' ) { 378 // table names starting with sqlite_ are reserved 379 if ( strpos( $name, 'sqlite_' ) === 0 ) { 380 return $name; 381 } 382 383 return str_replace( '"', '', parent::tableName( $name, $format ) ); 384 } 385 386 /** 387 * Index names have DB scope 388 * 389 * @param string $index 390 * @return string 391 */ 392 function indexName( $index ) { 393 return $index; 394 } 395 396 /** 397 * This must be called after nextSequenceVal 398 * 399 * @return int 400 */ 401 function insertId() { 402 // PDO::lastInsertId yields a string :( 403 return intval( $this->mConn->lastInsertId() ); 404 } 405 406 /** 407 * @param ResultWrapper|array $res 408 * @param int $row 409 */ 410 function dataSeek( $res, $row ) { 411 if ( $res instanceof ResultWrapper ) { 412 $r =& $res->result; 413 } else { 414 $r =& $res; 415 } 416 reset( $r ); 417 if ( $row > 0 ) { 418 for ( $i = 0; $i < $row; $i++ ) { 419 next( $r ); 420 } 421 } 422 } 423 424 /** 425 * @return string 426 */ 427 function lastError() { 428 if ( !is_object( $this->mConn ) ) { 429 return "Cannot return last error, no db connection"; 430 } 431 $e = $this->mConn->errorInfo(); 432 433 return isset( $e[2] ) ? $e[2] : ''; 434 } 435 436 /** 437 * @return string 438 */ 439 function lastErrno() { 440 if ( !is_object( $this->mConn ) ) { 441 return "Cannot return last error, no db connection"; 442 } else { 443 $info = $this->mConn->errorInfo(); 444 445 return $info[1]; 446 } 447 } 448 449 /** 450 * @return int 451 */ 452 function affectedRows() { 453 return $this->mAffectedRows; 454 } 455 456 /** 457 * Returns information about an index 458 * Returns false if the index does not exist 459 * - if errors are explicitly ignored, returns NULL on failure 460 * 461 * @param string $table 462 * @param string $index 463 * @param string $fname 464 * @return array 465 */ 466 function indexInfo( $table, $index, $fname = __METHOD__ ) { 467 $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')'; 468 $res = $this->query( $sql, $fname ); 469 if ( !$res ) { 470 return null; 471 } 472 if ( $res->numRows() == 0 ) { 473 return false; 474 } 475 $info = array(); 476 foreach ( $res as $row ) { 477 $info[] = $row->name; 478 } 479 480 return $info; 481 } 482 483 /** 484 * @param string $table 485 * @param string $index 486 * @param string $fname 487 * @return bool|null 488 */ 489 function indexUnique( $table, $index, $fname = __METHOD__ ) { 490 $row = $this->selectRow( 'sqlite_master', '*', 491 array( 492 'type' => 'index', 493 'name' => $this->indexName( $index ), 494 ), $fname ); 495 if ( !$row || !isset( $row->sql ) ) { 496 return null; 497 } 498 499 // $row->sql will be of the form CREATE [UNIQUE] INDEX ... 500 $indexPos = strpos( $row->sql, 'INDEX' ); 501 if ( $indexPos === false ) { 502 return null; 503 } 504 $firstPart = substr( $row->sql, 0, $indexPos ); 505 $options = explode( ' ', $firstPart ); 506 507 return in_array( 'UNIQUE', $options ); 508 } 509 510 /** 511 * Filter the options used in SELECT statements 512 * 513 * @param array $options 514 * @return array 515 */ 516 function makeSelectOptions( $options ) { 517 foreach ( $options as $k => $v ) { 518 if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) { 519 $options[$k] = ''; 520 } 521 } 522 523 return parent::makeSelectOptions( $options ); 524 } 525 526 /** 527 * @param array $options 528 * @return string 529 */ 530 protected function makeUpdateOptionsArray( $options ) { 531 $options = parent::makeUpdateOptionsArray( $options ); 532 $options = self::fixIgnore( $options ); 533 534 return $options; 535 } 536 537 /** 538 * @param array $options 539 * @return array 540 */ 541 static function fixIgnore( $options ) { 542 # SQLite uses OR IGNORE not just IGNORE 543 foreach ( $options as $k => $v ) { 544 if ( $v == 'IGNORE' ) { 545 $options[$k] = 'OR IGNORE'; 546 } 547 } 548 549 return $options; 550 } 551 552 /** 553 * @param array $options 554 * @return string 555 */ 556 function makeInsertOptions( $options ) { 557 $options = self::fixIgnore( $options ); 558 559 return parent::makeInsertOptions( $options ); 560 } 561 562 /** 563 * Based on generic method (parent) with some prior SQLite-sepcific adjustments 564 * @param string $table 565 * @param array $a 566 * @param string $fname 567 * @param array $options 568 * @return bool 569 */ 570 function insert( $table, $a, $fname = __METHOD__, $options = array() ) { 571 if ( !count( $a ) ) { 572 return true; 573 } 574 575 # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts 576 if ( isset( $a[0] ) && is_array( $a[0] ) ) { 577 $ret = true; 578 foreach ( $a as $v ) { 579 if ( !parent::insert( $table, $v, "$fname/multi-row", $options ) ) { 580 $ret = false; 581 } 582 } 583 } else { 584 $ret = parent::insert( $table, $a, "$fname/single-row", $options ); 585 } 586 587 return $ret; 588 } 589 590 /** 591 * @param string $table 592 * @param array $uniqueIndexes Unused 593 * @param string|array $rows 594 * @param string $fname 595 * @return bool|ResultWrapper 596 */ 597 function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) { 598 if ( !count( $rows ) ) { 599 return true; 600 } 601 602 # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries 603 if ( isset( $rows[0] ) && is_array( $rows[0] ) ) { 604 $ret = true; 605 foreach ( $rows as $v ) { 606 if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) { 607 $ret = false; 608 } 609 } 610 } else { 611 $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" ); 612 } 613 614 return $ret; 615 } 616 617 /** 618 * Returns the size of a text field, or -1 for "unlimited" 619 * In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though. 620 * 621 * @param string $table 622 * @param string $field 623 * @return int 624 */ 625 function textFieldSize( $table, $field ) { 626 return -1; 627 } 628 629 /** 630 * @return bool 631 */ 632 function unionSupportsOrderAndLimit() { 633 return false; 634 } 635 636 /** 637 * @param string $sqls 638 * @param bool $all Whether to "UNION ALL" or not 639 * @return string 640 */ 641 function unionQueries( $sqls, $all ) { 642 $glue = $all ? ' UNION ALL ' : ' UNION '; 643 644 return implode( $glue, $sqls ); 645 } 646 647 /** 648 * @return bool 649 */ 650 function wasDeadlock() { 651 return $this->lastErrno() == 5; // SQLITE_BUSY 652 } 653 654 /** 655 * @return bool 656 */ 657 function wasErrorReissuable() { 658 return $this->lastErrno() == 17; // SQLITE_SCHEMA; 659 } 660 661 /** 662 * @return bool 663 */ 664 function wasReadOnlyError() { 665 return $this->lastErrno() == 8; // SQLITE_READONLY; 666 } 667 668 /** 669 * @return string Wikitext of a link to the server software's web site 670 */ 671 public function getSoftwareLink() { 672 return "[{{int:version-db-sqlite-url}} SQLite]"; 673 } 674 675 /** 676 * @return string Version information from the database 677 */ 678 function getServerVersion() { 679 $ver = $this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION ); 680 681 return $ver; 682 } 683 684 /** 685 * @return string User-friendly database information 686 */ 687 public function getServerInfo() { 688 return wfMessage( self::getFulltextSearchModule() 689 ? 'sqlite-has-fts' 690 : 'sqlite-no-fts', $this->getServerVersion() )->text(); 691 } 692 693 /** 694 * Get information about a given field 695 * Returns false if the field does not exist. 696 * 697 * @param string $table 698 * @param string $field 699 * @return SQLiteField|bool False on failure 700 */ 701 function fieldInfo( $table, $field ) { 702 $tableName = $this->tableName( $table ); 703 $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')'; 704 $res = $this->query( $sql, __METHOD__ ); 705 foreach ( $res as $row ) { 706 if ( $row->name == $field ) { 707 return new SQLiteField( $row, $tableName ); 708 } 709 } 710 711 return false; 712 } 713 714 protected function doBegin( $fname = '' ) { 715 if ( $this->mTrxLevel == 1 ) { 716 $this->commit( __METHOD__ ); 717 } 718 try { 719 $this->mConn->beginTransaction(); 720 } catch ( PDOException $e ) { 721 throw new DBUnexpectedError( $this, 'Error in BEGIN query: ' . $e->getMessage() ); 722 } 723 $this->mTrxLevel = 1; 724 } 725 726 protected function doCommit( $fname = '' ) { 727 if ( $this->mTrxLevel == 0 ) { 728 return; 729 } 730 try { 731 $this->mConn->commit(); 732 } catch ( PDOException $e ) { 733 throw new DBUnexpectedError( $this, 'Error in COMMIT query: ' . $e->getMessage() ); 734 } 735 $this->mTrxLevel = 0; 736 } 737 738 protected function doRollback( $fname = '' ) { 739 if ( $this->mTrxLevel == 0 ) { 740 return; 741 } 742 $this->mConn->rollBack(); 743 $this->mTrxLevel = 0; 744 } 745 746 /** 747 * @param string $s 748 * @return string 749 */ 750 function strencode( $s ) { 751 return substr( $this->addQuotes( $s ), 1, -1 ); 752 } 753 754 /** 755 * @param string $b 756 * @return Blob 757 */ 758 function encodeBlob( $b ) { 759 return new Blob( $b ); 760 } 761 762 /** 763 * @param Blob|string $b 764 * @return string 765 */ 766 function decodeBlob( $b ) { 767 if ( $b instanceof Blob ) { 768 $b = $b->fetch(); 769 } 770 771 return $b; 772 } 773 774 /** 775 * @param Blob|string $s 776 * @return string 777 */ 778 function addQuotes( $s ) { 779 if ( $s instanceof Blob ) { 780 return "x'" . bin2hex( $s->fetch() ) . "'"; 781 } elseif ( is_bool( $s ) ) { 782 return (int)$s; 783 } elseif ( strpos( $s, "\0" ) !== false ) { 784 // SQLite doesn't support \0 in strings, so use the hex representation as a workaround. 785 // This is a known limitation of SQLite's mprintf function which PDO should work around, 786 // but doesn't. I have reported this to php.net as bug #63419: 787 // https://bugs.php.net/bug.php?id=63419 788 // There was already a similar report for SQLite3::escapeString, bug #62361: 789 // https://bugs.php.net/bug.php?id=62361 790 return "x'" . bin2hex( $s ) . "'"; 791 } else { 792 return $this->mConn->quote( $s ); 793 } 794 } 795 796 /** 797 * @return string 798 */ 799 function buildLike() { 800 $params = func_get_args(); 801 if ( count( $params ) > 0 && is_array( $params[0] ) ) { 802 $params = $params[0]; 803 } 804 805 return parent::buildLike( $params ) . "ESCAPE '\' "; 806 } 807 808 /** 809 * @return string 810 */ 811 public function getSearchEngine() { 812 return "SearchSqlite"; 813 } 814 815 /** 816 * No-op version of deadlockLoop 817 * 818 * @return mixed 819 */ 820 public function deadlockLoop( /*...*/ ) { 821 $args = func_get_args(); 822 $function = array_shift( $args ); 823 824 return call_user_func_array( $function, $args ); 825 } 826 827 /** 828 * @param string $s 829 * @return string 830 */ 831 protected function replaceVars( $s ) { 832 $s = parent::replaceVars( $s ); 833 if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) { 834 // CREATE TABLE hacks to allow schema file sharing with MySQL 835 836 // binary/varbinary column type -> blob 837 $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s ); 838 // no such thing as unsigned 839 $s = preg_replace( '/\b(un)?signed\b/i', '', $s ); 840 // INT -> INTEGER 841 $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s ); 842 // floating point types -> REAL 843 $s = preg_replace( 844 '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i', 845 'REAL', 846 $s 847 ); 848 // varchar -> TEXT 849 $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s ); 850 // TEXT normalization 851 $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s ); 852 // BLOB normalization 853 $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s ); 854 // BOOL -> INTEGER 855 $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s ); 856 // DATETIME -> TEXT 857 $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s ); 858 // No ENUM type 859 $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s ); 860 // binary collation type -> nothing 861 $s = preg_replace( '/\bbinary\b/i', '', $s ); 862 // auto_increment -> autoincrement 863 $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s ); 864 // No explicit options 865 $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s ); 866 // AUTOINCREMENT should immedidately follow PRIMARY KEY 867 $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s ); 868 } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) { 869 // No truncated indexes 870 $s = preg_replace( '/\(\d+\)/', '', $s ); 871 // No FULLTEXT 872 $s = preg_replace( '/\bfulltext\b/i', '', $s ); 873 } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) { 874 // DROP INDEX is database-wide, not table-specific, so no ON <table> clause. 875 $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s ); 876 } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) { 877 // INSERT IGNORE --> INSERT OR IGNORE 878 $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s ); 879 } 880 881 return $s; 882 } 883 884 public function lock( $lockName, $method, $timeout = 5 ) { 885 global $wgSQLiteDataDir; 886 887 if ( !is_dir( "$wgSQLiteDataDir/locks" ) ) { // create dir as needed 888 if ( !is_writable( $wgSQLiteDataDir ) || !mkdir( "$wgSQLiteDataDir/locks" ) ) { 889 throw new DBError( "Cannot create directory \"$wgSQLiteDataDir/locks\"." ); 890 } 891 } 892 893 return $this->lockMgr->lock( array( $lockName ), LockManager::LOCK_EX, $timeout )->isOK(); 894 } 895 896 public function unlock( $lockName, $method ) { 897 return $this->lockMgr->unlock( array( $lockName ), LockManager::LOCK_EX )->isOK(); 898 } 899 900 /** 901 * Build a concatenation list to feed into a SQL query 902 * 903 * @param string[] $stringList 904 * @return string 905 */ 906 function buildConcat( $stringList ) { 907 return '(' . implode( ') || (', $stringList ) . ')'; 908 } 909 910 public function buildGroupConcatField( 911 $delim, $table, $field, $conds = '', $join_conds = array() 912 ) { 913 $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')'; 914 915 return '(' . $this->selectSQLText( $table, $fld, $conds, null, array(), $join_conds ) . ')'; 916 } 917 918 /** 919 * @throws MWException 920 * @param string $oldName 921 * @param string $newName 922 * @param bool $temporary 923 * @param string $fname 924 * @return bool|ResultWrapper 925 */ 926 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) { 927 $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" . 928 $this->addQuotes( $oldName ) . " AND type='table'", $fname ); 929 $obj = $this->fetchObject( $res ); 930 if ( !$obj ) { 931 throw new MWException( "Couldn't retrieve structure for table $oldName" ); 932 } 933 $sql = $obj->sql; 934 $sql = preg_replace( 935 '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/', 936 $this->addIdentifierQuotes( $newName ), 937 $sql, 938 1 939 ); 940 if ( $temporary ) { 941 if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) { 942 wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" ); 943 } else { 944 $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql ); 945 } 946 } 947 948 return $this->query( $sql, $fname ); 949 } 950 951 /** 952 * List all tables on the database 953 * 954 * @param string $prefix Only show tables with this prefix, e.g. mw_ 955 * @param string $fname Calling function name 956 * 957 * @return array 958 */ 959 function listTables( $prefix = null, $fname = __METHOD__ ) { 960 $result = $this->select( 961 'sqlite_master', 962 'name', 963 "type='table'" 964 ); 965 966 $endArray = array(); 967 968 foreach ( $result as $table ) { 969 $vars = get_object_vars( $table ); 970 $table = array_pop( $vars ); 971 972 if ( !$prefix || strpos( $table, $prefix ) === 0 ) { 973 if ( strpos( $table, 'sqlite_' ) !== 0 ) { 974 $endArray[] = $table; 975 } 976 } 977 } 978 979 return $endArray; 980 } 981 } // end DatabaseSqlite class 982 983 /** 984 * This class allows simple acccess to a SQLite database independently from main database settings 985 * @ingroup Database 986 */ 987 class DatabaseSqliteStandalone extends DatabaseSqlite { 988 public function __construct( $fileName, $flags = 0 ) { 989 $this->mFlags = $flags; 990 $this->tablePrefix( null ); 991 $this->openFile( $fileName ); 992 } 993 } 994 995 /** 996 * @ingroup Database 997 */ 998 class SQLiteField implements Field { 999 private $info, $tableName; 1000 1001 function __construct( $info, $tableName ) { 1002 $this->info = $info; 1003 $this->tableName = $tableName; 1004 } 1005 1006 function name() { 1007 return $this->info->name; 1008 } 1009 1010 function tableName() { 1011 return $this->tableName; 1012 } 1013 1014 function defaultValue() { 1015 if ( is_string( $this->info->dflt_value ) ) { 1016 // Typically quoted 1017 if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) { 1018 return str_replace( "''", "'", $this->info->dflt_value ); 1019 } 1020 } 1021 1022 return $this->info->dflt_value; 1023 } 1024 1025 /** 1026 * @return bool 1027 */ 1028 function isNullable() { 1029 return !$this->info->notnull; 1030 } 1031 1032 function type() { 1033 return $this->info->type; 1034 } 1035 } // end SQLiteField
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |