Commit ca435136899d715afee6ff549a588c269fe1bd9c

Authored by Andrii Shvaika
1 parent 42a629c6

Implementation of Hierarchical queries

@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 * you may not use this file except in compliance with the License. 5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at 6 * You may obtain a copy of the License at
7 * 7 *
8 - * http://www.apache.org/licenses/LICENSE-2.0 8 + * http://www.apache.org/licenses/LICENSE-2.0
9 * 9 *
10 * Unless required by applicable law or agreed to in writing, software 10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, 11 * distributed under the License is distributed on an "AS IS" BASIS,
@@ -20,11 +20,14 @@ import org.apache.commons.lang3.StringUtils; @@ -20,11 +20,14 @@ import org.apache.commons.lang3.StringUtils;
20 import org.springframework.stereotype.Repository; 20 import org.springframework.stereotype.Repository;
21 import org.thingsboard.server.common.data.EntityType; 21 import org.thingsboard.server.common.data.EntityType;
22 import org.thingsboard.server.common.data.UUIDConverter; 22 import org.thingsboard.server.common.data.UUIDConverter;
  23 +import org.thingsboard.server.common.data.asset.Asset;
23 import org.thingsboard.server.common.data.id.CustomerId; 24 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.EntityId; 25 import org.thingsboard.server.common.data.id.EntityId;
25 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
26 import org.thingsboard.server.common.data.page.PageData; 27 import org.thingsboard.server.common.data.page.PageData;
  28 +import org.thingsboard.server.common.data.query.AssetSearchQueryFilter;
27 import org.thingsboard.server.common.data.query.AssetTypeFilter; 29 import org.thingsboard.server.common.data.query.AssetTypeFilter;
  30 +import org.thingsboard.server.common.data.query.DeviceSearchQueryFilter;
28 import org.thingsboard.server.common.data.query.DeviceTypeFilter; 31 import org.thingsboard.server.common.data.query.DeviceTypeFilter;
29 import org.thingsboard.server.common.data.query.EntityCountQuery; 32 import org.thingsboard.server.common.data.query.EntityCountQuery;
30 import org.thingsboard.server.common.data.query.EntityData; 33 import org.thingsboard.server.common.data.query.EntityData;
@@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.query.EntityFilter; @@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.query.EntityFilter;
35 import org.thingsboard.server.common.data.query.EntityFilterType; 38 import org.thingsboard.server.common.data.query.EntityFilterType;
36 import org.thingsboard.server.common.data.query.EntityListFilter; 39 import org.thingsboard.server.common.data.query.EntityListFilter;
37 import org.thingsboard.server.common.data.query.EntityNameFilter; 40 import org.thingsboard.server.common.data.query.EntityNameFilter;
  41 +import org.thingsboard.server.common.data.query.EntitySearchQueryFilter;
38 import org.thingsboard.server.common.data.query.EntityViewTypeFilter; 42 import org.thingsboard.server.common.data.query.EntityViewTypeFilter;
39 import org.thingsboard.server.common.data.query.RelationsQueryFilter; 43 import org.thingsboard.server.common.data.query.RelationsQueryFilter;
40 import org.thingsboard.server.common.data.query.SingleEntityFilter; 44 import org.thingsboard.server.common.data.query.SingleEntityFilter;
@@ -57,7 +61,7 @@ import java.util.stream.Collectors; @@ -57,7 +61,7 @@ import java.util.stream.Collectors;
57 @Repository 61 @Repository
58 @Slf4j 62 @Slf4j
59 public class DefaultEntityQueryRepository implements EntityQueryRepository { 63 public class DefaultEntityQueryRepository implements EntityQueryRepository {
60 - 64 + //TODO: rafactoring to protect from SQL injections;
61 private static final Map<EntityType, String> entityTableMap = new HashMap<>(); 65 private static final Map<EntityType, String> entityTableMap = new HashMap<>();
62 66
63 static { 67 static {
@@ -70,6 +74,22 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -70,6 +74,22 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
70 entityTableMap.put(EntityType.TENANT, "tenant"); 74 entityTableMap.put(EntityType.TENANT, "tenant");
71 } 75 }
72 76
  77 + public static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" +
  78 + " SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" +
  79 + " FROM relation" +
  80 + " WHERE $in_id = '%s' and $in_type = '%s' and relation_type_group = 'COMMON'" +
  81 + " UNION ALL" +
  82 + " SELECT r.from_id, r.from_type, r.to_id, r.to_type, r.relation_type, lvl + 1" +
  83 + " FROM relation r" +
  84 + " INNER JOIN related_entities re ON" +
  85 + " r.$in_id = re.$out_id and r.$in_type = re.$out_type and" +
  86 + " relation_type_group = 'COMMON' %s)" +
  87 + " SELECT re.$out_id entity_id, re.$out_type entity_type, re.lvl lvl" +
  88 + " from related_entities re" +
  89 + " %s ) entity";
  90 + public static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from");
  91 + public static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to");
  92 +
73 @PersistenceContext 93 @PersistenceContext
74 private EntityManager entityManager; 94 private EntityManager entityManager;
75 95
@@ -109,13 +129,19 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -109,13 +129,19 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
109 129
110 130
111 String entityWhereClause = this.buildEntityWhere(tenantId, customerId, query.getEntityFilter(), entityFieldsFiltersMapping, entityType); 131 String entityWhereClause = this.buildEntityWhere(tenantId, customerId, query.getEntityFilter(), entityFieldsFiltersMapping, entityType);
112 - String latestJoins = EntityKeyMapping.buildLatestJoins(entityType, allLatestMappings); 132 + String latestJoins = EntityKeyMapping.buildLatestJoins(query.getEntityFilter(), entityType, allLatestMappings);
113 String whereClause = this.buildWhere(selectionMapping, latestFiltersMapping, pageLink.getTextSearch()); 133 String whereClause = this.buildWhere(selectionMapping, latestFiltersMapping, pageLink.getTextSearch());
114 String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping); 134 String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping);
  135 + String entityTypeStr;
  136 + if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) {
  137 + entityTypeStr = "e.entity_type";
  138 + } else {
  139 + entityTypeStr = "'" + entityType.name() + "'";
  140 + }
115 if (!StringUtils.isEmpty(entityFieldsSelection)) { 141 if (!StringUtils.isEmpty(entityFieldsSelection)) {
116 - entityFieldsSelection = String.format("e.id, '%s', %s", entityType.name(), entityFieldsSelection); 142 + entityFieldsSelection = String.format("e.id, %s, %s", entityTypeStr, entityFieldsSelection);
117 } else { 143 } else {
118 - entityFieldsSelection = String.format("e.id, '%s'", entityType.name()); 144 + entityFieldsSelection = String.format("e.id, %s", entityTypeStr);
119 } 145 }
120 String latestSelection = EntityKeyMapping.buildSelections(latestSelectionMapping); 146 String latestSelection = EntityKeyMapping.buildSelections(latestSelectionMapping);
121 String topSelection = "entities.*"; 147 String topSelection = "entities.*";
@@ -175,16 +201,19 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -175,16 +201,19 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
175 } 201 }
176 202
177 private String buildPermissionQuery(EntityFilter entityFilter, TenantId tenantId, CustomerId customerId, EntityType entityType) { 203 private String buildPermissionQuery(EntityFilter entityFilter, TenantId tenantId, CustomerId customerId, EntityType entityType) {
178 - if (entityFilter.getType().equals(EntityFilterType.RELATIONS_QUERY)) {  
179 - return String.format("e.tenant_id='%s' and e.customer_id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()), UUIDConverter.fromTimeUUID(customerId.getId()));  
180 - } else {  
181 - if (entityType == EntityType.TENANT) {  
182 - return String.format("e.id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()));  
183 - } else if (entityType == EntityType.CUSTOMER) {  
184 - return String.format("e.tenant_id='%s' and e.id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()), UUIDConverter.fromTimeUUID(customerId.getId()));  
185 - } else { 204 + switch (entityFilter.getType()) {
  205 + case RELATIONS_QUERY:
  206 + case DEVICE_SEARCH_QUERY:
  207 + case ASSET_SEARCH_QUERY:
186 return String.format("e.tenant_id='%s' and e.customer_id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()), UUIDConverter.fromTimeUUID(customerId.getId())); 208 return String.format("e.tenant_id='%s' and e.customer_id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()), UUIDConverter.fromTimeUUID(customerId.getId()));
187 - } 209 + default:
  210 + if (entityType == EntityType.TENANT) {
  211 + return String.format("e.id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()));
  212 + } else if (entityType == EntityType.CUSTOMER) {
  213 + return String.format("e.tenant_id='%s' and e.id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()), UUIDConverter.fromTimeUUID(customerId.getId()));
  214 + } else {
  215 + return String.format("e.tenant_id='%s' and e.customer_id='%s'", UUIDConverter.fromTimeUUID(tenantId.getId()), UUIDConverter.fromTimeUUID(customerId.getId()));
  216 + }
188 } 217 }
189 } 218 }
190 219
@@ -201,6 +230,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -201,6 +230,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
201 case ENTITY_VIEW_TYPE: 230 case ENTITY_VIEW_TYPE:
202 return this.typeQuery(entityFilter); 231 return this.typeQuery(entityFilter);
203 case RELATIONS_QUERY: 232 case RELATIONS_QUERY:
  233 + case DEVICE_SEARCH_QUERY:
  234 + case ASSET_SEARCH_QUERY:
204 return ""; 235 return "";
205 default: 236 default:
206 throw new RuntimeException("Not implemented!"); 237 throw new RuntimeException("Not implemented!");
@@ -211,84 +242,42 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -211,84 +242,42 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
211 switch (entityFilter.getType()) { 242 switch (entityFilter.getType()) {
212 case RELATIONS_QUERY: 243 case RELATIONS_QUERY:
213 return relationQuery((RelationsQueryFilter) entityFilter); 244 return relationQuery((RelationsQueryFilter) entityFilter);
  245 + case DEVICE_SEARCH_QUERY:
  246 + DeviceSearchQueryFilter deviceQuery = (DeviceSearchQueryFilter) entityFilter;
  247 + return entitySearchQuery(deviceQuery, EntityType.DEVICE, deviceQuery.getDeviceTypes());
  248 + case ASSET_SEARCH_QUERY:
  249 + AssetSearchQueryFilter assetQuery = (AssetSearchQueryFilter) entityFilter;
  250 + return entitySearchQuery(assetQuery, EntityType.ASSET, assetQuery.getAssetTypes());
214 default: 251 default:
215 return entityTableMap.get(entityType); 252 return entityTableMap.get(entityType);
216 } 253 }
217 } 254 }
218 255
219 - private String relationQuery(RelationsQueryFilter entityFilter) { 256 + private String entitySearchQuery(EntitySearchQueryFilter entityFilter, EntityType entityType, List<String> types) {
220 EntityId rootId = entityFilter.getRootEntity(); 257 EntityId rootId = entityFilter.getRootEntity();
221 - String lvlFilter = entityFilter.getMaxLevel() > 0 ? ("and lvl <= " + (entityFilter.getMaxLevel() - 1)) : "";  
222 - //TODO: refactoring  
223 - String selectFields = " select CASE\n" +  
224 - " WHEN entity.entity_type = 'DEVICE'\n" +  
225 - " THEN (select tenant_id from device where id = entity_id)\n" +  
226 - " WHEN entity.entity_type = 'ASSET'\n" +  
227 - " THEN (select tenant_id from asset where id = entity_id)\n" +  
228 - " WHEN entity.entity_type = 'CUSTOMER'\n" +  
229 - " THEN (select tenant_id from customer where id = entity_id)\n" +  
230 - " WHEN entity.entity_type = 'TENANT'\n" +  
231 - " THEN entity_id\n" +  
232 - " END as tenant_id,\n" +  
233 - " CASE\n" +  
234 - " WHEN entity.entity_type = 'DEVICE'\n" +  
235 - " THEN (select customer_id from device where id = entity_id)\n" +  
236 - " WHEN entity.entity_type = 'ASSET'\n" +  
237 - " THEN (select customer_id from asset where id = entity_id)\n" +  
238 - " WHEN entity.entity_type = 'CUSTOMER'\n" +  
239 - " THEN entity_id\n" +  
240 - " WHEN entity.entity_type = 'TENANT'\n" +  
241 - " THEN '1b21dd2138140008080808080808080'\n" +  
242 - " END as customer_id,\n" +  
243 - " entity.entity_id as id,\n" +  
244 - " CASE\n" +  
245 - " WHEN entity.entity_type = 'DEVICE'\n" +  
246 - " THEN (select type from device where id = entity_id)\n" +  
247 - " WHEN entity.entity_type = 'ASSET' THEN (select type from asset where id = entity_id)\n" +  
248 - " ELSE entity.entity_type\n" +  
249 - " END as type,\n" +  
250 - " CASE\n" +  
251 - " WHEN entity.entity_type = 'DEVICE'\n" +  
252 - " THEN (select name from device where id = entity_id)\n" +  
253 - " WHEN entity.entity_type = 'ASSET' THEN (select name from asset where id = entity_id)\n" +  
254 - " WHEN entity.entity_type = 'CUSTOMER'\n" +  
255 - " THEN (select title from customer where id = entity_id)\n" +  
256 - " WHEN entity.entity_type = 'TENANT'\n" +  
257 - " THEN (select title from tenant where id = entity_id)\n" +  
258 - " ELSE entity.entity_type\n" +  
259 - " END as name,\n" +  
260 - " CASE\n" +  
261 - " WHEN entity.entity_type = 'DEVICE'\n" +  
262 - " THEN (select label from device where id = entity_id)\n" +  
263 - " WHEN entity.entity_type = 'ASSET' THEN (select label from asset where id = entity_id)\n" +  
264 - " WHEN entity.entity_type = 'CUSTOMER'\n" +  
265 - " THEN (select title from customer where id = entity_id)\n" +  
266 - " WHEN entity.entity_type = 'TENANT'\n" +  
267 - " THEN (select title from tenant where id = entity_id)\n" +  
268 - " ELSE entity.entity_type\n" +  
269 - " END as label,\n" +  
270 - " entity.entity_type as entity_type";  
271 -  
272 - String fromTemplate = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" +  
273 - " SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" +  
274 - " FROM relation" +  
275 - " WHERE $in_id = '%s' and $in_type = '%s' and relation_type_group = 'COMMON'" +  
276 - " UNION ALL" +  
277 - " SELECT r.from_id, r.from_type, r.to_id, r.to_type, r.relation_type, lvl + 1" +  
278 - " FROM relation r" +  
279 - " INNER JOIN related_entities re ON" +  
280 - " r.$in_id = re.$out_id and r.$in_type = re.$out_type and" +  
281 - " relation_type_group = 'COMMON' %s)" +  
282 - " SELECT re.$out_id entity_id, re.$out_type entity_type, re.lvl lvl" +  
283 - " from related_entities re" +  
284 - " %s ) entity"; 258 + //TODO: fetch last level only.
  259 + String lvlFilter = getLvlFilter(entityFilter.getMaxLevel());
  260 + String selectFields = "SELECT tenant_id, customer_id, id, type, name, label FROM " + entityType.name() + " WHERE id in ( SELECT entity_id";
  261 + String from = getQueryTemplate(entityFilter.getDirection());
285 262
286 - String from;  
287 - if (entityFilter.getDirection().equals(EntitySearchDirection.FROM)) {  
288 - from = fromTemplate.replace("$in", "from").replace("$out", "to");  
289 - } else {  
290 - from = fromTemplate.replace("$in", "to").replace("$out", "from"); 263 + String whereFilter = " WHERE " + " re.relation_type = '" + entityFilter.getRelationType() + "'" +
  264 + " AND re.to_type = '" + entityType.name() + "'";
  265 + from = String.format(from, UUIDConverter.fromTimeUUID(rootId.getId()), rootId.getEntityType().name(), lvlFilter, whereFilter);
  266 + String query = "( " + selectFields + from + ")";
  267 + if (types != null && !types.isEmpty()) {
  268 + query += " and type in (" + types.stream().map(type -> "'" + type + "'").collect(Collectors.joining(", ")) + ")";
291 } 269 }
  270 + query += " )";
  271 + return query;
  272 + }
  273 +
  274 + private String relationQuery(RelationsQueryFilter entityFilter) {
  275 + EntityId rootId = entityFilter.getRootEntity();
  276 + String lvlFilter = getLvlFilter(entityFilter.getMaxLevel());
  277 + String selectFields = getSelectTenantId() + ", " + getSelectCustomerId() + ", " +
  278 + " entity.entity_id as id," + getSelectType() + ", " + getSelectName() + ", " +
  279 + getSelectLabel() + ", entity.entity_type as entity_type";
  280 + String from = getQueryTemplate(entityFilter.getDirection());
292 281
293 StringBuilder whereFilter; 282 StringBuilder whereFilter;
294 if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) { 283 if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) {
@@ -302,13 +291,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -302,13 +291,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
302 whereFilter.append(" AND "); 291 whereFilter.append(" AND ");
303 } 292 }
304 String relationType = etf.getRelationType(); 293 String relationType = etf.getRelationType();
305 - String types = etf.getEntityTypes().stream().map(type -> "'" + type + "'").collect(Collectors.joining(", ")); 294 + String entityTypes = etf.getEntityTypes().stream().map(type -> "'" + type + "'").collect(Collectors.joining(", "));
306 if (!single) { 295 if (!single) {
307 whereFilter.append(" ("); 296 whereFilter.append(" (");
308 } 297 }
309 whereFilter.append(" re.relation_type = '").append(relationType).append("' and re.") 298 whereFilter.append(" re.relation_type = '").append(relationType).append("' and re.")
310 .append(entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from") 299 .append(entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from")
311 - .append("_type in (").append(types).append(")"); 300 + .append("_type in (").append(entityTypes).append(")");
312 if (!single) { 301 if (!single) {
313 whereFilter.append(" )"); 302 whereFilter.append(" )");
314 } 303 }
@@ -320,6 +309,107 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -320,6 +309,107 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
320 return "( " + selectFields + from + ")"; 309 return "( " + selectFields + from + ")";
321 } 310 }
322 311
  312 + private String getLvlFilter(int maxLevel) {
  313 + return maxLevel > 0 ? ("and lvl <= " + (maxLevel - 1)) : "";
  314 + }
  315 +
  316 + private String getQueryTemplate(EntitySearchDirection direction) {
  317 + String from;
  318 + if (direction.equals(EntitySearchDirection.FROM)) {
  319 + from = HIERARCHICAL_FROM_QUERY_TEMPLATE;
  320 + } else {
  321 + from = HIERARCHICAL_TO_QUERY_TEMPLATE;
  322 + }
  323 + return from;
  324 + }
  325 +
  326 + private String getSelectTenantId() {
  327 + return "SELECT CASE" +
  328 + " WHEN entity.entity_type = 'TENANT' THEN entity_id" +
  329 + " WHEN entity.entity_type = 'CUSTOMER'" +
  330 + " THEN (select tenant_id from customer where id = entity_id)" +
  331 + " WHEN entity.entity_type = 'USER'" +
  332 + " THEN (select tenant_id from tb_user where id = entity_id)" +
  333 + " WHEN entity.entity_type = 'DASHBOARD'" +
  334 + " THEN (select tenant_id from dashboard where id = entity_id)" +
  335 + " WHEN entity.entity_type = 'ASSET'" +
  336 + " THEN (select tenant_id from asset where id = entity_id)" +
  337 + " WHEN entity.entity_type = 'DEVICE'" +
  338 + " THEN (select tenant_id from device where id = entity_id)" +
  339 + " WHEN entity.entity_type = 'ENTITY_VIEW'" +
  340 + " THEN (select tenant_id from entity_view where id = entity_id)" +
  341 + " END as tenant_id";
  342 + }
  343 +
  344 + private String getSelectCustomerId() {
  345 + return "CASE" +
  346 + " WHEN entity.entity_type = 'TENANT'" +
  347 + " THEN '" + UUIDConverter.fromTimeUUID(TenantId.NULL_UUID) + "'" +
  348 + " WHEN entity.entity_type = 'CUSTOMER' THEN entity_id" +
  349 + " WHEN entity.entity_type = 'USER'" +
  350 + " THEN (select customer_id from tb_user where id = entity_id)" +
  351 + " WHEN entity.entity_type = 'DASHBOARD'" +
  352 + //TODO: parse assigned customers or use contains?
  353 + " THEN NULL" +
  354 + " WHEN entity.entity_type = 'ASSET'" +
  355 + " THEN (select customer_id from asset where id = entity_id)" +
  356 + " WHEN entity.entity_type = 'DEVICE'" +
  357 + " THEN (select customer_id from device where id = entity_id)" +
  358 + " WHEN entity.entity_type = 'ENTITY_VIEW'" +
  359 + " THEN (select customer_id from entity_view where id = entity_id)" +
  360 + " END as customer_id";
  361 + }
  362 +
  363 + private String getSelectName() {
  364 + return " CASE" +
  365 + " WHEN entity.entity_type = 'TENANT'" +
  366 + " THEN (select title from tenant where id = entity_id)" +
  367 + " WHEN entity.entity_type = 'CUSTOMER' " +
  368 + " THEN (select title from customer where id = entity_id)" +
  369 + " WHEN entity.entity_type = 'USER'" +
  370 + " THEN (select CONCAT (first_name, ' ', last_name) from tb_user where id = entity_id)" +
  371 + " WHEN entity.entity_type = 'DASHBOARD'" +
  372 + " THEN (select title from dashboard where id = entity_id)" +
  373 + " WHEN entity.entity_type = 'ASSET'" +
  374 + " THEN (select name from asset where id = entity_id)" +
  375 + " WHEN entity.entity_type = 'DEVICE'" +
  376 + " THEN (select name from device where id = entity_id)" +
  377 + " WHEN entity.entity_type = 'ENTITY_VIEW'" +
  378 + " THEN (select name from entity_view where id = entity_id)" +
  379 + " END as name";
  380 + }
  381 +
  382 + private String getSelectType() {
  383 + return " CASE" +
  384 + " WHEN entity.entity_type = 'USER'" +
  385 + " THEN (select authority from tb_user where id = entity_id)" +
  386 + " WHEN entity.entity_type = 'ASSET'" +
  387 + " THEN (select type from asset where id = entity_id)" +
  388 + " WHEN entity.entity_type = 'DEVICE'" +
  389 + " THEN (select type from device where id = entity_id)" +
  390 + " WHEN entity.entity_type = 'ENTITY_VIEW'" +
  391 + " THEN (select type from entity_view where id = entity_id)" +
  392 + " ELSE entity.entity_type END as type";
  393 + }
  394 +
  395 + private String getSelectLabel() {
  396 + return " CASE" +
  397 + " WHEN entity.entity_type = 'TENANT'" +
  398 + " THEN (select title from tenant where id = entity_id)" +
  399 + " WHEN entity.entity_type = 'CUSTOMER' " +
  400 + " THEN (select title from customer where id = entity_id)" +
  401 + " WHEN entity.entity_type = 'USER'" +
  402 + " THEN (select CONCAT (first_name, ' ', last_name) from tb_user where id = entity_id)" +
  403 + " WHEN entity.entity_type = 'DASHBOARD'" +
  404 + " THEN (select title from dashboard where id = entity_id)" +
  405 + " WHEN entity.entity_type = 'ASSET'" +
  406 + " THEN (select label from asset where id = entity_id)" +
  407 + " WHEN entity.entity_type = 'DEVICE'" +
  408 + " THEN (select label from device where id = entity_id)" +
  409 + " WHEN entity.entity_type = 'ENTITY_VIEW'" +
  410 + " THEN (select name from entity_view where id = entity_id)" +
  411 + " END as label";
  412 + }
323 413
324 private String buildWhere 414 private String buildWhere
325 (List<EntityKeyMapping> selectionMapping, List<EntityKeyMapping> latestFiltersMapping, String searchText) { 415 (List<EntityKeyMapping> selectionMapping, List<EntityKeyMapping> latestFiltersMapping, String searchText) {
@@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.query.BooleanFilterPredicate; @@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
22 import org.thingsboard.server.common.data.query.ComplexFilterPredicate; 22 import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
23 import org.thingsboard.server.common.data.query.EntityDataQuery; 23 import org.thingsboard.server.common.data.query.EntityDataQuery;
24 import org.thingsboard.server.common.data.query.EntityDataSortOrder; 24 import org.thingsboard.server.common.data.query.EntityDataSortOrder;
  25 +import org.thingsboard.server.common.data.query.EntityFilter;
  26 +import org.thingsboard.server.common.data.query.EntityFilterType;
25 import org.thingsboard.server.common.data.query.EntityKey; 27 import org.thingsboard.server.common.data.query.EntityKey;
26 import org.thingsboard.server.common.data.query.EntityKeyType; 28 import org.thingsboard.server.common.data.query.EntityKeyType;
27 import org.thingsboard.server.common.data.query.FilterPredicateType; 29 import org.thingsboard.server.common.data.query.FilterPredicateType;
@@ -43,6 +45,7 @@ import java.util.stream.Stream; @@ -43,6 +45,7 @@ import java.util.stream.Stream;
43 public class EntityKeyMapping { 45 public class EntityKeyMapping {
44 46
45 public static final Map<String, String> entityFieldColumnMap = new HashMap<>(); 47 public static final Map<String, String> entityFieldColumnMap = new HashMap<>();
  48 +
46 static { 49 static {
47 entityFieldColumnMap.put("createdTime", "id"); 50 entityFieldColumnMap.put("createdTime", "id");
48 entityFieldColumnMap.put("name", "name"); 51 entityFieldColumnMap.put("name", "name");
@@ -107,14 +110,20 @@ public class EntityKeyMapping { @@ -107,14 +110,20 @@ public class EntityKeyMapping {
107 } 110 }
108 } 111 }
109 112
110 - public String toLatestJoin(EntityType entityType) { 113 + public String toLatestJoin(EntityFilter entityFilter, EntityType entityType) {
  114 + String entityTypeStr;
  115 + if (entityFilter.getType().equals(EntityFilterType.RELATIONS_QUERY)) {
  116 + entityTypeStr = "entities.entity_type";
  117 + } else {
  118 + entityTypeStr = "'" + entityType.name() + "'";
  119 + }
111 String join = hasFilter() ? "left join" : "left outer join"; 120 String join = hasFilter() ? "left join" : "left outer join";
112 if (entityKey.getType().equals(EntityKeyType.TIME_SERIES)) { 121 if (entityKey.getType().equals(EntityKeyType.TIME_SERIES)) {
113 // TODO: 122 // TODO:
114 throw new RuntimeException("Not implemented!"); 123 throw new RuntimeException("Not implemented!");
115 } else { 124 } else {
116 - String query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.entity_type='%s' AND %s.attribute_key='%s'",  
117 - join, alias, alias, alias, entityType.name(), alias, entityKey.getKey()); 125 + String query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.entity_type=%s AND %s.attribute_key='%s'",
  126 + join, alias, alias, alias, entityTypeStr, alias, entityKey.getKey());
118 if (!entityKey.getType().equals(EntityKeyType.ATTRIBUTE)) { 127 if (!entityKey.getType().equals(EntityKeyType.ATTRIBUTE)) {
119 String scope; 128 String scope;
120 if (entityKey.getType().equals(EntityKeyType.CLIENT_ATTRIBUTE)) { 129 if (entityKey.getType().equals(EntityKeyType.CLIENT_ATTRIBUTE)) {
@@ -135,8 +144,8 @@ public class EntityKeyMapping { @@ -135,8 +144,8 @@ public class EntityKeyMapping {
135 Collectors.joining(", ")); 144 Collectors.joining(", "));
136 } 145 }
137 146
138 - public static String buildLatestJoins(EntityType entityType, List<EntityKeyMapping> latestMappings) {  
139 - return latestMappings.stream().map(mapping -> mapping.toLatestJoin(entityType)).collect( 147 + public static String buildLatestJoins(EntityFilter entityFilter, EntityType entityType, List<EntityKeyMapping> latestMappings) {
  148 + return latestMappings.stream().map(mapping -> mapping.toLatestJoin(entityFilter, entityType)).collect(
140 Collectors.joining(" ")); 149 Collectors.joining(" "));
141 } 150 }
142 151
@@ -207,7 +216,7 @@ public class EntityKeyMapping { @@ -207,7 +216,7 @@ public class EntityKeyMapping {
207 if (mapping.getEntityKey().getType().equals(EntityKeyType.ENTITY_FIELD)) { 216 if (mapping.getEntityKey().getType().equals(EntityKeyType.ENTITY_FIELD)) {
208 index++; 217 index++;
209 } else { 218 } else {
210 - index +=2; 219 + index += 2;
211 } 220 }
212 } 221 }
213 if (!filters.isEmpty()) { 222 if (!filters.isEmpty()) {
@@ -220,7 +229,7 @@ public class EntityKeyMapping { @@ -220,7 +229,7 @@ public class EntityKeyMapping {
220 mapping.setSelection(false); 229 mapping.setSelection(false);
221 mapping.setEntityKey(filterField); 230 mapping.setEntityKey(filterField);
222 mappings.add(mapping); 231 mappings.add(mapping);
223 - index +=1; 232 + index += 1;
224 } 233 }
225 } 234 }
226 235
@@ -253,7 +262,7 @@ public class EntityKeyMapping { @@ -253,7 +262,7 @@ public class EntityKeyMapping {
253 262
254 private String buildPredicateQuery(String alias, EntityKey key, KeyFilterPredicate predicate) { 263 private String buildPredicateQuery(String alias, EntityKey key, KeyFilterPredicate predicate) {
255 if (predicate.getType().equals(FilterPredicateType.COMPLEX)) { 264 if (predicate.getType().equals(FilterPredicateType.COMPLEX)) {
256 - return this.buildComplexPredicateQuery(alias, key, (ComplexFilterPredicate)predicate); 265 + return this.buildComplexPredicateQuery(alias, key, (ComplexFilterPredicate) predicate);
257 } else { 266 } else {
258 return this.buildSimplePredicateQuery(alias, key, predicate); 267 return this.buildSimplePredicateQuery(alias, key, predicate);
259 } 268 }
@@ -270,10 +279,10 @@ public class EntityKeyMapping { @@ -270,10 +279,10 @@ public class EntityKeyMapping {
270 if (predicate.getType().equals(FilterPredicateType.NUMERIC)) { 279 if (predicate.getType().equals(FilterPredicateType.NUMERIC)) {
271 if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) { 280 if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) {
272 String column = entityFieldColumnMap.get(key.getKey()); 281 String column = entityFieldColumnMap.get(key.getKey());
273 - return this.buildNumericPredicateQuery(alias + "." + column, (NumericFilterPredicate)predicate); 282 + return this.buildNumericPredicateQuery(alias + "." + column, (NumericFilterPredicate) predicate);
274 } else { 283 } else {
275 - String longQuery = this.buildNumericPredicateQuery(alias + ".long_v", (NumericFilterPredicate)predicate);  
276 - String doubleQuery = this.buildNumericPredicateQuery(alias + ".dbl_v", (NumericFilterPredicate)predicate); 284 + String longQuery = this.buildNumericPredicateQuery(alias + ".long_v", (NumericFilterPredicate) predicate);
  285 + String doubleQuery = this.buildNumericPredicateQuery(alias + ".dbl_v", (NumericFilterPredicate) predicate);
277 return String.format("(%s or %s)", longQuery, doubleQuery); 286 return String.format("(%s or %s)", longQuery, doubleQuery);
278 } 287 }
279 } else { 288 } else {
@@ -285,9 +294,9 @@ public class EntityKeyMapping { @@ -285,9 +294,9 @@ public class EntityKeyMapping {
285 } 294 }
286 String field = alias + "." + column; 295 String field = alias + "." + column;
287 if (predicate.getType().equals(FilterPredicateType.STRING)) { 296 if (predicate.getType().equals(FilterPredicateType.STRING)) {
288 - return this.buildStringPredicateQuery(field, (StringFilterPredicate)predicate); 297 + return this.buildStringPredicateQuery(field, (StringFilterPredicate) predicate);
289 } else { 298 } else {
290 - return this.buildBooleanPredicateQuery(field, (BooleanFilterPredicate)predicate); 299 + return this.buildBooleanPredicateQuery(field, (BooleanFilterPredicate) predicate);
291 } 300 }
292 } 301 }
293 } 302 }
@@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.Device; @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.Device;
27 import org.thingsboard.server.common.data.EntityType; 27 import org.thingsboard.server.common.data.EntityType;
28 import org.thingsboard.server.common.data.Tenant; 28 import org.thingsboard.server.common.data.Tenant;
29 import org.thingsboard.server.common.data.asset.Asset; 29 import org.thingsboard.server.common.data.asset.Asset;
  30 +import org.thingsboard.server.common.data.asset.AssetSearchQuery;
30 import org.thingsboard.server.common.data.id.CustomerId; 31 import org.thingsboard.server.common.data.id.CustomerId;
31 import org.thingsboard.server.common.data.id.DeviceId; 32 import org.thingsboard.server.common.data.id.DeviceId;
32 import org.thingsboard.server.common.data.id.EntityId; 33 import org.thingsboard.server.common.data.id.EntityId;
@@ -36,6 +37,8 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; @@ -36,6 +37,8 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
36 import org.thingsboard.server.common.data.kv.KvEntry; 37 import org.thingsboard.server.common.data.kv.KvEntry;
37 import org.thingsboard.server.common.data.kv.LongDataEntry; 38 import org.thingsboard.server.common.data.kv.LongDataEntry;
38 import org.thingsboard.server.common.data.page.PageData; 39 import org.thingsboard.server.common.data.page.PageData;
  40 +import org.thingsboard.server.common.data.query.AssetSearchQueryFilter;
  41 +import org.thingsboard.server.common.data.query.DeviceSearchQueryFilter;
39 import org.thingsboard.server.common.data.query.DeviceTypeFilter; 42 import org.thingsboard.server.common.data.query.DeviceTypeFilter;
40 import org.thingsboard.server.common.data.query.EntityCountQuery; 43 import org.thingsboard.server.common.data.query.EntityCountQuery;
41 import org.thingsboard.server.common.data.query.EntityData; 44 import org.thingsboard.server.common.data.query.EntityData;
@@ -55,6 +58,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -55,6 +58,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
55 import org.thingsboard.server.dao.attributes.AttributesService; 58 import org.thingsboard.server.dao.attributes.AttributesService;
56 59
57 import java.util.ArrayList; 60 import java.util.ArrayList;
  61 +import java.util.Arrays;
58 import java.util.Collections; 62 import java.util.Collections;
59 import java.util.List; 63 import java.util.List;
60 import java.util.concurrent.ExecutionException; 64 import java.util.concurrent.ExecutionException;
@@ -128,6 +132,280 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -128,6 +132,280 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
128 public void testCountHierarchicalEntitiesByQuery() { 132 public void testCountHierarchicalEntitiesByQuery() {
129 List<Asset> assets = new ArrayList<>(); 133 List<Asset> assets = new ArrayList<>();
130 List<Device> devices = new ArrayList<>(); 134 List<Device> devices = new ArrayList<>();
  135 + createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
  136 +
  137 + RelationsQueryFilter filter = new RelationsQueryFilter();
  138 + filter.setRootEntity(tenantId);
  139 + filter.setDirection(EntitySearchDirection.FROM);
  140 +
  141 + EntityCountQuery countQuery = new EntityCountQuery(filter);
  142 +
  143 + long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  144 + Assert.assertEquals(30, count);
  145 +
  146 + filter.setFilters(Collections.singletonList(new EntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
  147 + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  148 + Assert.assertEquals(25, count);
  149 +
  150 + filter.setRootEntity(devices.get(0).getId());
  151 + filter.setDirection(EntitySearchDirection.TO);
  152 + filter.setFilters(Collections.singletonList(new EntityTypeFilter("Manages", Collections.singletonList(EntityType.TENANT))));
  153 + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  154 + Assert.assertEquals(1, count);
  155 +
  156 + DeviceSearchQueryFilter filter2 = new DeviceSearchQueryFilter();
  157 + filter2.setRootEntity(tenantId);
  158 + filter2.setDirection(EntitySearchDirection.FROM);
  159 + filter2.setRelationType("Contains");
  160 +
  161 + countQuery = new EntityCountQuery(filter2);
  162 +
  163 + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  164 + Assert.assertEquals(25, count);
  165 +
  166 + filter2.setDeviceTypes(Arrays.asList("default0", "default1"));
  167 + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  168 + Assert.assertEquals(10, count);
  169 +
  170 + filter2.setRootEntity(devices.get(0).getId());
  171 + filter2.setDirection(EntitySearchDirection.TO);
  172 + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  173 + Assert.assertEquals(0, count);
  174 +
  175 + AssetSearchQueryFilter filter3 = new AssetSearchQueryFilter();
  176 + filter3.setRootEntity(tenantId);
  177 + filter3.setDirection(EntitySearchDirection.FROM);
  178 + filter3.setRelationType("Manages");
  179 +
  180 + countQuery = new EntityCountQuery(filter3);
  181 +
  182 + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  183 + Assert.assertEquals(5, count);
  184 +
  185 + filter3.setAssetTypes(Arrays.asList("type0", "type1"));
  186 + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  187 + Assert.assertEquals(2, count);
  188 +
  189 + filter3.setRootEntity(devices.get(0).getId());
  190 + filter3.setDirection(EntitySearchDirection.TO);
  191 + count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
  192 + Assert.assertEquals(0, count);
  193 + }
  194 +
  195 + @Test
  196 + public void testHierarchicalFindEntityDataWithAttributesByQuery() throws ExecutionException, InterruptedException {
  197 + List<Asset> assets = new ArrayList<>();
  198 + List<Device> devices = new ArrayList<>();
  199 + List<Long> temperatures = new ArrayList<>();
  200 + List<Long> highTemperatures = new ArrayList<>();
  201 + createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
  202 +
  203 + List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
  204 + for (int i = 0; i < devices.size(); i++) {
  205 + Device device = devices.get(i);
  206 + attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), DataConstants.CLIENT_SCOPE));
  207 + }
  208 + Futures.successfulAsList(attributeFutures).get();
  209 +
  210 + RelationsQueryFilter filter = new RelationsQueryFilter();
  211 + filter.setRootEntity(tenantId);
  212 + filter.setDirection(EntitySearchDirection.FROM);
  213 + filter.setFilters(Collections.singletonList(new EntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
  214 +
  215 + EntityDataSortOrder sortOrder = new EntityDataSortOrder(
  216 + new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
  217 + );
  218 + EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
  219 + List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
  220 + List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
  221 +
  222 + EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
  223 + PageData<EntityData> data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  224 + List<EntityData> loadedEntities = new ArrayList<>(data.getData());
  225 + while (data.hasNext()) {
  226 + query = query.next();
  227 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  228 + loadedEntities.addAll(data.getData());
  229 + }
  230 + Assert.assertEquals(25, loadedEntities.size());
  231 + List<String> loadedTemperatures = loadedEntities.stream().map(entityData ->
  232 + entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
  233 + List<String> deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
  234 + Assert.assertEquals(deviceTemperatures, loadedTemperatures);
  235 +
  236 + pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
  237 + KeyFilter highTemperatureFilter = new KeyFilter();
  238 + highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
  239 + NumericFilterPredicate predicate = new NumericFilterPredicate();
  240 + predicate.setValue(45);
  241 + predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
  242 + highTemperatureFilter.setPredicate(predicate);
  243 + List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
  244 +
  245 + query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
  246 +
  247 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  248 +
  249 + loadedEntities = new ArrayList<>(data.getData());
  250 + while (data.hasNext()) {
  251 + query = query.next();
  252 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  253 + loadedEntities.addAll(data.getData());
  254 + }
  255 + Assert.assertEquals(highTemperatures.size(), loadedEntities.size());
  256 +
  257 + List<String> loadedHighTemperatures = loadedEntities.stream().map(entityData ->
  258 + entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
  259 + List<String> deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
  260 +
  261 + Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
  262 +
  263 + deviceService.deleteDevicesByTenantId(tenantId);
  264 + }
  265 +
  266 + @Test
  267 + public void testHierarchicalFindDevicesWithAttributesByQuery() throws ExecutionException, InterruptedException {
  268 + List<Asset> assets = new ArrayList<>();
  269 + List<Device> devices = new ArrayList<>();
  270 + List<Long> temperatures = new ArrayList<>();
  271 + List<Long> highTemperatures = new ArrayList<>();
  272 + createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
  273 +
  274 + List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
  275 + for (int i = 0; i < devices.size(); i++) {
  276 + Device device = devices.get(i);
  277 + attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), DataConstants.CLIENT_SCOPE));
  278 + }
  279 + Futures.successfulAsList(attributeFutures).get();
  280 +
  281 + DeviceSearchQueryFilter filter = new DeviceSearchQueryFilter();
  282 + filter.setRootEntity(tenantId);
  283 + filter.setDirection(EntitySearchDirection.FROM);
  284 + filter.setRelationType("Contains");
  285 +
  286 + EntityDataSortOrder sortOrder = new EntityDataSortOrder(
  287 + new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
  288 + );
  289 + EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
  290 + List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
  291 + List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
  292 +
  293 + EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
  294 + PageData<EntityData> data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  295 + List<EntityData> loadedEntities = new ArrayList<>(data.getData());
  296 + while (data.hasNext()) {
  297 + query = query.next();
  298 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  299 + loadedEntities.addAll(data.getData());
  300 + }
  301 + Assert.assertEquals(25, loadedEntities.size());
  302 + List<String> loadedTemperatures = loadedEntities.stream().map(entityData ->
  303 + entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
  304 + List<String> deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
  305 + Assert.assertEquals(deviceTemperatures, loadedTemperatures);
  306 +
  307 + pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
  308 + KeyFilter highTemperatureFilter = new KeyFilter();
  309 + highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
  310 + NumericFilterPredicate predicate = new NumericFilterPredicate();
  311 + predicate.setValue(45);
  312 + predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
  313 + highTemperatureFilter.setPredicate(predicate);
  314 + List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
  315 +
  316 + query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
  317 +
  318 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  319 +
  320 + loadedEntities = new ArrayList<>(data.getData());
  321 + while (data.hasNext()) {
  322 + query = query.next();
  323 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  324 + loadedEntities.addAll(data.getData());
  325 + }
  326 + Assert.assertEquals(highTemperatures.size(), loadedEntities.size());
  327 +
  328 + List<String> loadedHighTemperatures = loadedEntities.stream().map(entityData ->
  329 + entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList());
  330 + List<String> deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
  331 +
  332 + Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
  333 +
  334 + deviceService.deleteDevicesByTenantId(tenantId);
  335 + }
  336 +
  337 + @Test
  338 + public void testHierarchicalFindAssetsWithAttributesByQuery() throws ExecutionException, InterruptedException {
  339 + List<Asset> assets = new ArrayList<>();
  340 + List<Device> devices = new ArrayList<>();
  341 + List<Long> consumptions = new ArrayList<>();
  342 + List<Long> highConsumptions = new ArrayList<>();
  343 + createTestHierarchy(assets, devices, consumptions, highConsumptions, new ArrayList<>(), new ArrayList<>());
  344 +
  345 + List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
  346 + for (int i = 0; i < assets.size(); i++) {
  347 + Asset asset = assets.get(i);
  348 + attributeFutures.add(saveLongAttribute(asset.getId(), "consumption", consumptions.get(i), DataConstants.SERVER_SCOPE));
  349 + }
  350 + Futures.successfulAsList(attributeFutures).get();
  351 +
  352 + AssetSearchQueryFilter filter = new AssetSearchQueryFilter();
  353 + filter.setRootEntity(tenantId);
  354 + filter.setDirection(EntitySearchDirection.FROM);
  355 + filter.setRelationType("Manages");
  356 +
  357 + EntityDataSortOrder sortOrder = new EntityDataSortOrder(
  358 + new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
  359 + );
  360 + EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
  361 + List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
  362 + List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "consumption"));
  363 +
  364 + EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
  365 + PageData<EntityData> data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  366 + List<EntityData> loadedEntities = new ArrayList<>(data.getData());
  367 + while (data.hasNext()) {
  368 + query = query.next();
  369 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  370 + loadedEntities.addAll(data.getData());
  371 + }
  372 + Assert.assertEquals(5, loadedEntities.size());
  373 + List<String> loadedTemperatures = loadedEntities.stream().map(entityData ->
  374 + entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("consumption").getValue()).collect(Collectors.toList());
  375 + List<String> deviceTemperatures = consumptions.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
  376 + Assert.assertEquals(deviceTemperatures, loadedTemperatures);
  377 +
  378 + pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
  379 + KeyFilter highTemperatureFilter = new KeyFilter();
  380 + highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "consumption"));
  381 + NumericFilterPredicate predicate = new NumericFilterPredicate();
  382 + predicate.setValue(50);
  383 + predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
  384 + highTemperatureFilter.setPredicate(predicate);
  385 + List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
  386 +
  387 + query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
  388 +
  389 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  390 +
  391 + loadedEntities = new ArrayList<>(data.getData());
  392 + while (data.hasNext()) {
  393 + query = query.next();
  394 + data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query);
  395 + loadedEntities.addAll(data.getData());
  396 + }
  397 + Assert.assertEquals(highConsumptions.size(), loadedEntities.size());
  398 +
  399 + List<String> loadedHighTemperatures = loadedEntities.stream().map(entityData ->
  400 + entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("consumption").getValue()).collect(Collectors.toList());
  401 + List<String> deviceHighTemperatures = highConsumptions.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
  402 +
  403 + Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
  404 +
  405 + deviceService.deleteDevicesByTenantId(tenantId);
  406 + }
  407 +
  408 + private void createTestHierarchy(List<Asset> assets, List<Device> devices, List<Long> consumptions, List<Long> highConsumptions, List<Long> temperatures, List<Long> highTemperatures) {
131 for (int i = 0; i < 5; i++) { 409 for (int i = 0; i < 5; i++) {
132 Asset asset = new Asset(); 410 Asset asset = new Asset();
133 asset.setTenantId(tenantId); 411 asset.setTenantId(tenantId);
@@ -142,11 +420,16 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -142,11 +420,16 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
142 er.setType("Manages"); 420 er.setType("Manages");
143 er.setTypeGroup(RelationTypeGroup.COMMON); 421 er.setTypeGroup(RelationTypeGroup.COMMON);
144 relationService.saveRelation(tenantId, er); 422 relationService.saveRelation(tenantId, er);
  423 + long consumption = (long) (Math.random() * 100);
  424 + consumptions.add(consumption);
  425 + if (consumption > 50) {
  426 + highConsumptions.add(consumption);
  427 + }
145 for (int j = 0; j < 5; j++) { 428 for (int j = 0; j < 5; j++) {
146 Device device = new Device(); 429 Device device = new Device();
147 device.setTenantId(tenantId); 430 device.setTenantId(tenantId);
148 device.setName("A" + i + "Device" + j); 431 device.setName("A" + i + "Device" + j);
149 - device.setType("default"); 432 + device.setType("default" + j);
150 device.setLabel("testLabel" + (int) (Math.random() * 1000)); 433 device.setLabel("testLabel" + (int) (Math.random() * 1000));
151 device = deviceService.saveDevice(device); 434 device = deviceService.saveDevice(device);
152 devices.add(device); 435 devices.add(device);
@@ -156,27 +439,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -156,27 +439,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
156 er.setType("Contains"); 439 er.setType("Contains");
157 er.setTypeGroup(RelationTypeGroup.COMMON); 440 er.setTypeGroup(RelationTypeGroup.COMMON);
158 relationService.saveRelation(tenantId, er); 441 relationService.saveRelation(tenantId, er);
  442 + long temperature = (long) (Math.random() * 100);
  443 + temperatures.add(temperature);
  444 + if (temperature > 45) {
  445 + highTemperatures.add(temperature);
  446 + }
159 } 447 }
160 } 448 }
161 -  
162 - RelationsQueryFilter filter = new RelationsQueryFilter();  
163 - filter.setRootEntity(tenantId);  
164 - filter.setDirection(EntitySearchDirection.FROM);  
165 -  
166 - EntityCountQuery countQuery = new EntityCountQuery(filter);  
167 -  
168 - long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);  
169 - Assert.assertEquals(30, count);  
170 -  
171 - filter.setFilters(Collections.singletonList(new EntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));  
172 - count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);  
173 - Assert.assertEquals(25, count);  
174 -  
175 - filter.setRootEntity(devices.get(0).getId());  
176 - filter.setDirection(EntitySearchDirection.TO);  
177 - filter.setFilters(Collections.singletonList(new EntityTypeFilter("Manages", Collections.singletonList(EntityType.TENANT))));  
178 - count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);  
179 - Assert.assertEquals(1, count);  
180 } 449 }
181 450
182 @Test 451 @Test