* A single database table
* <p>
* Is not thread-safe.
- *
+ *
* @author Tim McCune
* @usage _intermediate_class_
*/
public class TableImpl implements Table
-{
+{
private static final Log LOG = LogFactory.getLog(TableImpl.class);
private static final short OFFSET_MASK = (short)0x1FFF;
private static final short DELETED_ROW_MASK = (short)0x8000;
-
+
private static final short OVERFLOW_ROW_MASK = (short)0x4000;
static final int MAGIC_TABLE_NUMBER = 1625;
/** default cursor for iterating through the table, kept here for basic
table traversal */
private CursorImpl _defaultCursor;
-
+
/**
* Only used by unit tests
* @usage _advanced_method_
*/
- protected TableImpl(boolean testing, List<ColumnImpl> columns)
- throws IOException
+ protected TableImpl(boolean testing, List<ColumnImpl> columns)
+ throws IOException
{
if(!testing) {
throw new IllegalArgumentException();
_ownedPages = null;
_freeSpacePages = null;
}
-
+
/**
* @param database database which owns this table
* @param tableBuffer Buffer to read the table with
_ownedPages = UsageMap.read(getDatabase(), tableBuffer);
tableBuffer.position(getFormat().OFFSET_FREE_SPACE_PAGES);
_freeSpacePages = UsageMap.read(getDatabase(), tableBuffer);
-
+
for (int i = 0; i < _indexCount; i++) {
_indexDatas.add(IndexData.create(this, tableBuffer, i, getFormat()));
}
-
+
readColumnDefinitions(tableBuffer, columnCount);
readIndexDefinitions(tableBuffer);
// read column usage map info
- while((tableBuffer.remaining() >= 2) &&
+ while((tableBuffer.remaining() >= 2) &&
readColumnUsageMaps(tableBuffer)) {
// keep reading ...
}
public int getMaxColumnCount() {
return _maxColumnCount;
}
-
+
public int getColumnCount() {
return _columns.size();
}
-
+
public DatabaseImpl getDatabase() {
return _database;
}
-
+
/**
* @usage _advanced_method_
*/
public void setErrorHandler(ErrorHandler newErrorHandler) {
_tableErrorHandler = newErrorHandler;
- }
+ }
public int getTableDefPageNumber() {
return _tableDefPageNumber;
return count;
}
-
+
protected TempPageHolder getLongValueBuffer() {
return _longValueBufferH;
}
throw new IllegalArgumentException(withErrorContext(
"Column with name " + name + " does not exist in this table"));
}
-
+
public boolean hasColumn(String name) {
for(ColumnImpl column : _columns) {
if(column.getName().equalsIgnoreCase(name)) {
}
return _propertyMaps;
}
-
+
public List<IndexImpl> getIndexes() {
return Collections.unmodifiableList(_indexes);
}
throw new IllegalArgumentException(withErrorContext(
"No primary key index found"));
}
-
+
public IndexImpl getForeignKeyIndex(Table otherTable) {
for(IndexImpl index : _indexes) {
if(index.isForeignKey() && (index.getReference() != null) &&
"No foreign key reference to " +
otherTable.getName() + " found"));
}
-
+
/**
* @return All of the IndexData on this table (unmodifiable List)
* @usage _advanced_method_
return _indexCount;
}
- public IndexImpl findIndexForColumns(Collection<String> searchColumns,
+ public IndexImpl findIndexForColumns(Collection<String> searchColumns,
IndexFeature feature) {
IndexImpl partialIndex = null;
for(IndexImpl index : _indexes) {
-
+
Collection<? extends Index.Column> indexColumns = index.getColumns();
if(indexColumns.size() < searchColumns.size()) {
continue;
}
if(searchMatches) {
-
- if(exactMatch && ((feature != IndexFeature.EXACT_UNIQUE_ONLY) ||
+
+ if(exactMatch && ((feature != IndexFeature.EXACT_UNIQUE_ONLY) ||
index.isUnique())) {
return index;
}
- if(!exactMatch && (feature == IndexFeature.ANY_MATCH) &&
- ((partialIndex == null) ||
+ if(!exactMatch && (feature == IndexFeature.ANY_MATCH) &&
+ ((partialIndex == null) ||
(indexColumns.size() < partialIndex.getColumnCount()))) {
// this is a better partial index match
partialIndex = index;
return partialIndex;
}
-
+
List<ColumnImpl> getAutoNumberColumns() {
return _autoNumColumns;
}
public CursorBuilder newCursor() {
return new CursorBuilder(this);
}
-
+
public void reset() {
getDefaultCursor().reset();
}
* Delete the row for the given rowId.
* @usage _advanced_method_
*/
- public void deleteRow(RowState rowState, RowIdImpl rowId)
- throws IOException
+ public void deleteRow(RowState rowState, RowIdImpl rowId)
+ throws IOException
{
requireValidRowId(rowId);
-
+
getPageChannel().startWrite();
try {
-
+
// ensure that the relevant row state is up-to-date
ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
return;
}
requireNonDeletedRow(rowState, rowId);
-
+
// delete flag always gets set in the "header" row (even if data is on
// overflow row)
int pageNumber = rowState.getHeaderRowId().getPageNumber();
rowValues = rowState.getRowCacheValues();
// check foreign keys before proceeding w/ deletion
- _fkEnforcer.deleteRow(rowValues);
+ _fkEnforcer.deleteRow(rowValues);
// move back to the header
rowBuffer = positionAtRowHeader(rowState, rowId);
for(IndexData indexData : _indexDatas) {
indexData.deleteRow(rowValues, rowId);
}
-
+
// make sure table def gets updated
updateTableDefinition(-1);
getPageChannel().finishWrite();
}
}
-
+
public Row getNextRow() throws IOException {
return getDefaultCursor().getNextRow();
}
-
+
/**
* Reads a single column from the given row.
* @usage _advanced_method_
"Given column " + column + " is not from this table"));
}
requireValidRowId(rowId);
-
+
// position at correct row
ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
requireNonDeletedRow(rowState, rowId);
-
+
return getRowColumn(getFormat(), rowBuffer, column, rowState, null);
}
}
return rtn;
}
-
+
/**
* Reads the column data from the given row buffer. Leaves limit unchanged.
* Caches the returned value in the rowState.
// we already have it, use it
return cachedValue;
}
-
+
// reset position to row start
rowBuffer.reset();
-
+
// locate the column data bytes
int rowStart = rowBuffer.position();
int colDataPos = 0;
int dataStart = rowStart + format.OFFSET_COLUMN_FIXED_DATA_ROW_OFFSET;
colDataPos = dataStart + column.getFixedDataOffset();
colDataLen = column.getType().getFixedSize(column.getLength());
-
+
} else {
- int varDataStart;
+ int varDataStart;
int varDataEnd;
if(format.SIZE_ROW_VAR_COL_OFFSET == 2) {
// to update the index on row deletion. note, most of the returned
// values are immutable, except for binary data (returned as byte[]),
// but binary data shouldn't be indexed anyway.
- return rowState.setRowCacheValue(column.getColumnIndex(),
+ return rowState.setRowCacheValue(column.getColumnIndex(),
column.read(columnData));
} catch(Exception e) {
// cache "raw" row value. see note about caching above
- rowState.setRowCacheValue(column.getColumnIndex(),
+ rowState.setRowCacheValue(column.getColumnIndex(),
ColumnImpl.rawDataWrapper(columnData));
return rowState.handleRowError(column, columnData, e);
private static short[] readJumpTableVarColOffsets(
RowState rowState, ByteBuffer rowBuffer, int rowStart,
- NullMask nullMask)
+ NullMask nullMask)
{
short[] varColOffsets = rowState.getVarColOffsets();
if(varColOffsets != null) {
// calculate offsets using jump-table info
int nullMaskSize = nullMask.byteSize();
int rowEnd = rowStart + rowBuffer.remaining() - 1;
- int numVarCols = ByteUtil.getUnsignedByte(rowBuffer,
+ int numVarCols = ByteUtil.getUnsignedByte(rowBuffer,
rowEnd - nullMaskSize);
varColOffsets = new short[numVarCols + 1];
-
+
int rowLen = rowEnd - rowStart + 1;
int numJumps = (rowLen - 1) / MAX_BYTE;
int colOffset = rowEnd - nullMaskSize - numJumps - 1;
-
+
// If last jump is a dummy value, ignore it
if(((colOffset - rowStart - numVarCols) / MAX_BYTE) < numJumps) {
numJumps--;
int jumpsUsed = 0;
for(int i = 0; i < numVarCols + 1; i++) {
- while((jumpsUsed < numJumps) &&
+ while((jumpsUsed < numJumps) &&
(i == ByteUtil.getUnsignedByte(
rowBuffer, rowEnd - nullMaskSize-jumpsUsed - 1))) {
jumpsUsed++;
}
-
+
varColOffsets[i] = (short)
(ByteUtil.getUnsignedByte(rowBuffer, colOffset - i)
+ (jumpsUsed * MAX_BYTE));
}
-
+
rowState.setVarColOffsets(varColOffsets);
return varColOffsets;
}
// Number of columns in this row
int columnCount = ByteUtil.getUnsignedVarInt(
rowBuffer, getFormat().SIZE_ROW_COLUMN_COUNT);
-
+
// read null mask
NullMask nullMask = new NullMask(columnCount);
rowBuffer.position(rowBuffer.limit() - nullMask.byteSize()); //Null mask at end
* Sets a new buffer to the correct row header page using the given rowState
* according to the given rowId. Deleted state is
* determined, but overflow row pointers are not followed.
- *
+ *
* @return a ByteBuffer of the relevant page, or null if row was invalid
* @usage _advanced_method_
*/
- public static ByteBuffer positionAtRowHeader(RowState rowState,
+ public static ByteBuffer positionAtRowHeader(RowState rowState,
RowIdImpl rowId)
throws IOException
{
// this task has already been accomplished
return rowBuffer;
}
-
+
if(!rowState.isValid()) {
// this was an invalid page/row
rowState.setStatus(RowStateStatus.AT_HEADER);
rowState.setStatus(RowStateStatus.AT_HEADER);
return rowBuffer;
}
-
+
/**
* Sets the position and limit in a new buffer using the given rowState
* according to the given row number and row end, following overflow row
* pointers as necessary.
- *
+ *
* @return a ByteBuffer narrowed to the actual row data, or null if row was
* invalid or deleted
* @usage _advanced_method_
*/
- public static ByteBuffer positionAtRowData(RowState rowState,
+ public static ByteBuffer positionAtRowData(RowState rowState,
RowIdImpl rowId)
throws IOException
{
ByteBuffer rowBuffer = rowState.getFinalPage();
int rowNum = rowState.getFinalRowId().getRowNumber();
JetFormat format = rowState.getTable().getFormat();
-
+
if(rowState.isAtFinalRow()) {
// we've already found the final row data
return PageChannel.narrowBuffer(
findRowStart(rowBuffer, rowNum, format),
findRowEnd(rowBuffer, rowNum, format));
}
-
+
while(true) {
-
+
// note, we don't use findRowStart here cause we need the unmasked value
short rowStart = rowBuffer.getShort(getRowStartOffset(rowNum, format));
short rowEnd = findRowEnd(rowBuffer, rowNum, format);
throw new IOException(rowState.getTable().withErrorContext(
"invalid overflow row info"));
}
-
+
// Overflow page. the "row" data in the current page points to
// another page/row
int overflowRowNum = ByteUtil.getUnsignedByte(rowBuffer, rowStart);
rowBuffer = rowState.setOverflowRow(
new RowIdImpl(overflowPageNum, overflowRowNum));
rowNum = overflowRowNum;
-
+
} else {
rowState.setStatus(RowStateStatus.AT_FINAL);
// next, determine how big the table def will be (in case it will be more
// than one page)
JetFormat format = creator.getFormat();
- int idxDataLen = (creator.getIndexCount() *
- (format.SIZE_INDEX_DEFINITION +
- format.SIZE_INDEX_COLUMN_BLOCK)) +
+ int idxDataLen = (creator.getIndexCount() *
+ (format.SIZE_INDEX_DEFINITION +
+ format.SIZE_INDEX_COLUMN_BLOCK)) +
(creator.getLogicalIndexCount() * format.SIZE_INDEX_INFO_BLOCK);
int colUmapLen = creator.getLongValueColumns().size() * 10;
int totalTableDefSize = format.SIZE_TDEF_HEADER +
- (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) +
+ (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) +
idxDataLen + colUmapLen + format.SIZE_TDEF_TRAILER;
// total up the amount of space used by the column and index names (2
for(ColumnBuilder col : creator.getColumns()) {
totalTableDefSize += DBMutator.calculateNameLength(col.getName());
}
-
+
for(IndexBuilder idx : creator.getIndexes()) {
totalTableDefSize += DBMutator.calculateNameLength(idx.getName());
}
-
+
// now, create the table definition
ByteBuffer buffer = PageChannel.createBuffer(Math.max(totalTableDefSize,
}
// column definitions
- ColumnImpl.writeDefinitions(creator, buffer);
-
+ ColumnImpl.writeDefinitions(creator, buffer);
+
if(creator.hasIndexes()) {
// index and index data definitions
IndexData.writeDefinitions(creator, buffer);
}
private static void writeTableDefinitionBuffer(
- ByteBuffer buffer, int tdefPageNumber,
+ ByteBuffer buffer, int tdefPageNumber,
TableMutator mutator, List<Integer> reservedPages)
throws IOException
{
// write table buffer to database
if(totalTableDefSize <= format.PAGE_SIZE) {
-
+
// easy case, fits on one page
// overwrite page free space
// Write the tdef page to disk.
buffer.clear();
pageChannel.writePage(buffer, tdefPageNumber);
-
+
} else {
// need to split across multiple pages
// reset for next write
partialTdef.clear();
-
+
if(nextTdefPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
-
+
// this is the first page. note, the first page already has the
// page header, so no need to write it here
nextTdefPageNumber = tdefPageNumber;
-
+
} else {
// write page header
// write partial page to disk
pageChannel.writePage(partialTdef, curTdefPageNumber);
}
-
+
}
-
+
}
/**
int umapPos = -1;
boolean success = false;
try {
-
+
////
// update various bits of the table def
ByteUtil.forward(tableBuffer, 29);
tableBuffer.putShort((short)(_columns.size() + 1));
// move to end of column def blocks
- tableBuffer.position(format.SIZE_TDEF_HEADER +
+ tableBuffer.position(format.SIZE_TDEF_HEADER +
(_indexCount * format.SIZE_INDEX_DEFINITION) +
(_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
} else {
// find the fixed offset
for(ColumnImpl col : _columns) {
- if(!col.isVariableLength() &&
+ if(!col.isVariableLength() &&
(col.getFixedDataOffset() >= fixedOffset)) {
- fixedOffset = col.getFixedDataOffset() +
+ fixedOffset = col.getFixedDataOffset() +
col.getType().getFixedSize(col.getLength());
}
}
colState.setUmapFreeRowNumber((byte)(rowNum + 1));
// skip past index defs
- ByteUtil.forward(tableBuffer, (_indexCount *
+ ByteUtil.forward(tableBuffer, (_indexCount *
format.SIZE_INDEX_COLUMN_BLOCK));
ByteUtil.forward(tableBuffer,
(_logicalIndexCount * format.SIZE_INDEX_INFO_BLOCK));
ByteUtil.forward(tableBuffer, -2);
break;
}
-
+
ByteUtil.forward(tableBuffer, 8);
// keep reading ...
////
// write updated table def back to the database
- writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
+ writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
mutator.getNextPages());
success = true;
////
// calculate how much more space we need in the table def
- mutator.addTdefLen(format.SIZE_INDEX_DEFINITION +
+ mutator.addTdefLen(format.SIZE_INDEX_DEFINITION +
format.SIZE_INDEX_COLUMN_BLOCK);
////
IndexData newIdxData = null;
boolean success = false;
try {
-
+
////
// update various bits of the table def
ByteUtil.forward(tableBuffer, 39);
tableBuffer.putInt(_indexCount + 1);
// move to end of index data def blocks
- tableBuffer.position(format.SIZE_TDEF_HEADER +
+ tableBuffer.position(format.SIZE_TDEF_HEADER +
(_indexCount * format.SIZE_INDEX_DEFINITION));
// write index row count definition (empty initially)
IndexData.writeRowCountDefinitions(mutator, tableBuffer, 1);
// skip columns and column names
- ByteUtil.forward(tableBuffer,
+ ByteUtil.forward(tableBuffer,
(_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
skipNames(tableBuffer, _columns.size());
// move to end of current index datas
- ByteUtil.forward(tableBuffer, (_indexCount *
+ ByteUtil.forward(tableBuffer, (_indexCount *
format.SIZE_INDEX_COLUMN_BLOCK));
// allocate usage maps and root page
////
// write updated table def back to the database
- writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
+ writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
mutator.getNextPages());
success = true;
for(IndexData.ColumnDescriptor iCol : newIdxData.getColumns()) {
_indexColumns.add(iCol.getColumn());
}
-
+
++_indexCount;
_indexDatas.add(newIdxData);
col.setRowValue(rowVals, col.getRowValue(row));
}
- IndexData.commitAll(
+ IndexData.commitAll(
idxData.prepareAddRow(rowVals, (RowIdImpl)row.getId(), null));
}
IndexImpl newIdx = null;
boolean success = false;
try {
-
+
////
// update various bits of the table def
ByteUtil.forward(tableBuffer, 35);
tableBuffer.putInt(_logicalIndexCount + 1);
// move to end of index data def blocks
- tableBuffer.position(format.SIZE_TDEF_HEADER +
+ tableBuffer.position(format.SIZE_TDEF_HEADER +
(_indexCount * format.SIZE_INDEX_DEFINITION));
// skip columns and column names
- ByteUtil.forward(tableBuffer,
+ ByteUtil.forward(tableBuffer,
(_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
skipNames(tableBuffer, _columns.size());
// move to end of current index datas
- ByteUtil.forward(tableBuffer, (_indexCount *
+ ByteUtil.forward(tableBuffer, (_indexCount *
format.SIZE_INDEX_COLUMN_BLOCK));
// move to end of current indexes
- ByteUtil.forward(tableBuffer, (_logicalIndexCount *
+ ByteUtil.forward(tableBuffer, (_logicalIndexCount *
format.SIZE_INDEX_INFO_BLOCK));
int idxDefPos = tableBuffer.position();
tableBuffer.position(idxDefPos);
newIdx = new IndexImpl(tableBuffer, _indexDatas, format);
newIdx.setName(index.getName());
-
+
////
// write updated table def back to the database
- writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
+ writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
mutator.getNextPages());
success = true;
private static void skipNames(ByteBuffer tableBuffer, int count) {
for(int i = 0; i < count; ++i) {
ByteUtil.forward(tableBuffer, tableBuffer.getShort());
- }
+ }
}
private ByteBuffer loadCompleteTableDefinitionBufferForUpdate(
}
if(umapPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
-
+
// didn't find any existing pages, need to create a new one
umapPageNumber = pageChannel.allocateNewPage();
freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE;
umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
+ int dataOffset = rowStart + 1;
if(firstUsedPage != null) {
// fill in the first used page of the usage map
- umapBuf.putInt(rowStart + 1, firstUsedPage);
- umapBuf.put(rowStart + 5, (byte)1);
+ umapBuf.putInt(dataOffset, firstUsedPage);
+ dataOffset += 4;
+ umapBuf.put(dataOffset, (byte)1);
+ dataOffset++;
}
- rowStart -= umapRowLength;
+ // zero remaining row data
+ ByteUtil.clearRange(umapBuf, dataOffset, (rowStart + umapRowLength));
+
+ rowStart -= umapRowLength;
++umapRowNum;
}
// finish the page
freeSpace -= totalUmapSpaceUsage;
umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
- umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
+ umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
(short)umapRowNum);
pageChannel.writePage(umapBuf, umapPageNumber);
for(ColumnImpl col : _columns) {
col.collectUsageMapPages(pages);
}
- }
-
+ }
+
/**
* @param buffer Buffer to write to
*/
buffer.put((byte) 0); //Unknown
buffer.putInt(0); //Next TDEF page pointer
}
-
+
/**
* Writes the given name into the given buffer in the format as expected by
* {@link #readName}.
buffer.putShort((short) encName.remaining());
buffer.put(encName);
}
-
+
/**
* Create the usage map definition page buffer. The "used pages" map is in
* row 0, the "pages with free space" map is in row 1. Index usage maps are
int freeSpace = 0;
int rowStart = 0;
int umapRowNum = 0;
-
+
for(int i = 0; i < umapNum; ++i) {
if(umapBuf == null) {
}
umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
-
+
if(i == 0) {
// table "owned pages" map definition
// index umap
int indexIdx = i - 2;
- TableMutator.IndexDataState idxDataState =
+ TableMutator.IndexDataState idxDataState =
creator.getIndexDataStates().get(indexIdx);
-
+
// allocate root page for the index
int rootPageNumber = pageChannel.allocateNewPage();
lvalColIdx /= 2;
ColumnBuilder lvalCol = lvalCols.get(lvalColIdx);
- TableMutator.ColumnState colState =
+ TableMutator.ColumnState colState =
creator.getColumnState(lvalCol);
umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
- if((umapType == 1) &&
+ if((umapType == 1) &&
(umapPageNumber != colState.getUmapPageNumber())) {
// we want to force both usage maps for a column to be on the same
// data page, so just discard the previous one we wrote
--i;
umapType = 0;
}
-
+
if(umapType == 0) {
// lval column "owned pages" usage map
colState.setUmapOwnedRowNumber((byte)umapRowNum);
if((freeSpace <= umapSpaceUsage) || (i == (umapNum - 1))) {
// finish current page
umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
- umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
+ umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
(short)umapRowNum);
pageChannel.writePage(umapBuf, umapPageNumber);
umapBuf = null;
umapBuf.putShort((short)freeSpace); //Free space in page
umapBuf.putInt(0); //Table definition
umapBuf.putInt(0); //Unknown
- umapBuf.putShort((short)0); //Number of records on this page
+ umapBuf.putShort((short)0); //Number of records on this page
return umapBuf;
}
}
return tableBuffer;
}
-
+
private ByteBuffer expandTableBuffer(ByteBuffer tableBuffer) {
ByteBuffer newBuffer = PageChannel.createBuffer(
tableBuffer.capacity() + getFormat().PAGE_SIZE - 8);
newBuffer.put(tableBuffer);
return newBuffer;
}
-
+
private void readColumnDefinitions(ByteBuffer tableBuffer, short columnCount)
throws IOException
{
List<String> colNames = new ArrayList<String>(columnCount);
for (int i = 0; i < columnCount; i++) {
colNames.add(readName(tableBuffer));
- }
-
+ }
+
int dispIndex = 0;
for (int i = 0; i < columnCount; i++) {
ColumnImpl column = ColumnImpl.create(this, tableBuffer,
for (int i = 0; i < _logicalIndexCount; i++) {
_indexes.get(i).setName(readName(tableBuffer));
}
-
+
Collections.sort(_indexes);
}
-
- private boolean readColumnUsageMaps(ByteBuffer tableBuffer)
+
+ private boolean readColumnUsageMaps(ByteBuffer tableBuffer)
throws IOException
{
short umapColNum = tableBuffer.getShort();
if(umapColNum == IndexData.COLUMN_UNUSED) {
return false;
}
-
+
int pos = tableBuffer.position();
UsageMap colOwnedPages = null;
UsageMap colFreeSpacePages = null;
colOwnedPages = null;
colFreeSpacePages = null;
tableBuffer.position(pos + 8);
- LOG.warn(withErrorContext("Invalid column " + umapColNum +
+ LOG.warn(withErrorContext("Invalid column " + umapColNum +
" usage map definition: " + e));
}
-
+
for(ColumnImpl col : _columns) {
if(col.getColumnNumber() == umapColNum) {
col.setUsageMaps(colOwnedPages, colFreeSpacePages);
// possibly invalidate the add row buffer if a different data buffer is
// being written (e.g. this happens during deleteRow)
_addRowBufferH.possiblyInvalidate(pageNumber, pageBuffer);
-
+
// update modification count so any active RowStates can keep themselves
// up-to-date
++_modCount;
/**
* Returns a name read from the buffer at the current position. The
- * expected name format is the name length followed by the name
+ * expected name format is the name length followed by the name
* encoded using the {@link JetFormat#CHARSET}
*/
- private String readName(ByteBuffer buffer) {
+ private String readName(ByteBuffer buffer) {
int nameLength = readNameLength(buffer);
byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
- return ColumnImpl.decodeUncompressedText(nameBytes,
+ return ColumnImpl.decodeUncompressedText(nameBytes,
getDatabase().getCharset());
}
-
+
/**
* Returns a name length read from the buffer at the current position.
*/
- private int readNameLength(ByteBuffer buffer) {
+ private int readNameLength(ByteBuffer buffer) {
return ByteUtil.getUnsignedVarInt(buffer, getFormat().SIZE_NAME_LENGTH);
}
-
+
public Object[] asRow(Map<String,?> rowMap) {
return asRow(rowMap, null, false);
}
-
+
/**
* Converts a map of columnName -> columnValue to an array of row values
* appropriate for a call to {@link #addRow(Object...)}, where the generated
public Object[] asRowWithRowId(Map<String,?> rowMap) {
return asRow(rowMap, null, true);
}
-
+
public Object[] asUpdateRow(Map<String,?> rowMap) {
return asRow(rowMap, Column.KEEP_VALUE, false);
}
/**
* Converts a map of columnName -> columnValue to an array of row values.
*/
- private Object[] asRow(Map<String,?> rowMap, Object defaultValue,
+ private Object[] asRow(Map<String,?> rowMap, Object defaultValue,
boolean returnRowId)
{
int len = _columns.size();
}
return row;
}
-
+
public Object[] addRow(Object... row) throws IOException {
return addRows(Collections.singletonList(row), false).get(0);
}
- public <M extends Map<String,Object>> M addRowFromMap(M row)
- throws IOException
+ public <M extends Map<String,Object>> M addRowFromMap(M row)
+ throws IOException
{
Object[] rowValues = asRow(row);
returnRowValues(row, rowValues, _autoNumColumns);
return row;
}
-
- public List<? extends Object[]> addRows(List<? extends Object[]> rows)
- throws IOException
+
+ public List<? extends Object[]> addRows(List<? extends Object[]> rows)
+ throws IOException
{
return addRows(rows, true);
}
-
- public <M extends Map<String,Object>> List<M> addRowsFromMaps(List<M> rows)
- throws IOException
+
+ public <M extends Map<String,Object>> List<M> addRowsFromMaps(List<M> rows)
+ throws IOException
{
List<Object[]> rowValuesList = new ArrayList<Object[]>(rows.size());
for(Map<String,Object> row : rows) {
rowValuesList.add(asRow(row));
- }
+ }
addRows(rowValuesList);
getPageChannel().startWrite();
try {
-
+
ByteBuffer dataPage = null;
int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
int updateCount = 0;
int autoNumAssignCount = 0;
- WriteRowState writeRowState =
+ WriteRowState writeRowState =
(!_autoNumColumns.isEmpty() ? new WriteRowState() : null);
try {
// handle various value massaging activities
for(ColumnImpl column : _columns) {
- if(!column.isAutoNumber()) {
+ if(!column.isAutoNumber()) {
// pass input value through column validator
column.setRowValue(row, column.validate(column.getRowValue(row)));
}
// fill in autonumbers
handleAutoNumbersForAdd(row, writeRowState);
++autoNumAssignCount;
-
+
// write the row of data to a temporary buffer
ByteBuffer rowData = createRow(
row, _writeRowBufferH.getPageBuffer(getPageChannel()));
-
+
int rowSize = rowData.remaining();
if (rowSize > getFormat().MAX_ROW_SIZE) {
throw new IOException(withErrorContext(
dataPage.put(rowData);
// return rowTd if desired
- if((row.length > numCols) &&
+ if((row.length > numCols) &&
(row[numCols] == ColumnImpl.RETURN_ROW_ID)) {
row[numCols] = rowId;
}
}
writeDataPage(dataPage, pageNumber);
-
+
// Update tdef page
updateTableDefinition(rows.size());
// recover them so we don't get ugly "holes"
restoreAutoNumbersFromAdd(rows.get(autoNumAssignCount - 1));
}
-
+
if(!isBatchWrite) {
// just re-throw the original exception
if(rowWriteFailure instanceof IOException) {
throw (IOException)rowWriteFailure;
- }
+ }
throw (RuntimeException)rowWriteFailure;
}
updateCount = 0;
} else if(updateCount > 0) {
-
+
// attempt to flush the rows already written to disk
try {
writeDataPage(dataPage, pageNumber);
-
+
// Update tdef page
updateTableDefinition(updateCount);
// write failure). we don't know the status of any rows at this
// point (and the original failure is probably irrelevant)
LOG.warn(withErrorContext(
- "Secondary row failure which preceded the write failure"),
+ "Secondary row failure which preceded the write failure"),
rowWriteFailure);
updateCount = 0;
rowWriteFailure = flushFailure;
} finally {
getPageChannel().finishWrite();
}
-
+
return rows;
}
return true;
}
t = t.getCause();
- }
+ }
// some other sort of exception which is not a write failure
return false;
}
-
+
public Row updateRow(Row row) throws IOException {
return updateRowFromMap(
getDefaultCursor().getRowState(), (RowIdImpl)row.getId(), row);
* @throws IllegalStateException if the given row is not valid, or deleted.
* @usage _intermediate_method_
*/
- public void updateValue(Column column, RowId rowId, Object value)
- throws IOException
+ public void updateValue(Column column, RowId rowId, Object value)
+ throws IOException
{
Object[] row = new Object[_columns.size()];
Arrays.fill(row, Column.KEEP_VALUE);
}
public <M extends Map<String,Object>> M updateRowFromMap(
- RowState rowState, RowIdImpl rowId, M row)
- throws IOException
+ RowState rowState, RowIdImpl rowId, M row)
+ throws IOException
{
Object[] rowValues = updateRow(rowState, rowId, asUpdateRow(row));
returnRowValues(row, rowValues, _columns);
* Update the row for the given rowId.
* @usage _advanced_method_
*/
- public Object[] updateRow(RowState rowState, RowIdImpl rowId, Object... row)
- throws IOException
+ public Object[] updateRow(RowState rowState, RowIdImpl rowId, Object... row)
+ throws IOException
{
requireValidRowId(rowId);
-
+
getPageChannel().startWrite();
try {
-
+
// ensure that the relevant row state is up-to-date
ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
int oldRowSize = rowBuffer.remaining();
// hang on to the raw values of var length columns we are "keeping". this
// will allow us to re-use pre-written var length data, which can save
// space for things like long value columns.
- Map<ColumnImpl,byte[]> keepRawVarValues =
+ Map<ColumnImpl,byte[]> keepRawVarValues =
(!_varColumns.isEmpty() ? new HashMap<ColumnImpl,byte[]>() : null);
// handle various value massaging activities
Object rowValue = column.getRowValue(row);
if(rowValue == Column.KEEP_VALUE) {
-
+
// fill in any "keep value" fields (restore old value)
rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
keepRawVarValues);
-
+
} else {
// set oldValue to something that could not possibly be a real value
if(oldValue != rowValue) {
// pass input value through column validator
rowValue = column.validate(rowValue);
- }
+ }
}
column.setRowValue(row, rowValue);
// prepare index updates
for(IndexData indexData : _indexDatas) {
- idxChange = indexData.prepareUpdateRow(oldRowValues, rowId, row,
+ idxChange = indexData.prepareUpdateRow(oldRowValues, rowId, row,
idxChange);
}
throw ce;
}
}
-
+
// see if we can squeeze the new row data into the existing row
rowBuffer.reset();
int rowSize = newRowData.remaining();
} else {
// bummer, need to find a new page for the data
- dataPage = findFreeRowSpace(rowSize, null,
+ dataPage = findFreeRowSpace(rowSize, null,
PageChannel.INVALID_PAGE_NUMBER);
pageNumber = _addRowBufferH.getPageNumber();
- RowIdImpl headerRowId = rowState.getHeaderRowId();
+ RowIdImpl headerRowId = rowState.getHeaderRowId();
ByteBuffer headerPage = rowState.getHeaderPage();
if(pageNumber == headerRowId.getPageNumber()) {
// new row is on the same page as header row, share page
return row;
}
-
- private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage,
+
+ private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage,
int pageNumber)
throws IOException
{
if(dataPage == null) {
// find owned page w/ free space
- dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages,
+ dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages,
_addRowBufferH);
if(dataPage == null) {
return null;
}
-
+
/**
* Updates the table definition after rows are modified.
*/
// load table definition
ByteBuffer tdefPage = _tableDefBufferH.setPage(getPageChannel(),
_tableDefPageNumber);
-
+
// make sure rowcount and autonumber are up-to-date
_rowCount += rowCountInc;
tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount);
// write modified table definition
getPageChannel().writePage(tdefPage, _tableDefPageNumber);
}
-
+
/**
* Create a new data page
* @return Page number of the new page
_freeSpacePages.addPageNumber(pageNumber);
return dataPage;
}
-
+
protected ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer)
throws IOException
{
/**
* Serialize a row of Objects into a byte buffer.
- *
+ *
* @param rowArray row data, expected to be correct length for this table
* @param buffer buffer to which to write the row data
* @param minRowSize min size for result row
* @return the given buffer, filled with the row data
*/
private ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer,
- int minRowSize,
+ int minRowSize,
Map<ColumnImpl,byte[]> rawVarValues)
throws IOException
{
buffer.putShort(_maxColumnCount);
NullMask nullMask = new NullMask(_maxColumnCount);
-
+
//Fixed length column data comes first
int fixedDataStart = buffer.position();
int fixedDataEnd = fixedDataStart;
if(col.isVariableLength()) {
continue;
}
-
+
Object rowValue = col.getRowValue(rowArray);
if (col.storeInNullMask()) {
-
+
if(col.writeToNullMask(rowValue)) {
nullMask.markNotNull(col);
}
rowValue = null;
}
-
+
if(rowValue != null) {
-
+
// we have a value to write
nullMask.markNotNull(col);
// keep track of the end of fixed data
if(buffer.position() > fixedDataEnd) {
fixedDataEnd = buffer.position();
- }
-
+ }
+
}
// reposition at end of fixed data
buffer.position(fixedDataEnd);
-
+
// only need this info if this table contains any var length data
if(_maxVarColumnCount > 0) {
maxRowSize -= getFormat().SIZE_LONG_VALUE_DEF;
}
}
-
+
//Now write out variable length column data
short[] varColumnOffsets = new short[_maxVarColumnCount];
int varColumnOffsetsIndex = 0;
byte[] rawValue = null;
ByteBuffer varDataBuf = null;
- if(((rawValue = rawVarValues.get(varCol)) != null) &&
+ if(((rawValue = rawVarValues.get(varCol)) != null) &&
(rawValue.length <= maxRowSize)) {
// save time and potentially db space, re-use raw value
varDataBuf = ByteBuffer.wrap(rawValue);
// the max row size
throw new IOException(withErrorContext(
"Row size " + buffer.limit() + " is too large"));
- }
+ }
}
// we do a loop here so that we fill in offsets for deleted columns
Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
ColumnImpl.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
- Object rowValue = ((inRowValue == null) ?
+ Object rowValue = ((inRowValue == null) ?
autoNumGen.getNext(writeRowState) :
autoNumGen.handleInsert(writeRowState, inRowValue));
col.setRowValue(row, rowValue);
}
}
-
+
/**
* Fill in all autonumber column values for update.
*/
// enabled)
Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
- Object rowValue =
+ Object rowValue =
((inRowValue == null) ?
getRowColumn(getFormat(), rowBuffer, col, rowState, null) :
col.getAutoNumberGenerator().handleInsert(rowState, inRowValue));
}
return inRowValue;
}
-
+
/**
* Restores all autonumber column values from a failed add row.
*/
// restores the last used auto number
_lastLongAutoNumber = lastLongAutoNumber - 1;
}
-
+
int getNextComplexTypeAutoNumber() {
// note, the saved value is the last one handed out, so pre-increment
return ++_lastComplexTypeAutoNumber;
// restores the last used auto number
_lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
}
-
+
@Override
public String toString() {
return CustomToStringStyle.builder(this)
.append("ownedPages", _ownedPages)
.toString();
}
-
+
/**
* @return A simple String representation of the entire table in
* tab-delimited format
public String display() throws IOException {
return display(Long.MAX_VALUE);
}
-
+
/**
* @param limit Maximum number of rows to display
* @return A simple String representation of the entire table in
*/
public static int addDataPageRow(ByteBuffer dataPage,
int rowSize,
- JetFormat format,
+ JetFormat format,
int rowFlags)
{
int rowSpaceUsage = getRowSpaceUsage(rowSize, format);
-
+
// Decrease free space record.
short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
dataPage.putShort(format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage -
rowLocation -= rowSize;
// write row position
- dataPage.putShort(getRowStartOffset(rowCount, format),
+ dataPage.putShort(getRowStartOffset(rowCount, format),
(short)(rowLocation | rowFlags));
// set position for row data
"Given rowId is invalid: " + rowId));
}
}
-
+
/**
* @throws IllegalStateException if the given row is invalid or deleted
*/
"Row is deleted: " + rowId));
}
}
-
+
/**
* @usage _advanced_method_
*/
public static boolean isDeletedRow(short rowStart) {
return ((rowStart & DELETED_ROW_MASK) != 0);
}
-
+
/**
* @usage _advanced_method_
*/
public static short cleanRowStart(short rowStart) {
return (short)(rowStart & OFFSET_MASK);
}
-
+
/**
* @usage _advanced_method_
*/
{
return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * rowNum);
}
-
+
/**
* @usage _advanced_method_
*/
private ErrorHandler _errorHandler;
/** cached variable column offsets for jump-table based rows */
private short[] _varColOffsets;
-
+
private RowState(TempBufferHolder.Type headerType) {
_headerRowBufferH = TempPageHolder.newHolder(headerType);
_rowValues = new Object[TableImpl.this.getColumnCount()];
public void setErrorHandler(ErrorHandler newErrorHandler) {
_errorHandler = newErrorHandler;
}
-
+
public void reset() {
resetAutoNumber();
_finalRowId = null;
public boolean isUpToDate() {
return(TableImpl.this._modCount == _lastModCount);
}
-
+
private void checkForModification() {
if(!isUpToDate()) {
reset();
_lastModCount = TableImpl.this._modCount;
}
}
-
+
private ByteBuffer getFinalPage()
throws IOException
{
public boolean isValid() {
return(_rowStatus.ordinal() >= RowStatus.VALID.ordinal());
}
-
+
public boolean isDeleted() {
return(_rowStatus == RowStatus.DELETED);
}
-
+
public boolean isOverflow() {
return(_rowStatus == RowStatus.OVERFLOW);
}
public boolean isHeaderPageNumberValid() {
return(_rowStatus.ordinal() > RowStatus.INVALID_PAGE.ordinal());
}
-
+
public boolean isHeaderRowNumberValid() {
return(_rowStatus.ordinal() > RowStatus.INVALID_ROW.ordinal());
}
-
+
private void setStatus(RowStateStatus status) {
_status = status;
}
-
+
public boolean isAtHeaderRow() {
return(_status.ordinal() >= RowStateStatus.AT_HEADER.ordinal());
}
-
+
public boolean isAtFinalRow() {
return(_status.ordinal() >= RowStateStatus.AT_FINAL.ordinal());
}
// modified externally and therefore could return an incorrect value
return(ColumnImpl.isImmutableValue(value) ? value : null);
}
-
+
public Object[] getRowCacheValues() {
return dupeRow(_rowValues, _rowValues.length);
}
private void setVarColOffsets(short[] varColOffsets) {
_varColOffsets = varColOffsets;
}
-
+
public RowIdImpl getHeaderRowId() {
return _headerRowId;
}
public int getRowsOnHeaderPage() {
return _rowsOnHeaderPage;
}
-
+
private ByteBuffer getHeaderPage()
throws IOException
{
setRowStatus(RowStatus.INVALID_PAGE);
return null;
}
-
+
_finalRowBuffer = _headerRowBufferH.setPage(getPageChannel(),
pageNumber);
_rowsOnHeaderPage = getRowsOnDataPage(_finalRowBuffer, getFormat());
-
+
if((rowNumber < 0) || (rowNumber >= _rowsOnHeaderPage)) {
setRowStatus(RowStatus.INVALID_ROW);
return null;
{
return getErrorHandler().handleRowError(column, columnData,
this, error);
- }
+ }
@Override
public String toString() {
.toString();
}
}
-
+
}