@Override
public Collection<SchemaSet> getSchemaSetsByDataspaceName(final String dataspaceName) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
- final List<SchemaSetEntity> schemaSetEntities = schemaSetRepository.getByDataspace(dataspaceEntity);
+ final List<SchemaSetEntity> schemaSetEntities = schemaSetRepository.findByDataspace(dataspaceEntity);
return schemaSetEntities.stream()
.map(CpsModulePersistenceServiceImpl::toSchemaSet).collect(Collectors.toList());
}
import org.onap.cps.spi.entities.SchemaSetEntity;
import org.onap.cps.spi.exceptions.AnchorNotFoundException;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
Collection<AnchorEntity> findAllBySchemaSet(SchemaSetEntity schemaSetEntity);
- Collection<AnchorEntity> findAllByDataspaceAndNameIn(DataspaceEntity dataspaceEntity,
- Collection<String> anchorNames);
+ @Query(value = "SELECT * FROM anchor WHERE dataspace_id = :dataspaceId AND name = ANY (:anchorNames)",
+ nativeQuery = true)
+ Collection<AnchorEntity> findAllByDataspaceIdAndNameIn(@Param("dataspaceId") int dataspaceId,
+ @Param("anchorNames") String[] anchorNames);
- Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(DataspaceEntity dataspaceEntity,
- Collection<String> schemaSetNames);
+ default Collection<AnchorEntity> findAllByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity,
+ final Collection<String> anchorNames) {
+ return findAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0]));
+ }
+
+ @Query(value = "SELECT a.* FROM anchor a"
+ + " LEFT OUTER JOIN schema_set s ON a.schema_set_id = s.id"
+ + " WHERE a.dataspace_id = :dataspaceId AND s.name = ANY (:schemaSetNames)",
+ nativeQuery = true)
+ Collection<AnchorEntity> findAllByDataspaceIdAndSchemaSetNameIn(@Param("dataspaceId") int dataspaceId,
+ @Param("schemaSetNames") String[] schemaSetNames);
+
+ default Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(final DataspaceEntity dataspaceEntity,
+ final Collection<String> schemaSetNames) {
+ return findAllByDataspaceIdAndSchemaSetNameIn(dataspaceEntity.getId(), schemaSetNames.toArray(new String[0]));
+ }
Integer countByDataspace(DataspaceEntity dataspaceEntity);
+ "JOIN schema_set_yang_resources ON schema_set_yang_resources.yang_resource_id = yang_resource.id\n"
+ "JOIN schema_set ON schema_set.id = schema_set_yang_resources.schema_set_id\n"
+ "JOIN anchor ON anchor.schema_set_id = schema_set.id\n"
- + "WHERE schema_set.dataspace_id = :dataspaceId AND module_name IN (:moduleNames)\n"
+ + "WHERE schema_set.dataspace_id = :dataspaceId AND module_name = ANY (:moduleNames)\n"
+ "GROUP BY anchor.id, anchor.name, anchor.dataspace_id, anchor.schema_set_id\n"
+ "HAVING COUNT(DISTINCT module_name) = :sizeOfModuleNames", nativeQuery = true)
Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(@Param("dataspaceId") int dataspaceId,
- @Param("moduleNames") Collection<String> moduleNames, @Param("sizeOfModuleNames") int sizeOfModuleNames);
+ @Param("moduleNames") String[] moduleNames,
+ @Param("sizeOfModuleNames") int sizeOfModuleNames);
+
+ default Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(final int dataspaceId,
+ final Collection<String> moduleNames,
+ final int sizeOfModuleNames) {
+ final String[] moduleNamesArray = moduleNames.toArray(new String[0]);
+ return getAnchorsByDataspaceIdAndModuleNames(dataspaceId, moduleNamesArray, sizeOfModuleNames);
+ }
+
+ @Modifying
+ @Query(value = "DELETE FROM anchor WHERE dataspace_id = :dataspaceId AND name = ANY (:anchorNames)",
+ nativeQuery = true)
+ void deleteAllByDataspaceIdAndNameIn(@Param("dataspaceId") int dataspaceId,
+ @Param("anchorNames") String[] anchorNames);
+
+ default void deleteAllByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity,
+ final Collection<String> anchorNames) {
+ deleteAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0]));
+ }
- void deleteAllByDataspaceAndNameIn(DataspaceEntity dataspaceEntity,
- Collection<String> anchorNames);
}
+++ /dev/null
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2023 Nordix Foundation
- * ================================================================================
- * 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.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.spi.repository;
-
-import java.util.Collection;
-
-/**
- * This interface is used in delete fragment entity by id with child using native sql queries.
- */
-public interface FragmentNativeRepository {
-
- /**
- * Delete fragment entities for each supplied xpath.
- * This method will delete list elements or other data nodes, but not whole lists.
- * Non-existing xpaths will not result in an exception.
- * @param anchorId the id of the anchor
- * @param xpaths xpaths of data nodes to remove
- */
- void deleteByAnchorIdAndXpaths(int anchorId, Collection<String> xpaths);
-
- /**
- * Delete fragment entities that are list elements of each supplied list xpath.
- * For example, if xpath '/parent/list' is provided, then list all elements in '/parent/list' will be deleted,
- * e.g. /parent/list[@key='A'], /parent/list[@key='B'].
- * This method will only delete whole lists by xpath; xpaths to list elements or other data nodes will be ignored.
- * Non-existing xpaths will not result in an exception.
- * @param anchorId the id of the anchor
- * @param listXpaths xpaths of whole lists to remove
- */
- void deleteListsByAnchorIdAndXpaths(int anchorId, Collection<String> listXpaths);
-}
+++ /dev/null
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2023 Nordix Foundation
- * ================================================================================
- * 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.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.cps.spi.repository;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.stream.Collectors;
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.persistence.Query;
-import lombok.RequiredArgsConstructor;
-
-@RequiredArgsConstructor
-public class FragmentNativeRepositoryImpl implements FragmentNativeRepository {
-
- @PersistenceContext
- private final EntityManager entityManager;
-
- @Override
- public void deleteByAnchorIdAndXpaths(final int anchorId, final Collection<String> xpaths) {
- final String queryString =
- "DELETE FROM fragment f WHERE f.anchor_id = ? AND (f.xpath IN (:parameterPlaceholders))";
- executeUpdateWithAnchorIdAndCollection(queryString, anchorId, xpaths);
- }
-
- @Override
- public void deleteListsByAnchorIdAndXpaths(final int anchorId, final Collection<String> listXpaths) {
- final Collection<String> listXpathPatterns =
- listXpaths.stream().map(listXpath -> listXpath + "[%").collect(Collectors.toSet());
- final String queryString =
- "DELETE FROM fragment f WHERE f.anchor_id = ? AND (f.xpath LIKE ANY (array[:parameterPlaceholders]))";
- executeUpdateWithAnchorIdAndCollection(queryString, anchorId, listXpathPatterns);
- }
-
- // Accept security hotspot as placeholders in SQL query are created internally, not from user input.
- @SuppressWarnings("squid:S2077")
- private void executeUpdateWithAnchorIdAndCollection(final String sqlTemplate, final int anchorId,
- final Collection<String> collection) {
- if (!collection.isEmpty()) {
- final String parameterPlaceholders = String.join(",", Collections.nCopies(collection.size(), "?"));
- final String queryStringWithParameterPlaceholders =
- sqlTemplate.replaceFirst(":parameterPlaceholders\\b", parameterPlaceholders);
-
- final Query query = entityManager.createNativeQuery(queryStringWithParameterPlaceholders);
- query.setParameter(1, anchorId);
- int parameterIndex = 2;
- for (final String parameterValue : collection) {
- query.setParameter(parameterIndex++, parameterValue);
- }
- query.executeUpdate();
- }
- }
-
-}
import org.springframework.stereotype.Repository;\r
\r
@Repository\r
-public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery,\r
- FragmentNativeRepository {\r
+public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery {\r
\r
Optional<FragmentEntity> findByAnchorAndXpath(AnchorEntity anchorEntity, String xpath);\r
\r
@Query("SELECT f FROM FragmentEntity f WHERE anchor = :anchor")\r
List<FragmentExtract> findAllExtractsByAnchor(@Param("anchor") AnchorEntity anchorEntity);\r
\r
- List<FragmentEntity> findAllByAnchorAndXpathIn(AnchorEntity anchorEntity, Collection<String> xpath);\r
+ @Query(value = "SELECT * FROM fragment WHERE xpath = ANY (:xpaths)", nativeQuery = true)\r
+ List<FragmentEntity> findAllByXpathIn(@Param("xpaths") String[] xpath);\r
\r
- List<FragmentEntity> findAllByXpathIn(Collection<String> xpath);\r
+ default List<FragmentEntity> findAllByXpathIn(final Collection<String> xpaths) {\r
+ return findAllByXpathIn(xpaths.toArray(new String[0]));\r
+ }\r
+\r
+ @Modifying\r
+ @Query(value = "DELETE FROM fragment WHERE anchor_id = ANY (:anchorIds)", nativeQuery = true)\r
+ void deleteByAnchorIdIn(@Param("anchorIds") int[] anchorIds);\r
+\r
+ default void deleteByAnchorIn(final Collection<AnchorEntity> anchorEntities) {\r
+ deleteByAnchorIdIn(anchorEntities.stream().map(AnchorEntity::getId).mapToInt(id -> id).toArray());\r
+ }\r
\r
@Modifying\r
- @Query("DELETE FROM FragmentEntity WHERE anchor IN (:anchors)")\r
- void deleteByAnchorIn(@Param("anchors") Collection<AnchorEntity> anchorEntities);\r
+ @Query(value = "DELETE FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", nativeQuery = true)\r
+ void deleteByAnchorIdAndXpaths(@Param("anchorId") int anchorId, @Param("xpaths") String[] xpaths);\r
+\r
+ default void deleteByAnchorIdAndXpaths(final int 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") int anchorId,\r
+ @Param("xpathPatterns") String[] xpathPatterns);\r
+\r
+ default void deleteListsByAnchorIdAndXpaths(int anchorId, Collection<String> xpaths) {\r
+ final String[] listXpathPatterns = xpaths.stream().map(xpath -> xpath + "[%").toArray(String[]::new);\r
+ deleteByAnchorIdAndXpathLikeAny(anchorId, listXpathPatterns);\r
+ }\r
\r
@Query("SELECT f FROM FragmentEntity f WHERE anchor = :anchor"\r
+ " AND (xpath = :parentXpath OR xpath LIKE CONCAT(:parentXpath,'/%'))")\r
List<FragmentExtract> quickFindWithDescendants(@Param("anchorId") int anchorId,\r
@Param("xpathRegex") String xpathRegex);\r
\r
- @Query("SELECT xpath FROM FragmentEntity WHERE anchor = :anchor AND xpath IN :xpaths")\r
- List<String> findAllXpathByAnchorAndXpathIn(@Param("anchor") AnchorEntity anchorEntity,\r
- @Param("xpaths") Collection<String> xpaths);\r
+ @Query(value = "SELECT xpath FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",\r
+ nativeQuery = true)\r
+ List<String> findAllXpathByAnchorIdAndXpathIn(@Param("anchorId") int 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
= "WITH RECURSIVE parent_search AS ("\r
+ " SELECT id, 0 AS depth "\r
+ " FROM fragment "\r
- + " WHERE anchor_id = :anchorId AND xpath IN :xpaths "\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
nativeQuery = true\r
)\r
List<FragmentExtract> findExtractsWithDescendants(@Param("anchorId") int anchorId,\r
- @Param("xpaths") Collection<String> xpaths,\r
+ @Param("xpaths") String[] xpaths,\r
@Param("maxDepth") int maxDepth);\r
\r
+ default List<FragmentExtract> findExtractsWithDescendants(final int anchorId, final Collection<String> xpaths,\r
+ final int maxDepth) {\r
+ return findExtractsWithDescendants(anchorId, xpaths.toArray(new String[0]), maxDepth);\r
+ }\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 xpath ~ :xpathRegex",\r
import java.util.Collection;
import java.util.List;
import java.util.Optional;
-import java.util.stream.Collectors;
import org.onap.cps.spi.entities.DataspaceEntity;
import org.onap.cps.spi.entities.SchemaSetEntity;
import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
* @param dataspaceEntity dataspace entity
* @return list of schema set entity
*/
- Collection<SchemaSetEntity> findByDataspace(DataspaceEntity dataspaceEntity);
+ List<SchemaSetEntity> findByDataspace(DataspaceEntity dataspaceEntity);
Integer countByDataspace(DataspaceEntity dataspaceEntity);
.orElseThrow(() -> new SchemaSetNotFoundException(dataspaceEntity.getName(), schemaSetName));
}
- /**
- * Gets all schema sets for a given dataspace.
- *
- * @param dataspaceEntity dataspace entity
- * @return list of schema set entity
- * @throws SchemaSetNotFoundException if SchemaSet not found
- */
- default List<SchemaSetEntity> getByDataspace(final DataspaceEntity dataspaceEntity) {
- return findByDataspace(dataspaceEntity).stream().collect(Collectors.toList());
- }
+ @Modifying
+ @Query(value = "DELETE FROM schema_set WHERE dataspace_id = :dataspaceId AND name = ANY (:schemaSetNames)",
+ nativeQuery = true)
+ void deleteByDataspaceIdAndNameIn(@Param("dataspaceId") final int dataspaceId,
+ @Param("schemaSetNames") final String[] schemaSetNames);
/**
* Delete multiple schema sets in a given dataspace.
* @param dataspaceEntity dataspace entity
* @param schemaSetNames schema set names
*/
- @Modifying
- @Query("DELETE FROM SchemaSetEntity s WHERE s.dataspace = :dataspaceEntity AND s.name IN (:schemaSetNames)")
- void deleteByDataspaceAndNameIn(@Param("dataspaceEntity") DataspaceEntity dataspaceEntity,
- @Param("schemaSetNames") Collection<String> schemaSetNames);
+ default void deleteByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity,
+ final Collection<String> schemaSetNames) {
+ deleteByDataspaceIdAndNameIn(dataspaceEntity.getId(), schemaSetNames.toArray(new String[0]));
+ }
+
}
public interface YangResourceRepository extends JpaRepository<YangResourceEntity, Long>,
YangResourceNativeRepository, SchemaSetYangResourceRepository {
- List<YangResourceEntity> findAllByChecksumIn(Set<String> checksum);
+ List<YangResourceEntity> findAllByChecksumIn(String[] checksums);
+
+ default List<YangResourceEntity> findAllByChecksumIn(final Collection<String> checksums) {
+ return findAllByChecksumIn(checksums.toArray(new String[0]));
+ }
@Query(value = "SELECT DISTINCT\n"
+ "yang_resource.module_name AS module_name,\n"
+ "schema_set.id\n"
+ "JOIN yang_resource ON yang_resource.id = schema_set_yang_resources.yang_resource_id\n"
+ "WHERE\n"
- + "dataspace.name = :dataspaceName and yang_resource.module_Name IN (:moduleNames)", nativeQuery = true)
+ + "dataspace.name = :dataspaceName and yang_resource.module_Name = ANY (:moduleNames)", nativeQuery = true)
Set<YangResourceModuleReference> findAllModuleReferencesByDataspaceAndModuleNames(
- @Param("dataspaceName") String dataspaceName, @Param("moduleNames") Collection<String> moduleNames);
+ @Param("dataspaceName") String dataspaceName, @Param("moduleNames") String[] moduleNames);
+
+ default Set<YangResourceModuleReference> findAllModuleReferencesByDataspaceAndModuleNames(
+ final String dataspaceName, final Collection<String> moduleNames) {
+ return findAllModuleReferencesByDataspaceAndModuleNames(dataspaceName, moduleNames.toArray(new String[0]));
+ }
@Modifying
@Query(value = "DELETE FROM yang_resource yr WHERE NOT EXISTS "
def 'Store schema set error scenario: #scenario.'() {
given: 'no yang resource are currently saved'
- yangResourceRepositoryMock.findAllByChecksumIn(_) >> Collections.emptyList()
+ yangResourceRepositoryMock.findAllByChecksumIn(_ as Collection<String>) >> Collections.emptyList()
and: 'persisting yang resource raises db constraint exception (in case of concurrent requests for example)'
yangResourceRepositoryMock.saveAll(_) >> { throw dbException }
when: 'attempt to store schema set '
import org.onap.cps.api.CpsAdminService
import org.onap.cps.integration.performance.base.CpsPerfTestBase
-import org.springframework.dao.DataAccessResourceFailureException
class CpsAdminServiceLimits extends CpsPerfTestBase {
def 'Get anchors from multiple schema set names limit exceeded: 32,766 (~ 2^15) schema set names.'() {
given: 'more than 32,766 schema set names'
- def schemaSetNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
+ def schemaSetNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
when: 'single get is executed to get all the anchors'
objectUnderTest.getAnchors(CPS_PERFORMANCE_TEST_DATASPACE, schemaSetNames)
- then: 'a database exception is thrown'
- thrown(DataAccessResourceFailureException.class)
+ then: 'a database exception is not thrown'
+ noExceptionThrown()
}
def 'Querying anchor names limit exceeded: 32,766 (~ 2^15) modules.'() {
given: 'more than 32,766 module names'
- def moduleNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
+ def moduleNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
when: 'single query is executed to get all the anchors'
objectUnderTest.queryAnchorNames(CPS_PERFORMANCE_TEST_DATASPACE, moduleNames)
- then: 'a database exception is thrown'
- thrown(DataAccessResourceFailureException.class)
+ then: 'a database exception is not thrown'
+ noExceptionThrown()
}
}
import java.time.OffsetDateTime
import org.onap.cps.api.CpsDataService
import org.onap.cps.integration.performance.base.CpsPerfTestBase
-import org.springframework.dao.DataAccessResourceFailureException
-import org.springframework.transaction.TransactionSystemException
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
def 'Multiple get limit exceeded: 32,764 (~ 2^15) xpaths.'() {
given: 'more than 32,764 xpaths'
- def xpaths = (0..32_764).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" }
+ def xpaths = (0..40_000).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" }
when: 'single operation is executed to get all datanodes with given xpaths'
objectUnderTest.getDataNodesForMultipleXpaths(CPS_PERFORMANCE_TEST_DATASPACE, 'bookstore1', xpaths, INCLUDE_ALL_DESCENDANTS)
- then: 'a database exception is thrown'
- thrown(DataAccessResourceFailureException.class)
+ then: 'a database exception is not thrown'
+ noExceptionThrown()
}
def 'Delete multiple datanodes limit exceeded: 32,767 (~ 2^15) xpaths.'() {
given: 'more than 32,767 xpaths'
- def xpaths = (0..32_767).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" }
+ def xpaths = (0..40_000).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" }
when: 'single operation is executed to delete all datanodes with given xpaths'
objectUnderTest.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'bookstore1', xpaths, OffsetDateTime.now())
- then: 'a database exception is thrown'
- thrown(TransactionSystemException.class)
+ then: 'a database exception is not thrown (but a CPS DataNodeNotFoundException is thrown)'
+ thrown(DataNodeNotFoundException.class)
}
def 'Delete datanodes from multiple anchors limit exceeded: 32,766 (~ 2^15) anchors.'() {
given: 'more than 32,766 anchor names'
- def anchorNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
+ def anchorNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it }
when: 'single operation is executed to delete all datanodes in given anchors'
objectUnderTest.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, anchorNames, OffsetDateTime.now())
- then: 'a database exception is thrown'
- thrown(DataAccessResourceFailureException.class)
+ then: 'a database exception is not thrown'
+ noExceptionThrown()
}
}