diff options
Diffstat (limited to 'cps-ri')
12 files changed, 151 insertions, 19 deletions
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java index 2cebfc72c..162b268d8 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2020-2022 Nordix Foundation. + * Copyright (C) 2020-2023 Nordix Foundation. * Modifications Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. @@ -131,6 +131,13 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic } @Override + public Collection<Anchor> getAnchors(final String dataspaceName, final Collection<String> schemaSetNames) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + return anchorRepository.findAllByDataspaceAndSchemaSetNameIn(dataspaceEntity, schemaSetNames) + .stream().map(CpsAdminPersistenceServiceImpl::toAnchor).collect(Collectors.toSet()); + } + + @Override public Collection<Anchor> queryAnchors(final String dataspaceName, final Collection<String> inputModuleNames) { try { validateDataspaceAndModuleNames(dataspaceName, inputModuleNames); @@ -157,6 +164,13 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic anchorRepository.delete(anchorEntity); } + @Transactional + @Override + public void deleteAnchors(final String dataspaceName, final Collection<String> anchorNames) { + final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + anchorRepository.deleteAllByDataspaceAndNameIn(dataspaceEntity, anchorNames); + } + private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) { final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 46439fdb3..2fba30ee2 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -612,6 +612,15 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService @Override @Transactional + public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + final Collection<AnchorEntity> anchorEntities = + anchorRepository.findAllByDataspaceAndNameIn(dataspaceEntity, anchorNames); + fragmentRepository.deleteByAnchorIn(anchorEntities); + } + + @Override + @Transactional public void deleteDataNodes(final String dataspaceName, final String anchorName, final Collection<String> xpathsToDelete) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java index c9f9a78ef..b4366de75 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2020-2022 Nordix Foundation + * Copyright (C) 2020-2023 Nordix Foundation * Modifications Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. @@ -193,6 +193,13 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ @Override @Transactional + public void deleteSchemaSets(final String dataspaceName, final Collection<String> schemaSetNames) { + final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + schemaSetRepository.deleteByDataspaceAndNameIn(dataspaceEntity, schemaSetNames); + } + + @Override + @Transactional public void deleteUnusedYangResourceModules() { yangResourceRepository.deleteOrphans(); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/utils/CpsValidatorImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/utils/CpsValidatorImpl.java index 064583142..1f61ee3c5 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/utils/CpsValidatorImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/utils/CpsValidatorImpl.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-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. @@ -21,6 +21,7 @@ package org.onap.cps.spi.impl.utils; import com.google.common.collect.Lists; +import java.util.Arrays; import java.util.Collection; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,13 +38,18 @@ public class CpsValidatorImpl implements CpsValidator { @Override public void validateNameCharacters(final String... names) { + validateNameCharacters(Arrays.asList(names)); + } + + @Override + public void validateNameCharacters(final Iterable<String> names) { for (final String name : names) { final Collection<Character> charactersOfName = Lists.charactersOf(name); for (final char unsupportedCharacter : UNSUPPORTED_NAME_CHARACTERS) { if (charactersOfName.contains(unsupportedCharacter)) { throw new DataValidationException("Name or ID Validation Error.", - name + " invalid token encountered at position " - + (name.indexOf(unsupportedCharacter) + 1)); + name + " invalid token encountered at position " + + (name.indexOf(unsupportedCharacter) + 1)); } } } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java index 3dbd578c7..46b0fec1c 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java @@ -47,6 +47,12 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> { Collection<AnchorEntity> findAllBySchemaSet(@NotNull SchemaSetEntity schemaSetEntity); + Collection<AnchorEntity> findAllByDataspaceAndNameIn(@NotNull DataspaceEntity dataspaceEntity, + @NotNull Collection<String> anchorNames); + + Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(@NotNull DataspaceEntity dataspaceEntity, + @NotNull Collection<String> schemaSetNames); + Integer countByDataspace(@NotNull DataspaceEntity dataspaceEntity); @Query(value = "SELECT anchor.* FROM yang_resource\n" @@ -58,4 +64,7 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> { + "HAVING COUNT(DISTINCT module_name) = :sizeOfModuleNames", nativeQuery = true) Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(@Param("dataspaceId") int dataspaceId, @Param("moduleNames") Collection<String> moduleNames, @Param("sizeOfModuleNames") int sizeOfModuleNames); -}
\ No newline at end of file + + void deleteAllByDataspaceAndNameIn(@NotNull DataspaceEntity dataspaceEntity, + @NotNull Collection<String> anchorNames); +} diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java index 8cecb0a8e..98d442010 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java @@ -2,6 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2020 Pantheon.tech * Modifications Copyright (C) 2022 TechMahindra Ltd. + * Modifications 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. @@ -29,6 +30,9 @@ import org.onap.cps.spi.entities.DataspaceEntity; import org.onap.cps.spi.entities.SchemaSetEntity; import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; 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; @Repository @@ -70,4 +74,14 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte default List<SchemaSetEntity> getByDataspace(@NotNull final DataspaceEntity dataspaceEntity) { return findByDataspace(dataspaceEntity).stream().collect(Collectors.toList()); } + + /** + * 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(@NotNull @Param("dataspaceEntity") final DataspaceEntity dataspaceEntity, + @NotNull @Param("schemaSetNames") final Collection<String> schemaSetNames); } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy index 99d44aac8..28d3bcfa4 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2022 Bell Canada * Modifications Copyright (C) 2022 TechMahindra Ltd. @@ -142,7 +142,8 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { where: 'the following data is used' dataspaceName || expectedAnchors DATASPACE_NAME || [Anchor.builder().name(ANCHOR_NAME1).schemaSetName(SCHEMA_SET_NAME1).dataspaceName(DATASPACE_NAME).build(), - Anchor.builder().name(ANCHOR_NAME2).schemaSetName(SCHEMA_SET_NAME2).dataspaceName(DATASPACE_NAME).build()] + Anchor.builder().name(ANCHOR_NAME2).schemaSetName(SCHEMA_SET_NAME2).dataspaceName(DATASPACE_NAME).build(), + Anchor.builder().name(ANCHOR_NAME3).schemaSetName(SCHEMA_SET_NAME2).dataspaceName(DATASPACE_NAME).build()] DATASPACE_WITH_NO_DATA || [] } @@ -179,6 +180,17 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) + def 'Get all anchors associated with multiple schemasets in a dataspace.'() { + when: 'anchors are retrieved by dataspace and schema-sets' + def anchors = objectUnderTest.getAnchors('DATASPACE-001', ['SCHEMA-SET-001', 'SCHEMA-SET-002']) + then: ' the response contains expected anchors' + anchors == Set.of( + new Anchor('ANCHOR-001', 'DATASPACE-001', 'SCHEMA-SET-001'), + new Anchor('ANCHOR-002', 'DATASPACE-001', 'SCHEMA-SET-002'), + new Anchor('ANCHOR-003', 'DATASPACE-001', 'SCHEMA-SET-002')) + } + + @Sql([CLEAR_DATA, SET_DATA]) def 'Delete anchor'() { when: 'delete anchor action is invoked' objectUnderTest.deleteAnchor(DATASPACE_NAME, ANCHOR_NAME2) @@ -198,6 +210,15 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { 'anchor does not exists' | DATASPACE_NAME | 'unknown' || AnchorNotFoundException } + @Sql([CLEAR_DATA, SET_DATA]) + def 'Delete multiple anchors'() { + when: 'delete anchors action is invoked' + objectUnderTest.deleteAnchors(DATASPACE_NAME, ['ANCHOR-002', 'ANCHOR-003']) + then: 'anchors are deleted' + anchorRepository.findById(3002).isEmpty() + anchorRepository.findById(3003).isEmpty() + } + @Sql([CLEAR_DATA, SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES]) def 'Query anchors that have #scenario.'() { when: 'all anchor are retrieved for the given dataspace name and module names' @@ -236,7 +257,7 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { where: 'the following data is used' scenario | dataspaceName || expectedException | expectedMessageDetails 'dataspace name does not exist' | 'unknown' || DataspaceNotFoundException | 'unknown does not exist' - 'dataspace contains an anchor' | 'DATASPACE-001' || DataspaceInUseException | 'contains 2 anchor(s)' + 'dataspace contains an anchor' | 'DATASPACE-001' || DataspaceInUseException | 'contains 3 anchor(s)' 'dataspace contains schemasets' | 'DATASPACE-003' || DataspaceInUseException | 'contains 1 schemaset(s)' } } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy index 336d6035a..bb80a1320 100755 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy @@ -55,8 +55,6 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { static DataNodeBuilder dataNodeBuilder = new DataNodeBuilder() static final String SET_DATA = '/data/fragment.sql' - static int DATASPACE_1001_ID = 1001L - static int ANCHOR_3003_ID = 3003L static long ID_DATA_NODE_WITH_DESCENDANTS = 4001 static String XPATH_DATA_NODE_WITH_DESCENDANTS = '/parent-1' static String XPATH_DATA_NODE_WITH_LEAVES = '/parent-207' @@ -666,11 +664,23 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { @Sql([CLEAR_DATA, SET_DATA]) def 'Delete data node for an anchor.'() { given: 'a data-node exists for an anchor' - assert fragmentsExistInDB(DATASPACE_1001_ID, ANCHOR_3003_ID) + assert fragmentsExistInDB(1001, 3003) when: 'data nodes are deleted ' objectUnderTest.deleteDataNodes(DATASPACE_NAME, ANCHOR_NAME3) then: 'all data-nodes are deleted successfully' - assert !fragmentsExistInDB(DATASPACE_1001_ID, ANCHOR_3003_ID) + assert !fragmentsExistInDB(1001, 3003) + } + + @Sql([CLEAR_DATA, SET_DATA]) + def 'Delete data node for multiple anchors.'() { + given: 'a data-node exists for an anchor' + assert fragmentsExistInDB(1001, 3001) + assert fragmentsExistInDB(1001, 3003) + when: 'data nodes are deleted ' + objectUnderTest.deleteDataNodes(DATASPACE_NAME, ['ANCHOR-001', 'ANCHOR-003']) + then: 'all data-nodes are deleted successfully' + assert !fragmentsExistInDB(1001, 3001) + assert !fragmentsExistInDB(1001, 3003) } def fragmentsExistInDB(dataSpaceId, anchorId) { diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy index 4c67f7e97..864b3e3b6 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2021-2022 Bell Canada. * Modifications Copyright (C) 2022 TechMahindra Ltd. * ================================================================================ @@ -223,7 +223,16 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase when: 'a schema set is deleted with cascade-prohibited option' objectUnderTest.deleteSchemaSet(DATASPACE_NAME, SCHEMA_SET_NAME_NO_ANCHORS) then: 'the schema set has been deleted' - schemaSetRepository.findByDataspaceAndName(dataspaceEntity, SCHEMA_SET_NAME_NO_ANCHORS).isPresent() == false + !schemaSetRepository.findByDataspaceAndName(dataspaceEntity, SCHEMA_SET_NAME_NO_ANCHORS).isPresent() + } + + @Sql([CLEAR_DATA, SET_DATA]) + def 'Delete schema sets'() { + when: 'schema sets are deleted' + objectUnderTest.deleteSchemaSets(DATASPACE_NAME, ['SCHEMA-SET-001', 'SCHEMA-SET-002']) + then: 'the schema sets have been deleted' + !schemaSetRepository.findByDataspaceAndName(dataspaceEntity, 'SCHEMA-SET-001').isPresent() + !schemaSetRepository.findByDataspaceAndName(dataspaceEntity, 'SCHEMA-SET-002').isPresent() } @Sql([CLEAR_DATA, SET_DATA]) diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/utils/CpsValidatorSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/utils/CpsValidatorSpec.groovy index ae2ff16bc..345089c93 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/utils/CpsValidatorSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/utils/CpsValidatorSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-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. @@ -46,4 +46,22 @@ class CpsValidatorSpec extends Specification { 'position 5' | 'name with spaces' || 'name with spaces invalid token encountered at position 5' 'position 9' | 'nameWith Space' || 'nameWith Space invalid token encountered at position 9' } + + def 'Validating a list of valid names.'() { + given: 'a list of valid names' + def names = ['valid-name', 'another-valid-name'] + when: 'a list of strings is validated' + objectUnderTest.validateNameCharacters(names) + then: 'no exception is thrown' + noExceptionThrown() + } + + def 'Validating a list of names with invalid names.'() { + given: 'a list of names with an invalid name' + def names = ['valid-name', 'name with spaces'] + when: 'a list of strings is validated' + objectUnderTest.validateNameCharacters(names) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + } } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy index 3b9338ce4..706b35ebb 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy @@ -212,7 +212,7 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase } @Sql([CLEAR_DATA, PERF_TEST_DATA]) - def 'Delete data nodes for an anchor'() {212 + def 'Delete data nodes for an anchor'() { given: 'a node with a large number of descendants is created' createLineage(objectUnderTest, 50, 50, false) createLineage(objectUnderTest, 50, 50, true) @@ -225,4 +225,18 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase recordAndAssertPerformance('Delete data nodes for anchor', 300, deleteDurationInMillis) } + @Sql([CLEAR_DATA, PERF_TEST_DATA]) + def 'Delete data nodes for multiple anchors'() { + given: 'a node with a large number of descendants is created' + createLineage(objectUnderTest, 50, 50, false) + createLineage(objectUnderTest, 50, 50, true) + when: 'data nodes are deleted' + stopWatch.start() + objectUnderTest.deleteDataNodes(PERF_DATASPACE, [PERF_ANCHOR]) + stopWatch.stop() + def deleteDurationInMillis = stopWatch.getTotalTimeMillis() + then: 'delete duration is under 300 milliseconds' + recordAndAssertPerformance('Delete data nodes for anchor', 300, deleteDurationInMillis) + } + } diff --git a/cps-ri/src/test/resources/data/anchor.sql b/cps-ri/src/test/resources/data/anchor.sql index 40fc44c0a..2ab7966e1 100644 --- a/cps-ri/src/test/resources/data/anchor.sql +++ b/cps-ri/src/test/resources/data/anchor.sql @@ -1,7 +1,7 @@ /* ============LICENSE_START======================================================= Copyright (C) 2020 Pantheon.tech - Modifications Copyright (C) 2020 Nordix Foundation. + Modifications Copyright (C) 2020-2023 Nordix Foundation. Modifications Copyright (C) 2021-2022 Bell Canada. ================================================================================ Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,8 @@ INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES (3001, 'ANCHOR-001', 1001, 2001), - (3002, 'ANCHOR-002', 1001, 2002); + (3002, 'ANCHOR-002', 1001, 2002), + (3003, 'ANCHOR-003', 1001, 2002); INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES (4001, 1001, 3001, null, '/xpath', '{}'); |