diff --git a/lib/Database.php b/lib/Database.php
index 13587f6f..5f6e2469 100644
--- a/lib/Database.php
+++ b/lib/Database.php
@@ -619,9 +619,9 @@ class Database {
if(!$context) $context = new Context;
$q = new Query(
"SELECT
- arsse_articles.id,
- arsse_articles.url,
- title,author,content,feed,guid,
+ arsse_articles.id as id,
+ arsse_articles.url as url,
+ title,author,content,guid,
DATEFORMAT(?, published) as published,
DATEFORMAT(?, edited) as edited,
DATEFORMAT(?, max(
@@ -630,7 +630,8 @@ class Database {
)) as modified,
NOT (SELECT count(*) from arsse_marks join user on user is owner where article is arsse_articles.id and read is 1) as unread,
(SELECT count(*) from arsse_marks join user on user is owner where article is arsse_articles.id and starred is 1) as starred,
- (SELECT max(id) from arsse_editions where article is arsse_articles.id) as latestEdition,
+ (SELECT max(id) from arsse_editions where article is arsse_articles.id) as edition,
+ subscribed_feeds.sub as subscription,
url_title_hash||':'||url_content_hash||':'||title_content_hash as fingerprint,
arsse_enclosures.url as media_url,
arsse_enclosures.type as media_type
@@ -639,7 +640,7 @@ class Database {
left join arsse_enclosures on arsse_enclosures.article is arsse_articles.id
",
"", // WHERE clause
- "latestEdition".(!$context->reverse ? " desc" : ""), // ORDER BY clause
+ "edition".(!$context->reverse ? " desc" : ""), // ORDER BY clause
$context->limit,
$context->offset
);
@@ -648,31 +649,29 @@ class Database {
// if a subscription is specified, make sure it exists
$id = $this->subscriptionValidateId($user, $context->subscription)['feed'];
// add a basic CTE that will join in only the requested subscription
- $q->setCTE("subscribed_feeds(id) as (SELECT ?)", "int", $id);
+ $q->setCTE("subscribed_feeds(id,sub) as (SELECT ?,?)", ["int","int"], [$id,$context->subscription]);
} else if($context->folder()) {
// if a folder is specified, make sure it exists
$this->folderValidateId($user, $context->folder);
// if it does exist, add a common table expression to list it and its children so that we select from the entire subtree
$q->setCTE("folders(folder) as (SELECT ? union select id from arsse_folders join folders on parent is folder)", "int", $context->folder);
// add another CTE for the subscriptions within the folder
- $q->setCTE("subscribed_feeds(id) as (SELECT feed from arsse_subscriptions join user on user is owner join folders on arsse_subscription.folder is folders.folder)");
+ $q->setCTE("subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner join folders on arsse_subscriptions.folder is folders.folder)");
} else {
// otherwise add a CTE for all the user's subscriptions
- $q->setCTE("subscribed_feeds(id) as (SELECT feed from arsse_subscriptions join user on user is owner)");
+ $q->setCTE("subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner)");
}
// filter based on edition offset
- if($context->oldestEdition()) {
- $q->setWhere("latestEdition >= ?", "int", $context->oldestEdition);
- } else if($context->latestEdition()) {
- $q->setWhere("latestEdition <= ?", "int", $context->oldestEdition);
- }
+ if($context->oldestEdition()) $q->setWhere("edition >= ?", "int", $context->oldestEdition);
+ if($context->latestEdition()) $q->setWhere("edition <= ?", "int", $context->latestEdition);
// filter based on lastmod time
if($context->modifiedSince()) $q->setWhere("modified >= ?", "datetime", $context->modifiedSince);
+ if($context->notModifiedSince()) $q->setWhere("modified <= ?", "datetime", $context->notModifiedSince);
// filter for un/read and un/starred status if specified
if($context->unread()) $q->setWhere("unread is ?", "bool", $context->unread);
if($context->starred()) $q->setWhere("starred is ?", "bool", $context->starred);
// perform the query and return results
- return $this->db->prepare($q, "str", "str", "str")-run($this->dateFormatDefault, $this->dateFormatDefault, $this->dateFormatDefault);
+ return $this->db->prepare($q, "str", "str", "str")->run($this->dateFormatDefault, $this->dateFormatDefault, $this->dateFormatDefault);
}
public function articlePropertiesSet(string $user, array $data, Context $context = null): bool {
@@ -712,8 +711,11 @@ class Database {
$q = new Query(
"SELECT
arsse_articles.id as id,
- max(arsse_editions.id) as latestEdition
- (select count(*) from arsse_marks join user on user is owner where article is arsse_articles.id) as exists
+ max(arsse_editions.id) as edition
+ (select count(*) from arsse_marks join user on user is owner where article is arsse_articles.id) as exists,
+ max(modified,
+ coalesce((SELECT modified from arsse_marks join user on user is owner where article is arsse_articles.id),'')
+ ) as modified,
FROM arsse_articles
join subscribed_feeds on feed is subscribed_feeds.id
join arsse_editions on arsse_articles.id is arsse_editions.article
@@ -724,26 +726,27 @@ class Database {
// if a subscription is specified, make sure it exists
$id = $this->subscriptionValidateId($user, $context->subscription)['feed'];
// add a basic CTE that will join in only the requested subscription
- $q->setCTE("subscribed_feeds(id) as (SELECT ?)", "int", $id);
+ $q->setCTE("subscribed_feeds(id,sub) as (SELECT ?,?)", ["int","int"], [$id,$context->subscription]);
} else if($context->folder()) {
// if a folder is specified, make sure it exists
$this->folderValidateId($user, $context->folder);
// if it does exist, add a common table expression to list it and its children so that we select from the entire subtree
$q->setCTE("folders(folder) as (SELECT ? union select id from arsse_folders join folders on parent is folder)", "int", $context->folder);
// add another CTE for the subscriptions within the folder
- $q->setCTE("subscribed_feeds(id) as (SELECT feed from arsse_subscriptions join user on user is owner join folders on arsse_subscription.folder is folders.folder)");
+ $q->setCTE("subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner join folders on arsse_subscriptions.folder is folders.folder)");
} else {
// otherwise add a CTE for all the user's subscriptions
- $q->setCTE("subscribed_feeds(id) as (SELECT feed from arsse_subscriptions join user on user is owner)");
+ $q->setCTE("subscribed_feeds(id,sub) as (SELECT feed,id from arsse_subscriptions join user on user is owner)");
}
// filter for specific article or edition
if($context->article()) $q->setWhere("arsse_article.id is ?", "int", $context->article);
if($context->edition()) $q->setWhere("arsse_article.id is (SELECT article from arsse_editions where id is ?)", "int", $context->edition);
- // filter for un/read and un/starred status if specified
- if($context->unread()) $q->setWhere("unread is ?", "bool", $context->unread);
- if($context->starred()) $q->setWhere("starred is ?", "bool", $context->starred);
+ // filter based on edition offset
+ if($context->oldestEdition()) $q->setWhere("edition >= ?", "int", $context->oldestEdition);
+ if($context->latestEdition()) $q->setWhere("edition <= ?", "int", $context->latestEdition);
// filter based on lastmod time
if($context->modifiedSince()) $q->setWhere("modified >= ?", "datetime", $context->modifiedSince);
+ if($context->notModifiedSince()) $q->setWhere("modified <= ?", "datetime", $context->notModifiedSince);
// push the current query onto the CTE stack and execute the query we're actually interested in
$q->pushCTE(
"target_articles(id, exists)", // CTE table specification
diff --git a/tests/Db/SQLite3/Database/TestDatabaseArticleSQLite3.php b/tests/Db/SQLite3/Database/TestDatabaseArticleSQLite3.php
new file mode 100644
index 00000000..5da9784c
--- /dev/null
+++ b/tests/Db/SQLite3/Database/TestDatabaseArticleSQLite3.php
@@ -0,0 +1,9 @@
+ [
+ 'columns' => [
+ 'id' => 'str',
+ 'password' => 'str',
+ 'name' => 'str',
+ 'rights' => 'int',
+ ],
+ 'rows' => [
+ ["jane.doe@example.com", "", "Jane Doe", UserDriver::RIGHTS_NONE],
+ ["john.doe@example.com", "", "John Doe", UserDriver::RIGHTS_NONE],
+ ],
+ ],
+ 'arsse_folders' => [
+ 'columns' => [
+ 'id' => "int",
+ 'owner' => "str",
+ 'parent' => "int",
+ 'name' => "str",
+ ],
+ 'rows' => [
+ [1, "john.doe@example.com", null, "Technology"],
+ [2, "john.doe@example.com", 1, "Software"],
+ [3, "john.doe@example.com", 1, "Rocketry"],
+ [4, "jane.doe@example.com", null, "Politics"],
+ [5, "john.doe@example.com", null, "Politics"],
+ [6, "john.doe@example.com", 2, "Politics"],
+ ]
+ ],
+ 'arsse_feeds' => [
+ 'columns' => [
+ 'id' => "int",
+ 'url' => "str",
+ ],
+ 'rows' => [
+ [1,"http://example.com/1"],
+ [2,"http://example.com/2"],
+ [3,"http://example.com/3"],
+ [4,"http://example.com/4"],
+ [5,"http://example.com/5"],
+ [6,"http://example.com/6"],
+ [7,"http://example.com/7"],
+ [8,"http://example.com/8"],
+ [9,"http://example.com/9"],
+ [10,"http://example.com/10"],
+ [11,"http://example.com/11"],
+ [12,"http://example.com/12"],
+ [13,"http://example.com/13"],
+ [14,"http://example.com/14"],
+ [15,"http://example.com/15"],
+ [16,"http://example.com/16"],
+ [17,"http://example.com/17"],
+ [18,"http://example.com/18"],
+ [19,"http://example.com/19"],
+ [20,"http://example.com/20"],
+ ]
+ ],
+ 'arsse_subscriptions' => [
+ 'columns' => [
+ 'id' => "int",
+ 'owner' => "str",
+ 'feed' => "int",
+ 'folder' => "int",
+ ],
+ 'rows' => [
+ [1,"john.doe@example.com",1,null],
+ [2,"john.doe@example.com",2,null],
+ [3,"john.doe@example.com",3,1],
+ [4,"john.doe@example.com",4,6],
+ [5,"john.doe@example.com",10,5],
+ [6,"jane.doe@example.com",1,null],
+ [7,"jane.doe@example.com",12,null],/*
+ [8,"john.doe@example.com",1,null],
+ [9,"john.doe@example.com",1,null],
+ [10,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],
+ [1,"john.doe@example.com",1,null],*/
+ ]
+ ],
+ 'arsse_articles' => [
+ 'columns' => [
+ 'id' => "int",
+ 'feed' => "int",
+ 'modified' => "datetime",
+ 'url_title_hash' => "str",
+ 'url_content_hash' => "str",
+ 'title_content_hash' => "str",
+ ],
+ 'rows' => [] // filled by series setup
+ ],
+ 'arsse_enclosures' => [
+ 'columns' => [
+ 'article' => "int",
+ 'url' => "str",
+ 'type' => "str",
+ ],
+ 'rows' => []
+ ],
+ 'arsse_editions' => [
+ 'columns' => [
+ 'id' => "int",
+ 'article' => "int",
+ ],
+ 'rows' => [ // lower IDs are filled by series setup
+ [1001,20],
+ ]
+ ],
+ 'arsse_marks' => [
+ 'columns' => [
+ 'owner' => "str",
+ 'article' => "int",
+ 'read' => "bool",
+ 'starred' => "bool",
+ ],
+ 'rows' => [
+ ["john.doe@example.com",1,1,1],
+ ["john.doe@example.com",19,1,0],
+ ["john.doe@example.com",20,0,1],
+ ["jane.doe@example.com",20,1,0],
+ ]
+ ],
+ ];
+
+ function setUpSeries() {
+ for($a = 0, $b = 1; $b <= sizeof($this->data['arsse_feeds']['rows']); $b++) {
+ // add two generic articles per feed, and matching initial editions
+ $this->data['arsse_articles']['rows'][] = [++$a,$b,"2000-01-01T00:00:00Z","","",""];
+ $this->data['arsse_editions']['rows'][] = [$a,$a];
+ $this->data['arsse_articles']['rows'][] = [++$a,$b,"2010-01-01T00:00:00Z","","",""];
+ $this->data['arsse_editions']['rows'][] = [$a,$a];
+ }
+ $this->user = "john.doe@example.com";
+ }
+
+ function testListArticlesByContext() {
+ // get all items for user
+ $exp = [1,2,3,4,5,6,7,8,19,20];
+ $this->compareIds($exp, new Context);
+ // get items from a folder tree
+ $exp = [5,6,7,8];
+ $this->compareIds($exp, (new Context)->folder(1));
+ // get items from a leaf folder
+ $exp = [7,8];
+ $this->compareIds($exp, (new Context)->folder(6));
+ // get items from a single subscription
+ $exp = [19,20];
+ $this->compareIds($exp, (new Context)->subscription(5));
+ // get un/read items from a single subscription
+ $this->compareIds([20], (new Context)->subscription(5)->unread(true));
+ $this->compareIds([19], (new Context)->subscription(5)->unread(false));
+ // get starred articles
+ $this->compareIds([1,20], (new Context)->starred(true));
+ $this->compareIds([1], (new Context)->starred(true)->unread(false));
+ $this->compareIds([], (new Context)->starred(true)->unread(false)->subscription(5));
+ // get items relative to edition
+ $this->compareIds([19], (new Context)->subscription(5)->latestEdition(999));
+ $this->compareIds([19], (new Context)->subscription(5)->latestEdition(19));
+ $this->compareIds([20], (new Context)->subscription(5)->oldestEdition(999));
+ $this->compareIds([20], (new Context)->subscription(5)->oldestEdition(1001));
+ // get items relative to modification date
+ $exp = [2,4,6,8,20];
+ $this->compareIds($exp, (new Context)->modifiedSince("2005-01-01T00:00:00Z"));
+ $this->compareIds($exp, (new Context)->modifiedSince("2010-01-01T00:00:00Z"));
+ $exp = [1,3,5,7,19];
+ $this->compareIds($exp, (new Context)->notModifiedSince("2005-01-01T00:00:00Z"));
+ $this->compareIds($exp, (new Context)->notModifiedSince("2000-01-01T00:00:00Z"));
+
+ }
+
+ protected function compareIds(array $exp, Context $c) {
+ $ids = array_column($ids = Data::$db->articleList($this->user, $c)->getAll(), "id");
+ sort($ids);
+ sort($exp);
+ $this->assertEquals($exp, $ids);
+ }
+}
\ No newline at end of file
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
index 62886791..6263443a 100644
--- a/tests/phpunit.xml
+++ b/tests/phpunit.xml
@@ -46,6 +46,7 @@
Db/SQLite3/Database/TestDatabaseFolderSQLite3.php
Db/SQLite3/Database/TestDatabaseFeedSQLite3.php
Db/SQLite3/Database/TestDatabaseSubscriptionSQLite3.php
+ Db/SQLite3/Database/TestDatabaseArticleSQLite3.php
REST/NextCloudNews/TestNCNVersionDiscovery.php