Browse Source

Making Comparison correctly traverse expressions in array values
Started testing the type conversion to expressions

Jose Lorenzo Rodriguez 10 years ago
parent
commit
98a5267553

+ 3 - 0
src/Database/Expression/CaseExpression.php

@@ -15,6 +15,7 @@
 namespace Cake\Database\Expression;
 
 use Cake\Database\ExpressionInterface;
+use Cake\Database\Type\TypeExpressionCasterTrait;
 use Cake\Database\ValueBinder;
 
 /**
@@ -25,6 +26,8 @@ use Cake\Database\ValueBinder;
 class CaseExpression implements ExpressionInterface
 {
 
+    use TypeExpressionCasterTrait;
+
     /**
      * A list of strings or other expression objects that represent the conditions of
      * the case statement. For example one key of the array might look like "sum > :value"

+ 37 - 2
src/Database/Expression/Comparison.php

@@ -53,6 +53,10 @@ class Comparison implements ExpressionInterface, FieldInterface
      */
     protected $_operator;
 
+    protected $_isMultiple = false;
+
+    protected $_valueExpressions = [];
+
     /**
      * Constructor
      *
@@ -80,10 +84,18 @@ class Comparison implements ExpressionInterface, FieldInterface
      */
     public function setValue($value)
     {
-        if (isset($this->_type)) {
+        $hasType = isset($this->_type);
+        $isMultiple = $hasType && strpos($this->_type, '[]') !== false;
+
+        if ($hasType) {
             $value = $this->_castToExpression($value, $this->_type);
         }
 
+        if ($isMultiple) {
+            $this->_valueExpressions = $this->_collectExpressions($value);
+        }
+
+        $this->_isMultiple = $isMultiple;
         $this->_value = $value;
     }
 
@@ -157,6 +169,13 @@ class Comparison implements ExpressionInterface, FieldInterface
             $callable($this->_value);
             $this->_value->traverse($callable);
         }
+
+        if (!empty($this->_valueExpressions)) {
+            foreach ($this->_valueExpressions as $v) {
+                $callable($v);
+                $v->traverse($callable);
+            }
+        }
     }
 
     /**
@@ -190,7 +209,7 @@ class Comparison implements ExpressionInterface, FieldInterface
             $template = '(%s) ';
         }
 
-        if (strpos($this->_type, '[]') !== false) {
+        if ($this->_isMultiple) {
             $template .= '%s (%s)';
             $type = str_replace('[]', '', $this->_type);
             $value = $this->_flattenValue($this->_value, $generator, $type);
@@ -239,9 +258,25 @@ class Comparison implements ExpressionInterface, FieldInterface
     {
         $parts = [];
         foreach ($value as $k => $v) {
+            if (isset($this->_valueExpressions[$k])) {
+                $parts[] = $this->_valueExpressions[$k]->sql($generator);
+                continue;
+            }
             $parts[] = $this->_bindValue($v, $generator, $type);
         }
 
         return implode(',', $parts);
     }
+
+    protected function _collectExpressions($values)
+    {
+        $result = [];
+        foreach ($values as $k => $v) {
+            if ($v instanceof ExpressionInterface) {
+                $result[$k] = $v;
+            }
+        }
+
+        return $result;
+    }
 }

+ 2 - 1
src/Database/Type/ExpressionTypeInterface.php

@@ -28,7 +28,8 @@ interface ExpressionTypeInterface
      * Returns an ExpressionInterface object for the given value that can
      * be used in queries.
      *
+     * @param mixed $value The value to be converted to an expression
      * @return \Cake\Database\ExpressionInterface
      */
-    public function toExpression($value, Driver $driver);
+    public function toExpression($value);
 }

+ 2 - 2
src/Database/Type/TypeExpressionCasterTrait.php

@@ -26,15 +26,15 @@ trait TypeExpressionCasterTrait
 
     protected function _castToExpression($value, $type)
     {
-        return $value;
         $baseType = str_replace('[]', '', $type);
-        $multi = $type !== $baseType;
         $converter = Type::build($baseType);
 
         if (!$converter instanceof ExpressionTypeInterface) {
             return $value;
         }
 
+        $multi = $type !== $baseType;
+
         if ($multi) {
             $result = [];
             foreach ($value as $k => $v) {

+ 71 - 0
tests/TestCase/Database/ExpressionTypeCastingTest.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The Open Group Test Suite License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Database;
+
+use Cake\TestSuite\TestCase;
+use Cake\Database\Type\StringType;
+use Cake\Database\Type\ExpressionTypeInterface;
+use Cake\Database\Expression\FunctionExpression;
+use Cake\Database\ValueBinder;
+use Cake\Database\Type;
+use Cake\Database\Expression\Comparison;
+
+class TestType extends StringType implements ExpressionTypeInterface
+{
+
+    public function toExpression($value)
+    {
+        return new FunctionExpression('CONCAT', [$value, ' - foo']);
+    }
+}
+
+/**
+ * Tests for Expression objects casting values to other expressions
+ * using the type classes
+ *
+ */
+class FunctionsBuilderTest extends TestCase
+{
+
+    /**
+     * Setups a mock for FunctionsBuilder
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        parent::setUp();
+        Type::map('test', new TestType);
+    }
+
+    public function testComparisonSimple()
+    {
+        $comparison = new Comparison('field', 'the thing', 'test', '=');
+        $binder = new ValueBinder;
+        $sql = $comparison->sql($binder);
+        $this->assertEquals('field = (CONCAT(:c0, :c1))', $sql);
+        $this->assertEquals('the thing', $binder->bindings()[':c0']['value']);
+    }
+
+    public function testComparisonMultiple()
+    {
+        $comparison = new Comparison('field', ['2', '3'], 'test[]', 'IN');
+        $binder = new ValueBinder;
+        $sql = $comparison->sql($binder);
+        $this->assertEquals('field IN (CONCAT(:c0, :c1),CONCAT(:c2, :c3))', $sql);
+        $this->assertEquals('2', $binder->bindings()[':c0']['value']);
+        $this->assertEquals('3', $binder->bindings()[':c2']['value']);
+    }
+
+}