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

Reply via email to