Browse Source

Remove arbitrary search term limits; fixes #150

J. King 2 weeks ago
parent
commit
44366f48bf

+ 0
- 1
README.md View File

@@ -142,7 +142,6 @@ We are not aware of any other extensions to the TTRSS protocol. If you know of a
142 142
 - Providing the `setArticleLabel` operation with an invalid label normally silently fails; The Arsse returns an `INVALID_USAGE` error instead
143 143
 - Processing of the `search` parameter of the `getHeadlines` operation differs in the following ways:
144 144
     - Values other than `"true"` or `"false"` for the `unread`, `star`, and `pub` special keywords treat the entire token as a search term rather than as `"false"`
145
-    - Limits are placed on the number of search terms: ten each for `title`, `author`, and `note`, and twenty for content searching; exceeding the limits will yield a non-standard `TOO_MANY_SEARCH_TERMS` error
146 145
     - Invalid dates are ignored rather than assumed to be `"1970-01-01"`
147 146
     - Only a single negative date is allowed (this is a known bug rather than intentional)
148 147
     - Dates are always relative to UTC

+ 1
- 0
lib/AbstractException.php View File

@@ -11,6 +11,7 @@ abstract class AbstractException extends \Exception {
11 11
         "Exception.uncoded"                           => -1,
12 12
         "Exception.unknown"                           => 10000,
13 13
         "Exception.constantUnknown"                   => 10001,
14
+        "Exception.arrayEmpty"                        => 10002,
14 15
         "ExceptionType.strictFailure"                 => 10011,
15 16
         "ExceptionType.typeUnknown"                   => 10012,
16 17
         "Lang/Exception.defaultFileMissing"           => 10101,

+ 21
- 18
lib/Database.php View File

@@ -185,23 +185,33 @@ class Database {
185 185
      * @param boolean $matchAny Whether the search is successful when it matches any (true) or all (false) terms
186 186
      */
187 187
     protected function generateSearch(array $terms, array $cols, bool $matchAny = false): array {
188
+        if (!$cols) {
189
+            throw new Exception("arrayEmpty", "cols"); // @codeCoverageIgnore
190
+        }
188 191
         $clause = [];
189 192
         $types = [];
190 193
         $values = [];
191 194
         $like = $this->db->sqlToken("like");
195
+        $embedSet = sizeof($terms) > ((int) (self::LIMIT_SET_SIZE / sizeof($cols)));
192 196
         foreach($terms as $term) {
197
+            $embedTerm = ($embedSet && strlen($term) <= self::LIMIT_SET_STRING_LENGTH);
193 198
             $term = str_replace(["%", "_", "^"], ["^%", "^_", "^^"], $term);
194 199
             $term = "%$term%";
200
+            $term = $embedTerm ? $this->db->literalString($term) : $term;
195 201
             $spec = [];
196 202
             foreach ($cols as $col) {
197
-                $spec[] = "$col $like ? escape '^'";
198
-                $types[] = "str";
199
-                $values[] = $term;
203
+                if ($embedTerm) {
204
+                    $spec[] = "$col $like $term escape '^'";
205
+                } else {
206
+                    $spec[] = "$col $like ? escape '^'";
207
+                    $types[] = "str";
208
+                    $values[] = $term;
209
+                }
200 210
             }
201 211
             $clause[] = "(".implode(" or ", $spec).")";
202 212
         }
203 213
         $glue = $matchAny ? "or" : "and";
204
-        $clause = "(".implode(" $glue ", $clause).")";
214
+        $clause = $clause ? "(".implode(" $glue ", $clause).")" : "";
205 215
         return [$clause, $types, $values];
206 216
     }
207 217
 
@@ -1307,34 +1317,27 @@ class Database {
1307 1317
         }
1308 1318
         // handle text-matching context options
1309 1319
         $options = [
1310
-            "titleTerms"      => [10, ["arsse_articles.title"]],
1311
-            "searchTerms"     => [20, ["arsse_articles.title", "arsse_articles.content"]],
1312
-            "authorTerms"     => [10, ["arsse_articles.author"]],
1313
-            "annotationTerms" => [20, ["arsse_marks.note"]],
1320
+            "titleTerms"      => ["arsse_articles.title"],
1321
+            "searchTerms"     => ["arsse_articles.title", "arsse_articles.content"],
1322
+            "authorTerms"     => ["arsse_articles.author"],
1323
+            "annotationTerms" => ["arsse_marks.note"],
1314 1324
         ];
1315
-        foreach ($options as $m => list($max, $cols)) {
1325
+        foreach ($options as $m => $cols) {
1316 1326
             if (!$context->$m()) {
1317 1327
                 continue;
1318 1328
             } elseif (!$context->$m) {
1319 1329
                 throw new Db\ExceptionInput("tooShort", ['field' => $m, 'action' => $this->caller(), 'min' => 1]); // must have at least one array element
1320
-            } elseif (sizeof($context->$m) > $max) {
1321
-                throw new Db\ExceptionInput("tooLong", ['field' => $m, 'action' => $this->caller(), 'max' => $max]);
1322 1330
             }
1323 1331
             $q->setWhere(...$this->generateSearch($context->$m, $cols));
1324 1332
         }
1325 1333
         // further handle exclusionary text-matching context options
1326
-        foreach ($options as $m => list($max, $cols)) {
1327
-            if (!$context->not->$m()) {
1328
-                continue;
1329
-            } elseif (!$context->not->$m) {
1334
+        foreach ($options as $m => $cols) {
1335
+            if (!$context->not->$m() || !$context->not->$m) {
1330 1336
                 continue;
1331
-            } elseif (sizeof($context->not->$m) > $max) {
1332
-                throw new Db\ExceptionInput("tooLong", ['field' => "$m (not)", 'action' => $this->caller(), 'max' => $max]);
1333 1337
             }
1334 1338
             $q->setWhereNot(...$this->generateSearch($context->not->$m, $cols, true));
1335 1339
         }
1336 1340
         // return the query
1337
-        //var_export((string) $q);
1338 1341
         return $q;
1339 1342
     }
1340 1343
 

+ 2
- 0
locale/en.php View File

@@ -36,6 +36,8 @@ return [
36 36
     'Exception.JKingWeb/Arsse/Exception.unknown'                           => 'An unknown error has occurred',
37 37
     // indicates programming error
38 38
     'Exception.JKingWeb/Arsse/Exception.constantUnknown'                   => 'Supplied constant value ({0}) is unknown or invalid in the context in which it was used',
39
+    // indicates programming error
40
+    'Exception.JKingWeb/Arsse/Exception.arrayEmpty'                        => 'Supplied array "{0}" is empty, but should have at least one element',
39 41
     'Exception.JKingWeb/Arsse/ExceptionType.strictFailure'                 => 'Supplied value could not be normalized to {0, select,
40 42
         1 {null}
41 43
         2 {boolean}

+ 1
- 10
tests/cases/Database/SeriesArticle.php View File

@@ -456,6 +456,7 @@ trait SeriesArticle {
456 456
             "Excluded folder tree" => [(new Context)->not->folder(1), [1,2,3,4,19,20]],
457 457
             "Excluding label ID 2" => [(new Context)->not->label(2), [2,3,4,6,7,8,19]],
458 458
             "Excluding label 'Fascinating'" => [(new Context)->not->labelName("Fascinating"), [2,3,4,6,7,8,19]],
459
+            "Search 501 terms" => [(new Context)->searchTerms(array_merge(range(1,500),[str_repeat("a", 1000)])), []],
459 460
         ];
460 461
     }
461 462
 
@@ -991,18 +992,8 @@ trait SeriesArticle {
991 992
         Arsse::$db->articleList($this->user, (new Context)->searchTerms([]));
992 993
     }
993 994
 
994
-    public function testSearchTooManyTerms() {
995
-        $this->assertException("tooLong", "Db", "ExceptionInput");
996
-        Arsse::$db->articleList($this->user, (new Context)->searchTerms(range(1, 105)));
997
-    }
998
-
999 995
     public function testSearchTooFewTermsInNote() {
1000 996
         $this->assertException("tooShort", "Db", "ExceptionInput");
1001 997
         Arsse::$db->articleList($this->user, (new Context)->annotationTerms([]));
1002 998
     }
1003
-
1004
-    public function testSearchTooManyTermsInNote() {
1005
-        $this->assertException("tooLong", "Db", "ExceptionInput");
1006
-        Arsse::$db->articleList($this->user, (new Context)->annotationTerms(range(1, 105)));
1007
-    }
1008 999
 }

+ 0
- 1
tests/cases/REST/TinyTinyRSS/TestSearch.php View File

@@ -120,7 +120,6 @@ class TestSearch extends \JKingWeb\Arsse\Test\AbstractTest {
120 120
     /** @dataProvider provideSearchStrings */
121 121
     public function testApplySearchToContext(string $search, $exp) {
122 122
         $act = Search::parse($search);
123
-        //var_export($act);
124 123
         $this->assertEquals($exp, $act);
125 124
     }
126 125
 }

Loading…
Cancel
Save