package org.onap.cps.spi.repository;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.persistence.Query;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.persistence.Query;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.cpspath.parser.CpsPathPrefixType;
import org.onap.cps.cpspath.parser.CpsPathQuery;
+import org.onap.cps.spi.PaginationOption;
import org.onap.cps.spi.entities.AnchorEntity;
import org.onap.cps.spi.entities.DataspaceEntity;
import org.onap.cps.spi.entities.FragmentEntity;
-import org.onap.cps.utils.JsonObjectMapper;
+import org.onap.cps.spi.exceptions.CpsPathException;
+import org.onap.cps.spi.utils.EscapeUtils;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Slf4j
@Component
public class FragmentQueryBuilder {
- private static final String REGEX_ABSOLUTE_PATH_PREFIX = "^";
- private static final String REGEX_DESCENDANT_PATH_PREFIX = "^.*\\/";
- private static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?$";
- private static final String REGEX_FOR_QUICK_FIND_WITH_DESCENDANTS = "(\\[@.*?])?(\\/.*)?$";
private static final AnchorEntity ACROSS_ALL_ANCHORS = null;
@PersistenceContext
private EntityManager entityManager;
- private final JsonObjectMapper jsonObjectMapper;
-
/**
* Create a sql query to retrieve by anchor(id) and cps path.
*
* @return a executable query object
*/
public Query getQueryForAnchorAndCpsPath(final AnchorEntity anchorEntity, final CpsPathQuery cpsPathQuery) {
- return getQueryForDataspaceOrAnchorAndCpsPath(anchorEntity.getDataspace(), anchorEntity, cpsPathQuery);
+ return getQueryForDataspaceOrAnchorAndCpsPath(anchorEntity.getDataspace(),
+ anchorEntity, cpsPathQuery, Collections.emptyList());
}
/**
* @return a executable query object
*/
public Query getQueryForDataspaceAndCpsPath(final DataspaceEntity dataspaceEntity,
- final CpsPathQuery cpsPathQuery) {
- return getQueryForDataspaceOrAnchorAndCpsPath(dataspaceEntity, ACROSS_ALL_ANCHORS, cpsPathQuery);
- }
-
- /**
- * Create a regular expression (string) for matching xpaths based on the given cps path query.
- *
- * @param cpsPathQuery the cps path query to determine the required regular expression
- * @return a string representing the required regular expression
- */
- public static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery) {
- final StringBuilder xpathRegexBuilder = getRegexStringBuilderWithPrefix(cpsPathQuery);
- xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX);
- return xpathRegexBuilder.toString();
+ final CpsPathQuery cpsPathQuery,
+ final List<Long> anchorIdsForPagination) {
+ return getQueryForDataspaceOrAnchorAndCpsPath(dataspaceEntity, ACROSS_ALL_ANCHORS,
+ cpsPathQuery, anchorIdsForPagination);
}
/**
- * Create a regular expression (string) for matching xpaths with (all) descendants
- * based on the given cps path query.
- *
- * @param cpsPathQuery the cps path query to determine the required regular expression
- * @return a string representing the required regular expression
+ * Get query for dataspace, cps path, page index and page size.
+ * @param dataspaceEntity data space entity
+ * @param cpsPathQuery cps path query
+ * @param paginationOption pagination option
+ * @return query for given dataspace, cps path and pagination parameters
*/
- public static String getXpathSqlRegexForQuickFindWithDescendants(final CpsPathQuery cpsPathQuery) {
- final StringBuilder xpathRegexBuilder = getRegexStringBuilderWithPrefix(cpsPathQuery);
- xpathRegexBuilder.append(REGEX_FOR_QUICK_FIND_WITH_DESCENDANTS);
- return xpathRegexBuilder.toString();
- }
-
- private Query getQueryForDataspaceOrAnchorAndCpsPath(final DataspaceEntity dataspaceEntity,
- final AnchorEntity anchorEntity,
- final CpsPathQuery cpsPathQuery) {
+ public Query getQueryForAnchorIdsForPagination(final DataspaceEntity dataspaceEntity,
+ final CpsPathQuery cpsPathQuery,
+ final PaginationOption paginationOption) {
final StringBuilder sqlStringBuilder = new StringBuilder();
final Map<String, Object> queryParameters = new HashMap<>();
-
- sqlStringBuilder.append("SELECT * FROM fragment WHERE ");
- addDataspaceOrAnchor(sqlStringBuilder, queryParameters, dataspaceEntity, anchorEntity);
+ sqlStringBuilder.append("SELECT distinct(fragment.anchor_id) FROM fragment "
+ + "JOIN anchor ON anchor.id = fragment.anchor_id WHERE dataspace_id = :dataspaceId");
+ queryParameters.put("dataspaceId", dataspaceEntity.getId());
addXpathSearch(cpsPathQuery, sqlStringBuilder, queryParameters);
addLeafConditions(cpsPathQuery, sqlStringBuilder);
addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
addContainsFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
+ if (PaginationOption.NO_PAGINATION != paginationOption) {
+ sqlStringBuilder.append(" ORDER BY fragment.anchor_id");
+ addPaginationCondition(sqlStringBuilder, queryParameters, paginationOption);
+ }
- final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class);
+ final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString());
setQueryParameters(query, queryParameters);
return query;
}
- private static void addDataspaceOrAnchor(final StringBuilder sqlStringBuilder,
- final Map<String, Object> queryParameters,
- final DataspaceEntity dataspaceEntity,
- final AnchorEntity anchorEntity) {
+ private Query getQueryForDataspaceOrAnchorAndCpsPath(final DataspaceEntity dataspaceEntity,
+ final AnchorEntity anchorEntity,
+ final CpsPathQuery cpsPathQuery,
+ final List<Long> anchorIdsForPagination) {
+ final StringBuilder sqlStringBuilder = new StringBuilder();
+ final Map<String, Object> queryParameters = new HashMap<>();
+
if (anchorEntity == ACROSS_ALL_ANCHORS) {
- sqlStringBuilder.append("dataspace_id = :dataspaceId");
+ sqlStringBuilder.append("SELECT fragment.* FROM fragment JOIN anchor ON anchor.id = fragment.anchor_id"
+ + " WHERE dataspace_id = :dataspaceId");
queryParameters.put("dataspaceId", dataspaceEntity.getId());
+ if (!anchorIdsForPagination.isEmpty()) {
+ sqlStringBuilder.append(" AND anchor_id IN (:anchorIdsForPagination)");
+ queryParameters.put("anchorIdsForPagination", anchorIdsForPagination);
+ }
} else {
- sqlStringBuilder.append("anchor_id = :anchorId");
+ sqlStringBuilder.append("SELECT * FROM fragment WHERE anchor_id = :anchorId");
queryParameters.put("anchorId", anchorEntity.getId());
}
+ addXpathSearch(cpsPathQuery, sqlStringBuilder, queryParameters);
+ addLeafConditions(cpsPathQuery, sqlStringBuilder);
+ addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
+ addContainsFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
+
+ final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class);
+ setQueryParameters(query, queryParameters);
+ return query;
}
private static void addXpathSearch(final CpsPathQuery cpsPathQuery,
final StringBuilder sqlStringBuilder,
final Map<String, Object> queryParameters) {
- sqlStringBuilder.append(" AND xpath ~ :xpathRegex");
- final String xpathRegex = getXpathSqlRegex(cpsPathQuery);
- queryParameters.put("xpathRegex", xpathRegex);
- }
-
- private static StringBuilder getRegexStringBuilderWithPrefix(final CpsPathQuery cpsPathQuery) {
- final StringBuilder xpathRegexBuilder = new StringBuilder();
+ sqlStringBuilder.append(" AND (xpath LIKE :escapedXpath OR "
+ + "(xpath LIKE :escapedXpath||'[@%]' AND xpath NOT LIKE :escapedXpath||'[@%]/%[@%]'))");
if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) {
- xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX);
- xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix()));
- return xpathRegexBuilder;
+ queryParameters.put("escapedXpath", EscapeUtils.escapeForSqlLike(cpsPathQuery.getXpathPrefix()));
+ } else {
+ queryParameters.put("escapedXpath", "%/" + EscapeUtils.escapeForSqlLike(cpsPathQuery.getDescendantName()));
}
- xpathRegexBuilder.append(REGEX_DESCENDANT_PATH_PREFIX);
- xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName()));
- return xpathRegexBuilder;
}
- private static String escapeXpath(final String xpath) {
- // See https://jira.onap.org/browse/CPS-500 for limitations of this basic escape mechanism
- return xpath.replace("[@", "\\[@");
+ private static void addPaginationCondition(final StringBuilder sqlStringBuilder,
+ final Map<String, Object> queryParameters,
+ final PaginationOption paginationOption) {
+ final Integer offset = (paginationOption.getPageIndex() - 1) * paginationOption.getPageSize();
+ sqlStringBuilder.append(" LIMIT :pageSize OFFSET :offset");
+ queryParameters.put("pageSize", paginationOption.getPageSize());
+ queryParameters.put("offset", offset);
}
private static Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) {
private void addLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
if (cpsPathQuery.hasLeafConditions()) {
- sqlStringBuilder.append(" AND (");
- final List<String> queryBooleanOperatorsType = cpsPathQuery.getBooleanOperatorsType();
- final Queue<String> booleanOperatorsQueue = (queryBooleanOperatorsType == null) ? null : new LinkedList<>(
- queryBooleanOperatorsType);
- cpsPathQuery.getLeavesData().entrySet().forEach(entry -> {
- sqlStringBuilder.append(" attributes @> ");
- sqlStringBuilder.append("'");
- sqlStringBuilder.append(jsonObjectMapper.asJsonString(entry));
- sqlStringBuilder.append("'");
- if (!(booleanOperatorsQueue == null || booleanOperatorsQueue.isEmpty())) {
- sqlStringBuilder.append(" ");
- sqlStringBuilder.append(booleanOperatorsQueue.poll());
- sqlStringBuilder.append(" ");
- }
- });
- sqlStringBuilder.append(")");
+ queryLeafConditions(cpsPathQuery, sqlStringBuilder);
}
}
+ private void queryLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
+ sqlStringBuilder.append(" AND (");
+ final Queue<String> booleanOperatorsQueue = new LinkedList<>(cpsPathQuery.getBooleanOperators());
+ final Queue<String> comparativeOperatorQueue = new LinkedList<>(cpsPathQuery.getComparativeOperators());
+ cpsPathQuery.getLeavesData().forEach(leaf -> {
+ final String nextComparativeOperator = comparativeOperatorQueue.poll();
+ if (leaf.getValue() instanceof Integer) {
+ sqlStringBuilder.append("(attributes ->> '").append(leaf.getName()).append("')\\:\\:int");
+ sqlStringBuilder.append(nextComparativeOperator);
+ sqlStringBuilder.append(leaf.getValue());
+ } else {
+ if ("=".equals(nextComparativeOperator)) {
+ final String leafValueAsText = leaf.getValue().toString();
+ sqlStringBuilder.append("attributes ->> '").append(leaf.getName()).append("'");
+ sqlStringBuilder.append(" = '");
+ sqlStringBuilder.append(EscapeUtils.escapeForSqlStringLiteral(leafValueAsText));
+ sqlStringBuilder.append("'");
+ } else {
+ throw new CpsPathException(" can use only " + nextComparativeOperator + " with integer ");
+ }
+ }
+ if (!booleanOperatorsQueue.isEmpty()) {
+ sqlStringBuilder.append(" ");
+ sqlStringBuilder.append(booleanOperatorsQueue.poll());
+ sqlStringBuilder.append(" ");
+ }
+ });
+ sqlStringBuilder.append(")");
+ }
+
private static void addTextFunctionCondition(final CpsPathQuery cpsPathQuery,
final StringBuilder sqlStringBuilder,
final Map<String, Object> queryParameters) {
if (cpsPathQuery.hasContainsFunctionCondition()) {
sqlStringBuilder.append(" AND attributes ->> :containsLeafName LIKE CONCAT('%',:containsValue,'%') ");
queryParameters.put("containsLeafName", cpsPathQuery.getContainsFunctionConditionLeafName());
- queryParameters.put("containsValue", cpsPathQuery.getContainsFunctionConditionValue());
+ queryParameters.put("containsValue",
+ EscapeUtils.escapeForSqlLike(cpsPathQuery.getContainsFunctionConditionValue()));
}
}