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 5 months 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() { private EntityDataQuery buildEntityDataQuery() {
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null,
new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, ModelConstants.CREATED_TIME_PROPERTY))); 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() { 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; entitiesSortOrder = sortOrder;
} }
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder); 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()); validateEntityNameQuery((EntityNameFilter) query.getEntityFilter());
} }
if (!keyFiltersOrConditionsEnabled && query.getKeyFiltersOperation() == ComplexOperation.OR) { 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) { public Stream<String> toQueries(SqlQueryContext ctx, EntityFilterType filterType, boolean outerContext) {
if (hasFilter()) { if (!hasFilter()) {
String keyAlias; return Stream.empty();
if (entityKey.getType().equals(EntityKeyType.ENTITY_FIELD) && getEntityKeyColumn() != null) { }
if (outerContext) { String keyAlias;
// In the middle layer (OR relocation), entity field columns are available boolean useAliasDirectly = false;
// by their alias name from the inner subquery SELECT (e.g., "alias2" from if (entityKey.getType().equals(EntityKeyType.ENTITY_FIELD) && getEntityKeyColumn() != null) {
// "cast(e.name as varchar) as alias2"). Temporarily null out entityKeyColumn if (outerContext) {
// so buildSimplePredicateQuery uses the alias directly as the field. // In the middle layer (OR relocation), entity field columns are exposed
String savedColumn = this.entityKeyColumn; // by their alias name from the inner subquery SELECT (e.g., "alias2" from
try { // "cast(e.name as varchar) as alias2"), so buildSimplePredicateQuery must
this.entityKeyColumn = null; // use the alias directly as the field instead of appending entityKeyColumn.
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 {
keyAlias = alias; keyAlias = alias;
useAliasDirectly = true;
} else {
keyAlias = "e";
} }
return keyFilters.stream().map(keyFilter ->
this.buildKeyQuery(ctx, keyAlias, keyFilter, filterType));
} else { } 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) { 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, private String buildKeyQuery(SqlQueryContext ctx, String alias, KeyFilter keyFilter,
EntityFilterType filterType) { 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, 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)) { 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 { } 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, private String buildComplexPredicateQuery(SqlQueryContext ctx, String alias, EntityKey key,
ComplexFilterPredicate predicate, EntityFilterType filterType) { ComplexFilterPredicate predicate, EntityFilterType filterType, boolean useAliasAsField) {
String result = predicate.getPredicates().stream() 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) .filter(StringUtils::isNotEmpty)
.collect(Collectors.joining(" " + predicate.getOperation().name() + " ")); .collect(Collectors.joining(" " + predicate.getOperation().name() + " "));
if (!result.trim().isEmpty()) { if (!result.trim().isEmpty()) {
@ -645,9 +643,9 @@ public class EntityKeyMapping {
} }
private String buildSimplePredicateQuery(SqlQueryContext ctx, String alias, EntityKey key, 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)) { 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)) { if (predicate.getType().equals(FilterPredicateType.NUMERIC)) {
return this.buildNumericPredicateQuery(ctx, field, (NumericFilterPredicate) predicate); return this.buildNumericPredicateQuery(ctx, field, (NumericFilterPredicate) predicate);
} else if (predicate.getType().equals(FilterPredicateType.STRING)) { } else if (predicate.getType().equals(FilterPredicateType.STRING)) {

Loading…
Cancel
Save