DbRepository: Add support for join probabilities

Finally solves the issue with those nasty column prefixes..
This commit is contained in:
Johannes Meyer 2017-06-06 09:27:13 +02:00
parent 7482a34b45
commit b8ae738f7f
2 changed files with 84 additions and 13 deletions

View File

@ -49,6 +49,25 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
*/ */
protected $tableAliases; protected $tableAliases;
/**
* The join probability rules
*
* This may be initialized by repositories which make use of the table join capability. It allows to define
* probability rules to enhance control how ambiguous column aliases are associated with the correct table.
* To define a rule use the name of a base table as key and another array of table names as probable join
* targets ordered by priority. (Ascending: Lower means higher priority)
* <code>
* array(
* 'table_name' => array('target1', 'target2', 'target3')
* )
* </code>
*
* @todo Support for tree-ish rules
*
* @var array
*/
protected $joinProbabilities;
/** /**
* The statement columns being provided * The statement columns being provided
* *
@ -168,6 +187,32 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
return array(); return array();
} }
/**
* Return the join probability rules
*
* Calls $this->initializeJoinProbabilities() in case $this->joinProbabilities is null.
*
* @return array
*/
public function getJoinProbabilities()
{
if ($this->joinProbabilities === null) {
$this->joinProbabilities = $this->initializeJoinProbabilities();
}
return $this->joinProbabilities;
}
/**
* Overwrite this in your repository implementation in case you need to initialize the join probabilities lazily
*
* @return array
*/
protected function initializeJoinProbabilities()
{
return array();
}
/** /**
* Remove each COLLATE SQL-instruction from all given query columns * Remove each COLLATE SQL-instruction from all given query columns
* *
@ -513,7 +558,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
return parent::providesValueConversion($table, $column); return parent::providesValueConversion($table, $column);
} }
if (($tableName = $this->findTableName($column))) { if (($tableName = $this->findTableName($column, $table))) {
return parent::providesValueConversion($tableName, $column); return parent::providesValueConversion($tableName, $column);
} }
@ -549,7 +594,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
if (! ($query !== null && $this->validateQueryColumnAssociation($table, $name)) if (! ($query !== null && $this->validateQueryColumnAssociation($table, $name))
&& !($query === null && $this->validateStatementColumnAssociation($table, $name)) && !($query === null && $this->validateStatementColumnAssociation($table, $name))
) { ) {
$table = $this->findTableName($name); $table = $this->findTableName($name, $table);
if (! $table) { if (! $table) {
if ($query !== null) { if ($query !== null) {
// It may be an aliased Zend_Db_Expr // It may be an aliased Zend_Db_Expr
@ -629,7 +674,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
$alias = parent::reassembleQueryColumnAlias($table, $column); $alias = parent::reassembleQueryColumnAlias($table, $column);
if ($alias === null if ($alias === null
&& !$this->validateQueryColumnAssociation($table, $column) && !$this->validateQueryColumnAssociation($table, $column)
&& ($tableName = $this->findTableName($column)) && ($tableName = $this->findTableName($column, $table))
) { ) {
return parent::reassembleQueryColumnAlias($tableName, $column); return parent::reassembleQueryColumnAlias($tableName, $column);
} }
@ -739,7 +784,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
if (! empty($this->caseInsensitiveColumns)) { if (! empty($this->caseInsensitiveColumns)) {
if ($joined) { if ($joined) {
$table = $this->findTableName($name); $table = $this->findTableName($name, $table);
} }
if ($column === $name) { if ($column === $name) {
@ -898,7 +943,7 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
*/ */
public function joinColumn($name, $target, RepositoryQuery $query) public function joinColumn($name, $target, RepositoryQuery $query)
{ {
if (! ($tableName = $this->findTableName($name))) { if (! ($tableName = $this->findTableName($name, $target))) {
return; return;
} }
@ -930,24 +975,45 @@ abstract class DbRepository extends Repository implements Extensible, Updatable,
/** /**
* Return the table name for the given alias or column name * Return the table name for the given alias or column name
* *
* @param string $column * @param string $column The alias or column name
* @param string $origin The base table of a SELECT query
* *
* @return string|null null in case no table is found * @return string|null null in case no table is found
*/ */
protected function findTableName($column) protected function findTableName($column, $origin)
{ {
// First, try to produce an exact match since it's faster and cheaper
$aliasTableMap = $this->getAliasTableMap(); $aliasTableMap = $this->getAliasTableMap();
if (isset($aliasTableMap[$column])) { if (isset($aliasTableMap[$column])) {
return $aliasTableMap[$column]; $table = $aliasTableMap[$column];
} else {
$columnTableMap = $this->getColumnTableMap();
if (isset($columnTableMap[$column])) {
$table = $columnTableMap[$column];
}
} }
$columnTableMap = $this->getColumnTableMap(); // But only return it if it's a probable join...
if (isset($columnTableMap[$column])) { $joinProbabilities = $this->getJoinProbabilities();
return $columnTableMap[$column]; if (isset($joinProbabilities[$origin])) {
$probableJoins = $joinProbabilities[$origin];
} }
// TODO(jom): Elaborate whether it makes sense to throw ProgrammingError // ...if probability can be determined
// instead (duplicate aliases in different tables?) if (isset($table) && (empty($probableJoins) || in_array($table, $probableJoins, true))) {
return $table;
}
// Without a proper exact match, there is only one fast and cheap way to find a suitable table..
if (! empty($probableJoins)) {
foreach ($probableJoins as $table) {
if (isset($aliasTableMap[$table . '.' . $column])) {
return $table;
}
}
}
// Last chance to find a table. Though, this usually ends up with a QueryException..
foreach ($aliasTableMap as $prefixedAlias => $table) { foreach ($aliasTableMap as $prefixedAlias => $table) {
if (strpos($prefixedAlias, '.') !== false) { if (strpos($prefixedAlias, '.') !== false) {
list($_, $alias) = explode('.', $prefixedAlias, 2); list($_, $alias) = explode('.', $prefixedAlias, 2);

View File

@ -644,6 +644,11 @@ abstract class Repository implements Selectable
} }
foreach ($queryColumns as $table => $columns) { foreach ($queryColumns as $table => $columns) {
foreach ($columns as $alias => $column) {
$alias = is_string($alias) ? $alias : $column;
$columns[$table . '_' . $alias] = $column;
}
foreach ($columns as $alias => $column) { foreach ($columns as $alias => $column) {
if (! is_string($alias)) { if (! is_string($alias)) {
$key = $column; $key = $column;