Querying DTOs with INDEX BY #6853

Open
opened 2026-01-22 15:40:03 +01:00 by admin · 4 comments
Owner

Originally created by @flaushi on GitHub (Oct 6, 2021).

Bug Report

Q A
BC Break no
Version 2.8.4

Summary

Results from a DTO-query with INDEX BY are not correctly indexed. I am getting standard arrays instead.

Current behavior

if I query
SELECT x.name FROM Entity x INDEX BY x.name the result is array like ['alice' => 'alice', 'bob' => 'bob', ...]
however

`SELECT new DTO(x.name) FROM Entity x INDEX BY x.name` 

will return `[0 => Dto('alice'), 1 => DTO('bob'), ...]

Expected behavior

I would expect to get ['alice' =>DTO( 'alice'), 'bob' => DTO('bob'), ...]

In view that partial queries are deprecated and that DTOs should replace them, the index by feature might be important for all developers who used index-by together with partial objects until now and have to migrate. For partial objects, this worked fine.

Originally created by @flaushi on GitHub (Oct 6, 2021). ### Bug Report | Q | A |------------ | ------ | BC Break | no | Version | 2.8.4 #### Summary Results from a DTO-query with INDEX BY are not correctly indexed. I am getting standard arrays instead. #### Current behavior if I query `SELECT x.name FROM Entity x INDEX BY x.name` the result is array like `['alice' => 'alice', 'bob' => 'bob', ...]` however ``` `SELECT new DTO(x.name) FROM Entity x INDEX BY x.name` ``` will return `[0 => Dto('alice'), 1 => DTO('bob'), ...] #### Expected behavior I would expect to get `['alice' =>DTO( 'alice'), 'bob' => DTO('bob'), ...]` In view that partial queries are deprecated and that DTOs should replace them, the index by feature might be important for all developers who used index-by together with partial objects until now and have to migrate. For partial objects, this worked fine.
admin added the Bug label 2026-01-22 15:40:03 +01:00
Author
Owner

@nCrazed commented on GitHub (Dec 22, 2021):

Running into the same problem in version 2.7.3

@nCrazed commented on GitHub (Dec 22, 2021): Running into the same problem in version `2.7.3`
Author
Owner

@greg0ire commented on GitHub (Feb 1, 2022):

Duplicate of https://github.com/doctrine/orm/issues/4415

@greg0ire commented on GitHub (Feb 1, 2022): Duplicate of https://github.com/doctrine/orm/issues/4415
Author
Owner

@greg0ire commented on GitHub (Feb 1, 2022):

We've been looking into this with @sir-kain , and it seems that ResultSetMapping::addIndexBy() does nothing, because it does not know how to map A.title to an SQL column. This foreach has nothing to loop on:
536b65f02b/lib/Doctrine/ORM/Query/ResultSetMapping.php (L222)

Not sure if we should have another lookup map, or if one of those should be modified, here maybe:

536b65f02b/lib/Doctrine/ORM/Query/SqlWalker.php (L1592-L1604)

I tried and failed to do so by using addFieldResult, that resulted in an error during hydration later on: 536b65f02b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php (L320-L321)

$rowData['data'] became not empty and the aliasMap was missing the required key for the code to continue, which it probably shouldn't.

@sir-kain has written the following test in case anyone wants to continue this:

<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\Tests\OrmFunctionalTestCase;
use Exception;

use function var_dump;

/**
 * @group GH-9101
 */
class GH9101Test extends OrmFunctionalTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        try {
            $this->_schemaTool->createSchema(
                [
                    $this->_em->getClassMetadata(GH9101Author::class),
                ]
            );

          // Create author 11/Joe
            $author        = new GH9101Author();
            $author->id    = 11;
            $author->name  = 'Joe';
            $author->title = 'Joe';
            $this->_em->persist($author);

          // Create author 12/Sir
            $author1        = new GH9101Author();
            $author1->id    = 12;
            $author1->name  = 'Sir';
            $author1->title = 'Sir';
            $this->_em->persist($author1);

            $this->_em->flush();
            $this->_em->clear();
        } catch (Exception $e) {
        }
    }

    public function testIndexByOk(): void
    {
        /* $dql     = "SELECT 'hello' FROM Doctrine\Tests\ORM\Functional\Ticket\GH9101Author A INDEX BY A.title "; */
        $dql2    = "SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO(A.title)
      FROM Doctrine\Tests\ORM\Functional\Ticket\GH9101Author A INDEX BY A.title ";
        /* $result  = $this->_em->createQuery($dql)->getResult(); */
        $result2 = $this->_em->createQuery($dql2)->getResult();

        /* var_dump($result); */
        var_dump($result2);
      // $joe   = $this->_em->find(GH9101Author::class, 10);
      // $alice = $this->_em->find(GH9101Author::class, 11);

      // self::assertArrayHasKey('Joe', $result, "INDEX BY A.name should return an index by the name of 'Joe'.");
      // self::assertArrayHasKey('Alice', $result, "INDEX BY A.name should return an index by the name of 'Alice'.");
    }
}

/**
 * @Entity
 */
class GH9101Author
{
  /**
   * @var int
   * @Id
   * @Column(type="integer")
   */
    public $id;

  /**
   * @var string
   * @Column(type="string")
   */
    public $name;

  /**
   * @var string
   * @Column(type="string")
   */
    public $title;

    public function __construct()
    {
    }
}
@greg0ire commented on GitHub (Feb 1, 2022): We've been looking into this with @sir-kain , and it seems that `ResultSetMapping::addIndexBy()` does nothing, because it does not know how to map `A.title` to an SQL column. This `foreach` has nothing to loop on: https://github.com/doctrine/orm/blob/536b65f02b853cc9c8bfb184d23a4cc4feecf002/lib/Doctrine/ORM/Query/ResultSetMapping.php#L222 Not sure if we should have another lookup map, or if one of those should be modified, here maybe: https://github.com/doctrine/orm/blob/536b65f02b853cc9c8bfb184d23a4cc4feecf002/lib/Doctrine/ORM/Query/SqlWalker.php#L1592-L1604 I tried and failed to do so by using `addFieldResult`, that resulted in an error during hydration later on: https://github.com/doctrine/orm/blob/536b65f02b853cc9c8bfb184d23a4cc4feecf002/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php#L320-L321 `$rowData['data']` became not empty and the `aliasMap` was missing the required key for the code to continue, which it probably shouldn't. @sir-kain has written the following test in case anyone wants to continue this: ```php <?php declare(strict_types=1); namespace Doctrine\Tests\ORM\Functional\Ticket; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; use Doctrine\Tests\OrmFunctionalTestCase; use Exception; use function var_dump; /** * @group GH-9101 */ class GH9101Test extends OrmFunctionalTestCase { protected function setUp(): void { parent::setUp(); try { $this->_schemaTool->createSchema( [ $this->_em->getClassMetadata(GH9101Author::class), ] ); // Create author 11/Joe $author = new GH9101Author(); $author->id = 11; $author->name = 'Joe'; $author->title = 'Joe'; $this->_em->persist($author); // Create author 12/Sir $author1 = new GH9101Author(); $author1->id = 12; $author1->name = 'Sir'; $author1->title = 'Sir'; $this->_em->persist($author1); $this->_em->flush(); $this->_em->clear(); } catch (Exception $e) { } } public function testIndexByOk(): void { /* $dql = "SELECT 'hello' FROM Doctrine\Tests\ORM\Functional\Ticket\GH9101Author A INDEX BY A.title "; */ $dql2 = "SELECT new Doctrine\Tests\Models\CMS\CmsUserDTO(A.title) FROM Doctrine\Tests\ORM\Functional\Ticket\GH9101Author A INDEX BY A.title "; /* $result = $this->_em->createQuery($dql)->getResult(); */ $result2 = $this->_em->createQuery($dql2)->getResult(); /* var_dump($result); */ var_dump($result2); // $joe = $this->_em->find(GH9101Author::class, 10); // $alice = $this->_em->find(GH9101Author::class, 11); // self::assertArrayHasKey('Joe', $result, "INDEX BY A.name should return an index by the name of 'Joe'."); // self::assertArrayHasKey('Alice', $result, "INDEX BY A.name should return an index by the name of 'Alice'."); } } /** * @Entity */ class GH9101Author { /** * @var int * @Id * @Column(type="integer") */ public $id; /** * @var string * @Column(type="string") */ public $name; /** * @var string * @Column(type="string") */ public $title; public function __construct() { } } ```
Author
Owner

@Arkemlar commented on GitHub (Dec 13, 2023):

Bump it (version 2.15.3)

@Arkemlar commented on GitHub (Dec 13, 2023): Bump it (version 2.15.3)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#6853