diff --git a/library/Director/Data/Db/IcingaObjectFilterRenderer.php b/library/Director/Data/Db/IcingaObjectFilterRenderer.php new file mode 100644 index 00000000..608327b4 --- /dev/null +++ b/library/Director/Data/Db/IcingaObjectFilterRenderer.php @@ -0,0 +1,132 @@ +<?php + +namespace Icinga\Module\Director\Data\Db; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterChain; +use Icinga\Data\Filter\FilterException; +use Icinga\Data\Filter\FilterExpression; + +class IcingaObjectFilterRenderer +{ + /** @var Filter */ + protected $filter; + + /** @var IcingaObjectQuery */ + protected $query; + + protected $columnMap = array( + 'host.name' => 'host.object_name', + 'service.name' => 'service.object_name', + ); + + public function __construct(Filter $filter, IcingaObjectQuery $query) + { + $this->filter = clone($filter); + $this->fixFilterColumns($this->filter); + $this->query = $query; + } + + /** + * @param Filter $filter + * @param IcingaObjectQuery $query + * + * @return IcingaObjectQuery + */ + public static function apply(Filter $filter, IcingaObjectQuery $query) + { + $self = new static($filter, $query); + return $self->applyFilterToQuery(); + } + + /** + * @return IcingaObjectQuery + */ + protected function applyFilterToQuery() + { + $this->query->escapedWhere($this->renderFilter($this->filter)); + return $this->query; + } + + /** + * @param Filter $filter + */ + protected function renderFilter(Filter $filter) + { + if ($filter->isChain()) { + /** @var FilterChain $filter */ + return $this->renderFilterChain($filter); + } else { + /** @var FilterExpression $filter */ + return $this->renderFilterExpression($filter); + } + } + + /** + * @param FilterChain $filter + * + * @throws FilterException + * + * @return string + */ + protected function renderFilterChain(FilterChain $filter) + { + $parts = array(); + foreach ($filter->filters() as $sub) { + $parts[] = $this->renderFilter($sub); + } + + $op = $filter->getOperatorName(); + if ($op === 'NOT') { + if (count($parts) !== 1) { + throw new FilterException( + 'NOT should have exactly one child, got %s', + count($parts) + ); + } + + return $op . ' ' . $parts[0]; + } else { + if ($filter->isRootNode()) { + return implode(' ' . $op . ' ', $parts); + } else { + return '(' . implode(' ' . $op . ' ', $parts) . ')'; + } + } + } + + protected function fixFilterColumns(Filter $filter) + { + if ($filter->isExpression()) { + /** @var FilterExpression $filter */ + $col = $filter->getColumn(); + if (array_key_exists($col, $this->columnMap)) { + $filter->setColumn($this->columnMap[$col]); + } + if (strpos($col, 'vars.') === false) { + $filter->setExpression(json_decode($filter->getExpression())); + } + } else { + /** @var FilterChain $filter */ + foreach ($filter->filters() as $sub) { + $this->fixFilterColumns($sub); + } + } + } + + /** + * @param FilterExpression $filter + * + * @return string + */ + protected function renderFilterExpression(FilterExpression $filter) + { + $query = $this->query; + $column = $query->getAliasforRequiredFilterColumn($filter->getColumn()); + return $query->whereToSql( + $column, + $filter->getSign(), + $filter->getExpression() + ); + } +} \ No newline at end of file diff --git a/library/Director/Data/Db/IcingaObjectQuery.php b/library/Director/Data/Db/IcingaObjectQuery.php new file mode 100644 index 00000000..984ae23d --- /dev/null +++ b/library/Director/Data/Db/IcingaObjectQuery.php @@ -0,0 +1,261 @@ +<?php + +namespace Icinga\Module\Director\Data\Db; + +use Icinga\Data\Db\DbQuery; +use Icinga\Exception\NotFoundError; +use Icinga\Exception\NotImplementedError; +use Icinga\Module\Director\Db; +use Zend_Db_Expr as ZfDbExpr; +use Zend_Db_Select as ZfDbSelect; + +class IcingaObjectQuery +{ + const BASE_ALIAS = 'o'; + + /** @var Db */ + protected $connection; + + /** @var string */ + protected $type; + + /** @var \Zend_Db_Adapter_Abstract */ + protected $db; + + /** @var ZfDbSelect */ + protected $query; + + /** @var bool */ + protected $resolved; + + /** @var array joined tables, alias => table */ + protected $requiredTables; + + /** @var array maps table aliases, alias => table*/ + protected $aliases; + + /** @var DbQuery */ + protected $dummyQuery; + + /** @var array varname => alias */ + protected $joinedVars = array(); + + protected $customVarTable; + + protected $baseQuery; + + /** + * IcingaObjectQuery constructor. + * + * @param string $type + * @param Db $connection + * @param bool $resolved + */ + public function __construct($type, Db $connection, $resolved = true) + { + $this->type = $type; + $this->connection = $connection; + $this->db = $connection->getDbAdapter(); + $this->resolved = $resolved; + $baseTable = 'icinga_' . $type; + $this->baseQuery = $this->db->select() + ->from( + array(self::BASE_ALIAS => $baseTable), + array('name' => 'object_name') + )->order(self::BASE_ALIAS . '.object_name'); + } + + public function joinVar($name) + { + if (! $this->hasJoinedVar($name)) { + $type = $this->type; + $alias = $this->safeVarAlias($name); + $varAlias = $alias . '_v'; + // TODO: optionally $varRelation = sprintf('icinga_%s_resolved_var', $type); + $varRelation = sprintf('icinga_%s_var', $type); + $idCol = sprintf('%s.%s_id', $alias, $type); + + $joinOn = sprintf('%s = %s.id', $idCol, self::BASE_ALIAS); + $joinVarOn = $this->db->quoteInto( + sprintf('%s.checksum = %s.checksum AND %s.varname = ?', $alias, $varAlias, $alias), + $name + ); + + $this->baseQuery->join( + array($alias => $varRelation), + $joinOn, + array() + )->join( + array($varAlias => 'icinga_var'), + $joinVarOn, + array($alias => $varAlias . '.varvalue') + ); + + $this->joinedVars[$name] = $varAlias . '.varvalue'; + } + + return $this; + } + + public function list() + { + return $this->db->fetchCol( + $this->baseQuery + ); + } + + protected function hasJoinedVar($name) + { + return array_key_exists($name, $this->joinedVars); + } + + public function getJoinedVarAlias($name) + { + return $this->joinedVars[$name]; + } + + // TODO: recheck this + protected function safeVarAlias($name) + { + $alias = preg_replace('/[^a-zA-Z0-9_]/', '', (string) $name); + $cnt = 1; + $checkAlias = $alias; + while (in_array($checkAlias, $this->joinedVars)) { + $cnt++; + $checkAlias = $alias . '_' . $cnt; + } + + return $checkAlias; + } + + public function escapedWhere($where) + { + $this->baseQuery->where(new ZfDbExpr($where)); + } + + /** + * @param $column + * + * @return string + */ + public function getAliasforRequiredFilterColumn($column) + { + $dot = strpos($column, '.'); + list($key, $sub) = $this->splitFilterKey($column); + if ($sub === null) { + return $key; + } else { + $objectType = $key; + } + + if ($objectType === $this->type) { + list($key, $sub) = $this->splitFilterKey($sub); + if ($sub === null) { + return $key; + } + + if ($key === 'vars') { + return $this->joinVar($sub)->getJoinedVarAlias($sub); + } else { + throw new NotFoundError('Not yet, my type: %s - %s', $objectType, $key); + } + } else { + throw new NotImplementedError('Not yet: %s - %s', $objectType, $sub); + } + } + + protected function splitFilterKey($key) + { + $dot = strpos($key, '.'); + if ($dot === false) { + return array($key, null); + } else { + return array(substr($key, 0, $dot), substr($key, $dot + 1)); + } + } + + protected function requireTable($name) + { + if ($alias = $this->getTableAliasFromQuery($name)) { + return $alias; + } + + $this->joinTable($name); + } + + protected function joinTable($name) + { + if (!array_key_exists($name, $this->requiredTables)) { + $alias = $this->makeAlias($name); + } + + return $this->tableAliases($name); + } + + protected function hasAlias($name) + { + return array_key_exists($name, $this->aliases); + } + + protected function makeAlias($name) + { + if (substr($name, 0, 7) === 'icinga_') { + $shortName = substr($name, 7); + } else { + $shortName = $name; + } + + $parts = preg_split('/_/', $shortName, -1); + $alias = ''; + foreach ($parts as $part) { + $alias .= $part[0]; + if (! $this->hasAlias($alias)) { + return $alias; + } + } + + $cnt = 1; + { + $cnt++; + if (! $this->hasAlias($alias . $cnt)) { + return $alias . $cnt; + } + + } while (! $this->hasAlias($alias)); + + return $alias; + } + + protected function getTableAliasFromQuery($table) + { + $tables = $this->query->getPart('from'); + $key = array_search($table, $tables); + if ($key === null || $key === false) { + return false; + } + /* + 'joinType' => $type, + 'schema' => $schema, + 'tableName' => $tableName, + 'joinCondition' => $cond + */ + return $key; + } + + public function whereToSql($col, $sign, $expression) + { + return $this->dummyQuery()->whereToSql($col, $sign, $expression); + } + + /** + * @return DbQuery + */ + protected function dummyQuery() + { + if ($this->dummyQuery === null) { + $this->dummyQuery = $this->connection->select(); + } + + return $this->dummyQuery; + } +}