Selaa lähdekoodia

SQL : grosse mise à jour avec mise en cache du nombre d'articles lus/non-lus

* Mise en cache du nombre d'articles lus et non-lus par flux, via
`f.cache_nbEntries, f.cache_nbUnreads` pour de biens meilleures
performances
* Implémente https://github.com/marienfressinaud/FreshRSS/issues/268
* Révision de la plupart des requêtes de modification en conséquence
* En cas d'affichage `not_read`, évite de faire une requête si on sait
déjà qu'il n'y a pas d'article non lu et fait directement un affichage
`all`.
* Appelle `cleanOldEntries` seulement une fois de temps en temps
aléatoirement (1 fois sur 30 actuellement) pour économiser les
ressources, et avant les insertions pour plus de robustesse.
* Utilisation des transactions lors de mises à jour multiples et liées
* Lors de requêtes de modifications, retourne le nombre de lignes
impactées plutôt qu'un booléen en cas de succès
* Suppression de code oublié relatif à is_public qui n'est plus utilisé
Alexandre Alapetite 12 vuotta sitten
vanhempi
commit
32306a78d2

+ 5 - 9
app/controllers/entryController.php

@@ -43,28 +43,27 @@ class entryController extends ActionController {
 		$entryDAO = new EntryDAO ();
 		if ($id == false) {
 			if (!$get) {
-				$entryDAO->markReadEntries ($is_read, $dateMax);
+				$entryDAO->markReadEntries ($dateMax);
 			} else {
 				$typeGet = $get[0];
 				$get = substr ($get, 2);
 
 				if ($typeGet == 'c') {
-					$entryDAO->markReadCat ($get, $is_read, $dateMax);
+					$entryDAO->markReadCat ($get, $dateMax);
 					$this->params = array ('get' => $nextGet); 
 				} elseif ($typeGet == 'f') {
-					$entryDAO->markReadFeed ($get, $is_read, $dateMax);
+					$entryDAO->markReadFeed ($get, $dateMax);
 					$this->params = array ('get' => $nextGet);
 				}
 			}
 
-			// notif
 			$notif = array (
 				'type' => 'good',
 				'content' => Translate::t ('feeds_marked_read')
 			);
 			Session::_param ('notification', $notif);
 		} else {
-			$entryDAO->updateEntry ($id, array ('is_read' => $is_read));
+			$entryDAO->markRead ($id, $is_read);
 		}
 	}
 
@@ -74,10 +73,7 @@ class entryController extends ActionController {
 		$id = Request::param ('id');
 		if ($id) {
 			$entryDAO = new EntryDAO ();
-			$values = array (
-				'is_favorite' => (bool)(Request::param ('is_favorite')),
-			);
-			$entryDAO->updateEntry ($id, $values);
+			$entryDAO->markFavorite ($id, Request::param ('is_favorite'));
 		}
 	}
 

+ 19 - 3
app/controllers/feedController.php

@@ -33,6 +33,7 @@ class feedController extends ActionController {
 			$pass = Request::param ('password');
 			$params = array ();
 
+			$transactionStarted = false;
 			try {
 				$feed = new Feed ($url);
 				$feed->_category ($cat);
@@ -79,6 +80,8 @@ class feedController extends ActionController {
 					$nb_month_old = $this->view->conf->oldEntries ();
 					$date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old);
 
+					$transactionStarted = true;
+					$feedDAO->beginTransaction ();
 					// on ajoute les articles en masse sans vérification
 					foreach ($entries as $entry) {
 						if ($entry->date (true) >= $date_min ||
@@ -87,6 +90,9 @@ class feedController extends ActionController {
 							$entryDAO->addEntry ($values);
 						}
 					}
+					$feedDAO->updateLastUpdate ($feed->id ());
+					$feedDAO->commit ();
+					$transactionStarted = false;
 
 					// ok, ajout terminé
 					$notif = array (
@@ -121,6 +127,9 @@ class feedController extends ActionController {
 				);
 				Session::_param ('notification', $notif);
 			}
+			if ($transactionStarted) {
+				$feedDAO->rollBack ();
+			}
 
 			Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true);
 		}
@@ -149,6 +158,13 @@ class feedController extends ActionController {
 		// on calcule la date des articles les plus anciens qu'on accepte
 		$nb_month_old = $this->view->conf->oldEntries ();
 		$date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old);
+		if (rand(0, 30) === 1) {
+			Minz_Log::record ('CleanOldEntries', Minz_Log::NOTICE);	//TODO: Remove
+			if ($entryDAO->cleanOldEntries ($date_min) > 0) {
+				Minz_Log::record ('UpdateCachedValues', Minz_Log::NOTICE);	//TODO: Remove
+				$feedDAO->updateCachedValues ();
+			}
+		}
 
 		$i = 0;
 		$flux_update = 0;
@@ -165,6 +181,7 @@ class feedController extends ActionController {
 				// car demanderait plus de ressources
 				// La BDD refusera l'ajout de son côté car l'id doit être
 				// unique
+				$feedDAO->beginTransaction ();
 				foreach ($entries as $entry) {
 					if ((!isset ($existingIds[$entry->id ()])) &&
 						($entry->date (true) >= $date_min ||
@@ -176,10 +193,11 @@ class feedController extends ActionController {
 
 				// on indique que le flux vient d'être mis à jour en BDD
 				$feedDAO->updateLastUpdate ($feed->id ());
+				$feedDAO->commit ();
 				$flux_update++;
 			} catch (FeedException $e) {
 				Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
-				$feedDAO->isInError ($feed->id ());
+				$feedDAO->updateLastUpdate ($feed->id (), 1);
 			}
 
 			// On arrête à 10 flux pour ne pas surcharger le serveur
@@ -190,8 +208,6 @@ class feedController extends ActionController {
 			}
 		}
 
-		$entryDAO->cleanOldEntries ($nb_month_old);
-
 		$url = array ();
 		if ($flux_update === 1) {
 			// on a mis un seul flux à jour

+ 26 - 1
app/controllers/indexController.php

@@ -90,6 +90,30 @@ class indexController extends ActionController {
 			$nb = Request::param ('nb', $this->view->conf->postsPerPage ());
 			$first = Request::param ('next', '');
 
+			if ($state === 'not_read') {	//Any unread article in this category at all?
+				switch ($type['type']) {
+					case 'all':
+						$hasUnread = $this->view->nb_not_read > 0;
+						break;
+					case 'favoris':
+						$hasUnread = $this->view->nb_favorites['unread'] > 0;
+						break;
+					case 'c':
+						$hasUnread = (!isset($this->view->cat_aside[$type['id']])) || ($this->view->cat_aside[$type['id']]->nbNotRead() > 0);
+						break;
+					case 'f':
+						$myFeed = HelperCategory::findFeed($this->view->cat_aside, $type['id']);
+						$hasUnread = ($myFeed === null) || ($myFeed->nbNotRead() > 0);
+						break;
+					default:
+						$hasUnread = true;
+						break;
+				}
+				if (!$hasUnread) {
+					$this->view->state = $state = 'all';
+				}
+			}
+
 			try {
 				// EntriesGetter permet de déporter la complexité du filtrage
 				$getter = new EntriesGetter ($type, $state, $filter, $order, $nb, $first);
@@ -98,7 +122,8 @@ class indexController extends ActionController {
 
 				// Si on a récupéré aucun article "non lus"
 				// on essaye de récupérer tous les articles
-				if ($state == 'not_read' && $entries->isEmpty ()) {
+				if ($state === 'not_read' && $entries->isEmpty ()) {	//TODO: Remove in v0.8
+					Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::NOTICE);	//TODO: Consider adding a Minz_Log::DEBUG level
 					$this->view->state = 'all';
 					$getter->_state ('all');
 					$getter->execute ();

+ 4 - 7
app/models/Category.php

@@ -100,7 +100,7 @@ class CategoryDAO extends Model_pdo {
 		);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -119,7 +119,7 @@ class CategoryDAO extends Model_pdo {
 		);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -134,7 +134,7 @@ class CategoryDAO extends Model_pdo {
 		$values = array ($id);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -179,12 +179,9 @@ class CategoryDAO extends Model_pdo {
 		if ($prePopulateFeeds) {
 			$sql = 'SELECT c.id AS c_id, c.name AS c_name, '
 			     . ($details ? 'c.color AS c_color, ' : '')
-			     . 'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbNotRead, '
-			     . 'COUNT(e.id) AS nbEntries, '
-			     . ($details ? 'f.* ' : 'f.id, f.name, f.website, f.priority, f.error ')
+			     . ($details ? 'f.* ' : 'f.id, f.name, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ')
 			     . 'FROM ' . $this->prefix . 'category c '
 			     . 'LEFT OUTER JOIN ' . $this->prefix . 'feed f ON f.category = c.id '
-			     . 'LEFT OUTER JOIN ' . $this->prefix . 'entry e ON e.id_feed = f.id '
 			     . 'GROUP BY f.id '
 			     . 'ORDER BY c.name, f.name';
 			$stm = $this->bd->prepare ($sql);

+ 0 - 8
app/models/EntriesGetter.php

@@ -117,14 +117,6 @@ class EntriesGetter {
 				$sqlLimit
 			);
 			break;
-		case 'public':
-			list ($this->entries, $this->next) = $entryDAO->listPublic (
-				$this->state,
-				$this->order,
-				$this->first,
-				$sqlLimit
-			);
-			break;
 		case 'c':
 			list ($this->entries, $this->next) = $entryDAO->listByCategory (
 				$this->type['id'],

+ 176 - 52
app/models/Entry.php

@@ -213,17 +213,17 @@ class EntryDAO extends Model_pdo {
 		);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			if ((int)($info[0] / 1000) !== 23) {	//Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
-				Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2], Minz_Log::NOTICE);	//TODO: Consider adding a Minz_Log::DEBUG level
+				Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2], Minz_Log::ERROR);
 			}
 			return false;
 		}
 	}
 
-	public function updateEntry ($id, $valuesTmp) {
+	/*public function updateEntry ($id, $valuesTmp) {
 		if (isset ($valuesTmp['content'])) {
 			$valuesTmp['content'] = base64_encode (gzdeflate (serialize ($valuesTmp['content'])));
 		}
@@ -243,73 +243,201 @@ class EntryDAO extends Model_pdo {
 		$values[] = $id;
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
 			return false;
 		}
-	}
-
-	public function markReadEntries ($read, $dateMax = 0) {
-		$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id SET is_read = ? WHERE priority > 0';
-
-		$values = array ($read);
-		if ($dateMax > 0) {
-			$sql .= ' AND date < ?';
-			$values[] = $dateMax;
-		}
-
+	}*/
+	public function markFavorite ($id, $is_favorite = true) {
+		$sql = 'UPDATE ' . $this->prefix . 'entry e '
+		     . 'SET e.is_favorite = ? '
+		     . 'WHERE e.id=?';
+		$values = array ($is_favorite ? 1 : 0, $id);
 		$stm = $this->bd->prepare ($sql);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
 			return false;
 		}
 	}
-	public function markReadCat ($id, $read, $dateMax = 0) {
-		$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id SET is_read = ? WHERE category = ?';
-
-		$values = array ($read, $id);
-		if ($dateMax > 0) {
-			$sql .= ' AND date < ?';
-			$values[] = $dateMax;
-		}
-
+	public function markRead ($id, $is_read = true) {
+		$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id '
+		     . 'SET e.is_read = ?,'
+		     . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 '
+		     . 'WHERE e.id=?';
+		$values = array ($is_read ? 1 : 0, $id);
 		$stm = $this->bd->prepare ($sql);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
 			return false;
 		}
 	}
-	public function markReadFeed ($id, $read, $dateMax = 0) {
-		$sql = 'UPDATE ' . $this->prefix . 'entry SET is_read = ? WHERE id_feed = ?';
+	public function markReadEntries ($dateMax = 0) {
+		if ($dateMax === 0) {
+			$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+			     . 'WHERE e.is_read = 0 AND f.priority > 0';
+			$stm = $this->bd->prepare ($sql);
+			if ($stm && $stm->execute ()) {
+				return $stm->rowCount();
+			} else {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				return false;
+			}
+		} else {
+			$this->bd->beginTransaction ();
+
+			$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1 '
+			     . 'WHERE e.is_read = 0 AND e.date < ? AND f.priority > 0';
+			$values = array ($dateMax);
+			$stm = $this->bd->prepare ($sql);
+			if (!($stm && $stm->execute ($values))) {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				$this->bd->rollBack ();
+				return false;
+			}
+			$affected = $stm->rowCount();
+
+			if ($affected > 0) {
+				$sql = 'UPDATE freshrss_feed f '
+			     . 'LEFT OUTER JOIN ('
+			     .	'SELECT e.id_feed, '
+			     .	'COUNT(*) AS nbUnreads '
+			     .	'FROM freshrss_entry e '
+			     .	'WHERE e.is_read = 0 '
+			     .	'GROUP BY e.id_feed'
+			     . ') x ON x.id_feed=f.id '
+			     . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0)';
+				$stm = $this->bd->prepare ($sql);
+				if (!($stm && $stm->execute ())) {
+					$info = $stm->errorInfo();
+					Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+					$this->bd->rollBack ();
+					return false;
+				}
+			}
 
-		$values = array ($read, $id);
-		if ($dateMax > 0) {
-			$sql .= ' AND date < ?';
-			$values[] = $dateMax;
+			$this->bd->commit ();
+			return $affected;
 		}
+	}
+	public function markReadCat ($id, $dateMax = 0) {
+		if ($dateMax === 0) {
+			$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+			     . 'WHERE f.category = ? AND e.is_read = 0';
+			$values = array ($id);
+			$stm = $this->bd->prepare ($sql);
+			if ($stm && $stm->execute ($values)) {
+				return $stm->rowCount();
+			} else {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				return false;
+			}
+		} else {
+			$this->bd->beginTransaction ();
+
+			$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1 '
+			     . 'WHERE f.category = ? AND e.is_read = 0 AND e.date < ?';
+			$values = array ($id, $dateMax);
+			$stm = $this->bd->prepare ($sql);
+			if (!($stm && $stm->execute ($values))) {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				$this->bd->rollBack ();
+				return false;
+			}
+			$affected = $stm->rowCount();
+
+			if ($affected > 0) {
+				$sql = 'UPDATE freshrss_feed f '
+			     . 'LEFT OUTER JOIN ('
+			     .	'SELECT e.id_feed, '
+			     .	'COUNT(*) AS nbUnreads '
+			     .	'FROM freshrss_entry e '
+			     .	'WHERE e.is_read = 0 '
+			     .	'GROUP BY e.id_feed'
+			     . ') x ON x.id_feed=f.id '
+			     . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
+			     . 'WHERE f.category = ?';
+				$values = array ($id);
+				$stm = $this->bd->prepare ($sql);
+				if (!($stm && $stm->execute ($values))) {
+					$info = $stm->errorInfo();
+					Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+					$this->bd->rollBack ();
+					return false;
+				}
+			}
 
-		$stm = $this->bd->prepare ($sql);
-
-		if ($stm && $stm->execute ($values)) {
-			return true;
+			$this->bd->commit ();
+			return $affected;
+		}
+	}
+	public function markReadFeed ($id, $dateMax = 0) {
+		if ($dateMax === 0) {
+			$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+			     . 'WHERE f.id=? AND e.is_read = 0';
+			$values = array ($id);
+			$stm = $this->bd->prepare ($sql);
+			if ($stm && $stm->execute ($values)) {
+				return $stm->rowCount();
+			} else {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				return false;
+			}
 		} else {
-			$info = $stm->errorInfo();
-			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
-			return false;
+			$this->bd->beginTransaction ();
+
+			$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1 '
+			     . 'WHERE f.id=? AND e.is_read = 0 AND e.date < ?';
+			$values = array ($id, $dateMax);
+			$stm = $this->bd->prepare ($sql);
+			if (!($stm && $stm->execute ($values))) {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				$this->bd->rollBack ();
+				return false;
+			}
+			$affected = $stm->rowCount();
+
+			if ($affected > 0) {
+				$sql = 'UPDATE ' . $this->prefix . 'feed f '
+				     . 'SET f.cache_nbUnreads=f.cache_nbUnreads-' . $affected
+				     . ' WHERE f.id=?';
+				$values = array ($id);
+				$stm = $this->bd->prepare ($sql);
+				if (!($stm && $stm->execute ($values))) {
+					$info = $stm->errorInfo();
+					Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+					$this->bd->rollBack ();
+					return false;
+				}
+			}
+
+			$this->bd->commit ();
+			return $affected;
 		}
 	}
 
-	public function updateEntries ($valuesTmp) {
+	/*public function updateEntries ($valuesTmp) {
 		if (isset ($valuesTmp['content'])) {
 			$valuesTmp['content'] = base64_encode (gzdeflate (serialize ($valuesTmp['content'])));
 		}
@@ -328,25 +456,24 @@ class EntryDAO extends Model_pdo {
 		}
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
 			return false;
 		}
-	}
+	}*/
 
-	public function cleanOldEntries ($nb_month) {
-		$date = 60 * 60 * 24 * 30 * $nb_month;
+	public function cleanOldEntries ($date_min) {
 		$sql = 'DELETE e.* FROM ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id WHERE e.date <= ? AND e.is_favorite = 0 AND f.keep_history = 0';
 		$stm = $this->bd->prepare ($sql);
 
 		$values = array (
-			time () - $date
+			$date_min
 		);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -393,16 +520,16 @@ class EntryDAO extends Model_pdo {
 	}
 
 	private function listWhere ($where, $state, $order, $limitFromId = '', $limitCount = '', $values = array ()) {
-		if ($state == 'not_read') {
+		if ($state === 'not_read') {
 			$where .= ' AND is_read = 0';
-		} elseif ($state == 'read') {
+		} elseif ($state === 'read') {
 			$where .= ' AND is_read = 1';
 		}
 		if (!empty($limitFromId)) {	//TODO: Consider using LPAD(e.date, 11)	//CONCAT is for cases when many entries have the same date
 			$where .= ' AND CONCAT(e.date, e.id) ' . ($order === 'low_to_high' ? '<=' : '>=') . ' (SELECT CONCAT(s.date, s.id) FROM ' . $this->prefix . 'entry s WHERE s.id = "' . $limitFromId . '")';
 		}
 
-		if ($order == 'low_to_high') {
+		if ($order === 'low_to_high') {
 			$order = ' DESC';
 		} else {
 			$order = '';
@@ -430,9 +557,6 @@ class EntryDAO extends Model_pdo {
 	public function listFavorites ($state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') {
 		return $this->listWhere (' WHERE is_favorite = 1', $state, $order, $limitFromId, $limitCount);
 	}
-	public function listPublic ($state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') {
-		return $this->listWhere (' WHERE is_public = 1', $state, $order, $limitFromId, $limitCount);
-	}
 	public function listByCategory ($cat, $state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') {
 		return $this->listWhere (' WHERE category = ?', $state, $order, $limitFromId, $limitCount, array ($cat));
 	}

+ 41 - 43
app/models/Feed.php

@@ -337,7 +337,7 @@ class FeedDAO extends Model_pdo {
 		);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -365,7 +365,7 @@ class FeedDAO extends Model_pdo {
 		$values[] = $id;
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -373,34 +373,23 @@ class FeedDAO extends Model_pdo {
 		}
 	}
 
-	public function updateLastUpdate ($id) {
-		$sql = 'UPDATE ' . $this->prefix . 'feed SET lastUpdate=?, error=0 WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			time (),
-			$id
-		);
+	public function updateLastUpdate ($id, $inError = 0) {
+		$sql = 'UPDATE ' . $this->prefix . 'feed f '	//2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
+		     . 'SET f.cache_nbEntries=(SELECT COUNT(e1.id) FROM ' . $this->prefix . 'entry e1 WHERE e1.id_feed=f.id),'
+		     . 'f.cache_nbUnreads=(SELECT COUNT(e2.id) FROM ' . $this->prefix . 'entry e2 WHERE e2.id_feed=f.id AND e2.is_read=0),'
+		     . 'lastUpdate=?, error=? '
+		     . 'WHERE f.id=?';
 
-		if ($stm && $stm->execute ($values)) {
-			return true;
-		} else {
-			$info = $stm->errorInfo();
-			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
-			return false;
-		}
-	}
-
-	public function isInError ($id) {
-		$sql = 'UPDATE ' . $this->prefix . 'feed SET error=1 WHERE id=?';
 		$stm = $this->bd->prepare ($sql);
 
 		$values = array (
-			$id
+			time (),
+			$inError,
+			$id,
 		);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -424,7 +413,7 @@ class FeedDAO extends Model_pdo {
 		);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -439,7 +428,7 @@ class FeedDAO extends Model_pdo {
 		$values = array ($id);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -453,7 +442,7 @@ class FeedDAO extends Model_pdo {
 		$values = array ($id);
 
 		if ($stm && $stm->execute ($values)) {
-			return true;
+			return $stm->rowCount();
 		} else {
 			$info = $stm->errorInfo();
 			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
@@ -521,15 +510,6 @@ class FeedDAO extends Model_pdo {
 		return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
 	}
 
-	public function count () {	//Is this used?
-		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'feed';
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ();
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-
-		return $res[0]['count'];
-	}
-
 	public function countEntries ($id) {
 		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'entry WHERE id_feed=?';
 		$stm = $this->bd->prepare ($sql);
@@ -539,8 +519,8 @@ class FeedDAO extends Model_pdo {
 
 		return $res[0]['count'];
 	}
-	public function countNotRead ($id) {	//Is this used?
-		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'entry WHERE is_read=0 AND id_feed=?';
+	public function countNotRead ($id) {
+		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'entry WHERE id_feed=? AND is_read=0';
 		$stm = $this->bd->prepare ($sql);
 		$values = array ($id);
 		$stm->execute ($values);
@@ -548,6 +528,28 @@ class FeedDAO extends Model_pdo {
 
 		return $res[0]['count'];
 	}
+	public function updateCachedValues () {	//For one single feed, call updateLastUpdate($id)
+		$sql = 'UPDATE freshrss_feed f '
+		     . 'INNER JOIN ('
+		     .	'SELECT e.id_feed, '
+		     .	'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, '
+		     .	'COUNT(e.id) AS nbEntries '
+		     .	'FROM freshrss_entry e '
+		     .	'GROUP BY e.id_feed'
+		     . ') x ON x.id_feed=f.id '
+		     . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($feed_id);
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
 }
 
 class HelperFeed {
@@ -577,12 +579,8 @@ class HelperFeed {
 			$myFeed->_httpAuth (isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : '');
 			$myFeed->_error ($dao['error']);
 			$myFeed->_keepHistory (isset($dao['keep_history']) ? $dao['keep_history'] : '');
-			if (isset ($dao['nbNotRead'])) {
-				$myFeed->_nbNotRead ($dao['nbNotRead']);
-			}
-			if (isset ($dao['nbEntries'])) {
-				$myFeed->_nbEntries ($dao['nbEntries']);
-			}
+			$myFeed->_nbNotRead ($dao['cache_nbUnreads']);
+			$myFeed->_nbEntries ($dao['cache_nbEntries']);
 			if (isset ($dao['id'])) {
 				$myFeed->_id ($dao['id']);
 			}

+ 10 - 0
lib/minz/dao/Model_pdo.php

@@ -71,6 +71,16 @@ class Model_pdo {
 			);
 		}
 	}
+
+	public function beginTransaction() {
+		$this->bd->beginTransaction();
+	}
+	public function commit() {
+		$this->bd->commit();
+	}
+	public function rollBack() {
+		$this->bd->rollBack();
+	}
 }
 
 class FreshPDO extends PDO {

+ 13 - 11
public/install.php

@@ -15,7 +15,7 @@ define ('SQL_REQ_CAT', 'CREATE TABLE IF NOT EXISTS `%scategory` (
   `name` varchar(255) NOT NULL,
   `color` char(7) NOT NULL,
   PRIMARY KEY (`id`),
-  INDEX (`name`)
+  INDEX (`name`)	//v0.7
 );');
 
 define ('SQL_REQ_FEED', 'CREATE TABLE IF NOT EXISTS `%sfeed` (
@@ -26,16 +26,18 @@ define ('SQL_REQ_FEED', 'CREATE TABLE IF NOT EXISTS `%sfeed` (
   `website` varchar(255) NOT NULL,
   `description` text NOT NULL,
   `lastUpdate` int(11) NOT NULL,
-  `priority` tinyint(2) NOT NULL DEFAULT \'10\',
+  `priority` tinyint(2) NOT NULL DEFAULT 10,
   `pathEntries` varchar(511) DEFAULT NULL,
   `httpAuth` varchar(511) DEFAULT NULL,
-  `error` boolean NOT NULL DEFAULT \'0\',
-  `keep_history` boolean NOT NULL DEFAULT \'0\',
+  `error` boolean NOT NULL DEFAULT 0,
+  `keep_history` boolean NOT NULL DEFAULT 0,
+  `cache_nbEntries` int NOT NULL DEFAULT 0,	//v0.7
+  `cache_nbUnreads` int NOT NULL DEFAULT 0,	//v0.7
   PRIMARY KEY (`id`),
   FOREIGN KEY (`category`) REFERENCES %scategory(id) ON DELETE SET NULL ON UPDATE CASCADE,
-  INDEX (`name`),
-  INDEX (`priority`),
-  INDEX (`keep_history`)
+  INDEX (`name`),	//v0.7
+  INDEX (`priority`),	//v0.7
+  INDEX (`keep_history`)	//v0.7
 );');
 
 define ('SQL_REQ_ENTRY', 'CREATE TABLE IF NOT EXISTS `%sentry` (
@@ -46,14 +48,14 @@ define ('SQL_REQ_ENTRY', 'CREATE TABLE IF NOT EXISTS `%sentry` (
   `content` text NOT NULL,
   `link` varchar(1023) NOT NULL,
   `date` int(11) NOT NULL,
-  `is_read` boolean NOT NULL DEFAULT \'0\',
-  `is_favorite` boolean NOT NULL DEFAULT \'0\',
+  `is_read` boolean NOT NULL DEFAULT 0,
+  `is_favorite` boolean NOT NULL DEFAULT 0,
   `id_feed` char(6) NOT NULL,
   `tags` varchar(1023) NOT NULL,
   PRIMARY KEY (`id`),
   FOREIGN KEY (`id_feed`) REFERENCES %sfeed(id) ON DELETE CASCADE ON UPDATE CASCADE,
-  INDEX (`is_favorite`),
-  INDEX (`is_read`)
+  INDEX (`is_favorite`),	//v0.7
+  INDEX (`is_read`)	//v0.7
 );');