diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataPageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataPageLink.java index ebf40d6042..e1f4d48e32 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataPageLink.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataPageLink.java @@ -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); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataQuery.java index 03a2ca7977..8b01d89a75 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataQuery.java @@ -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); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java index e1a6f9fb3b..233dcb4f5b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityKey.java @@ -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; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index daf89b8306..aee8cc41c0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -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 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 entityTableMap = new HashMap<>(); static { entityTableMap.put(EntityType.ASSET, "asset"); @@ -112,210 +82,77 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @Override public PageData findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query) { EntityType entityType = resolveEntityType(query.getEntityFilter()); + EntityDataPageLink pageLink = query.getPageLink(); - List mappings = prepareKeyMapping(query); + List mappings = EntityKeyMapping.prepareKeyMapping(query); - List selectionMapping = mappings.stream().filter(mapping -> mapping.isSelection()) + List selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection) .collect(Collectors.toList()); List entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest()) .collect(Collectors.toList()); - List latestSelectionMapping = selectionMapping.stream().filter(mapping -> mapping.isLatest()) + List latestSelectionMapping = selectionMapping.stream().filter(EntityKeyMapping::isLatest) .collect(Collectors.toList()); - List filterMapping = mappings.stream().filter(mapping -> mapping.hasFilter()) + List filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter) .collect(Collectors.toList()); List entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest()) .collect(Collectors.toList()); - List latestFiltersMapping = filterMapping.stream().filter(mapping -> mapping.isLatest()) + List latestFiltersMapping = filterMapping.stream().filter(EntityKeyMapping::isLatest) .collect(Collectors.toList()); - List allLatestMappings = mappings.stream().filter(mapping -> mapping.isLatest()) + List 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 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 entitiesData = convertListToEntityData(result, entityType, selectionMapping); - return new PageData<>(entitiesData, totalPages, totalElements, hasNext); - } - - private List convertListToEntityData(List result, EntityType entityType, List selectionMapping) { - return result.stream().map(obj -> this.toEntityData(obj, entityType, selectionMapping)).collect(Collectors.toList()); - } - - private EntityData toEntityData(Object obj, EntityType entityType, List selectionMapping) { - String id = obj instanceof String ? (String)obj : (String)((Object[]) obj)[0]; - EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, UUIDConverter.fromString(id)); - Map> latest = new HashMap<>(); - Map 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 prepareKeyMapping(EntityDataQuery query) { - List entityFields = query.getEntityFields() != null ? query.getEntityFields() : Collections.emptyList(); - List latestValues = query.getLatestValues() != null ? query.getLatestValues() : Collections.emptyList(); - Map> filters = - query.getKeyFilters() != null ? - query.getKeyFilters().stream().collect(Collectors.groupingBy(KeyFilter::getKey)) : Collections.emptyMap(); - int index = 1; - List 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 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 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 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 latestFilters) { - List 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 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 selectionMapping, List 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 selectionMapping, String searchText) { + if (!StringUtils.isEmpty(searchText) && !selectionMapping.isEmpty()) { + String lowerSearchText = searchText.toLowerCase() + "%"; + List 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) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java new file mode 100644 index 0000000000..914f6d4ec5 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java @@ -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 createEntityData(EntityDataPageLink pageLink, + List selectionMapping, + List 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 entitiesData = convertListToEntityData(rows, selectionMapping); + return new PageData<>(entitiesData, totalPages, totalElements, hasNext); + } + + private static List convertListToEntityData(List result, List selectionMapping) { + return result.stream().map(row -> toEntityData(row, selectionMapping)).collect(Collectors.toList()); + } + + private static EntityData toEntityData(Object[] row, List selectionMapping) { + String id = (String)row[0]; + EntityType entityType = EntityType.valueOf((String)row[1]); + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, UUIDConverter.fromString(id)); + Map> latest = new HashMap<>(); + Map 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; + } + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 133665af99..aa734f1166 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -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 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 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 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 mappings) { + return mappings.stream().map(EntityKeyMapping::toSelection).collect( + Collectors.joining(", ")); + } + + public static String buildLatestJoins(EntityType entityType, List latestMappings) { + return latestMappings.stream().map(mapping -> mapping.toLatestJoin(entityType)).collect( + Collectors.joining(" ")); + } + + public static String buildQuery(List mappings) { + return mappings.stream().flatMap(EntityKeyMapping::toQueries).collect( + Collectors.joining(" AND ")); + } + + public static List prepareKeyMapping(EntityDataQuery query) { + List entityFields = query.getEntityFields() != null ? query.getEntityFields() : Collections.emptyList(); + List latestValues = query.getLatestValues() != null ? query.getLatestValues() : Collections.emptyList(); + Map> 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 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); + } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index 32fd45c188..9c095b4664 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -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 { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index 8094e144ec..f0c6a3bed5 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -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; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java new file mode 100644 index 0000000000..cc0c77da2a --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java @@ -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 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 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 entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); + + EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null); + PageData 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 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 loadedIds = loadedEntities.stream().map(EntityData::getEntityId).collect(Collectors.toList()); + List deviceIds = devices.stream().map(Device::getId).collect(Collectors.toList()); + + Assert.assertEquals(deviceIds, loadedIds); + + List loadedNames = loadedEntities.stream().map(entityData -> + entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).collect(Collectors.toList()); + List 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()); + } +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceSqlTest.java new file mode 100644 index 0000000000..ea8affa5af --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceSqlTest.java @@ -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 { +}