Fix for recursive SQL returning extra level of descendants
[cps.git] / cps-ri / src / main / java / org / onap / cps / spi / repository / FragmentRepository.java
index 2c25a61..82c422f 100755 (executable)
@@ -1,8 +1,9 @@
 /*\r
  * ============LICENSE_START=======================================================\r
- * Copyright (C) 2021-2022 Nordix Foundation.\r
+ * Copyright (C) 2021-2023 Nordix Foundation.\r
  * Modifications Copyright (C) 2020-2021 Bell Canada.\r
  * Modifications Copyright (C) 2020-2021 Pantheon.tech.\r
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.\r
  * ================================================================================\r
  * Licensed under the Apache License, Version 2.0 (the "License");\r
  * you may not use this file except in compliance with the License.\r
@@ -25,8 +26,6 @@ package org.onap.cps.spi.repository;
 import java.util.Collection;\r
 import java.util.List;\r
 import java.util.Optional;\r
-import javax.validation.constraints.NotNull;\r
-import org.checkerframework.checker.nullness.qual.NonNull;\r
 import org.onap.cps.spi.entities.AnchorEntity;\r
 import org.onap.cps.spi.entities.DataspaceEntity;\r
 import org.onap.cps.spi.entities.FragmentEntity;\r
@@ -41,57 +40,119 @@ import org.springframework.stereotype.Repository;
 @Repository\r
 public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery {\r
 \r
-    Optional<FragmentEntity> findByDataspaceAndAnchorAndXpath(@NonNull DataspaceEntity dataspaceEntity,\r
-                                                              @NonNull AnchorEntity anchorEntity,\r
-                                                              @NonNull String xpath);\r
+    Optional<FragmentEntity> findByAnchorAndXpath(AnchorEntity anchorEntity, String xpath);\r
 \r
-    default FragmentEntity getByDataspaceAndAnchorAndXpath(@NonNull DataspaceEntity dataspaceEntity,\r
-                                                           @NonNull AnchorEntity anchorEntity,\r
-                                                           @NonNull String xpath) {\r
-        return findByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, xpath)\r
-            .orElseThrow(() -> new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath));\r
+    default FragmentEntity getByAnchorAndXpath(final AnchorEntity anchorEntity, final String xpath) {\r
+        return findByAnchorAndXpath(anchorEntity, xpath).orElseThrow(() ->\r
+            new DataNodeNotFoundException(anchorEntity.getDataspace().getName(), anchorEntity.getName(), xpath));\r
     }\r
 \r
-    @Query(\r
-        value = "SELECT * FROM FRAGMENT WHERE anchor_id = :anchor AND dataspace_id = :dataspace AND parent_id is NULL",\r
-        nativeQuery = true)\r
-    List<FragmentEntity> findRootsByDataspaceAndAnchor(@Param("dataspace") int dataspaceId,\r
-                                                       @Param("anchor") int anchorId);\r
-\r
-    @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"\r
-            + " CAST(attributes AS TEXT) AS attributes"\r
-            + " FROM FRAGMENT WHERE anchor_id = :anchorId",\r
-            nativeQuery = true)\r
-    List<FragmentExtract> findRootsByAnchorId(@Param("anchorId") int anchorId);\r
-\r
-    /**\r
-     * find top level fragment by anchor.\r
-     *\r
-     * @param dataspaceEntity dataspace entity\r
-     * @param anchorEntity anchor entity\r
-     * @return FragmentEntity fragment entity\r
-     */\r
-    default List<FragmentExtract> getTopLevelFragments(DataspaceEntity dataspaceEntity,\r
-                                                       AnchorEntity anchorEntity) {\r
-        final List<FragmentExtract> fragmentExtracts = findRootsByAnchorId(anchorEntity.getId());\r
-        if (fragmentExtracts.isEmpty()) {\r
-            throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName());\r
-        }\r
-        return fragmentExtracts;\r
+    List<FragmentEntity> findByAnchorIdAndXpathIn(long anchorId, String[] xpaths);\r
+\r
+    default List<FragmentEntity> findByAnchorAndXpathIn(final AnchorEntity anchorEntity,\r
+                                                        final Collection<String> xpaths) {\r
+        return findByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0]));\r
+    }\r
+\r
+    @Query(value = "SELECT fragment.* FROM fragment JOIN anchor ON anchor.id = fragment.anchor_id "\r
+        + "WHERE dataspace_id = :dataspaceId AND xpath = ANY (:xpaths)", nativeQuery = true)\r
+    List<FragmentEntity> findByDataspaceIdAndXpathIn(@Param("dataspaceId") int dataspaceId,\r
+                                                     @Param("xpaths") String[] xpaths);\r
+\r
+    default List<FragmentEntity> findByDataspaceAndXpathIn(final DataspaceEntity dataspaceEntity,\r
+                                                           final Collection<String> xpaths) {\r
+        return findByDataspaceIdAndXpathIn(dataspaceEntity.getId(), xpaths.toArray(new String[0]));\r
     }\r
 \r
-    List<FragmentEntity> findAllByAnchorAndXpathIn(@NonNull AnchorEntity anchorEntity,\r
-                                                   @NonNull Collection<String> xpath);\r
+    boolean existsByAnchorId(long anchorId);\r
+\r
+    @Query("SELECT f FROM FragmentEntity f WHERE anchor = :anchor")\r
+    List<FragmentExtract> findAllExtractsByAnchor(@Param("anchor") AnchorEntity anchorEntity);\r
 \r
     @Modifying\r
-    @Query("DELETE FROM FragmentEntity fe WHERE fe.anchor IN (:anchors)")\r
-    void deleteByAnchorIn(@NotNull @Param("anchors") Collection<AnchorEntity> anchorEntities);\r
-\r
-    @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"\r
-        + " CAST(attributes AS TEXT) AS attributes"\r
-        + " FROM FRAGMENT WHERE anchor_id = :anchorId"\r
-        + " AND ( xpath = :parentXpath OR xpath LIKE CONCAT(:parentXpath,'/%') )",\r
-           nativeQuery = true)\r
-    List<FragmentExtract> findByAnchorIdAndParentXpath(@Param("anchorId") int anchorId,\r
-                                                       @Param("parentXpath") String parentXpath);\r
+    @Query(value = "DELETE FROM fragment WHERE anchor_id = ANY (:anchorIds)", nativeQuery = true)\r
+    void deleteByAnchorIdIn(@Param("anchorIds") long[] anchorIds);\r
+\r
+    default void deleteByAnchorIn(final Collection<AnchorEntity> anchorEntities) {\r
+        deleteByAnchorIdIn(anchorEntities.stream().map(AnchorEntity::getId).mapToLong(id -> id).toArray());\r
+    }\r
+\r
+    @Modifying\r
+    @Query(value = "DELETE FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", nativeQuery = true)\r
+    void deleteByAnchorIdAndXpaths(@Param("anchorId") long anchorId, @Param("xpaths") String[] xpaths);\r
+\r
+    default void deleteByAnchorIdAndXpaths(final long anchorId, final Collection<String> xpaths) {\r
+        deleteByAnchorIdAndXpaths(anchorId, xpaths.toArray(new String[0]));\r
+    }\r
+\r
+    @Modifying\r
+    @Query(value = "DELETE FROM fragment f WHERE anchor_id = :anchorId AND xpath LIKE ANY (:xpathPatterns)",\r
+        nativeQuery = true)\r
+    void deleteByAnchorIdAndXpathLikeAny(@Param("anchorId") long anchorId,\r
+                                         @Param("xpathPatterns") String[] xpathPatterns);\r
+\r
+    default void deleteListsByAnchorIdAndXpaths(long anchorId, Collection<String> xpaths) {\r
+        final String[] listXpathPatterns = xpaths.stream().map(xpath -> xpath + "[%").toArray(String[]::new);\r
+        deleteByAnchorIdAndXpathLikeAny(anchorId, listXpathPatterns);\r
+    }\r
+\r
+    @Query(value = "SELECT xpath FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",\r
+        nativeQuery = true)\r
+    List<String> findAllXpathByAnchorIdAndXpathIn(@Param("anchorId") long anchorId,\r
+                                                  @Param("xpaths") String[] xpaths);\r
+\r
+    default List<String> findAllXpathByAnchorAndXpathIn(final AnchorEntity anchorEntity,\r
+                                                        final Collection<String> xpaths) {\r
+        return findAllXpathByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0]));\r
+    }\r
+\r
+    boolean existsByAnchorAndXpathStartsWith(AnchorEntity anchorEntity, String xpath);\r
+\r
+    @Query("SELECT xpath FROM FragmentEntity WHERE anchor = :anchor AND parentId IS NULL")\r
+    List<String> findAllXpathByAnchorAndParentIdIsNull(@Param("anchor") AnchorEntity anchorEntity);\r
+\r
+    @Query(value\r
+        = "WITH RECURSIVE parent_search AS ("\r
+        + "  SELECT id, 0 AS depth "\r
+        + "    FROM fragment "\r
+        + "   WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths) "\r
+        + "   UNION "\r
+        + "  SELECT c.id, depth + 1 "\r
+        + "    FROM fragment c INNER JOIN parent_search p ON c.parent_id = p.id"\r
+        + "   WHERE depth < (SELECT CASE WHEN :maxDepth = -1 THEN " + Integer.MAX_VALUE + " ELSE :maxDepth END) "\r
+        + ") "\r
+        + "SELECT f.id, anchor_id AS anchorId, xpath, f.parent_id AS parentId, CAST(attributes AS TEXT) AS attributes "\r
+        + "FROM fragment f INNER JOIN parent_search p ON f.id = p.id",\r
+        nativeQuery = true\r
+    )\r
+    List<FragmentExtract> findExtractsWithDescendants(@Param("anchorId") long anchorId,\r
+                                                      @Param("xpaths") String[] xpaths,\r
+                                                      @Param("maxDepth") int maxDepth);\r
+\r
+    default List<FragmentExtract> findExtractsWithDescendants(final long anchorId, final Collection<String> xpaths,\r
+                                                              final int maxDepth) {\r
+        return findExtractsWithDescendants(anchorId, xpaths.toArray(new String[0]), maxDepth);\r
+    }\r
+\r
+    @Query(value\r
+        = "WITH RECURSIVE parent_search AS ("\r
+        + "  SELECT id, 0 AS depth "\r
+        + "    FROM fragment "\r
+        + "   WHERE id = ANY (:ids) "\r
+        + "   UNION "\r
+        + "  SELECT c.id, depth + 1 "\r
+        + "    FROM fragment c INNER JOIN parent_search p ON c.parent_id = p.id"\r
+        + "   WHERE depth < (SELECT CASE WHEN :maxDepth = -1 THEN " + Integer.MAX_VALUE + " ELSE :maxDepth END) "\r
+        + ") "\r
+        + "SELECT f.id, anchor_id AS anchorId, xpath, f.parent_id AS parentId, CAST(attributes AS TEXT) AS attributes "\r
+        + "FROM fragment f INNER JOIN parent_search p ON f.id = p.id",\r
+        nativeQuery = true\r
+    )\r
+    List<FragmentExtract> findExtractsWithDescendantsByIds(@Param("ids") long[] ids,\r
+                                                           @Param("maxDepth") int maxDepth);\r
+\r
+    default List<FragmentExtract> findExtractsWithDescendantsByIds(final Collection<Long> ids, final int maxDepth) {\r
+        return findExtractsWithDescendantsByIds(ids.stream().mapToLong(id -> id).toArray(), maxDepth);\r
+    }\r
+\r
 }\r