ObjectQuery, FilterRenderer: two new classes...

...required when working with the cache

refs #13068
This commit is contained in:
Thomas Gelf 2016-12-16 12:19:57 +01:00
parent 251eb4f9a5
commit ec0cbac657
2 changed files with 393 additions and 0 deletions

View File

@ -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()
);
}
}

View File

@ -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;
}
}