Skip to content

Commit

Permalink
Fixed Oracle SQL queries with IN condition and more than 1000 param…
Browse files Browse the repository at this point in the history
…eters
  • Loading branch information
SilverFire committed May 1, 2017
1 parent 33cff4b commit 2d0e3fb
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 2 deletions.
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Yii Framework 2 Change Log
- Enh #14059: Removed unused AR instantiating for calling of static methods (ElisDN)
- Enh: Added `yii\di\Instance::__set_state()` method to restore object after serialization using `var_export()` function (silvefire)

- Bug #10305: Oracle SQL queries with `IN` condition and more than 1000 parameters are working now (silverfire)

2.0.11.2 February 08, 2017
--------------------------
Expand Down
52 changes: 52 additions & 0 deletions framework/db/oci/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -331,4 +331,56 @@ public function buildLikeCondition($operator, $operands, &$params)
}
return parent::buildLikeCondition($operator, $operands, $params);
}

public function buildInCondition($operator, $operands, &$params)
{
$splitCondition = $this->splitInCondition($operator, $operands, $params);
if ($splitCondition !== null) {
return $splitCondition;
}

return parent::buildInCondition($operator, $operands, $params);
}

/**
* Oracle DBMS does not support more than 1000 parameters in `IN` condition.
* This method splits long `IN` condition into series of smaller ones.
*
* @param string $operator
* @param array $operands
* @param array $params
* @return null|string null when split is not required. Otherwise - built SQL condition.
* @throws Exception
* @since 2.0.12
*/
protected function splitInCondition($operator, $operands, &$params)
{
if (!isset($operands[0], $operands[1])) {
throw new Exception("Operator '$operator' requires two operands.");
}

list($column, $values) = $operands;

if ($values instanceof \Traversable) {
$values = iterator_to_array($values);
}

if (!is_array($values)) {
return null;
}

$maxParameters = 1000;
$count = count($values);
if ($count <= $maxParameters) {
return null;
}

$condition = [($operator === 'IN') ? 'OR' : 'AND'];
for ($i = 0; $i < $count; $i += $maxParameters) {
$condition[] = [$operator, $column, array_slice($values, $i, $maxParameters)];
}

return $this->buildCondition(['AND', $condition], $params);
}

}
2 changes: 0 additions & 2 deletions tests/framework/db/mssql/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace yiiunit\framework\db\mssql;

use yii\db\Expression;
use yii\db\mssql\Schema;
use yii\db\Query;

/**
Expand Down
49 changes: 49 additions & 0 deletions tests/framework/db/oci/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace yiiunit\framework\db\oci;

use yii\db\oci\Schema;
use yiiunit\data\base\TraversableObject;

/**
* @group db
Expand Down Expand Up @@ -86,4 +87,52 @@ public function likeConditionProvider()
$this->likeParameterReplacements[$encodedBackslash] = '\\';
return parent::likeConditionProvider();
}

public function conditionProvider()
{
return array_merge(parent::conditionProvider(), [
[
['in', 'id', range(0, 2500)],

' ('
. '([[id]] IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 0, 999)) . '))'
. ' OR ([[id]] IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 1000, 1999)) . '))'
. ' OR ([[id]] IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 2000, 2500)) .'))'
. ')',

array_flip($this->generateSprintfSeries(':qp%d', 0, 2500))
],
[
['not in', 'id', range(0, 2500)],

'('
. '([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 0, 999)) . '))'
. ' AND ([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 1000, 1999)) . '))'
. ' AND ([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 2000, 2500)) .'))'
. ')',

array_flip($this->generateSprintfSeries(':qp%d', 0, 2500))
],
[
['not in', 'id', new TraversableObject(range(0, 2500))],

'('
. '([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 0, 999)) . '))'
. ' AND ([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 1000, 1999)) . '))'
. ' AND ([[id]] NOT IN (' . implode(', ', $this->generateSprintfSeries(':qp%d', 2000, 2500)) .'))'
. ')',

array_flip($this->generateSprintfSeries(':qp%d', 0, 2500))
],
]);
}

protected function generateSprintfSeries ($pattern, $from, $to) {
$items = [];
for ($i = $from; $i <= $to; $i++) {
$items[] = sprintf($pattern, $i);
}

return $items;
}
}

0 comments on commit 2d0e3fb

Please sign in to comment.