MediaWiki  master
DatabaseSqlite.php
Go to the documentation of this file.
1 <?php
24 namespace Wikimedia\Rdbms;
25 
26 use PDO;
34 
38 class DatabaseSqlite extends Database {
40  private static $fulltextEnabled = null;
41 
43  protected $dbDir;
45  protected $dbPath;
47  protected $trxMode;
48 
52  protected $lastResultHandle;
53 
55  protected $conn;
56 
58  protected $lockMgr;
59 
61  private $alreadyAttached = [];
62 
71  function __construct( array $p ) {
72  if ( isset( $p['dbFilePath'] ) ) {
73  $this->dbPath = $p['dbFilePath'];
74  $lockDomain = md5( $this->dbPath );
75  // Use "X" for things like X.sqlite and ":memory:" for RAM-only DBs
76  if ( !isset( $p['dbname'] ) || !strlen( $p['dbname'] ) ) {
77  $p['dbname'] = preg_replace( '/\.sqlite\d?$/', '', basename( $this->dbPath ) );
78  }
79  } elseif ( isset( $p['dbDirectory'] ) ) {
80  $this->dbDir = $p['dbDirectory'];
81  $lockDomain = $p['dbname'];
82  } else {
83  throw new InvalidArgumentException( "Need 'dbDirectory' or 'dbFilePath' parameter." );
84  }
85 
86  $this->trxMode = isset( $p['trxMode'] ) ? strtoupper( $p['trxMode'] ) : null;
87  if ( $this->trxMode &&
88  !in_array( $this->trxMode, [ 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ] )
89  ) {
90  $this->trxMode = null;
91  $this->queryLogger->warning( "Invalid SQLite transaction mode provided." );
92  }
93 
94  $this->lockMgr = new FSLockManager( [
95  'domain' => $lockDomain,
96  'lockDirectory' => "{$this->dbDir}/locks"
97  ] );
98 
99  parent::__construct( $p );
100  }
101 
102  protected static function getAttributes() {
103  return [ self::ATTR_DB_LEVEL_LOCKING => true ];
104  }
105 
115  public static function newStandaloneInstance( $filename, array $p = [] ) {
116  $p['dbFilePath'] = $filename;
117  $p['schema'] = null;
118  $p['tablePrefix'] = '';
120  $db = Database::factory( 'sqlite', $p );
121 
122  return $db;
123  }
124 
125  protected function doInitConnection() {
126  if ( $this->dbPath !== null ) {
127  // Standalone .sqlite file mode.
128  $this->openFile(
129  $this->dbPath,
130  $this->connectionParams['dbname'],
131  $this->connectionParams['tablePrefix']
132  );
133  } elseif ( $this->dbDir !== null ) {
134  // Stock wiki mode using standard file names per DB
135  if ( strlen( $this->connectionParams['dbname'] ) ) {
136  $this->open(
137  $this->connectionParams['host'],
138  $this->connectionParams['user'],
139  $this->connectionParams['password'],
140  $this->connectionParams['dbname'],
141  $this->connectionParams['schema'],
142  $this->connectionParams['tablePrefix']
143  );
144  } else {
145  // Caller will manually call open() later?
146  $this->connLogger->debug( __METHOD__ . ': no database opened.' );
147  }
148  } else {
149  throw new InvalidArgumentException( "Need 'dbDirectory' or 'dbFilePath' parameter." );
150  }
151  }
152 
156  function getType() {
157  return 'sqlite';
158  }
159 
165  function implicitGroupby() {
166  return false;
167  }
168 
169  protected function open( $server, $user, $pass, $dbName, $schema, $tablePrefix ) {
170  $this->close();
171  $fileName = self::generateFileName( $this->dbDir, $dbName );
172  if ( !is_readable( $fileName ) ) {
173  $this->conn = false;
174  throw new DBConnectionError( $this, "SQLite database not accessible" );
175  }
176  // Only $dbName is used, the other parameters are irrelevant for SQLite databases
177  $this->openFile( $fileName, $dbName, $tablePrefix );
178 
179  return (bool)$this->conn;
180  }
181 
191  protected function openFile( $fileName, $dbName, $tablePrefix ) {
192  $err = false;
193 
194  $this->dbPath = $fileName;
195  try {
196  if ( $this->flags & self::DBO_PERSISTENT ) {
197  $this->conn = new PDO( "sqlite:$fileName", '', '',
198  [ PDO::ATTR_PERSISTENT => true ] );
199  } else {
200  $this->conn = new PDO( "sqlite:$fileName", '', '' );
201  }
202  } catch ( PDOException $e ) {
203  $err = $e->getMessage();
204  }
205 
206  if ( !$this->conn ) {
207  $this->queryLogger->debug( "DB connection error: $err\n" );
208  throw new DBConnectionError( $this, $err );
209  }
210 
211  $this->opened = is_object( $this->conn );
212  if ( $this->opened ) {
213  $this->currentDomain = new DatabaseDomain( $dbName, null, $tablePrefix );
214  # Set error codes only, don't raise exceptions
215  $this->conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
216  # Enforce LIKE to be case sensitive, just like MySQL
217  $this->query( 'PRAGMA case_sensitive_like = 1' );
218 
219  $sync = $this->sessionVars['synchronous'] ?? null;
220  if ( in_array( $sync, [ 'EXTRA', 'FULL', 'NORMAL' ], true ) ) {
221  $this->query( "PRAGMA synchronous = $sync" );
222  }
223 
224  return $this->conn;
225  }
226 
227  return false;
228  }
229 
234  public function getDbFilePath() {
235  return $this->dbPath;
236  }
237 
242  protected function closeConnection() {
243  $this->conn = null;
244 
245  return true;
246  }
247 
254  public static function generateFileName( $dir, $dbName ) {
255  return "$dir/$dbName.sqlite";
256  }
257 
262  public function checkForEnabledSearch() {
263  if ( self::$fulltextEnabled === null ) {
264  self::$fulltextEnabled = false;
265  $table = $this->tableName( 'searchindex' );
266  $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name = '$table'", __METHOD__ );
267  if ( $res ) {
268  $row = $res->fetchRow();
269  self::$fulltextEnabled = stristr( $row['sql'], 'fts' ) !== false;
270  }
271  }
272 
273  return self::$fulltextEnabled;
274  }
275 
280  static function getFulltextSearchModule() {
281  static $cachedResult = null;
282  if ( $cachedResult !== null ) {
283  return $cachedResult;
284  }
285  $cachedResult = false;
286  $table = 'dummy_search_test';
287 
288  $db = self::newStandaloneInstance( ':memory:' );
289  if ( $db->query( "CREATE VIRTUAL TABLE $table USING FTS3(dummy_field)", __METHOD__, true ) ) {
290  $cachedResult = 'FTS3';
291  }
292  $db->close();
293 
294  return $cachedResult;
295  }
296 
308  function attachDatabase( $name, $file = false, $fname = __METHOD__ ) {
309  if ( !$file ) {
310  $file = self::generateFileName( $this->dbDir, $name );
311  }
312  $file = $this->addQuotes( $file );
313 
314  return $this->query( "ATTACH DATABASE $file AS $name", $fname );
315  }
316 
317  protected function isWriteQuery( $sql ) {
318  return parent::isWriteQuery( $sql ) && !preg_match( '/^(ATTACH|PRAGMA)\b/i', $sql );
319  }
320 
321  protected function isTransactableQuery( $sql ) {
322  return parent::isTransactableQuery( $sql ) && !in_array(
323  $this->getQueryVerb( $sql ),
324  [ 'ATTACH', 'PRAGMA' ],
325  true
326  );
327  }
328 
335  protected function doQuery( $sql ) {
336  $res = $this->getBindingHandle()->query( $sql );
337  if ( $res === false ) {
338  return false;
339  }
340 
341  $r = $res instanceof ResultWrapper ? $res->result : $res;
342  $this->lastAffectedRowCount = $r->rowCount();
343  $res = new ResultWrapper( $this, $r->fetchAll() );
344 
345  return $res;
346  }
347 
351  function freeResult( $res ) {
352  if ( $res instanceof ResultWrapper ) {
353  $res->result = null;
354  } else {
355  $res = null;
356  }
357  }
358 
363  function fetchObject( $res ) {
364  if ( $res instanceof ResultWrapper ) {
365  $r =& $res->result;
366  } else {
367  $r =& $res;
368  }
369 
370  $cur = current( $r );
371  if ( is_array( $cur ) ) {
372  next( $r );
373  $obj = new stdClass;
374  foreach ( $cur as $k => $v ) {
375  if ( !is_numeric( $k ) ) {
376  $obj->$k = $v;
377  }
378  }
379 
380  return $obj;
381  }
382 
383  return false;
384  }
385 
390  function fetchRow( $res ) {
391  if ( $res instanceof ResultWrapper ) {
392  $r =& $res->result;
393  } else {
394  $r =& $res;
395  }
396  $cur = current( $r );
397  if ( is_array( $cur ) ) {
398  next( $r );
399 
400  return $cur;
401  }
402 
403  return false;
404  }
405 
412  function numRows( $res ) {
413  // false does not implement Countable
414  $r = $res instanceof ResultWrapper ? $res->result : $res;
415 
416  return is_array( $r ) ? count( $r ) : 0;
417  }
418 
423  function numFields( $res ) {
424  $r = $res instanceof ResultWrapper ? $res->result : $res;
425  if ( is_array( $r ) && count( $r ) > 0 ) {
426  // The size of the result array is twice the number of fields. (T67578)
427  return count( $r[0] ) / 2;
428  } else {
429  // If the result is empty return 0
430  return 0;
431  }
432  }
433 
439  function fieldName( $res, $n ) {
440  $r = $res instanceof ResultWrapper ? $res->result : $res;
441  if ( is_array( $r ) ) {
442  $keys = array_keys( $r[0] );
443 
444  return $keys[$n];
445  }
446 
447  return false;
448  }
449 
457  function tableName( $name, $format = 'quoted' ) {
458  // table names starting with sqlite_ are reserved
459  if ( strpos( $name, 'sqlite_' ) === 0 ) {
460  return $name;
461  }
462 
463  return str_replace( '"', '', parent::tableName( $name, $format ) );
464  }
465 
471  function insertId() {
472  // PDO::lastInsertId yields a string :(
473  return intval( $this->getBindingHandle()->lastInsertId() );
474  }
475 
480  function dataSeek( $res, $row ) {
481  if ( $res instanceof ResultWrapper ) {
482  $r =& $res->result;
483  } else {
484  $r =& $res;
485  }
486  reset( $r );
487  if ( $row > 0 ) {
488  for ( $i = 0; $i < $row; $i++ ) {
489  next( $r );
490  }
491  }
492  }
493 
497  function lastError() {
498  if ( !is_object( $this->conn ) ) {
499  return "Cannot return last error, no db connection";
500  }
501  $e = $this->conn->errorInfo();
502 
503  return $e[2] ?? '';
504  }
505 
509  function lastErrno() {
510  if ( !is_object( $this->conn ) ) {
511  return "Cannot return last error, no db connection";
512  } else {
513  $info = $this->conn->errorInfo();
514 
515  return $info[1];
516  }
517  }
518 
522  protected function fetchAffectedRowCount() {
524  }
525 
526  function tableExists( $table, $fname = __METHOD__ ) {
527  $tableRaw = $this->tableName( $table, 'raw' );
528  if ( isset( $this->sessionTempTables[$tableRaw] ) ) {
529  return true; // already known to exist
530  }
531 
532  $encTable = $this->addQuotes( $tableRaw );
533  $res = $this->query(
534  "SELECT 1 FROM sqlite_master WHERE type='table' AND name=$encTable" );
535 
536  return $res->numRows() ? true : false;
537  }
538 
549  function indexInfo( $table, $index, $fname = __METHOD__ ) {
550  $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
551  $res = $this->query( $sql, $fname );
552  if ( !$res || $res->numRows() == 0 ) {
553  return false;
554  }
555  $info = [];
556  foreach ( $res as $row ) {
557  $info[] = $row->name;
558  }
559 
560  return $info;
561  }
562 
569  function indexUnique( $table, $index, $fname = __METHOD__ ) {
570  $row = $this->selectRow( 'sqlite_master', '*',
571  [
572  'type' => 'index',
573  'name' => $this->indexName( $index ),
574  ], $fname );
575  if ( !$row || !isset( $row->sql ) ) {
576  return null;
577  }
578 
579  // $row->sql will be of the form CREATE [UNIQUE] INDEX ...
580  $indexPos = strpos( $row->sql, 'INDEX' );
581  if ( $indexPos === false ) {
582  return null;
583  }
584  $firstPart = substr( $row->sql, 0, $indexPos );
585  $options = explode( ' ', $firstPart );
586 
587  return in_array( 'UNIQUE', $options );
588  }
589 
597  foreach ( $options as $k => $v ) {
598  if ( is_numeric( $k ) && ( $v == 'FOR UPDATE' || $v == 'LOCK IN SHARE MODE' ) ) {
599  $options[$k] = '';
600  }
601  }
602 
603  return parent::makeSelectOptions( $options );
604  }
605 
610  protected function makeUpdateOptionsArray( $options ) {
611  $options = parent::makeUpdateOptionsArray( $options );
612  $options = self::fixIgnore( $options );
613 
614  return $options;
615  }
616 
621  static function fixIgnore( $options ) {
622  # SQLite uses OR IGNORE not just IGNORE
623  foreach ( $options as $k => $v ) {
624  if ( $v == 'IGNORE' ) {
625  $options[$k] = 'OR IGNORE';
626  }
627  }
628 
629  return $options;
630  }
631 
637  $options = self::fixIgnore( $options );
638 
639  return parent::makeInsertOptions( $options );
640  }
641 
650  function insert( $table, $a, $fname = __METHOD__, $options = [] ) {
651  if ( !count( $a ) ) {
652  return true;
653  }
654 
655  # SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
656  if ( isset( $a[0] ) && is_array( $a[0] ) ) {
657  $affectedRowCount = 0;
658  try {
659  $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
660  foreach ( $a as $v ) {
661  parent::insert( $table, $v, "$fname/multi-row", $options );
662  $affectedRowCount += $this->affectedRows();
663  }
664  $this->endAtomic( $fname );
665  } catch ( Exception $e ) {
666  $this->cancelAtomic( $fname );
667  throw $e;
668  }
669  $this->affectedRowCount = $affectedRowCount;
670  } else {
671  parent::insert( $table, $a, "$fname/single-row", $options );
672  }
673 
674  return true;
675  }
676 
683  function replace( $table, $uniqueIndexes, $rows, $fname = __METHOD__ ) {
684  if ( !count( $rows ) ) {
685  return;
686  }
687 
688  # SQLite can't handle multi-row replaces, so divide up into multiple single-row queries
689  if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
690  $affectedRowCount = 0;
691  try {
692  $this->startAtomic( $fname, self::ATOMIC_CANCELABLE );
693  foreach ( $rows as $v ) {
694  $this->nativeReplace( $table, $v, "$fname/multi-row" );
695  $affectedRowCount += $this->affectedRows();
696  }
697  $this->endAtomic( $fname );
698  } catch ( Exception $e ) {
699  $this->cancelAtomic( $fname );
700  throw $e;
701  }
702  $this->affectedRowCount = $affectedRowCount;
703  } else {
704  $this->nativeReplace( $table, $rows, "$fname/single-row" );
705  }
706  }
707 
716  function textFieldSize( $table, $field ) {
717  return -1;
718  }
719 
724  return false;
725  }
726 
732  function unionQueries( $sqls, $all ) {
733  $glue = $all ? ' UNION ALL ' : ' UNION ';
734 
735  return implode( $glue, $sqls );
736  }
737 
741  function wasDeadlock() {
742  return $this->lastErrno() == 5; // SQLITE_BUSY
743  }
744 
748  function wasReadOnlyError() {
749  return $this->lastErrno() == 8; // SQLITE_READONLY;
750  }
751 
752  public function wasConnectionError( $errno ) {
753  return $errno == 17; // SQLITE_SCHEMA;
754  }
755 
756  protected function wasKnownStatementRollbackError() {
757  // ON CONFLICT ROLLBACK clauses make it so that SQLITE_CONSTRAINT error is
758  // ambiguous with regard to whether it implies a ROLLBACK or an ABORT happened.
759  // https://sqlite.org/lang_createtable.html#uniqueconst
760  // https://sqlite.org/lang_conflict.html
761  return false;
762  }
763 
767  public function getSoftwareLink() {
768  return "[{{int:version-db-sqlite-url}} SQLite]";
769  }
770 
774  function getServerVersion() {
775  $ver = $this->getBindingHandle()->getAttribute( PDO::ATTR_SERVER_VERSION );
776 
777  return $ver;
778  }
779 
788  function fieldInfo( $table, $field ) {
789  $tableName = $this->tableName( $table );
790  $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')';
791  $res = $this->query( $sql, __METHOD__ );
792  foreach ( $res as $row ) {
793  if ( $row->name == $field ) {
794  return new SQLiteField( $row, $tableName );
795  }
796  }
797 
798  return false;
799  }
800 
801  protected function doBegin( $fname = '' ) {
802  if ( $this->trxMode ) {
803  $this->query( "BEGIN {$this->trxMode}", $fname );
804  } else {
805  $this->query( 'BEGIN', $fname );
806  }
807  $this->trxLevel = 1;
808  }
809 
814  function strencode( $s ) {
815  return substr( $this->addQuotes( $s ), 1, -1 );
816  }
817 
822  function encodeBlob( $b ) {
823  return new Blob( $b );
824  }
825 
830  function decodeBlob( $b ) {
831  if ( $b instanceof Blob ) {
832  $b = $b->fetch();
833  }
834 
835  return $b;
836  }
837 
842  function addQuotes( $s ) {
843  if ( $s instanceof Blob ) {
844  return "x'" . bin2hex( $s->fetch() ) . "'";
845  } elseif ( is_bool( $s ) ) {
846  return (int)$s;
847  } elseif ( strpos( (string)$s, "\0" ) !== false ) {
848  // SQLite doesn't support \0 in strings, so use the hex representation as a workaround.
849  // This is a known limitation of SQLite's mprintf function which PDO
850  // should work around, but doesn't. I have reported this to php.net as bug #63419:
851  // https://bugs.php.net/bug.php?id=63419
852  // There was already a similar report for SQLite3::escapeString, bug #62361:
853  // https://bugs.php.net/bug.php?id=62361
854  // There is an additional bug regarding sorting this data after insert
855  // on older versions of sqlite shipped with ubuntu 12.04
856  // https://phabricator.wikimedia.org/T74367
857  $this->queryLogger->debug(
858  __FUNCTION__ .
859  ': Quoting value containing null byte. ' .
860  'For consistency all binary data should have been ' .
861  'first processed with self::encodeBlob()'
862  );
863  return "x'" . bin2hex( (string)$s ) . "'";
864  } else {
865  return $this->getBindingHandle()->quote( (string)$s );
866  }
867  }
868 
869  public function buildSubstring( $input, $startPosition, $length = null ) {
870  $this->assertBuildSubstringParams( $startPosition, $length );
871  $params = [ $input, $startPosition ];
872  if ( $length !== null ) {
873  $params[] = $length;
874  }
875  return 'SUBSTR(' . implode( ',', $params ) . ')';
876  }
877 
883  public function buildStringCast( $field ) {
884  return 'CAST ( ' . $field . ' AS TEXT )';
885  }
886 
892  public function deadlockLoop( /*...*/ ) {
893  $args = func_get_args();
894  $function = array_shift( $args );
895 
896  return $function( ...$args );
897  }
898 
903  protected function replaceVars( $s ) {
904  $s = parent::replaceVars( $s );
905  if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
906  // CREATE TABLE hacks to allow schema file sharing with MySQL
907 
908  // binary/varbinary column type -> blob
909  $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'BLOB', $s );
910  // no such thing as unsigned
911  $s = preg_replace( '/\b(un)?signed\b/i', '', $s );
912  // INT -> INTEGER
913  $s = preg_replace( '/\b(tiny|small|medium|big|)int(\s*\(\s*\d+\s*\)|\b)/i', 'INTEGER', $s );
914  // floating point types -> REAL
915  $s = preg_replace(
916  '/\b(float|double(\s+precision)?)(\s*\(\s*\d+\s*(,\s*\d+\s*)?\)|\b)/i',
917  'REAL',
918  $s
919  );
920  // varchar -> TEXT
921  $s = preg_replace( '/\b(var)?char\s*\(.*?\)/i', 'TEXT', $s );
922  // TEXT normalization
923  $s = preg_replace( '/\b(tiny|medium|long)text\b/i', 'TEXT', $s );
924  // BLOB normalization
925  $s = preg_replace( '/\b(tiny|small|medium|long|)blob\b/i', 'BLOB', $s );
926  // BOOL -> INTEGER
927  $s = preg_replace( '/\bbool(ean)?\b/i', 'INTEGER', $s );
928  // DATETIME -> TEXT
929  $s = preg_replace( '/\b(datetime|timestamp)\b/i', 'TEXT', $s );
930  // No ENUM type
931  $s = preg_replace( '/\benum\s*\([^)]*\)/i', 'TEXT', $s );
932  // binary collation type -> nothing
933  $s = preg_replace( '/\bbinary\b/i', '', $s );
934  // auto_increment -> autoincrement
935  $s = preg_replace( '/\bauto_increment\b/i', 'AUTOINCREMENT', $s );
936  // No explicit options
937  $s = preg_replace( '/\)[^);]*(;?)\s*$/', ')\1', $s );
938  // AUTOINCREMENT should immedidately follow PRIMARY KEY
939  $s = preg_replace( '/primary key (.*?) autoincrement/i', 'PRIMARY KEY AUTOINCREMENT $1', $s );
940  } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) {
941  // No truncated indexes
942  $s = preg_replace( '/\(\d+\)/', '', $s );
943  // No FULLTEXT
944  $s = preg_replace( '/\bfulltext\b/i', '', $s );
945  } elseif ( preg_match( '/^\s*DROP INDEX/i', $s ) ) {
946  // DROP INDEX is database-wide, not table-specific, so no ON <table> clause.
947  $s = preg_replace( '/\sON\s+[^\s]*/i', '', $s );
948  } elseif ( preg_match( '/^\s*INSERT IGNORE\b/i', $s ) ) {
949  // INSERT IGNORE --> INSERT OR IGNORE
950  $s = preg_replace( '/^\s*INSERT IGNORE\b/i', 'INSERT OR IGNORE', $s );
951  }
952 
953  return $s;
954  }
955 
956  public function lock( $lockName, $method, $timeout = 5 ) {
957  if ( !is_dir( "{$this->dbDir}/locks" ) ) { // create dir as needed
958  if ( !is_writable( $this->dbDir ) || !mkdir( "{$this->dbDir}/locks" ) ) {
959  throw new DBError( $this, "Cannot create directory \"{$this->dbDir}/locks\"." );
960  }
961  }
962 
963  return $this->lockMgr->lock( [ $lockName ], LockManager::LOCK_EX, $timeout )->isOK();
964  }
965 
966  public function unlock( $lockName, $method ) {
967  return $this->lockMgr->unlock( [ $lockName ], LockManager::LOCK_EX )->isOK();
968  }
969 
976  function buildConcat( $stringList ) {
977  return '(' . implode( ') || (', $stringList ) . ')';
978  }
979 
980  public function buildGroupConcatField(
981  $delim, $table, $field, $conds = '', $join_conds = []
982  ) {
983  $fld = "group_concat($field," . $this->addQuotes( $delim ) . ')';
984 
985  return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
986  }
987 
996  function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = __METHOD__ ) {
997  $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" .
998  $this->addQuotes( $oldName ) . " AND type='table'", $fname );
999  $obj = $this->fetchObject( $res );
1000  if ( !$obj ) {
1001  throw new RuntimeException( "Couldn't retrieve structure for table $oldName" );
1002  }
1003  $sql = $obj->sql;
1004  $sql = preg_replace(
1005  '/(?<=\W)"?' .
1006  preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ), '/' ) .
1007  '"?(?=\W)/',
1008  $this->addIdentifierQuotes( $newName ),
1009  $sql,
1010  1
1011  );
1012  if ( $temporary ) {
1013  if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
1014  $this->queryLogger->debug(
1015  "Table $oldName is virtual, can't create a temporary duplicate.\n" );
1016  } else {
1017  $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
1018  }
1019  }
1020 
1021  $res = $this->query( $sql, $fname, self::QUERY_PSEUDO_PERMANENT );
1022 
1023  // Take over indexes
1024  $indexList = $this->query( 'PRAGMA INDEX_LIST(' . $this->addQuotes( $oldName ) . ')' );
1025  foreach ( $indexList as $index ) {
1026  if ( strpos( $index->name, 'sqlite_autoindex' ) === 0 ) {
1027  continue;
1028  }
1029 
1030  if ( $index->unique ) {
1031  $sql = 'CREATE UNIQUE INDEX';
1032  } else {
1033  $sql = 'CREATE INDEX';
1034  }
1035  // Try to come up with a new index name, given indexes have database scope in SQLite
1036  $indexName = $newName . '_' . $index->name;
1037  $sql .= ' ' . $indexName . ' ON ' . $newName;
1038 
1039  $indexInfo = $this->query( 'PRAGMA INDEX_INFO(' . $this->addQuotes( $index->name ) . ')' );
1040  $fields = [];
1041  foreach ( $indexInfo as $indexInfoRow ) {
1042  $fields[$indexInfoRow->seqno] = $indexInfoRow->name;
1043  }
1044 
1045  $sql .= '(' . implode( ',', $fields ) . ')';
1046 
1047  $this->query( $sql );
1048  }
1049 
1050  return $res;
1051  }
1052 
1061  function listTables( $prefix = null, $fname = __METHOD__ ) {
1062  $result = $this->select(
1063  'sqlite_master',
1064  'name',
1065  "type='table'"
1066  );
1067 
1068  $endArray = [];
1069 
1070  foreach ( $result as $table ) {
1071  $vars = get_object_vars( $table );
1072  $table = array_pop( $vars );
1073 
1074  if ( !$prefix || strpos( $table, $prefix ) === 0 ) {
1075  if ( strpos( $table, 'sqlite_' ) !== 0 ) {
1076  $endArray[] = $table;
1077  }
1078  }
1079  }
1080 
1081  return $endArray;
1082  }
1083 
1092  public function dropTable( $tableName, $fName = __METHOD__ ) {
1093  if ( !$this->tableExists( $tableName, $fName ) ) {
1094  return false;
1095  }
1096  $sql = "DROP TABLE " . $this->tableName( $tableName );
1097 
1098  return $this->query( $sql, $fName );
1099  }
1100 
1101  public function setTableAliases( array $aliases ) {
1102  parent::setTableAliases( $aliases );
1103  foreach ( $this->tableAliases as $params ) {
1104  if ( isset( $this->alreadyAttached[$params['dbname']] ) ) {
1105  continue;
1106  }
1107  $this->attachDatabase( $params['dbname'] );
1108  $this->alreadyAttached[$params['dbname']] = true;
1109  }
1110  }
1111 
1112  public function resetSequenceForTable( $table, $fname = __METHOD__ ) {
1113  $encTable = $this->addIdentifierQuotes( 'sqlite_sequence' );
1114  $encName = $this->addQuotes( $this->tableName( $table, 'raw' ) );
1115  $this->query( "DELETE FROM $encTable WHERE name = $encName", $fname );
1116  }
1117 
1118  public function databasesAreIndependent() {
1119  return true;
1120  }
1121 
1125  public function __toString() {
1126  return is_object( $this->conn )
1127  ? 'SQLite ' . (string)$this->conn->getAttribute( PDO::ATTR_SERVER_VERSION )
1128  : '(not connected)';
1129  }
1130 
1134  protected function getBindingHandle() {
1135  return parent::getBindingHandle();
1136  }
1137 }
1138 
1142 class_alias( DatabaseSqlite::class, 'DatabaseSqlite' );
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
static newStandaloneInstance( $filename, array $p=[])
FSLockManager $lockMgr
(hopefully on the same server as the DB)
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2633
tableName( $name, $format='quoted')
Use MySQL&#39;s naming (accounts for prefix etc) but remove surrounding backticks.
affectedRows()
Get the number of rows affected by the last write query.
Definition: Database.php:4128
__construct(array $p)
Additional params include:
if(is_array( $mode)) switch( $mode) $input
resetSequenceForTable( $table, $fname=__METHOD__)
unlock( $lockName, $method)
Release a lock.
replace( $table, $uniqueIndexes, $rows, $fname=__METHOD__)
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Result wrapper for grabbing data queried from an IDatabase object.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
assertBuildSubstringParams( $startPosition, $length)
Check type and bounds for parameters to self::buildSubstring()
Definition: Database.php:2348
string $dbPath
File name for SQLite database file.
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
trxLevel()
Gets the current transaction level.
Definition: Database.php:588
startAtomic( $fname=__METHOD__, $cancelable=self::ATOMIC_NOT_CANCELABLE)
Begin an atomic section of SQL statements.
Definition: Database.php:3723
static getFulltextSearchModule()
Returns version of currently supported SQLite fulltext search module or false if none present...
nativeReplace( $table, $rows, $fname)
REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE statement.
Definition: Database.php:2906
attachDatabase( $name, $file=false, $fname=__METHOD__)
Attaches external database to our connection, see https://sqlite.org/lang_attach.html for details...
static factory( $dbType, $p=[], $connect=self::NEW_CONNECTED)
Construct a Database subclass instance given a database type and parameters.
Definition: Database.php:434
endAtomic( $fname=__METHOD__)
Ends an atomic section of SQL statements.
Definition: Database.php:3753
makeSelectOptions( $options)
Filter the options used in SELECT statements.
query( $sql, $fname=__METHOD__, $flags=0)
Run an SQL query and return the result.
Definition: Database.php:1191
string $server
Server that this instance is currently connected to.
Definition: Database.php:81
insertId()
This must be called after nextSequenceVal.
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1980
const DBO_PERSISTENT
Definition: defines.php:14
if( $line===false) $args
Definition: cdb.php:64
close()
Close the database connection.
Definition: Database.php:938
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for tableName() and addQuotes(). You will need both of them. ------------------------------------------------------------------------ Basic query optimisation ------------------------------------------------------------------------ MediaWiki developers who need to write DB queries should have some understanding of databases and the performance issues associated with them. Patches containing unacceptably slow features will not be accepted. Unindexed queries are generally not welcome in MediaWiki
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1982
listTables( $prefix=null, $fname=__METHOD__)
List all tables on the database.
textFieldSize( $table, $field)
Returns the size of a text field, or -1 for "unlimited" In SQLite this is SQLITE_MAX_LENGTH, by default 1GB.
doQuery( $sql)
SQLite doesn&#39;t allow buffered results or data seeking etc, so we&#39;ll use fetchAll as the result...
static bool $fulltextEnabled
Whether full text is enabled.
setTableAliases(array $aliases)
Make certain table names use their own database, schema, and table prefix when passed into SQL querie...
indexName( $index)
Allows for index remapping in queries where this is not consistent across DBMS.
Definition: Database.php:2729
const LOCK_EX
Definition: LockManager.php:69
$res
Definition: database.txt:21
fieldInfo( $table, $field)
Get information about a given field Returns false if the field does not exist.
$params
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1982
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
integer null $affectedRowCount
Rows affected by the last query to query() or its CRUD wrappers.
Definition: Database.php:140
indexInfo( $table, $index, $fname=__METHOD__)
Returns information about an index Returns false if the index does not exist.
array $alreadyAttached
List of shared database already attached to this connection.
tableExists( $table, $fname=__METHOD__)
Query whether a given table exists.
databasesAreIndependent()
Returns true if DBs are assumed to be on potentially different servers.
selectSQLText( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
The equivalent of IDatabase::select() except that the constructed SQL is returned, instead of being immediately executed.
Definition: Database.php:1787
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:123
cancelAtomic( $fname=__METHOD__, AtomicSectionIdentifier $sectionId=null)
Cancel an atomic section of SQL statements.
Definition: Database.php:3787
static generateFileName( $dir, $dbName)
Generates a database file name.
numRows( $res)
The PDO::Statement class implements the array interface so count() will work.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Class to handle database/prefix specification for IDatabase domains.
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
Definition: Database.php:1870
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
Relational database abstraction object.
Definition: Database.php:48
select( $table, $vars, $conds='', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:1779
lock( $lockName, $method, $timeout=5)
Acquire a named lock.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and insert
Definition: hooks.txt:2061
closeConnection()
Does not actually close the connection, just destroys the reference for GC to do its work...
indexUnique( $table, $index, $fname=__METHOD__)
dropTable( $tableName, $fName=__METHOD__)
Override due to no CASCADE support.
string $user
User that this instance is currently connected under the name of.
Definition: Database.php:83
addIdentifierQuotes( $s)
Quotes an identifier, in order to make user controlled input safe.
Definition: Database.php:2750
buildGroupConcatField( $delim, $table, $field, $conds='', $join_conds=[])
Build a GROUP_CONCAT or equivalent statement for a query.
duplicateTableStructure( $oldName, $newName, $temporary=false, $fname=__METHOD__)
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
buildConcat( $stringList)
Build a concatenation list to feed into a SQL query.
open( $server, $user, $pass, $dbName, $schema, $tablePrefix)
int $lastAffectedRowCount
The number of rows affected as an integer.
deadlockLoop()
No-op version of deadlockLoop.
openFile( $fileName, $dbName, $tablePrefix)
Opens a database file.
buildSubstring( $input, $startPosition, $length=null)
string $trxMode
Transaction mode.
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2217
Database error base class.
Definition: DBError.php:30
insert( $table, $a, $fname=__METHOD__, $options=[])
Based on generic method (parent) with some prior SQLite-sepcific adjustments.
checkForEnabledSearch()
Check if the searchindext table is FTS enabled.