in phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMaintainer.java [727:894]
prependRegionStartKey ? (regionStartKey.length != 0 ? regionStartKey.length
: regionEndKey.length) : 0;
TrustedByteArrayOutputStream stream = new TrustedByteArrayOutputStream(estimatedIndexRowKeyBytes + (prependRegionStartKey ? prefixKeyLength : 0));
DataOutput output = new DataOutputStream(stream);
try {
// For local indexes, we must prepend the row key with the start region key
if (prependRegionStartKey) {
if (regionStartKey.length == 0) {
output.write(new byte[prefixKeyLength]);
} else {
output.write(regionStartKey);
}
}
if (isIndexSalted) {
output.write(0); // will be set at end to index salt byte
}
// The dataRowKeySchema includes the salt byte field,
// so we must adjust for that here.
int dataPosOffset = isDataTableSalted ? 1 : 0 ;
BitSet viewConstantColumnBitSet = this.rowKeyMetaData.getViewConstantColumnBitSet();
int nIndexedColumns = getIndexPkColumnCount() - getNumViewConstants();
int[][] dataRowKeyLocator = new int[2][nIndexedColumns];
// Skip data table salt byte
int maxRowKeyOffset = rowKeyPtr.getOffset() + rowKeyPtr.getLength();
dataRowKeySchema.iterator(rowKeyPtr, ptr, dataPosOffset);
if (viewIndexId != null) {
output.write(viewIndexId);
}
if (isMultiTenant) {
dataRowKeySchema.next(ptr, dataPosOffset, maxRowKeyOffset);
output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
if (!dataRowKeySchema.getField(dataPosOffset).getDataType().isFixedWidth()) {
output.write(SchemaUtil.getSeparatorBytes(
dataRowKeySchema.getField(dataPosOffset).getDataType(),
rowKeyOrderOptimizable,
ptr.getLength() == 0,
dataRowKeySchema.getField(dataPosOffset).getSortOrder()));
}
dataPosOffset++;
}
// Write index row key
for (int i = dataPosOffset; i < indexDataColumnCount; i++) {
Boolean hasValue=dataRowKeySchema.next(ptr, i, maxRowKeyOffset);
// Ignore view constants from the data table, as these
// don't need to appear in the index (as they're the
// same for all rows in this index)
if (!viewConstantColumnBitSet.get(i) || isIndexOnBaseTable()) {
int pos = rowKeyMetaData.getIndexPkPosition(i-dataPosOffset);
if (Boolean.TRUE.equals(hasValue)) {
dataRowKeyLocator[0][pos] = ptr.getOffset();
dataRowKeyLocator[1][pos] = ptr.getLength();
} else {
dataRowKeyLocator[0][pos] = 0;
dataRowKeyLocator[1][pos] = 0;
}
}
}
BitSet descIndexColumnBitSet = rowKeyMetaData.getDescIndexColumnBitSet();
Iterator<Expression> expressionIterator = indexedExpressions.iterator();
int trailingVariableWidthColumnNum = 0;
PDataType[] indexedColumnDataTypes = new PDataType[nIndexedColumns];
for (int i = 0; i < nIndexedColumns; i++) {
PDataType dataColumnType;
boolean isNullable;
SortOrder dataSortOrder;
if (dataPkPosition[i] == EXPRESSION_NOT_PRESENT) {
Expression expression = expressionIterator.next();
dataColumnType = expression.getDataType();
dataSortOrder = expression.getSortOrder();
isNullable = expression.isNullable();
if (expression instanceof PartitionIdFunction) {
if (i != 0) {
throw new DoNotRetryIOException("PARTITION_ID() has to be the prefix "
+ "of the index row key!");
} else if (!isCDCIndex) {
throw new DoNotRetryIOException("PARTITION_ID() should be used only for"
+ " CDC Indexes!");
}
ptr.set(encodedRegionName);
} else {
expression.evaluate(new ValueGetterTuple(valueGetter, ts), ptr);
}
}
else {
Field field = dataRowKeySchema.getField(dataPkPosition[i]);
dataColumnType = field.getDataType();
ptr.set(rowKeyPtr.get(), dataRowKeyLocator[0][i], dataRowKeyLocator[1][i]);
dataSortOrder = field.getSortOrder();
isNullable = field.isNullable();
}
boolean isDataColumnInverted = dataSortOrder != SortOrder.ASC;
PDataType indexColumnType = IndexUtil.getIndexColumnDataType(isNullable, dataColumnType);
indexedColumnDataTypes[i] = indexColumnType;
boolean isBytesComparable = dataColumnType.isBytesComparableWith(indexColumnType);
boolean isIndexColumnDesc = descIndexColumnBitSet.get(i);
if (isBytesComparable && isDataColumnInverted == isIndexColumnDesc) {
output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
} else {
if (!isBytesComparable) {
indexColumnType.coerceBytes(ptr, dataColumnType, dataSortOrder, SortOrder.getDefault());
}
if (isDataColumnInverted != isIndexColumnDesc) {
writeInverted(ptr.get(), ptr.getOffset(), ptr.getLength(), output);
} else {
output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
}
}
if (!indexColumnType.isFixedWidth()) {
output.write(
SchemaUtil.getSeparatorBytes(indexColumnType,
rowKeyOrderOptimizable,
ptr.getLength() == 0,
isIndexColumnDesc ? SortOrder.DESC : SortOrder.ASC));
trailingVariableWidthColumnNum++;
} else {
trailingVariableWidthColumnNum = 0;
}
}
byte[] indexRowKey = stream.getBuffer();
// Remove trailing nulls
int length = stream.size();
int minLength = length - maxTrailingNulls;
// The existing code does not eliminate the separator if the data type is not nullable. It not clear why.
// The actual bug is in the calculation of maxTrailingNulls with view indexes. So, in order not to impact some other cases, we should keep minLength check here.
int indexColumnIdx = nIndexedColumns - 1;
while (trailingVariableWidthColumnNum > 0 && length > minLength) {
if (indexColumnIdx < 0) {
break;
}
if (indexedColumnDataTypes[indexColumnIdx] != PVarbinaryEncoded.INSTANCE) {
if (indexRowKey[length - 1] == QueryConstants.SEPARATOR_BYTE) {
length--;
} else {
break;
}
} else {
byte[] sepBytes = QueryConstants.VARBINARY_ENCODED_SEPARATOR_BYTES;
if (length >= 2 && indexRowKey[length - 1] == sepBytes[1]
&& indexRowKey[length - 2] == sepBytes[0]) {
length -= 2;
} else {
break;
}
}
trailingVariableWidthColumnNum--;
indexColumnIdx--;
}
if (isIndexSalted) {
// Set salt byte
byte saltByte = SaltingUtil.getSaltingByte(indexRowKey, SaltingUtil.NUM_SALTING_BYTES, length-SaltingUtil.NUM_SALTING_BYTES, nIndexSaltBuckets);
indexRowKey[0] = saltByte;
}
return indexRowKey.length == length ? indexRowKey : Arrays.copyOf(indexRowKey, length);
} catch (IOException e) {
throw new RuntimeException(e); // Impossible
} finally {
try {
stream.close();
} catch (IOException e) {
throw new RuntimeException(e); // Impossible
}
}