Skip to content

Commit

Permalink
JNG-6025 BreakRecursion (#230)
Browse files Browse the repository at this point in the history
* JNG-6025 Break recursive query

* add rdbmsDaoMaximumRecursionCount default parameter

* add try finally block

---------

Co-authored-by: Robert Csakany <[email protected]>
  • Loading branch information
gaborflorian and robertcsakany authored Jan 13, 2025
1 parent c8a3626 commit c60b40e
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -185,6 +174,7 @@ public class SelectStatementExecutor<ID> extends StatementExecutor<ID> {
private final DataTypeManager dataTypeManager;
private final int chunkSize;
private final AsmUtils asmUtils;
private final int maximumRecursionCount;

@Builder
public SelectStatementExecutor(@NonNull final AsmModel asmModel,
Expand All @@ -197,13 +187,16 @@ public SelectStatementExecutor(@NonNull final AsmModel asmModel,
@NonNull final IdentifierProvider<ID> identifierProvider,
@NonNull final RdbmsBuilder<ID> 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());

Expand Down Expand Up @@ -358,7 +351,7 @@ public Collection<Payload> 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<Payload> ret = result.get(select.getMainTarget()).values();
if (queryCustomizer != null &&
Expand Down Expand Up @@ -433,8 +426,8 @@ public Payload executeSelect(final NamedParameterJdbcTemplate jdbcTemplate,
.orElseThrow(() -> new IllegalStateException("Query for static data not prepared yet"));

final Map<Target, Map<ID, Payload>> 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<Payload> resultSet = results.get(subSelect.getSelect().getMainTarget()).values();
checkArgument(resultSet.size() == 1, "Invalid result set");
Expand Down Expand Up @@ -527,7 +520,7 @@ public Collection<Payload> 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();
Expand Down Expand Up @@ -779,7 +772,8 @@ private QueryResult<ID> runQuery(
final boolean withoutFeatures,
final Map<String, Object> mask,
final Map<String, Object> queryParameters,
final boolean skipParents
final boolean skipParents,
final Stack<SubSelect> subSelectStack
) {

// get JDBC result set and process the records
Expand Down Expand Up @@ -883,7 +877,7 @@ private QueryResult<ID> 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);
}
}
}
Expand All @@ -901,7 +895,9 @@ private void mapResults(final Map<Target, Map<ID, Payload>> results,
final Map<String, Object> mask,
final List<EReference> referenceChain,
final boolean withoutFeatures,
final Map<String, Object> queryParameters) {
final Map<String, Object> queryParameters,
final Stack<SubSelect> subSelectStack
) {
// key used to identify parent instance in subselects
final String parentKey = RdbmsAliasUtil.getParentIdColumnAlias(query.getContainer());
final SelectStatementExecutorQueryMetaCache metaCache = new SelectStatementExecutorQueryMetaCache(query, mask, referenceChain);
Expand Down Expand Up @@ -1093,9 +1089,17 @@ private void mapResults(final Map<Target, Map<ID, Payload>> results,
if (!withoutFeatures) {
for (Pair<List<EReference>, List<SubSelect>> e : metaCache.getSingleEmbeddedReferences()) {
for (SubSelect subSelect : e.getValue()) {
runSubQuery(jdbcTemplate, query, subSelect, e.getKey(), results,
mask != null ? (Map<String, Object>) 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<String, Object>) mask.get(subSelect.getTransferRelation().getName()) : null,
queryParameters, subSelectStack);
} finally {
subSelectStack.pop();
}
}
}
}
}
Expand Down Expand Up @@ -1199,7 +1203,7 @@ private long countQuery(
false,
new HashMap<>(),
queryParameters,
true).getCount();
true, new Stack<>()).getCount();
}

private void runSubQuery(final NamedParameterJdbcTemplate jdbcTemplate,
Expand All @@ -1208,7 +1212,8 @@ private void runSubQuery(final NamedParameterJdbcTemplate jdbcTemplate,
final List<EReference> referenceChain,
final Map<Target, Map<ID, Payload>> results,
final Map<String, Object> mask,
final Map<String, Object> queryParameters) {
final Map<String, Object> queryParameters,
final Stack<SubSelect> subSelectStack) {
checkArgument(subSelect.getTransferRelation() != null,
"SubSelect must have transfer relation");

Expand All @@ -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);
}
}
}
Expand All @@ -1253,14 +1258,15 @@ private void executeSubQuery(final NamedParameterJdbcTemplate jdbcTemplate,
final Map<Target, Map<ID, Payload>> results,
final Collection<ID> ids,
final Map<String, Object> mask,
final Map<String, Object> queryParameters) {
final Map<String, Object> queryParameters,
final Stack<SubSelect> subSelectStack) {
if (log.isTraceEnabled()) {
log.trace(" IDs: {}", ids);
}

// map storing subquery results, it will be filled by recursive call
final Map<Target, Map<ID, Payload>> 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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -139,6 +140,7 @@ public JudoDefaultModule(
Boolean rdbmsDaoOptimisticLockEnabled,
Boolean rdbmsDaoMarkSelectedRangeItems,
Integer rdbmsDaoChunkSize,
Integer rdbmsDaoMaximumRecursionCount,
Boolean actorResolverCheckMappedActors,
Boolean dispatcherMetricsReturned,
Boolean dispatcherEnableDefaultValidation,
Expand Down Expand Up @@ -191,6 +193,7 @@ public JudoDefaultModule(
.rdbmsDaoOptimisticLockEnabled(rdbmsDaoOptimisticLockEnabled)
.rdbmsDaoMarkSelectedRangeItems(rdbmsDaoMarkSelectedRangeItems)
.rdbmsDaoChunkSize(rdbmsDaoChunkSize)
.rdbmsDaoMaximumRecursionCount(rdbmsDaoMaximumRecursionCount)
.actorResolverCheckMappedActors(actorResolverCheckMappedActors)
.dispatcherMetricsReturned(dispatcherMetricsReturned)
.dispatcherEnableDefaultValidation(dispatcherEnableDefaultValidation)
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@ public class SelectStatementExecutorProvider implements Provider<SelectStatement

@Inject(optional = true)
@JudoConfigurationQualifiers.RdbmsDaoChunkSize
@Nullable
private Integer chunkSize = 1000;

@Inject(optional = true)
@JudoConfigurationQualifiers.RdbmsDaoMaximumRecursionCount
private Integer maximumRecursionCount = 3;

@SuppressWarnings("unchecked")
@Override
public SelectStatementExecutor get() {
Expand All @@ -86,6 +89,7 @@ public SelectStatementExecutor get() {
.identifierProvider(identifierProvider)
.metricsCollector(metricsCollector)
.chunkSize(this.chunkSize)
.maximumRecursionCount(this.maximumRecursionCount)
.transformationTraceService(this.transformationTraceService)
.rdbmsParameterMapper(rdbmsParameterMapper)
.rdbmsBuilder(rdbmsBuilder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ public SelectStatementExecutor getSelectStatementExecutor(
) {
// TODO: Map parameter
Integer chunkSize = 1000;
Integer maximumRecursionCount=3;

return SelectStatementExecutor.builder()
.asmModel(asmModel)
Expand All @@ -273,6 +274,7 @@ public SelectStatementExecutor getSelectStatementExecutor(
.identifierProvider(identifierProvider)
.metricsCollector(metricsCollector)
.chunkSize(chunkSize)
.maximumRecursionCount(maximumRecursionCount)
.transformationTraceService(transformationTraceService)
.rdbmsParameterMapper(rdbmsParameterMapper)
.rdbmsBuilder(rdbmsBuilder)
Expand Down

0 comments on commit c60b40e

Please sign in to comment.