blob: cd827483d36cbf52e945bb63b739ae8e3ef348d5 [file] [log] [blame]
/*
* 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.phoenix.schema;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableUtils;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.util.ByteUtil;
/**
*
* Class that encapsulates accessing a value stored in the row key.
*
*
* @since 0.1
*/
public class RowKeyValueAccessor implements Writable {
/**
* Constructor solely for use during deserialization. Should not
* otherwise be used.
*/
public RowKeyValueAccessor() {
}
/**
* Constructor to compile access to the value in the row key formed from
* a list of PData.
*
* @param data the list of data that make up the key
* @param index the zero-based index of the data item to access.
*/
public RowKeyValueAccessor(List<? extends PDatum> data, int index) {
this.index = index;
int[] offsets = new int[data.size()];
int nOffsets = 0;
Iterator<? extends PDatum> iterator = data.iterator();
PDatum datum = iterator.next();
int pos = 0;
while (pos < index) {
int offset = 0;
if (datum.getDataType().isFixedWidth()) {
do {
// For non parameterized types such as BIGINT, the type will return its max length.
// For parameterized types, for example CHAR(10) the type cannot know the max length,
// so in this case, the max length is retrieved from the datum.
Integer maxLength = datum.getDataType().getByteSize();
offset += maxLength == null ? datum.getMaxLength() : maxLength;
datum = iterator.next();
pos++;
} while (pos < index && datum.getDataType().isFixedWidth());
offsets[nOffsets++] = offset; // Encode fixed byte offset as positive
} else {
do {
offset++; // Count the number of variable length columns
datum = iterator.next();
pos++;
} while (pos < index && !datum.getDataType().isFixedWidth());
offsets[nOffsets++] = -offset; // Encode number of variable length columns as negative
}
}
if (nOffsets < offsets.length) {
this.offsets = Arrays.copyOf(offsets, nOffsets);
} else {
this.offsets = offsets;
}
// Remember this so that we don't bother looking for the null separator byte in this case
this.isFixedLength = datum.getDataType().isFixedWidth();
this.hasSeparator = !isFixedLength && (datum != data.get(data.size()-1));
}
RowKeyValueAccessor(int[] offsets, boolean isFixedLength, boolean hasSeparator) {
this.offsets = offsets;
this.isFixedLength = isFixedLength;
this.hasSeparator = hasSeparator;
}
private int index = -1; // Only available on client side
private int[] offsets;
private boolean isFixedLength;
private boolean hasSeparator;
public int getIndex() {
return index;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (hasSeparator ? 1231 : 1237);
result = prime * result + (isFixedLength ? 1231 : 1237);
result = prime * result + Arrays.hashCode(offsets);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
RowKeyValueAccessor other = (RowKeyValueAccessor)obj;
if (hasSeparator != other.hasSeparator) return false;
if (isFixedLength != other.isFixedLength) return false;
if (!Arrays.equals(offsets, other.offsets)) return false;
return true;
}
@Override
public String toString() {
return "RowKeyValueAccessor [offsets=" + Arrays.toString(offsets) + ", isFixedLength=" + isFixedLength
+ ", hasSeparator=" + hasSeparator + "]";
}
@Override
public void readFields(DataInput input) throws IOException {
// Decode hasSeparator and isFixedLength from vint storing offset array length
int length = WritableUtils.readVInt(input);
hasSeparator = (length & 0x02) != 0;
isFixedLength = (length & 0x01) != 0;
length >>= 2;
offsets = ByteUtil.deserializeVIntArray(input, length);
}
@Override
public void write(DataOutput output) throws IOException {
// Encode hasSeparator and isFixedLength into vint storing offset array length
// (since there's plenty of room)
int length = offsets.length << 2;
length |= (hasSeparator ? 1 << 1 : 0) | (isFixedLength ? 1 : 0);
ByteUtil.serializeVIntArray(output, offsets, length);
}
private static boolean isSeparatorByte(byte b) {
return b == QueryConstants.SEPARATOR_BYTE || b == QueryConstants.DESC_SEPARATOR_BYTE;
}
/**
* Calculate the byte offset in the row key to the start of the PK column value
* @param keyBuffer the byte array of the row key
* @param keyOffset the offset in the byte array of where the key begins
* @return byte offset to the start of the PK column value
*/
public int getOffset(byte[] keyBuffer, int keyOffset) {
// Use encoded offsets to navigate through row key buffer
for (int offset : offsets) {
if (offset >= 0) { // If offset is non negative, it's a byte offset
keyOffset += offset;
} else { // Else, a negative offset is the number of variable length values to skip
while (offset++ < 0) {
// FIXME: keyOffset < keyBuffer.length required because HBase passes bogus keys to filter to position scan (HBASE-6562)
while (keyOffset < keyBuffer.length && !isSeparatorByte(keyBuffer[keyOffset++])) {
}
}
}
}
return keyOffset;
}
/**
* Calculate the length of the PK column value
* @param keyBuffer the byte array of the row key
* @param keyOffset the offset in the byte array of where the key begins
* @param maxOffset maximum offset to use while calculating length
* @return the length of the PK column value
*/
public int getLength(byte[] keyBuffer, int keyOffset, int maxOffset) {
if (!hasSeparator) {
return maxOffset - keyOffset - (keyBuffer[maxOffset-1] == QueryConstants.DESC_SEPARATOR_BYTE ? 1 : 0);
}
int offset = keyOffset;
// FIXME: offset < maxOffset required because HBase passes bogus keys to filter to position scan (HBASE-6562)
while (offset < maxOffset && !isSeparatorByte(keyBuffer[offset])) {
offset++;
}
return offset - keyOffset;
}
}