Skip to content

Commit

Permalink
CRM-8041: Extremely slow My Sales Flow B2C widget (#9111)
Browse files Browse the repository at this point in the history
* CRM-8041: Extremely slow My Sales Flow B2C widget
- fix slow query by casting to int
- add repository test for getFunnelChartData method to test My Sales Workflow B2C query
  • Loading branch information
aalgogiver authored and sprightly committed Apr 4, 2017
1 parent b3ab76b commit 41d5f9c
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 22 deletions.
39 changes: 31 additions & 8 deletions src/Oro/Bundle/WorkflowBundle/Helper/WorkflowQueryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;

use Doctrine\DBAL\Types\Type;
use Oro\Bundle\WorkflowBundle\Entity\WorkflowItem;

trait WorkflowQueryTrait
Expand All @@ -16,17 +16,11 @@ trait WorkflowQueryTrait
*/
public function joinWorkflowItem(QueryBuilder $queryBuilder, $workflowItemAlias = 'workflowItem')
{
list($entityClass) = $queryBuilder->getRootEntities();
list($entityIdentifier) = $queryBuilder->getEntityManager()->getClassMetadata($entityClass)
->getIdentifierFieldNames();

list($rootAlias) = $queryBuilder->getRootAliases();

$queryBuilder->leftJoin(
WorkflowItem::class,
$workflowItemAlias,
Join::WITH,
$this->getItemCondition($rootAlias, $entityClass, $entityIdentifier, $workflowItemAlias)
$this->getWorkflowItemJoinCondition($queryBuilder, $workflowItemAlias)
);

return $queryBuilder;
Expand Down Expand Up @@ -101,4 +95,33 @@ protected function getItemCondition($entityAlias, $entityClass, $entityIdentifie
$entityClass
);
}

/**
* @param QueryBuilder $queryBuilder
* @param string $itemAlias
* @return string
*/
private function getWorkflowItemJoinCondition(QueryBuilder $queryBuilder, $itemAlias)
{
list($entityAlias) = $queryBuilder->getRootAliases();
list($entityClass) = $queryBuilder->getRootEntities();

$metadata = $queryBuilder->getEntityManager()->getClassMetadata($entityClass);
list($entityIdentifier) = $metadata->getIdentifierFieldNames();

if ($metadata->getTypeOfField($entityIdentifier) === Type::INTEGER) {
$condition = '%s.%s = CAST(%s.entityId as int) AND %s.entityClass = \'%s\'';
} else {
$condition = 'CAST(%s.%s as string) = CAST(%s.entityId as string) AND %s.entityClass = \'%s\'';
}

return sprintf(
$condition,
$entityAlias,
$entityIdentifier,
$itemAlias,
$itemAlias,
$entityClass
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Oro\Bundle\WorkflowBundle\Tests\Unit\Helper;

use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
Expand All @@ -14,6 +15,8 @@ class WorkflowQueryTraitTest extends \PHPUnit_Framework_TestCase
{
use WorkflowQueryTrait;

const ENTITY_CLASS = 'SomeEntityClass';

/** @var QueryBuilder|\PHPUnit_Framework_MockObject_MockObject */
protected $queryBuilder;

Expand Down Expand Up @@ -48,33 +51,35 @@ public function testJoinWorkflowStepOnDry()
->method('getIdentifierFieldNames')
->willReturn(['ident1', 'ident2']);

$this->queryBuilder->expects($this->at(1))
$this->queryBuilder->expects($this->once())
->method('getRootEntities')
->willReturn(['entityClass1', 'entityClass2']);

$this->queryBuilder->expects($this->at(2))
$this->queryBuilder->expects($this->once())
->method('getEntityManager')
->willReturn($this->entityManager);

$this->queryBuilder->expects($this->at(3))
$this->queryBuilder->expects($this->once())
->method('getRootAliases')
->willReturn(['rootAlias']);

$this->queryBuilder->expects($this->at(4))
$this->queryBuilder->expects($this->exactly(2))
->method('leftJoin')
->with(
WorkflowItem::class,
'itemAlias',
Join::WITH,
sprintf('CAST(rootAlias.ident1 as string) = CAST(itemAlias.entityId as string)' .
' AND itemAlias.entityClass = \'entityClass1\'')
->withConsecutive(
[
WorkflowItem::class,
'itemAlias',
Join::WITH,
sprintf('CAST(rootAlias.ident1 as string) = CAST(itemAlias.entityId as string)' .
' AND itemAlias.entityClass = \'entityClass1\'')
],
[
'itemAlias.currentStep',
'stepAlias'
]
)
->willReturn($this->queryBuilder);

$this->queryBuilder->expects($this->at(5))
->method('leftJoin')
->with('itemAlias.currentStep', 'stepAlias')->willReturn($this->queryBuilder);

$this->assertSame(
$this->queryBuilder,
$this->joinWorkflowStep($this->queryBuilder, 'stepAlias', 'itemAlias')
Expand Down Expand Up @@ -133,4 +138,97 @@ public function testAddDatagridQuery()
)
);
}

/**
* @dataProvider identifierFieldTypeDataProvider
* @param string $identifierFieldType
* @param string $identifierFieldName
* @param string $entityAlias
* @param string $itemAlias
* @param string $expectedCondition
*/
public function testJoinWorkflowItem(
$identifierFieldType,
$identifierFieldName,
$entityAlias,
$itemAlias,
$expectedCondition
) {
/** @var WorkflowQueryTrait $trait */
$trait = static::getMockForTrait(WorkflowQueryTrait::class);

$this->classMetadata
->expects($this->any())
->method('getIdentifierFieldNames')
->willReturn([$identifierFieldName]);

$this->classMetadata
->expects($this->any())
->method('getTypeOfField')
->with($identifierFieldName)
->willReturn($identifierFieldType);

$this->entityManager
->expects($this->any())
->method('getClassMetadata')
->with(self::ENTITY_CLASS)
->willReturn($this->classMetadata);

$this->queryBuilder
->expects($this->any())
->method('getRootEntities')
->willReturn([self::ENTITY_CLASS]);

$this->queryBuilder
->expects($this->any())
->method('getRootAliases')
->willReturn([$entityAlias]);

$this->queryBuilder
->expects($this->any())
->method('getEntityManager')
->willReturn($this->entityManager);

$this->queryBuilder
->expects($this->once())
->method('leftJoin')
->with(
WorkflowItem::class,
$itemAlias,
Join::WITH,
$expectedCondition
);

$trait->joinWorkflowItem($this->queryBuilder, $itemAlias);
}

/**
* @return array
*/
public function identifierFieldTypeDataProvider()
{
return [
[
'identifierFieldType' => Type::INTEGER,
'identifierFieldName' => 'idField',
'entityAlias' => 't',
'itemAlias' => 'workflowAlias',
'expectedCondition' => sprintf(
't.idField = CAST(workflowAlias.entityId as int) AND workflowAlias.entityClass = \'%s\'',
self::ENTITY_CLASS
)
],
[
'identifierFieldType' => Type::STRING,
'identifierFieldName' => 'idField',
'entityAlias' => 'rootAlias',
'itemAlias' => 'workflowAlias',
'expectedCondition' => sprintf(
'CAST(rootAlias.idField as string) = CAST(workflowAlias.entityId as string) ' .
'AND workflowAlias.entityClass = \'%s\'',
self::ENTITY_CLASS
)
]
];
}
}

0 comments on commit 41d5f9c

Please sign in to comment.