/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022 Nordix Foundation
+ * Copyright (C) 2022-2023 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
package org.onap.cps.spi.repository;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.Map;
+import java.util.Queue;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.cpspath.parser.CpsPathPrefixType;
import org.onap.cps.cpspath.parser.CpsPathQuery;
+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_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?";
- private static final String REGEX_DESCENDANT_PATH_POSTFIX = "(\\/.*)?";
- private static final String REGEX_END_OF_INPUT = "$";
+ 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.
*
- * @param anchorId the id of the anchor
+ * @param anchorEntity the anchor
+ * @param cpsPathQuery the cps path query to be transformed into a sql query
+ * @return a executable query object
+ */
+ public Query getQueryForAnchorAndCpsPath(final AnchorEntity anchorEntity, final CpsPathQuery cpsPathQuery) {
+ return getQueryForDataspaceOrAnchorAndCpsPath(anchorEntity.getDataspace(), anchorEntity, cpsPathQuery);
+ }
+
+ /**
+ * Create a sql query to retrieve by cps path.
+ *
+ * @param dataspaceEntity the dataspace
* @param cpsPathQuery the cps path query to be transformed into a sql query
* @return a executable query object
*/
- public Query getQueryForAnchorAndCpsPath(final int anchorId, final CpsPathQuery cpsPathQuery) {
- final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId");
+ public Query getQueryForDataspaceAndCpsPath(final DataspaceEntity dataspaceEntity,
+ final CpsPathQuery cpsPathQuery) {
+ return getQueryForDataspaceOrAnchorAndCpsPath(dataspaceEntity, ACROSS_ALL_ANCHORS, cpsPathQuery);
+ }
+
+ private Query getQueryForDataspaceOrAnchorAndCpsPath(final DataspaceEntity dataspaceEntity,
+ final AnchorEntity anchorEntity,
+ final CpsPathQuery cpsPathQuery) {
+ final StringBuilder sqlStringBuilder = new StringBuilder();
final Map<String, Object> queryParameters = new HashMap<>();
- queryParameters.put("anchorId", anchorId);
- sqlStringBuilder.append(" AND xpath ~ :xpathRegex");
- final String xpathRegex = getXpathSqlRegex(cpsPathQuery, false);
- queryParameters.put("xpathRegex", xpathRegex);
- if (cpsPathQuery.hasLeafConditions()) {
- sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb");
- queryParameters.put("leafDataAsJson", jsonObjectMapper.asJsonString(
- cpsPathQuery.getLeavesData()));
- }
+ if (anchorEntity == ACROSS_ALL_ANCHORS) {
+ sqlStringBuilder.append("SELECT fragment.* FROM fragment JOIN anchor ON anchor.id = fragment.anchor_id"
+ + " WHERE dataspace_id = :dataspaceId");
+ queryParameters.put("dataspaceId", dataspaceEntity.getId());
+ } else {
+ 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;
}
- /**
- * Create a regular expression (string) for xpath based on the given cps path query.
- *
- * @param cpsPathQuery the cps path query to determine the required regular expression
- * @param includeDescendants include descendants yes or no
- * @return a string representing the required regular expression
- */
- public static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery, final boolean includeDescendants) {
- final StringBuilder xpathRegexBuilder = new StringBuilder();
+ private static void addXpathSearch(final CpsPathQuery cpsPathQuery,
+ final StringBuilder sqlStringBuilder,
+ final Map<String, Object> queryParameters) {
+ sqlStringBuilder.append(" AND (xpath LIKE :escapedXpath OR "
+ + "(xpath LIKE :escapedXpath||'[@%]' AND xpath NOT LIKE :escapedXpath||'[@%]/%[@%]'))");
if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) {
- xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix()));
+ queryParameters.put("escapedXpath", EscapeUtils.escapeForSqlLike(cpsPathQuery.getXpathPrefix()));
} else {
- xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX);
- xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName()));
+ queryParameters.put("escapedXpath", "%/" + EscapeUtils.escapeForSqlLike(cpsPathQuery.getDescendantName()));
}
- xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX);
- if (includeDescendants) {
- xpathRegexBuilder.append(REGEX_DESCENDANT_PATH_POSTFIX);
- }
- xpathRegexBuilder.append(REGEX_END_OF_INPUT);
- return xpathRegexBuilder.toString();
- }
-
- 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 Integer getTextValueAsInt(final CpsPathQuery cpsPathQuery) {
}
}
+ private void addLeafConditions(final CpsPathQuery cpsPathQuery, final StringBuilder sqlStringBuilder) {
+ if (cpsPathQuery.hasLeafConditions()) {
+ 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) {
}
}
+ private static void addContainsFunctionCondition(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",
+ EscapeUtils.escapeForSqlLike(cpsPathQuery.getContainsFunctionConditionValue()));
+ }
+ }
+
private static void setQueryParameters(final Query query, final Map<String, Object> queryParameters) {
for (final Map.Entry<String, Object> queryParameter : queryParameters.entrySet()) {
query.setParameter(queryParameter.getKey(), queryParameter.getValue());