Browse Source

Merge pull request #5364 from smatvienko-tb/relation-query-improvements

Relation query improvements (handle many relations between two entities, break loops, limit max level)
pull/5552/head
Andrew Shvayka 5 years ago
committed by GitHub
parent
commit
37d3b424f9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      application/pom.xml
  2. 28
      common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java
  3. 22
      dao/pom.xml
  4. 39
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  5. 30
      dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java
  6. 64
      dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java
  7. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
  8. 200
      dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
  9. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java
  10. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java
  11. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java
  12. 33
      dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java
  13. 8
      dao/src/test/java/org/thingsboard/server/dao/service/psql/EntityServicePostgreSqlTest.java
  14. 57
      dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepositoryTest.java
  15. 47
      dao/src/test/resources/psql-test.properties
  16. 2
      dao/src/test/resources/sql/system-test-psql.sql
  17. 11
      pom.xml

1
application/pom.xml

@ -360,6 +360,7 @@
</systemPropertyVariables>
<excludes>
<exclude>**/sql/*Test.java</exclude>
<exclude>**/psql/*Test.java</exclude>
<exclude>**/nosql/*Test.java</exclude>
</excludes>
<includes>

28
common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.id;
import org.junit.Assert;
import org.junit.Test;
public class EntityIdTest {
@Test
public void givenConstantNullUuid_whenCompare_thenToStringEqualsPredefinedUuid() {
Assert.assertEquals("13814000-1dd2-11b2-8080-808080808080", EntityId.NULL_UUID.toString());
}
}

22
dao/pom.xml

@ -201,6 +201,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
@ -211,6 +216,16 @@
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
@ -239,7 +254,14 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>${surfire.version}</version>
<configuration>
<excludes>
<exclude>**/sql/*Test.java</exclude>
<exclude>**/sql/*/*Test.java</exclude>
<exclude>**/psql/*Test.java</exclude>
<exclude>**/nosql/*Test.java</exclude>
</excludes>
<includes>
<include>**/*Test.java</include>
<include>**/*TestSuite.java</include>
</includes>
</configuration>

39
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java

@ -223,6 +223,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, aus.entity_id, " +
"coalesce((select title from tenant where id = aus.entity_id), (select title from customer where id = aus.entity_id)) as name " +
"from api_usage_state as aus)";
static final int MAX_LEVEL_DEFAULT = 50; //This value has to be reasonable small to prevent infinite recursion as early as possible
static {
entityTableMap.put(EntityType.ASSET, "asset");
@ -239,18 +240,30 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{
EntityType.TENANT, EntityType.CUSTOMER, EntityType.USER, EntityType.DASHBOARD, EntityType.ASSET, EntityType.DEVICE, EntityType.ENTITY_VIEW};
private static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" +
" SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" +
" FROM relation" +
private static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, lvl, path) AS (" +
" SELECT from_id, from_type, to_id, to_type," +
" 1 as lvl," +
" ARRAY[$in_id] as path" + // initial path
" FROM relation " +
" WHERE $in_id = :relation_root_id and $in_type = :relation_root_type and relation_type_group = 'COMMON'" +
" GROUP BY from_id, from_type, to_id, to_type, lvl, path" +
" UNION ALL" +
" SELECT r.from_id, r.from_type, r.to_id, r.to_type, r.relation_type, lvl + 1" +
" SELECT r.from_id, r.from_type, r.to_id, r.to_type," +
" (re.lvl + 1) as lvl, " +
" (re.path || ARRAY[r.$in_id]) as path" +
" FROM relation r" +
" INNER JOIN related_entities re ON" +
" r.$in_id = re.$out_id and r.$in_type = re.$out_type and" +
" relation_type_group = 'COMMON' %s)" +
" SELECT re.$out_id entity_id, re.$out_type entity_type, max(re.lvl) lvl" +
" from related_entities re" +
" relation_type_group = 'COMMON' " +
" AND r.$in_id NOT IN (SELECT * FROM unnest(re.path)) " +
" %s" +
" GROUP BY r.from_id, r.from_type, r.to_id, r.to_type, (re.lvl + 1), (re.path || ARRAY[r.$in_id])" +
" )" +
" SELECT re.$out_id entity_id, re.$out_type entity_type, max(r_int.lvl) lvl" +
" from related_entities r_int" +
" INNER JOIN relation re ON re.from_id = r_int.from_id AND re.from_type = r_int.from_type" +
" AND re.to_id = r_int.to_id AND re.to_type = r_int.to_type" +
" AND re.relation_type_group = 'COMMON'" +
" %s GROUP BY entity_id, entity_type) entity";
private static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from");
private static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to");
@ -580,7 +593,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
.append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type");
notExistsPart.append(")");
whereFilter += " and ( re.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")";
whereFilter += " and ( r_int.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")";
}
from = String.format(from, lvlFilter, whereFilter);
String query = "( " + selectFields + from + ")";
@ -659,7 +672,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
.append(whereFilter.toString().replaceAll("re\\.", "nr\\."));
notExistsPart.append(")");
whereFilter.append(" and ( re.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")");
whereFilter.append(" and ( r_int.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")");
}
from = String.format(from, lvlFilter, " WHERE " + whereFilter);
return "( " + selectFields + from + ")";
@ -693,8 +706,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
return whereFilter.toString();
}
private String getLvlFilter(int maxLevel) {
return maxLevel > 0 ? ("and lvl <= " + (maxLevel - 1)) : "";
String getLvlFilter(int maxLevel) {
return "and re.lvl <= " + (getMaxLevel(maxLevel) - 1);
}
int getMaxLevel(int maxLevel) {
return maxLevel > 0 ? maxLevel : MAX_LEVEL_DEFAULT;
}
private String getQueryTemplate(EntitySearchDirection direction) {

30
dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters;
import org.junit.runner.RunWith;
@RunWith(ClasspathSuite.class)
@ClassnameFilters({
"org.thingsboard.server.dao.service.psql.*SqlTest",
"org.thingsboard.server.dao.service.attributes.psql.*SqlTest",
"org.thingsboard.server.dao.service.event.psql.*SqlTest",
"org.thingsboard.server.dao.service.timeseries.psql.*SqlTest"
})
public class PostgreSqlDaoServiceTestSuite {
}

64
dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java

@ -0,0 +1,64 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
@Slf4j
public class PostgreSqlInitializer {
private static final List<String> sqlFiles = List.of(
"sql/schema-ts-psql.sql",
"sql/schema-entities.sql",
"sql/schema-entities-idx.sql",
"sql/system-data.sql",
"sql/system-test-psql.sql");
private static final String dropAllTablesSqlFile = "sql/psql/drop-all-tables.sql";
public static void initDb(Connection conn) {
cleanUpDb(conn);
log.info("initialize Postgres DB...");
try {
for (String sqlFile : sqlFiles) {
URL sqlFileUrl = Resources.getResource(sqlFile);
String sql = Resources.toString(sqlFileUrl, Charsets.UTF_8);
conn.createStatement().execute(sql);
}
} catch (IOException | SQLException e) {
throw new RuntimeException("Unable to init the Postgres database. Reason: " + e.getMessage(), e);
}
log.info("Postgres DB is initialized!");
}
private static void cleanUpDb(Connection conn) {
log.info("clean up Postgres DB...");
try {
URL dropAllTableSqlFileUrl = Resources.getResource(dropAllTablesSqlFile);
String dropAllTablesSql = Resources.toString(dropAllTableSqlFileUrl, Charsets.UTF_8);
conn.createStatement().execute(dropAllTablesSql);
} catch (IOException | SQLException e) {
throw new RuntimeException("Unable to clean up the Postgres database. Reason: " + e.getMessage(), e);
}
}
}

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java

@ -47,7 +47,7 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
public abstract class BaseDeviceProfileServiceTest extends AbstractServiceTest {
private IdComparator<DeviceProfile> idComparator = new IdComparator<>();
private IdComparator<DeviceProfileInfo> deviceProfileInfoIdComparator = new IdComparator<>();

200
dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java

@ -17,14 +17,17 @@ package org.thingsboard.server.dao.service;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@ -69,6 +72,7 @@ import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sql.relation.RelationRepository;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.ArrayList;
@ -82,9 +86,13 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
@Slf4j
public abstract class BaseEntityServiceTest extends AbstractServiceTest {
static final int ENTITY_COUNT = 5;
@Autowired
private AttributesService attributesService;
@ -96,6 +104,9 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
@Autowired
private JdbcTemplate template;
@Autowired
private RelationRepository relationRepository;
@Before
public void before() {
Tenant tenant = new Tenant();
@ -110,7 +121,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
tenantService.deleteTenant(tenantId);
}
@Test
public void testCountEntitiesByQuery() throws InterruptedException {
List<Device> devices = new ArrayList<>();
@ -154,12 +165,12 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
Assert.assertEquals(0, count);
}
@Test
public void testCountHierarchicalEntitiesByQuery() throws InterruptedException {
List<Asset> assets = new ArrayList<>();
List<Device> devices = new ArrayList<>();
createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
RelationsQueryFilter filter = new RelationsQueryFilter();
filter.setRootEntity(tenantId);
@ -168,7 +179,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
EntityCountQuery countQuery = new EntityCountQuery(filter);
long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(30, count);
Assert.assertEquals(31, count); //due to the loop relations in hierarchy, the TenantId included in total count (1*Tenant + 5*Asset + 5*5*Devices = 31)
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
@ -304,11 +315,25 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
@Test
public void testHierarchicalFindEntityDataWithAttributesByQuery() throws ExecutionException, InterruptedException {
doTestHierarchicalFindEntityDataWithAttributesByQuery(0, false);
}
@Test
public void testHierarchicalFindEntityDataWithAttributesByQueryWithLevel() throws ExecutionException, InterruptedException {
doTestHierarchicalFindEntityDataWithAttributesByQuery(2, false);
}
@Test
public void testHierarchicalFindEntityDataWithAttributesByQueryWithLastLevelOnly() throws ExecutionException, InterruptedException {
doTestHierarchicalFindEntityDataWithAttributesByQuery(2, true);
}
private void doTestHierarchicalFindEntityDataWithAttributesByQuery(final int maxLevel, final boolean fetchLastLevelOnly) throws ExecutionException, InterruptedException {
List<Asset> assets = new ArrayList<>();
List<Device> devices = new ArrayList<>();
List<Long> temperatures = new ArrayList<>();
List<Long> highTemperatures = new ArrayList<>();
createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) {
@ -321,6 +346,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
filter.setRootEntity(tenantId);
filter.setDirection(EntitySearchDirection.FROM);
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
filter.setMaxLevel(maxLevel);
filter.setFetchLastLevelOnly(fetchLastLevelOnly);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
@ -373,14 +400,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId);
}
@Test
public void testHierarchicalFindDevicesWithAttributesByQuery() throws ExecutionException, InterruptedException {
List<Asset> assets = new ArrayList<>();
List<Device> devices = new ArrayList<>();
List<Long> temperatures = new ArrayList<>();
List<Long> highTemperatures = new ArrayList<>();
createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) {
@ -393,6 +419,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
filter.setRootEntity(tenantId);
filter.setDirection(EntitySearchDirection.FROM);
filter.setRelationType("Contains");
filter.setMaxLevel(2);
filter.setFetchLastLevelOnly(true);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
@ -446,14 +474,14 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId);
}
@Test
public void testHierarchicalFindAssetsWithAttributesByQuery() throws ExecutionException, InterruptedException {
List<Asset> assets = new ArrayList<>();
List<Device> devices = new ArrayList<>();
List<Long> consumptions = new ArrayList<>();
List<Long> highConsumptions = new ArrayList<>();
createTestHierarchy(assets, devices, consumptions, highConsumptions, new ArrayList<>(), new ArrayList<>());
createTestHierarchy(tenantId, assets, devices, consumptions, highConsumptions, new ArrayList<>(), new ArrayList<>());
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
for (int i = 0; i < assets.size(); i++) {
@ -518,8 +546,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId);
}
private void createTestHierarchy(List<Asset> assets, List<Device> devices, List<Long> consumptions, List<Long> highConsumptions, List<Long> temperatures, List<Long> highTemperatures) throws InterruptedException {
for (int i = 0; i < 5; i++) {
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 {
for (int i = 0; i < ENTITY_COUNT; i++) {
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName("Asset" + i);
@ -529,18 +557,19 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
//TO make sure devices have different created time
Thread.sleep(1);
assets.add(asset);
EntityRelation er = new EntityRelation();
er.setFrom(tenantId);
er.setTo(asset.getId());
er.setType("Manages");
er.setTypeGroup(RelationTypeGroup.COMMON);
relationService.saveRelation(tenantId, er);
createRelation(tenantId, "Manages", tenantId, asset.getId());
long consumption = (long) (Math.random() * 100);
consumptions.add(consumption);
if (consumption > 50) {
highConsumptions.add(consumption);
}
for (int j = 0; j < 5; j++) {
//tenant -> asset : one-to-one but many edges
for (int n = 0; n < ENTITY_COUNT; n++) {
createRelation(tenantId, "UseCase-" + n, tenantId, asset.getId());
}
for (int j = 0; j < ENTITY_COUNT; j++) {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("A" + i + "Device" + j);
@ -550,22 +579,125 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
//TO make sure devices have different created time
Thread.sleep(1);
devices.add(device);
er = new EntityRelation();
er.setFrom(asset.getId());
er.setTo(device.getId());
er.setType("Contains");
er.setTypeGroup(RelationTypeGroup.COMMON);
relationService.saveRelation(tenantId, er);
createRelation(tenantId, "Contains", asset.getId(), device.getId());
long temperature = (long) (Math.random() * 100);
temperatures.add(temperature);
if (temperature > 45) {
highTemperatures.add(temperature);
}
//asset -> device : one-to-one but many edges
for (int n = 0; n < ENTITY_COUNT; n++) {
createRelation(tenantId, "UseCase-" + n, asset.getId(), device.getId());
}
}
}
//asset -> device one-to-many shared with other assets
for (int n = 0; n < devices.size(); n = n + ENTITY_COUNT) {
createRelation(tenantId, "SharedWithAsset0", assets.get(0).getId(), devices.get(n).getId());
}
createManyCustomRelationsBetweenTwoNodes(tenantId, "UseCase", assets, devices);
createHorizontalRingRelations(tenantId, "Ring(Loop)-Ast", assets);
createLoopRelations(tenantId, "Loop-Tnt-Ast-Dev", tenantId, assets.get(0).getId(), devices.get(0).getId());
createLoopRelations(tenantId, "Loop-Tnt-Ast", tenantId, assets.get(1).getId());
createLoopRelations(tenantId, "Loop-Ast-Tnt-Ast", assets.get(2).getId(), tenantId, assets.get(3).getId());
//printAllRelations();
}
private ResultSetExtractor<List<List<String>>> getListResultSetExtractor() {
return rs -> {
List<List<String>> list = new ArrayList<>();
final int columnCount = rs.getMetaData().getColumnCount();
List<String> columns = new ArrayList<>(columnCount);
for (int i = 1; i <= columnCount; i++) {
columns.add(rs.getMetaData().getColumnName(i));
}
list.add(columns);
while (rs.next()) {
List<String> data = new ArrayList<>(columnCount);
for (int i = 1; i <= columnCount; i++) {
data.add(rs.getString(i));
}
list.add(data);
}
return list;
};
}
/*
* This useful to reproduce exact data in the PostgreSQL and play around with pgadmin query and analyze tool
* */
private void printAllRelations() {
System.out.println("" +
"DO\n" +
"$$\n" +
" DECLARE\n" +
" someint integer;\n" +
" BEGIN\n" +
" DROP TABLE IF EXISTS relation_test;\n" +
" CREATE TABLE IF NOT EXISTS relation_test\n" +
" (\n" +
" from_id uuid,\n" +
" from_type varchar(255),\n" +
" to_id uuid,\n" +
" to_type varchar(255),\n" +
" relation_type_group varchar(255),\n" +
" relation_type varchar(255),\n" +
" additional_info varchar,\n" +
" CONSTRAINT relation_test_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)\n" +
" );");
relationRepository.findAll().forEach(r ->
System.out.printf("INSERT INTO relation_test (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" +
" VALUES (%s, %s, %s, %s, %s, %s, %s);\n",
quote(r.getFromId()), quote(r.getFromType()), quote(r.getToId()), quote(r.getToType()),
quote(r.getRelationTypeGroup()), quote(r.getRelationType()), quote(r.getAdditionalInfo()))
);
System.out.println("" +
" END\n" +
"$$;");
}
private String quote(Object s) {
return s == null ? null : "'" + s + "'";
}
void createLoopRelations(TenantId tenantId, String type, EntityId... ids) {
assertThat("ids lenght", ids.length, Matchers.greaterThanOrEqualTo(1));
//chain all from the head to the tail
for (int i = 1; i < ids.length; i++) {
relationService.saveRelation(tenantId, new EntityRelation(ids[i - 1], ids[i], type, RelationTypeGroup.COMMON));
}
//chain tail -> head
relationService.saveRelation(tenantId, new EntityRelation(ids[ids.length - 1], ids[0], type, RelationTypeGroup.COMMON));
}
void createHorizontalRingRelations(TenantId tenantId, String type, List<Asset> assets) {
createLoopRelations(tenantId, type, assets.stream().map(Asset::getId).toArray(EntityId[]::new));
}
void createManyCustomRelationsBetweenTwoNodes(TenantId tenantId, String type, List<Asset> assets, List<Device> devices) {
for (int i = 1; i <= 5; i++) {
final String typeI = type + i;
createOneToManyRelations(tenantId, typeI, tenantId, assets.stream().map(Asset::getId).collect(Collectors.toList()));
assets.forEach(asset ->
createOneToManyRelations(tenantId, typeI, asset.getId(), devices.stream().map(Device::getId).collect(Collectors.toList())));
}
}
void createOneToManyRelations(TenantId tenantId, String type, EntityId from, List<EntityId> toIds) {
toIds.forEach(toId -> createRelation(tenantId, type, from, toId));
}
void createRelation(TenantId tenantId, String type, EntityId from, EntityId toId) {
relationService.saveRelation(tenantId, new EntityRelation(from, toId, type, RelationTypeGroup.COMMON));
}
@Test
public void testSimpleFindEntityDataByQuery() throws InterruptedException {
List<Device> devices = new ArrayList<>();
@ -871,7 +1003,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
}
@Test
public void testBuildNumericPredicateQueryOperations() throws ExecutionException, InterruptedException{
public void testBuildNumericPredicateQueryOperations() throws ExecutionException, InterruptedException {
List<Device> devices = new ArrayList<>();
List<Long> temperatures = new ArrayList<>();
@ -1031,7 +1163,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId);
}
@Test
public void testFindEntityDataByQueryWithTimeseries() throws ExecutionException, InterruptedException {
@ -1122,7 +1254,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
}
@Test
public void testBuildStringPredicateQueryOperations() throws ExecutionException, InterruptedException{
public void testBuildStringPredicateQueryOperations() throws ExecutionException, InterruptedException {
List<Device> devices = new ArrayList<>();
List<String> attributeStrings = new ArrayList<>();
@ -1142,11 +1274,11 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
devices.add(deviceService.saveDevice(device));
//TO make sure devices have different created time
Thread.sleep(1);
List<StringFilterPredicate.StringOperation> operationValues= Arrays.asList(StringFilterPredicate.StringOperation.values());
List<StringFilterPredicate.StringOperation> operationValues = Arrays.asList(StringFilterPredicate.StringOperation.values());
StringFilterPredicate.StringOperation operation = operationValues.get(new Random().nextInt(operationValues.size()));
String operationName = operation.name();
attributeStrings.add(operationName);
switch(operation){
switch (operation) {
case EQUAL:
equalStrings.add(operationName);
notContainsStrings.add(operationName);
@ -1302,7 +1434,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
}
@Test
public void testBuildStringPredicateQueryOperationsForEntityType() throws ExecutionException, InterruptedException{
public void testBuildStringPredicateQueryOperationsForEntityType() throws ExecutionException, InterruptedException {
List<Device> devices = new ArrayList<>();
@ -1419,7 +1551,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
}
@Test
public void testBuildSimplePredicateQueryOperations() throws InterruptedException{
public void testBuildSimplePredicateQueryOperations() throws InterruptedException {
List<Device> devices = new ArrayList<>();
@ -1492,7 +1624,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
return loadedEntities;
}
private List<KeyFilter> createStringKeyFilters(String key, EntityKeyType keyType, StringFilterPredicate.StringOperation operation, String value){
private List<KeyFilter> createStringKeyFilters(String key, EntityKeyType keyType, StringFilterPredicate.StringOperation operation, String value) {
KeyFilter filter = new KeyFilter();
filter.setKey(new EntityKey(keyType, key));
StringFilterPredicate predicate = new StringFilterPredicate();
@ -1503,7 +1635,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
return Collections.singletonList(filter);
}
private KeyFilter createNumericKeyFilter(String key, EntityKeyType keyType, NumericFilterPredicate.NumericOperation operation, double value){
private KeyFilter createNumericKeyFilter(String key, EntityKeyType keyType, NumericFilterPredicate.NumericOperation operation, double value) {
KeyFilter filter = new KeyFilter();
filter.setKey(new EntityKey(keyType, key));
NumericFilterPredicate predicate = new NumericFilterPredicate();

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java

@ -31,7 +31,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import java.util.Arrays;
import java.util.UUID;
public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
public abstract class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
@Autowired
protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService;

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java

@ -43,7 +43,7 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public class BaseOAuth2ServiceTest extends AbstractServiceTest {
public abstract class BaseOAuth2ServiceTest extends AbstractServiceTest {
private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, Collections.emptyList());
@Autowired

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java

@ -34,7 +34,7 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class BaseTenantProfileServiceTest extends AbstractServiceTest {
public abstract class BaseTenantProfileServiceTest extends AbstractServiceTest {
private IdComparator<TenantProfile> idComparator = new IdComparator<>();
private IdComparator<EntityInfo> tenantProfileInfoIdComparator = new IdComparator<>();

33
dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import org.springframework.test.context.TestPropertySource;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@TestPropertySource(locations = {"classpath:application-test.properties", "classpath:psql-test.properties"})
public @interface DaoPostgreSqlTest {
}

8
dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceSqlTest.java → dao/src/test/java/org/thingsboard/server/dao/service/psql/EntityServicePostgreSqlTest.java

@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service.sql;
package org.thingsboard.server.dao.service.psql;
import org.thingsboard.server.dao.service.BaseEntityServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.service.DaoPostgreSqlTest;
@DaoSqlTest
public class EntityServiceSqlTest extends BaseEntityServiceTest {
@DaoPostgreSqlTest
public class EntityServicePostgreSqlTest extends BaseEntityServiceTest {
}

57
dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepositoryTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.query;
import org.junit.Test;
import org.thingsboard.server.common.data.id.CustomerId;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock;
public class DefaultEntityQueryRepositoryTest {
/*
* This value has to be reasonable small to prevent infinite recursion as early as possible
* */
@Test
public void givenDefaultMaxLevel_whenStaticConstant_thenEqualsTo() {
assertThat(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT, equalTo(10));
}
@Test
public void givenMaxLevelZeroOrNegative_whenGetMaxLevel_thenReturnDefaultMaxLevel() {
DefaultEntityQueryRepository repo = mock(DefaultEntityQueryRepository.class);
willCallRealMethod().given(repo).getMaxLevel(anyInt());
assertThat(repo.getMaxLevel(0), equalTo(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT));
assertThat(repo.getMaxLevel(-1), equalTo(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT));
assertThat(repo.getMaxLevel(-2), equalTo(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT));
assertThat(repo.getMaxLevel(Integer.MIN_VALUE), equalTo(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT));
}
@Test
public void givenMaxLevelPositive_whenGetMaxLevel_thenValueTheSame() {
DefaultEntityQueryRepository repo = mock(DefaultEntityQueryRepository.class);
willCallRealMethod().given(repo).getMaxLevel(anyInt());
assertThat(repo.getMaxLevel(1), equalTo(1));
assertThat(repo.getMaxLevel(2), equalTo(2));
assertThat(repo.getMaxLevel(Integer.MAX_VALUE), equalTo(Integer.MAX_VALUE));
}
}

47
dao/src/test/resources/psql-test.properties

@ -0,0 +1,47 @@
database.ts.type=sql
database.ts_latest.type=sql
sql.ts_inserts_executor_type=fixed
sql.ts_inserts_fixed_thread_pool_size=200
sql.ts_key_value_partitioning=MONTHS
#
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.order_by.default_null_ordering=last
spring.jpa.properties.hibernate.jdbc.log.warnings=false
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:12.8:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
#org.postgresql.Driver
spring.datasource.hikari.maximumPoolSize=50
service.type=monolith
#database.ts.type=timescale
#database.ts.type=sql
#database.entities.type=sql
#
#sql.ts_inserts_executor_type=fixed
#sql.ts_inserts_fixed_thread_pool_size=200
#sql.ts_key_value_partitioning=MONTHS
#
#spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
#spring.jpa.show-sql=false
#spring.jpa.hibernate.ddl-auto=none
#spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
#
#spring.datasource.username=postgres
#spring.datasource.password=postgres
#spring.datasource.url=jdbc:postgresql://localhost:5432/sqltest
#spring.datasource.driverClassName=org.postgresql.Driver
#spring.datasource.hikari.maximumPoolSize = 50
queue.core.pack-processing-timeout=3000
queue.rule-engine.pack-processing-timeout=3000
queue.rule-engine.queues[0].name=Main
queue.rule-engine.queues[0].topic=tb_rule_engine.main
queue.rule-engine.queues[0].poll-interval=25
queue.rule-engine.queues[0].partitions=3
queue.rule-engine.queues[0].pack-processing-timeout=3000
queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES
queue.rule-engine.queues[0].submit-strategy.type=BURST
sql.log_entity_queries=true

2
dao/src/test/resources/sql/system-test-psql.sql

@ -0,0 +1,2 @@
--PostgreSQL specific truncate to fit constraints
TRUNCATE TABLE device_credentials, device, device_profile, rule_node_state, rule_node, rule_chain;

11
pom.xml

@ -1643,6 +1643,17 @@
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>jdbc</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>

Loading…
Cancel
Save