blob: b6846c4fc88ed1144d41d7e9b9886b9c382f53ba [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.ignite.internal.schema;
import java.util.Arrays;
import java.util.Comparator;
import java.util.NoSuchElementException;
import org.apache.ignite.internal.tostring.S;
/**
* A set of columns representing a key or a value chunk in a row. Provides necessary machinery to locate a column value in a concrete row.
*
* <p><h3>Column ordering.</h3>
* Column instances are comparable in lexicographic order, native type first and then column name. Nullability flag is not taken into
* account when columns are compared. Native type order guarantees fixed-len columns will prior to varlen columns, column name order
* guarantees the same column order (for the same type) on all nodes.
*
* @see #COLUMN_COMPARATOR
*/
public class Columns {
public static final int[][] EMPTY_FOLDING_TABLE = new int[0][];
public static final int[] EMPTY_FOLDING_MASK = new int[0];
/**
* Lookup table to speed-up calculation of the number of null/non-null columns based on the null map.
* For a given byte {@code b}, {@code NULL_COLUMNS_LOOKUP[b]} will contain the number of {@code null} columns
* corresponding to the byte in nullability map. For example, if nullability map is {@code 0b00100001}, then the map
* encodes nulls for columns 0 and 5 and {@code NULL_COLUMNS_LOOKUP[0b00100001] == 2}.
*/
private static final int[] NULL_COLUMNS_LOOKUP;
/**
* Columns in packed order for this chunk.
*/
private final Column[] cols;
/**
* If the type contains varlength columns, this field will contain an index of the first such column. Otherwise, it will contain {@code
* -1}.
*/
private final int firstVarlenColIdx;
/**
* Number of bytes required to store the nullability map for this chunk.
*/
private final int nullMapSize;
/**
* Estimated max length of fixed-size columns.
*/
private int fixedSizeMaxLen;
/**
* Fixed-size column length folding table. The table is used to quickly calculate the offset of a fixed-length column based on the
* nullability map.
*/
private int[][] foldingTbl;
/**
* Additional mask values for folding table to cut off nullability map for columns with larger indexes.
*/
private int[] foldingMask;
static {
NULL_COLUMNS_LOOKUP = new int[256];
// Each nonzero bit is a null value.
for (int i = 0; i < 255; i++) {
NULL_COLUMNS_LOOKUP[i] = Integer.bitCount(i);
}
}
/**
* Column comparator.
*/
public static final Comparator<Column> COLUMN_COMPARATOR = Comparator.comparing(Column::type).thenComparing(Column::name);
/**
* Gets a number of null columns for the given byte from the nullability map (essentially, the number of non-zero bits in the given
* byte).
*
* @param nullMap Byte from a nullability map.
* @return Number of null columns for the given byte.
*/
public static int numberOfNullColumns(byte nullMap) {
return NULL_COLUMNS_LOOKUP[nullMap & 0xFF];
}
/**
* Constructs the columns chunk. The columns will be internally sorted in write-efficient order based on {@link Column} comparison.
*
* @param baseSchemaIdx First column absolute index in schema.
* @param cols Array of columns.
*/
Columns(int baseSchemaIdx, Column... cols) {
this.cols = sortedCopy(baseSchemaIdx, cols);
firstVarlenColIdx = findFirstVarlenColumn();
nullMapSize = hasNullableColumn() ? (cols.length + 7) / 8 : 0;
buildFoldingTable();
}
/**
* Calculates a sum of fixed-sized columns lengths given the mask of the present columns, assuming that the {@code maskByte} is an
* {@code i}-th byte is columns mask.
*
* @param i Mask byte index in the nullability map.
* @param maskByte Mask byte value, where a nonzero bit (counting from LSB to MSB) represents a {@code null} value and the corresponding
* column length should be skipped.
* @return Fixed columns length sizes summed wrt to the mask.
*/
public int foldFixedLength(int i, int maskByte) {
return foldingTbl[i][maskByte & foldingMask[i]];
}
/**
* Get number of bytes required to store the nullability map for these columns.
*/
public int nullMapSize() {
return nullMapSize;
}
/**
* Get column type's fixed size flag by column index.
*
* @param idx Column index to check.
* @return {@code true} if the column with the given index is fixed-size.
*/
public boolean isFixedSize(int idx) {
return cols[idx].type().spec().fixedLength();
}
/**
* Get column by it's index.
*
* @param idx Column index.
* @return Column instance.
*/
public Column column(int idx) {
return cols[idx];
}
/**
* Get sorted columns array.
*/
public Column[] columns() {
return cols;
}
/**
* Get number of columns in this chunk.
*/
public int length() {
return cols.length;
}
/**
* Get number of varlength columns in this chunk.
*/
public int numberOfVarlengthColumns() {
return cols.length - numberOfFixsizeColumns();
}
/**
* Get number of fixsize columns in this chunk.
*/
public int numberOfFixsizeColumns() {
return firstVarlenColIdx == -1 ? cols.length : firstVarlenColIdx;
}
/**
* Get index of the first varlength column in the sorted order of columns or return {@code -1} if varlength not found.
*/
public int firstVarlengthColumn() {
return firstVarlenColIdx;
}
/**
* Get has varlength columns flag: {@code True} if there is at least one varlength column.
*/
public boolean hasVarlengthColumns() {
return firstVarlenColIdx != -1;
}
/**
* Get fixsize columns size upper bound.
*/
public int fixsizeMaxLen() {
return fixedSizeMaxLen;
}
/**
* SortedCopy.
* TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
*
* @param schemaBaseIdx Base index of this columns object in its schema.
* @param cols User columns.
* @return A copy of user columns array sorted in column order.
*/
private Column[] sortedCopy(int schemaBaseIdx, Column[] cols) {
Column[] cp = Arrays.copyOf(cols, cols.length);
Arrays.sort(cp, COLUMN_COMPARATOR);
for (int i = 0; i < cp.length; i++) {
Column c = cp[i];
cp[i] = c.copy(schemaBaseIdx + i);
}
return cp;
}
/**
* Get index of the first varlength column or {@code -1} if there are none.
*/
private int findFirstVarlenColumn() {
for (int i = 0; i < cols.length; i++) {
if (!cols[i].type().spec().fixedLength()) {
return i;
}
}
return -1;
}
/**
* Get has nullable column flag: {@code True} if there is one or more nullable columns, {@code false} otherwise.
*/
private boolean hasNullableColumn() {
for (int i = 0; i < cols.length; i++) {
if (cols[i].nullable()) {
return true;
}
}
return false;
}
private void buildFoldingTable() {
int numFixsize = numberOfFixsizeColumns();
if (numFixsize == 0) {
foldingTbl = EMPTY_FOLDING_TABLE;
foldingMask = EMPTY_FOLDING_MASK;
fixedSizeMaxLen = 0;
return;
}
// First varlen column located right after last fixlen so we need one extra row in folding table.
if (hasVarlengthColumns()) {
numFixsize++;
}
int fixsizeNullMapSize = (numFixsize + 7) / 8;
int maxLen = 0;
int[][] res = new int[fixsizeNullMapSize][];
int[] resMask = new int[fixsizeNullMapSize];
for (int b = 0; b < fixsizeNullMapSize; b++) {
int bitsInMask = b == fixsizeNullMapSize - 1 ? (numFixsize - 8 * b) : 8;
int totalMasks = 1 << bitsInMask;
resMask[b] = 0xFF >>> (8 - bitsInMask);
res[b] = new int[totalMasks];
// Start with all non-nulls.
int mask = 0x00;
for (int i = 0; i < totalMasks; i++) {
res[b][mask] = foldManual(b, mask);
mask++;
}
maxLen += res[b][0];
}
foldingTbl = res;
foldingMask = resMask;
fixedSizeMaxLen = maxLen;
}
/**
* Manually fold the sizes of the fixed-size columns based on the nullability map byte.
*
* @param b Nullability map byte index.
* @param mask Nullability mask from the map.
* @return Sum of column sizes based nullability mask.
*/
private int foldManual(int b, int mask) {
int size = 0;
for (int bit = 0; bit < 8; bit++) {
boolean hasVal = (mask & (1 << bit)) == 0;
int idx = b * 8 + bit;
if (hasVal && idx < numberOfFixsizeColumns()) {
assert cols[idx].type().spec().fixedLength() : "Expected fixed-size column [b=" + b
+ ", mask=" + mask
+ ", cols" + Arrays.toString(cols) + ']';
size += cols[idx].type().sizeInBytes();
}
}
return size;
}
/**
* Returns columns index for given column name.
*
* @param colName Column name.
* @return Column index.
*/
public int columnIndex(String colName) {
for (int i = 0; i < cols.length; i++) {
if (cols[i].name().equalsIgnoreCase(colName)) {
return i;
}
}
throw new NoSuchElementException("No field '" + colName + "' defined");
}
/** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Columns columns = (Columns) o;
return Arrays.equals(cols, columns.cols);
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return Arrays.hashCode(cols);
}
/** {@inheritDoc} */
@Override
public String toString() {
return S.arrayToString(cols);
}
}