Repository: Check whether a column is queried from the correct table
refs #8826
This commit is contained in:
parent
399bbf0795
commit
053c9cdcb3
|
@ -101,7 +101,7 @@ class DbUserBackend extends DbRepository implements UserBackendInterface
|
|||
public function insert($table, array $data)
|
||||
{
|
||||
$newData['created_at'] = date('Y-m-d H:i:s');
|
||||
$newData = $this->requireStatementColumns($data);
|
||||
$newData = $this->requireStatementColumns($table, $data);
|
||||
|
||||
$values = array();
|
||||
foreach ($newData as $column => $_) {
|
||||
|
@ -138,9 +138,9 @@ class DbUserBackend extends DbRepository implements UserBackendInterface
|
|||
public function update($table, array $data, Filter $filter = null)
|
||||
{
|
||||
$newData['last_modified'] = date('Y-m-d H:i:s');
|
||||
$newData = $this->requireStatementColumns($data);
|
||||
$newData = $this->requireStatementColumns($table, $data);
|
||||
if ($filter) {
|
||||
$this->requireFilter($filter);
|
||||
$this->requireFilter($table, $filter);
|
||||
}
|
||||
|
||||
$set = array();
|
||||
|
|
|
@ -9,6 +9,7 @@ use Icinga\Data\Reducible;
|
|||
use Icinga\Data\Updatable;
|
||||
use Icinga\Exception\IcingaException;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Exception\StatementException;
|
||||
|
||||
/**
|
||||
* Abstract base class for concrete database repository implementations
|
||||
|
@ -101,6 +102,37 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the datasource's prefix from the given table name and return the remaining part
|
||||
*
|
||||
* @param mixed $table
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function removeTablePrefix($table)
|
||||
{
|
||||
$prefix = $this->ds->getTablePrefix();
|
||||
if (! $prefix) {
|
||||
return $table;
|
||||
}
|
||||
|
||||
if (is_array($table)) {
|
||||
foreach ($table as & $tableName) {
|
||||
if (strpos($tableName, $prefix) === 0) {
|
||||
$tableName = str_replace($prefix, '', $tableName);
|
||||
}
|
||||
}
|
||||
} elseif (is_string($table)) {
|
||||
if (strpos($table, $prefix) === 0) {
|
||||
$table = str_replace($prefix, '', $table);
|
||||
}
|
||||
} else {
|
||||
throw new IcingaException('Table prefix handling for type "%s" is not supported', type($table));
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a table row with the given data
|
||||
*
|
||||
|
@ -109,7 +141,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
*/
|
||||
public function insert($table, array $bind)
|
||||
{
|
||||
$this->ds->insert($this->prependTablePrefix($table), $this->requireStatementColumns($bind));
|
||||
$this->ds->insert($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,10 +154,10 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
public function update($table, array $bind, Filter $filter = null)
|
||||
{
|
||||
if ($filter) {
|
||||
$this->requireFilter($filter);
|
||||
$this->requireFilter($table, $filter);
|
||||
}
|
||||
|
||||
$this->ds->update($this->prependTablePrefix($table), $this->requireStatementColumns($bind), $filter);
|
||||
$this->ds->update($this->prependTablePrefix($table), $this->requireStatementColumns($table, $bind), $filter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,7 +169,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
public function delete($table, Filter $filter = null)
|
||||
{
|
||||
if ($filter) {
|
||||
$this->requireFilter($filter);
|
||||
$this->requireFilter($table, $filter);
|
||||
}
|
||||
|
||||
$this->ds->delete($this->prependTablePrefix($table), $filter);
|
||||
|
@ -216,17 +248,56 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
}
|
||||
|
||||
/**
|
||||
* Return whether the given column name or alias is a valid statement column
|
||||
* Return whether the given query column name or alias is available in the given table
|
||||
*
|
||||
* @param mixed $table
|
||||
* @param string $column
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateQueryColumnAssociation($table, $column)
|
||||
{
|
||||
if (is_array($table)) {
|
||||
$table = array_shift($table);
|
||||
}
|
||||
|
||||
return parent::validateQueryColumnAssociation($this->removeTablePrefix($table), $column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given statement column name or alias is available in the given table
|
||||
*
|
||||
* @param mixed $table
|
||||
* @param string $column
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateStatementColumnAssociation($table, $column)
|
||||
{
|
||||
if (is_array($table)) {
|
||||
$table = array_shift($table);
|
||||
}
|
||||
|
||||
$statementTableMap = $this->getStatementTableMap();
|
||||
return $statementTableMap[$column] === $this->removeTablePrefix($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given column name or alias of the given table is a valid statement column
|
||||
*
|
||||
* @param mixed $table The table where to look for the column or alias
|
||||
* @param string $name The column name or alias to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasStatementColumn($name)
|
||||
public function hasStatementColumn($table, $name)
|
||||
{
|
||||
$statementColumnMap = $this->getStatementColumnMap();
|
||||
if (! array_key_exists($name, $statementColumnMap)) {
|
||||
return parent::hasStatementColumn($name);
|
||||
if (
|
||||
! array_key_exists($name, $statementColumnMap)
|
||||
|| !$this->validateStatementColumnAssociation($table, $name)
|
||||
) {
|
||||
return parent::hasStatementColumn($table, $name);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -235,17 +306,22 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
|
|||
/**
|
||||
* Validate that the given column is a valid statement column and return it or the actual name if it's an alias
|
||||
*
|
||||
* @param mixed $table The table for which to require the column
|
||||
* @param string $name The name or alias of the column to validate
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
* @throws QueryException In case the given column is not a statement column
|
||||
* @throws StatementException In case the given column is not a statement column
|
||||
*/
|
||||
public function requireStatementColumn($name)
|
||||
public function requireStatementColumn($table, $name)
|
||||
{
|
||||
$statementColumnMap = $this->getStatementColumnMap();
|
||||
if (! array_key_exists($name, $statementColumnMap)) {
|
||||
return parent::requireStatementColumn($name);
|
||||
return parent::requireStatementColumn($table, $name);
|
||||
}
|
||||
|
||||
if (! $this->validateStatementColumnAssociation($table, $name)) {
|
||||
throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table);
|
||||
}
|
||||
|
||||
return $statementColumnMap[$name];
|
||||
|
|
|
@ -34,7 +34,7 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
|||
*/
|
||||
public function insert($target, array $data)
|
||||
{
|
||||
$newData = $this->requireStatementColumns($data);
|
||||
$newData = $this->requireStatementColumns($target, $data);
|
||||
$section = $this->extractSectionName($target, $newData);
|
||||
|
||||
if ($this->ds->hasSection($section)) {
|
||||
|
@ -65,7 +65,7 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
|||
*/
|
||||
public function update($target, array $data, Filter $filter = null)
|
||||
{
|
||||
$newData = $this->requireStatementColumns($data);
|
||||
$newData = $this->requireStatementColumns($target, $data);
|
||||
$keyColumn = $this->ds->getConfigObject()->getKeyColumn();
|
||||
if ($keyColumn && $filter === null && isset($newData[$keyColumn]) && !$this->ds->hasSection($target)) {
|
||||
throw new StatementException(
|
||||
|
@ -82,7 +82,7 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
|||
$contents = array($target => $this->ds->getSection($target));
|
||||
} else {
|
||||
if ($filter) {
|
||||
$this->requireFilter($filter);
|
||||
$this->requireFilter($target, $filter);
|
||||
}
|
||||
|
||||
$contents = iterator_to_array($this->ds);
|
||||
|
@ -148,7 +148,7 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
|||
$this->ds->removeSection($target);
|
||||
} else {
|
||||
if ($filter) {
|
||||
$this->requireFilter($filter);
|
||||
$this->requireFilter($target, $filter);
|
||||
}
|
||||
|
||||
foreach (iterator_to_array($this->ds) as $section => $config) {
|
||||
|
|
|
@ -574,63 +574,87 @@ abstract class Repository implements Selectable
|
|||
}
|
||||
|
||||
/**
|
||||
* Recurse the given filter, require each filter column and convert all values
|
||||
* Recurse the given filter, require each column for the given table and convert all values
|
||||
*
|
||||
* @param string $table
|
||||
* @param Filter $filter
|
||||
*/
|
||||
public function requireFilter(Filter $filter)
|
||||
public function requireFilter($table, Filter $filter)
|
||||
{
|
||||
if ($filter->isExpression()) {
|
||||
$column = $filter->getColumn();
|
||||
$filter->setColumn($this->requireFilterColumn($column));
|
||||
$filter->setColumn($this->requireFilterColumn($table, $column));
|
||||
$filter->setExpression($this->persistColumn($column, $filter->getExpression()));
|
||||
} elseif ($filter->isChain()) {
|
||||
foreach ($filter->filters() as $chainOrExpression) {
|
||||
$this->requireFilter($chainOrExpression);
|
||||
$this->requireFilter($table, $chainOrExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this repository's query columns mapped to their respective aliases
|
||||
* Return this repository's query columns of the given table mapped to their respective aliases
|
||||
*
|
||||
* @param string $table
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function requireAllQueryColumns()
|
||||
public function requireAllQueryColumns($table)
|
||||
{
|
||||
$map = array();
|
||||
foreach ($this->getAliasColumnMap() as $alias => $_) {
|
||||
if ($this->hasQueryColumn($alias)) {
|
||||
if ($this->hasQueryColumn($table, $alias)) {
|
||||
// Just in case $this->requireQueryColumn has been overwritten and there is some magic going on
|
||||
$map[$alias] = $this->requireQueryColumn($alias);
|
||||
$map[$alias] = $this->requireQueryColumn($table, $alias);
|
||||
}
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given query column name or alias is available in the given table
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $column
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validateQueryColumnAssociation($table, $column)
|
||||
{
|
||||
$aliasTableMap = $this->getAliasTableMap();
|
||||
return $aliasTableMap[$column] === $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given column name or alias is a valid query column
|
||||
*
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The column name or alias to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasQueryColumn($name)
|
||||
public function hasQueryColumn($table, $name)
|
||||
{
|
||||
return array_key_exists($name, $this->getAliasColumnMap()) && !in_array($name, $this->getFilterColumns());
|
||||
if (in_array($name, $this->getFilterColumns())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_key_exists($name, $this->getAliasColumnMap())
|
||||
&& $this->validateQueryColumnAssociation($table, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given column is a valid query target and return it or the actual name if it's an alias
|
||||
*
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The name or alias of the column to validate
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
* @throws QueryException In case the given column is not a valid query column
|
||||
*/
|
||||
public function requireQueryColumn($name)
|
||||
public function requireQueryColumn($table, $name)
|
||||
{
|
||||
if (in_array($name, $this->getFilterColumns())) {
|
||||
throw new QueryException(t('Filter column "%s" cannot be queried'), $name);
|
||||
|
@ -641,62 +665,75 @@ abstract class Repository implements Selectable
|
|||
throw new QueryException(t('Query column "%s" not found'), $name);
|
||||
}
|
||||
|
||||
return $aliasColumnMap[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given column name or alias is a valid filter column
|
||||
*
|
||||
* @param string $name The column name or alias to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFilterColumn($name)
|
||||
{
|
||||
return array_key_exists($name, $this->getAliasColumnMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given column is a valid filter target and return it or the actual name if it's an alias
|
||||
*
|
||||
* @param string $name The name or alias of the column to validate
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
* @throws QueryException In case the given column is not a valid filter column
|
||||
*/
|
||||
public function requireFilterColumn($name)
|
||||
{
|
||||
$aliasColumnMap = $this->getAliasColumnMap();
|
||||
if (! array_key_exists($name, $aliasColumnMap)) {
|
||||
throw new QueryException(t('Filter column "%s" not found'), $name);
|
||||
if (! $this->validateQueryColumnAssociation($table, $name)) {
|
||||
throw new QueryException(t('Query column "%s" not found in table "%s"'), $name, $table);
|
||||
}
|
||||
|
||||
return $aliasColumnMap[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given column name or alias is a valid statement column
|
||||
* Return whether the given column name or alias is a valid filter column
|
||||
*
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The column name or alias to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasStatementColumn($name)
|
||||
public function hasFilterColumn($table, $name)
|
||||
{
|
||||
return $this->hasQueryColumn($name);
|
||||
return array_key_exists($name, $this->getAliasColumnMap())
|
||||
&& $this->validateQueryColumnAssociation($table, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given column is a valid filter target and return it or the actual name if it's an alias
|
||||
*
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The name or alias of the column to validate
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
* @throws QueryException In case the given column is not a valid filter column
|
||||
*/
|
||||
public function requireFilterColumn($table, $name)
|
||||
{
|
||||
$aliasColumnMap = $this->getAliasColumnMap();
|
||||
if (! array_key_exists($name, $aliasColumnMap)) {
|
||||
throw new QueryException(t('Filter column "%s" not found'), $name);
|
||||
}
|
||||
|
||||
if (! $this->validateQueryColumnAssociation($table, $name)) {
|
||||
throw new QueryException(t('Filter column "%s" not found in table "%s"'), $name, $table);
|
||||
}
|
||||
|
||||
return $aliasColumnMap[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given column name or alias of the given table is a valid statement column
|
||||
*
|
||||
* @param string $table The table where to look for the column or alias
|
||||
* @param string $name The column name or alias to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasStatementColumn($table, $name)
|
||||
{
|
||||
return $this->hasQueryColumn($table, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the given column is a valid statement column and return it or the actual name if it's an alias
|
||||
*
|
||||
* @param string $table The table for which to require the column
|
||||
* @param string $name The name or alias of the column to validate
|
||||
*
|
||||
* @return string The given column's name
|
||||
*
|
||||
* @throws StatementException In case the given column is not a statement column
|
||||
*/
|
||||
public function requireStatementColumn($name)
|
||||
public function requireStatementColumn($table, $name)
|
||||
{
|
||||
if (in_array($name, $this->filterColumns)) {
|
||||
throw new StatementException('Filter column "%s" cannot be referenced in a statement', $name);
|
||||
|
@ -707,21 +744,26 @@ abstract class Repository implements Selectable
|
|||
throw new StatementException('Statement column "%s" not found', $name);
|
||||
}
|
||||
|
||||
if (! $this->validateQueryColumnAssociation($table, $name)) {
|
||||
throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table);
|
||||
}
|
||||
|
||||
return $aliasColumnMap[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given aliases or column names supposed to be persisted and convert their values
|
||||
* Resolve the given aliases or column names of the given table supposed to be persisted and convert their values
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function requireStatementColumns(array $data)
|
||||
public function requireStatementColumns($table, array $data)
|
||||
{
|
||||
$resolved = array();
|
||||
foreach ($data as $alias => $value) {
|
||||
$resolved[$this->requireStatementColumn($alias)] = $this->persistColumn($alias, $value);
|
||||
$resolved[$this->requireStatementColumn($table, $alias)] = $this->persistColumn($alias, $value);
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
|
|
|
@ -28,6 +28,13 @@ class RepositoryQuery implements QueryInterface
|
|||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* The current target to be queried
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* Create a new repository query
|
||||
*
|
||||
|
@ -54,14 +61,15 @@ class RepositoryQuery implements QueryInterface
|
|||
*
|
||||
* This notifies the repository about each desired query column.
|
||||
*
|
||||
* @param mixed $target The type and purpose of this parameter depends on this query's repository
|
||||
* @param mixed $target The target from which to fetch the columns
|
||||
* @param array $columns If null or an empty array, all columns will be fetched
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function from($target, array $columns = null)
|
||||
{
|
||||
$this->query->from($target, $this->prepareQueryColumns($columns));
|
||||
$this->query->from($target, $this->prepareQueryColumns($target, $columns));
|
||||
$this->target = $target;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -86,7 +94,7 @@ class RepositoryQuery implements QueryInterface
|
|||
*/
|
||||
public function columns(array $columns)
|
||||
{
|
||||
$this->query->columns($this->prepareQueryColumns($columns));
|
||||
$this->query->columns($this->prepareQueryColumns($this->target, $columns));
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -95,18 +103,19 @@ class RepositoryQuery implements QueryInterface
|
|||
*
|
||||
* This notifies the repository about each desired query column.
|
||||
*
|
||||
* @param mixed $target The target where to look for each column
|
||||
* @param array $desiredColumns Pass null or an empty array to require all query columns
|
||||
*
|
||||
* @return array The desired columns indexed by their respective alias
|
||||
*/
|
||||
protected function prepareQueryColumns(array $desiredColumns = null)
|
||||
protected function prepareQueryColumns($target, array $desiredColumns = null)
|
||||
{
|
||||
if (empty($desiredColumns)) {
|
||||
$columns = $this->repository->requireAllQueryColumns();
|
||||
$columns = $this->repository->requireAllQueryColumns($target);
|
||||
} else {
|
||||
$columns = array();
|
||||
foreach ($desiredColumns as $customAlias => $columnAlias) {
|
||||
$resolvedColumn = $this->repository->requireQueryColumn($columnAlias);
|
||||
$resolvedColumn = $this->repository->requireQueryColumn($target, $columnAlias);
|
||||
if ($resolvedColumn !== $columnAlias) {
|
||||
$columns[is_string($customAlias) ? $customAlias : $columnAlias] = $resolvedColumn;
|
||||
} elseif (is_string($customAlias)) {
|
||||
|
@ -133,7 +142,7 @@ class RepositoryQuery implements QueryInterface
|
|||
public function where($column, $value = null)
|
||||
{
|
||||
$this->query->where(
|
||||
$this->repository->requireFilterColumn($column),
|
||||
$this->repository->requireFilterColumn($this->target, $column),
|
||||
$this->repository->persistColumn($column, $value)
|
||||
);
|
||||
return $this;
|
||||
|
@ -164,7 +173,7 @@ class RepositoryQuery implements QueryInterface
|
|||
*/
|
||||
public function setFilter(Filter $filter)
|
||||
{
|
||||
$this->repository->requireFilter($filter);
|
||||
$this->repository->requireFilter($this->target, $filter);
|
||||
$this->query->setFilter($filter);
|
||||
return $this;
|
||||
}
|
||||
|
@ -180,7 +189,7 @@ class RepositoryQuery implements QueryInterface
|
|||
*/
|
||||
public function addFilter(Filter $filter)
|
||||
{
|
||||
$this->repository->requireFilter($filter);
|
||||
$this->repository->requireFilter($this->target, $filter);
|
||||
$this->query->addFilter($filter);
|
||||
return $this;
|
||||
}
|
||||
|
@ -245,7 +254,7 @@ class RepositoryQuery implements QueryInterface
|
|||
|
||||
try {
|
||||
$this->query->order(
|
||||
$this->repository->requireFilterColumn($column),
|
||||
$this->repository->requireFilterColumn($this->target, $column),
|
||||
$direction ? $baseDirection : ($specificDirection ?: $baseDirection)
|
||||
);
|
||||
} catch (QueryException $_) {
|
||||
|
|
Loading…
Reference in New Issue