Entity data query builder improvements + initial tests
This commit is contained in:
		
							parent
							
								
									3acae2fbf2
								
							
						
					
					
						commit
						9460b09eef
					
				@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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 {
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user