Commit ca435136899d715afee6ff549a588c269fe1bd9c
1 parent
42a629c6
Implementation of Hierarchical queries
Showing
3 changed files
with
487 additions
and
119 deletions
... | ... | @@ -5,7 +5,7 @@ |
5 | 5 | * you may not use this file except in compliance with the License. |
6 | 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 | 10 | * Unless required by applicable law or agreed to in writing, software |
11 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
... | ... | @@ -20,11 +20,14 @@ import org.apache.commons.lang3.StringUtils; |
20 | 20 | import org.springframework.stereotype.Repository; |
21 | 21 | import org.thingsboard.server.common.data.EntityType; |
22 | 22 | import org.thingsboard.server.common.data.UUIDConverter; |
23 | +import org.thingsboard.server.common.data.asset.Asset; | |
23 | 24 | import org.thingsboard.server.common.data.id.CustomerId; |
24 | 25 | import org.thingsboard.server.common.data.id.EntityId; |
25 | 26 | import org.thingsboard.server.common.data.id.TenantId; |
26 | 27 | import org.thingsboard.server.common.data.page.PageData; |
28 | +import org.thingsboard.server.common.data.query.AssetSearchQueryFilter; | |
27 | 29 | import org.thingsboard.server.common.data.query.AssetTypeFilter; |
30 | +import org.thingsboard.server.common.data.query.DeviceSearchQueryFilter; | |
28 | 31 | import org.thingsboard.server.common.data.query.DeviceTypeFilter; |
29 | 32 | import org.thingsboard.server.common.data.query.EntityCountQuery; |
30 | 33 | import org.thingsboard.server.common.data.query.EntityData; |
... | ... | @@ -35,6 +38,7 @@ import org.thingsboard.server.common.data.query.EntityFilter; |
35 | 38 | import org.thingsboard.server.common.data.query.EntityFilterType; |
36 | 39 | import org.thingsboard.server.common.data.query.EntityListFilter; |
37 | 40 | import org.thingsboard.server.common.data.query.EntityNameFilter; |
41 | +import org.thingsboard.server.common.data.query.EntitySearchQueryFilter; | |
38 | 42 | import org.thingsboard.server.common.data.query.EntityViewTypeFilter; |
39 | 43 | import org.thingsboard.server.common.data.query.RelationsQueryFilter; |
40 | 44 | import org.thingsboard.server.common.data.query.SingleEntityFilter; |
... | ... | @@ -57,7 +61,7 @@ import java.util.stream.Collectors; |
57 | 61 | @Repository |
58 | 62 | @Slf4j |
59 | 63 | public class DefaultEntityQueryRepository implements EntityQueryRepository { |
60 | - | |
64 | + //TODO: rafactoring to protect from SQL injections; | |
61 | 65 | private static final Map<EntityType, String> entityTableMap = new HashMap<>(); |
62 | 66 | |
63 | 67 | static { |
... | ... | @@ -70,6 +74,22 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
70 | 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 | 93 | @PersistenceContext |
74 | 94 | private EntityManager entityManager; |
75 | 95 | |
... | ... | @@ -109,13 +129,19 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
109 | 129 | |
110 | 130 | |
111 | 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 | 133 | String whereClause = this.buildWhere(selectionMapping, latestFiltersMapping, pageLink.getTextSearch()); |
114 | 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 | 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 | 143 | } else { |
118 | - entityFieldsSelection = String.format("e.id, '%s'", entityType.name()); | |
144 | + entityFieldsSelection = String.format("e.id, %s", entityTypeStr); | |
119 | 145 | } |
120 | 146 | String latestSelection = EntityKeyMapping.buildSelections(latestSelectionMapping); |
121 | 147 | String topSelection = "entities.*"; |
... | ... | @@ -175,16 +201,19 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
175 | 201 | } |
176 | 202 | |
177 | 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 | 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 | 230 | case ENTITY_VIEW_TYPE: |
202 | 231 | return this.typeQuery(entityFilter); |
203 | 232 | case RELATIONS_QUERY: |
233 | + case DEVICE_SEARCH_QUERY: | |
234 | + case ASSET_SEARCH_QUERY: | |
204 | 235 | return ""; |
205 | 236 | default: |
206 | 237 | throw new RuntimeException("Not implemented!"); |
... | ... | @@ -211,84 +242,42 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
211 | 242 | switch (entityFilter.getType()) { |
212 | 243 | case RELATIONS_QUERY: |
213 | 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 | 251 | default: |
215 | 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 | 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 | 282 | StringBuilder whereFilter; |
294 | 283 | if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) { |
... | ... | @@ -302,13 +291,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
302 | 291 | whereFilter.append(" AND "); |
303 | 292 | } |
304 | 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 | 295 | if (!single) { |
307 | 296 | whereFilter.append(" ("); |
308 | 297 | } |
309 | 298 | whereFilter.append(" re.relation_type = '").append(relationType).append("' and re.") |
310 | 299 | .append(entityFilter.getDirection().equals(EntitySearchDirection.FROM) ? "to" : "from") |
311 | - .append("_type in (").append(types).append(")"); | |
300 | + .append("_type in (").append(entityTypes).append(")"); | |
312 | 301 | if (!single) { |
313 | 302 | whereFilter.append(" )"); |
314 | 303 | } |
... | ... | @@ -320,6 +309,107 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { |
320 | 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 | 414 | private String buildWhere |
325 | 415 | (List<EntityKeyMapping> selectionMapping, List<EntityKeyMapping> latestFiltersMapping, String searchText) { | ... | ... |
... | ... | @@ -22,6 +22,8 @@ import org.thingsboard.server.common.data.query.BooleanFilterPredicate; |
22 | 22 | import org.thingsboard.server.common.data.query.ComplexFilterPredicate; |
23 | 23 | import org.thingsboard.server.common.data.query.EntityDataQuery; |
24 | 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 | 27 | import org.thingsboard.server.common.data.query.EntityKey; |
26 | 28 | import org.thingsboard.server.common.data.query.EntityKeyType; |
27 | 29 | import org.thingsboard.server.common.data.query.FilterPredicateType; |
... | ... | @@ -43,6 +45,7 @@ import java.util.stream.Stream; |
43 | 45 | public class EntityKeyMapping { |
44 | 46 | |
45 | 47 | public static final Map<String, String> entityFieldColumnMap = new HashMap<>(); |
48 | + | |
46 | 49 | static { |
47 | 50 | entityFieldColumnMap.put("createdTime", "id"); |
48 | 51 | entityFieldColumnMap.put("name", "name"); |
... | ... | @@ -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 | 120 | String join = hasFilter() ? "left join" : "left outer join"; |
112 | 121 | if (entityKey.getType().equals(EntityKeyType.TIME_SERIES)) { |
113 | 122 | // TODO: |
114 | 123 | throw new RuntimeException("Not implemented!"); |
115 | 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 | 127 | if (!entityKey.getType().equals(EntityKeyType.ATTRIBUTE)) { |
119 | 128 | String scope; |
120 | 129 | if (entityKey.getType().equals(EntityKeyType.CLIENT_ATTRIBUTE)) { |
... | ... | @@ -135,8 +144,8 @@ public class EntityKeyMapping { |
135 | 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 | 149 | Collectors.joining(" ")); |
141 | 150 | } |
142 | 151 | |
... | ... | @@ -207,7 +216,7 @@ public class EntityKeyMapping { |
207 | 216 | if (mapping.getEntityKey().getType().equals(EntityKeyType.ENTITY_FIELD)) { |
208 | 217 | index++; |
209 | 218 | } else { |
210 | - index +=2; | |
219 | + index += 2; | |
211 | 220 | } |
212 | 221 | } |
213 | 222 | if (!filters.isEmpty()) { |
... | ... | @@ -220,7 +229,7 @@ public class EntityKeyMapping { |
220 | 229 | mapping.setSelection(false); |
221 | 230 | mapping.setEntityKey(filterField); |
222 | 231 | mappings.add(mapping); |
223 | - index +=1; | |
232 | + index += 1; | |
224 | 233 | } |
225 | 234 | } |
226 | 235 | |
... | ... | @@ -253,7 +262,7 @@ public class EntityKeyMapping { |
253 | 262 | |
254 | 263 | private String buildPredicateQuery(String alias, EntityKey key, KeyFilterPredicate predicate) { |
255 | 264 | if (predicate.getType().equals(FilterPredicateType.COMPLEX)) { |
256 | - return this.buildComplexPredicateQuery(alias, key, (ComplexFilterPredicate)predicate); | |
265 | + return this.buildComplexPredicateQuery(alias, key, (ComplexFilterPredicate) predicate); | |
257 | 266 | } else { |
258 | 267 | return this.buildSimplePredicateQuery(alias, key, predicate); |
259 | 268 | } |
... | ... | @@ -270,10 +279,10 @@ public class EntityKeyMapping { |
270 | 279 | if (predicate.getType().equals(FilterPredicateType.NUMERIC)) { |
271 | 280 | if (key.getType().equals(EntityKeyType.ENTITY_FIELD)) { |
272 | 281 | String column = entityFieldColumnMap.get(key.getKey()); |
273 | - return this.buildNumericPredicateQuery(alias + "." + column, (NumericFilterPredicate)predicate); | |
282 | + return this.buildNumericPredicateQuery(alias + "." + column, (NumericFilterPredicate) predicate); | |
274 | 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 | 286 | return String.format("(%s or %s)", longQuery, doubleQuery); |
278 | 287 | } |
279 | 288 | } else { |
... | ... | @@ -285,9 +294,9 @@ public class EntityKeyMapping { |
285 | 294 | } |
286 | 295 | String field = alias + "." + column; |
287 | 296 | if (predicate.getType().equals(FilterPredicateType.STRING)) { |
288 | - return this.buildStringPredicateQuery(field, (StringFilterPredicate)predicate); | |
297 | + return this.buildStringPredicateQuery(field, (StringFilterPredicate) predicate); | |
289 | 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 | 27 | import org.thingsboard.server.common.data.EntityType; |
28 | 28 | import org.thingsboard.server.common.data.Tenant; |
29 | 29 | import org.thingsboard.server.common.data.asset.Asset; |
30 | +import org.thingsboard.server.common.data.asset.AssetSearchQuery; | |
30 | 31 | import org.thingsboard.server.common.data.id.CustomerId; |
31 | 32 | import org.thingsboard.server.common.data.id.DeviceId; |
32 | 33 | import org.thingsboard.server.common.data.id.EntityId; |
... | ... | @@ -36,6 +37,8 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
36 | 37 | import org.thingsboard.server.common.data.kv.KvEntry; |
37 | 38 | import org.thingsboard.server.common.data.kv.LongDataEntry; |
38 | 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 | 42 | import org.thingsboard.server.common.data.query.DeviceTypeFilter; |
40 | 43 | import org.thingsboard.server.common.data.query.EntityCountQuery; |
41 | 44 | import org.thingsboard.server.common.data.query.EntityData; |
... | ... | @@ -55,6 +58,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
55 | 58 | import org.thingsboard.server.dao.attributes.AttributesService; |
56 | 59 | |
57 | 60 | import java.util.ArrayList; |
61 | +import java.util.Arrays; | |
58 | 62 | import java.util.Collections; |
59 | 63 | import java.util.List; |
60 | 64 | import java.util.concurrent.ExecutionException; |
... | ... | @@ -128,6 +132,280 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
128 | 132 | public void testCountHierarchicalEntitiesByQuery() { |
129 | 133 | List<Asset> assets = new ArrayList<>(); |
130 | 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 | 409 | for (int i = 0; i < 5; i++) { |
132 | 410 | Asset asset = new Asset(); |
133 | 411 | asset.setTenantId(tenantId); |
... | ... | @@ -142,11 +420,16 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
142 | 420 | er.setType("Manages"); |
143 | 421 | er.setTypeGroup(RelationTypeGroup.COMMON); |
144 | 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 | 428 | for (int j = 0; j < 5; j++) { |
146 | 429 | Device device = new Device(); |
147 | 430 | device.setTenantId(tenantId); |
148 | 431 | device.setName("A" + i + "Device" + j); |
149 | - device.setType("default"); | |
432 | + device.setType("default" + j); | |
150 | 433 | device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
151 | 434 | device = deviceService.saveDevice(device); |
152 | 435 | devices.add(device); |
... | ... | @@ -156,27 +439,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
156 | 439 | er.setType("Contains"); |
157 | 440 | er.setTypeGroup(RelationTypeGroup.COMMON); |
158 | 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 | 451 | @Test | ... | ... |