/src/daos/sqlite/Database.php

https://github.com/SSilence/selfoss · PHP · 342 lines · 264 code · 35 blank · 43 comment · 13 complexity · b06cc3e115c80e6502325a886fee3aef MD5 · raw file

  1. <?php
  2. namespace daos\sqlite;
  3. use daos\CommonSqlDatabase;
  4. use helpers\DatabaseConnection;
  5. use Monolog\Logger;
  6. /**
  7. * Base class for database access -- sqlite
  8. *
  9. * @copyright Copyright (c) Harald Lapp (harald.lapp@gmail.com)
  10. * @license GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html)
  11. * @author Harald Lapp (harald.lapp@gmail.com)
  12. * @author Tobias Zeising <tobias.zeising@aditu.de>
  13. */
  14. class Database implements \daos\DatabaseInterface {
  15. use CommonSqlDatabase;
  16. /** @var DatabaseConnection database connection */
  17. private $connection;
  18. /** @var Logger */
  19. private $logger;
  20. /**
  21. * establish connection and create undefined tables
  22. *
  23. * @return void
  24. */
  25. public function __construct(DatabaseConnection $connection, Logger $logger) {
  26. $this->connection = $connection;
  27. $this->logger = $logger;
  28. $this->logger->debug('Establishing SQLite database connection');
  29. // create tables if necessary
  30. $result = @$this->exec('SELECT name FROM sqlite_master WHERE type = "table"');
  31. $tables = [];
  32. foreach ($result as $table) {
  33. foreach ($table as $key => $value) {
  34. $tables[] = $value;
  35. }
  36. }
  37. if (!in_array('items', $tables, true)) {
  38. $this->logger->debug('Creating items table');
  39. $this->beginTransaction();
  40. $this->exec('
  41. CREATE TABLE items (
  42. id INTEGER PRIMARY KEY AUTOINCREMENT,
  43. datetime DATETIME NOT NULL,
  44. title TEXT NOT NULL,
  45. content TEXT NOT NULL,
  46. thumbnail TEXT,
  47. icon TEXT,
  48. unread BOOL NOT NULL,
  49. starred BOOL NOT NULL,
  50. source INT NOT NULL,
  51. uid VARCHAR(255) NOT NULL,
  52. link TEXT NOT NULL,
  53. updatetime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  54. author VARCHAR(255)
  55. );
  56. ');
  57. $this->exec('
  58. CREATE INDEX source ON items (
  59. source
  60. );
  61. ');
  62. $this->exec('
  63. CREATE TRIGGER update_updatetime_trigger
  64. AFTER UPDATE ON items FOR EACH ROW
  65. BEGIN
  66. UPDATE items
  67. SET updatetime = CURRENT_TIMESTAMP
  68. WHERE id = NEW.id;
  69. END;
  70. ');
  71. $this->commit();
  72. }
  73. $isNewestSourcesTable = false;
  74. if (!in_array('sources', $tables, true)) {
  75. $this->logger->debug('Creating sources table');
  76. $this->exec('
  77. CREATE TABLE sources (
  78. id INTEGER PRIMARY KEY AUTOINCREMENT,
  79. title TEXT NOT NULL,
  80. tags TEXT,
  81. spout TEXT NOT NULL,
  82. params TEXT NOT NULL,
  83. filter TEXT,
  84. error TEXT,
  85. lastupdate INTEGER,
  86. lastentry INTEGER
  87. );
  88. ');
  89. $isNewestSourcesTable = true;
  90. }
  91. // version 1
  92. if (!in_array('version', $tables, true)) {
  93. $this->logger->debug('Upgrading database schema to version 8 from initial state');
  94. $this->beginTransaction();
  95. $this->exec('
  96. CREATE TABLE version (
  97. version INT
  98. );
  99. ');
  100. $this->exec('
  101. INSERT INTO version (version) VALUES (8);
  102. ');
  103. $this->exec('
  104. CREATE TABLE tags (
  105. tag TEXT NOT NULL,
  106. color TEXT NOT NULL
  107. );
  108. ');
  109. if ($isNewestSourcesTable === false) {
  110. $this->exec('
  111. ALTER TABLE sources ADD tags TEXT;
  112. ');
  113. }
  114. $this->commit();
  115. }
  116. $version = $this->getSchemaVersion();
  117. if ($version < 3) {
  118. $this->logger->debug('Upgrading database schema to version 3');
  119. $this->beginTransaction();
  120. $this->exec('
  121. ALTER TABLE sources ADD lastupdate INT;
  122. ');
  123. $this->exec('
  124. INSERT INTO version (version) VALUES (3);
  125. ');
  126. $this->commit();
  127. }
  128. if ($version < 4) {
  129. $this->logger->debug('Upgrading database schema to version 4');
  130. $this->beginTransaction();
  131. $this->exec('
  132. ALTER TABLE items ADD updatetime DATETIME;
  133. ');
  134. $this->exec('
  135. CREATE TRIGGER insert_updatetime_trigger
  136. AFTER INSERT ON items FOR EACH ROW
  137. BEGIN
  138. UPDATE items
  139. SET updatetime = CURRENT_TIMESTAMP
  140. WHERE id = NEW.id;
  141. END;
  142. ');
  143. $this->exec('
  144. CREATE TRIGGER update_updatetime_trigger
  145. AFTER UPDATE ON items FOR EACH ROW
  146. BEGIN
  147. UPDATE items
  148. SET updatetime = CURRENT_TIMESTAMP
  149. WHERE id = NEW.id;
  150. END;
  151. ');
  152. $this->exec('
  153. INSERT INTO version (version) VALUES (4);
  154. ');
  155. $this->commit();
  156. }
  157. if ($version < 5) {
  158. $this->logger->debug('Upgrading database schema to version 5');
  159. $this->beginTransaction();
  160. $this->exec('
  161. ALTER TABLE items ADD author VARCHAR(255);
  162. ');
  163. $this->exec('
  164. INSERT INTO version (version) VALUES (5);
  165. ');
  166. $this->commit();
  167. }
  168. if ($version < 6) {
  169. $this->logger->debug('Upgrading database schema to version 6');
  170. $this->beginTransaction();
  171. $this->exec('
  172. ALTER TABLE sources ADD filter TEXT;
  173. ');
  174. $this->exec('
  175. INSERT INTO version (version) VALUES (6);
  176. ');
  177. $this->commit();
  178. }
  179. // Jump straight from v6 to v8 due to bug in previous version of the code
  180. // in \daos\sqlite\Database which
  181. // set the database version to "7" for initial installs.
  182. if ($version < 8) {
  183. $this->logger->debug('Upgrading database schema to version 8');
  184. $this->beginTransaction();
  185. $this->exec('
  186. ALTER TABLE sources ADD lastentry INT;
  187. ');
  188. $this->exec('
  189. INSERT INTO version (version) VALUES (8);
  190. ');
  191. $this->initLastEntryFieldDuringUpgrade();
  192. $this->commit();
  193. }
  194. if ($version < 9) {
  195. $this->logger->debug('Upgrading database schema to version 9');
  196. $this->beginTransaction();
  197. $this->exec('
  198. ALTER TABLE items ADD shared BOOL;
  199. ');
  200. $this->exec('
  201. INSERT INTO version (version) VALUES (9);
  202. ');
  203. $this->commit();
  204. }
  205. if ($version < 11) {
  206. $this->logger->debug('Upgrading database schema to version 11');
  207. $this->beginTransaction();
  208. // Table needs to be re-created because ALTER TABLE is rather limited.
  209. // https://sqlite.org/lang_altertable.html#otheralter
  210. $this->exec(
  211. 'CREATE TABLE new_items (
  212. id INTEGER PRIMARY KEY AUTOINCREMENT,
  213. datetime DATETIME NOT NULL,
  214. title TEXT NOT NULL,
  215. content TEXT NOT NULL,
  216. thumbnail TEXT,
  217. icon TEXT,
  218. unread BOOL NOT NULL,
  219. starred BOOL NOT NULL,
  220. source INT NOT NULL,
  221. uid VARCHAR(255) NOT NULL,
  222. link TEXT NOT NULL,
  223. updatetime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  224. author VARCHAR(255),
  225. shared BOOL,
  226. lastseen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
  227. )'
  228. );
  229. $this->exec('UPDATE items SET updatetime = datetime WHERE updatetime IS NULL');
  230. $this->exec('INSERT INTO new_items SELECT *, CURRENT_TIMESTAMP FROM items');
  231. $this->exec('DROP TABLE items');
  232. $this->exec('ALTER TABLE new_items RENAME TO items');
  233. $this->exec('CREATE INDEX source ON items (source)');
  234. $this->exec('
  235. CREATE TRIGGER update_updatetime_trigger
  236. AFTER UPDATE ON items FOR EACH ROW
  237. WHEN (
  238. OLD.unread <> NEW.unread OR
  239. OLD.starred <> NEW.starred
  240. )
  241. BEGIN
  242. UPDATE items
  243. SET updatetime = CURRENT_TIMESTAMP
  244. WHERE id = NEW.id;
  245. END
  246. ');
  247. $this->exec('INSERT INTO version (version) VALUES (11)');
  248. $this->commit();
  249. }
  250. if ($version < 13) {
  251. $this->logger->debug('Upgrading database schema to version 13');
  252. $this->beginTransaction();
  253. $this->exec("UPDATE sources SET spout = 'spouts\\rss\\fulltextrss' WHERE spout = 'spouts\\rss\\instapaper'");
  254. $this->exec('INSERT INTO version (version) VALUES (13)');
  255. $this->commit();
  256. }
  257. }
  258. /**
  259. * wrap insert statement to return id
  260. *
  261. * @param string $query sql statement
  262. * @param array $params sql params
  263. *
  264. * @return int id after insert
  265. */
  266. public function insert($query, array $params) {
  267. $this->exec($query, $params);
  268. $res = $this->exec('SELECT last_insert_rowid() as lastid');
  269. return (int) $res[0]['lastid'];
  270. }
  271. /**
  272. * optimize database by database own optimize statement
  273. *
  274. * @return void
  275. */
  276. public function optimize() {
  277. @$this->exec('
  278. VACUUM;
  279. ');
  280. }
  281. /**
  282. * Initialize 'lastentry' Field in Source table during database upgrade
  283. *
  284. * @return void
  285. */
  286. private function initLastEntryFieldDuringUpgrade() {
  287. $sources = @$this->exec('SELECT id FROM sources');
  288. // have a look at each entry in the source table
  289. foreach ($sources as $current_src) {
  290. // get the date of the newest entry found in the database
  291. $latestEntryDate = @$this->exec(
  292. 'SELECT datetime FROM items WHERE source=? ORDER BY datetime DESC LIMIT 0, 1',
  293. $current_src['id']
  294. );
  295. // if an entry for this source was found in the database, write the date of the newest one into the sources table
  296. if (isset($latestEntryDate[0]['datetime'])) {
  297. @$this->exec(
  298. 'UPDATE sources SET lastentry=? WHERE id=?',
  299. [
  300. strtotime($latestEntryDate[0]['datetime']),
  301. $current_src['id'],
  302. ]
  303. );
  304. }
  305. }
  306. }
  307. }