From 2696f438e49cfc89c1d2da9ebc79f074a8c8b4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D1=83=D1=84=20=D0=9B=D0=B0=D1=82=D1=8B=D0=BF?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Mon, 21 Apr 2025 00:18:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D1=81=D0=BA=D1=80?= =?UTF-8?q?=D0=B8=D0=BF=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ervu_dashboard/dao/CustomCoalesceDao.java | 81 ++++++ .../ervu_dashboard/dao/LessJoinsDao.java | 3 +- .../dao/SubqueriesJoinsDao.java | 237 ------------------ .../DropdownTreeViewModelFilterValue.ts | 36 +++ 4 files changed, 118 insertions(+), 239 deletions(-) create mode 100644 backend/src/main/java/ru/micord/ervu_dashboard/dao/CustomCoalesceDao.java delete mode 100644 backend/src/main/java/ru/micord/ervu_dashboard/dao/SubqueriesJoinsDao.java create mode 100644 frontend/src/ts/ervu-dashboard/component/filter/DropdownTreeViewModelFilterValue.ts diff --git a/backend/src/main/java/ru/micord/ervu_dashboard/dao/CustomCoalesceDao.java b/backend/src/main/java/ru/micord/ervu_dashboard/dao/CustomCoalesceDao.java new file mode 100644 index 00000000..035c4229 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu_dashboard/dao/CustomCoalesceDao.java @@ -0,0 +1,81 @@ +package ru.micord.ervu_dashboard.dao; + +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.stream.Collectors; + +import database.dao.DefaultLoadDao; +import org.jooq.Condition; +import org.jooq.DSLContext; +import org.jooq.Field; +import org.springframework.beans.factory.annotation.Autowired; + +import ru.cg.webbpm.modules.database.api.utils.QueryUtils; +import ru.cg.webbpm.modules.database.bean.entity_graph.condition.Operator; +import ru.cg.webbpm.modules.database.bean.filter.EntityFilter; +import ru.cg.webbpm.modules.database.bean.filter.FilterOperation; + +/** + * @author r.latypov + */ +public class CustomCoalesceDao extends DefaultLoadDao { + @Autowired + private DSLContext dsl; + + @Override + public Condition getFilterCondition(EntityFilter entityFilter) { + Field field = QueryUtils.toJooqField( + entityFilter.getEntityColumn(), dsl.dialect(), false + ); + return condition(entityFilter, field); + } + + @Override + public Condition getFilterCondition(EntityFilter entityFilter, String tableName) { + Field field = toJooqField(entityFilter.getEntityColumn(), tableName); + return condition(entityFilter, field); + } + + private Condition condition(EntityFilter entityFilter, Field field) { + FilterOperation operation = entityFilter.getFilterOperation(); + Operator multiValueOperator = entityFilter.getMultiValueOperator(); + Object value = entityFilter.getValue(); + // todo this is KOSTIL + if (value instanceof String && field.getDataType().isDateTime()) { + if (java.sql.Date.class.isAssignableFrom(field.getType())) { + value = java.sql.Date.valueOf( + LocalDate.parse(String.valueOf(value), DateTimeFormatter.ISO_DATE_TIME)); + } + else if (Timestamp.class.isAssignableFrom(field.getType())) { + value = Timestamp.valueOf( + LocalDateTime.parse(String.valueOf(value), DateTimeFormatter.ISO_DATE_TIME)); + } + } + + return switch (operation) { + case EQUAL -> getEqualCondition(field, value, multiValueOperator); + case NOT_EQUAL -> getNotEqualCondition(field, value, multiValueOperator); + case EQUAL_IGNORE_CASE -> getEqualIgnoreCaseCondition(field, value, multiValueOperator); + case GREATER_THAN -> getGreaterThanCondition(field, value, multiValueOperator); + case LESS_THAN -> getLessThanCondition(field, value, multiValueOperator); + case GREATER_OR_EQUAL -> getGreaterOrEqualCondition(field, value, multiValueOperator); + case LESS_OR_EQUAL -> getLessOrEqualCondition(field, value, multiValueOperator); + case CONTAINS -> getContainsCondition(field, value, multiValueOperator); + case NOT_CONTAINS -> getNotContainsCondition(field, value, multiValueOperator); + case START_WITH -> getStartsWithCondition(field, value, multiValueOperator); + case ENDS_WITH -> getEndsWithCondition(field, value, multiValueOperator); + case IS_NULL -> field.isNull(); + case IS_NOT_NULL -> field.isNotNull(); + case IN -> field.in(value instanceof String + ? Arrays.stream(((String) value).split(",")) + .collect(Collectors.toUnmodifiableSet()) + : value) + .or(field.isNull()); + case NOT_IN -> field.notIn(value); + }; + } +} + diff --git a/backend/src/main/java/ru/micord/ervu_dashboard/dao/LessJoinsDao.java b/backend/src/main/java/ru/micord/ervu_dashboard/dao/LessJoinsDao.java index 10808646..7875eed8 100644 --- a/backend/src/main/java/ru/micord/ervu_dashboard/dao/LessJoinsDao.java +++ b/backend/src/main/java/ru/micord/ervu_dashboard/dao/LessJoinsDao.java @@ -6,7 +6,6 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import database.dao.DefaultLoadDao; import org.jooq.Condition; import org.jooq.Field; import org.jooq.Record; @@ -31,7 +30,7 @@ import ru.cg.webbpm.modules.database.bean.filter.EntityFilter; /** * @author r.latypov */ -public class LessJoinsDao extends DefaultLoadDao { +public class LessJoinsDao extends CustomCoalesceDao { @Override public List load(Set columns, LoadOptions loadOptions, boolean withGraphConditions) { diff --git a/backend/src/main/java/ru/micord/ervu_dashboard/dao/SubqueriesJoinsDao.java b/backend/src/main/java/ru/micord/ervu_dashboard/dao/SubqueriesJoinsDao.java deleted file mode 100644 index 7f7a87b6..00000000 --- a/backend/src/main/java/ru/micord/ervu_dashboard/dao/SubqueriesJoinsDao.java +++ /dev/null @@ -1,237 +0,0 @@ -package ru.micord.ervu_dashboard.dao; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collector; -import java.util.stream.Collectors; - -import database.dao.DefaultLoadDao; -import org.jooq.Condition; -import org.jooq.Field; -import org.jooq.Record; -import org.jooq.SelectConnectByStep; -import org.jooq.SelectForUpdateStep; -import org.jooq.SelectJoinStep; -import org.jooq.SelectLimitStep; -import org.jooq.SelectSelectStep; -import org.jooq.Table; -import org.jooq.impl.DSL; - -import ru.cg.webbpm.modules.database.api.bean.TableRow; -import ru.cg.webbpm.modules.database.api.dao.option.LoadOptions; -import ru.cg.webbpm.modules.database.api.utils.QueryUtils; -import ru.cg.webbpm.modules.database.bean.entity_graph.DefaultGraphBinding; -import ru.cg.webbpm.modules.database.bean.entity_graph.DefaultGraphNode; -import ru.cg.webbpm.modules.database.bean.entity_graph.EntityColumn; -import ru.cg.webbpm.modules.database.bean.entity_graph.condition.ConditionGroup; -import ru.cg.webbpm.modules.database.bean.filter.EntityFilter; - -/** - * @author r.latypov - */ -public class SubqueriesJoinsDao extends DefaultLoadDao { - @Override - public List load(Set columns, LoadOptions loadOptions, - boolean withGraphConditions) { - DefaultGraphNode mainNode = graph.getMainNode(); - EntityColumn[] mainNodePkColumns = getGraphNodeTablePkFields(mainNode); - - if (mainNodePkColumns == null || mainNodePkColumns.length == 0) { - throw new RuntimeException("Primary key does not exist in " + mainNode.getAlias() + " : " - + mainNode.getSchemaName() + "." + mainNode.getTableName()); - } - - if (loadOptions.isLoadPK()) { - Collections.addAll(columns, mainNodePkColumns); - } - // presume only one pk column - EntityColumn mainNodePkColumn = mainNodePkColumns[0]; - - Set fieldsEntityNames = columns.stream() - .map(EntityColumn::getEntity) - .collect(Collectors.toUnmodifiableSet()); - // filters without groups - List allEntityFilters = loadOptions.getEntityFilterGroup().getEntityFilters(); - Set conditionsEntityNames = allEntityFilters.stream() - .map(entityFilter -> entityFilter.getEntityColumn().getEntity()) - .collect(Collectors.toUnmodifiableSet()); - // push entity filters into map - Map> entitiesConditionsMap = conditionsEntityNames.stream() - .collect(Collectors.toMap( - entityName -> entityName, - entityName -> getFilterConditions( - allEntityFilters.stream() - .filter(entityFilter -> entityFilter.getEntityColumn() - .getEntity() - .equals(entityName)) - .toList() - ) - )); - - // queries for define main table ids condition - Set mainTableIds = null; - Set otherEntitiesNames = conditionsEntityNames.stream() - .filter(entityName -> !fieldsEntityNames.contains(entityName)) - .collect(Collectors.toUnmodifiableSet()); - - if (!otherEntitiesNames.isEmpty()) { - mainTableIds = otherEntitiesNames.stream() - .map(entityName -> { - DefaultGraphNode graphNode = graph.getNodesByEntityNames().get(entityName); - DefaultGraphBinding binding = graph.getBindingByEndNode(graphNode); - if (binding == null) { - throw new RuntimeException("Entity " + entityName + " is not bound"); - } - else { - EntityColumn refEntityColumn = null; - if (binding.getRefOnEntityName().equals(entityName)) { - refEntityColumn = binding.getRefOnColumns()[0]; - } - if (binding.getRefToEntityName().equals(entityName)) { - refEntityColumn = binding.getRefToColumns()[0]; - } - - if (refEntityColumn == null) { - throw new RuntimeException("Binding for entity " + entityName + " is not valid"); - } - else { - Field selectField = QueryUtils.toJooqField( - refEntityColumn, this.getDsl().dialect(), true - ); - return getDsl() - .select(selectField) - .from(getTableWithAlias(graphNode)) - .where(entitiesConditionsMap.get(entityName)) - .fetch(selectField); - } - } - }) - // collect only intersection of all lists of ids - .collect(intersecting()); - } - - List mainConditions = getFilterConditions( - allEntityFilters.stream() - .filter(entityFilter -> fieldsEntityNames.contains( - entityFilter.getEntityColumn().getTable())) - .collect(Collectors.toList()) - ); - Field pkField = QueryUtils.toJooqField(mainNodePkColumn, this.getDsl().dialect(), true); - mainConditions.add(mainTableIds == null ? DSL.trueCondition() : pkField.in(mainTableIds)); - - List> fieldList = convertEntityColumnsToJooqFields(columns); - SelectJoinStep selectStep = selectByJooqColumns(fieldsEntityNames, fieldList); - - SelectConnectByStep joinStep = buildWhereStep(selectStep, mainConditions, - withGraphConditions - ); - SelectLimitStep limitStep = getOrderByStep(joinStep, loadOptions.getSortFields(), true); - SelectForUpdateStep selectForUpdateStep = getSelectForUpdateStep(limitStep, - loadOptions.getOffset(), loadOptions.getLimit() - ); - - return recordListToTableFieldDataList(selectForUpdateStep.fetch(), columns); - } - - private SelectJoinStep selectByJooqColumns(Set fieldsEntityNames, - List> fieldList) { - SelectSelectStep select = uniqueResult - ? getDsl().selectDistinct(fieldList) - : getDsl().select(fieldList); - DefaultGraphNode mainNode = graph.getMainNode(); - SelectJoinStep selectJoinStep = select.from(getTableWithAlias(mainNode)); - addJoins( - fieldsEntityNames, selectJoinStep, graph.getMainNodeIndex(), -1, getMatrix() - ); - return selectJoinStep; - } - - private void addJoins(Set fieldsEntityNames, SelectJoinStep selectJoinStep, - int currentIndex, int parentIndex, DefaultGraphBinding[][] matrix) { - - for (int i = 0; i < matrix.length; i++) { - - if (matrix[currentIndex][i] == null || matrix[currentIndex][i].isCyclic() - || (parentIndex >= 0 && i == parentIndex)) { - continue; - } - DefaultGraphNode endNode = graph.getNodeByIndex(i); - Table endNodeTable = getTableWithAlias(endNode); - - String tableAlias = endNode.getAlias(); - if (!fieldsEntityNames.contains(tableAlias)) { - continue; - } - - DefaultGraphBinding binding = matrix[currentIndex][i]; - if (binding.getRefToColumns() != null && binding.getRefOnColumns() != null) { - EntityColumn refOnColumnsEntityColumn = binding.getRefOnColumns()[0]; - refOnColumnsEntityColumn.setEntity(binding.getRefOnEntityName()); - EntityColumn refToColumnsEntityColumn = binding.getRefToColumns()[0]; - refToColumnsEntityColumn.setEntity(binding.getRefToEntityName()); - Field startNodeCol = QueryUtils.toJooqField(refOnColumnsEntityColumn); - Field endNodeCol = QueryUtils.toJooqField(refToColumnsEntityColumn); - @SuppressWarnings("unchecked") - Condition bindingCondition = startNodeCol.equal(endNodeCol); - - for (int j = 1; j < binding.getRefOnColumns().length; j++) { - startNodeCol = QueryUtils.toJooqField(binding.getRefOnColumns()[j]); - endNodeCol = QueryUtils.toJooqField(binding.getRefToColumns()[j]); - @SuppressWarnings("unchecked") - Condition condition = startNodeCol.equal(endNodeCol); - bindingCondition = bindingCondition.and(condition); - } - ConditionGroup conditionGroup = binding.getConditionGroup(); - - if (conditionGroup != null) { - Condition condition = conditionBuilder.build(conditionGroup); - - if (condition != null) { - bindingCondition = bindingCondition.and(condition); - } - } - - if (binding.isRequired()) { - selectJoinStep.join(endNodeTable).on(bindingCondition); - } - else { - selectJoinStep.leftOuterJoin(endNodeTable).on(bindingCondition); - } - } - addJoins(fieldsEntityNames, selectJoinStep, i, currentIndex, matrix); - } - } - - private static > Collector> intersecting() { - class Accumulator { - Set result; - - void accumulate(S collection) { - if (result == null) { - result = new HashSet<>(collection); - } - else { - result.retainAll(collection); - } - } - - Accumulator combine(Accumulator other) { - if (result == null) { - return other; - } - if (other.result != null) { - result.retainAll(other.result); - } - return this; - } - } - return Collector.of(Accumulator::new, Accumulator::accumulate, Accumulator::combine, - acc -> acc.result == null ? Collections.emptySet() : acc.result, - Collector.Characteristics.UNORDERED - ); - } -} diff --git a/frontend/src/ts/ervu-dashboard/component/filter/DropdownTreeViewModelFilterValue.ts b/frontend/src/ts/ervu-dashboard/component/filter/DropdownTreeViewModelFilterValue.ts new file mode 100644 index 00000000..d7b99b17 --- /dev/null +++ b/frontend/src/ts/ervu-dashboard/component/filter/DropdownTreeViewModelFilterValue.ts @@ -0,0 +1,36 @@ +import {AdvancedProperty, AnalyticalScope, Behavior, Visible} from "@webbpm/base-package"; +import {DropdownTreeViewComponent} from "../../../component/field/DropdownTreeViewComponent"; +import {TreeItemDto} from "../../../generated/component/model/TreeItemDto"; + +@AnalyticalScope(DropdownTreeViewComponent) +export class DropdownTreeViewModelFilterValue extends Behavior { + + public isBusinessId: boolean; + @AdvancedProperty() + public separator: string; + + @Visible() + public getIds(): string { + const treeViewComponent = this.getScript( + DropdownTreeViewComponent) as DropdownTreeViewComponent; + let model: TreeItemDto = treeViewComponent.value; + const dtos: TreeItemDto[] = []; + + if (!model && treeViewComponent.cachedValue) { + model = treeViewComponent.cachedValue; + } + if (model) { + dtos.push(model); + this.fillArray(dtos, model); + } + const ids: string[] = dtos.map(value => this.isBusinessId ? value.businessId : value.id); + return this.separator ? ids.join(this.separator) : ids.join(); + } + + private fillArray(dtos: TreeItemDto[], dto: TreeItemDto): void { + if (dto.children) { + dtos.push(...dto.children); + dto.children.forEach(value => this.fillArray(dtos, value)); + } + } +}