From 3e7e7ccb0b1cf03420f4060307b9739ab621cc78 Mon Sep 17 00:00:00 2001 From: William Arin Date: Wed, 3 Aug 2022 16:52:51 +0800 Subject: [PATCH] fix: multiple EAV conditions now give correct result --- docker-compose.yml | 2 +- .../Repository/AbstractEntityRepository.php | 83 ++++++++++++------- .../Repository/ProductRepositoryTest.php | 10 +++ 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 79e2592..64d83d9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=wp_test - MYSQL_ROOT_HOST=% - command: --default-authentication-plugin=mysql_native_password + command: --default-authentication-plugin=mysql_native_password --max_connections=10000 wordpress: image: wordpress:fpm-alpine diff --git a/src/Bridge/Repository/AbstractEntityRepository.php b/src/Bridge/Repository/AbstractEntityRepository.php index 6646f4a..d06b78a 100644 --- a/src/Bridge/Repository/AbstractEntityRepository.php +++ b/src/Bridge/Repository/AbstractEntityRepository.php @@ -409,12 +409,15 @@ protected function handleRegularCriteria( true, ) ) { + $metaAlias = sprintf('pm_%s', $aliasNumber ?? 'self'); + if ($aliasNumber === null) { - $this->joinSelfMetaTable($queryBuilder); + $metaAlias = $this->joinSelfMetaTable($queryBuilder, true); + $this->tableAliases[$metaAlias][] = $field; } if ($this->isFieldMapped($field, $entityClassName)) { - $exprKey = sprintf('pm_%s.meta_key = :%s_key', $aliasNumber ?? 'self', $snakeField); + $exprKey = sprintf('%s.meta_key = :%s_key', $metaAlias, $snakeField); $queryBuilder->andWhere($exprKey) ->setParameter( sprintf('%s_key', $snakeField), @@ -422,17 +425,13 @@ protected function handleRegularCriteria( ) ; } else { - $exprKey = sprintf( - 'TRIM(LEADING \'_\' FROM pm_%s.meta_key) = :%s_key', - $aliasNumber ?? 'self', - $snakeField, - ); + $exprKey = sprintf("TRIM(LEADING '_' FROM %s.meta_key) = :%s_key", $metaAlias, $snakeField); $queryBuilder->andWhere($exprKey) ->setParameter(sprintf('%s_key', $snakeField), $field) ; } - $exprValue = sprintf('pm_%s.meta_value %s %s', $aliasNumber ?? 'self', $operator, $parameter); + $exprValue = sprintf('%s.meta_value %s %s', $metaAlias, $operator, $parameter); $queryBuilder->andWhere($exprValue); } else { if ($operator === Operand::OPERATOR_IN_ALL) { @@ -461,31 +460,52 @@ protected function addOrderByClause(QueryBuilder $queryBuilder, ?array $orderBy) } } - private function joinSelfMetaTable(QueryBuilder $queryBuilder): void + private function joinSelfMetaTable(QueryBuilder $queryBuilder, bool $incrementIfNecessary = false): string { - if (!array_key_exists('pm_self', $this->tableAliases)) { - $this->tableAliases['pm_self'] = []; + $applyJoin = function (QueryBuilder $queryBuilder, string $alias): void { + if (!array_key_exists($alias, $this->tableAliases)) { + $this->tableAliases[$alias] = []; + } + + $queryBuilder->leftJoin( + 'p', + $this->entityManager->getTablesPrefix() . static::TABLE_META_NAME, + $alias, + sprintf('p.%s = %s.%s', static::TABLE_IDENTIFIER, $alias, static::TABLE_META_IDENTIFIER), + ); + }; + + static $aliasNumber = 1; + $alias = 'pm_self'; + + if (!$incrementIfNecessary && $this->hasJoin($queryBuilder, $alias)) { + return $alias; } - $joinQueryPart = $queryBuilder->getQueryPart('join'); + if (!$this->hasJoin($queryBuilder, $alias)) { + $applyJoin($queryBuilder, $alias); - if ( - array_key_exists('p', $joinQueryPart) - && is_array($joinQueryPart['p']) - && count(array_filter( - $joinQueryPart['p'], - static fn (array $part) => !empty($part['joinAlias']) && $part['joinAlias'] === 'pm_self', - )) > 0 - ) { - return; + return $alias; } - $queryBuilder->leftJoin( - 'p', - $this->entityManager->getTablesPrefix() . static::TABLE_META_NAME, - 'pm_self', - sprintf('p.%s = pm_self.%s', static::TABLE_IDENTIFIER, static::TABLE_META_IDENTIFIER), - ); + $alias .= '_' . $aliasNumber++; + $applyJoin($queryBuilder, $alias); + + return $alias; + } + + private function hasJoin(QueryBuilder $queryBuilder, string $joinAlias): bool + { + $joinQueryPart = $queryBuilder->getQueryPart('join'); + + return array_key_exists('p', $joinQueryPart) + && is_array($joinQueryPart['p']) + && count( + array_filter( + $joinQueryPart['p'], + static fn (array $part) => !empty($part['joinAlias']) && $part['joinAlias'] === $joinAlias, + ) + ) > 0; } private function createRelationshipCriteria( @@ -540,10 +560,11 @@ private function createRelationshipCriteria( $this->addEntityExtraField($trimmedAlias); $this->additionalFieldsToSelect[] = sprintf( - "MAX(CASE WHEN %s.meta_key = '%s' THEN %s.post_id END) AS `%s`", + "MAX(CASE WHEN %s.meta_key = '%s' THEN %s.%s END) AS `%s`", $alias, $condition->getRelationshipFieldName(), $alias, + self::TABLE_META_IDENTIFIER, $trimmedAlias, ); } @@ -567,7 +588,7 @@ private function createTermRelationshipCriteria( $tableAlias, $parameterName, $tableAlias, - str_contains($tableAlias, 'relation') ? 'post_id' : 'meta_value', + str_contains($tableAlias, 'relation') ? self::TABLE_META_IDENTIFIER : 'meta_value', $aliasNumber, ); $queryBuilder->setParameter($parameterName, $mappedKey); @@ -676,9 +697,9 @@ private function addPostMetaJoinForPostRelationshipCondition( $queryBuilder->leftJoin( sprintf('p_%d', $aliasNumber), - $this->entityManager->getTablesPrefix() . 'postmeta', + $this->entityManager->getTablesPrefix() . self::TABLE_META_NAME, $alias, - sprintf('p_%d.id = pm_%d.post_id', $aliasNumber, $aliasNumber), + sprintf('p_%d.id = pm_%d.%s', $aliasNumber, $aliasNumber, self::TABLE_META_IDENTIFIER), ); foreach ($extraFields as $extraField) { diff --git a/test/Test/Bridge/Repository/ProductRepositoryTest.php b/test/Test/Bridge/Repository/ProductRepositoryTest.php index dac7366..fbf5603 100644 --- a/test/Test/Bridge/Repository/ProductRepositoryTest.php +++ b/test/Test/Bridge/Repository/ProductRepositoryTest.php @@ -148,6 +148,16 @@ public function testFindByEAVRegexp(): void self::assertEquals([16, 23], array_column($products, 'id')); } + public function testFindByMultipleEAVConditions(): void + { + $products = $this->repository->findBy([ + 'sku' => new Operand('hoodie.*logo|zipper', Operand::OPERATOR_REGEXP), + 'thumbnail_id' => 52, + ]); + + self::assertEquals([23], array_column($products, 'id')); + } + public function testFindByCriteriaOr(): void { $products = $this->repository->findBy([