Browse Source

Address backend PR review comments for OR key filter conditions

- EntityKeyMapping: replace fragile null-swap of entityKeyColumn with a useAliasAsField flag threaded through buildKeyQuery/buildPredicateQuery, avoiding mutation of shared instance state
- TbAlarmCountSubCtx, TbAlarmDataSubCtx: normalize keyFiltersOperation at query construction via getKeyFiltersOperationOrDefault(), matching DefaultEntityQueryService
- BaseEntityService: end the disabled-OR validation error with a period and attribute it to the system administrator, aligning with the existing UI translation
- EntityQueryControllerTest: extract helpers (createDeviceWithSharedAttributes, createDeviceWithTimeseries, createAlarm, deviceTypeFilter, numericKeyFilter, stringKeyFilter, pageLinkSortedByName, nameEntityField, extractNames) and replace the pagination test's obscure temperature ternary with an explicit array
pull/15286/head
Viacheslav Klimov 1 month ago
parent
commit
1a0f382d02
Failed to extract signature
  1. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java
  2. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  3. 1031
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  4. 2
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  5. 64
      dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmCountSubCtx.java

@ -130,7 +130,7 @@ public class TbAlarmCountSubCtx extends TbAbstractEntityQuerySubCtx<AlarmCountQu
private EntityDataQuery buildEntityDataQuery() {
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null,
new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, ModelConstants.CREATED_TIME_PROPERTY)));
return new EntityDataQuery(query.getEntityFilter(), edpl, null, null, query.getKeyFilters(), query.getKeyFiltersOperation());
return new EntityDataQuery(query.getEntityFilter(), edpl, null, null, query.getKeyFilters(), query.getKeyFiltersOperationOrDefault());
}
private void resetInvocationCounter() {

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -363,7 +363,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
entitiesSortOrder = sortOrder;
}
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder);
return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters(), query.getKeyFiltersOperation());
return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters(), query.getKeyFiltersOperationOrDefault());
}
}

1031
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

File diff suppressed because it is too large

2
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -348,7 +348,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateEntityNameQuery((EntityNameFilter) query.getEntityFilter());
}
if (!keyFiltersOrConditionsEnabled && query.getKeyFiltersOperation() == ComplexOperation.OR) {
throw new IncorrectParameterException("OR conditions between key filters are disabled");
throw new IncorrectParameterException("OR conditions between key filters are disabled by the system administrator.");
}
}

64
dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java

@ -288,35 +288,28 @@ public class EntityKeyMapping {
}
public Stream<String> toQueries(SqlQueryContext ctx, EntityFilterType filterType, boolean outerContext) {
if (hasFilter()) {
String keyAlias;
if (entityKey.getType().equals(EntityKeyType.ENTITY_FIELD) && getEntityKeyColumn() != null) {
if (outerContext) {
// In the middle layer (OR relocation), entity field columns are available
// by their alias name from the inner subquery SELECT (e.g., "alias2" from
// "cast(e.name as varchar) as alias2"). Temporarily null out entityKeyColumn
// so buildSimplePredicateQuery uses the alias directly as the field.
String savedColumn = this.entityKeyColumn;
try {
this.entityKeyColumn = null;
List<String> predicates = keyFilters.stream()
.map(keyFilter -> this.buildKeyQuery(ctx, alias, keyFilter, filterType))
.collect(Collectors.toList());
return predicates.stream();
} finally {
this.entityKeyColumn = savedColumn;
}
} else {
keyAlias = "e";
}
} else {
if (!hasFilter()) {
return Stream.empty();
}
String keyAlias;
boolean useAliasDirectly = false;
if (entityKey.getType().equals(EntityKeyType.ENTITY_FIELD) && getEntityKeyColumn() != null) {
if (outerContext) {
// In the middle layer (OR relocation), entity field columns are exposed
// by their alias name from the inner subquery SELECT (e.g., "alias2" from
// "cast(e.name as varchar) as alias2"), so buildSimplePredicateQuery must
// use the alias directly as the field instead of appending entityKeyColumn.
keyAlias = alias;
useAliasDirectly = true;
} else {
keyAlias = "e";
}
return keyFilters.stream().map(keyFilter ->
this.buildKeyQuery(ctx, keyAlias, keyFilter, filterType));
} else {
return Stream.empty();
keyAlias = alias;
}
final boolean aliasAsField = useAliasDirectly;
return keyFilters.stream().map(keyFilter ->
this.buildKeyQuery(ctx, keyAlias, keyFilter, filterType, aliasAsField));
}
public String toLatestJoin(SqlQueryContext ctx, EntityFilter entityFilter, EntityType entityType) {
@ -620,22 +613,27 @@ public class EntityKeyMapping {
private String buildKeyQuery(SqlQueryContext ctx, String alias, KeyFilter keyFilter,
EntityFilterType filterType) {
return this.buildPredicateQuery(ctx, alias, keyFilter.getKey(), keyFilter.getPredicate(), filterType);
return this.buildKeyQuery(ctx, alias, keyFilter, filterType, false);
}
private String buildKeyQuery(SqlQueryContext ctx, String alias, KeyFilter keyFilter,
EntityFilterType filterType, boolean useAliasAsField) {
return this.buildPredicateQuery(ctx, alias, keyFilter.getKey(), keyFilter.getPredicate(), filterType, useAliasAsField);
}
private String buildPredicateQuery(SqlQueryContext ctx, String alias, EntityKey key,
KeyFilterPredicate predicate, EntityFilterType filterType) {
KeyFilterPredicate predicate, EntityFilterType filterType, boolean useAliasAsField) {
if (predicate.getType().equals(FilterPredicateType.COMPLEX)) {
return this.buildComplexPredicateQuery(ctx, alias, key, (ComplexFilterPredicate) predicate, filterType);
return this.buildComplexPredicateQuery(ctx, alias, key, (ComplexFilterPredicate) predicate, filterType, useAliasAsField);
} else {
return this.buildSimplePredicateQuery(ctx, alias, key, predicate, filterType);
return this.buildSimplePredicateQuery(ctx, alias, key, predicate, filterType, useAliasAsField);
}
}
private String buildComplexPredicateQuery(SqlQueryContext ctx, String alias, EntityKey key,
ComplexFilterPredicate predicate, EntityFilterType filterType) {
ComplexFilterPredicate predicate, EntityFilterType filterType, boolean useAliasAsField) {
String result = predicate.getPredicates().stream()
.map(keyFilterPredicate -> this.buildPredicateQuery(ctx, alias, key, keyFilterPredicate, filterType))
.map(keyFilterPredicate -> this.buildPredicateQuery(ctx, alias, key, keyFilterPredicate, filterType, useAliasAsField))
.filter(StringUtils::isNotEmpty)
.collect(Collectors.joining(" " + predicate.getOperation().name() + " "));
if (!result.trim().isEmpty()) {
@ -645,9 +643,9 @@ public class EntityKeyMapping {
}
private String buildSimplePredicateQuery(SqlQueryContext ctx, String alias, EntityKey key,
KeyFilterPredicate predicate, EntityFilterType filterType) {
KeyFilterPredicate predicate, EntityFilterType filterType, boolean useAliasAsField) {
if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
String field = (getEntityKeyColumn() != null) ? alias + "." + getEntityKeyColumn() : alias;
String field = useAliasAsField || getEntityKeyColumn() == null ? alias : alias + "." + getEntityKeyColumn();
if (predicate.getType().equals(FilterPredicateType.NUMERIC)) {
return this.buildNumericPredicateQuery(ctx, field, (NumericFilterPredicate) predicate);
} else if (predicate.getType().equals(FilterPredicateType.STRING)) {

Loading…
Cancel
Save