ソースを参照

Fix types being lost when translating tuple comparisons.

The surrogate query isn't aware of the types configured for the tuple
comparison fields (or any types for that matter), so they end up being
bound with a type of `null`.
ndm2 4 年 前
コミット
35c7163d57

+ 8 - 1
src/Database/Dialect/TupleComparisonTranslatorTrait.php

@@ -69,6 +69,13 @@ trait TupleComparisonTranslatorTrait
             return;
         }
 
+        $type = $expression->getType();
+        if ($type) {
+            $typeMap = array_combine($fields, $type);
+        } else {
+            $typeMap = [];
+        }
+
         $surrogate = $query->getConnection()
             ->newQuery()
             ->select($true);
@@ -85,7 +92,7 @@ trait TupleComparisonTranslatorTrait
             }
             $conditions['OR'][] = $item;
         }
-        $surrogate->where($conditions);
+        $surrogate->where($conditions, $typeMap);
 
         $expression->setField($true);
         $expression->setValue($surrogate);

+ 10 - 0
src/Database/Expression/TupleComparison.php

@@ -46,6 +46,16 @@ class TupleComparison extends Comparison
     }
 
     /**
+     * Returns the type to be used for casting the value to a database representation
+     *
+     * @return array
+     */
+    public function getType()
+    {
+        return $this->_type;
+    }
+
+    /**
      * Convert the expression into a SQL fragment.
      *
      * @param \Cake\Database\ValueBinder $generator Placeholder generator object

+ 82 - 0
tests/TestCase/Database/QueryTest.php

@@ -17,6 +17,7 @@ namespace Cake\Test\TestCase\Database;
 use Cake\Database\ExpressionInterface;
 use Cake\Database\Expression\IdentifierExpression;
 use Cake\Database\Expression\QueryExpression;
+use Cake\Database\Expression\TupleComparison;
 use Cake\Database\Query;
 use Cake\Database\StatementInterface;
 use Cake\Database\Statement\StatementDecorator;
@@ -3884,6 +3885,87 @@ class QueryTest extends TestCase
     }
 
     /**
+     * Tests that the values in tuple comparison expression are being bound correctly,
+     * specifically for dialects that translate tuple comparisons.
+     *
+     * @return void
+     * @see \Cake\Database\Dialect\TupleComparisonTranslatorTrait::_transformTupleComparison()
+     * @see \Cake\Database\Driver\Sqlite::_expressionTranslators()
+     * @see \Cake\Database\Driver\Sqlserver::_expressionTranslators()
+     */
+    public function testTupleComparisonValuesAreBeingBoundCorrectly()
+    {
+        // Load with force dropping tables to avoid identities not being reset properly
+        // in SQL Server when reseeding is applied directly after table creation.
+        $this->fixtureManager->loadSingle('Profiles', null, true);
+
+        $profiles = $this->getTableLocator()->get('Profiles');
+
+        $query = $profiles
+            ->find()
+            ->where(
+                new TupleComparison(
+                    ['id', 'user_id'],
+                    [[1, 1]],
+                    ['integer', 'integer'],
+                    'IN'
+                )
+            );
+
+        $result = $query->firstOrFail();
+
+        $bindings = array_values($query->getValueBinder()->bindings());
+        $this->assertCount(2, $bindings);
+        $this->assertSame(1, $bindings[0]['value']);
+        $this->assertSame('integer', $bindings[0]['type']);
+        $this->assertSame(1, $bindings[1]['value']);
+        $this->assertSame('integer', $bindings[1]['type']);
+
+        $this->assertSame(1, $result['id']);
+        $this->assertSame(1, $result['user_id']);
+    }
+
+    /**
+     * Tests that the values in tuple comparison expressions are being bound as expected
+     * when types are omitted, specifically for dialects that translate tuple comparisons.
+     *
+     * @return void
+     * @see \Cake\Database\Dialect\TupleComparisonTranslatorTrait::_transformTupleComparison()
+     * @see \Cake\Database\Driver\Sqlite::_expressionTranslators()
+     * @see \Cake\Database\Driver\Sqlserver::_expressionTranslators()
+     */
+    public function testTupleComparisonTypesCanBeOmitted()
+    {
+        // Load with force dropping tables to avoid identities not being reset properly
+        // in SQL Server when reseeding is applied directly after table creation.
+        $this->fixtureManager->loadSingle('Profiles', null, true);
+
+        $profiles = $this->getTableLocator()->get('Profiles');
+
+        $query = $profiles
+            ->find()
+            ->where(
+                new TupleComparison(
+                    ['id', 'user_id'],
+                    [[1, 1]],
+                    [],
+                    'IN'
+                )
+            );
+        $result = $query->firstOrFail();
+
+        $bindings = array_values($query->getValueBinder()->bindings());
+        $this->assertCount(2, $bindings);
+        $this->assertSame(1, $bindings[0]['value']);
+        $this->assertNull($bindings[0]['type']);
+        $this->assertSame(1, $bindings[1]['value']);
+        $this->assertNull($bindings[1]['type']);
+
+        $this->assertSame(1, $result['id']);
+        $this->assertSame(1, $result['user_id']);
+    }
+
+    /**
      * Tests that default types are passed to functions accepting a $types param
      *
      * @return void