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);
|
||||
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 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);
|
||||
return null;
|
||||
}
|
||||
} 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.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