/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002-2006
* Sleepycat Software. All rights reserved.
*
* $Id: LN.java,v 1.1 2006/05/06 09:00:21 ckaestne Exp $
*/
package com.sleepycat.je.tree;
import java.nio.ByteBuffer;
import java.util.Map;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.cleaner.UtilizationTracker;
import com.sleepycat.je.dbi.DatabaseId;
import com.sleepycat.je.dbi.DatabaseImpl;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.dbi.INList;
import com.sleepycat.je.dbi.MemoryBudget;
import com.sleepycat.je.log.LogEntryType;
import com.sleepycat.je.log.LogException;
import com.sleepycat.je.log.LogManager;
import com.sleepycat.je.log.LogReadable;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.log.LoggableObject;
import com.sleepycat.je.log.entry.DeletedDupLNLogEntry;
import com.sleepycat.je.log.entry.LNLogEntry;
import com.sleepycat.je.txn.Locker;
import com.sleepycat.je.txn.Txn;
import com.sleepycat.je.txn.WriteLockInfo;
import com.sleepycat.je.utilint.DbLsn;
/**
* An LN represents a Leaf Node in the JE tree.
*/
public class LN extends Node implements LoggableObject, LogReadable {
private static final String BEGIN_TAG = "<ln>";
private static final String END_TAG = "</ln>";
private byte[] data;
/**
* Create an empty LN, to be filled in from the log.
*/
public LN() {
super(false);
this.data = null;
}
/**
* Create a new LN from a byte array.
*/
public LN(byte[] data) {
super(true);
if (data == null) {
this.data = null;
} else {
init(data, 0, data.length);
}
}
/**
* Create a new LN from a DatabaseEntry.
*/
public LN(DatabaseEntry dbt) {
super(true);
byte[] data = dbt.getData();
if (data == null) {
this.data = null;
} else if (dbt.getPartial()) {
init(data,
dbt.getOffset(),
dbt.getPartialOffset() + dbt.getSize(),
dbt.getPartialOffset(),
dbt.getSize());
} else {
init(data, dbt.getOffset(), dbt.getSize());
}
}
private void init(byte[] data, int off, int len, int doff, int dlen) {
if (len == 0) {
this.data = LogUtils.ZERO_LENGTH_BYTE_ARRAY;
} else {
this.data = new byte[len];
System.arraycopy(data, off, this.data, doff, dlen);
}
}
private void init(byte[] data, int off, int len) {
init(data, off, len, 0, len);
}
public byte[] getData() {
return data;
}
public byte[] copyData() {
int len = data.length;
byte[] ret = new byte[len];
System.arraycopy(data, 0, ret, 0, len);
return ret;
}
public boolean isDeleted() {
return (data == null);
}
void makeDeleted() {
data = null;
}
/*
* If you get to an LN, this subtree isn't valid for delete. True, the LN
* may have been deleted, but you can't be sure without taking a lock, and
* the validate -subtree-for-delete process assumes that bin compressing
* has happened and there are no committed, deleted LNS hanging off the
* BIN.
*/
boolean isValidForDelete() {
return false;
}
/**
* A LN can never be a child in the search chain.
*/
protected boolean isSoughtNode(long nid, boolean updateGeneration) {
return false;
}
/**
* A LN can never be the ancestor of another node.
*/
protected boolean canBeAncestor(boolean targetContainsDuplicates) {
return false;
}
/**
* Delete this LN's data and log the new version.
*/
public long delete(DatabaseImpl database,
byte[] lnKey,
byte[] dupKey,
long oldLsn,
Locker locker)
throws DatabaseException {
makeDeleted();
EnvironmentImpl env = database.getDbEnvironment();
long newLsn = DbLsn.NULL_LSN;
if (dupKey != null) {
/*
* Deleted Duplicate LNs are logged with two keys -- the one that
* identifies the main tree (the dup key) and the one that places
* them in the duplicate tree (really the data) since we can't
* recreate the latter because the data field has been nulled. Note
* that the dupKey is passed to the log manager FIRST, because the
* dup key is the one that navigates us in the main tree. The "key"
* is the one that navigates us in the duplicate tree. Also, we
* must check if this is a transactional entry that must be rolled
* back or one done on the behalf of a null txn.
*/
LogEntryType entryType;
long logAbortLsn;
boolean logAbortKnownDeleted;
Txn logTxn;
if (locker.isTransactional()) {
entryType = LogEntryType.LOG_DEL_DUPLN_TRANSACTIONAL;
WriteLockInfo info = locker.getWriteLockInfo(getNodeId());
logAbortLsn = info.getAbortLsn();
logAbortKnownDeleted = info.getAbortKnownDeleted();
logTxn = locker.getTxnLocker();
} else {
entryType = LogEntryType.LOG_DEL_DUPLN;
logAbortLsn = DbLsn.NULL_LSN;
logAbortKnownDeleted = true;
logTxn = null;
}
/* Don't count abortLsn as obsolete, this is done during commit. */
if (oldLsn == logAbortLsn) {
oldLsn = DbLsn.NULL_LSN;
}
DeletedDupLNLogEntry logEntry =
new DeletedDupLNLogEntry(entryType,
this,
database.getId(),
dupKey,
lnKey,
logAbortLsn,
logAbortKnownDeleted,
logTxn);
LogManager logManager = env.getLogManager();
newLsn = logManager.log(logEntry, false, oldLsn);
} else {
/*
* Non duplicate LN, just log the normal way.
*/
newLsn = log(env, database.getId(), lnKey, oldLsn, locker);
}
return newLsn;
}
/**
* Modify the LN's data and log the new version.
*/
public long modify(byte[] newData,
DatabaseImpl database,
byte[] lnKey,
long oldLsn,
Locker locker)
throws DatabaseException {
data = newData;
/* Log the new ln. */
EnvironmentImpl env = database.getDbEnvironment();
long newLsn = log(env, database.getId(), lnKey, oldLsn, locker);
return newLsn;
}
/**
* Add yourself to the dirty list if you're dirty. LNs are never dirty.
*/
void addToDirtyMap(Map dirtyMap) {
}
/**
* Add yourself to the in memory list if you're a type of node that should
* belong.
*/
void rebuildINList(INList inList) {
// don't add, LNs don't belong on the list.
}
/**
* No need to do anything, stop the search.
*/
void accountForSubtreeRemoval(INList inList,
UtilizationTracker tracker) {
/* Don't remove, LNs not on this list. */
}
/**
* Compute the approximate size of this node in memory for evictor
* invocation purposes.
*/
public long getMemorySizeIncludedByParent() {
int size = MemoryBudget.LN_OVERHEAD;
if (data != null) {
size += MemoryBudget.byteArraySize(data.length);
}
return size;
}
/*
* Dumping
*/
public String beginTag() {
return BEGIN_TAG;
}
public String endTag() {
return END_TAG;
}
public String dumpString(int nSpaces, boolean dumpTags) {
StringBuffer self = new StringBuffer();
if (dumpTags) {
self.append(TreeUtils.indent(nSpaces));
self.append(beginTag());
self.append('\n');
}
self.append(super.dumpString(nSpaces + 2, true));
self.append('\n');
if (data != null) {
self.append(TreeUtils.indent(nSpaces+2));
self.append("<data>");
self.append(TreeUtils.dumpByteArray(data));
self.append("</data>");
self.append('\n');
}
if (dumpTags) {
self.append(TreeUtils.indent(nSpaces));
self.append(endTag());
}
return self.toString();
}
/*
* Logging Support
*/
/**
* Log a provisional, non-txnal version of a ln.
* @param env the environment.
* @param dbId database id of this node. (Not stored in LN)
* @param key key of this node. (Not stored in LN)
* @param oldLsn is the LSN of the previous version or null.
*/
public long logProvisional(EnvironmentImpl env,
DatabaseId dbId,
byte[] key,
long oldLsn)
throws DatabaseException {
return log(env, dbId, key, oldLsn, null, true);
}
/**
* Log this LN. Whether its logged as
* a transactional entry or not depends on the type of locker.
* @param env the environment.
* @param dbId database id of this node. (Not stored in LN)
* @param key key of this node. (Not stored in LN)
* @param oldLsn is the LSN of the previous version or null.
* @param locker owning locker.
*/
public long log(EnvironmentImpl env,
DatabaseId dbId,
byte[] key,
long oldLsn,
Locker locker)
throws DatabaseException {
return log(env, dbId, key, oldLsn, locker, false);
}
/**
* Log this LN. Whether its logged as a transactional entry or not depends
* on the type of locker.
* @param env the environment.
* @param dbId database id of this node. (Not stored in LN)
* @param key key of this node. (Not stored in LN)
* @param oldLsn is the LSN of the previous version or null.
* @param locker owning locker.
*/
private long log(EnvironmentImpl env,
DatabaseId dbId,
byte[] key,
long oldLsn,
Locker locker,
boolean isProvisional)
throws DatabaseException {
LogEntryType entryType;
long logAbortLsn;
boolean logAbortKnownDeleted;
Txn logTxn;
if (locker != null && locker.isTransactional()) {
entryType = getTransactionalLogType();
WriteLockInfo info = locker.getWriteLockInfo(getNodeId());
logAbortLsn = info.getAbortLsn();
logAbortKnownDeleted = info.getAbortKnownDeleted();
logTxn = locker.getTxnLocker();
assert logTxn != null;
} else {
entryType = getLogType();
logAbortLsn = DbLsn.NULL_LSN;
logAbortKnownDeleted = false;
logTxn = null;
}
/* Don't count abortLsn as obsolete, this is done during commit. */
if (oldLsn == logAbortLsn) {
oldLsn = DbLsn.NULL_LSN;
}
LNLogEntry logEntry = new LNLogEntry(entryType,
this,
dbId,
key,
logAbortLsn,
logAbortKnownDeleted,
logTxn);
LogManager logManager = env.getLogManager();
return logManager.log(logEntry, isProvisional, oldLsn);
}
/**
* Log type for transactional entries
*/
protected LogEntryType getTransactionalLogType() {
return LogEntryType.LOG_LN_TRANSACTIONAL;
}
/**
* @see LoggableObject#countAsObsoleteWhenLogged
*/
public boolean countAsObsoleteWhenLogged() {
return false;
}
/**
* @see LoggableObject#getLogType
*/
public LogEntryType getLogType() {
return LogEntryType.LOG_LN;
}
/**
* @see LoggableObject#getLogSize
*/
public int getLogSize() {
int size = super.getLogSize();
// data
size += LogUtils.getBooleanLogSize(); // isDeleted flag
if (!isDeleted()) {
size += LogUtils.getByteArrayLogSize(data);
}
return size;
}
/**
* @see LoggableObject#writeToLog
*/
public void writeToLog(ByteBuffer logBuffer) {
/* Ask ancestors to write to log. */
super.writeToLog(logBuffer);
/* data: isData null flag, then length, then data. */
boolean dataExists = !isDeleted();
LogUtils.writeBoolean(logBuffer, dataExists);
if (dataExists) {
LogUtils.writeByteArray(logBuffer, data);
}
}
/**
* @see LogReadable#readFromLog
*/
public void readFromLog(ByteBuffer itemBuffer, byte entryTypeVersion)
throws LogException {
super.readFromLog(itemBuffer, entryTypeVersion);
boolean dataExists = LogUtils.readBoolean(itemBuffer);
if (dataExists) {
data = LogUtils.readByteArray(itemBuffer);
}
}
/**
* @see LogReadable#dumpLog
*/
public void dumpLog(StringBuffer sb, boolean verbose) {
sb.append(beginTag());
super.dumpLog(sb, verbose);
if (data != null) {
sb.append("<data>");
sb.append(TreeUtils.dumpByteArray(data));
sb.append("</data>");
}
dumpLogAdditional(sb, verbose);
sb.append(endTag());
}
/**
* Never called.
* @see LogReadable#logEntryIsTransactional.
*/
public boolean logEntryIsTransactional() {
return false;
}
/**
* Never called.
* @see LogReadable#getTransactionId
*/
public long getTransactionId() {
return 0;
}
/*
* Allows subclasses to add additional fields before the end tag.
*/
protected void dumpLogAdditional(StringBuffer sb, boolean verbose) {
}
}