</properties>
<body>
<release version="1.1.10" date="TBD">
+ <action dev="jahlborn" type="update" issue="1565216">
+ Add experimental support for auto-number columns, feature request
+ #1565216.
+ </action>
<action dev="jahlborn" type="update">
Move project to maven2 and change project groupId to
com.healthmarketscience.jackcess.
public class Column implements Comparable<Column> {
private static final Log LOG = LogFactory.getLog(Column.class);
+
+ /**
+ * Meaningless placeholder object for inserting values in an autonumber
+ * column. it is not required that this value be used (any passed in value
+ * is ignored), but using this placeholder may make code more obvious.
+ */
+ public static final Object AUTO_NUMBER = "<AUTO_NUMBER>";
/**
* Access starts counting dates at Jan 1, 1900. Java starts counting
*/
private static final byte LONG_VALUE_TYPE_OTHER_PAGES = (byte) 0x00;
+ /** mask for the fixed len bit */
+ public static final byte FIXED_LEN_FLAG_MASK = (byte)0x01;
+
+ /** mask for the auto number bit */
+ public static final byte AUTO_NUMBER_FLAG_MASK = (byte)0x04;
+
+ /** mask for the unknown bit */
+ public static final byte UNKNOWN_FLAG_MASK = (byte)0x02;
+
private static final Pattern GUID_PATTERN = Pattern.compile("\\s*[{]([\\p{XDigit}]{8})-([\\p{XDigit}]{4})-([\\p{XDigit}]{4})-([\\p{XDigit}]{4})-([\\p{XDigit}]{12})[}]\\s*");
/** For text columns, whether or not they are compressed */
private boolean _compressedUnicode = false;
/** Whether or not the column is of variable length */
private boolean _variableLength;
+ /** Whether or not the column is an autonumber column */
+ private boolean _autoNumber;
/** Numeric precision */
private byte _precision;
/** Numeric scale */
_precision = buffer.get(offset + format.OFFSET_COLUMN_PRECISION);
_scale = buffer.get(offset + format.OFFSET_COLUMN_SCALE);
}
- _variableLength = ((buffer.get(offset + format.OFFSET_COLUMN_VARIABLE)
- & 1) != 1);
+ byte flags = buffer.get(offset + format.OFFSET_COLUMN_FLAGS);
+ _variableLength = ((flags & FIXED_LEN_FLAG_MASK) == 0);
+ _autoNumber = ((flags & AUTO_NUMBER_FLAG_MASK) != 0);
_compressedUnicode = ((buffer.get(offset +
format.OFFSET_COLUMN_COMPRESSED_UNICODE) & 1) == 1);
public void setVariableLength(boolean variableLength) {
_variableLength = variableLength;
}
+
+ public boolean isAutoNumber() {
+ return _autoNumber;
+ }
+
+ public void setAutoNumber(boolean autoNumber) {
+ _autoNumber = autoNumber;
+ }
public short getColumnNumber() {
return _columnNumber;
getType().getMaxPrecision() + " inclusive");
}
}
+
+ if(isAutoNumber()) {
+ if(getType() != DataType.LONG) {
+ throw new IllegalArgumentException(
+ "Auto number column must be long integer");
+ }
+ }
}
/**
} else if (_type == DataType.OLE) {
if (data.length > 0) {
return readLongValue(data);
- } else {
- return null;
}
+ return null;
} else if (_type == DataType.MEMO) {
if (data.length > 0) {
return readLongStringValue(data);
- } else {
- return null;
}
+ return null;
} else if (_type == DataType.NUMERIC) {
return readNumericValue(buffer);
} else if (_type == DataType.GUID) {
return textBuf.toString();
- } else {
- return decodeUncompressedText(data);
}
+ return decodeUncompressedText(data);
+
} catch (IllegalInputException e) {
throw (IOException)
new IOException("Can't expand text column").initCause(e);
return _format.CHARSET.decode(ByteBuffer.wrap(textBytes, startPost,
length));
}
-
+
+ @Override
public String toString() {
StringBuilder rtn = new StringBuilder();
rtn.append("\tName: " + _name);
PAGE_SYSTEM_CATALOG + ", but page type is " + pageType);
}
_systemCatalog = new Table(_buffer, _pageChannel, _format, PAGE_SYSTEM_CATALOG, "System Catalog");
- Map row;
+ Map<String,Object> row;
while ( (row = _systemCatalog.getNextRow(SYSTEM_CATALOG_COLUMNS)) != null)
{
String name = (String) row.get(COL_NAME);
if ((tableInfo == null) || (tableInfo.pageNumber == null)) {
return null;
- } else {
- int pageNumber = tableInfo.pageNumber.intValue();
- _pageChannel.readPage(_buffer, pageNumber);
- return new Table(_buffer, _pageChannel, _format, pageNumber,
- tableInfo.tableName);
}
+
+ int pageNumber = tableInfo.pageNumber.intValue();
+ _pageChannel.readPage(_buffer, pageNumber);
+ return new Table(_buffer, _pageChannel, _format, pageNumber,
+ tableInfo.tableName);
}
/**
private void addToSystemCatalog(String name, int pageNumber) throws IOException {
Object[] catalogRow = new Object[_systemCatalog.getColumns().size()];
int idx = 0;
- Iterator iter;
- for (iter = _systemCatalog.getColumns().iterator(); iter.hasNext(); idx++) {
- Column col = (Column) iter.next();
+ for (Iterator<Column> iter = _systemCatalog.getColumns().iterator();
+ iter.hasNext(); idx++)
+ {
+ Column col = iter.next();
if (COL_ID.equals(col.getName())) {
catalogRow[idx] = Integer.valueOf(pageNumber);
} else if (COL_NAME.equals(col.getName())) {
private void addToAccessControlEntries(int pageNumber) throws IOException {
Object[] aceRow = new Object[_accessControlEntries.getColumns().size()];
int idx = 0;
- Iterator iter;
- for (iter = _accessControlEntries.getColumns().iterator(); iter.hasNext(); idx++) {
- Column col = (Column) iter.next();
+ for (Iterator<Column> iter = _accessControlEntries.getColumns().iterator();
+ iter.hasNext(); idx++)
+ {
+ Column col = iter.next();
if (col.getName().equals(COL_ACM)) {
aceRow[idx] = ACM;
} else if (col.getName().equals(COL_F_INHERITABLE)) {
private String escape(String s) {
if (RESERVED_WORDS.contains(s.toLowerCase())) {
return ESCAPE_PREFIX + s;
- } else {
- return s;
}
+ return s;
}
-
+
+ @Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
public final int OFFSET_NEXT_TABLE_DEF_PAGE;
public final int OFFSET_NUM_ROWS;
+ public final int OFFSET_NEXT_AUTO_NUMBER;
public final int OFFSET_TABLE_TYPE;
public final int OFFSET_MAX_COLS;
public final int OFFSET_NUM_VAR_COLS;
public final int OFFSET_COLUMN_NUMBER;
public final int OFFSET_COLUMN_PRECISION;
public final int OFFSET_COLUMN_SCALE;
- public final int OFFSET_COLUMN_VARIABLE;
+ public final int OFFSET_COLUMN_FLAGS;
public final int OFFSET_COLUMN_COMPRESSED_UNICODE;
public final int OFFSET_COLUMN_LENGTH;
public final int OFFSET_COLUMN_VARIABLE_TABLE_INDEX;
OFFSET_NEXT_TABLE_DEF_PAGE = defineOffsetNextTableDefPage();
OFFSET_NUM_ROWS = defineOffsetNumRows();
+ OFFSET_NEXT_AUTO_NUMBER = defineOffsetNextAutoNumber();
OFFSET_TABLE_TYPE = defineOffsetTableType();
OFFSET_MAX_COLS = defineOffsetMaxCols();
OFFSET_NUM_VAR_COLS = defineOffsetNumVarCols();
OFFSET_COLUMN_NUMBER = defineOffsetColumnNumber();
OFFSET_COLUMN_PRECISION = defineOffsetColumnPrecision();
OFFSET_COLUMN_SCALE = defineOffsetColumnScale();
- OFFSET_COLUMN_VARIABLE = defineOffsetColumnVariable();
+ OFFSET_COLUMN_FLAGS = defineOffsetColumnFlags();
OFFSET_COLUMN_COMPRESSED_UNICODE = defineOffsetColumnCompressedUnicode();
OFFSET_COLUMN_LENGTH = defineOffsetColumnLength();
OFFSET_COLUMN_VARIABLE_TABLE_INDEX = defineOffsetColumnVariableTableIndex();
protected abstract int defineOffsetNextTableDefPage();
protected abstract int defineOffsetNumRows();
+ protected abstract int defineOffsetNextAutoNumber();
protected abstract int defineOffsetTableType();
protected abstract int defineOffsetMaxCols();
protected abstract int defineOffsetNumVarCols();
protected abstract int defineOffsetColumnNumber();
protected abstract int defineOffsetColumnPrecision();
protected abstract int defineOffsetColumnScale();
- protected abstract int defineOffsetColumnVariable();
+ protected abstract int defineOffsetColumnFlags();
protected abstract int defineOffsetColumnCompressedUnicode();
protected abstract int defineOffsetColumnLength();
protected abstract int defineOffsetColumnVariableTableIndex();
@Override
protected int defineOffsetNumRows() { return 16; }
@Override
+ protected int defineOffsetNextAutoNumber() { return 20; }
+ @Override
protected int defineOffsetTableType() { return 40; }
@Override
protected int defineOffsetMaxCols() { return 41; }
@Override
protected int defineOffsetColumnScale() { return 12; }
@Override
- protected int defineOffsetColumnVariable() { return 15; }
+ protected int defineOffsetColumnFlags() { return 15; }
@Override
protected int defineOffsetColumnCompressedUnicode() { return 16; }
@Override
private int _indexSlotCount;
/** Number of rows in the table */
private int _rowCount;
+ /** last auto number for the table */
+ private int _lastAutoNumber;
/** page number of the definition of this table */
private int _tableDefPageNumber;
/** Number of rows left to be read on the current page */
buffer.put((byte) 0x06); //Unknown
buffer.putShort((short) 0); //Unknown
buffer.putInt(0); //Number of rows
- buffer.putInt(0); //Autonumber
+ buffer.putInt(0); //Last Autonumber
for (int i = 0; i < 16; i++) { //Unknown
buffer.put((byte) 0);
}
buffer.put((byte) 0x00); //unused
}
buffer.putShort((short) 0); //Unknown
- if (col.isVariableLength()) { //Variable length
- buffer.put((byte) 0x2);
- } else {
- buffer.put((byte) 0x3);
- }
+ buffer.put(getColumnBitFlags(col)); // misc col flags
if (col.isCompressedUnicode()) { //Compressed
buffer.put((byte) 1);
} else {
buffer.put(colName);
}
}
+
+ /**
+ * Constructs a byte containing the flags for the given column.
+ */
+ private static byte getColumnBitFlags(Column col) {
+ byte flags = Column.UNKNOWN_FLAG_MASK;
+ if(!col.isVariableLength()) {
+ flags |= Column.FIXED_LEN_FLAG_MASK;
+ }
+ if(col.isAutoNumber()) {
+ flags |= Column.AUTO_NUMBER_FLAG_MASK;
+ }
+ return flags;
+ }
/**
* Create the usage map definition page buffer. The "used pages" map is in
_format.SIZE_TDEF_HEADER));
}
_rowCount = tableBuffer.getInt(_format.OFFSET_NUM_ROWS);
+ _lastAutoNumber = tableBuffer.getInt(_format.OFFSET_NEXT_AUTO_NUMBER);
_tableType = tableBuffer.get(_format.OFFSET_TABLE_TYPE);
_maxColumnCount = tableBuffer.getShort(_format.OFFSET_MAX_COLS);
_maxVarColumnCount = tableBuffer.getShort(_format.OFFSET_NUM_VAR_COLS);
for (int i = 0; i < _indexCount; i++) {
byte[] nameBytes = new byte[tableBuffer.getShort()];
tableBuffer.get(nameBytes);
- ((Index) _indexes.get(i)).setName(_format.CHARSET.decode(ByteBuffer.wrap(
+ _indexes.get(i).setName(_format.CHARSET.decode(ByteBuffer.wrap(
nameBytes)).toString());
}
int idxEndOffset = tableBuffer.position();
tableBuffer.position(idxOffset);
for (int i = 0; i < _indexCount; i++) {
tableBuffer.getInt(); //Forward past Unknown
- ((Index) _indexes.get(i)).read(tableBuffer, _columns);
+ _indexes.get(i).read(tableBuffer, _columns);
}
// reset to end of index info
ByteBuffer[] rowData = new ByteBuffer[rows.size()];
Iterator<? extends Object[]> iter = rows.iterator();
for (int i = 0; iter.hasNext(); i++) {
- rowData[i] = createRow((Object[]) iter.next(), _format.MAX_ROW_SIZE);
+ rowData[i] = createRow(iter.next(), _format.MAX_ROW_SIZE);
if (rowData[i].limit() > _format.MAX_ROW_SIZE) {
throw new IOException("Row size " + rowData[i].limit() +
" is too large");
// update the indexes
Iterator<Index> indIter = _indexes.iterator();
while (indIter.hasNext()) {
- Index index = (Index) indIter.next();
- index.addRow((Object[]) rows.get(i), pageNumber, (byte) rowNum);
+ Index index = indIter.next();
+ index.addRow(rows.get(i), pageNumber, (byte) rowNum);
}
}
writeDataPage(dataPage, pageNumber);
ByteBuffer tdefPage = _pageChannel.createPageBuffer();
_pageChannel.readPage(tdefPage, _tableDefPageNumber);
tdefPage.putInt(_format.OFFSET_NUM_ROWS, ++_rowCount);
+ tdefPage.putInt(_format.OFFSET_NEXT_AUTO_NUMBER, _lastAutoNumber);
Iterator<Index> indIter = _indexes.iterator();
for (int i = 0; i < _indexes.size(); i++) {
tdefPage.putInt(_format.OFFSET_INDEX_DEF_BLOCK +
i * _format.SIZE_INDEX_DEFINITION + 4, _rowCount);
- Index index = (Index) indIter.next();
+ Index index = indIter.next();
index.update();
}
_pageChannel.writePage(tdefPage, _tableDefPageNumber);
nullMask.markNotNull(col.getColumnNumber());
}
- } else if(rowValue != null) {
+ } else {
+
+ if(col.isAutoNumber()) {
+ // ignore given row value, use next autonumber
+ rowValue = getNextAutoNumber();
+ }
+
+ if(rowValue != null) {
- // we have a value
- nullMask.markNotNull(col.getColumnNumber());
+ // 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));
+ //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();
+ // keep track of the end of fixed data
+ if(buffer.position() > fixedDataEnd) {
+ fixedDataEnd = buffer.position();
+ }
}
}
}
public int getRowCount() {
return _rowCount;
}
+
+ private int getNextAutoNumber() {
+ // note, the saved value is the last one handed out, so pre-increment
+ return ++_lastAutoNumber;
+ }
public String toString() {
StringBuilder rtn = new StringBuilder();
rtn.append("\nColumn count: " + _columns.size());
rtn.append("\nIndex count: " + _indexCount);
rtn.append("\nColumns:\n");
- Iterator iter = _columns.iterator();
- while (iter.hasNext()) {
- rtn.append(iter.next().toString());
+ for(Column col : _columns) {
+ rtn.append(col);
}
rtn.append("\nIndexes:\n");
- iter = _indexes.iterator();
- while (iter.hasNext()) {
- rtn.append(iter.next().toString());
+ for(Index index : _indexes) {
+ rtn.append(index);
}
rtn.append("\nOwned pages: " + _ownedPages + "\n");
return rtn.toString();
public String display(long limit) throws IOException {
reset();
StringBuilder rtn = new StringBuilder();
- Iterator iter = _columns.iterator();
- while (iter.hasNext()) {
- Column col = (Column) iter.next();
+ for(Iterator<Column> iter = _columns.iterator(); iter.hasNext(); ) {
+ Column col = iter.next();
rtn.append(col.getName());
if (iter.hasNext()) {
rtn.append("\t");
}
}
rtn.append("\n");
- Map row;
+ Map<String, Object> row;
int rowCount = 0;
while ((rowCount++ < limit) && (row = getNextRow()) != null) {
- iter = row.values().iterator();
- while (iter.hasNext()) {
+ for(Iterator<Object> iter = row.values().iterator(); iter.hasNext(); ) {
Object obj = iter.next();
if (obj instanceof byte[]) {
byte[] b = (byte[]) obj;
db.close();
}
+
+ public void testAutoNumber() throws Exception {
+ Database db = create();
+
+ List<Column> columns = new ArrayList<Column>();
+ Column col = new Column();
+ col.setName("a");
+ col.setType(DataType.LONG);
+ col.setAutoNumber(true);
+ columns.add(col);
+ col = new Column();
+ col.setName("b");
+ col.setType(DataType.TEXT);
+ columns.add(col);
+
+ db.createTable("test", columns);
+
+ Table table = db.getTable("test");
+ table.addRow(null, "row1");
+ table.addRow(13, "row2");
+ table.addRow("flubber", "row3");
+
+ table.reset();
+
+ table.addRow(Column.AUTO_NUMBER, "row4");
+ table.addRow(Column.AUTO_NUMBER, "row5");
+
+ table.reset();
+
+ List<Map<String, Object>> expectedRows =
+ createExpectedTable(
+ createExpectedRow(
+ "a", 1,
+ "b", "row1"),
+ createExpectedRow(
+ "a", 2,
+ "b", "row2"),
+ createExpectedRow(
+ "a", 3,
+ "b", "row3"),
+ createExpectedRow(
+ "a", 4,
+ "b", "row4"),
+ createExpectedRow(
+ "a", 5,
+ "b", "row5"));
+
+ assertTable(expectedRows, table);
+
+ db.close();
+ }
static Object[] createTestRow(String col1Val) {
return new Object[] {col1Val, "R", "McCune", 1234, (byte) 0xad, 555.66d,
return rtn;
}
+ static void assertTable(List<Map<String, Object>> expectedTable, Table table)
+ {
+ List<Map<String, Object>> foundTable =
+ new ArrayList<Map<String, Object>>();
+ for(Map<String, Object> row : table) {
+ foundTable.add(row);
+ }
+ assertEquals(expectedTable, foundTable);
+ }
+
+ static Map<String, Object> createExpectedRow(Object... rowElements) {
+ Map<String, Object> row = new HashMap<String, Object>();
+ for(int i = 0; i < rowElements.length; i += 2) {
+ row.put((String)rowElements[i],
+ rowElements[i + 1]);
+ }
+ return row;
+ }
+
+ @SuppressWarnings("unchecked")
+ static List<Map<String, Object>> createExpectedTable(Map... rows) {
+ return Arrays.<Map<String, Object>>asList(rows);
+ }
+
static void dumpDatabase(Database mdb) throws Exception {
dumpDatabase(mdb, new PrintWriter(System.out, true));
}