From 1169793213fb124b67ff10be1205fb2abfdc8e95 Mon Sep 17 00:00:00 2001
From: Johannes Meyer <johannes.meyer@netways.de>
Date: Thu, 18 Jun 2015 09:36:04 +0200
Subject: [PATCH] IdoQuery: Add prototype for dynamic GROUP BY clauses

---
 library/Icinga/Data/Db/DbQuery.php            |  42 +-
 .../Monitoring/Backend/Ido/Query/IdoQuery.php | 360 +++++++++++++++++-
 2 files changed, 379 insertions(+), 23 deletions(-)

diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php
index 28287df99..c6809ea61 100644
--- a/library/Icinga/Data/Db/DbQuery.php
+++ b/library/Icinga/Data/Db/DbQuery.php
@@ -3,13 +3,14 @@
 
 namespace Icinga\Data\Db;
 
-use Icinga\Data\SimpleQuery;
-use Icinga\Data\Filter\FilterChain;
-use Icinga\Data\Filter\FilterOr;
-use Icinga\Data\Filter\FilterAnd;
-use Icinga\Data\Filter\FilterNot;
-use Icinga\Exception\QueryException;
 use Zend_Db_Select;
+use Icinga\Data\Filter\FilterAnd;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterNot;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Data\SimpleQuery;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\QueryException;
 
 /**
  * Database query class
@@ -428,6 +429,35 @@ class DbQuery extends SimpleQuery
         return false;
     }
 
+    /**
+     * Return the alias used for joining the given table
+     *
+     * @param   string      $table
+     *
+     * @return  string|null         null in case no alias is being used
+     *
+     * @throws  ProgrammingError    In case the given table has not been joined
+     */
+    public function getJoinedTableAlias($table)
+    {
+        $fromPart = $this->select->getPart(Zend_Db_Select::FROM);
+        if (isset($fromPart[$table])) {
+            if ($fromPart[$table]['joinType'] === Zend_Db_Select::FROM) {
+                throw new ProgrammingError('Table "%s" has not been joined', $table);
+            }
+
+            return; // No alias in use
+        }
+
+        foreach ($fromPart as $alias => $options) {
+            if ($options['tableName'] === $table && $options['joinType'] !== Zend_Db_Select::FROM) {
+                return $alias;
+            }
+        }
+
+        throw new ProgrammingError('Table "%s" has not been joined', $table);
+    }
+
     /**
      * Add an INNER JOIN table and colums to the query
      *
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
index 3ee32b40b..5b42ebd58 100644
--- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
@@ -10,6 +10,7 @@ use Icinga\Data\Db\DbQuery;
 use Icinga\Data\Filter\Filter;
 use Icinga\Data\Filter\FilterExpression;
 use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotImplementedError;
 use Icinga\Exception\ProgrammingError;
 use Icinga\Web\Session;
 
@@ -46,24 +47,31 @@ abstract class IdoQuery extends DbQuery
     /**
      * The prefix to use
      *
-     * @var String
+     * @var string
      */
     protected $prefix;
 
     /**
-     * The alias name for the index column
+     * An array to map aliases to table names
      *
-     * @var String
+     * @var array
      */
     protected $idxAliasColumn;
 
     /**
-     * The table containing the index column alias
+     * An array to map aliases to column names
      *
-     * @var String
+     * @var array
      */
     protected $idxAliasTable;
 
+    /**
+     * An array to map custom aliases to aliases
+     *
+     * @var array
+     */
+    protected $idxCustomAliases;
+
     /**
      * The column map containing all filterable columns
      *
@@ -111,53 +119,235 @@ abstract class IdoQuery extends DbQuery
     protected $joinedVirtualTables = array();
 
     /**
-     * The primary field name for the object table
+     * The primary key column for the instances table
+     *
+     * @var string
+     */
+    protected $instance_id = 'instance_id';
+
+    /**
+     * The primary key column for the objects table
      *
      * @var string
      */
     protected $object_id       = 'object_id';
 
     /**
-     * The primary field name for the IDO host table
+     * The primary key column for the acknowledgements table
      *
      * @var string
      */
-    protected $host_id         = 'host_id';
+    protected $acknowledgement_id = 'acknowledgement_id';
 
     /**
-     * The primary field name for the IDO hostgroup table
+     * The primary key column for the commenthistory table
      *
      * @var string
      */
-    protected $hostgroup_id    = 'hostgroup_id';
+    protected $commenthistory_id = 'commenthistory_id';
 
     /**
-     * The primary field name for the IDO service table
+     * The primary key column for the contactnotifications table
      *
      * @var string
      */
-    protected $service_id      = 'service_id';
+    protected $contactnotification_id = 'contactnotification_id';
 
     /**
-     * The primary field name for the IDO serviegroup table
+     * The primary key column for the downtimehistory table
+     *
+     * @var string
+     */
+    protected $downtimehistory_id = 'downtimehistory_id';
+
+    /**
+     * The primary key column for the flappinghistory table
+     *
+     * @var string
+     */
+    protected $flappinghistory_id = 'flappinghistory_id';
+
+    /**
+     * The primary key column for the notifications table
+     *
+     * @var string
+     */
+    protected $notification_id = 'notification_id';
+
+    /**
+     * The primary key column for the statehistory table
+     *
+     * @var string
+     */
+    protected $statehistory_id = 'statehistory_id';
+
+    /**
+     * The primary key column for the comments table
+     *
+     * @var string
+     */
+    protected $comment_id = 'comment_id';
+
+    /**
+     * The primary key column for the customvariablestatus table
+     *
+     * @var string
+     */
+    protected $customvariablestatus_id = 'customvariablestatus_id';
+
+    /**
+     * The primary key column for the hoststatus table
+     *
+     * @var string
+     */
+    protected $hoststatus_id = 'hoststatus_id';
+
+    /**
+     * The primary key column for the programstatus table
+     *
+     * @var string
+     */
+    protected $programstatus_id = 'programstatus_id';
+
+    /**
+     * The primary key column for the runtimevariables table
+     *
+     * @var string
+     */
+    protected $runtimevariable_id = 'runtimevariable_id';
+
+    /**
+     * The primary key column for the scheduleddowntime table
+     *
+     * @var string
+     */
+    protected $scheduleddowntime_id = 'scheduleddowntime_id';
+
+    /**
+     * The primary key column for the servicestatus table
+     *
+     * @var string
+     */
+    protected $servicestatus_id = 'servicestatus_id';
+
+    /**
+     * The primary key column for the contactstatus table
+     *
+     * @var string
+     */
+    protected $contactstatus_id = 'contactstatus_id';
+
+    /**
+     * The primary key column for the commands table
+     *
+     * @var string
+     */
+    protected $command_id = 'command_id';
+
+    /**
+     * The primary key column for the contactgroup_members table
+     *
+     * @var string
+     */
+    protected $contactgroup_member_id = 'contactgroup_member_id';
+
+    /**
+     * The primary key column for the contactgroups table
+     *
+     * @var string
+     */
+    protected $contactgroup_id = 'contactgroup_id';
+
+    /**
+     * The primary key column for the contacts table
+     *
+     * @var string
+     */
+    protected $contact_id = 'contact_id';
+
+    /**
+     * The primary key column for the customvariables table
+     *
+     * @var string
+     */
+    protected $customvariable_id = 'customvariable_id';
+
+    /**
+     * The primary key column for the host_contactgroups table
+     *
+     * @var string
+     */
+    protected $host_contactgroup_id = 'host_contactgroup_id';
+
+    /**
+     * The primary key column for the host_contacts table
+     *
+     * @var string
+     */
+    protected $host_contact_id = 'host_contact_id';
+
+    /**
+     * The primary key column for the hostgroup_members table
+     *
+     * @var string
+     */
+    protected $hostgroup_member_id = 'hostgroup_member_id';
+
+    /**
+     * The primary key column for the hostgroups table
+     *
+     * @var string
+     */
+    protected $hostgroup_id = 'hostgroup_id';
+
+    /**
+     * The primary key column for the hosts table
+     *
+     * @var string
+     */
+    protected $host_id = 'host_id';
+
+    /**
+     * The primary key column for the service_contactgroup table
+     *
+     * @var string
+     */
+    protected $service_contactgroup_id = 'service_contactgroup_id';
+
+    /**
+     * The primary key column for the service_contact table
+     *
+     * @var string
+     */
+    protected $service_contact_id = 'service_contact_id';
+
+    /**
+     * The primary key column for the servicegroup_members table
+     *
+     * @var string
+     */
+    protected $servicegroup_member_id = 'servicegroup_member_id';
+
+    /**
+     * The primary key column for the servicegroups table
      *
      * @var string
      */
     protected $servicegroup_id = 'servicegroup_id';
 
     /**
-     * The primary field name for the IDO contact table
+     * The primary key column for the services table
      *
      * @var string
      */
-    protected $contact_id      = 'contact_id';
+    protected $service_id = 'service_id';
 
     /**
-     * The primary field name for the IDO contactgroup table
+     * The primary key column for the timeperiods table
      *
      * @var string
      */
-    protected $contactgroup_id = 'contactgroup_id';
+    protected $timeperiod_id = 'timeperiod_id';
 
     /**
      * An array containing Column names that cause an aggregation of the query
@@ -483,6 +673,8 @@ abstract class IdoQuery extends DbQuery
             }
             if (is_int($alias)) {
                 $alias = $col;
+            } else {
+                $this->idxCustomAliases[$alias] = $col;
             }
 
             $resolvedColumns[$alias] = preg_replace('|\n|', ' ', $name);
@@ -688,6 +880,11 @@ abstract class IdoQuery extends DbQuery
         return $this->idxAliasColumn[$alias];
     }
 
+    public function customAliasToAlias($alias)
+    {
+        return $this->idxCustomAliases[$alias];
+    }
+
     /**
      * Create a sub query
      *
@@ -714,12 +911,55 @@ abstract class IdoQuery extends DbQuery
      */
     public function columns(array $columns)
     {
+        $this->idxCustomAliases = array();
         $this->columns = $this->resolveColumns($columns);
         // TODO: we need to refresh our select!
         // $this->select->columns($columns);
         return $this;
     }
 
+    /**
+     * {@inheritdoc}
+     */
+    public function _getGroup()
+    {
+        throw new NotImplementedError('Does not work in its current state but will, probably, in the future');
+
+        // TODO: order by??
+        $group = parent::getGroup();
+        if (! empty($group) && $this->ds->getDbType() === 'pgsql') {
+            $group = is_array($group) ? $group : array($group);
+            foreach ($this->columns as $alias => $column) {
+                if ($column instanceof Zend_Db_Expr) {
+                    continue;
+                }
+
+                // TODO: What if $alias is neither a native nor a custom alias???
+                $table = $this->aliasToTableName(
+                    $this->hasAliasName($alias) ? $alias : $this->customAliasToAlias($alias)
+                );
+
+                // TODO: We cannot rely on the underlying select here, tables may be joined multiple times with
+                //       different aliases so the only way to get the correct alias here is to register such by ourself
+                //       for each virtual column (We may also inspect $column for the alias but this will probably lead
+                //       to false positives.. AND prevents custom implementations from providing their own "mapping")
+                if (($tableAlias = $this->getJoinedTableAlias($this->prefix . $table)) === null) {
+                    $tableAlias = $table;
+                }
+
+                // TODO: Same issue as with identifying table aliases; Our virtual tables are not named exactly how
+                //       they are in the IDO. We definitely need to register aliases explicitly (hint: DbRepository
+                //       is already providing such..)
+                $aliasedPk = $tableAlias . '.' . $this->getPrimaryKeyColumn($table);
+                if (! in_array($aliasedPk, $group)) {
+                    $group[] = $aliasedPk;
+                }
+            }
+        }
+
+        return $group;
+    }
+
     // TODO: Move this away, see note related to $idoVersion var
     protected function getIdoVersion()
     {
@@ -745,4 +985,90 @@ abstract class IdoQuery extends DbQuery
         }
         return self::$idoVersion;
     }
+
+    /**
+     * Return the name of the primary key column for the given table name
+     *
+     * @param   string  $table
+     *
+     * @return  string
+     *
+     * @throws ProgrammingError     In case $table is unknown
+     */
+    protected function getPrimaryKeyColumn($table)
+    {
+        // TODO: For god's sake, make this being a mapping
+        //       (instead of matching a ton of properties using a ridiculous long switch case)
+        switch ($table)
+        {
+            case 'instances':
+                return $this->instance_id;
+            case 'objects':
+                return $this->object_id;
+            case 'acknowledgements':
+                return $this->acknowledgement_id;
+            case 'commenthistory':
+                return $this->commenthistory_id;
+            case 'contactnotifiations':
+                return $this->contactnotification_id;
+            case 'downtimehistory':
+                return $this->downtimehistory_id;
+            case 'flappinghistory':
+                return $this->flappinghistory_id;
+            case 'notifications':
+                return $this->notification_id;
+            case 'statehistory':
+                return $this->statehistory_id;
+            case 'comments':
+                return $this->comment_id;
+            case 'customvariablestatus':
+                return $this->customvariablestatus_id;
+            case 'hoststatus':
+                return $this->hoststatus_id;
+            case 'programstatus':
+                return $this->programstatus_id;
+            case 'runtimevariables':
+                return $this->runtimevariable_id;
+            case 'scheduleddowntime':
+                return $this->scheduleddowntime_id;
+            case 'servicestatus':
+                return $this->servicestatus_id;
+            case 'contactstatus':
+                return $this->contactstatus_id;
+            case 'commands':
+                return $this->command_id;
+            case 'contactgroup_members':
+                return $this->contactgroup_member_id;
+            case 'contactgroups':
+                return $this->contactgroup_id;
+            case 'contacts':
+                return $this->contact_id;
+            case 'customvariables':
+                return $this->customvariable_id;
+            case 'host_contactgroups':
+                return $this->host_contactgroup_id;
+            case 'host_contacts':
+                return $this->host_contact_id;
+            case 'hostgroup_members':
+                return $this->hostgroup_member_id;
+            case 'hostgroups':
+                return $this->hostgroup_id;
+            case 'hosts':
+                return $this->host_id;
+            case 'service_contactgroups':
+                return $this->service_contactgroup_id;
+            case 'service_contacts':
+                return $this->service_contact_id;
+            case 'servicegroup_members':
+                return $this->servicegroup_member_id;
+            case 'servicegroups':
+                return $this->servicegroup_id;
+            case 'services':
+                return $this->service_id;
+            case 'timeperiods':
+                return $this->timeperiod_id;
+            default:
+                throw new ProgrammingError('Cannot provide a primary key column. Table "%s" is unknown', $table);
+        }
+    }
 }