Support conjunctive keyword search in RPC interface

Newly supported API Version 6 modifies `type=search` for _by_ type
`name-desc`: it now behaves the same as `name-desc` search through the
https://aur.archlinux.org/packages/ search page.

Search for packages containing the literal keyword `blah blah` AND `haha`:
https://aur.archlinux.org/rpc/?v=6&type=search&arg="blah blah"%20haha

Search for packages containing the literal keyword `abc 123`:
https://aur.archlinux.org/rpc/?v=6&type=search&arg="abc 123"

The following example searches for packages that contain `blah` AND `abc`:
https://aur.archlinux.org/rpc/?v=6&type=search&arg=blah%20abc

The legacy method still searches for packages that contain `blah abc`:
https://aur.archlinux.org/rpc/?v=5&type=search&arg=blah%20abc
https://aur.archlinux.org/rpc/?v=5&type=search&arg=blah%20abc

API Version 6 is currently only considered during a `search` of `name-desc`.

Note: This change was written as a solution to
https://bugs.archlinux.org/task/49133.

PS: + Some spacing issues fixed in comments.

Signed-off-by: Kevin Morris <kevr.gtalk@gmail.com>
Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
This commit is contained in:
Kevin Morris 2020-07-05 18:19:06 -07:00 committed by Lukas Fleischer
parent 239988def7
commit efe99dc16f
3 changed files with 43 additions and 24 deletions

View file

@ -39,6 +39,10 @@ Examples
`/rpc/?v=5&type=search&by=makedepends&arg=boost` `/rpc/?v=5&type=search&by=makedepends&arg=boost`
`search` with callback:: `search` with callback::
`/rpc/?v=5&type=search&arg=foobar&callback=jsonp1192244621103` `/rpc/?v=5&type=search&arg=foobar&callback=jsonp1192244621103`
`search` with API Version 6 for packages containing `cookie` AND `milk`::
`/rpc/?v=6&type=search&arg=cookie%20milk`
`search` with API Version 6 for packages containing `cookie milk`::
`/rpc/?v=6&type=search&arg="cookie milk"`
`info`:: `info`::
`/rpc/?v=5&type=info&arg[]=foobar` `/rpc/?v=5&type=info&arg[]=foobar`
`info` with multiple packages:: `info` with multiple packages::

View file

@ -1,6 +1,7 @@
<?php <?php
include_once("aur.inc.php"); include_once("aur.inc.php");
include_once("pkgfuncs.inc.php");
/* /*
* This class defines a remote interface for fetching data from the AUR using * This class defines a remote interface for fetching data from the AUR using
@ -80,7 +81,7 @@ class AurJSON {
if (isset($http_data['v'])) { if (isset($http_data['v'])) {
$this->version = intval($http_data['v']); $this->version = intval($http_data['v']);
} }
if ($this->version < 1 || $this->version > 5) { if ($this->version < 1 || $this->version > 6) {
return $this->json_error('Invalid version specified.'); return $this->json_error('Invalid version specified.');
} }
@ -370,7 +371,7 @@ class AurJSON {
} elseif ($this->version >= 2) { } elseif ($this->version >= 2) {
if ($this->version == 2 || $this->version == 3) { if ($this->version == 2 || $this->version == 3) {
$fields = implode(',', self::$fields_v2); $fields = implode(',', self::$fields_v2);
} else if ($this->version == 4 || $this->version == 5) { } else if ($this->version >= 4 && $this->version <= 6) {
$fields = implode(',', self::$fields_v4); $fields = implode(',', self::$fields_v4);
} }
$query = "SELECT {$fields} " . $query = "SELECT {$fields} " .
@ -492,13 +493,21 @@ class AurJSON {
if (strlen($keyword_string) < 2) { if (strlen($keyword_string) < 2) {
return $this->json_error('Query arg too small.'); return $this->json_error('Query arg too small.');
} }
$keyword_string = $this->dbh->quote("%" . addcslashes($keyword_string, '%_') . "%");
if ($this->version >= 6 && $search_by === 'name-desc') {
$where_condition = construct_keyword_search($this->dbh,
$keyword_string, true, false);
} else {
$keyword_string = $this->dbh->quote(
"%" . addcslashes($keyword_string, '%_') . "%");
if ($search_by === 'name') { if ($search_by === 'name') {
$where_condition = "(Packages.Name LIKE $keyword_string)"; $where_condition = "(Packages.Name LIKE $keyword_string)";
} else if ($search_by === 'name-desc') { } else if ($search_by === 'name-desc') {
$where_condition = "(Packages.Name LIKE $keyword_string OR "; $where_condition = "(Packages.Name LIKE $keyword_string ";
$where_condition .= "Description LIKE $keyword_string)"; $where_condition .= "OR Description LIKE $keyword_string)";
}
} }
} else if ($search_by === 'maintainer') { } else if ($search_by === 'maintainer') {
if (empty($keyword_string)) { if (empty($keyword_string)) {

View file

@ -696,8 +696,10 @@ function pkg_search_page($params, $show_headers=true, $SID="") {
$q_where .= "AND (PackageBases.Name LIKE " . $dbh->quote($K) . ") "; $q_where .= "AND (PackageBases.Name LIKE " . $dbh->quote($K) . ") ";
} }
elseif (isset($params["SeB"]) && $params["SeB"] == "k") { elseif (isset($params["SeB"]) && $params["SeB"] == "k") {
/* Search by keywords. */ /* Search by name. */
$q_where .= construct_keyword_search($dbh, $params['K'], false); $q_where .= "AND (";
$q_where .= construct_keyword_search($dbh, $params['K'], false, true);
$q_where .= ") ";
} }
elseif (isset($params["SeB"]) && $params["SeB"] == "N") { elseif (isset($params["SeB"]) && $params["SeB"] == "N") {
/* Search by name (exact match). */ /* Search by name (exact match). */
@ -709,7 +711,9 @@ function pkg_search_page($params, $show_headers=true, $SID="") {
} }
else { else {
/* Keyword search (default). */ /* Keyword search (default). */
$q_where .= construct_keyword_search($dbh, $params['K'], true); $q_where .= "AND (";
$q_where .= construct_keyword_search($dbh, $params['K'], true, true);
$q_where .= ") ";
} }
} }
@ -832,10 +836,11 @@ function pkg_search_page($params, $show_headers=true, $SID="") {
* @param handle $dbh Database handle * @param handle $dbh Database handle
* @param string $keywords The search term * @param string $keywords The search term
* @param bool $namedesc Search name and description fields * @param bool $namedesc Search name and description fields
* @param bool $keyword Search packages with a matching PackageBases.Keyword
* *
* @return string WHERE part of the SQL clause * @return string WHERE part of the SQL clause
*/ */
function construct_keyword_search($dbh, $keywords, $namedesc) { function construct_keyword_search($dbh, $keywords, $namedesc, $keyword=false) {
$count = 0; $count = 0;
$where_part = ""; $where_part = "";
$q_keywords = ""; $q_keywords = "";
@ -860,13 +865,18 @@ function construct_keyword_search($dbh, $keywords, $namedesc) {
$term = "%" . addcslashes($term, '%_') . "%"; $term = "%" . addcslashes($term, '%_') . "%";
$q_keywords .= $op . " ("; $q_keywords .= $op . " (";
$q_keywords .= "Packages.Name LIKE " . $dbh->quote($term) . " ";
if ($namedesc) { if ($namedesc) {
$q_keywords .= "Packages.Name LIKE " . $dbh->quote($term) . " OR "; $q_keywords .= "OR Description LIKE " . $dbh->quote($term) . " ";
$q_keywords .= "Description LIKE " . $dbh->quote($term) . " OR ";
} }
$q_keywords .= "EXISTS (SELECT * FROM PackageKeywords WHERE ";
if ($keyword) {
$q_keywords .= "OR EXISTS (SELECT * FROM PackageKeywords WHERE ";
$q_keywords .= "PackageKeywords.PackageBaseID = Packages.PackageBaseID AND "; $q_keywords .= "PackageKeywords.PackageBaseID = Packages.PackageBaseID AND ";
$q_keywords .= "PackageKeywords.Keyword LIKE " . $dbh->quote($term) . ")) "; $q_keywords .= "PackageKeywords.Keyword LIKE " . $dbh->quote($term) . ")) ";
} else {
$q_keywords .= ") ";
}
$count++; $count++;
if ($count >= 20) { if ($count >= 20) {
@ -875,11 +885,7 @@ function construct_keyword_search($dbh, $keywords, $namedesc) {
$op = "AND "; $op = "AND ";
} }
if (!empty($q_keywords)) { return $q_keywords;
$where_part = "AND (" . $q_keywords . ") ";
}
return $where_part;
} }
/** /**