From 4ae486a5f1173784aeb7f66f339b1aba9a217763 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 23 Mar 2026 16:38:29 +0200 Subject: [PATCH] Add feature toggle, SystemParams field, and service-layer validation - Add sql.query.key-filters-or-conditions.enabled property to thingsboard.yml - Add allowKeyFiltersOrConditions field to SystemParams - Wire config value into SystemInfoController for /api/system/params - Add validateKeyFiltersOperation to DefaultEntityQueryService - Add throws ThingsboardException to EntityQueryService interface methods - Reject OR queries with 400 when feature is disabled --- .../controller/SystemInfoController.java | 4 ++++ .../query/DefaultEntityQueryService.java | 22 +++++++++++++++---- .../service/query/EntityQueryService.java | 9 ++++---- .../src/main/resources/thingsboard.yml | 4 ++++ .../server/common/data/SystemParams.java | 1 + 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java index ee2e871d6e..9bcd756de0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java +++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java @@ -76,6 +76,9 @@ public class SystemInfoController extends BaseController { @Value("${debug.settings.default_duration:15}") private int defaultDebugDurationMinutes; + @Value("${sql.query.key-filters-or-conditions.enabled:true}") + private boolean keyFiltersOrConditionsEnabled; + @Autowired(required = false) private BuildProperties buildProperties; @@ -173,6 +176,7 @@ public class SystemInfoController extends BaseController { systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID)) .map(QrCodeSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage) .orElse(false)); + systemParams.setAllowKeyFiltersOrConditions(keyFiltersOrConditionsEnabled); return systemParams; } diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java index 92d76693f9..00426dae18 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java @@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.query.AvailableEntityKeysV2; import org.thingsboard.server.common.data.query.AvailableEntityKeysV2.KeyInfo; import org.thingsboard.server.common.data.query.AvailableEntityKeysV2.KeySample; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; +import org.thingsboard.server.common.data.query.ComplexOperation; import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; @@ -93,6 +94,9 @@ public class DefaultEntityQueryService implements EntityQueryService { @Value("${server.ws.max_entities_per_alarm_subscription:1000}") private int maxEntitiesPerAlarmSubscription; + @Value("${sql.query.key-filters-or-conditions.enabled:true}") + private boolean keyFiltersOrConditionsEnabled; + @Autowired private DbCallbackExecutorService dbCallbackExecutor; @@ -103,12 +107,14 @@ public class DefaultEntityQueryService implements EntityQueryService { private AttributesService attributesService; @Override - public long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) { + public long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) throws ThingsboardException { + validateKeyFiltersOperation(query); return entityService.countEntitiesByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); } @Override - public PageData findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) { + public PageData findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) throws ThingsboardException { + validateKeyFiltersOperation(query); if (query.getKeyFilters() != null) { resolveDynamicValuesInPredicates( query.getKeyFilters().stream() @@ -177,7 +183,8 @@ public class DefaultEntityQueryService implements EntityQueryService { } @Override - public PageData findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) { + public PageData findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) throws ThingsboardException { + validateKeyFiltersOperation(query); EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); PageData entities = entityService.findEntityDataByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), entityDataQuery); @@ -203,7 +210,8 @@ public class DefaultEntityQueryService implements EntityQueryService { } @Override - public long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) { + public long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) throws ThingsboardException { + validateKeyFiltersOperation(query); if (query.getEntityFilter() != null) { EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); PageData entities = entityService.findEntityDataByQuery(securityUser.getTenantId(), @@ -236,6 +244,12 @@ public class DefaultEntityQueryService implements EntityQueryService { return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters(), query.getKeyFiltersOperationOrDefault()); } + private void validateKeyFiltersOperation(EntityCountQuery query) throws ThingsboardException { + if (!keyFiltersOrConditionsEnabled && query.getKeyFiltersOperation() == ComplexOperation.OR) { + throw new ThingsboardException("OR conditions between key filters are disabled", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } + @Override public ListenableFuture getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query, boolean isTimeseries, boolean isAttributes, AttributeScope scope) { diff --git a/application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java index 354f8e9278..b3d2f208ca 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/EntityQueryService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.query; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.AttributeScope; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmCountQuery; @@ -33,13 +34,13 @@ import java.util.Set; public interface EntityQueryService { - long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query); + long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) throws ThingsboardException; - PageData findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query); + PageData findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) throws ThingsboardException; - PageData findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query); + PageData findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) throws ThingsboardException; - long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query); + long countAlarmsByQuery(SecurityUser securityUser, AlarmCountQuery query) throws ThingsboardException; ListenableFuture getKeysByQuery(SecurityUser securityUser, TenantId tenantId, EntityDataQuery query, boolean isTimeseries, boolean isAttributes, AttributeScope scope); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 15d6afb37d..f21cd5c7dd 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -469,6 +469,10 @@ sql: log_tenant_stats: "${SQL_LOG_TENANT_STATS:true}" # Interval in milliseconds for printing the latest statistic information about the tenant log_tenant_stats_interval_ms: "${SQL_LOG_TENANT_STATS_INTERVAL_MS:60000}" + query: + key-filters-or-conditions: + # Enable/disable OR conditions between key filters in entity data queries + enabled: "${SQL_QUERY_KEY_FILTERS_OR_CONDITIONS_ENABLED:true}" postgres: # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE. ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}" diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java index 3cd2ada38f..d3b2e8b73a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java @@ -45,4 +45,5 @@ public class SystemParams { long minAllowedAggregationIntervalInSecForCF; long intermediateAggregationIntervalInSecForCF; TrendzSettings trendzSettings; + boolean allowKeyFiltersOrConditions; }