[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/db/ -> DatabaseSqlite.php (source)

   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


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1