From ed825b2727ea74c0c09e059bc25362cb954e393e Mon Sep 17 00:00:00 2001
From: Morton Jonuschat <m.jonuschat@mojocode.de>
Date: Wed, 20 Apr 2016 19:11:46 +0200
Subject: [PATCH] [TASK] Doctrine: Add support for column aliases in select()

Add support for selecting a column under a different identifier (alias)
when building a SELECT query.

Releases: master
Resolves: #75823
Change-Id: I3464c9d848c4892ea57b92cc1257654ff9e14110
Reviewed-on: https://review.typo3.org/47820
Reviewed-by: Michael Oehlhof <typo3@oehlhof.de>
Tested-by: Michael Oehlhof <typo3@oehlhof.de>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
---
 .../Classes/Database/Query/QueryBuilder.php   | 30 +++++---
 .../Unit/Database/Query/QueryBuilderTest.php  | 69 +++++++++++++++++++
 2 files changed, 91 insertions(+), 8 deletions(-)

diff --git a/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php b/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
index c6f6218b6d3a..576ec2c3b737 100644
--- a/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
+++ b/typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
@@ -923,18 +923,32 @@ class QueryBuilder
      * @param array $input
      *
      * @return array
+     * @throws \InvalidArgumentException
      */
     public function quoteIdentifiersForSelect(array $input): array
     {
-        // The SQL * operator must not be quoted. As it can only occur either by itself
-        // or preceded by a tablename (tablename.*) check if the last character of a select
-        // expression is the * and quote only prepended table name. In all other cases the
-        // full expression is being quoted.
         foreach ($input as &$select) {
-            if (substr($select, -2) === '.*') {
-                $select = $this->quoteIdentifier(substr($select, 0, -2)) . '.*';
-            } elseif ($select !== '*') {
-                $select = $this->quoteIdentifier($select);
+            list($fieldName, $alias, $suffix) = GeneralUtility::trimExplode(' AS ', $select, 3);
+            if (!empty($suffix)) {
+                throw new \InvalidArgumentException(
+                    'QueryBuilder::quoteIdentifiersForSelect() could not parse the input "' . $input . '"',
+                    1461170686
+                );
+            }
+
+            // The SQL * operator must not be quoted. As it can only occur either by itself
+            // or preceded by a tablename (tablename.*) check if the last character of a select
+            // expression is the * and quote only prepended table name. In all other cases the
+            // full expression is being quoted.
+            if (substr($fieldName, -2) === '.*') {
+                $select = $this->quoteIdentifier(substr($fieldName, 0, -2)) . '.*';
+            } elseif ($fieldName !== '*') {
+                $select = $this->quoteIdentifier($fieldName);
+            }
+
+            // Quote the alias for the current fieldName, if given
+            if (!empty($alias)) {
+                $select .= ' AS ' . $this->quoteIdentifier($alias);
             }
         }
         return $input;
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php
index 35a547d0de08..03c8e4aab8dd 100644
--- a/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php
+++ b/typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php
@@ -279,6 +279,75 @@ class QueryBuilderTest extends UnitTestCase
         $this->subject->select('aField', 'anotherField');
     }
 
+    public function quoteIdentifiersForSelectDataProvider()
+    {
+        return [
+            'fieldName' => [
+                'fieldName',
+                '"fieldName"',
+            ],
+            'tableName.fieldName' => [
+                'tableName.fieldName',
+                '"tableName"."fieldName"',
+            ],
+            'tableName.*' => [
+                'tableName.*',
+                '"tableName".*',
+            ],
+            '*' => [
+                '*',
+                '*',
+            ],
+            'fieldName AS anotherFieldName' => [
+                'fieldName AS anotherFieldName',
+                '"fieldName" AS "anotherFieldName"',
+            ],
+            'tableName.fieldName AS anotherFieldName' => [
+                'tableName.fieldName AS anotherFieldName',
+                '"tableName"."fieldName" AS "anotherFieldName"',
+            ],
+            'tableName.fieldName AS anotherTable.anotherFieldName' => [
+                'tableName.fieldName AS anotherTable.anotherFieldName',
+                '"tableName"."fieldName" AS "anotherTable"."anotherFieldName"',
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider quoteIdentifiersForSelectDataProvider
+     * @param string $identifier
+     * @param string $expectedResult
+     */
+    public function quoteIdentifiersForSelect($identifier, $expectedResult)
+    {
+        $this->connection->quoteIdentifier(Argument::cetera())->will(
+            function ($args) {
+                $platform = new MockPlatform();
+
+                return $platform->quoteIdentifier($args[0]);
+            }
+        );
+
+        $this->assertSame([$expectedResult], $this->subject->quoteIdentifiersForSelect([$identifier]));
+    }
+
+    /**
+     * @test
+     * @expectedException \InvalidArgumentException
+     */
+    public function quoteIdentifiersForSelectWithInvalidAlias()
+    {
+        $this->connection->quoteIdentifier(Argument::cetera())->will(
+            function ($args) {
+                $platform = new MockPlatform();
+
+                return $platform->quoteIdentifier($args[0]);
+            }
+        );
+        $this->subject->quoteIdentifiersForSelect(['aField AS anotherField,someField AS someThing']);
+    }
+
     /**
      * @test
      */
-- 
GitLab