...
|
...
|
@@ -17,15 +17,18 @@ package org.thingsboard.server.dao.service; |
17
|
17
|
|
18
|
18
|
import com.google.common.util.concurrent.Futures;
|
19
|
19
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
20
|
+import lombok.extern.slf4j.Slf4j;
|
20
|
21
|
import org.apache.commons.lang3.RandomStringUtils;
|
21
|
22
|
import org.apache.commons.lang3.RandomUtils;
|
22
|
23
|
import org.hamcrest.Matchers;
|
|
24
|
+import org.jetbrains.annotations.NotNull;
|
23
|
25
|
import org.junit.After;
|
24
|
26
|
import org.junit.Assert;
|
25
|
27
|
import org.junit.Before;
|
26
|
28
|
import org.junit.Test;
|
27
|
29
|
import org.springframework.beans.factory.annotation.Autowired;
|
28
|
30
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
31
|
+import org.springframework.jdbc.core.ResultSetExtractor;
|
29
|
32
|
import org.thingsboard.server.common.data.DataConstants;
|
30
|
33
|
import org.thingsboard.server.common.data.Device;
|
31
|
34
|
import org.thingsboard.server.common.data.EntityType;
|
...
|
...
|
@@ -80,14 +83,17 @@ import java.util.Comparator; |
80
|
83
|
import java.util.List;
|
81
|
84
|
import java.util.Random;
|
82
|
85
|
import java.util.concurrent.ExecutionException;
|
|
86
|
+import java.util.concurrent.atomic.AtomicInteger;
|
83
|
87
|
import java.util.stream.Collectors;
|
84
|
88
|
import java.util.stream.Stream;
|
85
|
89
|
|
86
|
90
|
import static org.junit.Assert.assertEquals;
|
87
|
91
|
import static org.hamcrest.MatcherAssert.assertThat;
|
88
|
92
|
|
|
93
|
+@Slf4j
|
89
|
94
|
public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
90
|
95
|
|
|
96
|
+ static final int ENTITY_COUNT = 5;
|
91
|
97
|
@Autowired
|
92
|
98
|
private AttributesService attributesService;
|
93
|
99
|
|
...
|
...
|
@@ -525,7 +531,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
525
|
531
|
}
|
526
|
532
|
|
527
|
533
|
private void createTestHierarchy(TenantId tenantId, List<Asset> assets, List<Device> devices, List<Long> consumptions, List<Long> highConsumptions, List<Long> temperatures, List<Long> highTemperatures) throws InterruptedException {
|
528
|
|
- for (int i = 0; i < 5; i++) {
|
|
534
|
+ for (int i = 0; i < ENTITY_COUNT; i++) {
|
529
|
535
|
Asset asset = new Asset();
|
530
|
536
|
asset.setTenantId(tenantId);
|
531
|
537
|
asset.setName("Asset" + i);
|
...
|
...
|
@@ -535,18 +541,19 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
535
|
541
|
//TO make sure devices have different created time
|
536
|
542
|
Thread.sleep(1);
|
537
|
543
|
assets.add(asset);
|
538
|
|
- EntityRelation er = new EntityRelation();
|
539
|
|
- er.setFrom(tenantId);
|
540
|
|
- er.setTo(asset.getId());
|
541
|
|
- er.setType("Manages");
|
542
|
|
- er.setTypeGroup(RelationTypeGroup.COMMON);
|
543
|
|
- relationService.saveRelation(tenantId, er);
|
|
544
|
+ createRelation(tenantId, "Manages", tenantId, asset.getId());
|
544
|
545
|
long consumption = (long) (Math.random() * 100);
|
545
|
546
|
consumptions.add(consumption);
|
546
|
547
|
if (consumption > 50) {
|
547
|
548
|
highConsumptions.add(consumption);
|
548
|
549
|
}
|
549
|
|
- for (int j = 0; j < 5; j++) {
|
|
550
|
+
|
|
551
|
+ //tenant -> asset : one-to-one but many edges
|
|
552
|
+ for (int n = 0; n < ENTITY_COUNT; n++) {
|
|
553
|
+ createRelation(tenantId, "UseCase-" + n, tenantId, asset.getId());
|
|
554
|
+ }
|
|
555
|
+
|
|
556
|
+ for (int j = 0; j < ENTITY_COUNT; j++) {
|
550
|
557
|
Device device = new Device();
|
551
|
558
|
device.setTenantId(tenantId);
|
552
|
559
|
device.setName("A" + i + "Device" + j);
|
...
|
...
|
@@ -556,27 +563,158 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
556
|
563
|
//TO make sure devices have different created time
|
557
|
564
|
Thread.sleep(1);
|
558
|
565
|
devices.add(device);
|
559
|
|
- er = new EntityRelation();
|
560
|
|
- er.setFrom(asset.getId());
|
561
|
|
- er.setTo(device.getId());
|
562
|
|
- er.setType("Contains");
|
563
|
|
- er.setTypeGroup(RelationTypeGroup.COMMON);
|
564
|
|
- relationService.saveRelation(tenantId, er);
|
|
566
|
+ createRelation(tenantId, "Contains", asset.getId(), device.getId());
|
565
|
567
|
long temperature = (long) (Math.random() * 100);
|
566
|
568
|
temperatures.add(temperature);
|
567
|
569
|
if (temperature > 45) {
|
568
|
570
|
highTemperatures.add(temperature);
|
569
|
571
|
}
|
|
572
|
+
|
|
573
|
+ //asset -> device : one-to-one but many edges
|
|
574
|
+ for (int n = 0; n < ENTITY_COUNT; n++) {
|
|
575
|
+ createRelation(tenantId, "UseCase-" + n, asset.getId(), device.getId());
|
|
576
|
+ }
|
570
|
577
|
}
|
571
|
578
|
}
|
572
|
579
|
|
573
|
|
- createManyCustomRelationsBetweenTwoNodes(tenantId, "UseCase", assets, devices);
|
|
580
|
+ //asset -> device one-to-many shared with other assets
|
|
581
|
+ for (int n = 0; n < devices.size(); n = n + ENTITY_COUNT) {
|
|
582
|
+ createRelation(tenantId, "SharedWithAsset0", assets.get(0).getId(), devices.get(n).getId());
|
|
583
|
+ }
|
|
584
|
+
|
|
585
|
+ //createManyCustomRelationsBetweenTwoNodes(tenantId, "UseCase", assets, devices);
|
574
|
586
|
createHorizontalRingRelations(tenantId, "Ring(Loop)-Ast", assets);
|
575
|
587
|
createLoopRelations(tenantId, "Loop-Tnt-Ast-Dev", tenantId, assets.get(0).getId(), devices.get(0).getId());
|
576
|
588
|
createLoopRelations(tenantId, "Loop-Tnt-Ast", tenantId, assets.get(1).getId());
|
577
|
589
|
createLoopRelations(tenantId, "Loop-Ast-Tnt-Ast", assets.get(2).getId(), tenantId, assets.get(3).getId());
|
578
|
590
|
|
579
|
|
- printAllRelations();
|
|
591
|
+ //testQuery();
|
|
592
|
+
|
|
593
|
+ //printAllRelations();
|
|
594
|
+ }
|
|
595
|
+
|
|
596
|
+ private void testQuery() {
|
|
597
|
+
|
|
598
|
+ template.query("" +
|
|
599
|
+ " DROP TABLE IF EXISTS relation_test;\n" +
|
|
600
|
+ " CREATE TABLE IF NOT EXISTS relation_test\n" +
|
|
601
|
+ " (\n" +
|
|
602
|
+ " from_id uuid,\n" +
|
|
603
|
+ " from_type varchar(255),\n" +
|
|
604
|
+ " to_id uuid,\n" +
|
|
605
|
+ " to_type varchar(255),\n" +
|
|
606
|
+ " relation_type_group varchar(255),\n" +
|
|
607
|
+ " relation_type varchar(255),\n" +
|
|
608
|
+ " additional_info varchar,\n" +
|
|
609
|
+ " CONSTRAINT relation_test_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)\n" +
|
|
610
|
+ " );\n", r -> null);
|
|
611
|
+
|
|
612
|
+ log.error("insert test {}", (Object) template.query("" +
|
|
613
|
+ "INSERT INTO relation_test (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) " +
|
|
614
|
+ " VALUES " +
|
|
615
|
+ " ('11111111-0f19-11ec-ba23-e981fc95500d', 'TENANT', '22222222-0f19-11ec-ba23-e981fc95500d', 'ASSET', 'COMMON', 'Contains', null),\n" +
|
|
616
|
+ " ('22222222-0f19-11ec-ba23-e981fc95500d', 'ASSET', '33333333-0f19-11ec-ba23-e981fc95500d', 'DEVICE', 'COMMON', 'Contains', null),\n" +
|
|
617
|
+ " ('33333333-0f19-11ec-ba23-e981fc95500d', 'DEVICE', '11111111-0f19-11ec-ba23-e981fc95500d', 'TENANT', 'COMMON', 'Contains', null);\n" +
|
|
618
|
+ ""
|
|
619
|
+ , r -> null));
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+ //log.error("array_position hsql {}", template.queryForObject("select array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon') from relation_test", Long.class));
|
|
623
|
+
|
|
624
|
+ log.error("array_position hsql {}", template.queryForObject("select position_array('mon' in ARRAY['sun','mon','tue']);", Long.class));
|
|
625
|
+ log.error("array_position psql {}", template.queryForObject("select array_position(ARRAY['sun','mon','tue'], 'mon');", Long.class));
|
|
626
|
+
|
|
627
|
+ Long countTest = template.queryForObject("select count(*) from relation_test", Long.class);
|
|
628
|
+ log.error("count test {}", countTest);
|
|
629
|
+
|
|
630
|
+ Long count = template.queryForObject("select count(*) from relation", Long.class);
|
|
631
|
+ log.error("count {}", count);
|
|
632
|
+
|
|
633
|
+ List<List<String>> result = template.query("" +
|
|
634
|
+ "WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, lvl, path) " +
|
|
635
|
+ " AS (SELECT from_id, " +
|
|
636
|
+ " from_type, " +
|
|
637
|
+ " to_id, " +
|
|
638
|
+ " to_type, " +
|
|
639
|
+ " 1 as lvl, " +
|
|
640
|
+ " ARRAY [from_id] as path " +
|
|
641
|
+ " FROM relation_test r " +
|
|
642
|
+ " WHERE from_id = '11111111-0f19-11ec-ba23-e981fc95500d' " +
|
|
643
|
+ " and from_type = 'TENANT' " +
|
|
644
|
+ " and relation_type_group = 'COMMON' " +
|
|
645
|
+ " GROUP BY r.from_id, r.from_type, r.to_id, r.to_type , 1, ARRAY [from_id] " +
|
|
646
|
+ " UNION ALL " +
|
|
647
|
+ " SELECT r.from_id, " +
|
|
648
|
+ " r.from_type, " +
|
|
649
|
+ " r.to_id, " +
|
|
650
|
+ " r.to_type, " +
|
|
651
|
+ " (re.lvl + 1) as lvl, " +
|
|
652
|
+ " (re.path || ARRAY [r.from_id]) as path " +
|
|
653
|
+ " FROM relation_test r " +
|
|
654
|
+ " INNER JOIN related_entities re " +
|
|
655
|
+ " ON r.from_id = re.to_id and " +
|
|
656
|
+ " r.from_type = re.to_type and " +
|
|
657
|
+ " relation_type_group = 'COMMON' and " +
|
|
658
|
+ " r.from_id NOT IN (SELECT * FROM unnest(re.path)) and " +
|
|
659
|
+ " re.lvl <= 7 " +
|
|
660
|
+ " GROUP BY r.from_id, r.from_type, r.to_id, r.to_type, " +
|
|
661
|
+ " (re.lvl + 1), (re.path || ARRAY [r.from_id])) " +
|
|
662
|
+ " " +
|
|
663
|
+ " SELECT lvl, from_id, from_type, to_id, to_type, path " + //to_id IN (SELECT * FROM unnest(path)) as is_present,
|
|
664
|
+ " from related_entities r_int " +
|
|
665
|
+ " " +
|
|
666
|
+ " ",
|
|
667
|
+ getListResultSetExtractor());
|
|
668
|
+
|
|
669
|
+ log.error("result {}", result.size() - 1);
|
|
670
|
+ AtomicInteger counter = new AtomicInteger();
|
|
671
|
+ result.forEach(r -> System.out.printf("%s %s\n", counter.incrementAndGet(), r.toString()));
|
|
672
|
+ log.error("end");
|
|
673
|
+
|
|
674
|
+ log.error("query 1 (expected true): {}", template.queryForObject(
|
|
675
|
+ "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3'), UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')])) ",
|
|
676
|
+ String.class));
|
|
677
|
+ log.error("query 1.1 (expected true): {}", template.queryForObject(
|
|
678
|
+ "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')] || ARRAY[UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')] )) ",
|
|
679
|
+ String.class));
|
|
680
|
+ log.error("query 2 (expected true): {}", template.queryForObject(
|
|
681
|
+ "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')) ",
|
|
682
|
+ String.class));
|
|
683
|
+// log.error("query true3: ", template.queryForObject(
|
|
684
|
+// "SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3' IN (SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')) ",
|
|
685
|
+// String.class));
|
|
686
|
+ log.error("query 3 (expected true): {} ", template.queryForObject(
|
|
687
|
+ "SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3' NOT IN (SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3') ",
|
|
688
|
+ String.class));
|
|
689
|
+
|
|
690
|
+ List<List<String>> result2 = template.query("SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3'), UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')]) "
|
|
691
|
+ , getListResultSetExtractor());
|
|
692
|
+ log.error("result2 {}", result2.size() - 1);
|
|
693
|
+ AtomicInteger counter2 = new AtomicInteger();
|
|
694
|
+ result2.forEach(r -> System.out.printf("%s %s\n", counter2.incrementAndGet(), r.toString()));
|
|
695
|
+ log.error("end2");
|
|
696
|
+
|
|
697
|
+ }
|
|
698
|
+
|
|
699
|
+ @NotNull
|
|
700
|
+ private ResultSetExtractor<List<List<String>>> getListResultSetExtractor() {
|
|
701
|
+ return rs -> {
|
|
702
|
+ List<List<String>> list = new ArrayList<>();
|
|
703
|
+ final int columnCount = rs.getMetaData().getColumnCount();
|
|
704
|
+ List<String> columns = new ArrayList<>(columnCount);
|
|
705
|
+ for (int i = 1; i <= columnCount; i++) {
|
|
706
|
+ columns.add(rs.getMetaData().getColumnName(i));
|
|
707
|
+ }
|
|
708
|
+ list.add(columns);
|
|
709
|
+ while (rs.next()) {
|
|
710
|
+ List<String> data = new ArrayList<>(columnCount);
|
|
711
|
+ for (int i = 1; i <= columnCount; i++) {
|
|
712
|
+ data.add(rs.getString(i));
|
|
713
|
+ }
|
|
714
|
+ list.add(data);
|
|
715
|
+ }
|
|
716
|
+ return list;
|
|
717
|
+ };
|
580
|
718
|
}
|
581
|
719
|
|
582
|
720
|
/*
|
...
|
...
|
@@ -642,9 +780,14 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
642
|
780
|
}
|
643
|
781
|
|
644
|
782
|
void createOneToManyRelations(TenantId tenantId, String type, EntityId from, List<EntityId> toIds) {
|
645
|
|
- toIds.forEach(toId -> relationService.saveRelation(tenantId, new EntityRelation(from, toId, type, RelationTypeGroup.COMMON)));
|
|
783
|
+ toIds.forEach(toId -> createRelation(tenantId, type, from, toId));
|
|
784
|
+ }
|
|
785
|
+
|
|
786
|
+ void createRelation(TenantId tenantId, String type, EntityId from, EntityId toId) {
|
|
787
|
+ relationService.saveRelation(tenantId, new EntityRelation(from, toId, type, RelationTypeGroup.COMMON));
|
646
|
788
|
}
|
647
|
789
|
|
|
790
|
+
|
648
|
791
|
@Test
|
649
|
792
|
public void testSimpleFindEntityDataByQuery() throws InterruptedException {
|
650
|
793
|
List<Device> devices = new ArrayList<>();
|
...
|
...
|
|