private short _columnLength;
/** 0-based column number */
private short _columnNumber;
+ /** index of the data for this column within a list of row data */
+ private int _columnIndex;
/** Column name */
private String _name;
/** the offset of the fixed data in the row */
public boolean isVariableLength() {
return _variableLength;
}
+
public void setVariableLength(boolean variableLength) {
_variableLength = variableLength;
}
-
+
public short getColumnNumber() {
return _columnNumber;
}
+
+ public void setColumnNumber(short newColumnNumber) {
+ _columnNumber = newColumnNumber;
+ }
+
+ public int getColumnIndex() {
+ return _columnIndex;
+ }
+
+ public void setColumnIndex(int newColumnIndex) {
+ _columnIndex = newColumnIndex;
+ }
/**
* Also sets the length and the variable length flag, inferred from the
public short getLengthInUnits() {
return (short)(getLength() / getType().getUnitSize());
}
+
+ public void setVarLenTableIndex(int idx) {
+ _varLenTableIndex = idx;
+ }
public int getVarLenTableIndex() {
return _varLenTableIndex;
}
+ public void setFixedDataOffset(int newOffset) {
+ _fixedDataOffset = newOffset;
+ }
+
public int getFixedDataOffset() {
return _fixedDataOffset;
}
* @param columns A list of columns in a table definition
* @return The number of variable length columns found in the list
*/
- public static short countVariableLength(List columns) {
+ public static short countVariableLength(List<Column> columns) {
short rtn = 0;
- Iterator iter = columns.iterator();
- while (iter.hasNext()) {
- Column col = (Column) iter.next();
+ for (Column col : columns) {
if (col.isVariableLength()) {
rtn++;
}
return rtn;
}
+ /**
+ * @param columns A list of columns in a table definition
+ * @return The number of variable length columns which are not long values
+ * found in the list
+ */
+ public static short countNonLongVariableLength(List<Column> columns) {
+ short rtn = 0;
+ for (Column col : columns) {
+ if (col.isVariableLength() && !col.getType().isLongValue()) {
+ rtn++;
+ }
+ }
+ return rtn;
+ }
+
/**
* @return an appropriate BigDecimal representation of the given object.
* <code>null</code> is returned as 0 and Numbers are converted
* @param buffer Buffer to write to
* @param columns List of Columns to write definitions for
*/
- private void writeColumnDefinitions(ByteBuffer buffer, List columns)
+ private void writeColumnDefinitions(ByteBuffer buffer, List<Column> columns)
throws IOException {
- Iterator iter;
short columnNumber = (short) 0;
short fixedOffset = (short) 0;
short variableOffset = (short) 0;
- for (iter = columns.iterator(); iter.hasNext(); columnNumber++) {
- Column col = (Column) iter.next();
+ // we specifically put the "long variable" values after the normal
+ // variable length values so that we have a better chance of fitting it
+ // all (because "long variable" values can go in separate pages)
+ short longVariableOffset =
+ (short) Column.countNonLongVariableLength(columns);
+ for (Column col : columns) {
int position = buffer.position();
buffer.put(col.getType().getValue());
buffer.put((byte) 0x59); //Unknown
buffer.putShort((short) 0); //Unknown
buffer.putShort(columnNumber); //Column Number
if (col.isVariableLength()) {
- buffer.putShort(variableOffset++);
+ if(!col.getType().isLongValue()) {
+ buffer.putShort(variableOffset++);
+ } else {
+ buffer.putShort(longVariableOffset++);
+ }
} else {
buffer.putShort((short) 0);
}
} else {
buffer.putShort((short)0x0000); // unused
}
+ columnNumber++;
if (LOG.isDebugEnabled()) {
LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
buffer, position, _format.SIZE_COLUMN_DEF_BLOCK));
}
}
- iter = columns.iterator();
- while (iter.hasNext()) {
- Column col = (Column) iter.next();
+ for (Column col : columns) {
ByteBuffer colName = _format.CHARSET.encode(col.getName());
buffer.putShort((short) colName.remaining());
buffer.put(colName);
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
public static final byte TYPE_SYSTEM = 0x53;
/** Table type code for user tables */
public static final byte TYPE_USER = 0x4e;
+
+ /** comparator which sorts variable length columns vased on their index into
+ the variable length offset table */
+ private static final Comparator<Column> VAR_LEN_COLUMN_COMPARATOR =
+ new Comparator<Column>() {
+ public int compare(Column c1, Column c2) {
+ return ((c1.getVarLenTableIndex() < c2.getVarLenTableIndex()) ? -1 :
+ ((c1.getVarLenTableIndex() > c2.getVarLenTableIndex()) ? 1 :
+ 0));
+ }
+ };
/** State used for reading the table rows */
private RowState _rowState;
private short _maxColumnCount;
/** max Number of variable columns in the table */
private short _maxVarColumnCount;
- /** Number of variable columns in the table */
- private short _varColumnCount;
- /** Number of fixed columns in the table */
- private short _fixedColumnCount;
/** Number of columns in the table */
private short _columnCount;
/** Format of the database that contains this table */
private JetFormat _format;
- /** List of columns in this table */
+ /** List of columns in this table, ordered by column number */
private List<Column> _columns = new ArrayList<Column>();
+ /** List of variable length columns in this table, ordered by offset */
+ private List<Column> _varColumns = new ArrayList<Column>();
/** List of indexes on this table */
private List<Index> _indexes = new ArrayList<Index>();
/** Used to read in pages */
*/
void setColumns(List<Column> columns) {
_columns = columns;
- _maxVarColumnCount = Column.countVariableLength(_columns);
+ int colIdx = 0;
+ int varLenIdx = 0;
+ int fixedOffset = 0;
+ for(Column col : _columns) {
+ col.setColumnNumber((short)colIdx);
+ col.setColumnIndex(colIdx++);
+ if(col.isVariableLength()) {
+ col.setVarLenTableIndex(varLenIdx++);
+ _varColumns.add(col);
+ } else {
+ col.setFixedDataOffset(fixedOffset);
+ fixedOffset += col.getType().getFixedSize();
+ }
+ }
+ _maxColumnCount = (short)_columns.size();
+ _maxVarColumnCount = (short)_varColumns.size();
}
/**
for (int i = 0; i < _columnCount; i++) {
column = new Column(tableBuffer,
offset + i * _format.SIZE_COLUMN_HEADER, _pageChannel, _format);
+ _columns.add(column);
if(column.isVariableLength()) {
- _varColumnCount++;
- } else {
- _fixedColumnCount++;
+ // also shove it in the variable columns list, which is ordered
+ // differently from the _columns list
+ _varColumns.add(column);
}
- _columns.add(column);
}
offset += _columnCount * _format.SIZE_COLUMN_HEADER;
for (int i = 0; i < _columnCount; i++) {
}
Collections.sort(_columns);
+ // setup the data index for the columns
+ int colIdx = 0;
+ for(Column col : _columns) {
+ col.setColumnIndex(colIdx++);
+ }
+
+ // sort variable length columns based on their index into the variable
+ // length offset table, because we will write the columns in this order
+ Collections.sort(_varColumns, VAR_LEN_COLUMN_COMPARATOR);
+
int idxOffset = tableBuffer.position();
tableBuffer.position(idxOffset +
(_format.OFFSET_INDEX_NUMBER_BLOCK * _indexCount));
tableBuffer.position(idxEndOffset);
}
+ /**
+ * Sets up the _varColumns list, assuming the _columns has already been set
+ * up.
+ */
+ private void setupVariableColumns()
+ {
+ // pull out the variable length columns into a separate list
+ for(Column col : _columns) {
+ if(col.isVariableLength()) {
+ _varColumns.add(col);
+ }
+ }
+
+ // lastly sort these columns based on their index into the variable length
+ // offset table, because we will write the columns in this order
+ Collections.sort(_varColumns, VAR_LEN_COLUMN_COMPARATOR);
+ }
+
/**
* Writes the given page data to the given page number, clears any other
* relevant buffers.
*/
ByteBuffer createRow(Object[] rowArray, int maxRowSize) throws IOException {
ByteBuffer buffer = _pageChannel.createPageBuffer();
- buffer.putShort((short) _columns.size());
- NullMask nullMask = new NullMask(_columns.size());
- Iterator iter;
- int index = 0;
- Column col;
+ buffer.putShort((short) _maxColumnCount);
+ NullMask nullMask = new NullMask(_maxColumnCount);
List<Object> row = new ArrayList<Object>(Arrays.asList(rowArray));
//Append null for arrays that are too small
for (int i = rowArray.length; i < _columnCount; i++) {
row.add(null);
}
-
- for (iter = _columns.iterator(); iter.hasNext() && index < row.size(); index++) {
- col = (Column) iter.next();
- if (!col.isVariableLength()) {
- //Fixed length column data comes first (remainingRowLength is ignored
- //when writing fixed length data
- buffer.put(col.write(row.get(index), 0));
- }
- if (col.getType() == DataType.BOOLEAN) {
- if(!Column.toBooleanValue(row.get(index))) {
- //Booleans are stored in the null mask
- nullMask.markNull(index);
+
+ //Fixed length column data comes first
+ int fixedDataStart = buffer.position();
+ int fixedDataEnd = fixedDataStart;
+ for (Column col : _columns) {
+
+ if(!col.isVariableLength()) {
+
+ Object rowValue = row.get(col.getColumnIndex());
+
+ if (col.getType() == DataType.BOOLEAN) {
+
+ if(Column.toBooleanValue(rowValue)) {
+ //Booleans are stored in the null mask
+ nullMask.markNotNull(col.getColumnNumber());
+ }
+
+ } else if(rowValue != null) {
+
+ // we have a value
+ nullMask.markNotNull(col.getColumnNumber());
+
+ //remainingRowLength is ignored when writing fixed length data
+ buffer.position(fixedDataStart + col.getFixedDataOffset());
+ buffer.put(col.write(rowValue, 0));
+
+ // keep track of the end of fixed data
+ if(buffer.position() > fixedDataEnd) {
+ fixedDataEnd = buffer.position();
+ }
}
- } else if (row.get(index) == null) {
- nullMask.markNull(index);
}
}
- int varLengthCount = Column.countVariableLength(_columns);
- short[] varColumnOffsets = null;
+ // reposition at end of fixed data
+ buffer.position(fixedDataEnd);
+
+ // only need this info if this table contains any var length data
+ if(_maxVarColumnCount > 0) {
- if(varLengthCount > 0) {
// figure out how much space remains for var length data. first,
// account for already written space
maxRowSize -= buffer.position();
// now, account for trailer space
- maxRowSize -= (nullMask.byteSize() + 4 + (varLengthCount * 2));
+ maxRowSize -= (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
- varColumnOffsets = new short[varLengthCount];
- index = 0;
- int varColumnOffsetsIndex = 0;
//Now write out variable length column data
- for (iter = _columns.iterator(); iter.hasNext() && index < row.size();
- index++) {
- col = (Column) iter.next();
+ short[] varColumnOffsets = new short[_maxVarColumnCount];
+ int varColumnOffsetsIndex = 0;
+ for (Column varCol : _varColumns) {
short offset = (short) buffer.position();
- if (col.isVariableLength()) {
- if (row.get(index) != null) {
- ByteBuffer varDataBuf = col.write(row.get(index), maxRowSize);
- maxRowSize -= varDataBuf.remaining();
- buffer.put(varDataBuf);
- }
+ Object rowValue = row.get(varCol.getColumnIndex());
+ if (rowValue != null) {
+ // we have a value
+ nullMask.markNotNull(varCol.getColumnNumber());
+
+ ByteBuffer varDataBuf = varCol.write(rowValue, maxRowSize);
+ maxRowSize -= varDataBuf.remaining();
+ buffer.put(varDataBuf);
+ }
+
+ // we do a loop here so that we fill in offsets for deleted columns
+ while(varColumnOffsetsIndex <= varCol.getVarLenTableIndex()) {
varColumnOffsets[varColumnOffsetsIndex++] = offset;
}
}
- }
- // only need this info if this table contains any var length data
- if(_maxVarColumnCount > 0) {
buffer.putShort((short) buffer.position()); //EOD marker
//Now write out variable length offsets
//Offsets are stored in reverse order
- for (int i = varLengthCount - 1; i >= 0; i--) {
+ for (int i = _maxVarColumnCount - 1; i >= 0; i--) {
buffer.putShort(varColumnOffsets[i]);
}
- buffer.putShort((short) varLengthCount); //Number of var length columns
+ buffer.putShort((short) _maxVarColumnCount); //Number of var length columns
}
buffer.put(nullMask.wrap()); //Null mask
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
+import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
assertEquals(7, countRows(table));
}
+
+ public void testLongValueAsMiddleColumn() throws Exception
+ {
+
+ Database db = create();
+ Column a = new Column();
+ a.setName("a");
+ a.setSQLType(Types.INTEGER);
+ Column b = new Column();
+ b.setName("b");
+ b.setSQLType(Types.LONGVARCHAR);
+ Column c = new Column();
+ c.setName("c");
+ c.setSQLType(Types.VARCHAR);
+ db.createTable("NewTable", Arrays.asList(a, b, c));
+ Table newTable = db.getTable("NewTable");
+
+ String lval = createString(2000); // "--2000 chars long text--";
+ String tval = createString(40); // "--40chars long text--";
+ newTable.addRow(new Integer(1), lval, tval);
+
+ newTable = db.getTable("NewTable");
+ Map<String, Object> readRow = newTable.getNextRow();
+ assertEquals(new Integer(1), readRow.get("a"));
+ assertEquals(lval, readRow.get("b"));
+ assertEquals(tval, readRow.get("c"));
+
+ }
static Object[] createTestRow(String col1Val) {
return new Object[] {col1Val, "R", "McCune", 1234, (byte) 0xad, 555.66d,
db.createTable("test", columns);
}
+ static String createString(int len) {
+ StringBuilder builder = new StringBuilder(len);
+ for(int i = 0; i < len; ++i) {
+ builder.append((char)('a' + (i % 26)));
+ }
+ String str = builder.toString();
+ return str;
+ }
+
static int countRows(Table table) throws Exception {
int rtn = 0;
for(Map<String, Object> row : table) {