From 730f0e56b58883006a1052834f75cd9ef9bc8f42 Mon Sep 17 00:00:00 2001 From: ntesic Date: Tue, 8 Feb 2022 14:51:38 +0100 Subject: [PATCH] Fix #18821: Allow `yii\db\ExpressionInterface` as column in `yii\db\conditions\InBuilder` --- framework/CHANGELOG.md | 1 + .../db/conditions/InConditionBuilder.php | 20 +++++++++++++++++++ .../mssql/conditions/InConditionBuilder.php | 7 +++++++ .../sqlite/conditions/InConditionBuilder.php | 7 +++++++ tests/framework/db/QueryBuilderTest.php | 4 ++++ 5 files changed, 39 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6297e334609..c3296558d8c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.45 under development ------------------------ +- Enh #18821: Allow `yii\db\ExpressionInterface` as column in `yii\db\conditions\InBuilder` (ntesic) - Bug #19182: RBAC Migration failed when use oracle with oci8 (Murolike) - Bug #19138: Allow digits in language code (ntesic) - Bug #19148: Fix undefined array key errors in `yii\db\ActiveRelationTrait` (stevekr) diff --git a/framework/db/conditions/InConditionBuilder.php b/framework/db/conditions/InConditionBuilder.php index dd238b7a7ce..9f09e57a701 100644 --- a/framework/db/conditions/InConditionBuilder.php +++ b/framework/db/conditions/InConditionBuilder.php @@ -66,6 +66,10 @@ public function build(ExpressionInterface $expression, array &$params = []) $column = $column->current(); } + if ($column instanceof ExpressionInterface) { + $column = $column->expression; + } + if (is_array($values)) { $rawValues = $values; } elseif ($values instanceof \Traversable) { @@ -124,6 +128,10 @@ protected function buildValues(ConditionInterface $condition, $values, &$params) $column = $column->current(); } + if ($column instanceof ExpressionInterface) { + $column = $column->expression; + } + foreach ($values as $i => $value) { if (is_array($value) || $value instanceof \ArrayAccess) { $value = isset($value[$column]) ? $value[$column] : null; @@ -155,6 +163,9 @@ protected function buildSubqueryInCondition($operator, $columns, $values, &$para if (is_array($columns)) { foreach ($columns as $i => $col) { + if ($col instanceof ExpressionInterface) { + $col = $col->expression; + } if (strpos($col, '(') === false) { $columns[$i] = $this->queryBuilder->db->quoteColumnName($col); } @@ -163,6 +174,9 @@ protected function buildSubqueryInCondition($operator, $columns, $values, &$para return '(' . implode(', ', $columns) . ") $operator $sql"; } + if ($columns instanceof ExpressionInterface) { + $columns = $columns->expression; + } if (strpos($columns, '(') === false) { $columns = $this->queryBuilder->db->quoteColumnName($columns); } @@ -185,6 +199,9 @@ protected function buildCompositeInCondition($operator, $columns, $values, &$par foreach ($values as $value) { $vs = []; foreach ($columns as $column) { + if ($column instanceof ExpressionInterface) { + $column = $column->expression; + } if (isset($value[$column])) { $vs[] = $this->queryBuilder->bindParam($value[$column], $params); } else { @@ -200,6 +217,9 @@ protected function buildCompositeInCondition($operator, $columns, $values, &$par $sqlColumns = []; foreach ($columns as $i => $column) { + if ($column instanceof ExpressionInterface) { + $column = $column->expression; + } $sqlColumns[] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column; } diff --git a/framework/db/mssql/conditions/InConditionBuilder.php b/framework/db/mssql/conditions/InConditionBuilder.php index 7892b545569..573848bdd0b 100644 --- a/framework/db/mssql/conditions/InConditionBuilder.php +++ b/framework/db/mssql/conditions/InConditionBuilder.php @@ -8,6 +8,7 @@ namespace yii\db\mssql\conditions; use yii\base\NotSupportedException; +use yii\db\ExpressionInterface; /** * {@inheritdoc} @@ -37,12 +38,18 @@ protected function buildCompositeInCondition($operator, $columns, $values, &$par { $quotedColumns = []; foreach ($columns as $i => $column) { + if ($column instanceof ExpressionInterface) { + $column = $column->expression; + } $quotedColumns[$i] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column; } $vss = []; foreach ($values as $value) { $vs = []; foreach ($columns as $i => $column) { + if ($column instanceof ExpressionInterface) { + $column = $column->expression; + } if (isset($value[$column])) { $phName = $this->queryBuilder->bindParam($value[$column], $params); $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName; diff --git a/framework/db/sqlite/conditions/InConditionBuilder.php b/framework/db/sqlite/conditions/InConditionBuilder.php index 9ad84558c50..03c0deb490a 100644 --- a/framework/db/sqlite/conditions/InConditionBuilder.php +++ b/framework/db/sqlite/conditions/InConditionBuilder.php @@ -8,6 +8,7 @@ namespace yii\db\sqlite\conditions; use yii\base\NotSupportedException; +use yii\db\ExpressionInterface; /** * {@inheritdoc} @@ -37,12 +38,18 @@ protected function buildCompositeInCondition($operator, $columns, $values, &$par { $quotedColumns = []; foreach ($columns as $i => $column) { + if ($column instanceof ExpressionInterface) { + $column = $column->expression; + } $quotedColumns[$i] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column; } $vss = []; foreach ($values as $value) { $vs = []; foreach ($columns as $i => $column) { + if ($column instanceof ExpressionInterface) { + $column = $column->expression; + } if (isset($value[$column])) { $phName = $this->queryBuilder->bindParam($value[$column], $params); $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName; diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 3f0fd800e70..88c6319dc7a 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -1184,6 +1184,7 @@ public function conditionProvider() //in using array object containing only null value [['in', 'id', new TraversableObject([null])], '[[id]] IS NULL', []], [['not in', 'id', new TraversableObject([null])], '[[id]] IS NOT NULL', []], + [['not in', new Expression('id'), new TraversableObject([null])], '[[id]] IS NOT NULL', []], 'composite in using array objects' => [ ['in', new TraversableObject(['id', 'name']), new TraversableObject([ @@ -1196,6 +1197,7 @@ public function conditionProvider() // in object conditions [new InCondition('id', 'in', 1), '[[id]]=:qp0', [':qp0' => 1]], + [new InCondition(new Expression('id'), 'in', 1), '[[id]]=:qp0', [':qp0' => 1]], [new InCondition('id', 'in', [1]), '[[id]]=:qp0', [':qp0' => 1]], [new InCondition('id', 'not in', 1), '[[id]]<>:qp0', [':qp0' => 1]], [new InCondition('id', 'not in', [1]), '[[id]]<>:qp0', [':qp0' => 1]], @@ -1237,6 +1239,7 @@ public function conditionProvider() case 'sqlite': $conditions = array_merge($conditions, [ [['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '(([[id]] = :qp0 AND [[name]] = :qp1) OR ([[id]] = :qp2 AND [[name]] = :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']], + [['in', [new Expression('id'), 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '(([[id]] = :qp0 AND [[name]] = :qp1) OR ([[id]] = :qp2 AND [[name]] = :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']], [['not in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '(([[id]] != :qp0 OR [[name]] != :qp1) AND ([[id]] != :qp2 OR [[name]] != :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']], //[ ['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'EXISTS (SELECT 1 FROM (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0) AS a WHERE a.[[id]] = [[id AND a.]]name[[ = ]]name`)', [':qp0' => 1] ], //[ ['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], 'NOT EXISTS (SELECT 1 FROM (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0) AS a WHERE a.[[id]] = [[id]] AND a.[[name = ]]name`)', [':qp0' => 1] ], @@ -1246,6 +1249,7 @@ public function conditionProvider() $conditions = array_merge($conditions, [ [['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '([[id]], [[name]]) IN ((:qp0, :qp1), (:qp2, :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']], [['not in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '([[id]], [[name]]) NOT IN ((:qp0, :qp1), (:qp2, :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']], + [['not in', [new Expression('id'), 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]], '([[id]], [[name]]) NOT IN ((:qp0, :qp1), (:qp2, :qp3))', [':qp0' => 1, ':qp1' => 'foo', ':qp2' => 2, ':qp3' => 'bar']], [['in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], '([[id]], [[name]]) IN (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1]], [['not in', ['id', 'name'], (new Query())->select(['id', 'name'])->from('users')->where(['active' => 1])], '([[id]], [[name]]) NOT IN (SELECT [[id]], [[name]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1]], ]);