[BUGFIX] Typo3DbQueryParser must handle LowerCase 35/28735/13
authorMarkus Klein <klein.t3@mfc-linz.at>
Tue, 25 Mar 2014 00:29:05 +0000 (01:29 +0100)
committerMarkus Klein <klein.t3@mfc-linz.at>
Tue, 25 Mar 2014 12:37:25 +0000 (13:37 +0100)
Resolves: #57263
Releases: 6.2
Change-Id: I89b5051a5fe1d6fc908428019115fcd4bc0d2b38
Reviewed-on: https://review.typo3.org/28735
Reviewed-by: Felix Oertel
Tested-by: Felix Oertel
Reviewed-by: Markus Klein
Tested-by: Markus Klein
typo3/sysext/extbase/Classes/DomainObject/AbstractDomainObject.php
typo3/sysext/extbase/Classes/Persistence/Generic/Qom/LowerCase.php
typo3/sysext/extbase/Classes/Persistence/Generic/Qom/LowerCaseInterface.php
typo3/sysext/extbase/Classes/Persistence/Generic/Qom/QueryObjectModelFactory.php
typo3/sysext/extbase/Classes/Persistence/Generic/Qom/UpperCase.php
typo3/sysext/extbase/Classes/Persistence/Generic/Qom/UpperCaseInterface.php
typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbQueryParser.php
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/posts.xml
typo3/sysext/extbase/Tests/Functional/Persistence/OperatorTest.php

index 7b9337a..53130ec 100644 (file)
@@ -32,7 +32,7 @@ namespace TYPO3\CMS\Extbase\DomainObject;
  *
  * All Model domain objects need to inherit from either AbstractEntity or AbstractValueObject, as this provides important framework information.
  */
-abstract class AbstractDomainObject implements \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface, \TYPO3\CMS\Extbase\Persistence\ObjectMonitoringInterface {
+abstract class AbstractDomainObject implements DomainObjectInterface, \TYPO3\CMS\Extbase\Persistence\ObjectMonitoringInterface {
 
        /**
         * @var int The uid of the record. The uid is only unique in the context of the database table.
@@ -291,7 +291,7 @@ abstract class AbstractDomainObject implements \TYPO3\CMS\Extbase\DomainObject\D
                // In case it is an object and it implements the ObjectMonitoringInterface, we call _isDirty() instead of a simple comparison of objects.
                // We do this, because if the object itself contains a lazy loaded property, the comparison of the objects might fail even if the object didn't change
                if (is_object($currentValue)) {
-                       if ($currentValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
+                       if ($currentValue instanceof DomainObjectInterface) {
                                $result = !is_object($previousValue) || get_class($previousValue) !== get_class($currentValue) || $currentValue->getUid() !== $previousValue->getUid();
                        } elseif ($currentValue instanceof \TYPO3\CMS\Extbase\Persistence\ObjectMonitoringInterface) {
                                $result = !is_object($previousValue) || $currentValue->_isDirty() || get_class($previousValue) !== get_class($currentValue);
index a2384fd..357c050 100644 (file)
@@ -39,25 +39,43 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Qom;
 class LowerCase implements LowerCaseInterface {
 
        /**
-        * @var DynamicOperandInterface
+        * @var PropertyValueInterface
         */
        protected $operand;
 
        /**
         * Constructs this LowerCase instance
         *
-        * @param DynamicOperandInterface $operand
+        * @param PropertyValueInterface $operand
         */
-       public function __construct(DynamicOperandInterface $operand) {
+       public function __construct(PropertyValueInterface $operand) {
                $this->operand = $operand;
        }
 
        /**
         * Gets the operand whose value is converted to a lower-case string.
         *
-        * @return DynamicOperandInterface the operand; non-null
+        * @return PropertyValueInterface the operand; non-null
         */
        public function getOperand() {
                return $this->operand;
        }
+
+       /**
+        * Gets the name of the selector against which to evaluate this operand.
+        *
+        * @return string the selector name; non-null
+        */
+       public function getSelectorName() {
+               return $this->operand->getSelectorName();
+       }
+
+       /**
+        * Gets the name of the property.
+        *
+        * @return string the property name; non-null
+        */
+       public function getPropertyName() {
+               return 'LOWER' .  $this->operand->getPropertyName();
+       }
 }
index fdd9e86..2b19613 100644 (file)
@@ -36,12 +36,12 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Qom;
  *
  * If operand evaluates to null, the LowerCase operand also evaluates to null.
  */
-interface LowerCaseInterface extends DynamicOperandInterface {
+interface LowerCaseInterface extends PropertyValueInterface {
 
        /**
         * Gets the operand whose value is converted to a lower-case string.
         *
-        * @return DynamicOperandInterface the operand; non-null
+        * @return PropertyValueInterface the operand; non-null
         */
        public function getOperand();
 }
index 033de28..4449d0d 100644 (file)
@@ -129,13 +129,13 @@ class QueryObjectModelFactory implements \TYPO3\CMS\Core\SingletonInterface {
        /**
         * Filters node-tuples based on the outcome of a binary operation.
         *
-        * @param DynamicOperandInterface $operand1 the first operand; non-null
+        * @param PropertyValueInterface $operand1 the first operand; non-null
         * @param string $operator the operator; one of QueryObjectModelConstants.JCR_OPERATOR_*
         * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\StaticOperandInterface $operand2 the second operand; non-null
         * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface the constraint; non-null
         * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException if the operation otherwise fails
         */
-       public function comparison(DynamicOperandInterface $operand1, $operator, $operand2) {
+       public function comparison(PropertyValueInterface $operand1, $operator, $operand2) {
                return $this->objectManager->get('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Qom\\Comparison', $operand1, $operator, $operand2);
        }
 
@@ -154,7 +154,7 @@ class QueryObjectModelFactory implements \TYPO3\CMS\Core\SingletonInterface {
        /**
         * Evaluates to the lower-case string value (or values, if multi-valued) of an operand.
         *
-        * @param DynamicOperandInterface $operand the operand whose value is converted to a lower-case string; non-null
+        * @param PropertyValueInterface $operand the operand whose value is converted to a lower-case string; non-null
         * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\LowerCaseInterface the operand; non-null
         * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException if the operation otherwise fails
         */
@@ -165,7 +165,7 @@ class QueryObjectModelFactory implements \TYPO3\CMS\Core\SingletonInterface {
        /**
         * Evaluates to the upper-case string value (or values, if multi-valued) of an operand.
         *
-        * @param DynamicOperandInterface $operand the operand whose value is converted to a upper-case string; non-null
+        * @param PropertyValueInterface $operand the operand whose value is converted to a upper-case string; non-null
         * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\UpperCaseInterface the operand; non-null
         * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException if the operation otherwise fails
         */
index ca1aa55..4a94a09 100644 (file)
@@ -41,25 +41,43 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Qom;
 class UpperCase implements UpperCaseInterface {
 
        /**
-        * @var DynamicOperandInterface
+        * @var PropertyValueInterface
         */
        protected $operand;
 
        /**
         * Constructs this UpperCase instance
         *
-        * @param DynamicOperandInterface $operand
+        * @param PropertyValueInterface $operand
         */
-       public function __construct(DynamicOperandInterface $operand) {
+       public function __construct(PropertyValueInterface $operand) {
                $this->operand = $operand;
        }
 
        /**
         * Gets the operand whose value is converted to a upper-case string.
         *
-        * @return DynamicOperandInterface the operand; non-null
+        * @return PropertyValueInterface the operand; non-null
         */
        public function getOperand() {
                return $this->operand;
        }
+
+       /**
+        * Gets the name of the selector against which to evaluate this operand.
+        *
+        * @return string the selector name; non-null
+        */
+       public function getSelectorName() {
+               return $this->operand->getSelectorName();
+       }
+
+       /**
+        * Gets the name of the property.
+        *
+        * @return string the property name; non-null
+        */
+       public function getPropertyName() {
+               return 'UPPER' .  $this->operand->getPropertyName();
+       }
 }
index 2857dae..df5bb40 100644 (file)
@@ -36,12 +36,12 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Qom;
  *
  * If operand evaluates to null, the UpperCase operand also evaluates to null.
  */
-interface UpperCaseInterface extends DynamicOperandInterface {
+interface UpperCaseInterface extends PropertyValueInterface {
 
        /**
         * Gets the operand whose value is converted to a upper-case string.
         *
-        * @return DynamicOperandInterface the operand; non-null
+        * @return PropertyValueInterface the operand; non-null
         */
        public function getOperand();
 }
index 22af78c..1470fc9 100644 (file)
@@ -345,7 +345,7 @@ class Typo3DbQueryParser {
                        if ($hasValue === FALSE) {
                                $sql['where'][] = '1<>1';
                        } else {
-                               $this->parseDynamicOperand($comparison, $operator, $source, $sql);
+                               $this->parseDynamicOperand($comparison, $source, $sql);
                        }
                } elseif ($operator === QueryInterface::OPERATOR_CONTAINS) {
                        if ($operand2 === NULL) {
@@ -381,7 +381,7 @@ class Typo3DbQueryParser {
                                }
                        }
                } else {
-                       $this->parseDynamicOperand($comparison, $operator, $source, $sql);
+                       $this->parseDynamicOperand($comparison, $source, $sql);
                }
        }
 
@@ -389,18 +389,37 @@ class Typo3DbQueryParser {
         * Parse a DynamicOperand into SQL and parameter arrays.
         *
         * @param Qom\ComparisonInterface $comparison
-        * @param string $operator One of the JCR_OPERATOR_* constants
         * @param Qom\SourceInterface $source The source
         * @param array &$sql The query parts
-        * @param string $valueFunction an optional SQL function to apply to the operand value
         * @return void
         */
-       protected function parseDynamicOperand(Qom\ComparisonInterface $comparison, $operator, Qom\SourceInterface $source, array &$sql, $valueFunction = NULL) {
+       protected function parseDynamicOperand(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source, array &$sql) {
+               $operator = $this->resolveOperator($comparison->getOperator());
                $operand = $comparison->getOperand1();
+
+               $constraintSQL = $this->parseOperand($operand, $source, $sql) . ' ' . $operator . ' ';
+
+               $parameterIdentifier = $this->normalizeParameterIdentifier($comparison->getParameterIdentifier());
+               if ($operator === 'IN') {
+                       $parameterIdentifier = '(' . $parameterIdentifier . ')';
+               }
+               $constraintSQL .= $parameterIdentifier;
+
+               $sql['where'][] = $constraintSQL;
+       }
+
+       /**
+        * @param Qom\DynamicOperandInterface $operand
+        * @param Qom\SourceInterface $source The source
+        * @param array &$sql The query parts
+        * @return string
+        * @throws \InvalidArgumentException
+        */
+       protected function parseOperand(Qom\DynamicOperandInterface $operand, Qom\SourceInterface $source, array &$sql) {
                if ($operand instanceof Qom\LowerCaseInterface) {
-                       $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $sql, 'LOWER');
+                       $constraintSQL = 'LOWER(' . $this->parseOperand($operand->getOperand(), $source, $sql) . ')';
                } elseif ($operand instanceof Qom\UpperCaseInterface) {
-                       $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $sql, 'UPPER');
+                       $constraintSQL = 'UPPER(' . $this->parseOperand($operand->getOperand(), $source, $sql) . ')';
                } elseif ($operand instanceof Qom\PropertyValueInterface) {
                        $propertyName = $operand->getPropertyName();
                        $className = '';
@@ -415,22 +434,11 @@ class Typo3DbQueryParser {
                                $tableName = $source->getJoinCondition()->getSelector1Name();
                        }
                        $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
-                       $operator = $this->resolveOperator($operator);
-                       $constraintSQL = '';
-                       if ($valueFunction === NULL) {
-                               $constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $operator . ' ';
-                       } else {
-                               $constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $operator . ' ';
-                       }
-
-                       if ($operator === 'LIKE' || $operator === 'IN') {
-                               $constraintSQL .= '(' . $this->normalizeParameterIdentifier($comparison->getParameterIdentifier()) . ')';
-                       } else {
-                               $constraintSQL .= $this->normalizeParameterIdentifier($comparison->getParameterIdentifier());
-                       }
-
-                       $sql['where'][] = $constraintSQL;
+                       $constraintSQL = (!empty($tableName) ? $tableName . '.' : '') . $columnName;
+               } else {
+                       throw new \InvalidArgumentException('Given operand has invalid type "' . get_class($operand) . '".', 1395710211);
                }
+               return $constraintSQL;
        }
 
        /**
index ed86e29..b00872a 100644 (file)
@@ -96,7 +96,7 @@
                <pid>0</pid>
                <blog>2</blog>
                <tags>0</tags>
-               <title>Post11</title>
+               <title>post1</title>
                <sorting>11</sorting>
                <deleted>0</deleted>
        </tx_blogexample_domain_model_post>
index 5d5d8b7..b59d2b2 100644 (file)
@@ -25,7 +25,6 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
  ***************************************************************/
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
 
 class OperatorTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
 
@@ -84,4 +83,17 @@ class OperatorTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
 
                $this->assertSame(0, $query->count());
        }
+
+       /**
+        * @test
+        */
+       public function equalsCorrectlyHandlesCaseSensivity() {
+               $query = $this->postRepository->createQuery();
+
+               $query->matching(
+                       $query->equals('title', 'PoSt1', FALSE)
+               );
+
+               $this->assertSame(2, $query->count());
+       }
 }