Commit 40ae780a authored by alexpott's avatar alexpott

Issue #2443679 by bzrudi71, daffie, mradcliffe, alexpott: PostgreSQL: Fix taxonomy\Tests\TermTest

parent 200e1b88
<?php
/**
* @file
* Contains Drupal\Core\Database\Driver\mysql\EntityQuery\Condition
*/
namespace Drupal\Core\Database\Driver\mysql\EntityQuery;
use Drupal\Core\Database\EntityQuery\ConditionInterface as EntityQueryConditionInterface;
/**
* Implements entity query conditions for SQL databases.
*/
class Condition implements EntityQueryConditionInterface {
/**
* {@inheritdoc}
*/
public static function translateCondition(array &$condition, $case_sensitive) { }
}
<?php
/**
* @file
* Contains Drupal\Core\Database\Driver\pgsql\EntityQuery\Condition
*/
namespace Drupal\Core\Database\Driver\pgsql\EntityQuery;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\EntityQuery\ConditionInterface as EntityQueryConditionInterface;
/**
* Implements entity query conditions for SQL databases.
*/
class Condition implements EntityQueryConditionInterface {
/**
* {@inheritdoc}
*/
public static function translateCondition(array &$condition, $case_sensitive) {
$connection = Database::getConnection();
// For PostgreSQL all the condition arguments need to have case
// lowered to support not case sensitive fields.
if (is_array($condition['value']) && $case_sensitive === FALSE) {
$condition['where'] = 'LOWER(' . $connection->escapeField($condition['real_field']) . ') ' . $condition['operator'] . ' (';
$condition['where_args'] = [];
$n = 1;
// Only use the array values in case an associative array is passed as an
// argument following similar pattern in
// \Drupal\Core\Database\Connection::expandArguments().
foreach ($condition['value'] as $value) {
$condition['where'] .= 'LOWER(:value' . $n . '),';
$condition['where_args'][':value' . $n] = $value;
$n++;
}
$condition['where'] = trim($condition['where'], ',');
$condition['where'] .= ')';
}
}
}
<?php
/**
* @file
* Contains Drupal\Core\Database\Driver\sqlite\EntityQuery\Condition
*/
namespace Drupal\Core\Database\Driver\sqlite\EntityQuery;
use Drupal\Core\Database\EntityQuery\ConditionInterface as EntityQueryConditionInterface;
/**
* Implements entity query conditions for SQL databases.
*/
class Condition implements EntityQueryConditionInterface {
/**
* {@inheritdoc}
*/
public static function translateCondition(array &$condition, $case_sensitive) { }
}
<?php
/**
* @file
* Contains \Drupal\Core\Database\EntityQuery\ConditionInterface.
*/
namespace Drupal\Core\Database\EntityQuery;
/**
* Provides an interface for entity query conditions for SQL databases.
*/
interface ConditionInterface {
/**
* Translates the string operators to SQL equivalents. This is a no-op method
* for MySQL and SQLite databases but allows override if needed, e.g. for
* PostgreSQL to support case insensitive queries.
*
* @param array $condition
* The condition array.
* @param bool|null $case_sensitive
* If the condition should be case sensitive or not, NULL if the field does
* not define it.
*
* @see \Drupal\Core\Entity\Query\Sql\Condition::$entityQueryCondition
*/
public static function translateCondition(array &$condition, $case_sensitive);
}
......@@ -7,10 +7,11 @@
namespace Drupal\Core\Entity\Query\Sql;
use Drupal\Core\Entity\Query\ConditionBase;
use Drupal\Core\Entity\Query\ConditionInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Database\Query\Condition as SqlCondition;
use Drupal\Core\Entity\Query\ConditionBase;
use Drupal\Core\Entity\Query\ConditionInterface;
/**
* Implements entity query conditions for SQL databases.
......@@ -28,6 +29,7 @@ class Condition extends ConditionBase {
* {@inheritdoc}
*/
public function compile($conditionContainer) {
// If this is not the top level condition group then the sql query is
// added to the $conditionContainer object by this function itself. The
// SQL query object is only necessary to pass to Query::addField() so it
......@@ -46,8 +48,16 @@ public function compile($conditionContainer) {
else {
$type = strtoupper($this->conjunction) == 'OR' || $condition['operator'] == 'IS NULL' ? 'LEFT' : 'INNER';
$field = $tables->addField($condition['field'], $type, $condition['langcode']);
$condition['real_field'] = $field;
static::translateCondition($condition, $sql_query, $tables->isFieldCaseSensitive($condition['field']));
$conditionContainer->condition($field, $condition['value'], $condition['operator']);
// Add the translated conditions back to the condition container.
if (isset($condition['where']) && isset($condition['where_args'])) {
$conditionContainer->where($condition['where'], $condition['where_args']);
}
else {
$conditionContainer->condition($field, $condition['value'], $condition['operator']);
}
}
}
}
......@@ -80,10 +90,16 @@ public function notExists($field, $langcode = NULL) {
* @see \Drupal\Core\Database\Query\ConditionInterface::condition()
*/
public static function translateCondition(&$condition, SelectInterface $sql_query, $case_sensitive) {
// There is nothing we can do for IN ().
// There is nothing to do for IN () queries except for PostgreSQL for which
// the condition arguments need to have case lowered to support not case
// sensitive fields.
if (is_array($condition['value'])) {
$entityQueryCondition = Database::getConnection()->getDriverClass('EntityQuery\\Condition');
$entityQueryCondition::translateCondition($condition, $case_sensitive);
return;
}
// Ensure that the default operator is set to simplify the cases below.
if (empty($condition['operator'])) {
$condition['operator'] = '=';
......
......@@ -210,6 +210,26 @@ function testEntityQuery() {
// Unit 0 and unit 1, so bits 0 1.
$this->assertResult(3, 7, 11, 15);
// Do the same test but with IN operator.
$query = $this->factory->get('entity_test_mulrev');
$group_blue = $query->andConditionGroup()->condition("$figures.color", array('blue'), 'IN');
$group_red = $query->andConditionGroup()->condition("$figures.color", array('red'), 'IN');
$query
->condition($group_blue)
->condition($group_red)
->sort('id')
->execute();
// Unit 0 and unit 1, so bits 0 1.
$this->assertResult(3, 7, 11, 15);
// An entity might have either red or blue figure.
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition("$figures.color", array('blue', 'red'), 'IN')
->sort('id')
->execute();
// Bit 0 or 1 is on.
$this->assertResult(1, 2, 3, 5, 6, 7, 9, 10, 11, 13, 14, 15);
$this->queryResults = $this->factory->get('entity_test_mulrev')
->exists("$figures.color")
->notExists("$greetings.value")
......@@ -613,6 +633,38 @@ public function testCaseSensitivity() {
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, exact match.');
// Check the case insensitive field, IN operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', array($fixtures[0]['lowercase'] . $fixtures[1]['lowercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, lowercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', array($fixtures[0]['uppercase'] . $fixtures[1]['uppercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, uppercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', array($fixtures[0]['uppercase'] . $fixtures[1]['lowercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 1, 'Case insensitive, mixed');
// Check the case sensitive field, IN operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', array($fixtures[0]['lowercase'] . $fixtures[1]['lowercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, lowercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', array($fixtures[0]['uppercase'] . $fixtures[1]['uppercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 0, 'Case sensitive, uppercase');
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_cs', array($fixtures[0]['uppercase'] . $fixtures[1]['lowercase']), 'IN'
)->execute();
$this->assertIdentical(count($result), 1, 'Case sensitive, mixed');
// Check the case insensitive field, STARTS_WITH operator.
$result = \Drupal::entityQuery('entity_test_mulrev')->condition(
'field_ci', $fixtures[0]['lowercase'], 'STARTS_WITH'
......@@ -762,4 +814,20 @@ public function testForwardRevisions() {
$this->assertEqual($result, [16 => '14']);
}
/**
* Test against SQL inject of condition field. This covers a
* database driver's EntityQuery\Condition class.
*/
public function testInjectionInCondition() {
try {
$this->queryResults = $this->factory->get('entity_test_mulrev')
->condition('1 ; -- ', array(0, 1), 'IN')
->sort('id')
->execute();
$this->fail('SQL Injection attempt in Entity Query condition in operator should result in an exception.');
}
catch (\Exception $e) {
$this->pass('SQL Injection attempt in Entity Query condition in operator should result in an exception.');
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment