/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.accumulo.core.data; import static java.util.Objects.requireNonNull; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.accumulo.core.data.thrift.TMutation; import org.apache.accumulo.core.security.ColumnVisibility; import org.apache.accumulo.core.util.ByteBufferUtil; import org.apache.accumulo.core.util.UnsynchronizedBuffer; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableUtils; /** * Mutation represents an action that manipulates a row in a table. A mutation holds a list of column/value pairs that represent an atomic set of modifications * to make to a row. * * <p> * Convenience methods which takes columns and value as CharSequence (String implements CharSequence) are provided. CharSequence is converted to UTF-8 by * constructing a new Text object. * * <p> * When always passing in the same data as a CharSequence/String, it's probably more efficient to call the Text put methods. This way the data is only encoded * once and only one Text object is created. * * <p> * All of the put methods append data to the mutation; they do not overwrite anything that was previously put. The mutation holds a list of all columns/values * that were put into it. * * <p> * The putDelete() methods do not remove something that was previously added to the mutation; rather, they indicate that Accumulo should insert a delete marker * for that row column. A delete marker effectively hides entries for that row column with a timestamp earlier than the marker's. (The hidden data is eventually * removed during Accumulo garbage collection.) */ public class Mutation implements Writable { /** * Internally, this class keeps most mutation data in a byte buffer. If a cell value put into a mutation exceeds this size, then it is stored in a separate * buffer, and a reference to it is inserted into the main buffer. */ static final int VALUE_SIZE_COPY_CUTOFF = 1 << 15; /** * Formats available for serializing Mutations. The formats are described in a <a href="doc-files/mutation-serialization.html">separate document</a>. */ public static enum SERIALIZED_FORMAT { VERSION1, VERSION2 } private boolean useOldDeserialize = false; private byte[] row; private byte[] data; private int entries; private List<byte[]> values; private UnsynchronizedBuffer.Writer buffer; private List<ColumnUpdate> updates; private static final Set<String> EMPTY = Collections.emptySet(); private Set<String> replicationSources = EMPTY; private static final byte[] EMPTY_BYTES = new byte[0]; private void serialize() { if (buffer != null) { data = buffer.toArray(); buffer = null; } } /** * This is so hashCode and equals can be called without changing this object. * * It will return a copy of the current data buffer if serialized has not been called previously. Otherwise, this.data will be returned since the buffer is * null and will not change. */ private ByteBuffer serializedSnapshot() { if (buffer != null) { return this.buffer.toByteBuffer(); } else { return ByteBuffer.wrap(this.data); } } /** * Creates a new mutation. A defensive copy is made. * * @param row * row ID * @since 1.5.0 */ public Mutation(byte[] row) { this(row, 0, row.length); } /** * Creates a new mutation. A defensive copy is made. * * @param row * row ID * @param initialBufferSize * the initial size, in bytes, of the internal buffer for serializing * @since 1.7.0 */ public Mutation(byte[] row, int initialBufferSize) { this(row, 0, row.length, initialBufferSize); } /** * Creates a new mutation. A defensive copy is made. * * @param row * byte array containing row ID * @param start * starting index of row ID in byte array * @param length * length of row ID in byte array * @throws IndexOutOfBoundsException * if start or length is invalid * @since 1.5.0 */ public Mutation(byte[] row, int start, int length) { this(row, start, length, 64); } /** * Creates a new mutation. A defensive copy is made. * * @param row * byte array containing row ID * @param start * starting index of row ID in byte array * @param length * length of row ID in byte array * @param initialBufferSize * the initial size, in bytes, of the internal buffer for serializing * @throws IndexOutOfBoundsException * if start or length is invalid * @since 1.7.0 */ public Mutation(byte[] row, int start, int length, int initialBufferSize) { this.row = new byte[length]; System.arraycopy(row, start, this.row, 0, length); buffer = new UnsynchronizedBuffer.Writer(initialBufferSize); } /** * Creates a new mutation. A defensive copy is made. * * @param row * row ID */ public Mutation(Text row) { this(row.getBytes(), 0, row.getLength()); } /** * Creates a new mutation. A defensive copy is made. * * @param row * row ID * @param initialBufferSize * the initial size, in bytes, of the internal buffer for serializing * @since 1.7.0 */ public Mutation(Text row, int initialBufferSize) { this(row.getBytes(), 0, row.getLength(), initialBufferSize); } /** * Creates a new mutation. * * @param row * row ID */ public Mutation(CharSequence row) { this(new Text(row.toString())); } /** * Creates a new mutation. * * @param row * row ID * @param initialBufferSize * the initial size, in bytes, of the internal buffer for serializing * @since 1.7.0 */ public Mutation(CharSequence row, int initialBufferSize) { this(new Text(row.toString()), initialBufferSize); } /** * Creates a new mutation. */ public Mutation() {} /** * Creates a new mutation from a Thrift mutation. * * @param tmutation * Thrift mutation */ public Mutation(TMutation tmutation) { this.row = ByteBufferUtil.toBytes(tmutation.row); this.data = ByteBufferUtil.toBytes(tmutation.data); this.entries = tmutation.entries; this.values = ByteBufferUtil.toBytesList(tmutation.values); if (tmutation.isSetSources()) { this.replicationSources = new HashSet<>(tmutation.sources); } if (this.row == null) { throw new IllegalArgumentException("null row"); } if (this.data == null) { throw new IllegalArgumentException("null serialized data"); } } /** * Creates a new mutation by copying another. * * @param m * mutation to copy */ public Mutation(Mutation m) { m.serialize(); this.row = m.row; this.data = m.data; this.entries = m.entries; this.values = m.values; this.replicationSources = m.replicationSources; } /** * Gets the row ID for this mutation. Not a defensive copy. * * @return row ID */ public byte[] getRow() { return row; } private void put(byte b[]) { put(b, b.length); } private void put(byte b[], int length) { buffer.writeVLong(length); buffer.add(b, 0, length); } private void put(boolean b) { buffer.add(b); } private void put(int i) { buffer.writeVLong(i); } private void put(long l) { buffer.writeVLong(l); } private void put(byte[] cf, byte[] cq, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val) { put(cf, cf.length, cq, cq.length, cv, hasts, ts, deleted, val, val.length); } /* * When dealing with Text object the length must be gotten from the object, not from the byte array. */ private void put(Text cf, Text cq, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val) { put(cf.getBytes(), cf.getLength(), cq.getBytes(), cq.getLength(), cv, hasts, ts, deleted, val, val.length); } private void put(byte[] cf, int cfLength, byte[] cq, int cqLength, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val, int valLength) { if (buffer == null) { throw new IllegalStateException("Can not add to mutation after serializing it"); } put(cf, cfLength); put(cq, cqLength); put(cv); put(hasts); if (hasts) { put(ts); } put(deleted); if (valLength < VALUE_SIZE_COPY_CUTOFF) { put(val, valLength); } else { if (values == null) { values = new ArrayList<>(); } byte copy[] = new byte[valLength]; System.arraycopy(val, 0, copy, 0, valLength); values.add(copy); put(-1 * values.size()); } entries++; } private void put(CharSequence cf, CharSequence cq, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val) { put(new Text(cf.toString()), new Text(cq.toString()), cv, hasts, ts, deleted, val); } private void put(Text cf, Text cq, byte[] cv, boolean hasts, long ts, boolean deleted, Text val) { put(cf.getBytes(), cf.getLength(), cq.getBytes(), cq.getLength(), cv, hasts, ts, deleted, val.getBytes(), val.getLength()); } private void put(CharSequence cf, CharSequence cq, byte[] cv, boolean hasts, long ts, boolean deleted, CharSequence val) { put(new Text(cf.toString()), new Text(cq.toString()), cv, hasts, ts, deleted, new Text(val.toString())); } /** * Puts a modification in this mutation. Column visibility is empty; timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param value * cell value */ public void put(Text columnFamily, Text columnQualifier, Value value) { put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, false, value.get()); } /** * Puts a modification in this mutation. Timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param value * cell value */ public void put(Text columnFamily, Text columnQualifier, ColumnVisibility columnVisibility, Value value) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, false, value.get()); } /** * Puts a modification in this mutation. Column visibility is empty. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param timestamp * timestamp * @param value * cell value */ public void put(Text columnFamily, Text columnQualifier, long timestamp, Value value) { put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, false, value.get()); } /** * Puts a modification in this mutation. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param timestamp * timestamp * @param value * cell value */ public void put(Text columnFamily, Text columnQualifier, ColumnVisibility columnVisibility, long timestamp, Value value) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, false, value.get()); } /** * Puts a deletion in this mutation. Matches empty column visibility; timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier */ public void putDelete(Text columnFamily, Text columnQualifier) { put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. Timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility */ public void putDelete(Text columnFamily, Text columnQualifier, ColumnVisibility columnVisibility) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. Matches empty column visibility. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param timestamp * timestamp */ public void putDelete(Text columnFamily, Text columnQualifier, long timestamp) { put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param timestamp * timestamp */ public void putDelete(Text columnFamily, Text columnQualifier, ColumnVisibility columnVisibility, long timestamp) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, true, EMPTY_BYTES); } /** * Puts a modification in this mutation. Column visibility is empty; timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier */ public void put(CharSequence columnFamily, CharSequence columnQualifier, Value value) { put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, false, value.get()); } /** * Puts a modification in this mutation. Timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param value * cell value */ public void put(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, Value value) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, false, value.get()); } /** * Puts a modification in this mutation. Column visibility is empty. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param timestamp * timestamp * @param value * cell value */ public void put(CharSequence columnFamily, CharSequence columnQualifier, long timestamp, Value value) { put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, false, value.get()); } /** * Puts a modification in this mutation. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param timestamp * timestamp * @param value * cell value */ public void put(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, long timestamp, Value value) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, false, value.get()); } /** * Puts a deletion in this mutation. Matches empty column visibility; timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier */ public void putDelete(CharSequence columnFamily, CharSequence columnQualifier) { put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. Timestamp is not set. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility */ public void putDelete(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. Matches empty column visibility. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param timestamp * timestamp */ public void putDelete(CharSequence columnFamily, CharSequence columnQualifier, long timestamp) { put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param timestamp * timestamp */ public void putDelete(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, long timestamp) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, true, EMPTY_BYTES); } /** * Puts a modification in this mutation. Column visibility is empty; timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param value * cell value */ public void put(CharSequence columnFamily, CharSequence columnQualifier, CharSequence value) { put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, false, value); } /** * Puts a modification in this mutation. Timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param value * cell value */ public void put(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, CharSequence value) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, false, value); } /** * Puts a modification in this mutation. Column visibility is empty. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param timestamp * timestamp * @param value * cell value */ public void put(CharSequence columnFamily, CharSequence columnQualifier, long timestamp, CharSequence value) { put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, false, value); } /** * Puts a modification in this mutation. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param timestamp * timestamp * @param value * cell value */ public void put(CharSequence columnFamily, CharSequence columnQualifier, ColumnVisibility columnVisibility, long timestamp, CharSequence value) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, false, value); } /** * Puts a modification in this mutation. Column visibility is empty; timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param value * cell value * @since 1.5.0 */ public void put(byte[] columnFamily, byte[] columnQualifier, byte[] value) { put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, false, value); } /** * Puts a modification in this mutation. Timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param value * cell value * @since 1.5.0 */ public void put(byte[] columnFamily, byte[] columnQualifier, ColumnVisibility columnVisibility, byte[] value) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, false, value); } /** * Puts a modification in this mutation. Column visibility is empty. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param timestamp * timestamp * @param value * cell value * @since 1.5.0 */ public void put(byte[] columnFamily, byte[] columnQualifier, long timestamp, byte[] value) { put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, false, value); } /** * Puts a modification in this mutation. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param timestamp * timestamp * @param value * cell value * @since 1.5.0 */ public void put(byte[] columnFamily, byte[] columnQualifier, ColumnVisibility columnVisibility, long timestamp, byte[] value) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, false, value); } /** * Puts a deletion in this mutation. Matches empty column visibility; timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @since 1.5.0 */ public void putDelete(byte[] columnFamily, byte[] columnQualifier) { put(columnFamily, columnQualifier, EMPTY_BYTES, false, 0l, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. Timestamp is not set. All parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @since 1.5.0 */ public void putDelete(byte[] columnFamily, byte[] columnQualifier, ColumnVisibility columnVisibility) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), false, 0l, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. Matches empty column visibility. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param timestamp * timestamp * @since 1.5.0 */ public void putDelete(byte[] columnFamily, byte[] columnQualifier, long timestamp) { put(columnFamily, columnQualifier, EMPTY_BYTES, true, timestamp, true, EMPTY_BYTES); } /** * Puts a deletion in this mutation. All appropriate parameters are defensively copied. * * @param columnFamily * column family * @param columnQualifier * column qualifier * @param columnVisibility * column visibility * @param timestamp * timestamp * @since 1.5.0 */ public void putDelete(byte[] columnFamily, byte[] columnQualifier, ColumnVisibility columnVisibility, long timestamp) { put(columnFamily, columnQualifier, columnVisibility.getExpression(), true, timestamp, true, EMPTY_BYTES); } private byte[] oldReadBytes(UnsynchronizedBuffer.Reader in) { int len = in.readInt(); if (len == 0) return EMPTY_BYTES; byte bytes[] = new byte[len]; in.readBytes(bytes); return bytes; } private byte[] readBytes(UnsynchronizedBuffer.Reader in) { int len = (int) in.readVLong(); if (len == 0) return EMPTY_BYTES; byte bytes[] = new byte[len]; in.readBytes(bytes); return bytes; } /** * Gets the modifications and deletions in this mutation. After calling this method, further modifications to this mutation are ignored. Changes made to the * returned updates do not affect this mutation. * * @return list of modifications and deletions */ public List<ColumnUpdate> getUpdates() { serialize(); UnsynchronizedBuffer.Reader in = new UnsynchronizedBuffer.Reader(data); if (updates == null) { if (entries == 1) { updates = Collections.singletonList(deserializeColumnUpdate(in)); } else { ColumnUpdate[] tmpUpdates = new ColumnUpdate[entries]; for (int i = 0; i < entries; i++) tmpUpdates[i] = deserializeColumnUpdate(in); updates = Arrays.asList(tmpUpdates); } } return updates; } protected ColumnUpdate newColumnUpdate(byte[] cf, byte[] cq, byte[] cv, boolean hasts, long ts, boolean deleted, byte[] val) { return new ColumnUpdate(cf, cq, cv, hasts, ts, deleted, val); } private ColumnUpdate deserializeColumnUpdate(UnsynchronizedBuffer.Reader in) { byte[] cf = readBytes(in); byte[] cq = readBytes(in); byte[] cv = readBytes(in); boolean hasts = in.readBoolean(); long ts = 0; if (hasts) ts = in.readVLong(); boolean deleted = in.readBoolean(); byte[] val; int valLen = (int) in.readVLong(); if (valLen < 0) { val = values.get((-1 * valLen) - 1); } else if (valLen == 0) { val = EMPTY_BYTES; } else { val = new byte[valLen]; in.readBytes(val); } return newColumnUpdate(cf, cq, cv, hasts, ts, deleted, val); } private int cachedValLens = -1; /** * Gets the byte length of all large values stored in this mutation. * * @return length of all large values * @see #VALUE_SIZE_COPY_CUTOFF */ long getValueLengths() { if (values == null) return 0; if (cachedValLens == -1) { int tmpCVL = 0; for (byte[] val : values) tmpCVL += val.length; cachedValLens = tmpCVL; } return cachedValLens; } /** * Gets the total number of bytes in this mutation. * * @return length of mutation in bytes */ public long numBytes() { serialize(); return row.length + data.length + getValueLengths(); } /** * Gets an estimate of the amount of memory used by this mutation. The estimate includes data sizes and object overhead. * * @return memory usage estimate */ public long estimatedMemoryUsed() { return numBytes() + 238; } /** * Gets the number of modifications / deletions in this mutation. * * @return the number of modifications / deletions */ public int size() { return entries; } /** * Add a new element to the set of peers which this Mutation originated from * * @param peer * the peer to add * @since 1.7.0 */ public void addReplicationSource(String peer) { if (null == replicationSources || replicationSources == EMPTY) { replicationSources = new HashSet<>(); } replicationSources.add(peer); } /** * Set the replication peers which this Mutation originated from * * @param sources * Set of peer names which have processed this update * @since 1.7.0 */ public void setReplicationSources(Set<String> sources) { requireNonNull(sources); this.replicationSources = sources; } /** * Return the replication sources for this Mutation * * @return An unmodifiable view of the replication sources */ public Set<String> getReplicationSources() { if (null == replicationSources) { return EMPTY; } return Collections.unmodifiableSet(replicationSources); } @Override public void readFields(DataInput in) throws IOException { // Clear out cached column updates and value lengths so // that we recalculate them based on the (potentially) new // data we are about to read in. updates = null; cachedValLens = -1; buffer = null; useOldDeserialize = false; byte first = in.readByte(); if ((first & 0x80) != 0x80) { oldReadFields(first, in); useOldDeserialize = true; return; } int len = WritableUtils.readVInt(in); row = new byte[len]; in.readFully(row); len = WritableUtils.readVInt(in); data = new byte[len]; in.readFully(data); entries = WritableUtils.readVInt(in); boolean valuesPresent = (first & 0x01) == 0x01; if (!valuesPresent) { values = null; } else { values = new ArrayList<>(); int numValues = WritableUtils.readVInt(in); for (int i = 0; i < numValues; i++) { len = WritableUtils.readVInt(in); byte val[] = new byte[len]; in.readFully(val); values.add(val); } } if (0x02 == (first & 0x02)) { int numMutations = WritableUtils.readVInt(in); this.replicationSources = new HashSet<>(); for (int i = 0; i < numMutations; i++) { replicationSources.add(WritableUtils.readString(in)); } } } protected void droppingOldTimestamp(long ts) {} private void oldReadFields(byte first, DataInput in) throws IOException { byte b = in.readByte(); byte c = in.readByte(); byte d = in.readByte(); int len = (((first & 0xff) << 24) | ((b & 0xff) << 16) | ((c & 0xff) << 8) | (d & 0xff)); row = new byte[len]; in.readFully(row); len = in.readInt(); byte[] localData = new byte[len]; in.readFully(localData); int localEntries = in.readInt(); List<byte[]> localValues; boolean valuesPresent = in.readBoolean(); if (!valuesPresent) { localValues = null; } else { localValues = new ArrayList<>(); int numValues = in.readInt(); for (int i = 0; i < numValues; i++) { len = in.readInt(); byte val[] = new byte[len]; in.readFully(val); localValues.add(val); } } // convert data to new format UnsynchronizedBuffer.Reader din = new UnsynchronizedBuffer.Reader(localData); buffer = new UnsynchronizedBuffer.Writer(); for (int i = 0; i < localEntries; i++) { byte[] cf = oldReadBytes(din); byte[] cq = oldReadBytes(din); byte[] cv = oldReadBytes(din); boolean hasts = din.readBoolean(); long ts = din.readLong(); boolean deleted = din.readBoolean(); byte[] val; int valLen = din.readInt(); if (valLen < 0) { val = localValues.get((-1 * valLen) - 1); } else if (valLen == 0) { val = EMPTY_BYTES; } else { val = new byte[valLen]; din.readBytes(val); } put(cf, cq, cv, hasts, ts, deleted, val); if (!hasts) droppingOldTimestamp(ts); } serialize(); } @Override public void write(DataOutput out) throws IOException { serialize(); byte hasValues = (values == null) ? 0 : (byte) 1; if (!replicationSources.isEmpty()) { // Use 2nd least-significant bit for whether or not we have replication sources hasValues = (byte) (0x02 | hasValues); } out.write((byte) (0x80 | hasValues)); WritableUtils.writeVInt(out, row.length); out.write(row); WritableUtils.writeVInt(out, data.length); out.write(data); WritableUtils.writeVInt(out, entries); if (0x01 == (0x01 & hasValues)) { WritableUtils.writeVInt(out, values.size()); for (int i = 0; i < values.size(); i++) { byte val[] = values.get(i); WritableUtils.writeVInt(out, val.length); out.write(val); } } if (0x02 == (0x02 & hasValues)) { WritableUtils.writeVInt(out, replicationSources.size()); for (String source : replicationSources) { WritableUtils.writeString(out, source); } } } @Override public boolean equals(Object o) { if (o == this) { return true; } if (o != null && o.getClass().equals(this.getClass())) { return equalMutation((Mutation) o); } return false; } @Override public int hashCode() { return serializedSnapshot().hashCode(); } /** * Checks if this mutation equals another. Two mutations are equal if they target the same row and have the same modifications and deletions, in order. This * method may be removed in a future API revision in favor of {@link #equals(Object)}. See ACCUMULO-1627 for more information. * * @param m * mutation to compare * @return true if this mutation equals the other, false otherwise */ public boolean equals(Mutation m) { return this.equals((Object) m); } private boolean equalMutation(Mutation m) { ByteBuffer myData = serializedSnapshot(); ByteBuffer otherData = m.serializedSnapshot(); if (Arrays.equals(row, m.row) && entries == m.entries && myData.equals(otherData)) { // If two mutations don't have the same if (!replicationSources.equals(m.replicationSources)) { return false; } if (values == null && m.values == null) return true; if (values != null && m.values != null && values.size() == m.values.size()) { for (int i = 0; i < values.size(); i++) { if (!Arrays.equals(values.get(i), m.values.get(i))) return false; } return true; } } return false; } /** * Creates a {@link org.apache.accumulo.core.data.thrift.TMutation} object containing this Mutation's data. * * Note that this method will move the Mutation into a "serialized" state that will prevent users from adding more data via Mutation#put(). * * @return a thrift form of this Mutation */ public TMutation toThrift() { return toThrift(true); } private TMutation toThrift(boolean serialize) { if (serialize) { this.serialize(); } ByteBuffer data = serializedSnapshot(); TMutation tmutation = new TMutation(ByteBuffer.wrap(row), data, ByteBufferUtil.toByteBuffers(values), entries); if (!this.replicationSources.isEmpty()) { tmutation.setSources(new ArrayList<>(replicationSources)); } return tmutation; } /** * Gets the serialization format used to (de)serialize this mutation. * * @return serialization format */ protected SERIALIZED_FORMAT getSerializedFormat() { return this.useOldDeserialize ? SERIALIZED_FORMAT.VERSION1 : SERIALIZED_FORMAT.VERSION2; } }