Avoid simple text replacements in SQL if possible
MAILPOET-4986
This commit is contained in:
committed by
Aschepikov
parent
1cab4a944a
commit
03da98e5df
@@ -3,6 +3,7 @@
|
|||||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
use MailPoet\Entities\SubscriberEntity;
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
|
use MailPoet\Util\Security;
|
||||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||||
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
use MailPoetVendor\Doctrine\ORM\EntityManager;
|
||||||
|
|
||||||
@@ -34,4 +35,27 @@ class FilterHelper {
|
|||||||
->getClassMetadata(SubscriberEntity::class)
|
->getClassMetadata(SubscriberEntity::class)
|
||||||
->getTableName();
|
->getTableName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getInterpolatedSQL(QueryBuilder $query): string {
|
||||||
|
$sql = $query->getSQL();
|
||||||
|
$params = $query->getParameters();
|
||||||
|
$search = array_map(function($key) {
|
||||||
|
return ":$key";
|
||||||
|
}, array_keys($params));
|
||||||
|
$replace = array_map(function($value) use ($query) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$quotedValues = array_map(function($arrayValue) use ($query) {
|
||||||
|
return $query->expr()->literal($arrayValue);
|
||||||
|
}, $value);
|
||||||
|
return implode(',', $quotedValues);
|
||||||
|
}
|
||||||
|
return $query->expr()->literal($value);
|
||||||
|
}, array_values($params));
|
||||||
|
return str_replace($search, $replace, $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUniqueParameterName(string $parameter): string {
|
||||||
|
$suffix = Security::generateRandomString();
|
||||||
|
return sprintf("%s_%s", $parameter, $suffix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,7 +37,7 @@ class WooCommercePurchaseDate implements Filter {
|
|||||||
if (in_array($operator, [DateFilterHelper::NOT_ON, DateFilterHelper::NOT_IN_THE_LAST])) {
|
if (in_array($operator, [DateFilterHelper::NOT_ON, DateFilterHelper::NOT_IN_THE_LAST])) {
|
||||||
$subQuery = $this->filterHelper->getNewSubscribersQueryBuilder();
|
$subQuery = $this->filterHelper->getNewSubscribersQueryBuilder();
|
||||||
$this->applyConditionsToQueryBuilder($operator, $date, $subQuery);
|
$this->applyConditionsToQueryBuilder($operator, $date, $subQuery);
|
||||||
$queryBuilder->andWhere($queryBuilder->expr()->notIn("{$subscribersTable}.id", $subQuery->getSQL()));
|
$queryBuilder->andWhere($queryBuilder->expr()->notIn("{$subscribersTable}.id", $this->filterHelper->getInterpolatedSQL($subQuery)));
|
||||||
} else {
|
} else {
|
||||||
$this->applyConditionsToQueryBuilder($operator, $date, $queryBuilder);
|
$this->applyConditionsToQueryBuilder($operator, $date, $queryBuilder);
|
||||||
}
|
}
|
||||||
@@ -47,27 +47,29 @@ class WooCommercePurchaseDate implements Filter {
|
|||||||
|
|
||||||
private function applyConditionsToQueryBuilder(string $operator, string $date, QueryBuilder $queryBuilder): QueryBuilder {
|
private function applyConditionsToQueryBuilder(string $operator, string $date, QueryBuilder $queryBuilder): QueryBuilder {
|
||||||
$orderStatsAlias = $this->wooFilterHelper->applyOrderStatusFilter($queryBuilder);
|
$orderStatsAlias = $this->wooFilterHelper->applyOrderStatusFilter($queryBuilder);
|
||||||
$quotedDate = $queryBuilder->expr()->literal($date);
|
$dateParam = $this->filterHelper->getUniqueParameterName('date');
|
||||||
|
|
||||||
switch ($operator) {
|
switch ($operator) {
|
||||||
case DateFilterHelper::BEFORE:
|
case DateFilterHelper::BEFORE:
|
||||||
$queryBuilder->andWhere("DATE($orderStatsAlias.date_created) < $quotedDate");
|
$queryBuilder->andWhere("DATE($orderStatsAlias.date_created) < :$dateParam");
|
||||||
break;
|
break;
|
||||||
case DateFilterHelper::AFTER:
|
case DateFilterHelper::AFTER:
|
||||||
$queryBuilder->andWhere("DATE($orderStatsAlias.date_created) > $quotedDate");
|
$queryBuilder->andWhere("DATE($orderStatsAlias.date_created) > :$dateParam");
|
||||||
break;
|
break;
|
||||||
case DateFilterHelper::IN_THE_LAST:
|
case DateFilterHelper::IN_THE_LAST:
|
||||||
case DateFilterHelper::NOT_IN_THE_LAST:
|
case DateFilterHelper::NOT_IN_THE_LAST:
|
||||||
$queryBuilder->andWhere("DATE($orderStatsAlias.date_created) >= $quotedDate");
|
$queryBuilder->andWhere("DATE($orderStatsAlias.date_created) >= :$dateParam");
|
||||||
break;
|
break;
|
||||||
case DateFilterHelper::ON:
|
case DateFilterHelper::ON:
|
||||||
case DateFilterHelper::NOT_ON:
|
case DateFilterHelper::NOT_ON:
|
||||||
$queryBuilder->andWhere("DATE($orderStatsAlias.date_created) = $quotedDate");
|
$queryBuilder->andWhere("DATE($orderStatsAlias.date_created) = :$dateParam");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidFilterException('Incorrect value for operator', InvalidFilterException::MISSING_VALUE);
|
throw new InvalidFilterException('Incorrect value for operator', InvalidFilterException::MISSING_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$queryBuilder->setParameter($dateParam, $date);
|
||||||
|
|
||||||
return $queryBuilder;
|
return $queryBuilder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
namespace MailPoet\Segments\DynamicSegments\Filters;
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
use MailPoet\Util\DBCollationChecker;
|
use MailPoet\Util\DBCollationChecker;
|
||||||
|
use MailPoetVendor\Doctrine\DBAL\Connection;
|
||||||
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||||
|
|
||||||
class WooFilterHelper {
|
class WooFilterHelper {
|
||||||
@@ -76,10 +77,10 @@ class WooFilterHelper {
|
|||||||
$allowedStatuses = $this->defaultIncludedStatuses();
|
$allowedStatuses = $this->defaultIncludedStatuses();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$statusParam = $this->filterHelper->getUniqueParameterName('status');
|
||||||
$orderStatsAlias = $this->applyCustomerOrderJoin($queryBuilder);
|
$orderStatsAlias = $this->applyCustomerOrderJoin($queryBuilder);
|
||||||
$quotedStatus = array_map([$queryBuilder->expr(), 'literal'], $allowedStatuses);
|
$queryBuilder->andWhere("$orderStatsAlias.status IN (:$statusParam)");
|
||||||
$queryBuilder->andWhere($queryBuilder->expr()->in("$orderStatsAlias.status", $quotedStatus));
|
$queryBuilder->setParameter($statusParam, $allowedStatuses, Connection::PARAM_STR_ARRAY);
|
||||||
|
|
||||||
return $orderStatsAlias;
|
return $orderStatsAlias;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,47 @@
|
|||||||
|
<?php declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace MailPoet\Segments\DynamicSegments\Filters;
|
||||||
|
|
||||||
|
use MailPoet\Entities\SubscriberEntity;
|
||||||
|
use MailPoetVendor\Doctrine\DBAL\Query\QueryBuilder;
|
||||||
|
|
||||||
|
class FilterHelperTest extends \MailPoetTest {
|
||||||
|
/** @var FilterHelper */
|
||||||
|
private $filterHelper;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $subscribersTable;
|
||||||
|
|
||||||
|
public function _before() {
|
||||||
|
parent::_before();
|
||||||
|
$this->filterHelper = $this->diContainer->get(FilterHelper::class);
|
||||||
|
$this->subscribersTable = $this->entityManager
|
||||||
|
->getClassMetadata(SubscriberEntity::class)
|
||||||
|
->getTableName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItCanReturnSQLThatDoesNotIncludeParams(): void {
|
||||||
|
$queryBuilder = $this->getSubscribersQueryBuilder();
|
||||||
|
$defaultResult = $queryBuilder->getSQL();
|
||||||
|
expect($defaultResult)->equals("SELECT id FROM $this->subscribersTable");
|
||||||
|
expect($this->filterHelper->getInterpolatedSQL($queryBuilder))->equals($defaultResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItCanReturnInterpolatedSQL(): void {
|
||||||
|
$queryBuilder = $this->getSubscribersQueryBuilder();
|
||||||
|
$queryBuilder->where("$this->subscribersTable.created_at < :date");
|
||||||
|
$queryBuilder->setParameter('date', '2023-03-09');
|
||||||
|
expect($this->filterHelper->getInterpolatedSQL($queryBuilder))->equals("SELECT id FROM $this->subscribersTable WHERE $this->subscribersTable.created_at < '2023-03-09'");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItProperlyInterpolatesArrayValues(): void {
|
||||||
|
$queryBuilder = $this->getSubscribersQueryBuilder();
|
||||||
|
$queryBuilder->where("$this->subscribersTable.status IN (:statuses)");
|
||||||
|
$queryBuilder->setParameter('statuses', ['subscribed', 'inactive']);
|
||||||
|
expect($this->filterHelper->getInterpolatedSQL($queryBuilder))->equals("SELECT id FROM $this->subscribersTable WHERE $this->subscribersTable.status IN ('subscribed','inactive')");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSubscribersQueryBuilder(): QueryBuilder {
|
||||||
|
return $this->entityManager->getConnection()->createQueryBuilder()->select('id')->from($this->subscribersTable);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user