Entity data query builder improvements + initial tests

This commit is contained in:
Igor Kulikov 2020-06-15 13:39:18 +03:00
parent 3acae2fbf2
commit 9460b09eef
10 changed files with 689 additions and 341 deletions

View File

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.query;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
@Data
@ -22,6 +23,12 @@ public class EntityDataPageLink {
private final int pageSize;
private final int page;
private final String textSearch;
private final EntityDataSortOrder sortOrder;
@JsonIgnore
public EntityDataPageLink nextPageLink() {
return new EntityDataPageLink(this.pageSize, this.page+1, this.textSearch, this.sortOrder);
}
}

View File

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.query;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import java.util.List;
@ -41,4 +42,9 @@ public class EntityDataQuery extends EntityCountQuery {
this.latestValues = latestValues;
this.keyFilters = keyFilters;
}
@JsonIgnore
public EntityDataQuery next() {
return new EntityDataQuery(getEntityFilter(), pageLink.nextPageLink(), entityFields, latestValues, keyFilters);
}
}

View File

@ -19,6 +19,6 @@ import lombok.Data;
@Data
public class EntityKey {
private EntityKeyType type;
private String key;
private final EntityKeyType type;
private final String key;
}

View File

@ -18,35 +18,24 @@ package org.thingsboard.server.dao.sql.query;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AssetTypeFilter;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityFilter;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.EntityNameFilter;
import org.thingsboard.server.common.data.query.EntityViewTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.util.SqlDao;
import javax.persistence.EntityManager;
@ -57,7 +46,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@ -66,25 +55,6 @@ import java.util.stream.Collectors;
@Slf4j
public class DefaultEntityQueryRepository implements EntityQueryRepository {
private static final Map<String, String> entityFieldColumnMap = new HashMap<>();
static {
entityFieldColumnMap.put("createdTime", "id");
entityFieldColumnMap.put("name", "name");
entityFieldColumnMap.put("type", "type");
entityFieldColumnMap.put("label", "label");
entityFieldColumnMap.put("firstName", "first_name");
entityFieldColumnMap.put("lastName", "last_name");
entityFieldColumnMap.put("email", "email");
entityFieldColumnMap.put("title", "title");
entityFieldColumnMap.put("country", "country");
entityFieldColumnMap.put("state", "state");
entityFieldColumnMap.put("city", "city");
entityFieldColumnMap.put("address", "address");
entityFieldColumnMap.put("address2", "address2");
entityFieldColumnMap.put("zip", "zip");
entityFieldColumnMap.put("phone", "phone");
}
private static final Map<EntityType, String> entityTableMap = new HashMap<>();
static {
entityTableMap.put(EntityType.ASSET, "asset");
@ -112,210 +82,77 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
@Override
public PageData<EntityData> findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query) {
EntityType entityType = resolveEntityType(query.getEntityFilter());
EntityDataPageLink pageLink = query.getPageLink();
List<EntityKeyMapping> mappings = prepareKeyMapping(query);
List<EntityKeyMapping> mappings = EntityKeyMapping.prepareKeyMapping(query);
List<EntityKeyMapping> selectionMapping = mappings.stream().filter(mapping -> mapping.isSelection())
List<EntityKeyMapping> selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection)
.collect(Collectors.toList());
List<EntityKeyMapping> entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest())
.collect(Collectors.toList());
List<EntityKeyMapping> latestSelectionMapping = selectionMapping.stream().filter(mapping -> mapping.isLatest())
List<EntityKeyMapping> latestSelectionMapping = selectionMapping.stream().filter(EntityKeyMapping::isLatest)
.collect(Collectors.toList());
List<EntityKeyMapping> filterMapping = mappings.stream().filter(mapping -> mapping.hasFilter())
List<EntityKeyMapping> filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter)
.collect(Collectors.toList());
List<EntityKeyMapping> entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest())
.collect(Collectors.toList());
List<EntityKeyMapping> latestFiltersMapping = filterMapping.stream().filter(mapping -> mapping.isLatest())
List<EntityKeyMapping> latestFiltersMapping = filterMapping.stream().filter(EntityKeyMapping::isLatest)
.collect(Collectors.toList());
List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(mapping -> mapping.isLatest())
List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(EntityKeyMapping::isLatest)
.collect(Collectors.toList());
String entityWhereClause = this.buildEntityWhere(tenantId, customerId, query.getEntityFilter(), entityFieldsFiltersMapping, entityType);
String latestJoins = this.buildLatestJoins(entityType, allLatestMappings);
String latestFilters = this.buildLatestQuery(latestFiltersMapping);
String countQuery = String.format("select count(*) from (select e.id from %s e where %s) entities %s",
entityTableMap.get(entityType), entityWhereClause, latestJoins);
if (!StringUtils.isEmpty(latestFilters)) {
countQuery = String.format("%s where %s", countQuery, latestFilters);
}
int totalElements = ((BigInteger)entityManager.createNativeQuery(countQuery)
.getSingleResult()).intValue();
String entityFieldsSelection = this.buildEntityFieldsSelection(entityFieldsSelectionMapping);
String latestJoins = EntityKeyMapping.buildLatestJoins(entityType, allLatestMappings);
String whereClause = this.buildWhere(selectionMapping, latestFiltersMapping, pageLink.getTextSearch());
String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping);
if (!StringUtils.isEmpty(entityFieldsSelection)) {
entityFieldsSelection = String.format("e.id, %s", entityFieldsSelection);
entityFieldsSelection = String.format("e.id, '%s', %s", entityType.name(), entityFieldsSelection);
} else {
entityFieldsSelection = "e.id";
entityFieldsSelection = String.format("e.id, '%s'", entityType.name());
}
String latestSelection = this.buildLatestSelections(latestSelectionMapping);
String topSelection = "entities.*";
String latestSelection = EntityKeyMapping.buildSelections(latestSelectionMapping);
String selection = entityFieldsSelection;
if (!StringUtils.isEmpty(latestSelection)) {
topSelection = topSelection + ", " + latestSelection;
selection = entityFieldsSelection + ", " + latestSelection;
}
String dataQuery = String.format("select %s from (select %s from %s e where %s) entities %s", topSelection,
entityFieldsSelection,
String fromClause = String.format("from (select %s from %s e where %s) entities %s",
selection,
entityTableMap.get(entityType),
entityWhereClause,
latestJoins);
if (!StringUtils.isEmpty(latestFilters)) {
dataQuery = String.format("%s where %s", dataQuery, latestFilters);
if (!StringUtils.isEmpty(whereClause)) {
fromClause = String.format("%s where %s", fromClause, whereClause);
}
EntityDataPageLink pageLink = query.getPageLink();
int totalElements = ((BigInteger)entityManager.createNativeQuery(String.format("select count(*) %s", fromClause))
.getSingleResult()).intValue();
// TODO: order by
String dataQuery = String.format("select entities.* %s", fromClause);
EntityDataSortOrder sortOrder = pageLink.getSortOrder();
if (sortOrder != null) {
Optional<EntityKeyMapping> sortOrderMappingOpt = mappings.stream().filter(EntityKeyMapping::isSortOrder).findFirst();
if (sortOrderMappingOpt.isPresent()) {
EntityKeyMapping sortOrderMapping = sortOrderMappingOpt.get();
dataQuery = String.format("%s order by %s", dataQuery, sortOrderMapping.getValueAlias());
if (sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC) {
dataQuery += " asc";
} else {
dataQuery += " desc";
}
}
}
int startIndex = pageLink.getPageSize() * pageLink.getPage();
if (pageLink.getPageSize() > 0) {
dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
}
List result = entityManager.createNativeQuery(dataQuery).getResultList();
int totalPages = pageLink.getPageSize() > 0 ? (int)Math.ceil((float)totalElements / pageLink.getPageSize()) : 1;
boolean hasNext = pageLink.getPageSize() > 0 && totalElements > startIndex + result.size();
List<EntityData> entitiesData = convertListToEntityData(result, entityType, selectionMapping);
return new PageData<>(entitiesData, totalPages, totalElements, hasNext);
}
private List<EntityData> convertListToEntityData(List<Object> result, EntityType entityType, List<EntityKeyMapping> selectionMapping) {
return result.stream().map(obj -> this.toEntityData(obj, entityType, selectionMapping)).collect(Collectors.toList());
}
private EntityData toEntityData(Object obj, EntityType entityType, List<EntityKeyMapping> selectionMapping) {
String id = obj instanceof String ? (String)obj : (String)((Object[]) obj)[0];
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, UUIDConverter.fromString(id));
Map<EntityKeyType, Map<String, TsValue>> latest = new HashMap<>();
Map<String, TsValue[]> timeseries = new HashMap<>();
EntityData entityData = new EntityData(entityId, latest, timeseries);
for (EntityKeyMapping mapping: selectionMapping) {
Object value = ((Object[]) obj)[mapping.getIndex()];
// TODO:
}
return entityData;
}
private List<EntityKeyMapping> prepareKeyMapping(EntityDataQuery query) {
List<EntityKey> entityFields = query.getEntityFields() != null ? query.getEntityFields() : Collections.emptyList();
List<EntityKey> latestValues = query.getLatestValues() != null ? query.getLatestValues() : Collections.emptyList();
Map<EntityKey, List<KeyFilter>> filters =
query.getKeyFilters() != null ?
query.getKeyFilters().stream().collect(Collectors.groupingBy(KeyFilter::getKey)) : Collections.emptyMap();
int index = 1;
List<EntityKeyMapping> mappings = new ArrayList<>();
for (EntityKey entityField : entityFields) {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setIndex(index);
mapping.setAlias(String.format("alias%s", index));
mapping.setKeyFilters(filters.remove(entityField));
mapping.setLatest(false);
mapping.setSelection(true);
mapping.setEntityKey(entityField);
mappings.add(mapping);
index++;
}
for (EntityKey latestField : latestValues) {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setIndex(index);
mapping.setAlias(String.format("alias%s", index));
mapping.setKeyFilters(filters.remove(latestField));
mapping.setLatest(true);
mapping.setSelection(true);
mapping.setEntityKey(latestField);
mappings.add(mapping);
index +=2;
}
if (!filters.isEmpty()) {
for (EntityKey filterField : filters.keySet()) {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setIndex(index);
mapping.setAlias(String.format("alias%s", index));
mapping.setKeyFilters(filters.get(filterField));
mapping.setLatest(!filterField.getType().equals(EntityKeyType.ENTITY_FIELD));
mapping.setSelection(false);
mapping.setEntityKey(filterField);
mappings.add(mapping);
index +=1;
}
}
return mappings;
}
private String buildEntityFieldsSelection(List<EntityKeyMapping> entityFieldsSelectionMapping) {
return entityFieldsSelectionMapping.stream().map(mapping -> {
String column = entityFieldColumnMap.get(mapping.getEntityKey().getKey());
return String.format("e.%s as %s", column, mapping.getAlias());
}).collect(
Collectors.joining(", "));
}
private String buildLatestSelections(List<EntityKeyMapping> latestSelectionMapping) {
return latestSelectionMapping.stream().map(mapping -> this.buildLatestSelection(mapping))
.collect(
Collectors.joining(", "));
}
private String buildLatestSelection(EntityKeyMapping mapping) {
if (mapping.getEntityKey().getType().equals(EntityKeyType.TIME_SERIES)) {
return buildTimeseriesSelection(mapping);
} else {
return buildAttributeSelection(mapping);
}
}
private String buildAttributeSelection(EntityKeyMapping mapping) {
String alias = mapping.getAlias();
String attrValAlias = alias + "_value";
String attrTsAlias = alias + "_ts";
String attrTsSelection = String.format("%s.last_update_ts as %s", alias, attrTsAlias);
String attrValSelection =
String.format("coalesce(cast(%s.bool_v as varchar), '') || " +
"coalesce(%s.str_v, '') || " +
"coalesce(cast(%s.long_v as varchar), '') || " +
"coalesce(cast(%s.dbl_v as varchar), '') || " +
"coalesce(cast(%s.json_v as varchar), '')) as %s", alias, alias, alias, alias, alias, attrValAlias);
return String.join(", ", attrTsSelection, attrValSelection);
}
private String buildTimeseriesSelection(EntityKeyMapping mapping) {
// TODO:
String alias = mapping.getAlias();
String attrValAlias = alias + "_value";
String attrTsAlias = alias + "_ts";
return String.format("(select 1) as %s, (select '') as %s", attrTsAlias, attrValAlias);
}
private String buildLatestJoins(EntityType entityType, List<EntityKeyMapping> latestMappings) {
return latestMappings.stream().map(mapping -> this.buildLatestJoin(entityType, mapping)).collect(
Collectors.joining(" "));
}
private String buildLatestJoin(EntityType entityType, EntityKeyMapping mapping) {
String join = mapping.hasFilter() ? "left join" : "left outer join";
if (mapping.getEntityKey().getType().equals(EntityKeyType.TIME_SERIES)) {
// TODO:
throw new RuntimeException("Not implemented!");
} else {
String alias = mapping.getAlias();
String query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.entity_type='%s' AND %s.attribute_key='%s'",
join, alias, alias, alias, entityType.name(), alias, mapping.getEntityKey().getKey());
if (!mapping.getEntityKey().getType().equals(EntityKeyType.ATTRIBUTE)) {
String scope;
if (mapping.getEntityKey().getType().equals(EntityKeyType.CLIENT_ATTRIBUTE)) {
scope = DataConstants.CLIENT_SCOPE;
} else if (mapping.getEntityKey().getType().equals(EntityKeyType.SHARED_ATTRIBUTE)) {
scope = DataConstants.SHARED_SCOPE;
} else {
scope = DataConstants.SERVER_SCOPE;
}
query = String.format("%s AND %s.attribute_type=%s", query, alias, scope);
}
return query;
}
List rows = entityManager.createNativeQuery(dataQuery).getResultList();
return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements);
}
private String buildEntityWhere(TenantId tenantId,
@ -326,7 +163,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
String permissionQuery = this.buildPermissionQuery(tenantId, customerId, entityType);
String entityFilterQuery = this.buildEntityFilterQuery(entityFilter);
if (!entityFieldsFilters.isEmpty()) {
String entityFieldsQuery = this.buildEntityFieldsQuery(entityFieldsFilters);
String entityFieldsQuery = EntityKeyMapping.buildQuery(entityFieldsFilters);
return String.join(" and ", permissionQuery, entityFilterQuery, entityFieldsQuery);
} else {
return String.join(" and ", permissionQuery, entityFilterQuery);
@ -334,9 +171,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
}
private String buildPermissionQuery(TenantId tenantId, CustomerId customerId, EntityType entityType) {
String permissionQuery = String.format("e.tenant_id=%s", UUIDConverter.fromTimeUUID(tenantId.getId()));
String permissionQuery = String.format("e.tenant_id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()));
if (entityType != EntityType.TENANT && entityType != EntityType.CUSTOMER) {
permissionQuery = String.format("%s and e.customerId=%s", permissionQuery, UUIDConverter.fromTimeUUID(customerId.getId()));
permissionQuery = String.format("%s and e.customer_id='%s'", permissionQuery, UUIDConverter.fromTimeUUID(customerId.getId()));
}
return permissionQuery;
}
@ -358,151 +195,41 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
}
}
private String buildLatestQuery(List<EntityKeyMapping> latestFilters) {
List<String> latestQueries = new ArrayList<>();
for (EntityKeyMapping mapping: latestFilters) {
latestQueries.addAll(mapping.getKeyFilters().stream().map(keyFilter ->
this.buildKeyQuery(mapping.getAlias(), keyFilter))
.collect(Collectors.toList()));
}
return latestQueries.stream().collect(Collectors.joining(" AND "));
}
private String buildEntityFieldsQuery(List<EntityKeyMapping> entityFieldsFilters) {
return entityFieldsFilters.stream().flatMap(mapping -> mapping.getKeyFilters().stream())
.map(keyFilter -> this.buildKeyQuery("e", keyFilter)).collect(
Collectors.joining(" AND ")
);
}
private String buildKeyQuery(String alias, KeyFilter keyFilter) {
return this.buildPredicateQuery(alias, keyFilter.getKey(), keyFilter.getPredicate());
}
private String buildPredicateQuery(String alias, EntityKey key, KeyFilterPredicate predicate) {
if (predicate.getType().equals(FilterPredicateType.COMPLEX)) {
return this.buildComplexPredicateQuery(alias, key, (ComplexFilterPredicate)predicate);
private String buildWhere(List<EntityKeyMapping> selectionMapping, List<EntityKeyMapping> latestFiltersMapping, String searchText) {
String latestFilters = EntityKeyMapping.buildQuery(latestFiltersMapping);
String textSearchQuery = this.buildTextSearchQuery(selectionMapping, searchText);
if (!StringUtils.isEmpty(latestFilters) && !StringUtils.isEmpty(textSearchQuery)) {
return String.join(" AND ", latestFilters, textSearchQuery);
} else if (!StringUtils.isEmpty(latestFilters)) {
return latestFilters;
} else {
return this.buildSimplePredicateQuery(alias, key, predicate);
return textSearchQuery;
}
}
private String buildComplexPredicateQuery(String alias, EntityKey key, ComplexFilterPredicate predicate) {
return predicate.getPredicates().stream()
.map(keyFilterPredicate -> this.buildPredicateQuery(alias, key, keyFilterPredicate)).collect(Collectors.joining(
" " + predicate.getOperation().name() + " "
));
}
private String buildSimplePredicateQuery(String alias, EntityKey key, KeyFilterPredicate predicate) {
if (predicate.getType().equals(FilterPredicateType.NUMERIC)) {
if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
String column = entityFieldColumnMap.get(key.getKey());
return this.buildNumericPredicateQuery(alias + "." + column, (NumericFilterPredicate)predicate);
} else {
String longQuery = this.buildNumericPredicateQuery(alias + ".long_v", (NumericFilterPredicate)predicate);
String doubleQuery = this.buildNumericPredicateQuery(alias + ".dbl_v", (NumericFilterPredicate)predicate);
return String.format("(%s or %s)", longQuery, doubleQuery);
}
private String buildTextSearchQuery(List<EntityKeyMapping> selectionMapping, String searchText) {
if (!StringUtils.isEmpty(searchText) && !selectionMapping.isEmpty()) {
String lowerSearchText = searchText.toLowerCase() + "%";
List<String> searchPredicates = selectionMapping.stream().map(mapping -> String.format("LOWER(%s) LIKE '%s'",
mapping.getValueAlias(), lowerSearchText)).collect(Collectors.toList());
return String.format("(%s)", String.join(" or ", searchPredicates));
} else {
String column;
if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
column = entityFieldColumnMap.get(key.getKey());
} else {
column = predicate.getType().equals(FilterPredicateType.STRING) ? "str_v" : "bool_v";
}
String field = alias + "." + column;
if (predicate.getType().equals(FilterPredicateType.STRING)) {
return this.buildStringPredicateQuery(field, (StringFilterPredicate)predicate);
} else {
return this.buildBooleanPredicateQuery(field, (BooleanFilterPredicate)predicate);
}
return null;
}
}
private String buildStringPredicateQuery(String field, StringFilterPredicate stringFilterPredicate) {
String operationField = field;
String value = stringFilterPredicate.getValue();
String stringOperationQuery = "";
if (stringFilterPredicate.isIgnoreCase()) {
value.toLowerCase();
operationField = String.format("lower(%s)", operationField);
}
switch (stringFilterPredicate.getOperation()) {
case EQUAL:
stringOperationQuery = String.format("%s = '%s'", operationField, value);
break;
case NOT_EQUAL:
stringOperationQuery = String.format("%s != '%s'", operationField, value);
break;
case STARTS_WITH:
stringOperationQuery = String.format("%s like '%s%'", operationField, value);
break;
case ENDS_WITH:
stringOperationQuery = String.format("%s like '%%s'", operationField, value);
break;
case CONTAINS:
stringOperationQuery = String.format("%s like '%%s%'", operationField, value);
break;
case NOT_CONTAINS:
stringOperationQuery = String.format("%s not like '%%s%'", operationField, value);
break;
}
return String.format("(%s is not null and %s)", field, stringOperationQuery);
}
private String buildNumericPredicateQuery(String field, NumericFilterPredicate numericFilterPredicate) {
double value = numericFilterPredicate.getValue();
String numericOperationQuery = "";
switch (numericFilterPredicate.getOperation()) {
case EQUAL:
numericOperationQuery = String.format("%s = %s", field, value);
break;
case NOT_EQUAL:
numericOperationQuery = String.format("%s != '%s'", field, value);
break;
case GREATER:
numericOperationQuery = String.format("%s > %s", field, value);
break;
case GREATER_OR_EQUAL:
numericOperationQuery = String.format("%s >= %s", field, value);
break;
case LESS:
numericOperationQuery = String.format("%s < %s", field, value);
break;
case LESS_OR_EQUAL:
numericOperationQuery = String.format("%s <= %s", field, value);
break;
}
return String.format("(%s is not null and %s)", field, numericOperationQuery);
}
private String buildBooleanPredicateQuery(String field,
BooleanFilterPredicate booleanFilterPredicate) {
boolean value = booleanFilterPredicate.isValue();
String booleanOperationQuery = "";
switch (booleanFilterPredicate.getOperation()) {
case EQUAL:
booleanOperationQuery = String.format("%s = %s", field, value);
break;
case NOT_EQUAL:
booleanOperationQuery = String.format("%s != %s", field, value);
break;
}
return String.format("(%s is not null and %s)", field, booleanOperationQuery);
}
private String singleEntityQuery(SingleEntityFilter filter) {
return String.format("e.id=%s", UUIDConverter.fromTimeUUID(filter.getSingleEntity().getId()));
return String.format("e.id='%s'", UUIDConverter.fromTimeUUID(filter.getSingleEntity().getId()));
}
private String entityListQuery(EntityListFilter filter) {
return String.format("e.id in (%s)",
filter.getEntityList().stream().map(UUID::fromString).map(UUIDConverter::fromTimeUUID).collect(Collectors.joining(",")));
filter.getEntityList().stream().map(UUID::fromString).map(UUIDConverter::fromTimeUUID)
.map(s -> String.format("'%s'", s)).collect(Collectors.joining(",")));
}
private String entityNameQuery(EntityNameFilter filter) {
return String.format("lower(e.searchText) like lower(concat(%s, '%'))", filter.getEntityNameFilter());
return String.format("lower(e.search_text) like lower(concat(%s, '%%'))", filter.getEntityNameFilter());
}
private String typeQuery(EntityFilter filter) {
@ -524,7 +251,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
default:
throw new RuntimeException("Not supported!");
}
return String.format("e.type = %s and lower(e.searchText) like lower(concat(%s, '%'))", type, name);
return String.format("e.type = '%s' and lower(e.search_text) like lower(concat('%s', '%%'))", type, name);
}
private EntityType resolveEntityType(EntityFilter entityFilter) {

View File

@ -0,0 +1,101 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.query;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class EntityDataAdapter {
public static PageData<EntityData> createEntityData(EntityDataPageLink pageLink,
List<EntityKeyMapping> selectionMapping,
List<Object[]> rows,
int totalElements) {
int totalPages = pageLink.getPageSize() > 0 ? (int)Math.ceil((float)totalElements / pageLink.getPageSize()) : 1;
int startIndex = pageLink.getPageSize() * pageLink.getPage();
boolean hasNext = pageLink.getPageSize() > 0 && totalElements > startIndex + rows.size();
List<EntityData> entitiesData = convertListToEntityData(rows, selectionMapping);
return new PageData<>(entitiesData, totalPages, totalElements, hasNext);
}
private static List<EntityData> convertListToEntityData(List<Object[]> result, List<EntityKeyMapping> selectionMapping) {
return result.stream().map(row -> toEntityData(row, selectionMapping)).collect(Collectors.toList());
}
private static EntityData toEntityData(Object[] row, List<EntityKeyMapping> selectionMapping) {
String id = (String)row[0];
EntityType entityType = EntityType.valueOf((String)row[1]);
EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, UUIDConverter.fromString(id));
Map<EntityKeyType, Map<String, TsValue>> latest = new HashMap<>();
Map<String, TsValue[]> timeseries = new HashMap<>();
EntityData entityData = new EntityData(entityId, latest, timeseries);
for (EntityKeyMapping mapping: selectionMapping) {
EntityKey entityKey = mapping.getEntityKey();
Object value = row[mapping.getIndex()];
String strValue;
long ts;
if (entityKey.getType().equals(EntityKeyType.ENTITY_FIELD)) {
strValue = value != null ? value.toString() : null;
ts = System.currentTimeMillis();
} else {
strValue = convertValue(value);
Object tsObject = row[mapping.getIndex()+1];
ts = Long.parseLong(tsObject.toString());
}
TsValue tsValue = new TsValue(ts, strValue);
latest.computeIfAbsent(entityKey.getType(), entityKeyType -> new HashMap<>()).put(entityKey.getKey(), tsValue);
}
return entityData;
}
private static String convertValue(Object value) {
if (value != null) {
String strVal = value.toString();
// check number
if (strVal.length() > 0) {
try {
int intVal = Integer.parseInt(strVal);
return Integer.toString(intVal);
} catch (NumberFormatException ignored) {
}
try {
double dblVal = Double.parseDouble(strVal);
if (!Double.isInfinite(dblVal)) {
return Double.toString(dblVal);
}
} catch (NumberFormatException ignored) {
}
}
return strVal;
} else {
return null;
}
}
}

View File

@ -16,21 +16,338 @@
package org.thingsboard.server.dao.sql.query;
import lombok.Data;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Data
public class EntityKeyMapping {
public static final Map<String, String> entityFieldColumnMap = new HashMap<>();
static {
entityFieldColumnMap.put("createdTime", "id");
entityFieldColumnMap.put("name", "name");
entityFieldColumnMap.put("type", "type");
entityFieldColumnMap.put("label", "label");
entityFieldColumnMap.put("firstName", "first_name");
entityFieldColumnMap.put("lastName", "last_name");
entityFieldColumnMap.put("email", "email");
entityFieldColumnMap.put("title", "title");
entityFieldColumnMap.put("country", "country");
entityFieldColumnMap.put("state", "state");
entityFieldColumnMap.put("city", "city");
entityFieldColumnMap.put("address", "address");
entityFieldColumnMap.put("address2", "address2");
entityFieldColumnMap.put("zip", "zip");
entityFieldColumnMap.put("phone", "phone");
}
private int index;
private String alias;
private boolean isLatest;
private boolean isSelection;
private boolean isSortOrder;
private List<KeyFilter> keyFilters;
private EntityKey entityKey;
public boolean hasFilter() {
return keyFilters != null && !keyFilters.isEmpty();
}
public String getValueAlias() {
if (entityKey.getType().equals(EntityKeyType.ENTITY_FIELD)) {
return alias;
} else {
return alias + "_value";
}
}
public String getTsAlias() {
return alias + "_ts";
}
public String toSelection() {
if (entityKey.getType().equals(EntityKeyType.ENTITY_FIELD)) {
String column = entityFieldColumnMap.get(entityKey.getKey());
return String.format("e.%s as %s", column, getValueAlias());
} else if (entityKey.getType().equals(EntityKeyType.TIME_SERIES)) {
return buildTimeseriesSelection();
} else {
return buildAttributeSelection();
}
}
public Stream<String> toQueries() {
if (hasFilter()) {
String keyAlias = entityKey.getType().equals(EntityKeyType.ENTITY_FIELD) ? "e" : alias;
return keyFilters.stream().map(keyFilter ->
this.buildKeyQuery(keyAlias, keyFilter));
} else {
return null;
}
}
public String toLatestJoin(EntityType entityType) {
String join = hasFilter() ? "left join" : "left outer join";
if (entityKey.getType().equals(EntityKeyType.TIME_SERIES)) {
// TODO:
throw new RuntimeException("Not implemented!");
} else {
String query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.entity_type='%s' AND %s.attribute_key='%s'",
join, alias, alias, alias, entityType.name(), alias, entityKey.getKey());
if (!entityKey.getType().equals(EntityKeyType.ATTRIBUTE)) {
String scope;
if (entityKey.getType().equals(EntityKeyType.CLIENT_ATTRIBUTE)) {
scope = DataConstants.CLIENT_SCOPE;
} else if (entityKey.getType().equals(EntityKeyType.SHARED_ATTRIBUTE)) {
scope = DataConstants.SHARED_SCOPE;
} else {
scope = DataConstants.SERVER_SCOPE;
}
query = String.format("%s AND %s.attribute_type=%s", query, alias, scope);
}
return query;
}
}
public static String buildSelections(List<EntityKeyMapping> mappings) {
return mappings.stream().map(EntityKeyMapping::toSelection).collect(
Collectors.joining(", "));
}
public static String buildLatestJoins(EntityType entityType, List<EntityKeyMapping> latestMappings) {
return latestMappings.stream().map(mapping -> mapping.toLatestJoin(entityType)).collect(
Collectors.joining(" "));
}
public static String buildQuery(List<EntityKeyMapping> mappings) {
return mappings.stream().flatMap(EntityKeyMapping::toQueries).collect(
Collectors.joining(" AND "));
}
public static List<EntityKeyMapping> prepareKeyMapping(EntityDataQuery query) {
List<EntityKey> entityFields = query.getEntityFields() != null ? query.getEntityFields() : Collections.emptyList();
List<EntityKey> latestValues = query.getLatestValues() != null ? query.getLatestValues() : Collections.emptyList();
Map<EntityKey, List<KeyFilter>> filters =
query.getKeyFilters() != null ?
query.getKeyFilters().stream().collect(Collectors.groupingBy(KeyFilter::getKey)) : Collections.emptyMap();
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder();
EntityKey sortOrderKey = sortOrder != null ? sortOrder.getKey() : null;
EntityKeyMapping sortOrderMapping = null;
int index = 2;
List<EntityKeyMapping> mappings = new ArrayList<>();
for (EntityKey entityField : entityFields) {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setIndex(index);
mapping.setAlias(String.format("alias%s", index));
mapping.setKeyFilters(filters.remove(entityField));
mapping.setLatest(false);
mapping.setSelection(true);
mapping.setEntityKey(entityField);
if (entityField.equals(sortOrderKey)) {
mapping.setSortOrder(true);
sortOrderMapping = mapping;
} else {
mapping.setSortOrder(false);
}
mappings.add(mapping);
index++;
}
for (EntityKey latestField : latestValues) {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setIndex(index);
mapping.setAlias(String.format("alias%s", index));
mapping.setKeyFilters(filters.remove(latestField));
mapping.setLatest(true);
mapping.setSelection(true);
mapping.setEntityKey(latestField);
if (latestField.equals(sortOrderKey)) {
mapping.setSortOrder(true);
sortOrderMapping = mapping;
} else {
mapping.setSortOrder(false);
}
mappings.add(mapping);
index +=2;
}
if (!filters.isEmpty()) {
for (EntityKey filterField : filters.keySet()) {
EntityKeyMapping mapping = new EntityKeyMapping();
mapping.setIndex(index);
mapping.setAlias(String.format("alias%s", index));
mapping.setKeyFilters(filters.get(filterField));
mapping.setLatest(!filterField.getType().equals(EntityKeyType.ENTITY_FIELD));
mapping.setSelection(false);
mapping.setEntityKey(filterField);
mappings.add(mapping);
index +=1;
}
}
if (sortOrderKey != null && sortOrderMapping == null) {
sortOrderMapping = new EntityKeyMapping();
sortOrderMapping.setIndex(index);
sortOrderMapping.setAlias(String.format("alias%s", index));
sortOrderMapping.setLatest(!sortOrderKey.getType().equals(EntityKeyType.ENTITY_FIELD));
sortOrderMapping.setSelection(true);
sortOrderMapping.setEntityKey(sortOrderKey);
sortOrderMapping.setSortOrder(true);
mappings.add(sortOrderMapping);
}
return mappings;
}
private String buildAttributeSelection() {
String attrValAlias = getValueAlias();
String attrTsAlias = getTsAlias();
String attrTsSelection = String.format("%s.last_update_ts as %s", alias, attrTsAlias);
String attrValSelection =
String.format("coalesce(cast(%s.bool_v as varchar), '') || " +
"coalesce(%s.str_v, '') || " +
"coalesce(cast(%s.long_v as varchar), '') || " +
"coalesce(cast(%s.dbl_v as varchar), '') || " +
"coalesce(cast(%s.json_v as varchar), '')) as %s", alias, alias, alias, alias, alias, attrValAlias);
return String.join(", ", attrTsSelection, attrValSelection);
}
private String buildTimeseriesSelection() {
// TODO:
String attrValAlias = getValueAlias();
String attrTsAlias = getTsAlias();
return String.format("(select 1) as %s, (select '') as %s", attrTsAlias, attrValAlias);
}
private String buildKeyQuery(String alias, KeyFilter keyFilter) {
return this.buildPredicateQuery(alias, keyFilter.getKey(), keyFilter.getPredicate());
}
private String buildPredicateQuery(String alias, EntityKey key, KeyFilterPredicate predicate) {
if (predicate.getType().equals(FilterPredicateType.COMPLEX)) {
return this.buildComplexPredicateQuery(alias, key, (ComplexFilterPredicate)predicate);
} else {
return this.buildSimplePredicateQuery(alias, key, predicate);
}
}
private String buildComplexPredicateQuery(String alias, EntityKey key, ComplexFilterPredicate predicate) {
return predicate.getPredicates().stream()
.map(keyFilterPredicate -> this.buildPredicateQuery(alias, key, keyFilterPredicate)).collect(Collectors.joining(
" " + predicate.getOperation().name() + " "
));
}
private String buildSimplePredicateQuery(String alias, EntityKey key, KeyFilterPredicate predicate) {
if (predicate.getType().equals(FilterPredicateType.NUMERIC)) {
if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
String column = entityFieldColumnMap.get(key.getKey());
return this.buildNumericPredicateQuery(alias + "." + column, (NumericFilterPredicate)predicate);
} else {
String longQuery = this.buildNumericPredicateQuery(alias + ".long_v", (NumericFilterPredicate)predicate);
String doubleQuery = this.buildNumericPredicateQuery(alias + ".dbl_v", (NumericFilterPredicate)predicate);
return String.format("(%s or %s)", longQuery, doubleQuery);
}
} else {
String column;
if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
column = entityFieldColumnMap.get(key.getKey());
} else {
column = predicate.getType().equals(FilterPredicateType.STRING) ? "str_v" : "bool_v";
}
String field = alias + "." + column;
if (predicate.getType().equals(FilterPredicateType.STRING)) {
return this.buildStringPredicateQuery(field, (StringFilterPredicate)predicate);
} else {
return this.buildBooleanPredicateQuery(field, (BooleanFilterPredicate)predicate);
}
}
}
private String buildStringPredicateQuery(String field, StringFilterPredicate stringFilterPredicate) {
String operationField = field;
String value = stringFilterPredicate.getValue();
String stringOperationQuery = "";
if (stringFilterPredicate.isIgnoreCase()) {
value = value.toLowerCase();
operationField = String.format("lower(%s)", operationField);
}
switch (stringFilterPredicate.getOperation()) {
case EQUAL:
stringOperationQuery = String.format("%s = '%s'", operationField, value);
break;
case NOT_EQUAL:
stringOperationQuery = String.format("%s != '%s'", operationField, value);
break;
case STARTS_WITH:
stringOperationQuery = String.format("%s like '%s%%'", operationField, value);
break;
case ENDS_WITH:
stringOperationQuery = String.format("%s like '%%%s'", operationField, value);
break;
case CONTAINS:
stringOperationQuery = String.format("%s like '%%%s%%'", operationField, value);
break;
case NOT_CONTAINS:
stringOperationQuery = String.format("%s not like '%%%s%%'", operationField, value);
break;
}
return String.format("(%s is not null and %s)", field, stringOperationQuery);
}
private String buildNumericPredicateQuery(String field, NumericFilterPredicate numericFilterPredicate) {
double value = numericFilterPredicate.getValue();
String numericOperationQuery = "";
switch (numericFilterPredicate.getOperation()) {
case EQUAL:
numericOperationQuery = String.format("%s = %s", field, value);
break;
case NOT_EQUAL:
numericOperationQuery = String.format("%s != '%s'", field, value);
break;
case GREATER:
numericOperationQuery = String.format("%s > %s", field, value);
break;
case GREATER_OR_EQUAL:
numericOperationQuery = String.format("%s >= %s", field, value);
break;
case LESS:
numericOperationQuery = String.format("%s < %s", field, value);
break;
case LESS_OR_EQUAL:
numericOperationQuery = String.format("%s <= %s", field, value);
break;
}
return String.format("(%s is not null and %s)", field, numericOperationQuery);
}
private String buildBooleanPredicateQuery(String field,
BooleanFilterPredicate booleanFilterPredicate) {
boolean value = booleanFilterPredicate.isValue();
String booleanOperationQuery = "";
switch (booleanFilterPredicate.getOperation()) {
case EQUAL:
booleanOperationQuery = String.format("%s = %s", field, value);
break;
case NOT_EQUAL:
booleanOperationQuery = String.format("%s != %s", field, value);
break;
}
return String.format("(%s is not null and %s)", field, booleanOperationQuery);
}
}

View File

@ -24,7 +24,7 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClassnameFilters({
"org.thingsboard.server.dao.service.*ServiceSqlTest"
"org.thingsboard.server.dao.service.*EntityServiceSqlTest"
})
public class SqlDaoServiceTestSuite {

View File

@ -33,9 +33,6 @@ import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentScope;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.audit.AuditLogLevelFilter;
@ -45,6 +42,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.relation.RelationService;
@ -94,6 +92,9 @@ public abstract class AbstractServiceTest {
@Autowired
protected EntityViewService entityViewService;
@Autowired
protected EntityService entityService;
@Autowired
protected DeviceCredentialsService deviceCredentialsService;

View File

@ -0,0 +1,166 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityFilter;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public abstract class BaseEntityServiceTest extends AbstractServiceTest {
private TenantId tenantId;
@Before
public void before() {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
}
@After
public void after() {
tenantService.deleteTenant(tenantId);
}
@Test
public void testCountEntitiesByQuery() {
List<Device> devices = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Device" + i);
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
devices.add(deviceService.saveDevice(device));
}
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceType("default");
filter.setDeviceNameFilter("");
EntityCountQuery countQuery = new EntityCountQuery(filter);
long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(97, count);
filter.setDeviceType("unknown");
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(0, count);
filter.setDeviceType("default");
filter.setDeviceNameFilter("Device1");
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(11, count);
EntityListFilter entityListFilter = new EntityListFilter();
entityListFilter.setEntityType(EntityType.DEVICE);
entityListFilter.setEntityList(devices.stream().map(Device::getId).map(DeviceId::toString).collect(Collectors.toList()));
countQuery = new EntityCountQuery(entityListFilter);
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(97, count);
deviceService.deleteDevicesByTenantId(tenantId);
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(0, count);
}
@Test
public void testFindEntityDataByQuery() {
List<Device> devices = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Device" + i);
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
devices.add(deviceService.saveDevice(device));
}
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceType("default");
filter.setDeviceNameFilter("");
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
PageData<EntityData> data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
Assert.assertEquals(97, data.getTotalElements());
Assert.assertEquals(10, data.getTotalPages());
Assert.assertTrue(data.hasNext());
Assert.assertEquals(10, data.getData().size());
List<EntityData> loadedEntities = new ArrayList<>(data.getData());
while(data.hasNext()) {
query = query.next();
data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
loadedEntities.addAll(data.getData());
}
Assert.assertEquals(97, loadedEntities.size());
List<EntityId> loadedIds = loadedEntities.stream().map(EntityData::getEntityId).collect(Collectors.toList());
List<EntityId> deviceIds = devices.stream().map(Device::getId).collect(Collectors.toList());
Assert.assertEquals(deviceIds, loadedIds);
List<String> loadedNames = loadedEntities.stream().map(entityData ->
entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).collect(Collectors.toList());
List<String> deviceNames = devices.stream().map(Device::getName).collect(Collectors.toList());
Assert.assertEquals(deviceNames, loadedNames);
sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), EntityDataSortOrder.Direction.DESC
);
pageLink = new EntityDataPageLink(10, 0, "device1", sortOrder);
query = new EntityDataQuery(filter, pageLink, entityFields, null, null);
data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
Assert.assertEquals(11, data.getTotalElements());
Assert.assertEquals("Device19", data.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue());
}
}

View File

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service.sql;
import org.thingsboard.server.dao.service.BaseEntityServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class EntityServiceSqlTest extends BaseEntityServiceTest {
}