1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-23 09:02:41 +00:00

Rewrite article marking procedure

- Marking of a simple context is now done with one query; the "touched"
field is no longer needed
- Union contexts are now handled, with some quirks; these quirks can be
worked around later if needed
This commit is contained in:
J. King 2023-02-10 14:59:11 -05:00
parent b1d2611e5b
commit 212d842e05

View file

@ -1887,6 +1887,7 @@ class Database {
* @param bool $updateTimestamp Whether to also update the timestamp. This should only be false if a mark is changed as a result of an automated action not taken by the user * @param bool $updateTimestamp Whether to also update the timestamp. This should only be false if a mark is changed as a result of an automated action not taken by the user
*/ */
public function articleMark(string $user, array $data, RootContext $context = null, bool $updateTimestamp = true): int { public function articleMark(string $user, array $data, RootContext $context = null, bool $updateTimestamp = true): int {
// normalize requested marks
$data = [ $data = [
'read' => $data['read'] ?? null, 'read' => $data['read'] ?? null,
'starred' => $data['starred'] ?? null, 'starred' => $data['starred'] ?? null,
@ -1894,114 +1895,94 @@ class Database {
'note' => $data['note'] ?? null, 'note' => $data['note'] ?? null,
]; ];
if (!isset($data['read']) && !isset($data['starred']) && !isset($data['hidden']) && !isset($data['note'])) { if (!isset($data['read']) && !isset($data['starred']) && !isset($data['hidden']) && !isset($data['note'])) {
// no changes were requested
return 0; return 0;
} }
$context = $context ?? new Context; // begin a transaction
$tr = $this->begin(); $tr = $this->begin();
$context = $context ?? new Context;
if ($context instanceof UnionContext) {
$out = 0; $out = 0;
if ($data['read'] || $data['starred'] || $data['hidden'] || strlen($data['note'] ?? "")) { // if we were provided a union context, mark each context in series;
// first prepare a query to insert any missing marks rows for the articles we want to mark // this is atomic (due to the transaction already begun), but may
// but only insert new mark records if we're setting at least one "positive" mark // result in multiple timestamps as well as an inaccurate output
$q = $this->articleQuery($user, $context, ["id", "subscription", "note"]); // integer as articles may be in multiple contexts
$q->setWhere("arsse_marks.starred is null"); // null means there is no marks row for the article, because the column is defined not-null // TODO: The above quirks could be fixed by resolving the union
$this->db->prepare("INSERT INTO arsse_marks(article,subscription,note) ".$q->getQuery(), $q->getTypes())->run($q->getValues()); // context to a single list of article IDs noting which are
// valid read marks (if editions were selected in any of the child
// contexts); this functionality is not needed yet, however
foreach ($context as $c) {
$out += $this->articleMark($user, $data, $c, $updateTimestamp);
} }
if (isset($data['read']) && (isset($data['starred']) || isset($data['hidden']) || isset($data['note'])) && ($context->edition() || $context->editions())) { $tr->commit();
// if marking by edition both read and something else, do separate marks for starred and note than for read return $out;
// marking as read is ignored if the edition is not the latest, but the same is not true of the other two marks
$this->db->query("UPDATE arsse_marks set touched = 0 where touched <> 0");
// set read marks
$subq = $this->articleQuery($user, $context, ["id", "subscription"]);
$subq->setWhere("arsse_marks.read <> coalesce(?,arsse_marks.read)", "bool", $data['read']);
$q = new Query(
"WITH RECURSIVE
target_articles(article, subscription) as (
{$subq->getQuery()}
)
update arsse_marks
set
\"read\" = ?,
touched = 1
where
article in (select article from target_articles)
and subscription in (select distinct subscription from target_articles)",
[$subq->getTypes(), "bool"],
[$subq->getValues(), $data['read']]
);
$this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues());
// get the articles associated with the requested editions
if ($context->edition()) {
$context->article($this->articleValidateEdition($user, $context->edition)['article'])->edition(null);
} else {
$context->articles($this->editionArticle(...$context->editions))->editions(null);
} }
// set starred, hidden, and/or note marks (unless all requested editions actually do not exist) // prepare the subquery which selects the articles to act on
if ($context->article || $context->articles) { $subq = $this->articleQuery($user, $context);
$setData = array_filter($data, function($v) { $subq->setWhere("(arsse_articles.note <> coalesce(?,arsse_articles.note) or arsse_articles.starred <> coalesce(?,arsse_articles.starred) or arsse_articles.read <> coalesce(?,arsse_articles.read) or arsse_articles.hidden <> coalesce(?,arsse_articles.hidden))", ["str", "bool", "bool", "bool"], [$data['note'], $data['starred'], $data['read'], $data['hidden']]);
return isset($v); // if we're marking as read/unread by edition, we have to use a different query so that we only mark read/unread if the edition is the latest for the article
if (isset($data['read']) && ($context->edition() || $context->editions())) {
// set up the "SET" clause for the update
$setData = array_filter($data, function($v, $k) {
// filter out anyhing with a value of null (no change), as well as the "rea" key as it required special handling
return isset($v) && $k !== "read";
}); });
[$set, $setTypes, $setValues] = $this->generateSet($setData, ['starred' => "bool", 'hidden' => "bool", 'note' => "str"]); [$set, $setTypes, $setValues] = $this->generateSet($setData, ['read' => "bool", 'starred' => "bool", 'hidden' => "bool", 'note' => "str"]);
$subq = $this->articleQuery($user, $context, ["id", "subscription"]); $set = $set ? "$set, " : "";
$subq->setWhere("(arsse_marks.note <> coalesce(?,arsse_marks.note) or arsse_marks.starred <> coalesce(?,arsse_marks.starred) or arsse_marks.hidden <> coalesce(?,arsse_marks.hidden))", ["str", "bool", "bool"], [$data['note'], $data['starred'], $data['hidden']]); $set .= "read = case when id in (select article from valid_read) then ? else \"read\" end";
$q = new Query( $setTypes[] = "bool";
$setValues[] = $data['read'];
if ($updateTimestamp) {
$set .= ", modified = CURRENT_TIMESTAMP";
}
// prepare the rest of the query
[$inClause, $inTypes, $inValues] = $this->generateIn($context->editions ?: (array) $context->edition, "int");
$out = $this->db->prepare(
"WITH RECURSIVE "WITH RECURSIVE
target_articles(article, subscription) as ( target_articles(article) as (
{$subq->getQuery()} {$subq->getQuery()}
),
valid_read as (
select article from (
select max(id) as edition, article from arsse_editions where id in ($inClause) group by article
) as selected_editions join (
SELECT max(id) as edition, article from arsse_editions group by article
) as latest_editions on selected_editions.edition = latest_editions.edition
) )
update arsse_marks update arsse_articles
set set
touched = 1,
$set $set
where where
article in (select article from target_articles) article in (select article from target_articles)",
and subscription in (select distinct subscription from target_articles)", $subq->getTypes(), $inTypes, $setTypes
[$subq->getTypes(), $setTypes], )->run(
[$subq->getValues(), $setValues] $subq->getValues(), $inValues, $setValues
); );
$this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues());
}
// finally set the modification date for all touched marks and return the number of affected marks
if ($updateTimestamp) {
$out = $this->db->query("UPDATE arsse_marks set modified = CURRENT_TIMESTAMP, touched = 0 where touched = 1")->changes();
} else { } else {
$out = $this->db->query("UPDATE arsse_marks set touched = 0 where touched = 1")->changes(); // set up the "SET" clause for the update
}
} else {
if (!isset($data['read']) && ($context->edition() || $context->editions())) {
// get the articles associated with the requested editions
if ($context->edition()) {
$context->article($this->articleValidateEdition($user, $context->edition)['article'])->edition(null);
} else {
$context->articles($this->editionArticle(...$context->editions))->editions(null);
}
if (!$context->article && !$context->articles) {
return 0;
}
}
$setData = array_filter($data, function($v) { $setData = array_filter($data, function($v) {
// filter out anyhing with a value of null (no change)
return isset($v); return isset($v);
}); });
[$set, $setTypes, $setValues] = $this->generateSet($setData, ['read' => "bool", 'starred' => "bool", 'hidden' => "bool", 'note' => "str"]); [$set, $setTypes, $setValues] = $this->generateSet($setData, ['read' => "bool", 'starred' => "bool", 'hidden' => "bool", 'note' => "str"]);
if ($updateTimestamp) { if ($updateTimestamp) {
$set .= ", modified = CURRENT_TIMESTAMP"; $set .= ", modified = CURRENT_TIMESTAMP";
} }
$subq = $this->articleQuery($user, $context, ["id", "subscription"]); // prepare the rest of the query
$subq->setWhere("(arsse_marks.note <> coalesce(?,arsse_marks.note) or arsse_marks.starred <> coalesce(?,arsse_marks.starred) or arsse_marks.read <> coalesce(?,arsse_marks.read) or arsse_marks.hidden <> coalesce(?,arsse_marks.hidden))", ["str", "bool", "bool", "bool"], [$data['note'], $data['starred'], $data['read'], $data['hidden']]); $out = $this->db->prepare(
$q = new Query(
"WITH RECURSIVE "WITH RECURSIVE
target_articles(article, subscription) as ( target_articles(article) as (
{$subq->getQuery()} {$subq->getQuery()}
) )
update arsse_marks update arsse_articles
set set
$set $set
where where
article in (select article from target_articles) article in (select article from target_articles)",
and subscription in (select distinct subscription from target_articles)", $subq->getTypes(), $setTypes
[$subq->getTypes(), $setTypes], )->run(
[$subq->getValues(), $setValues] $subq->getValues(), $setValues
); );
$out = $this->db->prepare($q->getQuery(), $q->getTypes())->run($q->getValues())->changes();
} }
$tr->commit(); $tr->commit();
return $out; return $out;
@ -2022,7 +2003,7 @@ class Database {
coalesce(sum(abs(\"read\" - 1)),0) as unread, coalesce(sum(abs(\"read\" - 1)),0) as unread,
coalesce(sum(\"read\"),0) as \"read\" coalesce(sum(\"read\"),0) as \"read\"
FROM ( FROM (
select \"read\" from arsse_marks where starred = 1 and hidden <> 1 and subscription in (select id from arsse_subscriptions where owner = ?) select \"read\" from arsse_articles where starred = 1 and hidden <> 1 and subscription in (select id from arsse_subscriptions where owner = ?)
) as starred_data", ) as starred_data",
"str" "str"
)->run($user)->getRow(); )->run($user)->getRow();