array( 'ASC' => array( 'host_name ASC', 'service_description ASC' ), 'DESC' => array( 'host_name DESC', 'service_description ASC' ), 'default' => 'ASC' ), 'host_address' => array( 'ASC' => array( 'host_ipv4 ASC', 'service_description ASC' ), 'DESC' => array( 'host_ipv4 ASC', 'service_description ASC' ), 'default' => 'ASC' ), 'service' => array( 'ASC' => array( 'service_description ASC' ), 'DESC' => array( 'service_description DESC' ), 'default' => 'ASC' ), 'service_state_change' => array( 'ASC' => array( 'ss.last_state_change ASC' ), 'DESC' => array( 'ss.last_state_change DESC' ), 'default' => 'DESC' ), 'service_state' => array( 'ASC' => array( 'CASE WHEN (ss.current_state = 3) THEN 2 WHEN (ss.current_state = 2) THEN 3 ELSE ss.current_state END DESC', // TODO: distinct severity in a better way 'ss.problem_has_been_acknowledged ASC', // 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END ASC', 'service_in_downtime ASC', // TODO: Check if all dbs allow sorting by alias 'ss.last_state_change DESC', 'so.name1 ASC', 'so.name2 ASC' ), 'DESC' => array( 'CASE WHEN (ss.current_state = 3) THEN 2 WHEN (ss.current_state = 2) THEN 3 ELSE ss.current_state END ASC', 'ss.problem_has_been_acknowledged ASC', // 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END ASC', 'service_in_downtime ASC', 'ss.last_state_change DESC' ), 'default' => 'ASC' ) ); abstract protected function createQuery(); public function dump() { $this->finalize(); return "QUERY\n=====\n" . $this->query . "\n\nCOUNT\n=====\n" . $this->count_query . "\n\n"; } public function getCountQueryObject() { return $this->finalize()->count_query; } public function getQueryObject() { return $this->finalize()->query; } protected function createCountQuery() { return clone($this->query); } protected function init() { $this->db = $this->backend->getAdapter(); $this->dbtype = $this->backend->getDbType(); if ($this->dbtype === 'oracle') { $this->object_id = $this->hostgroup_id = $this->servicegroup_id = 'id'; } $this->prefix = $this->backend->getPrefix(); $this->query = $this->createQuery(); $this->count_query = $this->createCountQuery(); } protected function finalize() { if ($this->finalized) return $this; $this->finalized = true; $this->query->columns($this->columns); if ($this->count_columns === null) { $this->count_columns = array('cnt' => 'COUNT(*)'); } if (! $this->ordered) { $this->order(); } $this->count_query->columns($this->count_columns); return $this; } protected function prepareServiceStatesQuery() { $query = $this->db->select() ->from( array('hs' => $this->prefix . 'hoststatus'), array() )->join( array('h' => $this->prefix . 'hosts'), 'hs.host_object_id = h.host_object_id', array() )->join( array('s' => $this->prefix . 'services'), 's.host_object_id = h.host_object_id', array() )->join( array('so' => $this->prefix . 'objects'), "so.$this->object_id = s.service_object_id AND so.is_active = 1", array() )->joinLeft( array('ss' => $this->prefix . 'servicestatus'), "so.$this->object_id = ss.service_object_id", array() ); // $this->joinServiceDowntimes($query); // $query->group('so.object_id'); return $query; } protected function prepareServicesCount() { // TODO: Depends on filter, some cols could be avoided $query = $this->db->select()->from( array('hs' => $this->prefix . 'hoststatus'), array() )->join( array('h' => $this->prefix . 'hosts'), 'hs.host_object_id = h.host_object_id', array() )->join( array('s' => $this->prefix . 'services'), 's.host_object_id = h.host_object_id', array() )->join( array('so' => $this->prefix . 'objects'), "so.$this->object_id = s.service_object_id AND so.is_active = 1", "COUNT(so.$this->object_id)" )->joinLeft( array('ss' => $this->prefix . 'servicestatus'), "so.$this->object_id = ss.service_object_id", array() ); // $this->joinServiceDowntimes($query); return $query; } protected function joinHostgroups($query = null) { if ($query === null) $query = $this->query; $query->join( array('hgm' => $this->prefix . 'hostgroup_members'), 'hgm.host_object_id = h.host_object_id', array() )->join( array('hg' => $this->prefix . 'hostgroups'), "hgm.hostgroup_id = hg.$this->hostgroup_id", array() ); return $this; } protected function joinServiceHostgroups($query) { if ($query === null) $query = $this->query; $query->join( array('hgm' => $this->prefix . 'hostgroup_members'), 'hgm.host_object_id = s.host_object_id', array() )->join( array('hg' => $this->prefix . 'hostgroups'), "hgm.hostgroup_id = hg.$this->hostgroup_id", array() ); return $this; } protected function joinServicegroups($query) { if ($query === null) $query = $this->query; $query->join( array('sgm' => $this->prefix . 'servicegroup_members'), 'sgm.service_object_id = s.service_object_id', array() )->join( array('sg' => $this->prefix . 'servicegroups'), "sgm.servicegroup_id = sg.$this->servicegroup_id", array() ); return $this; } protected function joinServiceDowntimes($query) { $query->joinLeft( array('dt' => $this->prefix . 'scheduleddowntime'), "so.$this->object_id = dt.object_id" . ' AND dt.is_in_effect = 1', array() ); // NDO compat (doesn't work correctly like this): // $now = "'" . date('Y-m-d H:i:s') . "'"; // . ' AND dt.was_started = 1' // . ' AND dt.scheduled_end_time > ' . $now // . ' AND dt.actual_start_time < ' . $now, return $query; } public function where($column, $value = null) { // Ugly temporary hack: foreach (array($this->query, $this->count_query) as $query) { if ($column === 'search') { if ($this->dbtype === 'mysql') { $query->where($this->db->quoteInto( 'so.name2 COLLATE latin1_general_ci LIKE ?' . ' OR so.name1 COLLATE latin1_general_ci LIKE ?', '%' . $value . '%', '%' . $value . '%' )); } else { $query->where($this->db->quoteInto( 'LOWER(so.name2) LIKE ?' . ' OR LOWER(so.name1) LIKE ?', '%' . strtolower($value) . '%', '%' . strtolower($value) . '%' )); } continue; } // TODO: Check if this also works based on column: if ($column === 'hostgroups') { $this->appendHostgroupLimit($query, $value); continue; } if (preg_match('~^_([^_]+)_(.+)$~', $column, $m)) { switch($m[1]) { case 'host': $this->appendHostCustomVarLimit($query, $m[2], $value); break; case 'service': $this->appendServiceCustomVarLimit($query, $m[2], $value); break; } continue; } //$column = preg_replace('~^current_state~', 'ss.current_state', $column); if (array_key_exists($column, $this->available_columns)) { $column = $this->available_columns[$column]; } $query->where($this->prepareFilterStringForColumn($column, $value)); } /*->orWhere('last_state_change > ?', $new)*/ return $this; } public function order($column = '', $dir = null) { $this->ordered = true; return $this->applyOrder($column, $dir); } protected function applyOrder($order = '', $order_dir = null) { if (! array_key_exists($order, $this->order_columns)) { $order = key($this->order_columns); } if ($order_dir === null) { $order_dir = $this->order_columns[$order]['default']; } foreach ($this->order_columns[$order][$order_dir] as $col) { $this->query->order($col); } return $this; } protected function addServiceComments($query = null) { if ($query === null) { $query = $this->query; } $query->joinLeft( array('co' => $this->prefix . 'comments'), "so.$this->object_id = co.object_id", array() ) ->group('so.object_id') ; return $this; } /** * $column = col * $value = abc,cde,cd*,!egh,!*hh* * -> (col IN ('abc', 'cde') OR col LIKE 'cd%') AND (col != 'egh' AND col NOT LIKE '%hh%') */ protected function prepareFilterStringForColumn($column, $value) { $filter = ''; $filters = array(); $or = array(); $and = array(); if (strpos($value, ',') !== false) { $value = preg_split('~,~', $value, -1, PREG_SPLIT_NO_EMPTY); } if (! is_array($value)) { $value = array($value); } // Go through all given values foreach ($value as $val) { // Value starting with - means negation if ($val[0] === '-') { $val = substr($val, 1); if (strpos($val, '*') === false) { $and[] = $this->db->quoteInto($column . ' != ?', $val); } else { $and[] = $this->db->quoteInto( $column . ' NOT LIKE ?', str_replace('*', '%', $val) ); } // Starting with + enforces AND } elseif ($val[0] === '+') { $val = substr($val, 1); if (strpos($val, '*') === false) { $and[] = $this->db->quoteInto($column . ' = ?', $val); } else { $and[] = $this->db->quoteInto( $column . ' LIKE ?', str_replace('*', '%', $val) ); } // All others ar ORs: } else { if (strpos($val, '*') === false) { $or[] = $this->db->quoteInto($column . ' = ?', $val); } else { $or[] = $this->db->quoteInto( $column . ' LIKE ?', str_replace('*', '%', $val) ); } } } if (! empty($or)) { $filters[] = implode(' OR ', $or); } if (! empty($and)) { $filters[] = implode(' AND ', $and); } if (! empty($filters)) { $filter = '(' . implode(') AND (', $filters) . ')'; } return $filter; } protected function addCustomVarColumn($query, $alias, $name, $filter = null) { // TODO: Improve this: if (! preg_match('~^[a-zA-Z0-9_]+$~', $name)) { throw new \Exception(sprintf( 'Got invalid custom var: "%s"', $name )); } $qobj = spl_object_hash($query); if (! array_key_exists($qobj, $this->custom_cols)) { $this->custom_cols[$qobj] = array(); } if (array_key_exists($alias, $this->custom_cols[$qobj])) { if ($name !== $this->custom_cols[$qobj][$alias]) { throw new \Exception(sprintf( 'Cannot add CV alias "%s" twice with different target', $alias )); } return $this; } $query->join( // TODO: Allow multiple limits with different aliases array($alias => $this->prefix . 'customvariablestatus'), 's.host_object_id = ' . $alias . '.object_id' . ' AND ' . $alias . '.varname = ' . $this->db->quote(strtoupper($name)) //. ($filter === null ? '' : ' AND ' . $filter), , array() ); $this->custom_cols[$qobj][$alias] = $name; return $this; } protected function appendHostCustomVarLimit($query, $key, $value) { $alias = 'hcv_' . strtolower($key); $filter = $this->prepareFilterStringForColumn($alias . '.varvalue', $value); $this->addCustomVarColumn($query, $alias, $key); $query->where($filter); return $query; } protected function appendHostgroupLimit($query, $hostgroups) { return $query->join( array('hgm' => $this->prefix . 'hostgroup_members'), 'hgm.host_object_id = s.host_object_id', array() )->join( array('hg' => $this->prefix . 'hostgroups'), "hgm.hostgroup_id = hg.$this->hostgroup_id", array() ) ->where('hg.alias IN (?)', $hostgroups); } public function count() { return $this->db->fetchOne( $this->finalize()->count_query ); } public function fetchAll() { return $this->db->fetchAll($this->finalize()->query); } public function fetchRow() { return $this->db->fetchRow($this->finalize()->query); } public function fetchOne() { return $this->db->fetchOne($this->finalize()->query); } public function fetchPairs() { return $this->db->fetchPairs($this->finalize()->query); } /** * Sets a limit count and offset to the query * * @param int $count Number of rows to return * @param int $offset Row offset to start from * @return \Icinga\Backend\Query This Query object */ public function limit($count = null, $offset = null) { $this->query->limit($count, $offset); return $this; } public function __toString() { $this->finalize(); return (string) $this->query; } }