IdoQuery: Fix that PostgreSQL queries use LOWER() on non-CI columns

refs #10364
refs #9954
This commit is contained in:
Johannes Meyer 2015-11-11 14:33:00 +01:00
parent e58d747d4a
commit 0b2b1c5d1e
1 changed files with 33 additions and 49 deletions

View File

@ -55,14 +55,14 @@ abstract class IdoQuery extends DbQuery
protected $prefix; protected $prefix;
/** /**
* An array to map aliases to table names * An array to map aliases to column names
* *
* @var array * @var array
*/ */
protected $idxAliasColumn; protected $idxAliasColumn;
/** /**
* An array to map aliases to column names * An array to map aliases to table names
* *
* @var array * @var array
*/ */
@ -400,14 +400,14 @@ abstract class IdoQuery extends DbQuery
protected static $idoVersion; protected static $idoVersion;
/** /**
* List of columns where the COLLATE SQL-instruction has been removed * List of column aliases mapped to their table where the COLLATE SQL-instruction has been removed
* *
* This list is being populated in case of a PostgreSQL backend only, * This list is being populated in case of a PostgreSQL backend only,
* to ensure case-insensitive string comparison in WHERE clauses. * to ensure case-insensitive string comparison in WHERE clauses.
* *
* @var array * @var array
*/ */
protected $columnsWithoutCollation = array(); protected $caseInsensitiveColumns;
/** /**
* Return true when the column is an aggregate column * Return true when the column is an aggregate column
@ -493,6 +493,15 @@ abstract class IdoQuery extends DbQuery
$column = $this->getCustomvarColumnName($alias); $column = $this->getCustomvarColumnName($alias);
} else { } else {
$column = $this->aliasToColumnName($alias); $column = $this->aliasToColumnName($alias);
if (isset($this->caseInsensitiveColumns[$this->aliasToTableName($alias)][$alias])) {
$column = 'LOWER(' . $column . ')';
$expression = $filter->getExpression();
if (is_array($expression)) {
$filter->setExpression(array_map('strtolower', $expression));
} else {
$filter->setExpression(strtolower($expression));
}
}
} }
$filter->setColumn($column); $filter->setColumn($column);
@ -513,42 +522,6 @@ abstract class IdoQuery extends DbQuery
return parent::addFilter($filter); return parent::addFilter($filter);
} }
/**
* Recurse the given filter and ensure that any string conversion is case-insensitive
*
* @param Filter $filter
*/
protected function lowerColumnsWithoutCollation(Filter $filter)
{
if ($filter instanceof FilterExpression) {
if (
in_array($filter->getColumn(), $this->columnsWithoutCollation)
&& strpos($filter->getColumn(), 'LOWER') !== 0
) {
$filter->setColumn('LOWER(' . $filter->getColumn() . ')');
$expression = $filter->getExpression();
if (is_array($expression)) {
$filter->setExpression(array_map('strtolower', $expression));
} else {
$filter->setExpression(strtolower($expression));
}
}
} else {
foreach ($filter->filters() as $chainedFilter) {
$this->lowerColumnsWithoutCollation($chainedFilter);
}
}
}
protected function applyFilterSql($select)
{
if (! empty($this->columnsWithoutCollation)) {
$this->lowerColumnsWithoutCollation($this->filter);
}
parent::applyFilterSql($select);
}
public function where($condition, $value = null) public function where($condition, $value = null)
{ {
if ($value === '*') { if ($value === '*') {
@ -582,28 +555,37 @@ abstract class IdoQuery extends DbQuery
} }
/** /**
* Return whether the given alias or column name provides case insensitive value comparison * Return whether the given alias provides case insensitive value comparison
* *
* @param string $aliasOrColumn * @param string $alias
* *
* @return bool * @return bool
*/ */
public function isCaseInsensitive($aliasOrColumn) public function isCaseInsensitive($alias)
{ {
if ($this->isCustomVar($aliasOrColumn)) { if ($this->isCustomVar($alias)) {
return false; return false;
} }
$column = $this->getMappedField($aliasOrColumn) ?: $aliasOrColumn; $column = $this->getMappedField($alias);
if (! $column) { if (! $column) {
return false; return false;
} }
if (! empty($this->columnsWithoutCollation)) { if (empty($this->caseInsensitiveColumns)) {
return in_array($column, $this->columnsWithoutCollation) || strpos($column, 'LOWER') === 0; return preg_match('/ COLLATE .+$/', $column) === 1;
} }
return preg_match('/ COLLATE .+$/', $column) === 1; if (strpos($column, 'LOWER') === 0) {
return true;
}
$table = $this->aliasToTableName($alias);
if (! $table) {
return false;
}
return isset($this->caseInsensitiveColumns[$table][$alias]);
} }
/** /**
@ -635,10 +617,12 @@ abstract class IdoQuery extends DbQuery
'%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s'; '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s';
foreach ($this->columnMap as $table => & $columns) { foreach ($this->columnMap as $table => & $columns) {
foreach ($columns as $alias => & $column) { foreach ($columns as $alias => & $column) {
// Using a regex here because COLLATE may occur anywhere in the string
$column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count); $column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
if ($count > 0) { if ($count > 0) {
$this->columnsWithoutCollation[] = $this->getMappedField($alias); $this->caseInsensitiveColumns[$table][$alias] = true;
} }
$column = preg_replace( $column = preg_replace(
'/inet_aton\(([[:word:].]+)\)/i', '/inet_aton\(([[:word:].]+)\)/i',
'(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)', '(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)',