/* * 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; /** * This is the Key used to store and access individual values in Accumulo. A Key is a tuple composed of a row, column family, column qualifier, * column visibility, timestamp, and delete marker. * * Keys are comparable and therefore have a sorted order defined by {@link #compareTo(Key)}. * */ import static org.apache.accumulo.core.util.ByteBufferUtil.toBytes; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.data.thrift.TKey; import org.apache.accumulo.core.data.thrift.TKeyValue; import org.apache.accumulo.core.security.ColumnVisibility; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; import org.apache.hadoop.io.WritableUtils; public class Key implements WritableComparable<Key>, Cloneable { protected byte[] row; protected byte[] colFamily; protected byte[] colQualifier; protected byte[] colVisibility; protected long timestamp; protected boolean deleted; /** * Create a {@link Key} builder. * * @since 2.0 * @param copyBytes * if the bytes of the {@link Key} components should be copied * @return the builder at the {@link KeyBuilder.RowStep} */ public static KeyBuilder.RowStep builder(boolean copyBytes) { return new KeyBuilder.KeyBuilderImpl(copyBytes); } /** * Create a {@link Key} builder. Copy bytes defaults to true. * * @since 2.0 * @return the builder at the {@link KeyBuilder.RowStep} */ public static KeyBuilder.RowStep builder() { return new KeyBuilder.KeyBuilderImpl(true); } @Override public boolean equals(Object o) { if (o instanceof Key) return this.equals((Key) o, PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL); return false; } private static final byte EMPTY_BYTES[] = new byte[0]; static byte[] copyIfNeeded(byte ba[], int off, int len, boolean copyData) { if (len == 0) return EMPTY_BYTES; if (!copyData && ba.length == len && off == 0) return ba; byte[] copy = new byte[len]; System.arraycopy(ba, off, copy, 0, len); return copy; } private final void init(byte r[], int rOff, int rLen, byte cf[], int cfOff, int cfLen, byte cq[], int cqOff, int cqLen, byte cv[], int cvOff, int cvLen, long ts, boolean del, boolean copy) { row = copyIfNeeded(r, rOff, rLen, copy); colFamily = copyIfNeeded(cf, cfOff, cfLen, copy); colQualifier = copyIfNeeded(cq, cqOff, cqLen, copy); colVisibility = copyIfNeeded(cv, cvOff, cvLen, copy); timestamp = ts; deleted = del; } /** * Creates a key with empty row, empty column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and delete marker * false. */ public Key() { row = EMPTY_BYTES; colFamily = EMPTY_BYTES; colQualifier = EMPTY_BYTES; colVisibility = EMPTY_BYTES; timestamp = Long.MAX_VALUE; deleted = false; } /** * Creates a key with the specified row, empty column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and delete * marker false. * * @param row * row ID */ public Key(Text row) { init(row.getBytes(), 0, row.getLength(), EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false, true); } /** * Creates a key with the specified row, empty column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and delete * marker false. This constructor creates a copy of row. If you don't want to create a copy of row, you should call * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @param row * row ID * @since 1.8.0 */ public Key(byte[] row) { init(row, 0, row.length, EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false, true); } /** * Creates a key with the specified row, empty column family, empty column qualifier, empty column visibility, the specified timestamp, and delete marker * false. * * @param row * row ID * @param ts * timestamp */ public Key(Text row, long ts) { this(row); timestamp = ts; } /** * Creates a key with the specified row, empty column family, empty column qualifier, empty column visibility, the specified timestamp, and delete marker * false. This constructor creates a copy of row. If you don't want to create a copy, you should call * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @param row * row ID * @param ts * timestamp * @since 1.8.0 */ public Key(byte[] row, long ts) { this(row); timestamp = ts; } /** * Creates a key. The delete marker defaults to false. This constructor creates a copy of each specified array. If you don't want to create a copy of the * arrays, you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @param row * bytes containing row ID * @param rOff * offset into row where key's row ID begins (inclusive) * @param rLen * length of row ID in row * @param cf * bytes containing column family * @param cfOff * offset into cf where key's column family begins (inclusive) * @param cfLen * length of column family in cf * @param cq * bytes containing column qualifier * @param cqOff * offset into cq where key's column qualifier begins (inclusive) * @param cqLen * length of column qualifier in cq * @param cv * bytes containing column visibility * @param cvOff * offset into cv where key's column visibility begins (inclusive) * @param cvLen * length of column visibility in cv * @param ts * timestamp */ public Key(byte row[], int rOff, int rLen, byte cf[], int cfOff, int cfLen, byte cq[], int cqOff, int cqLen, byte cv[], int cvOff, int cvLen, long ts) { init(row, rOff, rLen, cf, cfOff, cfLen, cq, cqOff, cqLen, cv, cvOff, cvLen, ts, false, true); } /** * Creates a key. The delete marker defaults to false. This constructor creates a copy of each specified array. If you don't want to create a copy of the * arrays, you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @param row * bytes containing row ID * @param rOff * offset into row where key's row ID begins (inclusive) * @param rLen * length of row ID in row * @param cf * bytes containing column family * @param cfOff * offset into cf where key's column family begins (inclusive) * @param cfLen * length of column family in cf * @param cq * bytes containing column qualifier * @param cqOff * offset into cq where key's column qualifier begins (inclusive) * @param cqLen * length of column qualifier in cq * @param cv * bytes containing column visibility * @param cvOff * offset into cv where key's column visibility begins (inclusive) * @param cvLen * length of column visibility in cv * @param ts * timestamp * @param deleted * delete marker * @param copy * if true, forces copy of byte array values into key */ Key(byte row[], int rOff, int rLen, byte cf[], int cfOff, int cfLen, byte cq[], int cqOff, int cqLen, byte cv[], int cvOff, int cvLen, long ts, boolean deleted, boolean copy) { init(row, rOff, rLen, cf, cfOff, cfLen, cq, cqOff, cqLen, cv, cvOff, cvLen, ts, deleted, copy); } /** * Creates a key. The delete marker defaults to false. This constructor creates a copy of each specified array. If you don't want to create a copy of the * arrays, you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @param row * row ID * @param colFamily * column family * @param colQualifier * column qualifier * @param colVisibility * column visibility * @param timestamp * timestamp */ public Key(byte[] row, byte[] colFamily, byte[] colQualifier, byte[] colVisibility, long timestamp) { this(row, colFamily, colQualifier, colVisibility, timestamp, false, true); } /** * Creates a key. This constructor creates a copy of each specified arrays. If you don't want to create a copy, you should call * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @param row * row ID * @param cf * column family * @param cq * column qualifier * @param cv * column visibility * @param ts * timestamp * @param deleted * delete marker */ public Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted) { this(row, cf, cq, cv, ts, deleted, true); } /** * Creates a key. * * @param row * row ID * @param cf * column family * @param cq * column qualifier * @param cv * column visibility * @param ts * timestamp * @param deleted * delete marker * @param copy * if true, forces copy of byte array values into key */ public Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy) { init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, cv, 0, cv.length, ts, deleted, copy); } /** * Creates a key with the specified row, the specified column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and * delete marker false. */ public Key(Text row, Text cf) { init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false, true); } /** * Creates a key with the specified row, the specified column family, empty column qualifier, empty column visibility, timestamp {@link Long#MAX_VALUE}, and * delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays, you should call * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @since 1.8.0 */ public Key(byte[] row, byte[] cf) { init(row, 0, row.length, cf, 0, cf.length, EMPTY_BYTES, 0, 0, EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, empty column visibility, timestamp * {@link Long#MAX_VALUE}, and delete marker false. */ public Key(Text row, Text cf, Text cq) { init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(), EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, empty column visibility, timestamp * {@link Long#MAX_VALUE}, and delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays, * you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @since 1.8.0 */ public Key(byte[] row, byte[] cf, byte[] cq) { init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, EMPTY_BYTES, 0, 0, Long.MAX_VALUE, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, timestamp * {@link Long#MAX_VALUE}, and delete marker false. */ public Key(Text row, Text cf, Text cq, Text cv) { init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(), cv.getBytes(), 0, cv.getLength(), Long.MAX_VALUE, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, timestamp * {@link Long#MAX_VALUE}, and delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays, * you should call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @since 1.8.0 */ public Key(byte[] row, byte[] cf, byte[] cq, byte[] cv) { init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, cv, 0, cv.length, Long.MAX_VALUE, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, empty column visibility, the specified timestamp, and * delete marker false. */ public Key(Text row, Text cf, Text cq, long ts) { init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(), EMPTY_BYTES, 0, 0, ts, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, empty column visibility, the specified timestamp, and * delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays, you should call * {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @since 1.8.0 */ public Key(byte[] row, byte[] cf, byte[] cq, long ts) { init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, EMPTY_BYTES, 0, 0, ts, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, the specified * timestamp, and delete marker false. */ public Key(Text row, Text cf, Text cq, Text cv, long ts) { init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(), cv.getBytes(), 0, cv.getLength(), ts, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, the specified * timestamp, and delete marker false. */ public Key(Text row, Text cf, Text cq, ColumnVisibility cv, long ts) { byte[] expr = cv.getExpression(); init(row.getBytes(), 0, row.getLength(), cf.getBytes(), 0, cf.getLength(), cq.getBytes(), 0, cq.getLength(), expr, 0, expr.length, ts, false, true); } /** * Creates a key with the specified row, the specified column family, the specified column qualifier, the specified column visibility, the specified * timestamp, and delete marker false. This constructor creates a copy of each specified array. If you don't want to create a copy of the arrays, you should * call {@link Key#Key(byte[] row, byte[] cf, byte[] cq, byte[] cv, long ts, boolean deleted, boolean copy)} instead. * * @since 1.8.0 */ public Key(byte[] row, byte[] cf, byte[] cq, ColumnVisibility cv, long ts) { byte[] expr = cv.getExpression(); init(row, 0, row.length, cf, 0, cf.length, cq, 0, cq.length, expr, 0, expr.length, ts, false, true); } /** * Converts CharSequence to Text and creates a Key using {@link #Key(Text)}. */ public Key(CharSequence row) { this(new Text(row.toString())); } /** * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text)}. */ public Key(CharSequence row, CharSequence cf) { this(new Text(row.toString()), new Text(cf.toString())); } /** * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text)}. */ public Key(CharSequence row, CharSequence cf, CharSequence cq) { this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString())); } /** * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text,Text)}. */ public Key(CharSequence row, CharSequence cf, CharSequence cq, CharSequence cv) { this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()), new Text(cv.toString())); } /** * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text,long)}. */ public Key(CharSequence row, CharSequence cf, CharSequence cq, long ts) { this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()), ts); } /** * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text,Text,long)}. */ public Key(CharSequence row, CharSequence cf, CharSequence cq, CharSequence cv, long ts) { this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()), new Text(cv.toString()), ts); } /** * Converts CharSequence to Text and creates a Key using {@link #Key(Text,Text,Text,ColumnVisibility,long)}. */ public Key(CharSequence row, CharSequence cf, CharSequence cq, ColumnVisibility cv, long ts) { this(new Text(row.toString()), new Text(cf.toString()), new Text(cq.toString()), new Text(cv.getExpression()), ts); } private byte[] followingArray(byte ba[]) { byte[] fba = new byte[ba.length + 1]; System.arraycopy(ba, 0, fba, 0, ba.length); fba[ba.length] = (byte) 0x00; return fba; } /** * Returns a key that will sort immediately after this key. * * @param part * PartialKey except {@link PartialKey#ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL} */ public Key followingKey(PartialKey part) { Key returnKey = new Key(); switch (part) { case ROW: returnKey.row = followingArray(row); break; case ROW_COLFAM: returnKey.row = row; returnKey.colFamily = followingArray(colFamily); break; case ROW_COLFAM_COLQUAL: returnKey.row = row; returnKey.colFamily = colFamily; returnKey.colQualifier = followingArray(colQualifier); break; case ROW_COLFAM_COLQUAL_COLVIS: // This isn't useful for inserting into accumulo, but may be useful for lookups. returnKey.row = row; returnKey.colFamily = colFamily; returnKey.colQualifier = colQualifier; returnKey.colVisibility = followingArray(colVisibility); break; case ROW_COLFAM_COLQUAL_COLVIS_TIME: returnKey.row = row; returnKey.colFamily = colFamily; returnKey.colQualifier = colQualifier; returnKey.colVisibility = colVisibility; returnKey.setTimestamp(timestamp - 1); returnKey.deleted = false; break; default: throw new IllegalArgumentException("Partial key specification " + part + " disallowed"); } return returnKey; } /** * Creates a key with the same row, column family, column qualifier, column visibility, timestamp, and delete marker as the given key. */ public Key(Key other) { set(other); } /** * Creates a key from Thrift. * * @param tkey * Thrift key */ public Key(TKey tkey) { this.row = toBytes(tkey.row); this.colFamily = toBytes(tkey.colFamily); this.colQualifier = toBytes(tkey.colQualifier); this.colVisibility = toBytes(tkey.colVisibility); this.timestamp = tkey.timestamp; this.deleted = false; if (row == null) { throw new IllegalArgumentException("null row"); } if (colFamily == null) { throw new IllegalArgumentException("null column family"); } if (colQualifier == null) { throw new IllegalArgumentException("null column qualifier"); } if (colVisibility == null) { throw new IllegalArgumentException("null column visibility"); } } /** * Writes the row ID into the given <code>Text</code>. This method gives users control over allocation of Text objects by copying into the passed in text. * * @param r * <code>Text</code> object to copy into * @return the <code>Text</code> that was passed in */ public Text getRow(Text r) { r.set(row, 0, row.length); return r; } /** * Returns the row ID as a byte sequence. This method returns a pointer to the key's internal data and does not copy it. * * @return ByteSequence that points to the internal key row ID data */ public ByteSequence getRowData() { return new ArrayByteSequence(row); } /** * Gets the row ID as a <code>Text</code> object. * * @return Text containing the row ID */ public Text getRow() { return getRow(new Text()); } /** * Compares this key's row ID with another. * * @param r * row ID to compare * @return same as {@link #getRow()}.compareTo(r) */ public int compareRow(Text r) { return WritableComparator.compareBytes(row, 0, row.length, r.getBytes(), 0, r.getLength()); } /** * Returns the column family as a byte sequence. This method returns a pointer to the key's internal data and does not copy it. * * @return ByteSequence that points to the internal key column family data */ public ByteSequence getColumnFamilyData() { return new ArrayByteSequence(colFamily); } /** * Writes the column family into the given <code>Text</code>. This method gives users control over allocation of Text objects by copying into the passed in * text. * * @param cf * <code>Text</code> object to copy into * @return the <code>Text</code> that was passed in */ public Text getColumnFamily(Text cf) { cf.set(colFamily, 0, colFamily.length); return cf; } /** * Gets the column family as a <code>Text</code> object. * * @return Text containing the column family */ public Text getColumnFamily() { return getColumnFamily(new Text()); } /** * Compares this key's column family with another. * * @param cf * column family to compare * @return same as {@link #getColumnFamily()}.compareTo(cf) */ public int compareColumnFamily(Text cf) { return WritableComparator.compareBytes(colFamily, 0, colFamily.length, cf.getBytes(), 0, cf.getLength()); } /** * Returns the column qualifier as a byte sequence. This method returns a pointer to the key's internal data and does not copy it. * * @return ByteSequence that points to the internal key column qualifier data */ public ByteSequence getColumnQualifierData() { return new ArrayByteSequence(colQualifier); } /** * Writes the column qualifier into the given <code>Text</code>. This method gives users control over allocation of Text objects by copying into the passed in * text. * * @param cq * <code>Text</code> object to copy into * @return the <code>Text</code> that was passed in */ public Text getColumnQualifier(Text cq) { cq.set(colQualifier, 0, colQualifier.length); return cq; } /** * Gets the column qualifier as a <code>Text</code> object. * * @return Text containing the column qualifier */ public Text getColumnQualifier() { return getColumnQualifier(new Text()); } /** * Compares this key's column qualifier with another. * * @param cq * column qualifier to compare * @return same as {@link #getColumnQualifier()}.compareTo(cq) */ public int compareColumnQualifier(Text cq) { return WritableComparator.compareBytes(colQualifier, 0, colQualifier.length, cq.getBytes(), 0, cq.getLength()); } /** * Sets the timestamp. * * @param ts * timestamp */ public void setTimestamp(long ts) { this.timestamp = ts; } /** * Gets the timestamp. * * @return timestamp */ public long getTimestamp() { return timestamp; } /** * Determines if this key is deleted (i.e., has a delete marker = true). * * @return true if key is deleted, false if not */ public boolean isDeleted() { return deleted; } /** * Sets the delete marker on this key. * * @param deleted * delete marker (true to delete) */ public void setDeleted(boolean deleted) { this.deleted = deleted; } /** * Returns the column visibility as a byte sequence. This method returns a pointer to the key's internal data and does not copy it. * * @return ByteSequence that points to the internal key column visibility data */ public ByteSequence getColumnVisibilityData() { return new ArrayByteSequence(colVisibility); } /** * Gets the column visibility as a <code>Text</code> object. * * @return Text containing the column visibility */ public final Text getColumnVisibility() { return getColumnVisibility(new Text()); } /** * Writes the column visibvility into the given <code>Text</code>. This method gives users control over allocation of Text objects by copying into the passed * in text. * * @param cv * <code>Text</code> object to copy into * @return the <code>Text</code> that was passed in */ public final Text getColumnVisibility(Text cv) { cv.set(colVisibility, 0, colVisibility.length); return cv; } /** * Gets the column visibility. <b>WARNING:</b> using this method may inhibit performance since a new ColumnVisibility object is created on every call. * * @return ColumnVisibility representing the column visibility * @since 1.5.0 */ public final ColumnVisibility getColumnVisibilityParsed() { return new ColumnVisibility(colVisibility); } /** * Sets this key's row, column family, column qualifier, column visibility, timestamp, and delete marker to be the same as another key's. This method does not * copy data from the other key, but only references to it. * * @param k * key to set from */ public void set(Key k) { row = k.row; colFamily = k.colFamily; colQualifier = k.colQualifier; colVisibility = k.colVisibility; timestamp = k.timestamp; deleted = k.deleted; } @Override public void readFields(DataInput in) throws IOException { // this method is a little screwy so it will be compatible with older // code that serialized data int colFamilyOffset = WritableUtils.readVInt(in); int colQualifierOffset = WritableUtils.readVInt(in); int colVisibilityOffset = WritableUtils.readVInt(in); int totalLen = WritableUtils.readVInt(in); row = new byte[colFamilyOffset]; colFamily = new byte[colQualifierOffset - colFamilyOffset]; colQualifier = new byte[colVisibilityOffset - colQualifierOffset]; colVisibility = new byte[totalLen - colVisibilityOffset]; in.readFully(row); in.readFully(colFamily); in.readFully(colQualifier); in.readFully(colVisibility); timestamp = WritableUtils.readVLong(in); deleted = in.readBoolean(); } @Override public void write(DataOutput out) throws IOException { int colFamilyOffset = row.length; int colQualifierOffset = colFamilyOffset + colFamily.length; int colVisibilityOffset = colQualifierOffset + colQualifier.length; int totalLen = colVisibilityOffset + colVisibility.length; WritableUtils.writeVInt(out, colFamilyOffset); WritableUtils.writeVInt(out, colQualifierOffset); WritableUtils.writeVInt(out, colVisibilityOffset); WritableUtils.writeVInt(out, totalLen); out.write(row); out.write(colFamily); out.write(colQualifier); out.write(colVisibility); WritableUtils.writeVLong(out, timestamp); out.writeBoolean(deleted); } /** * Compares part of a key. For example, compares just the row and column family, and if those are equal then return true. * * @param other * key to compare to * @param part * part of key to compare * @return true if specified parts of keys match, false otherwise */ public boolean equals(Key other, PartialKey part) { switch (part) { case ROW: return isEqual(row, other.row); case ROW_COLFAM: return isEqual(row, other.row) && isEqual(colFamily, other.colFamily); case ROW_COLFAM_COLQUAL: return isEqual(row, other.row) && isEqual(colFamily, other.colFamily) && isEqual(colQualifier, other.colQualifier); case ROW_COLFAM_COLQUAL_COLVIS: return isEqual(row, other.row) && isEqual(colFamily, other.colFamily) && isEqual(colQualifier, other.colQualifier) && isEqual(colVisibility, other.colVisibility); case ROW_COLFAM_COLQUAL_COLVIS_TIME: return isEqual(row, other.row) && isEqual(colFamily, other.colFamily) && isEqual(colQualifier, other.colQualifier) && isEqual(colVisibility, other.colVisibility) && timestamp == other.timestamp; case ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL: return isEqual(row, other.row) && isEqual(colFamily, other.colFamily) && isEqual(colQualifier, other.colQualifier) && isEqual(colVisibility, other.colVisibility) && timestamp == other.timestamp && deleted == other.deleted; default: throw new IllegalArgumentException("Unrecognized partial key specification " + part); } } /** * Compares elements of a key given by a {@link PartialKey}. The corresponding elements (row, column family, column qualifier, column visibility, timestamp, * and delete marker) are compared in order until unequal elements are found. The row, column family, column qualifier, and column visibility are compared * lexographically and sorted ascending. The timestamps are compared numerically and sorted descending so that the most recent data comes first. Lastly, a * delete marker of true sorts before a delete marker of false. The result of the first unequal comparison is returned. * * For example, for {@link PartialKey#ROW_COLFAM}, this method compares just the row and column family. If the row IDs are not equal, return the result of the * row comparison; otherwise, returns the result of the column family comparison. * * @param other * key to compare to * @param part * part of key to compare * @return comparison result * @see #compareTo(Key) */ public int compareTo(Key other, PartialKey part) { // check for matching row int result = WritableComparator.compareBytes(row, 0, row.length, other.row, 0, other.row.length); if (result != 0 || part.equals(PartialKey.ROW)) return result; // check for matching column family result = WritableComparator.compareBytes(colFamily, 0, colFamily.length, other.colFamily, 0, other.colFamily.length); if (result != 0 || part.equals(PartialKey.ROW_COLFAM)) return result; // check for matching column qualifier result = WritableComparator.compareBytes(colQualifier, 0, colQualifier.length, other.colQualifier, 0, other.colQualifier.length); if (result != 0 || part.equals(PartialKey.ROW_COLFAM_COLQUAL)) return result; // check for matching column visibility result = WritableComparator.compareBytes(colVisibility, 0, colVisibility.length, other.colVisibility, 0, other.colVisibility.length); if (result != 0 || part.equals(PartialKey.ROW_COLFAM_COLQUAL_COLVIS)) return result; // check for matching timestamp if (timestamp < other.timestamp) result = 1; else if (timestamp > other.timestamp) result = -1; else result = 0; if (result != 0 || part.equals(PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME)) return result; // check for matching deleted flag if (deleted) result = other.deleted ? 0 : -1; else result = other.deleted ? 1 : 0; return result; } @Override public int compareTo(Key other) { return compareTo(other, PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME_DEL); } @Override public int hashCode() { return WritableComparator.hashBytes(row, row.length) + WritableComparator.hashBytes(colFamily, colFamily.length) + WritableComparator.hashBytes(colQualifier, colQualifier.length) + WritableComparator.hashBytes(colVisibility, colVisibility.length) + (int) (timestamp ^ (timestamp >>> 32)); } /** * Returns an ASCII printable string form of the given byte array, treating the bytes as ASCII characters. See * {@link #appendPrintableString(byte[], int, int, int, StringBuilder)} for caveats. * * @param ba * byte array * @param offset * offset to start with in byte array (inclusive) * @param len * number of bytes to print * @param maxLen * maximum number of bytes to convert to printable form * @return printable string * @see #appendPrintableString(byte[], int, int, int, StringBuilder) */ public static String toPrintableString(byte ba[], int offset, int len, int maxLen) { return appendPrintableString(ba, offset, len, maxLen, new StringBuilder()).toString(); } /** * Appends ASCII printable characters to a string, based on the given byte array, treating the bytes as ASCII characters. If a byte can be converted to a * ASCII printable character it is appended as is; otherwise, it is appended as a character code, e.g., %05; for byte value 5. If len > maxlen, the string * includes a "TRUNCATED" note at the end. * * @param ba * byte array * @param offset * offset to start with in byte array (inclusive) * @param len * number of bytes to print * @param maxLen * maximum number of bytes to convert to printable form * @param sb * <code>StringBuilder</code> to append to * @return given <code>StringBuilder</code> */ public static StringBuilder appendPrintableString(byte ba[], int offset, int len, int maxLen, StringBuilder sb) { int plen = Math.min(len, maxLen); for (int i = 0; i < plen; i++) { int c = 0xff & ba[offset + i]; if (c >= 32 && c <= 126) sb.append((char) c); else sb.append("%" + String.format("%02x;", c)); } if (len > maxLen) { sb.append("... TRUNCATED"); } return sb; } private StringBuilder rowColumnStringBuilder() { return rowColumnStringBuilder(Constants.MAX_DATA_TO_PRINT); } private StringBuilder rowColumnStringBuilder(int maxComponentLength) { StringBuilder sb = new StringBuilder(); appendPrintableString(row, 0, row.length, maxComponentLength, sb); sb.append(" "); appendPrintableString(colFamily, 0, colFamily.length, maxComponentLength, sb); sb.append(":"); appendPrintableString(colQualifier, 0, colQualifier.length, maxComponentLength, sb); sb.append(" ["); appendPrintableString(colVisibility, 0, colVisibility.length, maxComponentLength, sb); sb.append("]"); return sb; } @Override public String toString() { StringBuilder sb = rowColumnStringBuilder(); sb.append(" "); sb.append(Long.toString(timestamp)); sb.append(" "); sb.append(deleted); return sb.toString(); } /** * Stringify this {@link Key}, avoiding truncation of each component, only limiting each component to a length of {@link Integer#MAX_VALUE} * * @since 1.7.0 */ public String toStringNoTruncate() { StringBuilder sb = rowColumnStringBuilder(Integer.MAX_VALUE); sb.append(" "); sb.append(Long.toString(timestamp)); sb.append(" "); sb.append(deleted); return sb.toString(); } /** * Converts this key to a string, not including timestamp or delete marker. * * @return string form of key */ public String toStringNoTime() { return rowColumnStringBuilder().toString(); } /** * Returns the sums of the lengths of the row, column family, column qualifier, and column visibility. * * @return sum of key field lengths */ public int getLength() { return row.length + colFamily.length + colQualifier.length + colVisibility.length; } /** * Same as {@link #getLength()}. * * @return sum of key field lengths */ public int getSize() { return getLength(); } private static boolean isEqual(byte a1[], byte a2[]) { if (a1 == a2) return true; int last = a1.length; if (last != a2.length) return false; if (last == 0) return true; // since sorted data is usually compared in accumulo, // the prefixes will normally be the same... so compare // the last two charachters first.. the most likely place // to have disorder is at end of the strings when the // data is sorted... if those are the same compare the rest // of the data forward... comparing backwards is slower // (compiler and cpu optimized for reading data forward).. // do not want slower comparisons when data is equal... // sorting brings equals data together last--; if (a1[last] == a2[last]) { for (int i = 0; i < last; i++) if (a1[i] != a2[i]) return false; } else { return false; } return true; } /** * Compresses a list of key/value pairs before sending them via thrift. * * @param param * list of key/value pairs * @return list of Thrift key/value pairs */ public static List<TKeyValue> compress(List<? extends KeyValue> param) { List<TKeyValue> tkvl = Arrays.asList(new TKeyValue[param.size()]); if (param.size() > 0) tkvl.set(0, new TKeyValue(param.get(0).getKey().toThrift(), ByteBuffer.wrap(param.get(0).getValue().get()))); for (int i = param.size() - 1; i > 0; i--) { Key prevKey = param.get(i - 1).getKey(); KeyValue kv = param.get(i); Key key = kv.getKey(); TKey newKey = null; if (isEqual(prevKey.row, key.row)) { newKey = key.toThrift(); newKey.row = null; } if (isEqual(prevKey.colFamily, key.colFamily)) { if (newKey == null) newKey = key.toThrift(); newKey.colFamily = null; } if (isEqual(prevKey.colQualifier, key.colQualifier)) { if (newKey == null) newKey = key.toThrift(); newKey.colQualifier = null; } if (isEqual(prevKey.colVisibility, key.colVisibility)) { if (newKey == null) newKey = key.toThrift(); newKey.colVisibility = null; } if (newKey == null) newKey = key.toThrift(); tkvl.set(i, new TKeyValue(newKey, ByteBuffer.wrap(kv.getValue().get()))); } return tkvl; } /** * Decompresses a list of key/value pairs received from thrift. Decompression occurs in place, in the list. * * @param param * list of Thrift key/value pairs */ public static void decompress(List<TKeyValue> param) { for (int i = 1; i < param.size(); i++) { TKey prevKey = param.get(i - 1).key; TKey key = param.get(i).key; if (key.row == null) { key.row = prevKey.row; } if (key.colFamily == null) { key.colFamily = prevKey.colFamily; } if (key.colQualifier == null) { key.colQualifier = prevKey.colQualifier; } if (key.colVisibility == null) { key.colVisibility = prevKey.colVisibility; } } } /** * Gets the row ID as a byte array. * * @return row ID */ byte[] getRowBytes() { return row; } /** * Gets the column family as a byte array. * * @return column family */ byte[] getColFamily() { return colFamily; } /** * Gets the column qualifier as a byte array. * * @return column qualifier */ byte[] getColQualifier() { return colQualifier; } /** * Gets the column visibility as a byte array. * * @return column visibility */ byte[] getColVisibility() { return colVisibility; } /** * Converts this key to Thrift. * * @return Thrift key */ public TKey toThrift() { return new TKey(ByteBuffer.wrap(row), ByteBuffer.wrap(colFamily), ByteBuffer.wrap(colQualifier), ByteBuffer.wrap(colVisibility), timestamp); } @Override public Object clone() throws CloneNotSupportedException { Key r = (Key) super.clone(); r.row = Arrays.copyOf(row, row.length); r.colFamily = Arrays.copyOf(colFamily, colFamily.length); r.colQualifier = Arrays.copyOf(colQualifier, colQualifier.length); r.colVisibility = Arrays.copyOf(colVisibility, colVisibility.length); return r; } }