From 461e25605273167c7a355c6f67575f14f7e42a55 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Thu, 7 Jan 2021 10:12:38 -0500 Subject: [PATCH] Work around MySQL syntax weirdness Also improve test for token translation to actually test that the translated tokens are accepted by the database system --- lib/Database.php | 16 ++++++++++------ lib/Db/Driver.php | 1 + lib/Db/MySQL/Driver.php | 2 ++ tests/cases/Db/BaseDriver.php | 21 ++++++++++++++------- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/Database.php b/lib/Database.php index 95ccc61..343e2ce 100644 --- a/lib/Database.php +++ b/lib/Database.php @@ -762,6 +762,7 @@ class Database { // validate inputs $folder = $this->folderValidateId($user, $folder)['id']; // create a complex query + $integer = $this->db->sqlToken("integer"); $q = new Query( "SELECT s.id as id, @@ -789,7 +790,7 @@ class Database { select subscription, sum(hidden) as hidden, - sum(cast((\"read\" = 1 and hidden = 0) as integer)) as marked + sum(cast((\"read\" = 1 and hidden = 0) as $integer)) as marked from arsse_marks group by subscription ) as mark_stats on mark_stats.subscription = s.id" ); @@ -1211,7 +1212,7 @@ class Database { * - "block": The block rule; any article which matches this rule are hidden */ public function feedRulesGet(int $feedID): Db\Result { - return $this->db->prepare("SELECT owner, coalesce(keep_rule, '') as keep, coalesce(block_rule, '') as block from arsse_subscriptions where feed = ? and (keep || block) <> '' order by owner", "int")->run($feedID); + return $this->db->prepare("SELECT owner, coalesce(keep_rule, '') as keep, coalesce(block_rule, '') as block from arsse_subscriptions where feed = ? and (coalesce(keep_rule, '') || coalesce(block_rule, '')) <> '' order by owner", "int")->run($feedID); } /** Retrieves various identifiers for the latest $count articles in the given newsfeed. The identifiers are: @@ -1803,6 +1804,7 @@ class Database { /** Deletes from the database articles which are beyond the configured clean-up threshold */ public function articleCleanup(): bool { + $integer = $this->db->sqlToken("integer"); $query = $this->db->prepareArray( "WITH RECURSIVE exempt_articles as ( @@ -1828,8 +1830,8 @@ class Database { left join ( select article, - sum(cast((starred = 1 and hidden = 0) as integer)) as starred, - sum(cast((\"read\" = 1 or hidden = 1) as integer)) as \"read\", + sum(cast((starred = 1 and hidden = 0) as $integer)) as starred, + sum(cast((\"read\" = 1 or hidden = 1) as $integer)) as \"read\", max(arsse_marks.modified) as marked_date from arsse_marks group by article @@ -1960,6 +1962,7 @@ class Database { * @param boolean $includeEmpty Whether to include (true) or supress (false) labels which have no articles assigned to them */ public function labelList(string $user, bool $includeEmpty = true): Db\Result { + $integer = $this->db->sqlToken("integer"); return $this->db->prepareArray( "SELECT * FROM ( SELECT @@ -1975,7 +1978,7 @@ class Database { SELECT label, sum(hidden) as hidden, - sum(cast((\"read\" = 1 and hidden = 0) as integer)) as marked + sum(cast((\"read\" = 1 and hidden = 0) as $integer)) as marked from arsse_marks join arsse_subscriptions on arsse_subscriptions.id = arsse_marks.subscription join arsse_label_members on arsse_label_members.article = arsse_marks.article @@ -2025,6 +2028,7 @@ class Database { $this->labelValidateId($user, $id, $byName, false); $field = $byName ? "name" : "id"; $type = $byName ? "str" : "int"; + $integer = $this->db->sqlToken("integer"); $out = $this->db->prepareArray( "SELECT id, @@ -2039,7 +2043,7 @@ class Database { SELECT label, sum(hidden) as hidden, - sum(cast((\"read\" = 1 and hidden = 0) as integer)) as marked + sum(cast((\"read\" = 1 and hidden = 0) as $integer)) as marked from arsse_marks join arsse_subscriptions on arsse_subscriptions.id = arsse_marks.subscription join arsse_label_members on arsse_label_members.article = arsse_marks.article diff --git a/lib/Db/Driver.php b/lib/Db/Driver.php index 1488b1b..d533b92 100644 --- a/lib/Db/Driver.php +++ b/lib/Db/Driver.php @@ -74,6 +74,7 @@ interface Driver { * - "greatest": the GREATEST function implemented by PostgreSQL and MySQL * - "nocase": the name of a general-purpose case-insensitive collation sequence * - "like": the case-insensitive LIKE operator + * - "integer": the integer type to use for explicit casts */ public function sqlToken(string $token): string; diff --git a/lib/Db/MySQL/Driver.php b/lib/Db/MySQL/Driver.php index 023a281..8a82be4 100644 --- a/lib/Db/MySQL/Driver.php +++ b/lib/Db/MySQL/Driver.php @@ -81,6 +81,8 @@ class Driver extends \JKingWeb\Arsse\Db\AbstractDriver { switch (strtolower($token)) { case "nocase": return '"utf8mb4_unicode_ci"'; + case "integer": + return "signed integer"; default: return $token; } diff --git a/tests/cases/Db/BaseDriver.php b/tests/cases/Db/BaseDriver.php index 94091ac..665443d 100644 --- a/tests/cases/Db/BaseDriver.php +++ b/tests/cases/Db/BaseDriver.php @@ -90,13 +90,6 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest { $this->assertTrue($this->drv->charsetAcceptable()); } - public function testTranslateAToken(): void { - $this->assertRegExp("/^[a-z][a-z0-9]*$/i", $this->drv->sqlToken("greatest")); - $this->assertRegExp("/^\"?[a-z][a-z0-9_\-]*\"?$/i", $this->drv->sqlToken("nocase")); - $this->assertRegExp("/^[a-z][a-z0-9]*$/i", $this->drv->sqlToken("like")); - $this->assertSame("distinct", $this->drv->sqlToken("distinct")); - } - public function testExecAValidStatement(): void { $this->assertTrue($this->drv->exec($this->create)); } @@ -386,4 +379,18 @@ abstract class BaseDriver extends \JKingWeb\Arsse\Test\AbstractTest { // this performs maintenance in the absence of tables; see BaseUpdate.php for another test with tables $this->assertTrue($this->drv->maintenance()); } + + public function testTranslateTokens(): void { + $greatest = $this->drv->sqlToken("GrEatESt"); + $nocase = $this->drv->sqlToken("noCASE"); + $like = $this->drv->sqlToken("liKe"); + $integer = $this->drv->sqlToken("InTEGer"); + + $this->assertSame("NOT_A_TOKEN", $this->drv->sqlToken("NOT_A_TOKEN")); + + $this->assertSame("Z", $this->drv->query("SELECT $greatest('Z', 'A')")->getValue()); + $this->assertSame("Z", $this->drv->query("SELECT 'Z' collate $nocase")->getValue()); + $this->assertSame("Z", $this->drv->query("SELECT 'Z' where 'Z' $like 'z'")->getValue()); + $this->assertEquals(1, $this->drv->query("SELECT CAST((1=1) as $integer)")->getValue()); + } }