summaryrefslogtreecommitdiff
path: root/app/Models/DatabaseDAO.php
diff options
context:
space:
mode:
Diffstat (limited to 'app/Models/DatabaseDAO.php')
-rw-r--r--app/Models/DatabaseDAO.php250
1 files changed, 215 insertions, 35 deletions
diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php
index b331eccc3..a36b469b1 100644
--- a/app/Models/DatabaseDAO.php
+++ b/app/Models/DatabaseDAO.php
@@ -8,25 +8,50 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
//MySQL error codes
const ER_BAD_FIELD_ERROR = '42S22';
const ER_BAD_TABLE_ERROR = '42S02';
- const ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = '1366';
+ const ER_DATA_TOO_LONG = '1406';
//MySQL InnoDB maximum index length for UTF8MB4
//https://dev.mysql.com/doc/refman/8.0/en/innodb-restrictions.html
const LENGTH_INDEX_UNICODE = 191;
+ public function create() {
+ require(APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php');
+ $db = FreshRSS_Context::$system_conf->db;
+
+ try {
+ $sql = sprintf($SQL_CREATE_DB, empty($db['base']) ? '' : $db['base']);
+ return $this->pdo->exec($sql) !== false;
+ } catch (PDOException $e) {
+ $_SESSION['bd_error'] = $e->getMessage();
+ syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage());
+ return false;
+ }
+ }
+
+ public function testConnection() {
+ try {
+ $sql = 'SELECT 1';
+ $stm = $this->pdo->query($sql);
+ $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ return $res != false;
+ } catch (PDOException $e) {
+ $_SESSION['bd_error'] = $e->getMessage();
+ syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage());
+ return false;
+ }
+ }
+
public function tablesAreCorrect() {
- $sql = 'SHOW TABLES';
- $stm = $this->bd->prepare($sql);
- $stm->execute();
+ $stm = $this->pdo->query('SHOW TABLES');
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$tables = array(
- $this->prefix . 'category' => false,
- $this->prefix . 'feed' => false,
- $this->prefix . 'entry' => false,
- $this->prefix . 'entrytmp' => false,
- $this->prefix . 'tag' => false,
- $this->prefix . 'entrytag' => false,
+ $this->pdo->prefix() . 'category' => false,
+ $this->pdo->prefix() . 'feed' => false,
+ $this->pdo->prefix() . 'entry' => false,
+ $this->pdo->prefix() . 'entrytmp' => false,
+ $this->pdo->prefix() . 'tag' => false,
+ $this->pdo->prefix() . 'entrytag' => false,
);
foreach ($res as $value) {
$tables[array_pop($value)] = true;
@@ -36,10 +61,8 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
}
public function getSchema($table) {
- $sql = 'DESC ' . $this->prefix . $table;
- $stm = $this->bd->prepare($sql);
- $stm->execute();
-
+ $sql = 'DESC `_' . $table . '`';
+ $stm = $this->pdo->query($sql);
return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC));
}
@@ -63,7 +86,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
public function feedIsCorrect() {
return $this->checkTable('feed', array(
'id', 'url', 'category', 'name', 'website', 'description', 'lastUpdate',
- 'priority', 'pathEntries', 'httpAuth', 'error', 'keep_history', 'ttl', 'attributes',
+ 'priority', 'pathEntries', 'httpAuth', 'error', 'ttl', 'attributes',
'cache_nbEntries', 'cache_nbUnreads',
));
}
@@ -119,9 +142,9 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
$values = array($db['base']);
if (!$all) {
$sql .= ' AND table_name LIKE ?';
- $values[] = $this->prefix . '%';
+ $values[] = $this->pdo->prefix() . '%';
}
- $stm = $this->bd->prepare($sql);
+ $stm = $this->pdo->prepare($sql);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
return $res[0];
@@ -132,30 +155,23 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
$tables = array('category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag');
foreach ($tables as $table) {
- $sql = 'OPTIMIZE TABLE `' . $this->prefix . $table . '`'; //MySQL
- $stm = $this->bd->prepare($sql);
- $ok &= $stm != false;
- if ($stm) {
- $ok &= $stm->execute();
- }
+ $sql = 'OPTIMIZE TABLE `_' . $table . '`'; //MySQL
+ $ok &= ($this->pdo->exec($sql) !== false);
}
return $ok;
}
public function ensureCaseInsensitiveGuids() {
$ok = true;
- $db = FreshRSS_Context::$system_conf->db;
- if ($db['type'] === 'mysql') {
- include_once(APP_PATH . '/SQL/install.sql.mysql.php');
- if (defined('SQL_UPDATE_GUID_LATIN1_BIN')) { //FreshRSS 1.12
- try {
- $sql = sprintf(SQL_UPDATE_GUID_LATIN1_BIN, $this->prefix);
- $stm = $this->bd->prepare($sql);
- $ok = $stm->execute();
- } catch (Exception $e) {
- $ok = false;
- Minz_Log::error('FreshRSS_DatabaseDAO::ensureCaseInsensitiveGuids error: ' . $e->getMessage());
- }
+ if ($this->pdo->dbType() === 'mysql') {
+ include(APP_PATH . '/SQL/install.sql.mysql.php');
+
+ $ok = false;
+ try {
+ $ok = $this->pdo->exec($SQL_UPDATE_GUID_LATIN1_BIN) !== false; //FreshRSS 1.12
+ } catch (Exception $e) {
+ $ok = false;
+ Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage());
}
}
return $ok;
@@ -164,4 +180,168 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
public function minorDbMaintenance() {
$this->ensureCaseInsensitiveGuids();
}
+
+ private static function stdError($error) {
+ if (defined('STDERR')) {
+ fwrite(STDERR, $error . "\n");
+ }
+ Minz_Log::error($error);
+ return false;
+ }
+
+ const SQLITE_EXPORT = 1;
+ const SQLITE_IMPORT = 2;
+
+ public function dbCopy($filename, $mode, $clearFirst = false) {
+ $error = '';
+
+ $userDAO = FreshRSS_Factory::createUserDao();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $entryDAO = FreshRSS_Factory::createEntryDao();
+ $tagDAO = FreshRSS_Factory::createTagDao();
+
+ switch ($mode) {
+ case self::SQLITE_EXPORT:
+ if (@filesize($filename) > 0) {
+ $error = 'Error: SQLite export file already exists: ' . $filename;
+ }
+ break;
+ case self::SQLITE_IMPORT:
+ if (!is_readable($filename)) {
+ $error = 'Error: SQLite import file is not readable: ' . $filename;
+ } elseif ($clearFirst) {
+ $userDAO->deleteUser();
+ if ($this->pdo->dbType() === 'sqlite') {
+ //We cannot just delete the .sqlite file otherwise PDO gets buggy.
+ //SQLite is the only one with database-level optimization, instead of at table level.
+ $this->optimize();
+ }
+ } else {
+ $nbEntries = $entryDAO->countUnreadRead();
+ if (!empty($nbEntries['all'])) {
+ $error = 'Error: Destination database already contains some entries!';
+ }
+ }
+ break;
+ default:
+ $error = 'Invalid copy mode!';
+ break;
+ }
+ if ($error != '') {
+ return self::stdError($error);
+ }
+
+ $sqlite = null;
+
+ try {
+ $sqlite = new MinzPDOSQLite('sqlite:' . $filename);
+ } catch (Exception $e) {
+ $error = 'Error while initialising SQLite copy: ' . $e->getMessage();
+ return self::stdError($error);
+ }
+
+ Minz_ModelPdo::clean();
+ $userDAOSQLite = new FreshRSS_UserDAO('', $sqlite);
+ $categoryDAOSQLite = new FreshRSS_CategoryDAOSQLite('', $sqlite);
+ $feedDAOSQLite = new FreshRSS_FeedDAOSQLite('', $sqlite);
+ $entryDAOSQLite = new FreshRSS_EntryDAOSQLite('', $sqlite);
+ $tagDAOSQLite = new FreshRSS_TagDAOSQLite('', $sqlite);
+
+ switch ($mode) {
+ case self::SQLITE_EXPORT:
+ $userFrom = $userDAO; $userTo = $userDAOSQLite;
+ $catFrom = $catDAO; $catTo = $categoryDAOSQLite;
+ $feedFrom = $feedDAO; $feedTo = $feedDAOSQLite;
+ $entryFrom = $entryDAO; $entryTo = $entryDAOSQLite;
+ $tagFrom = $tagDAO; $tagTo = $tagDAOSQLite;
+ break;
+ case self::SQLITE_IMPORT:
+ $userFrom = $userDAOSQLite; $userTo = $userDAO;
+ $catFrom = $categoryDAOSQLite; $catTo = $catDAO;
+ $feedFrom = $feedDAOSQLite; $feedTo = $feedDAO;
+ $entryFrom = $entryDAOSQLite; $entryTo = $entryDAO;
+ $tagFrom = $tagDAOSQLite; $tagTo = $tagDAO;
+ break;
+ }
+
+ $idMaps = [];
+
+ if (defined('STDERR')) {
+ fwrite(STDERR, "Start SQL copy…\n");
+ }
+
+ $userTo->createUser();
+
+ $catTo->beginTransaction();
+ foreach ($catFrom->selectAll() as $category) {
+ $cat = $catTo->searchByName($category['name']); //Useful for the default category
+ if ($cat != null) {
+ $catId = $cat->id();
+ } else {
+ $catId = $catTo->addCategory($category);
+ if ($catId == false) {
+ $error = 'Error during SQLite copy of categories!';
+ return self::stdError($error);
+ }
+ }
+ $idMaps['c' . $category['id']] = $catId;
+ }
+ foreach ($feedFrom->selectAll() as $feed) {
+ $feed['category'] = empty($idMaps['c' . $feed['category']]) ? FreshRSS_CategoryDAO::DEFAULTCATEGORYID : $idMaps['c' . $feed['category']];
+ $feedId = $feedTo->addFeed($feed);
+ if ($feedId == false) {
+ $error = 'Error during SQLite copy of feeds!';
+ return self::stdError($error);
+ }
+ $idMaps['f' . $feed['id']] = $feedId;
+ }
+ $catTo->commit();
+
+ $nbEntries = $entryFrom->count();
+ $n = 0;
+ $entryTo->beginTransaction();
+ foreach ($entryFrom->selectAll() as $entry) {
+ $n++;
+ if (!empty($idMaps['f' . $entry['id_feed']])) {
+ $entry['id_feed'] = $idMaps['f' . $entry['id_feed']];
+ if (!$entryTo->addEntry($entry, false)) {
+ $error = 'Error during SQLite copy of entries!';
+ return self::stdError($error);
+ }
+ }
+ if ($n % 100 === 1 && defined('STDERR')) { //Display progression
+ fwrite(STDERR, "\033[0G" . $n . '/' . $nbEntries);
+ }
+ }
+ if (defined('STDERR')) {
+ fwrite(STDERR, "\033[0G" . $n . '/' . $nbEntries . "\n");
+ }
+ $entryTo->commit();
+ $feedTo->updateCachedValues();
+
+ $idMaps = [];
+
+ $tagTo->beginTransaction();
+ foreach ($tagFrom->selectAll() as $tag) {
+ $tagId = $tagTo->addTag($tag);
+ if ($tagId == false) {
+ $error = 'Error during SQLite copy of tags!';
+ return self::stdError($error);
+ }
+ $idMaps['t' . $tag['id']] = $tagId;
+ }
+ foreach ($tagFrom->selectEntryTag() as $entryTag) {
+ if (!empty($idMaps['t' . $entryTag['id_tag']])) {
+ $entryTag['id_tag'] = $idMaps['t' . $entryTag['id_tag']];
+ if (!$tagTo->tagEntry($entryTag['id_tag'], $entryTag['id_entry'])) {
+ $error = 'Error during SQLite copy of entry-tags!';
+ return self::stdError($error);
+ }
+ }
+ }
+ $tagTo->commit();
+
+ return true;
+ }
}