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