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

com.sleepycat.je.log.entry.LNLogEntry Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.log.entry;

import static com.sleepycat.je.EnvironmentFailureException.unexpectedState;

import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;

import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.DupKeyData;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.TTL;
import com.sleepycat.je.log.LogEntryHeader;
import com.sleepycat.je.log.LogEntryType;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.log.VersionedWriteLoggable;
import com.sleepycat.je.tree.Key;
import com.sleepycat.je.tree.LN;
import com.sleepycat.je.tree.VersionedLN;
import com.sleepycat.je.txn.Txn;
import com.sleepycat.je.utilint.DbLsn;
import com.sleepycat.je.utilint.VLSN;

/**
 * An LNLogEntry is the in-memory image of an LN logrec describing a write op
 * (insertion, update, or deletion) performed by a locker T on a record R.
 * T always locks R in exclusive (WRITE or WRITE_RANGE) mode before performing
 * any write ops on it, and it retains its exclusive lock on R until it
 * terminates (commits or aborts). (Non-transactional lockers can be viewed as
 * "simple" transactions that perform at most one write op, and then
 * immediately commit).
 *
 * On disk, an LN logrec contains :
 *
 * {@literal
 * 1 <= version <= 5
 *
 *   LN data
 *   databaseid
 *   key
 *   abortLsn             -- if transactional
 *   abortKnownDeleted    -- if transactional
 *   txn id               -- if transactional
 *   prev LSN of same txn -- if transactional
 *
 * 6 <= versions <= 10 :
 *
 *   databaseid
 *   abortLsn             -- if transactional
 *   abortKnownDeleted    -- if transactional
 *   txn id               -- if transactional
 *   prev LSN of same txn -- if transactional
 *   data
 *   key
 *
 * 11 == version :
 *
 *   databaseid
 *   abortLsn               -- if transactional
 *   1-byte flags
 *     abortKnownDeleted
 *     embeddedLN
 *     haveAbortKey
 *     haveAbortData
 *     haveAbortVLSN
 *   txn id                 -- if transactional
 *   prev LSN of same txn   -- if transactional
 *   abort key              -- if haveAbortKey
 *   abort data             -- if haveAbortData
 *   abort vlsn             -- if haveAbortVLSN
 *   data
 *   key
 *
 *   In forReplication mode, these flags and fields are omitted:
 *     embeddedLN, haveAbortKey, haveAbortData, haveAbortVLSN,
 *     abort key, abort data, abort vlsn
 *
 * 12 <= version :
 *
 *   1-byte flags
 *     abortKnownDeleted
 *     embeddedLN
 *     haveAbortKey
 *     haveAbortData
 *     haveAbortVLSN
 *     haveAbortLSN
 *     haveAbortExpiration
 *     haveExpiration
 *   databaseid
 *   abortLsn                -- if transactional and haveAbortLSN
 *   txn id                  -- if transactional
 *   prev LSN of same txn    -- if transactional
 *   abort key               -- if haveAbortKey
 *   abort data              -- if haveAbortData
 *   abort vlsn              -- if haveAbortVLSN
 *   abort expiration        -- if haveAbortExpiration
 *   expiration              -- if haveExpiration
 *   data
 *   key
 *
 *   In forReplication mode, these flags and fields are omitted:
 *     abortKnownDeleted, embeddedLN, haveAbortKey, haveAbortData,
 *     haveAbortVLSN, abort key, abort data, abort vlsn,
 *     haveAbortLSN, abortLsn, haveAbortExpiration, abort expiration
 *
 * 16 <= version :
 *
 *   1-byte flags
 *     abortKnownDeleted
 *     embeddedLN
 *     haveAbortKey
 *     haveAbortData
 *     haveAbortVLSN
 *     haveAbortLSN
 *     haveAbortExpiration
 *     haveExpiration
 *   1-byte flags2
 *     havePriorSize
 *     havePriorFile
 *   databaseid
 *   abortLsn                -- if transactional and haveAbortLSN
 *   txn id                  -- if transactional
 *   prev LSN of same txn    -- if transactional
 *   abort key               -- if haveAbortKey
 *   abort data              -- if haveAbortData
 *   abort vlsn              -- if haveAbortVLSN
 *   abort expiration        -- if haveAbortExpiration
 *   expiration              -- if haveExpiration
 *   priorSize               -- if havePriorSize
 *   priorFile               -- if havePriorFile
 *   data
 *   key
 * }
 *
 *   In forReplication mode, these flags and fields are omitted:
 *     abortKnownDeleted, embeddedLN, haveAbortKey, haveAbortData,
 *     haveAbortVLSN, abort key, abort data, abort vlsn,
 *     haveAbortLSN, abortLsn, haveAbortExpiration, abort expiration,
 *     havePriorSize, priorSize, havePriorFile, priorFile
 *
 * NOTE: LNLogEntry is sub-classed by NameLNLogEntry, which adds some extra
 * fields after the record key.
 */
public class LNLogEntry extends BaseReplicableEntry {

    /* flags */
    private static final byte ABORT_KD_MASK = 0x1;
    private static final byte EMBEDDED_LN_MASK = 0x2;
    private static final byte HAVE_ABORT_KEY_MASK = 0x4;
    private static final byte HAVE_ABORT_DATA_MASK = 0x8;
    private static final byte HAVE_ABORT_VLSN_MASK = 0x10;
    private static final byte HAVE_ABORT_LSN_MASK = 0x20;
    private static final byte HAVE_ABORT_EXPIRATION_MASK = 0x40;
    private static final byte HAVE_EXPIRATION_MASK = (byte) 0x80;
    /* flags2 */
    private static final byte HAVE_PRIOR_SIZE_MASK = 0x1;
    private static final byte HAVE_PRIOR_FILE_MASK = 0x2;

    /**
     * Used for computing the minimum log space used by an LNLogEntry.
     */
    public static final int MIN_LOG_SIZE = 2 + // Flags
                                           1 + // DatabaseId
                                           1 + // LN with zero-length data
                                           LogEntryHeader.MIN_HEADER_SIZE;

    /**
     * The log version when the most recent format change for this entry was
     * made (including any changes to the format of the underlying LN and other
     * loggables).
     *
     * @see #getLastFormatChange
     */
    private static final int LAST_FORMAT_CHANGE = 16;

    /*
     * Persistent fields.
     */

    /*
     * The id of the DB containing the record.
     */
    private DatabaseId dbId;

    /*
     * The Txn performing the write op. It is null for non-transactional DBs.
     * On disk we store only the txn id and the LSN of the previous logrec
     * (if any) generated by this txn.
     */
    private Txn txn;

    /*
     * The LSN of the record's "abort" version, i.e., the version to revert to
     * if this logrec must be undone as a result of a txn abort. It is set to
     * the most recent version before the record was locked by the locker T
     * associated with this logrec. Because T locks R before it writes it, the
     * abort version is always a committed version.
     *
     * It is null for non-transactional lockers, because such lockers never
     * abort.
     */
    private long abortLsn = DbLsn.NULL_LSN;

    /*
     * Whether the record's abort version was a deleted version or not.
     */
    private boolean abortKnownDeleted;

    /*
     * The key of the record's abort version, if haveAbortKey is true;
     * null otherwise.
     */
    private byte[] abortKey = null;

    /*
     * The data portion of the record's abort version, if haveAbortData is
     * true; null otherwise.
     */
    private byte[] abortData = null;

    /*
     * The VLSN of the record's abort version, if haveAbortVLSN is true;
     * NULL_VLSN otherwise.
     */
    private long abortVLSN = VLSN.NULL_VLSN_SEQUENCE;

    /* Abort expiration time in days or hours. */
    private int abortExpiration = 0;
    private boolean abortExpirationInHours = false;

    /*
     * True if the logrec stores an abort LSN, which is the case only if
     * (a) this is a transactional logrec (b) the abort LSN is non-null.
     */
    private boolean haveAbortLSN;

    /*
     * True if the logrec stores an abort key, which is the case only if
     * (a) this is a transactional logrec, (b) the record's abort version
     * was embedded in the BIN, and (c) the DB allows key updates.
     */
    private boolean haveAbortKey;

    /*
     * True if the logrec stores abort data, which is the case only if
     * (a) this is a transactional logrec and (b) the record's abort
     * version was embedded in the BIN.
     */
    private boolean haveAbortData;

    /*
     * True if the logrec stores an abort VLSN, which is the case only if
     * (a) this is a transactional logrec (b) the record's abort version
     * was embedded in the BIN, and (c) VLSN caching is enabled.
     */
    private boolean haveAbortVLSN;

    /*
     * True if the logrec stores an abort expiration, which is the case only if
     * (a) this is a transactional logrec (b) the record's abort version has a
     * non-zero expiration.
     */
    private boolean haveAbortExpiration;

    /*
     * True if the logrec stores a non-zero expiration.
     */
    private boolean haveExpiration;

    /*
     * True if the logrec stores the size of the obsolete prior version.
     * Used to count the prior version obsolete during recovery.
     *
     * The size is stored if has the LN has prior version (it is not a pure
     * insertion) and the prior version is not immediately obsolete.
     */
    private boolean havePriorSize;

    /*
     * True if the logrec stores the file of the obsolete prior version.
     * Used to count the prior version obsolete during recovery. If false and
     * havePriorSize is true, the file of the abortLsn is used.
     *
     * The file number is stored only if havePriorSize is true. And it is
     * stored only if the abortLsn is absent (because the LN is not txnal) or
     * the abortLsn not the prior prior version (because the prior version is
     * part of the same txn).
     */
    private boolean havePriorFile;

    /*
     * Whether, after the write op described by this logrec, the record is
     * embedded in the BIN or not.
     */
    private boolean embeddedLN;

    /*
     * The LN storing the record's data, after the write op described by this
     * logrec. The ln has a null data value if the write op is a deletion. For
     * replicated DBs, the ln contains the record's VLSN as well.
     */
    private LN ln;

    /*
     * The value of the record's key, after the write op described by this
     * logrec.
     */
    private byte[] key;

    /* Expiration time in days or hours. */
    private int expiration;
    private boolean expirationInHours;

    /* Use for obsolete counting during recovery. */
    private int priorSize;
    private long priorFile = DbLsn.MAX_FILE_NUM;

    /*
     * Transient fields.
     */

    /* Transient field for duplicates conversion and user key/data methods. */
    enum DupStatus { UNKNOWN, NEED_CONVERSION, DUP_DB, NOT_DUP_DB }
    private DupStatus dupStatus;

    /* For construction of VersionedLN, when VLSN is preserved. */
    private final Constructor versionedLNConstructor;

    /**
     * Creates an instance to read an entry.
     *
     * @param  the type of the contained LN
     * @param cls the class of the contained LN
     * @return the log entry
     */
    public static  LNLogEntry create(final Class cls) {
        return new LNLogEntry<>(cls);
    }

    /* Constructor to read an entry. */
    LNLogEntry(final Class cls) {
        super(cls);
        if (cls == LN.class) {
            versionedLNConstructor = getNoArgsConstructor(VersionedLN.class);
        } else {
            versionedLNConstructor = null;
        }
    }

    /* Constructor to write an entry. */
    public LNLogEntry(
        LogEntryType entryType,
        DatabaseId dbId,
        Txn txn,
        long abortLsn,
        boolean abortKD,
        byte[] abortKey,
        byte[] abortData,
        long abortVLSN,
        int abortExpiration,
        boolean abortExpirationInHours,
        byte[] key,
        T ln,
        boolean embeddedLN,
        int expiration,
        boolean expirationInHours,
        int priorSize,
        long priorLsn) {

        setLogType(entryType);
        this.dbId = dbId;
        this.txn = txn;
        this.abortLsn = abortLsn;
        this.abortKnownDeleted = abortKD;
        this.abortKey = abortKey;
        this.abortData = abortData;
        this.abortVLSN = abortVLSN;
        this.abortExpiration = abortExpiration;
        this.abortExpirationInHours = abortExpirationInHours;

        haveAbortLSN = (abortLsn != DbLsn.NULL_LSN);
        haveAbortKey = (abortKey != null);
        haveAbortData = (abortData != null);
        haveAbortVLSN = !VLSN.isNull(abortVLSN);
        haveAbortExpiration = (abortExpiration != 0);
        haveExpiration = (expiration != 0);

        this.embeddedLN = embeddedLN;
        this.key = key;
        this.ln = ln;
        this.expiration = expiration;
        this.expirationInHours = expirationInHours;

        this.priorSize = priorSize;
        havePriorSize = (priorSize != 0);

        if (havePriorSize == (priorLsn == DbLsn.NULL_LSN)) {
            throw EnvironmentFailureException.unexpectedState(
                "priorSize=" + priorSize +
                " priorLsn=" + DbLsn.getNoFormatString(priorLsn));
        }

        priorFile = (!havePriorSize || priorLsn == abortLsn) ?
            DbLsn.MAX_FILE_NUM : DbLsn.getFileNumber(priorLsn);

        havePriorFile = (priorFile != DbLsn.MAX_FILE_NUM);

        versionedLNConstructor = null;

        /* A txn should only be provided for transactional entry types. */
        assert(entryType.isTransactional() == (txn != null));
    }

    private void reset() {
        dbId = null;
        txn = null;
        abortLsn = DbLsn.NULL_LSN;
        abortKnownDeleted = false;
        abortKey = null;
        abortData = null;
        abortVLSN = VLSN.NULL_VLSN_SEQUENCE;
        abortExpiration = 0;
        abortExpirationInHours = false;

        haveAbortLSN = false;
        haveAbortKey = false;
        haveAbortData = false;
        haveAbortVLSN = false;
        haveAbortExpiration = false;
        haveExpiration = false;
        havePriorSize = false;
        havePriorFile = false;

        embeddedLN = false;
        key = null;
        ln = null;
        expiration = 0;
        expirationInHours = false;
        priorSize = 0;
        priorFile = DbLsn.MAX_FILE_NUM;

        dupStatus = null;
    }

    @Override
    public void readEntry(
        EnvironmentImpl envImpl,
        LogEntryHeader header,
        ByteBuffer entryBuffer) {

        /* Subclasses must call readBaseLNEntry. */
        assert getClass() == LNLogEntry.class;

        /*
         * Prior to version 8, the optimization to omit the key size was
         * mistakenly not applied to internal LN types such as FileSummaryLN
         * and MapLN, and was only applied to user LN types.  The optimization
         * should be applicable whenever LNLogEntry is not subclassed to add
         * additional fields. [#18055]
         */
        final boolean keyIsLastSerializedField =
            header.getVersion() >= 8 || entryType.isUserLNType();

        readBaseLNEntry(envImpl, header, entryBuffer,
                        keyIsLastSerializedField);
    }

    /**
     * Method shared by LNLogEntry subclasses.
     *
     * @param keyIsLastSerializedField specifies whether the key length can be
     * omitted because the key is the last field.  This should be false when
     * an LNLogEntry subclass adds fields to the serialized format.
     */
    final void readBaseLNEntry(
        EnvironmentImpl envImpl,
        LogEntryHeader header,
        ByteBuffer entryBuffer,
        boolean keyIsLastSerializedField) {

        reset();

        int logVersion = header.getVersion();
        boolean unpacked = (logVersion < 6);
        int recStartPosition = entryBuffer.position();

        if (logVersion >= 12) {
            byte flags = entryBuffer.get();
            byte flags2 = (logVersion >= 16) ? entryBuffer.get() : (byte) 0;
            setFlags(flags, flags2);
        }

        /*
         * For log version 6 and above we store the key last so that we can
         * avoid storing the key size. Instead, we derive it from the LN size
         * and the total entry size. The DatabaseId is also packed.
         */
        if (logVersion < 6) {
            /* LN is first for log versions prior to 6. */
            ln = newLNInstance(envImpl);
            ln.readFromLog(entryBuffer, logVersion);
        }

        /* DatabaseImpl Id. */
        dbId = new DatabaseId();
        dbId.readFromLog(entryBuffer, logVersion);

        /* Key. */
        if (logVersion < 6) {
            key = LogUtils.readByteArray(entryBuffer, true/*unpacked*/);
        }

        if (entryType.isTransactional()) {

            /*
             * AbortLsn. If it was a marker LSN that was used to fill in a
             * create, mark it null.
             */
            if (haveAbortLSN || logVersion < 12) {
                abortLsn = LogUtils.readLong(entryBuffer, unpacked);
                if (DbLsn.getFileNumber(abortLsn) ==
                    DbLsn.getFileNumber(DbLsn.NULL_LSN)) {
                    abortLsn = DbLsn.NULL_LSN;
                }
            }

            if (logVersion < 12) {
                setFlags(entryBuffer.get(), (byte) 0);
                haveAbortLSN = (abortLsn != DbLsn.NULL_LSN);
            }

            /* txn id and prev LSN by same txn. */
            txn = new Txn();
            txn.readFromLog(entryBuffer, logVersion);

        } else if (logVersion == 11) {
            setFlags(entryBuffer.get(), (byte) 0);
        }

        if (logVersion >= 11) {
            if (haveAbortKey) {
                abortKey = LogUtils.readByteArray(entryBuffer, false);
            }
            if (haveAbortData) {
                abortData = LogUtils.readByteArray(entryBuffer, false);
            }
            if (haveAbortVLSN) {
                abortVLSN = LogUtils.readPackedLong(entryBuffer);
            }
        }

        if (logVersion >= 12) {
            if (haveAbortExpiration) {
                abortExpiration = LogUtils.readPackedInt(entryBuffer);
                if (abortExpiration < 0) {
                    abortExpiration = (- abortExpiration);
                    abortExpirationInHours = true;
                }
            }
            if (haveExpiration) {
                expiration = LogUtils.readPackedInt(entryBuffer);
                if (expiration < 0) {
                    expiration = (- expiration);
                    expirationInHours = true;
                }
            }
        }

        if (logVersion >= 16) {
            if (havePriorSize) {
                priorSize = LogUtils.readPackedInt(entryBuffer);
            }
            if (havePriorFile) {
                priorFile = LogUtils.readPackedLong(entryBuffer);
            }
        }

        if (logVersion >= 6) {

            ln = newLNInstance(envImpl);
            ln.readFromLog(entryBuffer, logVersion);

            int keySize;
            if (keyIsLastSerializedField) {
                int bytesWritten = entryBuffer.position() - recStartPosition;
                keySize = header.getItemSize() - bytesWritten;
            } else {
                keySize = LogUtils.readPackedInt(entryBuffer);
            }
            key = LogUtils.readBytesNoLength(entryBuffer, keySize);
        }

        /* Save transient fields after read. */

        if (header.getVLSN() != null) {
            ln.setVLSNSequence(header.getVLSN().getSequence());
        }

        /* Dup conversion will be done by postFetchInit. */
        dupStatus =
            (logVersion < 8) ? DupStatus.NEED_CONVERSION : DupStatus.UNKNOWN;
    }

    private void setFlags(final byte flags, final byte flags2) {

        embeddedLN = ((flags & EMBEDDED_LN_MASK) != 0);
        abortKnownDeleted = ((flags & ABORT_KD_MASK) != 0);
        haveAbortLSN = ((flags & HAVE_ABORT_LSN_MASK) != 0);
        haveAbortKey = ((flags & HAVE_ABORT_KEY_MASK) != 0);
        haveAbortData = ((flags & HAVE_ABORT_DATA_MASK) != 0);
        haveAbortVLSN = ((flags & HAVE_ABORT_VLSN_MASK) != 0);
        haveAbortExpiration = ((flags & HAVE_ABORT_EXPIRATION_MASK) != 0);
        haveExpiration = ((flags & HAVE_EXPIRATION_MASK) != 0);

        havePriorSize = ((flags2 & HAVE_PRIOR_SIZE_MASK) != 0);
        havePriorFile = ((flags2 & HAVE_PRIOR_FILE_MASK) != 0);
    }

    @Override
    public boolean hasReplicationFormat() {
        return true;
    }

    @Override
    public boolean isReplicationFormatWorthwhile(final ByteBuffer logBuffer,
                                                 final int srcVersion,
                                                 final int destVersion) {

        /* The replication format is optimized only in versions >= 11. */
        if (destVersion < 11) {
            return false;
        }

        /*
         * It is too much trouble to parse versions older than 12, because the
         * flags are not at the front in older versions.
         */
        if (srcVersion < 12) {
            return false;
        }

        final byte flags = logBuffer.get(0);

        /*
         * If we have an abort key or data, assume that the savings is
         * substantial enough to be worthwhile.
         *
         * The abort key is unusual and implies that data is hidden in the key
         * using a partial comparator, so we assume it is probably large,
         * relative to the total size.
         *
         * If there is abort data, it may be small, however, because the
         * presence of abort data implies that this is an update or deletion,
         * there will also be an abort LSN and an abort VLSN (with HA). Plus,
         * abort data is likely to be around the same size as the non-abort
         * data, and keys are normally smallish, meaning that the abort data is
         * largish relative to the total record size. So we assume the savings
         * are substantial enough.
         */
        return (flags &
            (HAVE_ABORT_KEY_MASK | HAVE_ABORT_DATA_MASK)) != 0;
    }

    /**
     * newLNInstance usually returns exactly the type of LN of the type that
     * was contained in in the log. For example, if a LNLogEntry holds a MapLN,
     * newLNInstance will return that MapLN. There is one extra possibility for
     * vanilla (data record) LNs. In that case, this method may either return a
     * LN or a generated type, the VersionedLN, which adds the vlsn information
     * from the log header to the LN object.
     */
    LN newLNInstance(EnvironmentImpl envImpl) {
        if (versionedLNConstructor != null && envImpl.getPreserveVLSN()) {
            return newInstanceOfType(versionedLNConstructor);
        }
        return newInstanceOfType();
    }

    @Override
    public StringBuilder dumpEntry(StringBuilder sb, boolean verbose) {

        dbId.dumpLog(sb, verbose);

        ln.dumpKey(sb, key);
        ln.dumpLog(sb, verbose);

        sb.append("");

        if (haveExpiration) {
            sb.append("");
        }

        if (havePriorSize || havePriorFile) {
            sb.append("");
        }

        if (entryType.isTransactional()) {

            txn.dumpLog(sb, verbose);

            sb.append("");

            sb.append("");

            if (haveAbortKey) {
                sb.append(Key.dumpString(abortKey, "abortKey", 0));
            }
            if (haveAbortData) {
                sb.append(Key.dumpString(abortData, "abortData", 0));
            }
            if (haveAbortVLSN) {
                sb.append("");
            }
            if (haveAbortExpiration) {
                sb.append("");
            }
        }

        return sb;
    }

    @Override
    public void dumpRep(StringBuilder sb) {
        if (entryType.isTransactional()) {
            sb.append(" txn=").append(txn.getId());
        }
    }

    @Override
    public LN getMainItem() {
        return ln;
    }

    @Override
    public long getTransactionId() {
        if (entryType.isTransactional()) {
            return txn.getId();
        }
        return 0;
    }

    /*
     * Writing support.
     */

    @Override
    public int getLastFormatChange() {
        return LAST_FORMAT_CHANGE;
    }

    @Override
    public Collection getEmbeddedLoggables() {
        return Arrays.asList(new LN(), new DatabaseId(), new Txn());
    }

    @Override
    public int getSize(final int logVersion, final boolean forReplication) {

        assert getClass() == LNLogEntry.class;

        return getBaseLNEntrySize(
            logVersion, true /*keyIsLastSerializedField*/, forReplication);
    }

    /**
     * Method shared by LNLogEntry subclasses.
     *
     * @param keyIsLastSerializedField specifies whether the key length can be
     * omitted because the key is the last field.  This should be false when
     * an LNLogEntry subclass adds fields to the serialized format.
     */
    final int getBaseLNEntrySize(
        final int logVersion,
        final boolean keyIsLastSerializedField,
        final boolean forReplication) {

        int size = ln.getLogSize(logVersion, forReplication) +
            dbId.getLogSize(logVersion, forReplication) +
            key.length;

        if (!keyIsLastSerializedField) {
            size += LogUtils.getPackedIntLogSize(key.length);
        }

        if (entryType.isTransactional() || logVersion >= 11) {
            size += 1;   // flags
        }

        if (logVersion >= 16) {
            size += 1;   // flags2
        }

        if (entryType.isTransactional()) {
            if (logVersion < 12 || (haveAbortLSN && !forReplication)) {
                size += LogUtils.getPackedLongLogSize(abortLsn);
            }
            size += txn.getLogSize(logVersion, forReplication);
        }

        if (!forReplication) {
            if (logVersion >= 11) {
                if (haveAbortKey) {
                    size += LogUtils.getByteArrayLogSize(abortKey);
                }
                if (haveAbortData) {
                    size += LogUtils.getByteArrayLogSize(abortData);
                }
                if (haveAbortVLSN) {
                    size += LogUtils.getPackedLongLogSize(abortVLSN);
                }
            }
            if (logVersion >= 12) {
                if (haveAbortExpiration) {
                    size += LogUtils.getPackedIntLogSize(
                        abortExpirationInHours ?
                            (-abortExpiration) : abortExpiration);
                }
            }
            if (logVersion >= 16) {
                if (havePriorSize) {
                    size += LogUtils.getPackedIntLogSize(priorSize);
                }
                if (havePriorFile) {
                    size += LogUtils.getPackedLongLogSize(priorFile);
                }
            }
        }

        if (logVersion >= 12) {
            if (haveExpiration) {
                size += LogUtils.getPackedIntLogSize(
                    expirationInHours ? (- expiration) : expiration);
            }
        }

        return size;
    }

    @Override
    public void writeEntry(final ByteBuffer destBuffer,
                           final int logVersion,
                           final boolean forReplication) {

        /* Subclasses must call writeBaseLNEntry. */
        assert getClass() == LNLogEntry.class;

        writeBaseLNEntry(
            destBuffer, logVersion, true /*keyIsLastSerializedField*/,
            forReplication);
    }

    /**
     * Method shared by LNLogEntry subclasses.
     *
     * @param keyIsLastSerializedField specifies whether the key length can be
     * omitted because the key is the last field.  This should be false when
     * an LNLogEntry subclass adds fields to the serialized format.
     */
    final void writeBaseLNEntry(
        final ByteBuffer destBuffer,
        final int logVersion,
        final boolean keyIsLastSerializedField,
        final boolean forReplication) {

        byte flags = 0;
        byte flags2 = 0;

        if (entryType.isTransactional() &&
            (logVersion < 12 || !forReplication)) {

            if (abortKnownDeleted) {
                flags |= ABORT_KD_MASK;
            }
            if (haveAbortLSN) {
                flags |= HAVE_ABORT_LSN_MASK;
            }
        }

        if (!forReplication) {
            if (logVersion >= 11) {
                if (embeddedLN) {
                    flags |= EMBEDDED_LN_MASK;
                }
                if (haveAbortKey) {
                    flags |= HAVE_ABORT_KEY_MASK;
                }
                if (haveAbortData) {
                    flags |= HAVE_ABORT_DATA_MASK;
                }
                if (haveAbortVLSN) {
                    flags |= HAVE_ABORT_VLSN_MASK;
                }
            }
            if (logVersion >= 12) {
                if (haveAbortExpiration) {
                    flags |= HAVE_ABORT_EXPIRATION_MASK;
                }
            }
            if (logVersion >= 16) {
                if (havePriorSize) {
                    flags2 |= HAVE_PRIOR_SIZE_MASK;
                }
                if (havePriorFile) {
                    flags2 |= HAVE_PRIOR_FILE_MASK;
                }
            }
        }

        if (logVersion >= 12) {
            if (haveExpiration) {
                flags |= HAVE_EXPIRATION_MASK;
            }
            destBuffer.put(flags);
        }

        if (logVersion >= 16) {
            destBuffer.put(flags2);
        }

        dbId.writeToLog(destBuffer, logVersion, forReplication);

        if (entryType.isTransactional()) {

            if (logVersion < 12 || (haveAbortLSN && !forReplication)) {
                LogUtils.writePackedLong(destBuffer, abortLsn);
            }

            if (logVersion < 12) {
                destBuffer.put(flags);
            }

            txn.writeToLog(destBuffer, logVersion, forReplication);

        } else if (logVersion == 11) {
            destBuffer.put(flags);
        }

        if (!forReplication) {
            if (logVersion >= 11) {
                if (haveAbortKey) {
                    LogUtils.writeByteArray(destBuffer, abortKey);
                }
                if (haveAbortData) {
                    LogUtils.writeByteArray(destBuffer, abortData);
                }
                if (haveAbortVLSN) {
                    LogUtils.writePackedLong(destBuffer, abortVLSN);
                }
            }
            if (logVersion >= 12) {
                if (haveAbortExpiration) {
                    LogUtils.writePackedInt(
                        destBuffer,
                        abortExpirationInHours ?
                            (-abortExpiration) : abortExpiration);
                }
            }
        }

        if (logVersion >= 12) {
            if (haveExpiration) {
                LogUtils.writePackedInt(
                    destBuffer,
                    expirationInHours ? (-expiration) : expiration);
            }
        }

        if (!forReplication) {
            if (logVersion >= 16) {
                if (havePriorSize) {
                    LogUtils.writePackedInt(destBuffer, priorSize);
                }
                if (havePriorFile) {
                    LogUtils.writePackedLong(destBuffer, priorFile);
                }
            }
        }

        ln.writeToLog(destBuffer, logVersion, forReplication);

        if (!keyIsLastSerializedField) {
            LogUtils.writePackedInt(destBuffer, key.length);
        }
        LogUtils.writeBytesNoLength(destBuffer, key);
    }

    @Override
    public boolean isImmediatelyObsolete(DatabaseImpl dbImpl) {
        return (ln.isDeleted() ||
                embeddedLN ||
                dbImpl.isLNImmediatelyObsolete());
    }

    @Override
    public boolean isDeleted() {
        return ln.isDeleted();
    }

    /**
     * For LN entries, we need to record the latest LSN for that node with the
     * owning transaction, within the protection of the log latch. This is a
     * callback for the log manager to do that recording.
     */
    @Override
    public void postLogWork(
        LogEntryHeader header,
        long justLoggedLsn,
        VLSN vlsn) {

        if (entryType.isTransactional()) {
            txn.addLogInfo(justLoggedLsn);
        }

        /* Save transient fields after write. */
        if (vlsn != null) {
            ln.setVLSNSequence(vlsn.getSequence());
        }
    }

    @Override
    public void postFetchInit(DatabaseImpl dbImpl) {
        postFetchInit(dbImpl.getSortedDuplicates());
    }

    /**
     * Converts the key/data for old format LNs in a duplicates DB.
     *
     * This method MUST be called before calling any of the following methods:
     *  getLN
     *  getKey
     *  getUserKeyData
     *
     * TODO:
     * This method is not called by the HA feeder when materializing entries.
     * This is OK because entries with log version 7 and below are never
     * materialized. But we may want to rename this method to make it clear
     * that it only is, and only must be, called for the log versions < 8.
     */
    public void postFetchInit(boolean isDupDb) {

        final boolean needConversion =
            (dupStatus == DupStatus.NEED_CONVERSION);

        dupStatus = isDupDb ? DupStatus.DUP_DB : DupStatus.NOT_DUP_DB;

        /* Do not convert more than once. */
        if (!needConversion) {
            return;
        }

        /* Nothing to convert for non-duplicates DB. */
        if (dupStatus == DupStatus.NOT_DUP_DB) {
            return;
        }

        key = combineDupKeyData();
    }

    /**
     * Combine old key and old LN's data into a new key, and set the LN's data
     * to empty.
     */
    byte[] combineDupKeyData() {
        assert !ln.isDeleted(); // DeletedLNLogEntry overrides this method.
        return DupKeyData.combine(key, ln.setEmpty());
    }

    /**
     * Translates two-part keys in duplicate DBs back to the original user
     * operation params.  postFetchInit must be called before calling this
     * method.
     */
    public void getUserKeyData(
        DatabaseEntry keyParam,
        DatabaseEntry dataParam) {

        requireKnownDupStatus();

        if (dupStatus == DupStatus.DUP_DB) {
            DupKeyData.split(new DatabaseEntry(key), keyParam, dataParam);
        } else {
            if (keyParam != null) {
                keyParam.setData(key);
            }
            if (dataParam != null) {
                dataParam.setData(ln.getData());
            }
        }
    }

    /*
     * Accessors.
     */
    public boolean isEmbeddedLN() {
        return embeddedLN;
    }

    public LN getLN() {
        requireKnownDupStatus();
        return ln;
    }

    public byte[] getKey() {
        requireKnownDupStatus();
        return key;
    }

    public byte[] getData() {
        return ln.getData();
    }

    public byte[] getEmbeddedData() {

        if (!isEmbeddedLN()) {
            return null;
        }

        if (ln.isDeleted()) {
            return Key.EMPTY_KEY;
        }

        return ln.getData();
    }

    public int getExpiration() {
        return expiration;
    }

    public boolean isExpirationInHours() {
        return expirationInHours;
    }

    private void requireKnownDupStatus() {
        if (dupStatus != DupStatus.DUP_DB &&
            dupStatus != DupStatus.NOT_DUP_DB) {
            throw unexpectedState(
                "postFetchInit was not called");
        }
    }

    /**
     * This method is only used when the converted length is not needed, for
     * example by StatsFileReader.
     */
    public int getUnconvertedDataLength() {
        return ln.getData().length;
    }

    /**
     * This method is only used when the converted length is not needed, for
     * example by StatsFileReader.
     */
    public int getUnconvertedKeyLength() {
        return key.length;
    }

    @Override
    public DatabaseId getDbId() {
        return dbId;
    }

    public long getAbortLsn() {
        return abortLsn;
    }

    public boolean getAbortKnownDeleted() {
        return abortKnownDeleted;
    }

    public byte[] getAbortKey() {
        return abortKey;
    }

    public byte[] getAbortData() {
        return abortData;
    }

    public long getAbortVLSN() {
        return abortVLSN;
    }

    /**
     * Returns true if recovery should count the prior version obsolete using
     * {@link #getPriorVersionSize()} and {@link #getPriorVersionLsn()} ()}.
     * True is returned if there is a prior version of this LN that is not
     * immediately obsolete.
     */
    public boolean countPriorVersionObsolete() {
        return havePriorSize;
    }

    /**
     * Returns the log size of the prior version of this LN.
     *
     * Must not be called if {@link #countPriorVersionObsolete()} returns
     * false.
     */
    public int getPriorVersionSize() {
        if (!havePriorSize) {
            throw EnvironmentFailureException.unexpectedState();
        }
        return priorSize;
    }

    /**
     * Returns the LSN of the prior version of this LN, for purposes of
     * obsolete counting -- the LSN offset may be incorrect, but the LSN file
     * is correct. If the prior version LSN is the abortLsn, then the abortLsn
     * (including its true offset) is returned by this method.
     *
     * Must not be called if {@link #countPriorVersionObsolete()} returns
     * false.
     */
    public long getPriorVersionLsn() {
        if (!havePriorSize) {
            throw EnvironmentFailureException.unexpectedState();
        }
        if (!havePriorFile && !haveAbortLSN) {
            throw EnvironmentFailureException.unexpectedState();
        }
        return havePriorFile ? DbLsn.makeLsn(priorFile, 0) : abortLsn;
    }

    public int getAbortExpiration() {
        return abortExpiration;
    }

    public boolean isAbortExpirationInHours() {
        return abortExpirationInHours;
    }

    public Long getTxnId() {
        if (entryType.isTransactional()) {
            return txn.getId();
        }
        return null;
    }

    public Txn getUserTxn() {
        if (entryType.isTransactional()) {
            return txn;
        }
        return null;
    }

    @Override
    public boolean logicalEquals(LogEntry other) {
        if (!(other instanceof LNLogEntry)) {
            return false;
        }

        LNLogEntry otherEntry = (LNLogEntry) other;

        if (!dbId.logicalEquals(otherEntry.dbId)) {
            return false;
        }

        if (txn != null) {
            if (!txn.logicalEquals(otherEntry.txn)) {
                return false;
            }
        } else {
            if (otherEntry.txn != null) {
                return false;
            }
        }

        if (!Arrays.equals(key, otherEntry.key)) {
            return false;
        }

        if (!ln.logicalEquals(otherEntry.ln)) {
            return false;
        }

        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy