icingaweb2/library/Icinga/Data/BaseQuery.php

503 lines
13 KiB
PHP

<?php
namespace Icinga\Data;
use Icinga\Logger\Logger;
use Icinga\Exception;
use Icinga\Filter\Filterable;
use Icinga\Filter\Query\Node;
use Icinga\Filter\Query\Tree;
use Zend_Paginator;
use Icinga\Web\Paginator\Adapter\QueryAdapter;
abstract class BaseQuery implements Filterable
{
/**
* Sort ascending
*/
const SORT_ASC = 1;
/**
* Sort descending
*/
const SORT_DESC = -1;
/**
* Query data source
*
* @var DatasourceInterface
*/
protected $ds;
/**
* The target of this query
* @var string
*/
protected $table;
/**
* The columns of the target that should be returned
* @var array
*/
private $columns;
/**
* The columns you're using to sort the query result
* @var array
*/
private $orderColumns = array();
/**
* Return not more than that many rows
* @var int
*/
private $limitCount;
/**
* Result starts with this row
* @var int
*/
private $limitOffset;
/**
* The backend independent filter to use for this query
*
* @var Tree
*/
private $filter;
/**
* Constructor
*
* @param DatasourceInterface $ds Your data source
*/
public function __construct(DatasourceInterface $ds, array $columns = null)
{
$this->ds = $ds;
$this->columns = $columns;
$this->clearFilter();
$this->init();
}
public function getDatasource()
{
return $this->ds;
}
public function setColumns(array $columns)
{
$this->columns = $columns;
}
/**
* Define the target and attributes for this query
*
* The Query will return the default attribute the attributes parameter is omitted
*
* @param String $target The target of this query (tablename, objectname, depends on the concrete implementation)
* @param array $columns An optional array of columns to select, if none are given the default
* columnset is returned
*
* @return self Fluent interface
*/
public function from($target, array $attributes = null)
{
$this->table = $target;
if ($attributes !== null) {
$this->columns = $attributes;
}
return $this;
}
/**
* Add a filter expression to be applied on this query.
*
* This is an alias to andWhere()
* The syntax of the expression and valid parameters are to be defined by the concrete
* backend-specific query implementation.
*
* @param string $expression Implementation specific search expression
* @param mixed $parameters Implementation specific search value to use for query placeholders
*
* @return self Fluent Interface
* @see BaseQuery::andWhere() This is an alias to andWhere()
*/
public function where($expression, $parameters = null)
{
return $this->andWhere($expression, $parameters);
}
/**
* Add an mandatory filter expression to be applied on this query
*
* The syntax of the expression and valid parameters are to be defined by the concrete
* backend-specific query implementation.
*
* @param string $expression Implementation specific search expression
* @param mixed $parameters Implementation specific search value to use for query placeholders
* @return self Fluent interface
*/
public function andWhere($expression, $parameters = null)
{
$node = $this->parseFilterExpression($expression, $parameters);
if ($node === null) {
Logger::debug('Ignoring invalid filter expression: %s (params: %s)', $expression, $parameters);
return $this;
}
$this->filter->insert(Node::createAndNode());
$this->filter->insert($node);
return $this;
}
/**
* Add an lower priority filter expression to be applied on this query
*
* The syntax of the expression and valid parameters are to be defined by the concrete
* backend-specific query implementation.
*
* @param string $expression Implementation specific search expression
* @param mixed $parameters Implementation specific search value to use for query placeholders
* @return self Fluent interface
*/
public function orWhere($expression, $parameters = null)
{
$node = $this->parseFilterExpression($expression, $parameters);
if ($node === null) {
Logger::debug('Ignoring invalid filter expression: %s (params: %s)', $expression, $parameters);
return $this;
}
$this->filter->insert(Node::createOrNode());
$this->filter->insert($node);
return $this;
}
/**
* Determine whether the given field is a valid filter target
*
* The base implementation always returns true, overwrite it in concrete backend-specific
* implementations
*
* @param String $field The field to test for being filterable
* @return bool True if the field can be filtered, otherwise false
*/
public function isValidFilterTarget($field)
{
return true;
}
/**
* Return the internally used field name for the given alias
*
* The base implementation just returns the given field, overwrite it in concrete backend-specific
* implementations
*
* @param String $field The field to test for being filterable
* @return bool True if the field can be filtered, otherwise false
*/
public function getMappedField($field)
{
return $field;
}
/**
* Add a filter to this query
*
* This is the implementation for the Filterable, use where instead
*
* @param $filter
*/
public function addFilter($filter)
{
if (is_string($filter)) {
$this->addFilter(call_user_func_array(array($this, 'parseFilterExpression'), func_get_args()));
} elseif ($filter instanceof Node) {
$this->filter->insert($filter);
}
}
public function setFilter(Tree $filter)
{
$this->filter = $filter;
}
/**
* Return all default columns
*
* @return array An array of default columns to use when none are selected
*/
public function getDefaultColumns()
{
return array();
}
/**
* Sort query result by the given column name
*
* Sort direction can be ascending (self::SORT_ASC, being the default)
* or descending (self::SORT_DESC).
*
* Preferred usage:
* <code>
* $query->sort('column_name ASC')
* </code>
*
* @param string $columnOrAlias Column, may contain direction separated by space
* @param int $dir Sort direction
*
* @return BaseQuery
*/
public function order($columnOrAlias, $dir = null)
{
if ($dir === null) {
$colDirPair = explode(' ', $columnOrAlias, 2);
if (count($colDirPair) === 1) {
$dir = $this->getDefaultSortDir($columnOrAlias);
} else {
$dir = $colDirPair[1];
$columnOrAlias = $colDirPair[0];
}
}
$dir = (strtoupper(trim($dir)) === 'DESC') ? self::SORT_DESC : self::SORT_ASC;
$this->orderColumns[] = array($columnOrAlias, $dir);
return $this;
}
/**
* Determine the default sort direction constant for the given column
*
* @param String $col The column to get the sort direction for
* @return int Either SORT_ASC or SORT_DESC
*/
protected function getDefaultSortDir($col)
{
return self::SORT_ASC;
}
/**
* Limit the result set
*
* @param int $count The numeric maximum limit to apply on the query result
* @param int $offset The offset to use for the result set
*
* @return BaseQuery
*/
public function limit($count = null, $offset = null)
{
$this->limitCount = $count !== null ? intval($count) : null;
$this->limitOffset = intval($offset);
return $this;
}
/**
* Determine whether this query will be ordered explicitly
*
* @return bool True when an order column has been set
*/
public function hasOrder()
{
return !empty($this->orderColumns);
}
/**
* Determine whether this query will be limited explicitly
*
* @return bool True when an limit count has been set, otherwise false
*/
public function hasLimit()
{
return $this->limitCount !== null;
}
/**
* Determine whether an offset is set or not
*
* @return bool True when an offset > 0 is set
*/
public function hasOffset()
{
return $this->limitOffset > 0;
}
/**
* Get the query limit
*
* @return int The query limit or null if none is set
*/
public function getLimit()
{
return $this->limitCount;
}
/**
* Get the query starting offset
*
* @return int The query offset or null if none is set
*/
public function getOffset()
{
return $this->limitOffset;
}
/**
* Implementation specific initialization
*
* Overwrite this instead of __construct (it's called at the end of the construct) to
* implement custom initialization logic on construction time
*/
protected function init()
{
}
/**
* Return all columns set in this query or the default columns if none are set
*
* @return array An array of columns
*/
public function getColumns()
{
return ($this->columns !== null) ? $this->columns : $this->getDefaultColumns();
}
/**
* Return all columns used for ordering
*
* @return array
*/
public function getOrderColumns()
{
return $this->orderColumns;
}
public function getFilter()
{
return $this->filter;
}
public function clearFilter()
{
$this->filter = new Tree();
}
/**
* Return a pagination adapter for this query
*
* @return \Zend_Paginator
*/
public function paginate($limit = null, $page = null)
{
if ($page === null && $limit === null) {
$request = \Zend_Controller_Front::getInstance()->getRequest();
if ($page === null) {
$page = $request->getParam('page', 0);
}
if ($limit === null) {
$limit = $request->getParam('limit', 20);
}
}
$this->limit($limit, $page * $limit);
$paginator = new Zend_Paginator(new QueryAdapter($this));
$paginator->setItemCountPerPage($limit);
$paginator->setCurrentPageNumber($page);
return $paginator;
}
/**
* Parse a backend specific filter expression and return a Query\Node object
*
* @param $expression The expression to parse
* @param $parameters Optional parameters for the expression
*
* @return Node A query node or null if it's an invalid expression
*/
protected function parseFilterExpression($expression, $parameter = null)
{
$splitted = explode(' ', $expression, 3);
if (count($splitted) === 1 && $parameter) {
return Node::createOperatorNode(Node::OPERATOR_EQUALS, $splitted[0], $parameter);
} elseif (count($splitted) === 2 && $parameter) {
Node::createOperatorNode($splitted[0], $splitted[1], is_string($parameter));
return Node::createOperatorNode(Node::OPERATOR_EQUALS, $splitted[0], $parameter);
} elseif (count($splitted) === 3) {
if (trim($splitted[2]) === '?' && is_string($parameter)) {
return Node::createOperatorNode($splitted[1], $splitted[0], $parameter);
} else {
return Node::createOperatorNode($splitted[1], $splitted[0], $splitted[2]);
}
}
return null;
}
/**
* Total result size regardless of limit and offset
*
* @return int
*/
public function count()
{
return $this->ds->count($this);
}
/**
* Fetch result as an array of objects
*
* @return array
*/
public function fetchAll()
{
return $this->ds->fetchAll($this);
}
/**
* Fetch first result row
*
* @return object
*/
public function fetchRow()
{
return $this->ds->fetchRow($this);
}
/**
* Fetch first result column
*
* @return array
*/
public function fetchColumn()
{
return $this->ds->fetchColumn($this);
}
/**
* Fetch first column value from first result row
*
* @return mixed
*/
public function fetchOne()
{
return $this->ds->fetchOne($this);
}
/**
* Fetch result as a key/value pair array
*
* @return array
*/
public function fetchPairs()
{
return $this->ds->fetchPairs($this);
}
}