Add new method of multiple by[] search in API v6. The 'provides' by field is also supported in the new multiple by[] method included with this patch.
'provides' may not be used in legacy (v5) singular by search. A helpful json error message is returned to the user to indicate so. This patch is a follow-up implementation to https://patchwork.archlinux.org/patch/479/ Signed-off-by: Kevin Morris <kevr.gt...@gmail.com> --- doc/rpc.txt | 24 ++++++--- web/lib/aurjson.class.php | 105 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 13 deletions(-) diff --git a/doc/rpc.txt b/doc/rpc.txt index 3148ebe..486a49d 100644 --- a/doc/rpc.txt +++ b/doc/rpc.txt @@ -5,7 +5,7 @@ Package Search -------------- Package searches can be performed by issuing HTTP GET requests of the form -+/rpc/?v=5&type=search&by=_field_&arg=_keywords_+ where _keywords_ is the ++/rpc/?v=6&type=search&by[]=_field_&arg[]=_keywords_+ where _keywords_ is the search argument and _field_ is one of the following values: * `name` (search by package name only) @@ -15,31 +15,39 @@ search argument and _field_ is one of the following values: * `makedepends` (search for packages that makedepend on _keywords_) * `optdepends` (search for packages that optdepend on _keywords_) * `checkdepends` (search for packages that checkdepend on _keywords_) +* `provides` (search by package provides; v6 multiple by[] only) The _by_ parameter can be skipped and defaults to `name-desc`. If a maintainer search is performed and the search argument is left empty, a list of orphan packages is returned. +Note: Legacy v5 support is still enabled for singular _by_ searches. + Package Details --------------- Package information can be obtained by issuing HTTP GET requests of the form -+/rpc/?v=5&type=info&arg[]=_pkg1_&arg[]=_pkg2_&...+ where _pkg1_, _pkg2_, ... ++/rpc/?v=6&type=info&arg[]=_pkg1_&arg[]=_pkg2_&...+ where _pkg1_, _pkg2_, ... are the names of packages to retrieve package details for. Examples -------- `search`:: - `/rpc/?v=5&type=search&arg=foobar` + `/rpc/?v=6&type=search&arg=foobar` `search` by maintainer:: - `/rpc/?v=5&type=search&by=maintainer&arg=john` + `/rpc/?v=6&type=search&by[]=maintainer&arg=john` `search` packages that have _boost_ as `makedepends`:: - `/rpc/?v=5&type=search&by=makedepends&arg=boost` + `/rpc/?v=6&type=search&by[]=makedepends&arg=boost` `search` with callback:: - `/rpc/?v=5&type=search&arg=foobar&callback=jsonp1192244621103` + `/rpc/?v=6&type=search&arg=foobar&callback=jsonp1192244621103` +`search` by provides:: + `/rpc/?v=6&type=search&by[]=provides&arg[]=gcc` +`search` by provides and name:: + `/rpc/?v=6&type=search&by[]=name&by[]=provides&arg[]=gcc` `info`:: - `/rpc/?v=5&type=info&arg[]=foobar` + `/rpc/?v=6&type=info&arg[]=foobar` `info` with multiple packages:: - `/rpc/?v=5&type=info&arg[]=foo&arg[]=bar` + `/rpc/?v=6&type=info&arg[]=foo&arg[]=bar` + diff --git a/web/lib/aurjson.class.php b/web/lib/aurjson.class.php index c275d21..ab4eb19 100644 --- a/web/lib/aurjson.class.php +++ b/web/lib/aurjson.class.php @@ -80,7 +80,8 @@ class AurJSON { if (isset($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.'); } @@ -94,8 +95,14 @@ class AurJSON { if (isset($http_data['search_by']) && !isset($http_data['by'])) { $http_data['by'] = $http_data['search_by']; } - if (isset($http_data['by']) && !in_array($http_data['by'], self::$exposed_fields)) { - return $this->json_error('Incorrect by field specified.'); + + if (isset($http_data['by']) && !is_array($http_data['by'])) { + if ($http_data['by'] === 'provides') { + return $this->json_error("The 'provides' by field " . + "may only be used via multiple by[] search."); + } elseif (!in_array($http_data['by'], self::$exposed_fields)) { + return $this->json_error('Incorrect by field specified.'); + } } $this->dbh = DB::connect(); @@ -360,7 +367,7 @@ class AurJSON { } elseif ($this->version >= 2) { if ($this->version == 2 || $this->version == 3) { $fields = implode(',', self::$fields_v2); - } else if ($this->version == 4 || $this->version == 5) { + } else if ($this->version == 4 || $this->version == 5 || $this->version == 6) { $fields = implode(',', self::$fields_v4); } $query = "SELECT {$fields} " . @@ -470,6 +477,10 @@ class AurJSON { * @return mixed Returns an array of package matches. */ private function search($http_data) { + if ($this->version == 6 && is_array($http_data['by'])) { + return $this->search_v6($http_data); + } + $keyword_string = $http_data['arg']; if (isset($http_data['by'])) { @@ -497,6 +508,8 @@ class AurJSON { $keyword_string = $this->dbh->quote($keyword_string); $where_condition = "Users.Username = $keyword_string "; } + } else if ($search_by === 'provides') { + return $this->json_error("The 'provides' by field is only available via multiple by[] search."); } else if (in_array($search_by, self::$exposed_depfields)) { if (empty($keyword_string)) { return $this->json_error('Query arg is empty.'); @@ -515,6 +528,89 @@ class AurJSON { return $this->process_query('search', $where_condition); } + /* + * Returns multiple-by, multiple-arg and/or search information + * a feature not included in the v5 and before RPC API, + * which introduces the beginnings of the v6 API. + * + * @param array $http_data Query parameters. + * + * @return mixed Returns an array of results containing the package data + */ + private function search_v6($http_data) { + if (!is_array($http_data['arg'])) { + $http_data['arg'] = array($http_data['arg']); + } + + $max_results = config_get_int('options', 'max_rpc_results'); + $fields = implode(',', self::$fields_v4); + + $query = "SELECT {$fields} FROM " . + "Packages LEFT JOIN PackageBases " . + "ON PackageBases.ID = Packages.PackageBaseID " . + "LEFT JOIN Users " . + "ON PackageBases.MaintainerUID = Users.ID " . + "LEFT JOIN PackageRelations " . + "ON PackageRelations.PackageID = Packages.ID " . + "AND PackageRelations.RelTypeID = 2 "; + + $where = array(); + + foreach($http_data['by'] as $by) { + if($by == 'provides') { + foreach ($http_data['arg'] as $provide) { + array_push($where, + "PackageRelations.RelName = " . + $this->dbh->quote($provide)); + } + } elseif($by == 'name') { + foreach ($http_data['arg'] as $name) { + array_push($where, "Packages.NAME LIKE " . + $this->dbh->quote('%' . $name . '%_')); + array_push($where, "Packages.Name = " . + $this->dbh->quote($name)); + } + } elseif($by == 'name-desc') { + foreach ($http_data['arg'] as $name) { + array_push($where, "Packages.NAME LIKE " . + $this->dbh->quote('%' . $name . '%_')); + array_push($where, "Packages.Name = " . + $this->dbh->quote($name)); + array_push($where, "Packages.Description LIKE " . + $this->dbh->quote('%' . $name . '%_')); + } + } elseif (in_array($by, self::$exposed_depfields)) { + foreach ($http_data['arg'] as $dep) { + $subquery = "SELECT PackageDepends.DepName FROM PackageDepends "; + $subquery .= "LEFT JOIN DependencyTypes "; + $subquery .= "ON PackageDepends.DepTypeID = DependencyTypes.ID "; + $subquery .= "WHERE PackageDepends.PackageID = Packages.ID "; + $subquery .= "AND DependencyTypes.Name = " . $this->dbh->quote($by); + $sub_condition = $this->dbh->quote($dep) . " IN (${subquery})"; + array_push($where, $sub_condition); + } + } else { + return $this->json_error( + "${by} is not supported in v6 multi-argument by search." + ); + } + } + + $query .= "WHERE " . implode(" OR ", $where) . " "; + "AND PackageBases.PackagerUID IS NOT NULL " . + "LIMIT ${max_results}"; + + $packages = array(); // Final list of packages + $result = $this->dbh->query($query); + + if ($result) { + while ($row = $result->fetch(PDO::FETCH_ASSOC)) + array_push($packages, $row); + } + + return $this->json_results('search', count($packages), $packages, NULL); + } + /* * Returns the info on a specific package. * @@ -569,7 +665,6 @@ class AurJSON { return $this->process_query('multiinfo', $where_condition); } - /* * Returns all the packages for a specific maintainer. * -- 2.20.1