diff --git a/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/executors/SelectStatementExecutor.java b/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/executors/SelectStatementExecutor.java index 18ab5965..7ca18d14 100644 --- a/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/executors/SelectStatementExecutor.java +++ b/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/executors/SelectStatementExecutor.java @@ -27,18 +27,7 @@ import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZoneOffset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; @@ -185,6 +174,7 @@ public class SelectStatementExecutor extends StatementExecutor { private final DataTypeManager dataTypeManager; private final int chunkSize; private final AsmUtils asmUtils; + private final int maximumRecursionCount; @Builder public SelectStatementExecutor(@NonNull final AsmModel asmModel, @@ -197,13 +187,16 @@ public SelectStatementExecutor(@NonNull final AsmModel asmModel, @NonNull final IdentifierProvider identifierProvider, @NonNull final RdbmsBuilder rdbmsBuilder, @NonNull final MetricsCollector metricsCollector, - @NonNull final Integer chunkSize) { + @NonNull final Integer chunkSize, + @NonNull final Integer maximumRecursionCount) { + super(asmModel, rdbmsModel, transformationTraceService, rdbmsParameterMapper, rdbmsResolver, dataTypeManager.getCoercer(), identifierProvider); this.queryFactory = queryFactory; this.dataTypeManager = dataTypeManager; this.metricsCollector = metricsCollector; this.chunkSize = chunkSize; + this.maximumRecursionCount = maximumRecursionCount; asmUtils = new AsmUtils(asmModel.getResourceSet()); @@ -358,7 +351,7 @@ public Collection executeSelect(final NamedParameterJdbcTemplate jdbcTe queryCustomizer != null ? queryCustomizer.getSeek() : null, queryCustomizer != null && queryCustomizer.isWithoutFeatures(), queryCustomizer != null ? queryCustomizer.getMask() : null, - queryCustomizer != null ? queryCustomizer.getParameters() : null, true).getResultSet(); + queryCustomizer != null ? queryCustomizer.getParameters() : null, true, new Stack<>()).getResultSet(); final Collection ret = result.get(select.getMainTarget()).values(); if (queryCustomizer != null && @@ -433,8 +426,8 @@ public Payload executeSelect(final NamedParameterJdbcTemplate jdbcTemplate, .orElseThrow(() -> new IllegalStateException("Query for static data not prepared yet")); final Map> results = - runQuery(jdbcTemplate, subSelect, false, null, null, Collections.emptyList(), null, - false, Collections.singletonMap(attribute.getName(), true), parameters, true).getResultSet(); + runQuery(jdbcTemplate, subSelect, false,null, null, Collections.emptyList(), null, + false, Collections.singletonMap(attribute.getName(), true), parameters, true, new Stack<>()).getResultSet(); final Collection resultSet = results.get(subSelect.getSelect().getMainTarget()).values(); checkArgument(resultSet.size() == 1, "Invalid result set"); @@ -527,7 +520,7 @@ public Collection executeSelect(final NamedParameterJdbcTemplate jdbcTe queryCustomizer != null ? queryCustomizer.getSeek() : null, queryCustomizer != null && queryCustomizer.isWithoutFeatures(), queryCustomizer != null ? queryCustomizer.getMask() : null, - queryCustomizer != null ? queryCustomizer.getParameters() : null, true) + queryCustomizer != null ? queryCustomizer.getParameters() : null, true, new Stack<>()) .getResultSet(); final Target subQueryTarget = query.getSelect().getMainTarget(); @@ -779,7 +772,8 @@ private QueryResult runQuery( final boolean withoutFeatures, final Map mask, final Map queryParameters, - final boolean skipParents + final boolean skipParents, + final Stack subSelectStack ) { // get JDBC result set and process the records @@ -883,7 +877,7 @@ private QueryResult runQuery( resultSet = jdbcTemplate.queryForList(sql, sqlParameters); } try (MetricsCancelToken ct = metricsCollector.start(METRICS_SELECT_PROCESSING)) { - mapResults(results, jdbcTemplate, query, resultSet, chunk, mask, referenceChain, withoutFeatures, queryParameters); + mapResults(results, jdbcTemplate, query, resultSet, chunk, mask, referenceChain, withoutFeatures, queryParameters, subSelectStack); } } } @@ -901,7 +895,9 @@ private void mapResults(final Map> results, final Map mask, final List referenceChain, final boolean withoutFeatures, - final Map queryParameters) { + final Map queryParameters, + final Stack subSelectStack + ) { // key used to identify parent instance in subselects final String parentKey = RdbmsAliasUtil.getParentIdColumnAlias(query.getContainer()); final SelectStatementExecutorQueryMetaCache metaCache = new SelectStatementExecutorQueryMetaCache(query, mask, referenceChain); @@ -1093,9 +1089,17 @@ private void mapResults(final Map> results, if (!withoutFeatures) { for (Pair, List> e : metaCache.getSingleEmbeddedReferences()) { for (SubSelect subSelect : e.getValue()) { - runSubQuery(jdbcTemplate, query, subSelect, e.getKey(), results, - mask != null ? (Map) mask.get(subSelect.getTransferRelation().getName()) : null, - queryParameters); + long cnt = subSelectStack.stream().filter(s -> s == subSelect).count(); + if (cnt < maximumRecursionCount) { + subSelectStack.push(subSelect); + try { + runSubQuery(jdbcTemplate, query, subSelect, e.getKey(), results, + mask != null ? (Map) mask.get(subSelect.getTransferRelation().getName()) : null, + queryParameters, subSelectStack); + } finally { + subSelectStack.pop(); + } + } } } } @@ -1199,7 +1203,7 @@ private long countQuery( false, new HashMap<>(), queryParameters, - true).getCount(); + true, new Stack<>()).getCount(); } private void runSubQuery(final NamedParameterJdbcTemplate jdbcTemplate, @@ -1208,7 +1212,8 @@ private void runSubQuery(final NamedParameterJdbcTemplate jdbcTemplate, final List referenceChain, final Map> results, final Map mask, - final Map queryParameters) { + final Map queryParameters, + final Stack subSelectStack) { checkArgument(subSelect.getTransferRelation() != null, "SubSelect must have transfer relation"); @@ -1233,10 +1238,10 @@ private void runSubQuery(final NamedParameterJdbcTemplate jdbcTemplate, (Objects.equals(subSelect.getBase(), query.getSelect()) || query.getSelect().getAllJoins().contains(subSelect.getBase())) && subSelect.getLimit() == null) { - executeSubQuery(jdbcTemplate, query, subSelect, newReferenceChain, results, ids, mask, queryParameters); + executeSubQuery(jdbcTemplate, query, subSelect, newReferenceChain, results, ids, mask, queryParameters, subSelectStack); } else { for (ID id : ids) { - executeSubQuery(jdbcTemplate, query, subSelect, newReferenceChain, results, Collections.singleton(id), mask, queryParameters); + executeSubQuery(jdbcTemplate, query, subSelect, newReferenceChain, results, Collections.singleton(id), mask, queryParameters, subSelectStack); } } } @@ -1253,14 +1258,15 @@ private void executeSubQuery(final NamedParameterJdbcTemplate jdbcTemplate, final Map> results, final Collection ids, final Map mask, - final Map queryParameters) { + final Map queryParameters, + final Stack subSelectStack) { if (log.isTraceEnabled()) { log.trace(" IDs: {}", ids); } // map storing subquery results, it will be filled by recursive call final Map> subQueryResults = - runQuery(jdbcTemplate, subSelect, false, null, ids, newReferenceChain, null, false, mask, queryParameters, false) + runQuery(jdbcTemplate, subSelect, false, null, ids, newReferenceChain, null, false, mask, queryParameters, false, subSelectStack) .getResultSet(); if (log.isDebugEnabled()) { diff --git a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoConfigurationQualifiers.java b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoConfigurationQualifiers.java index 23c1ece7..56c67816 100644 --- a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoConfigurationQualifiers.java +++ b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoConfigurationQualifiers.java @@ -27,6 +27,11 @@ public class JudoConfigurationQualifiers { @Retention(RetentionPolicy.RUNTIME) public @interface RdbmsDaoChunkSize {} + @Qualifier + @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface RdbmsDaoMaximumRecursionCount {} + @Qualifier @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) diff --git a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoDefaultModule.java b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoDefaultModule.java index 689845b4..3f10b8a3 100644 --- a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoDefaultModule.java +++ b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoDefaultModule.java @@ -87,6 +87,7 @@ public static class JudoDefaultModuleBuilder { Boolean rdbmsDaoOptimisticLockEnabled = JudoDefaultModuleConfiguration.DEFAULT.getRdbmsDaoOptimisticLockEnabled(); Boolean rdbmsDaoMarkSelectedRangeItems = JudoDefaultModuleConfiguration.DEFAULT.getRdbmsDaoMarkSelectedRangeItems(); Integer rdbmsDaoChunkSize = JudoDefaultModuleConfiguration.DEFAULT.getRdbmsDaoChunkSize(); + Integer rdbmsDaoMaximumRecursionCount = JudoDefaultModuleConfiguration.DEFAULT.getRdbmsDaoMaximumRecursionCount(); Boolean actorResolverCheckMappedActors = JudoDefaultModuleConfiguration.DEFAULT.getActorResolverCheckMappedActors(); Boolean dispatcherMetricsReturned = JudoDefaultModuleConfiguration.DEFAULT.getDispatcherMetricsReturned(); Boolean dispatcherEnableDefaultValidation = JudoDefaultModuleConfiguration.DEFAULT.getDispatcherEnableDefaultValidation(); @@ -139,6 +140,7 @@ public JudoDefaultModule( Boolean rdbmsDaoOptimisticLockEnabled, Boolean rdbmsDaoMarkSelectedRangeItems, Integer rdbmsDaoChunkSize, + Integer rdbmsDaoMaximumRecursionCount, Boolean actorResolverCheckMappedActors, Boolean dispatcherMetricsReturned, Boolean dispatcherEnableDefaultValidation, @@ -191,6 +193,7 @@ public JudoDefaultModule( .rdbmsDaoOptimisticLockEnabled(rdbmsDaoOptimisticLockEnabled) .rdbmsDaoMarkSelectedRangeItems(rdbmsDaoMarkSelectedRangeItems) .rdbmsDaoChunkSize(rdbmsDaoChunkSize) + .rdbmsDaoMaximumRecursionCount(rdbmsDaoMaximumRecursionCount) .actorResolverCheckMappedActors(actorResolverCheckMappedActors) .dispatcherMetricsReturned(dispatcherMetricsReturned) .dispatcherEnableDefaultValidation(dispatcherEnableDefaultValidation) @@ -267,6 +270,7 @@ protected void configureOptions() { bind(Boolean.class).annotatedWith(JudoConfigurationQualifiers.RdbmsDaoOptimisticLockEnabled.class).toInstance(configuration.getRdbmsDaoOptimisticLockEnabled()); bind(Boolean.class).annotatedWith(JudoConfigurationQualifiers.RdbmsDaoMarkSelectedRangeItems.class).toInstance(configuration.getRdbmsDaoMarkSelectedRangeItems()); bind(Integer.class).annotatedWith(JudoConfigurationQualifiers.RdbmsDaoChunkSize.class).toInstance(configuration.getRdbmsDaoChunkSize()); + bind(Integer.class).annotatedWith(JudoConfigurationQualifiers.RdbmsDaoMaximumRecursionCount.class).toInstance(configuration.getRdbmsDaoMaximumRecursionCount()); bind(Boolean.class).annotatedWith(JudoConfigurationQualifiers.ActorResolverCheckMappedActors.class).toInstance(configuration.getActorResolverCheckMappedActors()); bind(Boolean.class).annotatedWith(JudoConfigurationQualifiers.DispatcherMetricsReturned.class).toInstance(configuration.getDispatcherMetricsReturned()); bind(Boolean.class).annotatedWith(JudoConfigurationQualifiers.DispatcherEnableDefaultValidation.class).toInstance(configuration.getDispatcherEnableDefaultValidation()); diff --git a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoDefaultModuleConfiguration.java b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoDefaultModuleConfiguration.java index 6701bebb..e6925582 100644 --- a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoDefaultModuleConfiguration.java +++ b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/JudoDefaultModuleConfiguration.java @@ -57,6 +57,8 @@ public class JudoDefaultModuleConfiguration { @Builder.Default Integer rdbmsDaoChunkSize = 1000; @Builder.Default + Integer rdbmsDaoMaximumRecursionCount = 3; + @Builder.Default Boolean actorResolverCheckMappedActors = false; @Builder.Default Boolean dispatcherMetricsReturned = false; diff --git a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/dao/rdbms/SelectStatementExecutorProvider.java b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/dao/rdbms/SelectStatementExecutorProvider.java index 4cc886e5..12e03727 100644 --- a/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/dao/rdbms/SelectStatementExecutorProvider.java +++ b/judo-runtime-core-guice/src/main/java/hu/blackbelt/judo/runtime/core/guice/dao/rdbms/SelectStatementExecutorProvider.java @@ -72,9 +72,12 @@ public class SelectStatementExecutorProvider implements Provider