Commit ca435136899d715afee6ff549a588c269fe1bd9c
1 parent
42a629c6
Implementation of Hierarchical queries
Showing
3 changed files
with
487 additions
and
119 deletions
@@ -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 |