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