1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.log.entry;
9 
10 import java.lang.reflect.Constructor;
11 import java.nio.ByteBuffer;
12 import java.util.Arrays;
13 
14 import com.sleepycat.je.DatabaseEntry;
15 import com.sleepycat.je.EnvironmentFailureException;
16 import com.sleepycat.je.dbi.DatabaseId;
17 import com.sleepycat.je.dbi.DatabaseImpl;
18 import com.sleepycat.je.dbi.DupKeyData;
19 import com.sleepycat.je.dbi.EnvironmentImpl;
20 import com.sleepycat.je.log.LogEntryHeader;
21 import com.sleepycat.je.log.LogEntryType;
22 import com.sleepycat.je.log.LogUtils;
23 import com.sleepycat.je.tree.LN;
24 import com.sleepycat.je.tree.VersionedLN;
25 import com.sleepycat.je.txn.Txn;
26 import com.sleepycat.je.utilint.DbLsn;
27 import com.sleepycat.je.utilint.VLSN;
28 
29 /**
30  * LNLogEntry embodies all LN log entries.
31  * On disk, an LN log entry contains (pre version 6)
32  * <pre>
33  *   LN
34  *   databaseid
35  *   key
36  *   abortLsn          -- if transactional
37  *   abortKnownDeleted -- if transactional
38  *   txn               -- if transactional
39  *
40  * (version 6)
41  *   databaseid
42  *   abortLsn          -- if transactional
43  *   abortKnownDeleted -- if transactional
44  *   txn               -- if transactional
45  *   LN
46  *   key
47  * </pre>
48  * Before version 6, a non-full-item read of a log entry only retrieved
49  * the node ID. After version 6, the database id, transaction id and node ID
50  * are all available.
51  */
52 public class LNLogEntry<T extends LN> extends BaseReplicableEntry<T> {
53     private static final byte ABORT_KNOWN_DELETED_MASK = (byte) 1;
54 
55     /**
56      * Used for computing the minimum log space used by an LNLogEntry.
57      */
58     public static final int MIN_LOG_SIZE = 1 + // DatabaseId
59                                            1 + // LN with zero-length data
60                                            LogEntryHeader.MIN_HEADER_SIZE;
61 
62     /**
63      * The log version of the most recent format change for this entry,
64      * including any changes to the format of the underlying LN and other
65      * loggables.
66      *
67      * @see #getLastFormatChange
68      */
69     public static final int LAST_FORMAT_CHANGE = 8;
70 
71     /*
72      * Persistent fields in an LN entry.
73      */
74     private LN ln;
75     private DatabaseId dbId;
76     private byte[] key;
77     private long abortLsn = DbLsn.NULL_LSN;
78     private boolean abortKnownDeleted;
79     private Txn txn;     // conditional
80 
81     /* Transient field for duplicates conversion and user key/data methods. */
82     enum DupStatus { UNKNOWN, NEED_CONVERSION, DUP_DB, NOT_DUP_DB }
83     private DupStatus dupStatus;
84 
85     /* For construction of VersionedLN, when VLSN is preserved. */
86     private final Constructor<VersionedLN> versionedLNConstructor;
87 
88     /**
89      * Creates an instance to read an entry.
90      *
91      * @param <T> the type of the contained LN
92      * @param cls the class of the contained LN
93      * @return the log entry
94      */
create(final Class<T> cls)95     public static <T extends LN> LNLogEntry<T> create(final Class<T> cls) {
96         return new LNLogEntry<T>(cls);
97     }
98 
99     /* Constructor to read an entry. */
LNLogEntry(final Class<T> cls)100     LNLogEntry(final Class<T> cls) {
101         super(cls);
102         if (cls == LN.class) {
103             versionedLNConstructor = getNoArgsConstructor(VersionedLN.class);
104         } else {
105             versionedLNConstructor = null;
106         }
107     }
108 
109     /* Constructor to write an entry. */
LNLogEntry(LogEntryType entryType, T ln, DatabaseId dbId, byte[] key, long abortLsn, boolean abortKnownDeleted, Txn txn)110     public LNLogEntry(LogEntryType entryType,
111                       T ln,
112                       DatabaseId dbId,
113                       byte[] key,
114                       long abortLsn,
115                       boolean abortKnownDeleted,
116                       Txn txn) {
117         setLogType(entryType);
118         this.ln = ln;
119         this.dbId = dbId;
120         this.key = key;
121         this.abortLsn = abortLsn;
122         this.abortKnownDeleted = abortKnownDeleted;
123         this.txn = txn;
124         versionedLNConstructor = null;
125 
126         /* A txn should only be provided for transactional entry types. */
127         assert(entryType.isTransactional() == (txn != null));
128     }
129 
130     @Override
readEntry(EnvironmentImpl envImpl, LogEntryHeader header, ByteBuffer entryBuffer)131     public void readEntry(EnvironmentImpl envImpl,
132                           LogEntryHeader header,
133                           ByteBuffer entryBuffer) {
134 
135         /* Subclasses must call readBaseLNEntry. */
136         assert getClass() == LNLogEntry.class;
137 
138         /*
139          * Prior to version 8, the optimization to omit the key size was
140          * mistakenly not applied to internal LN types such as FileSummaryLN
141          * and MapLN, and was only applied to user LN types.  The optimization
142          * should be applicable whenever LNLogEntry is not subclassed to add
143          * additional fields. [#18055]
144          */
145         final boolean keyIsLastSerializedField =
146             header.getVersion() >= 8 || entryType.isUserLNType();
147 
148         readBaseLNEntry(envImpl, header, entryBuffer,
149                         keyIsLastSerializedField);
150     }
151 
152     /**
153      * Method shared by LNLogEntry subclasses.
154      *
155      * @param keyIsLastSerializedField specifies whether the key length can be
156      * omitted because the key is the last field.  This should be false when
157      * an LNLogEntry subclass adds fields to the serialized format.
158      */
readBaseLNEntry(EnvironmentImpl envImpl, LogEntryHeader header, ByteBuffer entryBuffer, boolean keyIsLastSerializedField)159     final void readBaseLNEntry(EnvironmentImpl envImpl,
160                                LogEntryHeader header,
161                                ByteBuffer entryBuffer,
162                                boolean keyIsLastSerializedField) {
163 
164         int logVersion = header.getVersion();
165         boolean unpacked = (logVersion < 6);
166         int recStartPosition = entryBuffer.position();
167 
168         /*
169          * For log version 6 and above we store the key last so that we can
170          * avoid storing the key size. Instead, we derive it from the LN size
171          * and the total entry size. The DatabaseId is also packed.
172          */
173         if (unpacked) {
174             /* LN is first for log versions prior to 6. */
175             ln = newLNInstance(envImpl);
176             ln.readFromLog(entryBuffer, logVersion);
177         }
178 
179         /* DatabaseImpl Id. */
180         dbId = new DatabaseId();
181         dbId.readFromLog(entryBuffer, logVersion);
182 
183         /* Key. */
184         if (unpacked) {
185             key = LogUtils.readByteArray(entryBuffer, true/*unpacked*/);
186         } else {
187             /* Read later. */
188         }
189 
190         if (entryType.isTransactional()) {
191 
192             /*
193              * AbortLsn. If it was a marker LSN that was used to fill in a
194              * create, mark it null.
195              */
196             abortLsn = LogUtils.readLong(entryBuffer, unpacked);
197             if (DbLsn.getFileNumber(abortLsn) ==
198                 DbLsn.getFileNumber(DbLsn.NULL_LSN)) {
199                 abortLsn = DbLsn.NULL_LSN;
200             }
201 
202             abortKnownDeleted =
203                 ((entryBuffer.get() & ABORT_KNOWN_DELETED_MASK) != 0) ?
204                 true : false;
205 
206             /* Locker. */
207             txn = new Txn();
208             txn.readFromLog(entryBuffer, logVersion);
209         }
210 
211         if (!unpacked) {
212             /* LN is next for log version 6 and above. */
213             ln = newLNInstance(envImpl);
214             ln.readFromLog(entryBuffer, logVersion);
215             final int keySize;
216             if (keyIsLastSerializedField) {
217                 final int bytesWritten =
218                     entryBuffer.position() - recStartPosition;
219                 keySize = header.getItemSize() - bytesWritten;
220             } else {
221                 keySize = LogUtils.readPackedInt(entryBuffer);
222             }
223             key = LogUtils.readBytesNoLength(entryBuffer, keySize);
224         }
225 
226         /* Save transient fields after read. */
227         setLNTransientFields(ln, header.getVLSN());
228 
229         /* Dup conversion will be done by postFetchInit. */
230         dupStatus =
231             (logVersion < 8) ? DupStatus.NEED_CONVERSION : DupStatus.UNKNOWN;
232     }
233 
234     /**
235      * newLNInstance usually returns exactly the type of LN of the type that
236      * was contained in in the log. For example, if a LNLogEntry holds a MapLN,
237      * newLNInstance will return that MapLN. There is one extra possibility for
238      * vanilla (data record) LNs. In that case, this method may either return a
239      * LN or a generated type, the VersionedLN, which adds the vlsn information
240      * from the log header to the LN object.
241      */
newLNInstance(EnvironmentImpl envImpl)242     LN newLNInstance(EnvironmentImpl envImpl) {
243         if (versionedLNConstructor != null && envImpl.getPreserveVLSN()) {
244             return newInstanceOfType(versionedLNConstructor);
245         }
246         return newInstanceOfType();
247     }
248 
249     @Override
dumpEntry(StringBuilder sb, boolean verbose)250     public StringBuilder dumpEntry(StringBuilder sb, boolean verbose) {
251         ln.dumpLog(sb, verbose);
252         dbId.dumpLog(sb, verbose);
253         ln.dumpKey(sb, key);
254         if (entryType.isTransactional()) {
255             if (abortLsn != DbLsn.NULL_LSN) {
256                 sb.append(DbLsn.toString(abortLsn));
257             }
258             sb.append("<knownDeleted val=\"");
259             sb.append(abortKnownDeleted ? "true" : "false");
260             sb.append("\"/>");
261             txn.dumpLog(sb, verbose);
262         }
263         return sb;
264     }
265 
266     @Override
dumpRep(StringBuilder sb)267     public void dumpRep(StringBuilder sb) {
268         if (entryType.isTransactional()) {
269             sb.append(" txn=").append(txn.getId());
270         }
271     }
272 
273     @Override
getMainItem()274     public LN getMainItem() {
275         return ln;
276     }
277 
278     @Override
getTransactionId()279     public long getTransactionId() {
280         if (entryType.isTransactional()) {
281             return txn.getId();
282         }
283         return 0;
284     }
285 
286     /*
287      * Writing support.
288      */
289 
290     @Override
getLastFormatChange()291     public int getLastFormatChange() {
292         return LAST_FORMAT_CHANGE;
293     }
294 
295     @Override
getSize()296     public int getSize() {
297 
298         /* Subclasses must call getBaseLNEntrySize. */
299         assert getClass() == LNLogEntry.class;
300 
301         return getBaseLNEntrySize(true /*keyIsLastSerializedField*/);
302     }
303 
304     /**
305      * Method shared by LNLogEntry subclasses.
306      *
307      * @param keyIsLastSerializedField specifies whether the key length can be
308      * omitted because the key is the last field.  This should be false when
309      * an LNLogEntry subclass adds fields to the serialized format.
310      */
getBaseLNEntrySize(boolean keyIsLastSerializedField)311     final int getBaseLNEntrySize(boolean keyIsLastSerializedField) {
312         int len = key.length;
313         int size = ln.getLogSize() +
314             dbId.getLogSize() +
315             len;
316         if (!keyIsLastSerializedField) {
317             size += LogUtils.getPackedIntLogSize(len);
318         }
319         if (entryType.isTransactional()) {
320             size += LogUtils.getPackedLongLogSize(abortLsn);
321             size++;   // abortKnownDeleted
322             size += txn.getLogSize();
323         }
324         return size;
325     }
326 
327     @Override
writeEntry(final ByteBuffer destBuffer, final int logVersion)328     public void writeEntry(final ByteBuffer destBuffer, final int logVersion) {
329 
330         /* Subclasses must call writeBaseLNEntry. */
331         assert getClass() == LNLogEntry.class;
332 
333         writeBaseLNEntry(destBuffer,
334                          true /*keyIsLastSerializedField*/,
335                          logVersion);
336     }
337 
338     /**
339      * Method shared by LNLogEntry subclasses.
340      *
341      * @param keyIsLastSerializedField specifies whether the key length can be
342      * omitted because the key is the last field.  This should be false when
343      * an LNLogEntry subclass adds fields to the serialized format.
344      */
writeBaseLNEntry(final ByteBuffer destBuffer, final boolean keyIsLastSerializedField, final int logVersion)345     final void writeBaseLNEntry(final ByteBuffer destBuffer,
346                                 final boolean keyIsLastSerializedField,
347                                 final int logVersion) {
348         checkCurrentVersion(this, logVersion);
349         assert ln.getLastFormatChange() <= LAST_FORMAT_CHANGE &&
350             dbId.getLastFormatChange() <= LAST_FORMAT_CHANGE
351             : "Format of loggable newer than format of entry";
352 
353         dbId.writeToLog(destBuffer, logVersion);
354 
355         if (entryType.isTransactional()) {
356             LogUtils.writePackedLong(destBuffer, abortLsn);
357             byte aKD = 0;
358             if (abortKnownDeleted) {
359                 aKD |= ABORT_KNOWN_DELETED_MASK;
360             }
361             destBuffer.put(aKD);
362             assert txn.getLastFormatChange() <= LAST_FORMAT_CHANGE
363                 : "Format of loggable newer than format of entry";
364             txn.writeToLog(destBuffer, logVersion);
365         }
366 
367         ln.writeToLog(destBuffer, logVersion);
368         if (!keyIsLastSerializedField) {
369             LogUtils.writePackedInt(destBuffer, key.length);
370         }
371         LogUtils.writeBytesNoLength(destBuffer, key);
372     }
373 
374     /**
375      * An LN has two transient fields that are derived from its parent log
376      * entry: last logged size and VLSN sequence.
377      */
setLNTransientFields(LN ln, VLSN vlsn)378     private void setLNTransientFields(LN ln, VLSN vlsn) {
379         if (vlsn != null) {
380             ln.setVLSNSequence(vlsn.getSequence());
381         }
382     }
383 
384     @Override
isImmediatelyObsolete(DatabaseImpl dbImpl)385     public boolean isImmediatelyObsolete(DatabaseImpl dbImpl) {
386         return ln.isDeleted() || dbImpl.isLNImmediatelyObsolete();
387     }
388 
389     @Override
isDeleted()390     public boolean isDeleted() {
391         return ln.isDeleted();
392     }
393 
394     /**
395      * For LN entries, we need to record the latest LSN for that node with the
396      * owning transaction, within the protection of the log latch. This is a
397      * callback for the log manager to do that recording.
398      */
399     @Override
postLogWork(LogEntryHeader header, long justLoggedLsn, VLSN vlsn)400     public void postLogWork(LogEntryHeader header,
401                             long justLoggedLsn,
402                             VLSN vlsn) {
403         if (entryType.isTransactional()) {
404             txn.addLogInfo(justLoggedLsn);
405         }
406         /* Save transient fields after write. */
407         setLNTransientFields(ln, vlsn);
408     }
409 
410     @Override
postFetchInit(DatabaseImpl dbImpl)411     public void postFetchInit(DatabaseImpl dbImpl) {
412         postFetchInit(dbImpl.getSortedDuplicates());
413     }
414 
415     /**
416      * Converts the key/data for old format LNs in a duplicates DB.
417      *
418      * This method MUST be called before calling any of the following methods:
419      *  getLN
420      *  getKey
421      *  getUserKeyData
422      */
postFetchInit(boolean isDupDb)423     public void postFetchInit(boolean isDupDb) {
424 
425         final boolean needConversion =
426             (dupStatus == DupStatus.NEED_CONVERSION);
427 
428         dupStatus = isDupDb ? DupStatus.DUP_DB : DupStatus.NOT_DUP_DB;
429 
430         /* Do not convert more than once. */
431         if (!needConversion) {
432             return;
433         }
434 
435         /* Nothing to convert for non-duplicates DB. */
436         if (dupStatus == DupStatus.NOT_DUP_DB) {
437             return;
438         }
439 
440         key = combineDupKeyData();
441     }
442 
443     /**
444      * Combine old key and old LN's data into a new key, and set the LN's data
445      * to empty.
446      */
combineDupKeyData()447     byte[] combineDupKeyData() {
448         assert !ln.isDeleted(); // DeletedLNLogEntry overrides this method.
449         return DupKeyData.combine(key, ln.setEmpty());
450     }
451 
452     /**
453      * Translates two-part keys in duplicate DBs back to the original user
454      * operation params.  postFetchInit must be called before calling this
455      * method.
456      */
getUserKeyData(DatabaseEntry keyParam, DatabaseEntry dataParam)457     public void getUserKeyData(DatabaseEntry keyParam,
458                                DatabaseEntry dataParam) {
459 
460         requireKnownDupStatus();
461 
462         if (dupStatus == DupStatus.DUP_DB) {
463             DupKeyData.split(new DatabaseEntry(key), keyParam, dataParam);
464         } else {
465             if (keyParam != null) {
466                 keyParam.setData(key);
467             }
468             if (dataParam != null) {
469                 dataParam.setData(ln.getData());
470             }
471         }
472     }
473 
474     /*
475      * Accessors.
476      */
getLN()477     public LN getLN() {
478         requireKnownDupStatus();
479         return ln;
480     }
481 
getKey()482     public byte[] getKey() {
483         requireKnownDupStatus();
484         return key;
485     }
486 
requireKnownDupStatus()487     private void requireKnownDupStatus() {
488         if (dupStatus != DupStatus.DUP_DB &&
489             dupStatus != DupStatus.NOT_DUP_DB) {
490             throw EnvironmentFailureException.unexpectedState
491                 ("postFetchInit was not called");
492         }
493     }
494 
495     /**
496      * This method is only used when the converted length is not needed, for
497      * example by StatsFileReader.
498      */
getUnconvertedDataLength()499     public int getUnconvertedDataLength() {
500         return ln.getData().length;
501     }
502 
503     /**
504      * This method is only used when the converted length is not needed, for
505      * example by StatsFileReader.
506      */
getUnconvertedKeyLength()507     public int getUnconvertedKeyLength() {
508         return key.length;
509     }
510 
511     @Override
getDbId()512     public DatabaseId getDbId() {
513         return dbId;
514     }
515 
getAbortLsn()516     public long getAbortLsn() {
517         return abortLsn;
518     }
519 
getAbortKnownDeleted()520     public boolean getAbortKnownDeleted() {
521         return abortKnownDeleted;
522     }
523 
getTxnId()524     public Long getTxnId() {
525         if (entryType.isTransactional()) {
526             return Long.valueOf(txn.getId());
527         }
528         return null;
529     }
530 
getUserTxn()531     public Txn getUserTxn() {
532         if (entryType.isTransactional()) {
533             return txn;
534         }
535         return null;
536     }
537 
538     @Override
logicalEquals(LogEntry other)539     public boolean logicalEquals(LogEntry other) {
540         if (!(other instanceof LNLogEntry)) {
541             return false;
542         }
543 
544         LNLogEntry<?> otherEntry = (LNLogEntry<?>) other;
545 
546         if (!dbId.logicalEquals(otherEntry.dbId)) {
547             return false;
548         }
549 
550         if (txn != null) {
551             if (!txn.logicalEquals(otherEntry.txn)) {
552                 return false;
553             }
554         } else {
555             if (otherEntry.txn != null) {
556                 return false;
557             }
558         }
559 
560         if (!Arrays.equals(key, otherEntry.key)) {
561             return false;
562         }
563 
564         if (!ln.logicalEquals(otherEntry.ln)) {
565             return false;
566         }
567 
568         return true;
569     }
570 }
571