diff --git a/library/Icinga/Data/BaseQuery.php b/library/Icinga/Data/BaseQuery.php
index ff66adedf..c84c66696 100644
--- a/library/Icinga/Data/BaseQuery.php
+++ b/library/Icinga/Data/BaseQuery.php
@@ -2,391 +2,54 @@
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 Countable;
+use Zend_Controller_Front;
use Zend_Paginator;
use Icinga\Web\Paginator\Adapter\QueryAdapter;
-abstract class BaseQuery implements Filterable
+abstract class BaseQuery implements Browsable, Fetchable, Filterable, Limitable, Queryable, Sortable, Countable
{
- /**
- * Sort ascending
- */
- const SORT_ASC = 1;
-
- /**
- * Sort descending
- */
- const SORT_DESC = -1;
-
/**
* Query data source
*
- * @var DatasourceInterface
+ * @var mixed
*/
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();
+ protected $order = array();
/**
- * Return not more than that many rows
+ * Number of rows to return
+ *
* @var int
*/
- private $limitCount;
+ protected $limitCount;
/**
* Result starts with this row
+ *
* @var int
*/
- private $limitOffset;
-
- /**
- * Whether its a distinct query or not
- *
- * @var bool
- */
- private $distinct = false;
-
- /**
- * The backend independent filter to use for this query
- *
- * @var Tree
- */
- private $filter;
+ protected $limitOffset;
/**
* Constructor
*
- * @param DatasourceInterface $ds Your data source
+ * @param mixed $ds
*/
- public function __construct(DatasourceInterface $ds, array $columns = null)
+ public function __construct($ds)
{
$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:
- *
- * $query->sort('column_name ASC')
- *
- *
- * @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;
- }
-
- /**
- * Set the columns used for ordering
- *
- * @param array $orderColumns
- */
- public function setOrderColumns(array $orderColumns)
- {
- $this->orderColumns = $orderColumns;
- }
-
- /**
- * 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;
- }
-
- /**
- * Return only distinct results
- *
- * @param bool $distinct Whether the query should be distinct or not
- *
- * @return BaseQuery
- */
- public function distinct($distinct = true)
- {
- $this->distinct = $distinct;
-
- return $this;
- }
-
- /**
- * Determine whether this query returns only distinct results
- *
- * @return bool True in case its a distinct query otherwise false
- */
- public function isDistinct()
- {
- return $this->distinct;
- }
-
- /**
- * 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
+ * Initialize query
*
* Overwrite this instead of __construct (it's called at the end of the construct) to
* implement custom initialization logic on construction time
@@ -396,103 +59,188 @@ abstract class BaseQuery implements Filterable
}
/**
- * Return all columns set in this query or the default columns if none are set
+ * Get the data source
*
- * @return array An array of columns
+ * @return mixed
*/
- public function getColumns()
+ public function getDatasource()
{
- 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 $this->ds;
}
/**
- * Return a pagination adapter for this query
+ * Add a where condition to the query by and
*
- * @return \Zend_Paginator
+ * The syntax of the condition and valid values are defined by the concrete backend-specific query implementation.
+ *
+ * @param string $condition
+ * @param mixed $value
+ *
+ * @return self
*/
- public function paginate($limit = null, $page = null)
- {
- if ($page === null || $limit === null) {
- $request = \Zend_Controller_Front::getInstance()->getRequest();
+ abstract public function where($condition, $value = null);
- if ($page === null) {
- $page = $request->getParam('page', 0);
+ /**
+ * Add a where condition to the query by or
+ *
+ * The syntax of the condition and valid values are defined by the concrete backend-specific query implementation.
+ *
+ * @param string $condition
+ * @param mixed $value
+ *
+ * @return self
+ */
+ abstract public function orWhere($condition, $value = null);
+
+ public function setOrderColumns(array $orderColumns)
+ {
+ throw new \Exception('This function does nothing and will be removed');
+ }
+
+ /**
+ * Sort result set by the given field (and direction)
+ *
+ * Preferred usage:
+ *
+ * $query->order('field, 'ASC')
+ *
+ *
+ * @param string $field
+ * @param int $direction
+ *
+ * @return self
+ */
+ public function order($field, $direction = null)
+ {
+ if ($direction === null) {
+ $fieldAndDirection = explode(' ', $field, 2);
+ if (count($fieldAndDirection) === 1) {
+ $direction = self::SORT_ASC;
+ } else {
+ $field = $fieldAndDirection[0];
+ $direction = (strtoupper(trim($fieldAndDirection[1])) === 'DESC') ?
+ Sortable::SORT_DESC : Sortable::SORT_ASC;
}
-
- if ($limit === null) {
- $limit = $request->getParam('limit', 20);
+ } else {
+ switch (($direction = strtoupper($direction))) {
+ case Sortable::SORT_ASC:
+ case Sortable::SORT_DESC:
+ break;
+ default:
+ $direction = Sortable::SORT_ASC;
+ break;
}
}
- $this->limit($limit, $page * $limit);
+ $this->order[] = array($field, $direction);
+ return $this;
+ }
+ /**
+ * Whether an order is set
+ *
+ * @return bool
+ */
+ public function hasOrder()
+ {
+ return !empty($this->order);
+ }
+
+ /**
+ * Get the order if any
+ *
+ * @return array|null
+ */
+ public function getOrder()
+ {
+ return $this->order;
+ }
+
+ /**
+ * Set a limit count and offset to the query
+ *
+ * @param int $count Number of rows to return
+ * @param int $offset Start returning after this many rows
+ *
+ * @return self
+ */
+ public function limit($count = null, $offset = null)
+ {
+ $this->limitCount = $count !== null ? (int) $count : null;
+ $this->limitOffset = (int) $offset;
+ return $this;
+ }
+
+ /**
+ * Whether a limit is set
+ *
+ * @return bool
+ */
+ public function hasLimit()
+ {
+ return $this->limitCount !== null;
+ }
+
+ /**
+ * Get the limit if any
+ *
+ * @return int|null
+ */
+ public function getLimit()
+ {
+ return $this->limitCount;
+ }
+
+ /**
+ * Whether an offset is set
+ *
+ * @return bool
+ */
+ public function hasOffset()
+ {
+ return $this->limitOffset > 0;
+ }
+
+ /**
+ * Get the offset if any
+ *
+ * @return int|null
+ */
+ public function getOffset()
+ {
+ return $this->limitOffset;
+ }
+
+ /**
+ * Paginate data
+ *
+ * Auto-detects pagination parameters from request when unset
+ *
+ * @param int $itemsPerPage Number of items per page
+ * @param int $pageNumber Current page number
+ *
+ * @return Zend_Paginator
+ */
+ public function paginate($itemsPerPage = null, $pageNumber = null)
+ {
+ if ($itemsPerPage === null || $pageNumber === null) {
+ // Detect parameters from request
+ $request = Zend_Controller_Front::getInstance()->getRequest();
+ if ($itemsPerPage === null) {
+ $itemsPerPage = $request->getParam('limit', 20);
+ }
+ if ($pageNumber === null) {
+ $pageNumber = $request->getParam('page', 0);
+ }
+ }
+ $this->limit($itemsPerPage, $pageNumber * $itemsPerPage);
$paginator = new Zend_Paginator(new QueryAdapter($this));
-
- $paginator->setItemCountPerPage($limit);
- $paginator->setCurrentPageNumber($page);
-
+ $paginator->setItemCountPerPage($itemsPerPage);
+ $paginator->setCurrentPageNumber($pageNumber);
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]) === '?') {
- 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
+ * Retrieve an array containing all rows of the result set
*
* @return array
*/
@@ -502,9 +250,9 @@ abstract class BaseQuery implements Filterable
}
/**
- * Fetch first result row
+ * Fetch the first row of the result set
*
- * @return object
+ * @return mixed
*/
public function fetchRow()
{
@@ -512,19 +260,21 @@ abstract class BaseQuery implements Filterable
}
/**
- * Fetch first result column
+ * Fetch a column of all rows of the result set as an array
*
- * @return array
+ * @param int $columnIndex Index of the column to fetch
+ *
+ * @return array
*/
- public function fetchColumn()
+ public function fetchColumn($columnIndex = 0)
{
- return $this->ds->fetchColumn($this);
+ return $this->ds->fetchColumn($this, $columnIndex);
}
/**
- * Fetch first column value from first result row
+ * Fetch the first column of the first row of the result set
*
- * @return mixed
+ * @return string
*/
public function fetchOne()
{
@@ -532,7 +282,9 @@ abstract class BaseQuery implements Filterable
}
/**
- * Fetch result as a key/value pair array
+ * Fetch all rows of the result set as an array of key-value pairs
+ *
+ * The first column is the key, the second column is the value.
*
* @return array
*/
@@ -540,4 +292,23 @@ abstract class BaseQuery implements Filterable
{
return $this->ds->fetchPairs($this);
}
+
+ /**
+ * Count all rows of the result set
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->ds->count($this);
+ }
+
+ /**
+ * Set columns
+ *
+ * @param array $columns
+ *
+ * @return self
+ */
+ abstract public function columns(array $columns);
}