Commit 252bc8aa authored by Виктор Волков's avatar Виктор Волков
Browse files

feat: improve row formula calculation

parent b45b3a15
......@@ -3,7 +3,8 @@
"vendor-dir": "./vendor"
},
"require-dev": {
"doctrine/orm": "*",
"doctrine/dbal": "*",
"doctrine/coding-standard": "^6.0",
"ext-json": "*",
"squizlabs/php_codesniffer": "3.5",
"phpunit/phpunit": "^6"
......
<?xml version="1.0"?>
<ruleset name="PSR12">
<description>The ASH2 coding standard.</description>
<rule ref="PSR12"/>
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint"/>
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint"/>
<file>src/</file>
<exclude-pattern>vendor</exclude-pattern>
</ruleset>
\ No newline at end of file
......@@ -26,4 +26,4 @@ class BackendFactory
return new RowFormulaInterpreter();
}
}
}
}
\ No newline at end of file
......@@ -19,7 +19,7 @@ final class RowFormulaDataRepository
$this->connection = $connection;
}
public function updateRecord(int $objectId, int $recordId)
public function updateRecord(int $objectId, int $recordId): void
{
//1. Получаем данные текущей редактируемой строки
$initRow = $this->connection->createQueryBuilder()
......@@ -32,28 +32,36 @@ final class RowFormulaDataRepository
$this->loopRowFormulas($objectId, $initRow);
}
private function loopRowFormulas(int $objectId, array $initRow)
private function loopRowFormulas(int $objectId, array $initRow): void
{
//2. Бежим по всем формулам-правилам, вокруг текущего объекта
//2. Бежим по всем формулам-правилам
$formulas = $this->connection->createQueryBuilder()
->select("f.id, f.object_id, f.alias_field_id, f.target_alias_value, f.prepared_statement")
->select("distinct f.id, f.object_id, f.alias_field_id, f.target_alias_value,
f.prepared_statement, r.alias_field_id as related_alias_id")
->from("row_formulas", "f")
->leftJoin("f", "logic", "l", "l.entity_id = f.object_id")
->leftJoin("l", "related_objects", "r", "r.logic_id = l.id")
->leftJoin("r", "row_formula_arguments", "ff", "ff.related_object_id = r.id")
->where("f.object_id = :objectId or r.object_id = :objectId")
->groupBy("f.id, f.object_id, f.alias_field_id, f.target_alias_value, f.prepared_statement")
->setParameter('objectId', $objectId)
->execute()
->fetchAll(\PDO::FETCH_ASSOC);
$targetFormulas = [];
foreach ($formulas as $formula) {
$alias_value = $initRow["attr_" . $formula['alias_field_id'] . "_"];
//И related_alias_id и alias_field_id оба могут быть не NULL
//но мы редактировали конкретную строку $initRow и только один вариант подходит
$isRelatedObject = !is_null($formula['related_alias_id'])
&& isset($initRow["attr_{$formula['related_alias_id']}_"]);
$aliasFieldId = $isRelatedObject ? $formula['related_alias_id'] : $formula['alias_field_id'];
$aliasValue = $initRow["attr_{$aliasFieldId}_"];
//Смотрим аргументы формулы, у которых псевдоним совпадает с псевдонимом текущей строки
//I. Редактируем строку в текущем реестре и пересчет делаем в текущем реестре
$args = $this->connection->createQueryBuilder()
->select("a.id", "array_to_string(array_agg(distinct f.field_id), ',') as filters")
$qb = $this->connection->createQueryBuilder();
$args = $qb->select("a.id, array_to_string(array_agg(f.field_id order by f.field_id desc), ',')
as filters, array_to_string(array_agg(concat_ws(' ', fd.related_object_field_id,
fd.current_object_field_id)), ',') as related_filters")
->from("row_formula_arguments", "a")
->leftJoin("a", "row_formula_argument_filters", "f", "f.row_formula_argument_id = a.id")
->leftJoin("a", "filters", "ff", "ff.related_object_id = a.related_object_id")
->leftJoin("ff", "filter_fields", "fd", "fd.filter_id = ff.id")
->where("a.row_formula_id = :rowFormulaId and
a.object_id = :objectId and
a.alias_field_id = :aliasFieldId and
......@@ -61,37 +69,61 @@ final class RowFormulaDataRepository
->groupBy("a.id")
->setParameter('rowFormulaId', $formula['id'])
->setParameter('objectId', $objectId)
->setParameter('aliasFieldId', $formula['alias_field_id'])
->setParameter('aliasFieldValue', $alias_value)
->setParameter('aliasFieldId', $aliasFieldId)
->setParameter('aliasFieldValue', $aliasValue)
->execute()
->fetchAll(\PDO::FETCH_ASSOC);
//Бежим по всем аргументам формулы и для каждого с помощью фильтров находим целевые строки
$filtersHit = [];
foreach ($args as $arg) {
$filters = $arg['filters'];
//Если фильтр совпадает с ранее просмотренным,
//то ему будет соответствовать та же самая целевая строка пересчета
if (!in_array($filters, $filtersHit)) {
$filtersHit[] = $filters;
$filterIds = !empty($filters) ? explode(",", $filters) : [];
$qb = $this->connection->createQueryBuilder()
->select("*")
->from("object_{$objectId}_")
->where("attr_{$formula['alias_field_id']}_ = :aliasFieldValue");
/*У целевой строки значения в полях-фильтрах будут совпадать
*со значениями в полях-фильтрах текущей редактируемой строки
*/
foreach ($filterIds as $filterId) {
$qb->andWhere("attr_{$filterId}_ = :attr_{$filterId}_");
$qb->setParameter("attr_{$filterId}_", $initRow["attr_{$filterId}_"]);
$isEvaluated = false;
//Старый алгоритм
if (!$isRelatedObject) {
$filters = $arg['filters'];
//Чтобы отсечь ранее найденные целевые строки
if (!in_array($filters, $filtersHit)) {
$isEvaluated = true;
$filtersHit[] = $filters;
$filterIds = !empty($filters) ? explode(",", $filters) : [];
$qb = $this->connection->createQueryBuilder()
->select("*")
->from("object_{$objectId}_")
->where("attr_{$aliasFieldId}_ = :aliasFieldValue");
/*
*У целевой строки значения в полях-фильтрах будут совпадать
*со значениями в полях-фильтрах текущей редактируемой строки
*/
foreach ($filterIds as $filterId) {
$qb->andWhere("attr_{$filterId}_ = :attr_{$filterId}_");
$qb->setParameter("attr_{$filterId}_", $initRow["attr_{$filterId}_"]);
}
/*У целевой строки значение в поле-псевдониме будет совпадать
*со значением поля-псевдонима формулы
*/
$qb->setParameter('aliasFieldValue', $formula['target_alias_value']);
}
/*У целевой строки значение в поле-псевдониме будет совпадать
*со значением поля-псевдонима формулы
*/
$qb->setParameter('aliasFieldValue', $formula['target_alias_value']);
//Одному аргументу с его фильтрами может соответствовать несколько целевых строк
} elseif ($isRelatedObject) {
$filters = $arg['related_filters'];
//Чтобы отсечь ранее найденные целевые строки
if (!in_array($filters, $filtersHit)) {
$isEvaluated = true;
$filtersHit[] = $filters;
//Пары идентификаторов - id-внешний, id-текущий
$filterIds = !empty($filters) ? explode(",", $filters) : [];
$qb = $this->connection->createQueryBuilder()
->select("*")
->from("object_{$formula['object_id']}_")
->where("attr_{$formula['alias_field_id']}_ = :aliasFieldValue");
$qb->setParameter('aliasFieldValue', $formula['target_alias_value']);
foreach ($filterIds as $pair) {
$ids = explode(' ', $pair);
$qb->andWhere("attr_{$ids[1]}_ = :attr_{$ids[1]}_");
$qb->setParameter("attr_{$ids[1]}_", $initRow["attr_{$ids[0]}_"]);
}
}
}
//Если нашли целевую строку
if ($isEvaluated) {
$targetRows = $qb->execute()->fetchAll(\PDO::FETCH_ASSOC);
foreach ($targetRows as $targetRow) {
$this->evaluateTargetRow($objectId, $initRow, $formula, $targetRow);
......@@ -101,23 +133,27 @@ final class RowFormulaDataRepository
}
}
private function evaluateTargetRow(int $objectId, array $initRow, array $formula, array $targetRow)
private function evaluateTargetRow(int $objectId, array $initRow, array $formula, array $targetRow): void
{
$prepared_statement = $formula['prepared_statement'];
$args = $this->connection->createQueryBuilder()
->select("a.id, a.object_id, a.related_object_id, a.alias_field_id, a.alias_field_value, " .
"a.guid, a.column_id, array_to_string(array_agg(distinct f.field_id), ',') as filters")
"a.guid, a.column_id, array_to_string(array_agg(distinct f.field_id), ',') as filters, " .
"array_to_string(array_agg(concat_ws(' ', fd.related_object_field_id, " .
"fd.current_object_field_id)), ',') as related_filters")
->from("row_formula_arguments", "a")
->leftJoin("a", "row_formula_argument_filters", "f", "f.row_formula_argument_id = a.id")
->leftJoin("a", "filters", "ff", "ff.related_object_id = a.related_object_id")
->leftJoin("ff", "filter_fields", "fd", "fd.filter_id = ff.id")
->where("a.row_formula_id = :rowFormulaId")
->groupBy("a.id, a.object_id, a.related_object_id, a.alias_field_id, a.alias_field_value, a.guid," .
"a.column_id")
->setParameter('rowFormulaId', $formula['id'])
->execute()
->fetchAll(\PDO::FETCH_ASSOC);
//Находим строки-источники, из которых должны браться значения для аргументов формулы
$sourceRows = $this->findSourceRows($objectId, $targetRow, $args);
//Находим строки-источники, из которых должны браться значения для аргументов формулы
$columns = $this->connection->createQueryBuilder()
->select("column_id")
->from("target_row_formula_columns")
......@@ -126,7 +162,7 @@ final class RowFormulaDataRepository
->execute()
->fetchAll(\PDO::FETCH_COLUMN);
$qb = $this->connection->createQueryBuilder()
->update("object_{$objectId}_");
->update("object_{$formula['object_id']}_");
foreach ($columns as $columnId) {
$colStatement = $prepared_statement;
foreach ($args as $arg) {
......@@ -148,12 +184,12 @@ final class RowFormulaDataRepository
}
$qb->set("attr_{$columnId}_", $colStatement);
}
$qb->where(sprintf("id = %d", $targetRow["id"]));
$qb->where("id = {$targetRow["id"]}");
$qb->execute();
$this->updateRecord($objectId, $targetRow["id"]);
$this->updateRecord($formula['object_id'], $targetRow["id"]);
}
private function findSourceRows(int $objectId, array $targetRow, array $args)
private function findSourceRows(int $objectId, array $targetRow, array $args): array
{
$rows = [];
foreach ($args as $arg) {
......@@ -162,7 +198,7 @@ final class RowFormulaDataRepository
$filterIds = !empty($filters) ? explode(",", $filters) : [];
$qb = $this->connection->createQueryBuilder()
->select("*")
->from("object_{$objectId}_")
->from("object_{$arg['object_id']}_")
->where("attr_{$arg['alias_field_id']}_ = :aliasFieldValue");
$qb->setParameter('aliasFieldValue', $arg['alias_field_value']);
foreach ($filterIds as $filterId) {
......@@ -174,7 +210,23 @@ final class RowFormulaDataRepository
$rows[$arg['guid']] = $sourceRow;
}
} else {
//Находим строку для аргумента из внешней таблички
//Находим строку для аргумента из внешней таблички, используем вьюху связанной таблицы
$filters = $arg['related_filters'];
$filterIds = !empty($filters) ? explode(",", $filters) : [];
$qb = $this->connection->createQueryBuilder()
->select("distinct *")
->from("related_object_{$arg['related_object_id']}")
->where("attr_{$arg['alias_field_id']}_ = :aliasFieldValue");
$qb->setParameter('aliasFieldValue', $arg['alias_field_value']);
foreach ($filterIds as $pair) {
$ids = explode(' ', $pair);
$qb->andWhere("attr_{$ids[0]}_ = :attr_{$ids[0]}_");
$qb->setParameter("attr_{$ids[0]}_", $targetRow["attr_{$ids[1]}_"]);
}
$sourceRow = $qb->execute()->fetch(\PDO::FETCH_ASSOC);
if ($sourceRow) {
$rows[$arg['guid']] = $sourceRow;
}
}
}
return $rows;
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment