From e9fee2dad68d13d0d3ef2de97da64ee273abefdf Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Wed, 13 May 2015 13:27:08 +0200 Subject: [PATCH] Repository: Handle column name ambiguousness automatically refs #8826 --- library/Icinga/Repository/DbRepository.php | 103 ++++++++++++++++++--- library/Icinga/Repository/Repository.php | 90 ++++++++++++------ 2 files changed, 153 insertions(+), 40 deletions(-) diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php index 0e5a4c665..1e511abe7 100644 --- a/library/Icinga/Repository/DbRepository.php +++ b/library/Icinga/Repository/DbRepository.php @@ -252,19 +252,66 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ protected function initializeStatementMaps() { + $this->statementTableMap = array(); + $this->statementColumnMap = array(); foreach ($this->getStatementColumns() as $table => $columns) { foreach ($columns as $alias => $column) { - if (! is_string($alias)) { - $this->statementTableMap[$column] = $table; - $this->statementColumnMap[$column] = $column; + $key = is_string($alias) ? $alias : $column; + if (array_key_exists($key, $this->statementTableMap)) { + if ($this->statementTableMap[$key] !== null) { + $existingTable = $this->statementTableMap[$key]; + $existingColumn = $this->statementColumnMap[$key]; + $this->statementTableMap[$existingTable . '.' . $key] = $existingTable; + $this->statementColumnMap[$existingTable . '.' . $key] = $existingColumn; + $this->statementTableMap[$key] = null; + $this->statementColumnMap[$key] = null; + } + + $this->statementTableMap[$table . '.' . $key] = $table; + $this->statementColumnMap[$table . '.' . $key] = $column; } else { - $this->statementTableMap[$alias] = $table; - $this->statementColumnMap[$alias] = $column; + $this->statementTableMap[$key] = $table; + $this->statementColumnMap[$key] = $column; } } } } + /** + * Return this repository's query columns of the given table mapped to their respective aliases + * + * @param mixed $table + * + * @return array + * + * @throws ProgrammingError In case $table does not exist + */ + public function requireAllQueryColumns($table) + { + if (is_array($table)) { + $table = array_shift($table); + } + + return parent::requireAllQueryColumns($this->removeTablePrefix($table)); + } + + /** + * Return the query column name for the given alias or null in case the alias does not exist + * + * @param mixed $table + * @param string $alias + * + * @return string|null + */ + public function resolveQueryColumnAlias($table, $alias) + { + if (is_array($table)) { + $table = array_shift($table); + } + + return parent::resolveQueryColumnAlias($this->removeTablePrefix($table), $alias); + } + /** * Return whether the given query column name or alias is available in the given table * @@ -283,21 +330,51 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, } /** - * Return whether the given statement column name or alias is available in the given table + * Return the statement column name for the given alias or null in case the alias does not exist * * @param mixed $table - * @param string $column + * @param string $alias + * + * @return string|null + */ + public function resolveStatementColumnAlias($table, $alias) + { + if (is_array($table)) { + $table = array_shift($table); + } + + $statementColumnMap = $this->getStatementColumnMap(); + if (isset($statementColumnMap[$alias])) { + return $statementColumnMap[$alias]; + } + + $prefixedAlias = $table . '.' . $alias; + if (isset($statementColumnMap[$prefixedAlias])) { + return $statementColumnMap[$prefixedAlias]; + } + } + + /** + * Return whether the given alias or statement column name is available in the given table + * + * @param mixed $table + * @param string $alias * * @return bool */ - public function validateStatementColumnAssociation($table, $column) + public function validateStatementColumnAssociation($table, $alias) { if (is_array($table)) { $table = array_shift($table); } $statementTableMap = $this->getStatementTableMap(); - return $statementTableMap[$column] === $this->removeTablePrefix($table); + if (isset($statementTableMap[$alias])) { + return $statementTableMap[$alias] === $this->removeTablePrefix($table); + } + + $prefixedAlias = $this->removeTablePrefix($table) . '.' . $alias; + return isset($statementTableMap[$prefixedAlias]); } /** @@ -310,9 +387,8 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ public function hasStatementColumn($table, $name) { - $statementColumnMap = $this->getStatementColumnMap(); if ( - ! array_key_exists($name, $statementColumnMap) + $this->resolveStatementColumnAlias($table, $name) === null || !$this->validateStatementColumnAssociation($table, $name) ) { return parent::hasStatementColumn($table, $name); @@ -333,8 +409,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, */ public function requireStatementColumn($table, $name) { - $statementColumnMap = $this->getStatementColumnMap(); - if (! array_key_exists($name, $statementColumnMap)) { + if (($column = $this->resolveStatementColumnAlias($table, $name)) === null) { return parent::requireStatementColumn($table, $name); } @@ -342,6 +417,6 @@ abstract class DbRepository extends Repository implements Extensible, Updatable, throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table); } - return $statementColumnMap[$name]; + return $column; } } diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php index 24cd48161..58f199f1b 100644 --- a/library/Icinga/Repository/Repository.php +++ b/library/Icinga/Repository/Repository.php @@ -362,11 +362,27 @@ abstract class Repository implements Selectable foreach ($queryColumns as $table => $columns) { foreach ($columns as $alias => $column) { if (! is_string($alias)) { - $this->aliasTableMap[$column] = $table; - $this->aliasColumnMap[$column] = $column; + $key = $column; } else { - $this->aliasTableMap[$alias] = $table; - $this->aliasColumnMap[$alias] = preg_replace('~\n\s*~', ' ', $column); + $key = $alias; + $column = preg_replace('~\n\s*~', ' ', $column); + } + + if (array_key_exists($key, $this->aliasTableMap)) { + if ($this->aliasTableMap[$key] !== null) { + $existingTable = $this->aliasTableMap[$key]; + $existingColumn = $this->aliasColumnMap[$key]; + $this->aliasTableMap[$existingTable . '.' . $key] = $existingTable; + $this->aliasColumnMap[$existingTable . '.' . $key] = $existingColumn; + $this->aliasTableMap[$key] = null; + $this->aliasColumnMap[$key] = null; + } + + $this->aliasTableMap[$table . '.' . $key] = $table; + $this->aliasColumnMap[$table . '.' . $key] = $column; + } else { + $this->aliasTableMap[$key] = $table; + $this->aliasColumnMap[$key] = $column; } } } @@ -598,32 +614,57 @@ abstract class Repository implements Selectable * @param string $table * * @return array + * + * @throws ProgrammingError In case $table does not exist */ public function requireAllQueryColumns($table) { - $map = array(); - foreach ($this->getAliasColumnMap() as $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($table, $alias); - } + $queryColumns = $this->getQueryColumns(); + if (! array_key_exists($table, $queryColumns)) { + throw new ProgrammingError('Table name "%s" not found', $table); } - return $map; + return $queryColumns[$table]; } /** - * Return whether the given query column name or alias is available in the given table + * Return the query column name for the given alias or null in case the alias does not exist * * @param string $table - * @param string $column + * @param string $alias + * + * @return string|null + */ + public function resolveQueryColumnAlias($table, $alias) + { + $aliasColumnMap = $this->getAliasColumnMap(); + if (isset($aliasColumnMap[$alias])) { + return $aliasColumnMap[$alias]; + } + + $prefixedAlias = $table . '.' . $alias; + if (isset($aliasColumnMap[$prefixedAlias])) { + return $aliasColumnMap[$prefixedAlias]; + } + } + + /** + * Return whether the given alias or query column name is available in the given table + * + * @param string $table + * @param string $alias * * @return bool */ - public function validateQueryColumnAssociation($table, $column) + public function validateQueryColumnAssociation($table, $alias) { $aliasTableMap = $this->getAliasTableMap(); - return $aliasTableMap[$column] === $table; + if (isset($aliasTableMap[$alias])) { + return $aliasTableMap[$alias] === $table; + } + + $prefixedAlias = $table . '.' . $alias; + return isset($aliasTableMap[$prefixedAlias]); } /** @@ -640,7 +681,7 @@ abstract class Repository implements Selectable return false; } - return array_key_exists($name, $this->getAliasColumnMap()) + return $this->resolveQueryColumnAlias($table, $name) !== null && $this->validateQueryColumnAssociation($table, $name); } @@ -660,8 +701,7 @@ abstract class Repository implements Selectable throw new QueryException(t('Filter column "%s" cannot be queried'), $name); } - $aliasColumnMap = $this->getAliasColumnMap(); - if (! array_key_exists($name, $aliasColumnMap)) { + if (($column = $this->resolveQueryColumnAlias($table, $name)) === null) { throw new QueryException(t('Query column "%s" not found'), $name); } @@ -669,7 +709,7 @@ abstract class Repository implements Selectable throw new QueryException(t('Query column "%s" not found in table "%s"'), $name, $table); } - return $aliasColumnMap[$name]; + return $column; } /** @@ -682,7 +722,7 @@ abstract class Repository implements Selectable */ public function hasFilterColumn($table, $name) { - return array_key_exists($name, $this->getAliasColumnMap()) + return $this->resolveQueryColumnAlias($table, $name) !== null && $this->validateQueryColumnAssociation($table, $name); } @@ -698,8 +738,7 @@ abstract class Repository implements Selectable */ public function requireFilterColumn($table, $name) { - $aliasColumnMap = $this->getAliasColumnMap(); - if (! array_key_exists($name, $aliasColumnMap)) { + if (($column = $this->resolveQueryColumnAlias($table, $name)) === null) { throw new QueryException(t('Filter column "%s" not found'), $name); } @@ -707,7 +746,7 @@ abstract class Repository implements Selectable throw new QueryException(t('Filter column "%s" not found in table "%s"'), $name, $table); } - return $aliasColumnMap[$name]; + return $column; } /** @@ -739,8 +778,7 @@ abstract class Repository implements Selectable throw new StatementException('Filter column "%s" cannot be referenced in a statement', $name); } - $aliasColumnMap = $this->getAliasColumnMap(); - if (! array_key_exists($name, $aliasColumnMap)) { + if (($column = $this->resolveQueryColumnAlias($table, $name)) === null) { throw new StatementException('Statement column "%s" not found', $name); } @@ -748,7 +786,7 @@ abstract class Repository implements Selectable throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table); } - return $aliasColumnMap[$name]; + return $column; } /**