Package com.persistit

Source Code of com.persistit.Tree

/**
* Copyright 2005-2012 Akiban Technologies, Inc.
*
* 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.
*/

package com.persistit;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import com.persistit.Accumulator.MaxAccumulator;
import com.persistit.Accumulator.MinAccumulator;
import com.persistit.Accumulator.SeqAccumulator;
import com.persistit.Accumulator.SumAccumulator;
import com.persistit.Version.PrunableVersion;
import com.persistit.Version.VersionCreator;
import com.persistit.exception.CorruptVolumeException;
import com.persistit.exception.PersistitException;
import com.persistit.exception.PersistitInterruptedException;
import com.persistit.exception.RollbackException;
import com.persistit.exception.TimeoutException;
import com.persistit.util.Debug;
import com.persistit.util.Util;

/**
* <p>
* Cached meta-data about a single B-Tree within a {@link Volume}. A
* <code>Tree</code> object keeps track of the <code>Volume</code>, the index
* root page, the index depth, various other statistics and the
* {@link Accumulator}s for a B-Tree.
* </p>
* <p>
* As of Persistit 3.3, this class supports version within transactions. A new
* <code>Tree</code> created within the cope of a {@link Transaction} is not
* visible within the other transactions until it commits. Similarly, if a
* <code>Tree</code> is removed within the scope of a transaction, other
* transactions that started before the current transaction commits will
* continue to be able to read and write the <code>Tree</code>. As a
* side-effect, the physical storage for a <code>Tree</code> is not deallocated
* until there are no remaining active transactions that started before the
* commit timestamp of the current transaction. Concurrent transactions that
* attempt to create or remove the same <code>Tree</code> instance are subject
* to a a write-write dependency (see {@link Transaction}); all but one such
* transaction must roll back.
* </p>
* <p>
* <code>Tree</code> instances are created by
* {@link Volume#getTree(String, boolean)}. If the <code>Volume</code> already
* has a B-Tree with the specified name, then the <code>Tree</code> object
* returned by <code>getTree</code> reflects the stored information. Otherwise,
* <code>getTree</code> can create a new B-Tree. In either case, the
* <code>Tree</code> is merely a transient in-memory cache for the B-Tree
* information ultimately stored on disk.
* </p>
* <p>
* Persistit ensures that <code>Tree</code> instances are unique, that is, for a
* given <code>Volume</code> and name, there is only one <code>Tree</code>. if
* multiple threads call {@link Volume#getTree(String, boolean)} for the same
* name on the same volume, the first such call will create a new
* <code>Tree</code> instance and subsequent calls will return the same
* instance.
* </p>
* <p>
* Each <code>Tree</code> may have up to 64 {@link Accumulator} instances that
* may be used to aggregate statistical information such as counters.
* <code>Accumulator</code>s work within the MVCC transaction scheme to provide
* highly concurrent access to a small number of variables that would otherwise
* cause a significant performance degradation.
* </p>
*/
public class Tree extends SharedResource {
    final static int MAX_SERIALIZED_SIZE = 512;
    final static int MAX_TREE_NAME_SIZE = 256;
    final static int MAX_ACCUMULATOR_COUNT = 64;

    private final String _name;
    private final Volume _volume;
    private final AtomicReference<Object> _appCache = new AtomicReference<Object>();
    private final AtomicInteger _handle = new AtomicInteger();

    private final TimelyResource<TreeVersion> _timelyResource;

    private final VersionCreator<TreeVersion> _creator = new VersionCreator<TreeVersion>() {

        @Override
        public TreeVersion createVersion(final TimelyResource<? extends TreeVersion> resource)
                throws PersistitException {
            return new TreeVersion();
        }
    };

    class TreeVersion implements PrunableVersion {
        volatile long _rootPageAddr;
        volatile int _depth;
        volatile long _generation = _persistit.getTimestampAllocator().updateTimestamp();
        final AtomicLong _changeCount = new AtomicLong();
        volatile boolean _pruned;
        private final Accumulator[] _accumulators = new Accumulator[MAX_ACCUMULATOR_COUNT];
        private final TreeStatistics _treeStatistics = new TreeStatistics();

        @Override
        public boolean prune() throws PersistitException {
            assert !_pruned;
            _volume.getStructure().deallocateTree(_rootPageAddr, _depth);
            discardAccumulators();
            _pruned = true;
            _rootPageAddr = -1;
            return true;
        }

        @Override
        public void vacate() {
            clearValid();
            _volume.getStructure().removed(Tree.this);
        }

        @Override
        public String toString() {
            return String.format("Tree(%d,%d)%s", _rootPageAddr, _depth, _pruned ? "#" : "");
        }

        /**
         * Forget about any instantiated accumulator and remove it from the
         * active list in Persistit. This should only be called in the during
         * the process of removing a tree.
         */
        void discardAccumulators() {
            for (int i = 0; i < _accumulators.length; ++i) {
                if (_accumulators[i] != null) {
                    _persistit.removeAccumulator(_accumulators[i]);
                    _accumulators[i] = null;
                }
            }
        }
    }

    /**
     * Unchecked wrapper for PersistitException thrown while trying to acquire a
     * TreeVersion.
     */
    public static class TreeVersionException extends RuntimeException {
        private static final long serialVersionUID = -6372589972106489591L;

        TreeVersionException(final Exception e) {
            super(e);
        }
    }

    Tree(final Persistit persistit, final Volume volume, final String name) {
        super(persistit);
        final int serializedLength = name.getBytes().length;
        if (serializedLength > MAX_TREE_NAME_SIZE) {
            throw new IllegalArgumentException("Tree name too long: " + name.length() + "(as " + serializedLength
                    + " bytes)");
        }
        _name = name;
        _volume = volume;
        _timelyResource = new TimelyResource<TreeVersion>(persistit);
    }

    TreeVersion version() {
        try {
            return _timelyResource.getVersion(_creator);
        } catch (final PersistitException e) {
            throw new TreeVersionException(e);
        }
    }

    public boolean isDeleted() throws TimeoutException, PersistitInterruptedException {
        return _timelyResource.isEmpty();
    }

    boolean isLive() throws TimeoutException, PersistitInterruptedException {
        return isValid() && !isDeleted();
    }

    boolean isTransactionPrivate(final boolean byStep) throws TimeoutException, PersistitInterruptedException {
        return _timelyResource.isTransactionPrivate(byStep);
    }

    boolean hasVersion(final long versionHandle) throws TimeoutException, PersistitInterruptedException {
        return _timelyResource.getVersion(versionHandle) != null;
    }

    void delete() throws RollbackException, PersistitException {
        _timelyResource.delete();
    }

    /**
     * @return The volume containing this <code>Tree</code>.
     */
    public Volume getVolume() {
        return _volume;
    }

    /**
     * @return This <code>Tree</code>'s name
     */
    public String getName() {
        return _name;
    }

    @Override
    public int hashCode() {
        return _volume.hashCode() ^ _name.hashCode();
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof Tree) {
            final Tree tree = (Tree) o;
            return _name.equals(tree._name) && _volume.equals(tree.getVolume());
        } else {
            return false;
        }
    }

    /**
     * Returns the page address of the root page of this <code>Tree</code>. The
     * root page will be a data page if the <code>Tree</code> has only one page,
     * or will be the top index page of the B-Tree.
     *
     * @return The page address
     */
    public long getRootPageAddr() {
        final TreeVersion version = version();
        return version._rootPageAddr;
    }

    /**
     * @return the number of levels of the <code>Tree</code>.
     */
    public int getDepth() {
        return version()._depth;
    }

    @Override
    public long getGeneration() {
        return version()._generation;
    }

    @Override
    void bumpGeneration() {
        version()._generation = _persistit.getTimestampAllocator().updateTimestamp();
    }

    void changeRootPageAddr(final long rootPageAddr, final int deltaDepth) throws PersistitException {
        Debug.$assert0.t(isOwnedAsWriterByMe());
        final TreeVersion version = version();
        version._rootPageAddr = rootPageAddr;
        version._depth += deltaDepth;
    }

    void bumpChangeCount() {
        //
        // Note: the changeCount only gets written when there's a structure
        // change in the tree that causes it to be committed.
        //
        version()._changeCount.incrementAndGet();
    }

    /**
     * @return The number of key-value insert/delete operations performed on
     *         this tree; does not including replacement of an existing value
     */
    long getChangeCount() {
        return version()._changeCount.get();
    }

    /**
     * Save a Tree in the directory
     *
     * @param value
     */
    int store(final byte[] bytes, final int index) {
        final byte[] nameBytes = Util.stringToBytes(_name);
        final TreeVersion version = version();
        Util.putLong(bytes, index, version._rootPageAddr);
        Util.putLong(bytes, index + 8, version._changeCount.get());
        Util.putShort(bytes, index + 16, version._depth);
        Util.putShort(bytes, index + 18, nameBytes.length);
        Util.putBytes(bytes, index + 20, nameBytes);
        return 20 + nameBytes.length;
    }

    /**
     * Load an existing Tree from the directory
     *
     * @param value
     */
    int load(final byte[] bytes, final int index, final int length) {
        final int nameLength = length < 20 ? -1 : Util.getShort(bytes, index + 18);
        if (nameLength < 1 || nameLength + 20 > length) {
            throw new IllegalStateException("Invalid tree record is too short for tree " + _name + ": " + length);
        }
        final String name = new String(bytes, index + 20, nameLength);
        if (!_name.equals(name)) {
            throw new IllegalStateException("Invalid tree name recorded: " + name + " for tree " + _name);
        }
        final TreeVersion version = version();
        version._rootPageAddr = Util.getLong(bytes, index);
        version._changeCount.set(Util.getLong(bytes, index + 8));
        version._depth = Util.getShort(bytes, index + 16);
        return length;
    }

    /**
     * Initialize a Tree.
     *
     * @param rootPageAddr
     * @throws PersistitException
     */
    void setRootPageAddress(final long rootPageAddr) throws PersistitException {
        final TreeVersion version = version();
        if (version._rootPageAddr != rootPageAddr) {
            // Derive the index depth
            Buffer buffer = null;
            try {
                buffer = getVolume().getStructure().getPool().get(_volume, rootPageAddr, false, true);
                final int type = buffer.getPageType();
                if (type < Buffer.PAGE_TYPE_DATA || type > Buffer.PAGE_TYPE_INDEX_MAX) {
                    throw new CorruptVolumeException(String.format("Tree root page %,d has invalid type %s",
                            rootPageAddr, buffer.getPageTypeName()));
                }
                version._rootPageAddr = rootPageAddr;
                version._depth = type - Buffer.PAGE_TYPE_DATA + 1;
            } finally {
                if (buffer != null) {
                    buffer.releaseTouched();
                }
            }
        }
    }

    /**
     * Invoked when this <code>Tree</code> is being deleted. This causes
     * subsequent operations by any <code>Exchange</code>s on this
     * <code>Tree</code> to fail.
     */
    void invalidate() {
        final TreeVersion version = version();
        super.clearValid();
        version._depth = -1;
        version._rootPageAddr = -1;
        version._generation = _persistit.getTimestampAllocator().updateTimestamp();
    }

    void setPrimordial() {
        _timelyResource.setPrimordial();
    }

    /**
     * @return a <code>TreeStatistics</code> object containing approximate
     *         counts of records added, removed and fetched from this
     *         </code>Tree</code>
     */
    public TreeStatistics getStatistics() {
        return version()._treeStatistics;
    }

    /**
     * @return a displayable description of the <code>Tree</code>, including its
     *         name, its internal tree index, its root page address, and its
     *         depth.
     */
    @Override
    public String toString() {
        final TreeVersion version = version();
        return "<Tree " + _name + " in volume " + _volume.getName() + " rootPageAddr=" + version._rootPageAddr
                + " depth=" + version._depth + " status=" + getStatusDisplayString() + ">";
    }

    /**
     * Store an Object with this Tree for the convenience of an application.
     *
     * @param appCache
     *            the object to be cached for application convenience.
     */
    public void setAppCache(final Object appCache) {
        _appCache.set(appCache);
    }

    /**
     * @return the object cached for application convenience
     */
    public Object getAppCache() {
        return _appCache.get();
    }

    /**
     * @return The handle value used to identify this <code>Tree</code> in the
     *         journal
     */
    public int getHandle() {
        return _handle.get();
    }

    /**
     * Assign and set the tree handle. The tree must may not be a member of a
     * temporary volume.
     *
     * @throws PersistitException
     */
    void loadHandle() throws PersistitException {
        assert !_volume.isTemporary() : "Handle allocation for temporary tree " + this;
        _persistit.getJournalManager().handleForTree(this);
    }

    /**
     * Return a <code>SumAccumulator</code> for this <code>Tree</code> and the
     * specified index value between 0 and 63, inclusive. If the
     * <code>Tree</code> does not yet have an <code>Accumulator</code> with the
     * specified index, this method creates one. Otherwise the previously
     * created <code>Accumulator</code>, which must be a
     * <code>SumAccumulator</code>, is returned.
     *
     * @param index
     *            Application-controlled value between 0 and 63, inclusive.
     * @return The <code>Accumulator</code>
     * @throws IllegalStateException
     *             if the previously created instance is not a
     *             <code>SumAccumulator</code>
     */
    public SumAccumulator getSumAccumulator(final int index) throws PersistitException {
        return (SumAccumulator) getAccumulator(Accumulator.Type.SUM, index);
    }

    /**
     * Return a <code>SeqAccumulator</code> for this <code>Tree</code> and the
     * specified index value between 0 and 63, inclusive. If the
     * <code>Tree</code> does not yet have an <code>Accumulator</code> with the
     * specified index, this method creates one. Otherwise the previously
     * created <code>Accumulator</code>, which must be a
     * <code>SeqAccumulator</code>, is returned.
     *
     * @param index
     *            Application-controlled value between 0 and 63, inclusive.
     * @return The <code>Accumulator</code>
     * @throws IllegalStateException
     *             if the previously created instance is not a
     *             <code>SeqAccumulator</code>
     */
    public SeqAccumulator getSeqAccumulator(final int index) throws PersistitException {
        return (SeqAccumulator) getAccumulator(Accumulator.Type.SEQ, index);
    }

    /**
     * Return a <code>MinAccumulator</code> for this <code>Tree</code> and the
     * specified index value between 0 and 63, inclusive. If the
     * <code>Tree</code> does not yet have an <code>Accumulator</code> with the
     * specified index, this method creates one. Otherwise the previously
     * created <code>Accumulator</code>, which must be a
     * <code>MinAccumulator</code>, is returned.
     *
     * @param index
     *            Application-controlled value between 0 and 63, inclusive.
     * @return The <code>Accumulator</code>
     * @throws IllegalStateException
     *             if the previously created instance is not a
     *             <code>MinAccumulator</code>
     */
    public MinAccumulator getMinAccumulator(final int index) throws PersistitException {
        return (MinAccumulator) getAccumulator(Accumulator.Type.MIN, index);
    }

    /**
     * Return a <code>MaxAccumulator</code> for this <code>Tree</code> and the
     * specified index value between 0 and 63, inclusive. If the
     * <code>Tree</code> does not yet have an <code>Accumulator</code> with the
     * specified index, this method creates one. Otherwise the previously
     * created <code>Accumulator</code>, which must be a
     * <code>MaxAccumulator</code>, is returned.
     *
     * @param index
     *            Application-controlled value between 0 and 63, inclusive.
     * @return The <code>Accumulator</code>
     * @throws IllegalStateException
     *             if the previously created instance is not a
     *             <code>MaxAccumulator</code>
     */
    public MaxAccumulator getMaxAccumulator(final int index) throws PersistitException {
        return (MaxAccumulator) getAccumulator(Accumulator.Type.MAX, index);
    }

    /**
     * <p>
     * Return an <code>Accumulator</code> for this Tree. The caller provides the
     * type (SUM, MAX, MIN or SEQ) of accumulator, and an index value between 0
     * and 63, inclusive. If the <code>Tree</code> does not yet have an
     * <code>Accumulator</code> with the specified index, this method creates
     * one of the the specified type. Otherwise the specified type must match
     * the type of the one previously.
     * </p>
     * <p>
     * This method is deprecated. One of the following methods should be used
     * instead:
     * <ul>
     * <li>{@link #getSumAccumulator(int)}</li>
     * <li>{@link #getSeqAccumulator(int)}</li>
     * <li>{@link #getMinAccumulator(int)}</li>
     * <li>{@link #getMaxAccumulator(int)}</li>
     * <ul>
     * </p>
     *
     * @param type
     *            Type of <code>Accumulator</code>
     * @param index
     *            Application-controlled value between 0 and 63, inclusive.
     * @return The <code>Accumulator</code>
     * @throws IllegalStateException
     *             if the supplied type does not match that of a previously
     *             created <code>Accumulator</code>
     */
    synchronized Accumulator getAccumulator(final Accumulator.Type type, final int index) throws PersistitException {
        if (index < 0 || index >= MAX_ACCUMULATOR_COUNT) {
            throw new IllegalArgumentException("Invalid accumulator index: " + index);
        }
        final TreeVersion version = version();
        Accumulator accumulator = version._accumulators[index];
        if (accumulator == null) {
            final AccumulatorState saved = Accumulator.getAccumulatorState(this, index);
            long savedValue = 0;
            if (saved != null) {
                if (!saved.getTreeName().equals(getName())) {
                    throw new IllegalStateException("AccumulatorState has wrong tree name: " + saved);
                }
                if (!saved.getType().equals(type)) {
                    throw new IllegalStateException("AccumulatorState has different type: " + saved);
                }
                savedValue = saved.getValue();
            }
            accumulator = Accumulator.accumulator(type, this, index, savedValue, _persistit.getTransactionIndex());
            version._accumulators[index] = accumulator;
            _persistit.addAccumulator(accumulator);
        } else if (accumulator.getType() != type) {
            throw new IllegalStateException("Wrong type " + accumulator + " is not a " + type + " accumulator");
        }
        return accumulator;
    }

    /**
     * Set the handle used to identify this Tree in the journal. May be invoked
     * only once.
     *
     * @param handle
     * @return the handle
     * @throws IllegalStateException
     *             if the handle has already been set
     */
    int setHandle(final int handle) {
        if (!_handle.compareAndSet(0, handle)) {
            throw new IllegalStateException("Tree handle already set");
        }
        return handle;
    }

    /**
     * Reset the handle to zero. Intended for use only by tests.
     */
    void resetHandle() {
        _handle.set(0);
    }

}
TOP

Related Classes of com.persistit.Tree

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.