|
|
@@ -11,7 +11,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
}
|
|
|
|
|
|
protected function addColumn($name) {
|
|
|
- Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn: ' . $name);
|
|
|
+ Minz_Log::warning('FreshRSS_EntryDAO::addColumn: ' . $name);
|
|
|
$hasTransaction = false;
|
|
|
try {
|
|
|
$stm = null;
|
|
|
@@ -38,7 +38,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
return $stm && $stm->execute();
|
|
|
}
|
|
|
} catch (Exception $e) {
|
|
|
- Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn error: ' . $e->getMessage());
|
|
|
+ Minz_Log::error('FreshRSS_EntryDAO::addColumn error: ' . $e->getMessage());
|
|
|
if ($hasTransaction) {
|
|
|
$this->bd->rollBack();
|
|
|
}
|
|
|
@@ -46,9 +46,44 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- protected function autoAddColumn($errorInfo) {
|
|
|
+ private $triedUpdateToUtf8mb4 = false;
|
|
|
+
|
|
|
+ protected function updateToUtf8mb4() {
|
|
|
+ if ($this->triedUpdateToUtf8mb4) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $this->triedUpdateToUtf8mb4 = true;
|
|
|
+ $db = FreshRSS_Context::$system_conf->db;
|
|
|
+ if ($db['type'] === 'mysql') {
|
|
|
+ include_once(APP_PATH . '/SQL/install.sql.mysql.php');
|
|
|
+ if (defined('SQL_UPDATE_UTF8MB4')) {
|
|
|
+ Minz_Log::warning('Updating MySQL to UTF8MB4...');
|
|
|
+ $hadTransaction = $this->bd->inTransaction();
|
|
|
+ if ($hadTransaction) {
|
|
|
+ $this->bd->commit();
|
|
|
+ }
|
|
|
+ $ok = false;
|
|
|
+ try {
|
|
|
+ $sql = sprintf(SQL_UPDATE_UTF8MB4, $this->prefix, $db['base']);
|
|
|
+ $stm = $this->bd->prepare($sql);
|
|
|
+ $ok = $stm->execute();
|
|
|
+ } catch (Exception $e) {
|
|
|
+ Minz_Log::error('FreshRSS_EntryDAO::updateToUtf8mb4 error: ' . $e->getMessage());
|
|
|
+ }
|
|
|
+ if ($hadTransaction) {
|
|
|
+ $this->bd->beginTransaction();
|
|
|
+ //NB: Transaction not starting. Why? (tested on PHP 7.0.8-0ubuntu and MySQL 5.7.13-0ubuntu)
|
|
|
+ }
|
|
|
+ return $ok;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function autoUpdateDb($errorInfo) {
|
|
|
if (isset($errorInfo[0])) {
|
|
|
- if ($errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR
|
|
|
+ if ($errorInfo[0] === '42S22') { //ER_BAD_FIELD_ERROR
|
|
|
+ //autoAddColumn
|
|
|
foreach (array('lastSeen', 'hash') as $column) {
|
|
|
if (stripos($errorInfo[2], $column) !== false) {
|
|
|
return $this->addColumn($column);
|
|
|
@@ -56,6 +91,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ if (isset($errorInfo[1])) {
|
|
|
+ if ($errorInfo[1] == '1366') { //ER_TRUNCATED_WRONG_VALUE_FOR_FIELD
|
|
|
+ return $this->updateToUtf8mb4();
|
|
|
+ }
|
|
|
+ }
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
@@ -94,7 +134,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
return $this->bd->lastInsertId();
|
|
|
} else {
|
|
|
$info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo();
|
|
|
- if ($this->autoAddColumn($info)) {
|
|
|
+ if ($this->autoUpdateDb($info)) {
|
|
|
return $this->addEntry($valuesTmp);
|
|
|
} elseif ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
|
|
|
Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
|
|
|
@@ -145,7 +185,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
return $this->bd->lastInsertId();
|
|
|
} else {
|
|
|
$info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo();
|
|
|
- if ($this->autoAddColumn($info)) {
|
|
|
+ if ($this->autoUpdateDb($info)) {
|
|
|
return $this->updateEntry($valuesTmp);
|
|
|
}
|
|
|
Minz_Log::error('SQL error updateEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
|
|
|
@@ -304,7 +344,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
* @param integer $priorityMin
|
|
|
* @return integer affected rows
|
|
|
*/
|
|
|
- public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
|
|
|
+ public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0, $filter = null, $state = 0) {
|
|
|
if ($idMax == 0) {
|
|
|
$idMax = time() . '000000';
|
|
|
Minz_Log::debug('Calling markReadEntries(0) is deprecated!');
|
|
|
@@ -319,8 +359,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
$sql .= ' AND f.priority > ' . intval($priorityMin);
|
|
|
}
|
|
|
$values = array($idMax);
|
|
|
- $stm = $this->bd->prepare($sql);
|
|
|
- if (!($stm && $stm->execute($values))) {
|
|
|
+
|
|
|
+ list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filter, $state);
|
|
|
+
|
|
|
+ $stm = $this->bd->prepare($sql . $search);
|
|
|
+ if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
|
|
|
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
|
|
|
Minz_Log::error('SQL error markReadEntries: ' . $info[2]);
|
|
|
return false;
|
|
|
@@ -343,7 +386,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
* @param integer $idMax fail safe article ID
|
|
|
* @return integer affected rows
|
|
|
*/
|
|
|
- public function markReadCat($id, $idMax = 0) {
|
|
|
+ public function markReadCat($id, $idMax = 0, $filter = null, $state = 0) {
|
|
|
if ($idMax == 0) {
|
|
|
$idMax = time() . '000000';
|
|
|
Minz_Log::debug('Calling markReadCat(0) is deprecated!');
|
|
|
@@ -353,8 +396,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
. 'SET e.is_read=1 '
|
|
|
. 'WHERE f.category=? AND e.is_read=0 AND e.id <= ?';
|
|
|
$values = array($id, $idMax);
|
|
|
- $stm = $this->bd->prepare($sql);
|
|
|
- if (!($stm && $stm->execute($values))) {
|
|
|
+
|
|
|
+ list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filter, $state);
|
|
|
+
|
|
|
+ $stm = $this->bd->prepare($sql . $search);
|
|
|
+ if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
|
|
|
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
|
|
|
Minz_Log::error('SQL error markReadCat: ' . $info[2]);
|
|
|
return false;
|
|
|
@@ -377,19 +423,22 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
* @param integer $idMax fail safe article ID
|
|
|
* @return integer affected rows
|
|
|
*/
|
|
|
- public function markReadFeed($id_feed, $idMax = 0) {
|
|
|
+ public function markReadFeed($id_feed, $idMax = 0, $filter = null, $state = 0) {
|
|
|
if ($idMax == 0) {
|
|
|
$idMax = time() . '000000';
|
|
|
Minz_Log::debug('Calling markReadFeed(0) is deprecated!');
|
|
|
}
|
|
|
$this->bd->beginTransaction();
|
|
|
|
|
|
- $sql = 'UPDATE `' . $this->prefix . 'entry` '
|
|
|
- . 'SET is_read=1 '
|
|
|
- . 'WHERE id_feed=? AND is_read=0 AND id <= ?';
|
|
|
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e '
|
|
|
+ . 'SET e.is_read=1 '
|
|
|
+ . 'WHERE e.id_feed=? AND e.is_read=0 AND e.id <= ?';
|
|
|
$values = array($id_feed, $idMax);
|
|
|
- $stm = $this->bd->prepare($sql);
|
|
|
- if (!($stm && $stm->execute($values))) {
|
|
|
+
|
|
|
+ list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filter, $state);
|
|
|
+
|
|
|
+ $stm = $this->bd->prepare($sql . $search);
|
|
|
+ if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
|
|
|
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
|
|
|
Minz_Log::error('SQL error markReadFeed: ' . $info[2]);
|
|
|
$this->bd->rollBack();
|
|
|
@@ -453,52 +502,24 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL
|
|
|
}
|
|
|
|
|
|
- private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
|
|
|
- if (!$state) {
|
|
|
- $state = FreshRSS_Entry::STATE_ALL;
|
|
|
- }
|
|
|
- $where = '';
|
|
|
- $joinFeed = false;
|
|
|
+ protected function sqlListEntriesWhere($alias = '', $filter = null, $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $firstId = '', $date_min = 0) {
|
|
|
+ $search = ' ';
|
|
|
$values = array();
|
|
|
- switch ($type) {
|
|
|
- case 'a':
|
|
|
- $where .= 'f.priority > 0 ';
|
|
|
- $joinFeed = true;
|
|
|
- break;
|
|
|
- case 's': //Deprecated: use $state instead
|
|
|
- $where .= 'e1.is_favorite=1 ';
|
|
|
- break;
|
|
|
- case 'c':
|
|
|
- $where .= 'f.category=? ';
|
|
|
- $values[] = intval($id);
|
|
|
- $joinFeed = true;
|
|
|
- break;
|
|
|
- case 'f':
|
|
|
- $where .= 'e1.id_feed=? ';
|
|
|
- $values[] = intval($id);
|
|
|
- break;
|
|
|
- case 'A':
|
|
|
- $where .= '1 ';
|
|
|
- break;
|
|
|
- default:
|
|
|
- throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!');
|
|
|
- }
|
|
|
-
|
|
|
if ($state & FreshRSS_Entry::STATE_NOT_READ) {
|
|
|
if (!($state & FreshRSS_Entry::STATE_READ)) {
|
|
|
- $where .= 'AND e1.is_read=0 ';
|
|
|
+ $search .= 'AND ' . $alias . 'is_read=0 ';
|
|
|
}
|
|
|
}
|
|
|
elseif ($state & FreshRSS_Entry::STATE_READ) {
|
|
|
- $where .= 'AND e1.is_read=1 ';
|
|
|
+ $search .= 'AND ' . $alias . 'is_read=1 ';
|
|
|
}
|
|
|
if ($state & FreshRSS_Entry::STATE_FAVORITE) {
|
|
|
if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) {
|
|
|
- $where .= 'AND e1.is_favorite=1 ';
|
|
|
+ $search .= 'AND ' . $alias . 'is_favorite=1 ';
|
|
|
}
|
|
|
}
|
|
|
elseif ($state & FreshRSS_Entry::STATE_NOT_FAVORITE) {
|
|
|
- $where .= 'AND e1.is_favorite=0 ';
|
|
|
+ $search .= 'AND ' . $alias . 'is_favorite=0 ';
|
|
|
}
|
|
|
|
|
|
switch ($order) {
|
|
|
@@ -512,76 +533,111 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
$firstId = $order === 'DESC' ? '9000000000'. '000000' : '0'; //MySQL optimization. TODO: check if this is needed again, after the filtering for old articles has been removed in 0.9-dev
|
|
|
}*/
|
|
|
if ($firstId !== '') {
|
|
|
- $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
|
|
|
+ $search .= 'AND ' . $alias . 'id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
|
|
|
}
|
|
|
if ($date_min > 0) {
|
|
|
- $where .= 'AND e1.id >= ' . $date_min . '000000 ';
|
|
|
+ $search .= 'AND ' . $alias . 'id >= ' . $date_min . '000000 ';
|
|
|
}
|
|
|
- $search = '';
|
|
|
if ($filter) {
|
|
|
if ($filter->getIntitle()) {
|
|
|
- $search .= 'AND e1.title LIKE ? ';
|
|
|
+ $search .= 'AND ' . $alias . 'title LIKE ? ';
|
|
|
$values[] = "%{$filter->getIntitle()}%";
|
|
|
}
|
|
|
if ($filter->getInurl()) {
|
|
|
- $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
|
|
|
+ $search .= 'AND CONCAT(' . $alias . 'link, ' . $alias . 'guid) LIKE ? ';
|
|
|
$values[] = "%{$filter->getInurl()}%";
|
|
|
}
|
|
|
if ($filter->getAuthor()) {
|
|
|
- $search .= 'AND e1.author LIKE ? ';
|
|
|
+ $search .= 'AND ' . $alias . 'author LIKE ? ';
|
|
|
$values[] = "%{$filter->getAuthor()}%";
|
|
|
}
|
|
|
if ($filter->getMinDate()) {
|
|
|
- $search .= 'AND e1.id >= ? ';
|
|
|
+ $search .= 'AND ' . $alias . 'id >= ? ';
|
|
|
$values[] = "{$filter->getMinDate()}000000";
|
|
|
}
|
|
|
if ($filter->getMaxDate()) {
|
|
|
- $search .= 'AND e1.id <= ? ';
|
|
|
+ $search .= 'AND ' . $alias . 'id <= ? ';
|
|
|
$values[] = "{$filter->getMaxDate()}000000";
|
|
|
}
|
|
|
if ($filter->getMinPubdate()) {
|
|
|
- $search .= 'AND e1.date >= ? ';
|
|
|
+ $search .= 'AND ' . $alias . 'date >= ? ';
|
|
|
$values[] = $filter->getMinPubdate();
|
|
|
}
|
|
|
if ($filter->getMaxPubdate()) {
|
|
|
- $search .= 'AND e1.date <= ? ';
|
|
|
+ $search .= 'AND ' . $alias . 'date <= ? ';
|
|
|
$values[] = $filter->getMaxPubdate();
|
|
|
}
|
|
|
if ($filter->getTags()) {
|
|
|
$tags = $filter->getTags();
|
|
|
foreach ($tags as $tag) {
|
|
|
- $search .= 'AND e1.tags LIKE ? ';
|
|
|
+ $search .= 'AND ' . $alias . 'tags LIKE ? ';
|
|
|
$values[] = "%{$tag}%";
|
|
|
}
|
|
|
}
|
|
|
if ($filter->getSearch()) {
|
|
|
$search_values = $filter->getSearch();
|
|
|
foreach ($search_values as $search_value) {
|
|
|
- $search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? ';
|
|
|
+ $search .= 'AND ' . $this->sqlconcat($alias . 'title', $this->isCompressed() ? 'UNCOMPRESS(' . $alias . 'content_bin)' : '' . $alias . 'content') . ' LIKE ? ';
|
|
|
$values[] = "%{$search_value}%";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- return array($values,
|
|
|
- 'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 '
|
|
|
- . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed=f.id ' : '')
|
|
|
+ return array($values, $search);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
|
|
|
+ if (!$state) {
|
|
|
+ $state = FreshRSS_Entry::STATE_ALL;
|
|
|
+ }
|
|
|
+ $where = '';
|
|
|
+ $joinFeed = false;
|
|
|
+ $values = array();
|
|
|
+ switch ($type) {
|
|
|
+ case 'a':
|
|
|
+ $where .= 'f.priority > 0 ';
|
|
|
+ $joinFeed = true;
|
|
|
+ break;
|
|
|
+ case 's': //Deprecated: use $state instead
|
|
|
+ $where .= 'e.is_favorite=1 ';
|
|
|
+ break;
|
|
|
+ case 'c':
|
|
|
+ $where .= 'f.category=? ';
|
|
|
+ $values[] = intval($id);
|
|
|
+ $joinFeed = true;
|
|
|
+ break;
|
|
|
+ case 'f':
|
|
|
+ $where .= 'e.id_feed=? ';
|
|
|
+ $values[] = intval($id);
|
|
|
+ break;
|
|
|
+ case 'A':
|
|
|
+ $where .= '1 ';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!');
|
|
|
+ }
|
|
|
+
|
|
|
+ list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filter, $state, $order, $firstId, $date_min);
|
|
|
+
|
|
|
+ return array(array_merge($values, $searchValues),
|
|
|
+ 'SELECT e.id FROM `' . $this->prefix . 'entry` e '
|
|
|
+ . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' : '')
|
|
|
. 'WHERE ' . $where
|
|
|
. $search
|
|
|
- . 'ORDER BY e1.id ' . $order
|
|
|
+ . 'ORDER BY e.id ' . $order
|
|
|
. ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
|
|
|
}
|
|
|
|
|
|
public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
|
|
|
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min);
|
|
|
|
|
|
- $sql = 'SELECT e.id, e.guid, e.title, e.author, '
|
|
|
+ $sql = 'SELECT e0.id, e0.guid, e0.title, e0.author, '
|
|
|
. ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
|
|
|
- . ', e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags '
|
|
|
- . 'FROM `' . $this->prefix . 'entry` e '
|
|
|
+ . ', e0.link, e0.date, e0.is_read, e0.is_favorite, e0.id_feed, e0.tags '
|
|
|
+ . 'FROM `' . $this->prefix . 'entry` e0 '
|
|
|
. 'INNER JOIN ('
|
|
|
. $sql
|
|
|
- . ') e2 ON e2.id=e.id '
|
|
|
- . 'ORDER BY e.id ' . $order;
|
|
|
+ . ') e2 ON e2.id=e0.id '
|
|
|
+ . 'ORDER BY e0.id ' . $order;
|
|
|
|
|
|
$stm = $this->bd->prepare($sql);
|
|
|
$stm->execute($values);
|
|
|
@@ -615,7 +671,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
return $result;
|
|
|
} else {
|
|
|
$info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
|
|
|
- if ($this->autoAddColumn($info)) {
|
|
|
+ if ($this->autoUpdateDb($info)) {
|
|
|
return $this->listHashForFeedGuids($id_feed, $guids);
|
|
|
}
|
|
|
Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
|
|
|
@@ -636,7 +692,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
return $stm->rowCount();
|
|
|
} else {
|
|
|
$info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
|
|
|
- if ($this->autoAddColumn($info)) {
|
|
|
+ if ($this->autoUpdateDb($info)) {
|
|
|
return $this->updateLastSeen($id_feed, $guids);
|
|
|
}
|
|
|
Minz_Log::error('SQL error updateLastSeen: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
|
|
|
@@ -692,7 +748,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
|
|
|
public function optimizeTable() {
|
|
|
$sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; //MySQL
|
|
|
$stm = $this->bd->prepare($sql);
|
|
|
- $stm->execute();
|
|
|
+ if ($stm) {
|
|
|
+ return $stm->execute();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public function size($all = false) {
|