From 1169793213fb124b67ff10be1205fb2abfdc8e95 Mon Sep 17 00:00:00 2001 From: Johannes Meyer 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); + } + } }