PageRenderTime 44ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/db/DatabaseSqlite.php

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