new relation path query
This commit is contained in:
parent
2c06aa475f
commit
909497703a
@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.common.util.ThingsBoardExecutors;
|
||||
import org.thingsboard.server.common.data.cf.configuration.Argument;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.RelationPathQueryDynamicSourceConfiguration;
|
||||
import org.thingsboard.server.common.data.cf.configuration.RelationQueryDynamicSourceConfiguration;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
@ -178,6 +179,11 @@ public abstract class AbstractCalculatedFieldProcessingService {
|
||||
yield Futures.transform(relationService.findByQuery(tenantId, configuration.toEntityRelationsQuery(entityId)),
|
||||
configuration::resolveEntityIds, calculatedFieldCallbackExecutor);
|
||||
}
|
||||
case RELATION_PATH_QUERY -> {
|
||||
var configuration = (RelationPathQueryDynamicSourceConfiguration) refDynamicSourceConfiguration;
|
||||
yield Futures.transform(relationService.findByRelationPathQueryAsync(tenantId, configuration.toRelationPathQuery(entityId)),
|
||||
configuration::resolveEntityIds, calculatedFieldCallbackExecutor);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
import org.thingsboard.server.common.data.rule.RuleChainType;
|
||||
@ -83,6 +84,8 @@ public interface RelationService {
|
||||
|
||||
List<EntityRelation> findRuleNodeToRuleChainRelations(TenantId tenantId, RuleChainType ruleChainType, int limit);
|
||||
|
||||
ListenableFuture<List<EntityRelation>> findByRelationPathQueryAsync(TenantId tenantId, EntityRelationPathQuery relationPathQuery);
|
||||
|
||||
// TODO: This method may be useful for some validations in the future
|
||||
// ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
|
||||
|
||||
|
||||
@ -17,6 +17,6 @@ package org.thingsboard.server.common.data.cf.configuration;
|
||||
|
||||
public enum CFArgumentDynamicSourceType {
|
||||
|
||||
RELATION_QUERY
|
||||
RELATION_QUERY, RELATION_PATH_QUERY
|
||||
|
||||
}
|
||||
|
||||
@ -26,7 +26,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
property = "type"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = RelationQueryDynamicSourceConfiguration.class, name = "RELATION_QUERY")
|
||||
@JsonSubTypes.Type(value = RelationQueryDynamicSourceConfiguration.class, name = "RELATION_QUERY"),
|
||||
@JsonSubTypes.Type(value = RelationPathQueryDynamicSourceConfiguration.class, name = "RELATION_PATH_QUERY")
|
||||
})
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public interface CfArgumentDynamicSourceConfiguration {
|
||||
|
||||
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright © 2016-2025 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.cf.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
|
||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
import org.thingsboard.server.common.data.relation.RelationPathLevel;
|
||||
import org.thingsboard.server.common.data.util.CollectionsUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
@Data
|
||||
public class RelationPathQueryDynamicSourceConfiguration implements CfArgumentDynamicSourceConfiguration, RelationQueryBased {
|
||||
|
||||
private List<RelationPathLevel> levels;
|
||||
|
||||
@Override
|
||||
public CFArgumentDynamicSourceType getType() {
|
||||
return CFArgumentDynamicSourceType.RELATION_PATH_QUERY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
if (CollectionsUtil.isEmpty(levels)) {
|
||||
throw new IllegalArgumentException("At least one relation level must be specified!");
|
||||
}
|
||||
levels.forEach(RelationPathLevel::validate);
|
||||
}
|
||||
|
||||
public List<EntityId> resolveEntityIds(List<EntityRelation> relations) {
|
||||
EntitySearchDirection lastLevelDirection = getLastLevel().direction();
|
||||
return switch (lastLevelDirection) {
|
||||
case FROM -> relations.stream().map(EntityRelation::getTo).toList();
|
||||
case TO -> relations.stream().map(EntityRelation::getFrom).toList();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public int getMaxLevel() {
|
||||
return levels != null ? levels.size() : 0;
|
||||
}
|
||||
|
||||
public EntityRelationPathQuery toRelationPathQuery(EntityId entityId) {
|
||||
return new EntityRelationPathQuery(entityId, levels);
|
||||
}
|
||||
|
||||
private RelationPathLevel getLastLevel() {
|
||||
if (CollectionsUtil.isEmpty(levels)) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return levels.get(levels.size() - 1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright © 2016-2025 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.cf.configuration;
|
||||
|
||||
public interface RelationQueryBased {
|
||||
|
||||
int getMaxLevel();
|
||||
|
||||
default void validateMaxRelationLevel(String argumentName, int maxAllowedRelationLevel) {
|
||||
if (getMaxLevel() > maxAllowedRelationLevel) {
|
||||
throw new IllegalArgumentException("Max relation level is greater than configured " +
|
||||
"maximum allowed relation level in tenant profile: " + maxAllowedRelationLevel + " for argument: " + argumentName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -29,7 +29,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class RelationQueryDynamicSourceConfiguration implements CfArgumentDynamicSourceConfiguration {
|
||||
public class RelationQueryDynamicSourceConfiguration implements CfArgumentDynamicSourceConfiguration, RelationQueryBased {
|
||||
|
||||
private int maxLevel;
|
||||
private boolean fetchLastLevelOnly;
|
||||
@ -59,13 +59,6 @@ public class RelationQueryDynamicSourceConfiguration implements CfArgumentDynami
|
||||
return maxLevel == 1;
|
||||
}
|
||||
|
||||
public void validateMaxRelationLevel(String argumentName, int maxAllowedRelationLevel) {
|
||||
if (maxLevel > maxAllowedRelationLevel) {
|
||||
throw new IllegalArgumentException("Max relation level is greater than configured " +
|
||||
"maximum allowed relation level in tenant profile: " + maxAllowedRelationLevel + " for argument: " + argumentName);
|
||||
}
|
||||
}
|
||||
|
||||
public EntityRelationsQuery toEntityRelationsQuery(EntityId rootEntityId) {
|
||||
if (isSimpleRelation()) {
|
||||
throw new IllegalArgumentException("Entity relations query can't be created for a simple relation!");
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright © 2016-2025 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.relation;
|
||||
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record EntityRelationPathQuery(EntityId rootEntityId, List<RelationPathLevel> levels) {
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright © 2016-2025 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.relation;
|
||||
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
|
||||
public record RelationPathLevel(EntitySearchDirection direction, String relationType) {
|
||||
|
||||
public void validate() {
|
||||
if (direction == null) {
|
||||
throw new IllegalArgumentException("Direction must be specified!");
|
||||
}
|
||||
if (StringUtils.isBlank(relationType)) {
|
||||
throw new IllegalArgumentException("Relation type must be specified!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,17 +32,21 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.thingsboard.common.util.ThingsBoardExecutors;
|
||||
import org.thingsboard.server.cache.TbTransactionalCache;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UUIDBased;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
|
||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
|
||||
import org.thingsboard.server.common.data.relation.RelationPathLevel;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
|
||||
import org.thingsboard.server.common.data.rule.RuleChainType;
|
||||
@ -495,6 +499,23 @@ public class BaseRelationService implements RelationService {
|
||||
return relationDao.findRuleNodeToRuleChainRelations(ruleChainType, limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<List<EntityRelation>> findByRelationPathQueryAsync(TenantId tenantId, EntityRelationPathQuery relationPathQuery) {
|
||||
log.trace("Executing findByRelationPathQuery, tenantId [{}], relationPathQuery {}", tenantId, relationPathQuery);
|
||||
validateId(tenantId, id -> "Invalid tenant id: " + id);
|
||||
validate(relationPathQuery);
|
||||
return executor.submit(() -> relationDao.findByRelationPathQuery(tenantId, relationPathQuery));
|
||||
}
|
||||
|
||||
private void validate(EntityRelationPathQuery relationPathQuery) {
|
||||
validateId((UUIDBased) relationPathQuery.rootEntityId(), id -> "Invalid root entity id: " + id);
|
||||
List<RelationPathLevel> levels = relationPathQuery.levels();
|
||||
if (CollectionUtils.isEmpty(levels)) {
|
||||
throw new DataValidationException("Relation path levels should be specified!");
|
||||
}
|
||||
levels.forEach(RelationPathLevel::validate);
|
||||
}
|
||||
|
||||
protected void validate(EntityRelation relation) {
|
||||
if (relation == null) {
|
||||
throw new DataValidationException("Relation type should be specified!");
|
||||
|
||||
@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
import org.thingsboard.server.common.data.rule.RuleChainType;
|
||||
|
||||
@ -71,4 +72,6 @@ public interface RelationDao {
|
||||
|
||||
List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit);
|
||||
|
||||
List<EntityRelation> findByRelationPathQuery(TenantId tenantId, EntityRelationPathQuery relationPathQuery);
|
||||
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedField;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
|
||||
import org.thingsboard.server.common.data.cf.configuration.RelationQueryDynamicSourceConfiguration;
|
||||
import org.thingsboard.server.common.data.cf.configuration.RelationQueryBased;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
|
||||
@ -98,10 +98,10 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
|
||||
if (!(calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration argumentsBasedCfg)) {
|
||||
return;
|
||||
}
|
||||
Map<String, RelationQueryDynamicSourceConfiguration> relationQueryBasedArguments = argumentsBasedCfg.getArguments().entrySet()
|
||||
Map<String, RelationQueryBased> relationQueryBasedArguments = argumentsBasedCfg.getArguments().entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue().hasDynamicSource())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> (RelationQueryDynamicSourceConfiguration) entry.getValue().getRefDynamicSourceConfiguration()));
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> (RelationQueryBased) entry.getValue().getRefDynamicSourceConfiguration()));
|
||||
if (relationQueryBasedArguments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -25,6 +25,9 @@ import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelationPathQuery;
|
||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
import org.thingsboard.server.common.data.relation.RelationPathLevel;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
import org.thingsboard.server.common.data.rule.RuleChainType;
|
||||
import org.thingsboard.server.dao.DaoUtil;
|
||||
@ -43,6 +46,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_FROM_ID_PROPERTY;
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_FROM_TYPE_PROPERTY;
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TABLE_NAME;
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TO_ID_PROPERTY;
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TO_TYPE_PROPERTY;
|
||||
import static org.thingsboard.server.dao.model.ModelConstants.RELATION_TYPE_GROUP_PROPERTY;
|
||||
@ -293,4 +297,99 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
|
||||
public List<EntityRelation> findRuleNodeToRuleChainRelations(RuleChainType ruleChainType, int limit) {
|
||||
return DaoUtil.convertDataList(relationRepository.findRuleNodeToRuleChainRelations(ruleChainType, PageRequest.of(0, limit)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EntityRelation> findByRelationPathQuery(TenantId tenantId, EntityRelationPathQuery query) {
|
||||
List<RelationPathLevel> levels = query.levels();
|
||||
if (levels == null || levels.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String sql = buildRelationPathSql(query);
|
||||
Object[] params = buildRelationPathParams(query);
|
||||
|
||||
log.info("[{}] relation path query: {}", tenantId, sql);
|
||||
|
||||
return jdbcTemplate.queryForList(sql, params).stream()
|
||||
.map(row -> {
|
||||
var entityRelation = new EntityRelation();
|
||||
var fromId = (UUID) row.get(RELATION_FROM_ID_PROPERTY);
|
||||
var fromType = (String) row.get(RELATION_FROM_TYPE_PROPERTY);
|
||||
var toId = (UUID) row.get(RELATION_TO_ID_PROPERTY);
|
||||
var toType = (String) row.get(RELATION_TO_TYPE_PROPERTY);
|
||||
var grp = (String) row.get(RELATION_TYPE_GROUP_PROPERTY);
|
||||
var type = (String) row.get(RELATION_TYPE_PROPERTY);
|
||||
var version = (Long) row.get(VERSION_COLUMN);
|
||||
|
||||
entityRelation.setFrom(EntityIdFactory.getByTypeAndUuid(fromType, fromId));
|
||||
entityRelation.setTo(EntityIdFactory.getByTypeAndUuid(toType, toId));
|
||||
entityRelation.setType(type);
|
||||
entityRelation.setTypeGroup(RelationTypeGroup.valueOf(grp));
|
||||
entityRelation.setVersion(version);
|
||||
return entityRelation;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Object[] buildRelationPathParams(EntityRelationPathQuery query) {
|
||||
final List<Object> params = new ArrayList<>();
|
||||
// seed
|
||||
params.add(query.rootEntityId().getId());
|
||||
params.add(query.rootEntityId().getEntityType().name());
|
||||
|
||||
// levels
|
||||
for (var lvl : query.levels()) {
|
||||
params.add(lvl.relationType());
|
||||
}
|
||||
return params.toArray();
|
||||
}
|
||||
|
||||
private static String buildRelationPathSql(EntityRelationPathQuery query) {
|
||||
List<RelationPathLevel> levels = query.levels();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append("WITH seed AS (\n")
|
||||
.append(" SELECT ?::uuid AS id, ?::varchar AS type\n")
|
||||
.append(")");
|
||||
|
||||
String prev = "seed";
|
||||
for (int i = 0; i < levels.size() - 1; i++) {
|
||||
RelationPathLevel lvl = levels.get(i);
|
||||
boolean down = lvl.direction() == EntitySearchDirection.FROM;
|
||||
|
||||
String cur = "lvl" + (i + 1);
|
||||
String joinCond = down
|
||||
? "r.from_id = p.id AND r.from_type = p.type"
|
||||
: "r.to_id = p.id AND r.to_type = p.type";
|
||||
String selectNext = down
|
||||
? "r.to_id AS id, r.to_type AS type"
|
||||
: "r.from_id AS id, r.from_type AS type";
|
||||
|
||||
sb.append(",\n").append(cur).append(" AS (\n")
|
||||
.append(" SELECT ").append(selectNext).append("\n")
|
||||
.append(" FROM ").append(RELATION_TABLE_NAME).append(" r\n")
|
||||
.append(" JOIN ").append(prev).append(" p ON ").append(joinCond).append("\n")
|
||||
.append(" WHERE r.relation_type_group = '").append(RelationTypeGroup.COMMON).append("'\n")
|
||||
.append(" AND r.relation_type = ?\n")
|
||||
.append(")");
|
||||
prev = cur;
|
||||
}
|
||||
|
||||
RelationPathLevel last = levels.get(levels.size() - 1);
|
||||
boolean lastDown = last.direction() == EntitySearchDirection.FROM;
|
||||
String prevForLast = (levels.size() == 1) ? "seed" : prev;
|
||||
String lastJoin = lastDown
|
||||
? "r.from_id = p.id AND r.from_type = p.type"
|
||||
: "r.to_id = p.id AND r.to_type = p.type";
|
||||
|
||||
sb.append("\n")
|
||||
.append("SELECT r.from_id, r.from_type, r.to_id, r.to_type,\n")
|
||||
.append(" r.relation_type_group, r.relation_type, r.version\n")
|
||||
.append("FROM ").append(RELATION_TABLE_NAME).append(" r\n")
|
||||
.append("JOIN ").append(prevForLast).append(" p ON ").append(lastJoin).append("\n")
|
||||
.append("WHERE r.relation_type_group = '").append(RelationTypeGroup.COMMON).append("'\n")
|
||||
.append(" AND r.relation_type = ?");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user