All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.sirix.access.trx.page.NodePageTrx Maven / Gradle / Ivy

Go to download

SirixDB is a hybrid on-disk and in-memory document oriented, versioned database system. It has a lightweight buffer manager, stores everything in a huge persistent and durable tree and allows efficient reconstruction of every revision. Furthermore, SirixDB implements change tracking, diffing and supports time travel queries.

There is a newer version: 0.11.0
Show newest version
/*
 * Copyright (c) 2011, University of Konstanz, Distributed Systems Group All rights reserved.
 * 

* Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * Redistributions of source code must retain the * above copyright notice, this list of conditions and the following disclaimer. * Redistributions * in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. * * Neither the name of the University of Konstanz nor the names of its contributors may be used to * endorse or promote products derived from this software without specific prior written permission. *

* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package io.sirix.access.trx.page; import io.sirix.access.ResourceConfiguration; import io.sirix.access.User; import io.sirix.access.trx.node.CommitCredentials; import io.sirix.access.trx.node.IndexController; import io.sirix.access.trx.node.xml.XmlIndexController; import io.sirix.api.PageReadOnlyTrx; import io.sirix.api.PageTrx; import io.sirix.cache.IndexLogKey; import io.sirix.cache.PageContainer; import io.sirix.cache.TransactionIntentLog; import io.sirix.exception.SirixIOException; import io.sirix.index.IndexType; import io.sirix.io.Writer; import io.sirix.node.DeletedNode; import io.sirix.node.NodeKind; import io.sirix.node.SirixDeweyID; import io.sirix.node.delegates.NodeDelegate; import io.sirix.node.interfaces.DataRecord; import io.sirix.node.interfaces.Node; import io.sirix.page.*; import io.sirix.page.interfaces.KeyValuePage; import io.sirix.settings.Constants; import io.sirix.settings.Fixed; import io.sirix.settings.VersioningType; import net.openhft.chronicle.bytes.Bytes; import org.checkerframework.checker.index.qual.NonNegative; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import static com.google.common.base.Preconditions.checkArgument; import static java.nio.file.Files.deleteIfExists; import static java.nio.file.Files.newOutputStream; import static java.nio.file.StandardOpenOption.CREATE; import static java.util.Objects.requireNonNull; /** *

* Implements the {@link PageTrx} interface to provide write capabilities to the persistent storage * layer. *

* * @author Marc Kramis, Seabix AG * @author Sebastian Graf, University of Konstanz * @author Johannes Lichtenberger */ final class NodePageTrx extends AbstractForwardingPageReadOnlyTrx implements PageTrx { private Bytes bufferBytes = Bytes.elasticByteBuffer(Writer.FLUSH_SIZE); /** * Page writer to serialize. */ private final Writer storagePageReaderWriter; /** * Transaction intent log. */ TransactionIntentLog log; /** * Last reference to the actual revRoot. */ private final RevisionRootPage newRevisionRootPage; /** * {@link NodePageReadOnlyTrx} instance. */ private final NodePageReadOnlyTrx pageRtx; /** * Determines if transaction is closed. */ private volatile boolean isClosed; /** * {@link XmlIndexController} instance. */ private final IndexController indexController; /** * The tree modifier. */ private final TreeModifier treeModifier; /** * The revision to represent. */ private final int representRevision; /** * {@code true} if this page write trx will be bound to a node trx, {@code false} otherwise */ private final boolean isBoundToNodeTrx; private record IndexLogKeyToPageContainer(IndexType indexType, long recordPageKey, int indexNumber, int revisionNumber, PageContainer pageContainer) { } /** * The most recent page container. */ private IndexLogKeyToPageContainer mostRecentPageContainer; /** * The second most recent page container. */ private IndexLogKeyToPageContainer secondMostRecentPageContainer; /** * The most recent path summary page container. */ private IndexLogKeyToPageContainer mostRecentPathSummaryPageContainer; private final LinkedHashMap pageContainerCache; /** * Constructor. * * @param writer the page writer * @param log the transaction intent log * @param revisionRootPage the revision root page * @param pageRtx the page reading transaction used as a delegate * @param indexController the index controller, which is used to update indexes * @param representRevision the revision to represent * @param isBoundToNodeTrx {@code true} if this page write trx will be bound to a node trx, * {@code false} otherwise */ NodePageTrx(final TreeModifier treeModifier, final Writer writer, final TransactionIntentLog log, final RevisionRootPage revisionRootPage, final NodePageReadOnlyTrx pageRtx, final IndexController indexController, final int representRevision, final boolean isBoundToNodeTrx) { this.treeModifier = requireNonNull(treeModifier); storagePageReaderWriter = requireNonNull(writer); this.log = requireNonNull(log); newRevisionRootPage = requireNonNull(revisionRootPage); this.pageRtx = requireNonNull(pageRtx); this.indexController = requireNonNull(indexController); checkArgument(representRevision >= 0, "The represented revision must be >= 0."); this.representRevision = representRevision; this.isBoundToNodeTrx = isBoundToNodeTrx; mostRecentPageContainer = new IndexLogKeyToPageContainer(IndexType.DOCUMENT, -1, -1, -1, null); secondMostRecentPageContainer = mostRecentPageContainer; mostRecentPathSummaryPageContainer = new IndexLogKeyToPageContainer(IndexType.PATH_SUMMARY, -1, -1, -1, null); pageContainerCache = new LinkedHashMap<>(2_500) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > 2_500; } }; } @Override public Bytes newBufferedBytesInstance() { bufferBytes = Bytes.elasticByteBuffer(Writer.FLUSH_SIZE); return bufferBytes; } @Override public int getRevisionToRepresent() { pageRtx.assertNotClosed(); return representRevision; } @Override public TransactionIntentLog getLog() { pageRtx.assertNotClosed(); return log; } @Override public int getRevisionNumber() { pageRtx.assertNotClosed(); return newRevisionRootPage.getRevision(); } @Override public DataRecord prepareRecordForModification(final long recordKey, @NonNull final IndexType indexType, final int index) { pageRtx.assertNotClosed(); checkArgument(recordKey >= 0, "recordKey must be >= 0!"); requireNonNull(indexType); final long recordPageKey = pageRtx.pageKey(recordKey, indexType); final PageContainer cont = prepareRecordPage(recordPageKey, index, indexType); final var modifiedPage = cont.getModifiedAsUnorderedKeyValuePage(); DataRecord record = pageRtx.getValue(modifiedPage, recordKey); if (record == null) { final DataRecord oldRecord = pageRtx.getValue(cont.getCompleteAsUnorderedKeyValuePage(), recordKey); if (oldRecord == null) { throw new SirixIOException( "Cannot retrieve record from cache: (key: " + recordKey + ") (indexType: " + indexType + ") (index: " + index + ")"); } record = oldRecord; modifiedPage.setRecord(record); } return record; } @Override public DataRecord createRecord(@NonNull final DataRecord record, @NonNull final IndexType indexType, @NonNegative final int index) { pageRtx.assertNotClosed(); // Allocate record key and increment record count. // $CASES-OMITTED$ final long createdRecordKey = switch (indexType) { case DOCUMENT -> newRevisionRootPage.incrementAndGetMaxNodeKeyInDocumentIndex(); case CHANGED_NODES -> newRevisionRootPage.incrementAndGetMaxNodeKeyInChangedNodesIndex(); case RECORD_TO_REVISIONS -> newRevisionRootPage.incrementAndGetMaxNodeKeyInRecordToRevisionsIndex(); case PATH_SUMMARY -> { final PathSummaryPage pathSummaryPage = ((PathSummaryPage) newRevisionRootPage.getPathSummaryPageReference().getPage()); yield pathSummaryPage.incrementAndGetMaxNodeKey(index); } case CAS -> { final CASPage casPage = ((CASPage) newRevisionRootPage.getCASPageReference().getPage()); yield casPage.incrementAndGetMaxNodeKey(index); } case PATH -> { final PathPage pathPage = ((PathPage) newRevisionRootPage.getPathPageReference().getPage()); yield pathPage.incrementAndGetMaxNodeKey(index); } case NAME -> { final NamePage namePage = ((NamePage) newRevisionRootPage.getNamePageReference().getPage()); yield namePage.incrementAndGetMaxNodeKey(index); } default -> throw new IllegalStateException(); }; final long recordPageKey = pageRtx.pageKey(createdRecordKey, indexType); final PageContainer cont = prepareRecordPage(recordPageKey, index, indexType); final KeyValuePage modified = cont.getModifiedAsUnorderedKeyValuePage(); modified.setRecord(record); return record; } @Override public void removeRecord(final long recordKey, @NonNull final IndexType indexType, final int index) { pageRtx.assertNotClosed(); final long recordPageKey = pageRtx.pageKey(recordKey, indexType); final PageContainer cont = prepareRecordPage(recordPageKey, index, indexType); final DataRecord node = getRecord(recordKey, indexType, index); if (node == null) { throw new IllegalStateException("Node not found: " + recordKey); } final Node delNode = new DeletedNode(new NodeDelegate(node.getNodeKey(), -1, null, -1, pageRtx.getRevisionNumber(), (SirixDeweyID) null)); cont.getModifiedAsUnorderedKeyValuePage().setRecord(delNode); cont.getCompleteAsUnorderedKeyValuePage().setRecord(delNode); } @Override public V getRecord(final long recordKey, @NonNull final IndexType indexType, @NonNegative final int index) { pageRtx.assertNotClosed(); checkArgument(recordKey >= Fixed.NULL_NODE_KEY.getStandardProperty()); requireNonNull(indexType); // Calculate page. final long recordPageKey = pageRtx.pageKey(recordKey, indexType); final PageContainer pageCont = getPageContainer(recordPageKey, index, indexType); if (pageCont == null) { return pageRtx.getRecord(recordKey, indexType, index); } else { DataRecord node = pageRtx.getValue(((KeyValueLeafPage) pageCont.getModified()), recordKey); if (node == null) { node = pageRtx.getValue(((KeyValueLeafPage) pageCont.getComplete()), recordKey); } return (V) pageRtx.checkItemIfDeleted(node); } } @Override public String getName(final int nameKey, @NonNull final NodeKind nodeKind) { pageRtx.assertNotClosed(); final NamePage currentNamePage = getNamePage(newRevisionRootPage); return (currentNamePage == null || currentNamePage.getName(nameKey, nodeKind, pageRtx) == null) ? pageRtx.getName( nameKey, nodeKind) : currentNamePage.getName(nameKey, nodeKind, pageRtx); } @Override public int createNameKey(final @Nullable String name, @NonNull final NodeKind nodeKind) { pageRtx.assertNotClosed(); requireNonNull(nodeKind); final String string = name == null ? "" : name; final NamePage namePage = getNamePage(newRevisionRootPage); return namePage.setName(string, nodeKind, this); } @Override public void commit(final @Nullable PageReference reference) { if (reference == null) { return; } PageContainer container = log.get(reference); if (container == null) { return; } final var page = container.getModified(); reference.setPage(page); // Recursively commit indirectly referenced pages and then write self. page.commit(this); storagePageReaderWriter.write(this, reference, bufferBytes); container.getComplete().clearPage(); page.clearPage(); // Remove page reference. reference.setPage(null); } @Override public UberPage commit(@Nullable final String commitMessage, @Nullable final Instant commitTimestamp) { pageRtx.assertNotClosed(); pageRtx.resourceSession.getCommitLock().lock(); try { final Path commitFile = pageRtx.resourceSession.getCommitFile(); commitFile.toFile().deleteOnExit(); // Issues with windows that it's not created in the first time? createIfAbsent(commitFile); final PageReference uberPageReference = new PageReference(); final UberPage uberPage = pageRtx.getUberPage(); uberPageReference.setPage(uberPage); setUserIfPresent(); setCommitMessageAndTimestampIfRequired(commitMessage, commitTimestamp); parallelSerializationOfKeyValuePages(); // Recursively write indirectly referenced pages. uberPage.commit(this); uberPageReference.setPage(uberPage); storagePageReaderWriter.writeUberPageReference(this, uberPageReference, bufferBytes); uberPageReference.setPage(null); final int revision = uberPage.getRevisionNumber(); serializeIndexDefinitions(revision); log.clear(); pageContainerCache.clear(); // Delete commit file which denotes that a commit must write the log in the data file. try { deleteIfExists(commitFile); } catch (final IOException e) { throw new SirixIOException("Commit file couldn't be deleted!"); } } finally { pageRtx.resourceSession.getCommitLock().unlock(); } return readUberPage(); } private void setCommitMessageAndTimestampIfRequired(@Nullable String commitMessage, @Nullable Instant commitTimestamp) { if (commitMessage != null) { newRevisionRootPage.setCommitMessage(commitMessage); } if (commitTimestamp != null) { newRevisionRootPage.setCommitTimestamp(commitTimestamp); } } private void serializeIndexDefinitions(int revision) { if (!indexController.getIndexes().getIndexDefs().isEmpty()) { final Path indexes = pageRtx.getResourceSession() .getResourceConfig().resourcePath.resolve(ResourceConfiguration.ResourcePaths.INDEXES.getPath()) .resolve(revision + ".xml"); try (final OutputStream out = newOutputStream(indexes, CREATE)) { indexController.serialize(out); } catch (final IOException e) { throw new SirixIOException("Index definitions couldn't be serialized!", e); } } } private void parallelSerializationOfKeyValuePages() { log.getList() .parallelStream() .map(PageContainer::getModified) .filter(page -> page instanceof KeyValueLeafPage) .forEach(page -> { final Bytes bytes = Bytes.elasticByteBuffer(60_000); PageKind.KEYVALUELEAFPAGE.serializePage(this, bytes, page, SerializationType.DATA); bytes.clear(); }); } private UberPage readUberPage() { return (UberPage) storagePageReaderWriter.read(storagePageReaderWriter.readUberPageReference(), pageRtx); } private void createIfAbsent(final Path file) { while (!Files.exists(file)) { try { Files.createFile(file); } catch (final IOException e) { throw new SirixIOException(e); } } } private void setUserIfPresent() { final Optional optionalUser = pageRtx.resourceSession.getUser(); optionalUser.ifPresent(newRevisionRootPage::setUser); } @Override public UberPage rollback() { pageRtx.assertNotClosed(); log.clear(); return readUberPage(); } @Override public synchronized void close() { if (!isClosed) { pageRtx.assertNotClosed(); final UberPage lastUberPage = readUberPage(); pageRtx.resourceSession.setLastCommittedUberPage(lastUberPage); if (!isBoundToNodeTrx) { pageRtx.resourceSession.closePageWriteTransaction(pageRtx.getTrxId()); } log.close(); pageRtx.close(); storagePageReaderWriter.close(); isClosed = true; } } @Override public DeweyIDPage getDeweyIDPage(@NonNull RevisionRootPage revisionRoot) { // TODO return null; } private PageContainer getPageContainer(final @NonNegative long recordPageKey, final int indexNumber, final IndexType indexType) { PageContainer pageContainer = getMostRecentPageContainer(indexType, recordPageKey, indexNumber, newRevisionRootPage.getRevision()); if (pageContainer != null) { return pageContainer; } return pageContainerCache.computeIfAbsent(new IndexLogKey(indexType, recordPageKey, indexNumber, newRevisionRootPage.getRevision()), (unused) -> { final PageReference pageReference = pageRtx.getPageReference(newRevisionRootPage, indexType, indexNumber); final var leafPageReference = pageRtx.getLeafPageReference(pageReference, recordPageKey, indexNumber, indexType, newRevisionRootPage); return log.get(leafPageReference); }); } @Nullable private PageContainer getMostRecentPageContainer(IndexType indexType, long recordPageKey, @NonNegative int indexNumber, @NonNegative int revisionNumber) { if (indexType == IndexType.PATH_SUMMARY) { return mostRecentPathSummaryPageContainer.indexType == indexType && mostRecentPathSummaryPageContainer.indexNumber == indexNumber && mostRecentPathSummaryPageContainer.recordPageKey == recordPageKey && mostRecentPathSummaryPageContainer.revisionNumber == revisionNumber ? mostRecentPathSummaryPageContainer.pageContainer : null; } var pageContainer = mostRecentPageContainer.indexType == indexType && mostRecentPageContainer.recordPageKey == recordPageKey && mostRecentPageContainer.indexNumber == indexNumber && mostRecentPageContainer.revisionNumber == revisionNumber ? mostRecentPageContainer.pageContainer : null; if (pageContainer == null) { pageContainer = secondMostRecentPageContainer.indexType == indexType && secondMostRecentPageContainer.recordPageKey == recordPageKey && secondMostRecentPageContainer.indexNumber == indexNumber && secondMostRecentPageContainer.revisionNumber == revisionNumber ? secondMostRecentPageContainer.pageContainer : null; } return pageContainer; } /** * Prepare record page. * * @param recordPageKey the key of the record page * @param indexNumber the index number if it's a record-page of an index, {@code -1}, else * @param indexType the index type * @return {@link PageContainer} instance * @throws SirixIOException if an I/O error occurs */ private PageContainer prepareRecordPage(final @NonNegative long recordPageKey, final int indexNumber, final IndexType indexType) { assert indexType != null; PageContainer mostRecentPageContainer1 = getMostRecentPageContainer(indexType, recordPageKey, indexNumber, newRevisionRootPage.getRevision()); if (mostRecentPageContainer1 != null) { return mostRecentPageContainer1; } final Function fetchPageContainer = (key) -> { final PageReference pageReference = pageRtx.getPageReference(newRevisionRootPage, indexType, indexNumber); // Get the reference to the unordered key/value page storing the records. final PageReference reference = treeModifier.prepareLeafOfTree(pageRtx, log, getUberPage().getPageCountExp(indexType), pageReference, recordPageKey, indexNumber, indexType, newRevisionRootPage); var pageContainer = log.get(reference); if (pageContainer != null) { return pageContainer; } if (reference.getKey() == Constants.NULL_ID_LONG) { final KeyValueLeafPage completePage = new KeyValueLeafPage(recordPageKey, indexType, pageRtx); final KeyValueLeafPage modifyPage = new KeyValueLeafPage(completePage); pageContainer = PageContainer.getInstance(completePage, modifyPage); } else { pageContainer = dereferenceRecordPageForModification(reference); } assert pageContainer != null; // $CASES-OMITTED$ switch (indexType) { case DOCUMENT, CHANGED_NODES, RECORD_TO_REVISIONS, DEWEYID_TO_RECORDID, PATH_SUMMARY, PATH, CAS, NAME -> appendLogRecord(reference, pageContainer); default -> throw new IllegalStateException("Page kind not known!"); } return pageContainer; }; var currPageContainer = pageContainerCache.computeIfAbsent(new IndexLogKey(indexType, recordPageKey, indexNumber, newRevisionRootPage.getRevision()), fetchPageContainer); if (indexType == IndexType.PATH_SUMMARY) { mostRecentPathSummaryPageContainer = new IndexLogKeyToPageContainer(indexType, recordPageKey, indexNumber, newRevisionRootPage.getRevision(), currPageContainer); } else { secondMostRecentPageContainer = mostRecentPageContainer; mostRecentPageContainer = new IndexLogKeyToPageContainer(indexType, recordPageKey, indexNumber, newRevisionRootPage.getRevision(), currPageContainer); } return currPageContainer; } /** * Dereference record page reference. * * @param reference reference to leaf, that is the record page * @return dereferenced page */ private PageContainer dereferenceRecordPageForModification(final PageReference reference) { final List> pageFragments = pageRtx.getPageFragments(reference); final VersioningType versioningType = pageRtx.resourceSession.getResourceConfig().versioningType; final int mileStoneRevision = pageRtx.resourceSession.getResourceConfig().maxNumberOfRevisionsToRestore; return versioningType.combineRecordPagesForModification(pageFragments, mileStoneRevision, pageRtx, reference, log); } @Override public RevisionRootPage getActualRevisionRootPage() { pageRtx.assertNotClosed(); return newRevisionRootPage; } @Override protected @NonNull PageReadOnlyTrx delegate() { return pageRtx; } @Override public PageReadOnlyTrx getPageReadOnlyTrx() { return pageRtx; } @Override public PageTrx appendLogRecord(@NonNull final PageReference reference, @NonNull final PageContainer pageContainer) { requireNonNull(pageContainer); log.put(reference, pageContainer); return this; } @Override public PageContainer getLogRecord(final PageReference reference) { requireNonNull(reference); return log.get(reference); } @Override public PageTrx truncateTo(final int revision) { storagePageReaderWriter.truncateTo(this, revision); return this; } @Override public long getTrxId() { return pageRtx.getTrxId(); } @Override public CommitCredentials getCommitCredentials() { return pageRtx.getCommitCredentials(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy