/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.rowdata; import java.util.Collection; import java.util.List; import java.util.Map; import com.foundationdb.ais.model.*; import com.foundationdb.server.AkServerUtil; /** * Contain the relevant schema information for one version of a table * definition. Instances of this class acquire table definition data from the * AIS, interpret it, and provide information on how to encode and decode fields * from a RowData structure. * * @author peter */ public class RowDef { private final Table table; /** * Array of FieldDef, one per column */ private final FieldDef[] fieldDefs; /** * Field(s) that constitute the foreign key by which this row is joined to * its parent table. */ private int[] parentJoinFields; /** * For a user table, the number of Persistit Key segments uses to encode the * hkey for rows of this table. */ private int hkeyDepth; /** * Array of index definitions for this row */ private Collection<TableIndex> indexes; /** * Array of group index definitions for this row. * Contains all GroupIndexes for which this table is the leaf most. */ private GroupIndex[] groupIndexes; /** * Array computed by the {@link #preComputeFieldCoordinates(FieldDef[])} * method to assist in looking up a field's offset and length. */ private final int[][] fieldCoordinates; /** * Array computed by the {@link #preComputeFieldCoordinates(FieldDef[])} * method to assist in looking up a field's offset and length. */ private final byte[][] varLenFieldMap; public RowDef(Table table) { this.table = table; table.rowDef(this); List<Column> columns = table.getColumnsIncludingInternal(); this.fieldDefs = new FieldDef[columns.size()]; for (Column column : columns) { this.fieldDefs[column.getPosition()] = new FieldDef(this, column); } fieldCoordinates = new int[(fieldDefs.length + 7) / 8][]; varLenFieldMap = new byte[(fieldDefs.length + 7) / 8][]; preComputeFieldCoordinates(fieldDefs); } public Table table() { return table; } /** * An implementation useful while debugging. */ @Override public String toString() { StringBuilder sb = new StringBuilder(String.format("RowDef #%d %s", table.getTableId(), table.getName())); sb.append(" fieldCount ").append(getFieldCount()).append(' '); for (int i = 0; i < fieldDefs.length; i++) { sb.append(i == 0 ? "[" : ","); sb.append(fieldDefs[i].column().getTypeName()); if (parentJoinFields != null) { for (int j = 0; j < parentJoinFields.length; j++) { if (parentJoinFields[j] == i) { sb.append("^"); sb.append(j); break; } } } } sb.append("]"); return sb.toString(); } /** * Returns the offset relative to the start of the byte array represented by * the supplied {@link RowData} of the field specified by the supplied * fieldIndex, or zero if the field is null. The location is a long which * encodes an offset to the data and its length in bytes. If * <code>offset</code> and <code>length</code> are the offset and length of * the data, respectively, then the location is returned as * * <code>(long)offset+((long)length << 32)</code> * * For fixed-length fields like numbers, the length is the fixed length of * the field, e.g., 8 for BIGINT values. For variable- length fields the * length is the number of bytes used in representing the value. */ public long fieldLocation(final RowData rowData, final int fieldIndex) { final int fieldCount = fieldDefs.length; if (fieldIndex < 0 || fieldIndex >= fieldCount) { throw new IllegalArgumentException("Field index out of bounds: " + fieldIndex); } int dataStart = rowData.getRowStartData(); // // If NullMap bit is set, return zero immediately // if (((rowData.getColumnMapByte(fieldIndex / 8) >>> (fieldIndex % 8)) & 1) == 1) { return 0; } if (fieldDefs[fieldIndex].isFixedSize()) { // // Look up the offset and width of a fixed-width field // int offset = dataStart; int width = 0; // // Loop over NullMap bytes until reaching fieldIndex // for (int k = 0; k <= fieldIndex; k += 8) { int byteIndex = k >>> 3; int mapByte = (~rowData.getColumnMapByte(byteIndex)) & 0xFF; // // Look up offset and width in the statically-generated // fieldCoordinates array. // int bitCount = fieldIndex - k; if (bitCount < 8) { mapByte &= (0xFF >>> (7 - bitCount)); } int fc = fieldCoordinates[byteIndex][mapByte]; // // Decode the offset and width fields // width = fc >>> 24; offset += (fc & 0xFFFFFF) + width; } // // Encode the width and offset fields // return ((offset & 0xFFFFFF) - width) | (((long) width) << 32); } else { // // Look up the offset and width of a variable-width field. // Previous and current refer to the coordinates of the // fixed-length fields delimiting the variable-length // data. // int offset = dataStart; int width = 0; int previous = 0; int current = 0; for (int k = 0; k < fieldCount; k += 8) { if (k <= fieldIndex) { current = current + width; } int byteIndex = k >>> 3; int mapByte = (~rowData.getColumnMapByte(byteIndex)) & 0xFF; int bitCount1 = fieldIndex - k; // // Look up offset and width in the statically-generated // fieldCoordinates array. // if (k <= fieldIndex) { int mbb1 = mapByte; int mask = 0xFF; if (bitCount1 < 7) { mask >>>= (7 - bitCount1); mbb1 &= mask; } int fc1 = fieldCoordinates[byteIndex][mbb1]; current = fc1 + (current & 0xFFFFFF); int mbb2 = varLenFieldMap[byteIndex][bitCount1 < 8 ? mbb1 : mbb1 + 256] & 0xFF; if (mbb2 != 0) { int fc2 = fieldCoordinates[byteIndex][mbb2]; previous = current + fc2 - fc1; } } // // In addition, because the overall size of the fixed-length // field array is not encoded, we need to compute the offset // of the byte after the last field. That's where the variable- // length bytes begin. // int bitCount2 = fieldCount - k; int mbb2 = mapByte; if (bitCount2 > 0 && bitCount2 < 8) { mbb2 &= (0xFF >>> (8 - bitCount2)); } int fc = fieldCoordinates[byteIndex][mbb2]; // // Decode the offset and width of the last field // width = (fc) >>> 24; offset += (fc & 0xFFFFFF) + width; } // // Compute the starting and ending offsets (from the beginning of // the rowData byte array) of the variable-length segment. // int start = (int) rowData.getUnsignedIntegerValue((previous & 0xFFFFFF) + dataStart, previous >>> 24); int end = (int) rowData.getUnsignedIntegerValue((current & 0xFFFFFF) + dataStart, current >>> 24); // // Encode and return the offset and length // return (start + offset) | ((long) (end - start) << 32); } } public int getFieldCount() { return fieldDefs.length; } public FieldDef getFieldDef(final int index) { return fieldDefs[index]; } public FieldDef[] getFieldDefs() { return fieldDefs; } public Collection<TableIndex> getIndexes() { return indexes; } public int[] getParentJoinFields() { return parentJoinFields; } public int getRowDefId() { return table.getTableId(); } public void setIndexes(Collection<TableIndex> indexes) { this.indexes = indexes; } public void setGroupIndexes(GroupIndex[] groupIndexes) { this.groupIndexes = groupIndexes; } public void setParentJoinFields(int[] parentJoinFields) { this.parentJoinFields = parentJoinFields; } public int getHKeyDepth() { return hkeyDepth; } public TableIndex getPKIndex() { if (indexes != null && indexes.size() > 0) { return indexes.iterator().next(); } else { return null; } } /** * Compute lookup tables used to in the {@link #fieldLocation(RowData, int)} * method. This method is invoked once when a RowDef is first constructed. * * @param fieldDefs */ void preComputeFieldCoordinates(final FieldDef[] fieldDefs) { final int fieldCount = fieldDefs.length; int voffset = 0; for (int field = 0; field < fieldCount; field++) { final FieldDef fieldDef = fieldDefs[field]; final int byteIndex = field / 8; final int bitIndex = field % 8; final int bit = 1 << bitIndex; if (bitIndex == 0) { fieldCoordinates[byteIndex] = new int[256]; varLenFieldMap[byteIndex] = new byte[512]; } final int width; if (fieldDef.isFixedSize()) { width = fieldDef.getMaxStorageSize(); } else { voffset += fieldDef.getMaxStorageSize(); width = AkServerUtil.varWidth(voffset); } for (int i = 0; i < bit; i++) { int from = fieldCoordinates[byteIndex][i]; int to = ((from & 0xFFFFFF) + (from >>> 24)) | (width << 24); int k = i + bit; fieldCoordinates[byteIndex][k] = to; for (int j = bitIndex; --j >= 0;) { if ((k & (1 << j)) != 0 && !fieldDefs[byteIndex * 8 + j].isFixedSize()) { varLenFieldMap[byteIndex][k] = (byte) (k & ((0xFF >>> (7 - j)))); break; } } for (int j = bitIndex + 1; --j >= 0;) { if ((k & (1 << j)) != 0 && !fieldDefs[byteIndex * 8 + j].isFixedSize()) { varLenFieldMap[byteIndex][k + 256] = (byte) (k & ((0xFF >>> (7 - j)))); break; } } } } } void computeFieldAssociations(Map<Table,Integer> ordinalMap) { // hkeyDepth is hkey position of the last column in the last segment. // (Or the position // of the last segment if that segment has no columns.) List<HKeySegment> segments = table.hKey().segments(); HKeySegment lastSegment = segments.get(segments.size() - 1); List<HKeyColumn> lastColumns = lastSegment.columns(); hkeyDepth = 1 + (lastColumns.isEmpty() ? lastSegment .positionInHKey() : lastColumns.get(lastColumns.size() - 1) .positionInHKey()); for (Index index : indexes) { index.computeFieldAssociations(ordinalMap); } if (groupIndexes != null) { for (GroupIndex index : groupIndexes) { index.computeFieldAssociations(ordinalMap); } } } public Group getGroup() { return table.getGroup(); } }