diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2007-11-16 19:57:37 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2007-11-16 19:57:37 +0000 |
commit | 9ba0e78522e68b40c75b0cb86fe74815146ce20d (patch) | |
tree | 82eee57e2e34795ddadd1a804b5f9019bba6065f | |
parent | 26f63555d13618ddd4fb1923efe1683f7c5a41a6 (diff) | |
download | jackcess-9ba0e78522e68b40c75b0cb86fe74815146ce20d.tar.gz jackcess-9ba0e78522e68b40c75b0cb86fe74815146ce20d.zip |
Add experimental support for auto-number columns, feature request
#1565216.
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@173 f203690c-595d-4dc9-a70b-905162fa7fd2
-rw-r--r-- | src/changes/changes.xml | 4 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Column.java | 51 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Database.java | 32 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/JetFormat.java | 13 | ||||
-rw-r--r-- | src/java/com/healthmarketscience/jackcess/Table.java | 89 | ||||
-rw-r--r-- | test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java | 75 |
6 files changed, 203 insertions, 61 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 979d68c..11d8e23 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,6 +6,10 @@ </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. diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java index cef17cc..20a26ae 100644 --- a/src/java/com/healthmarketscience/jackcess/Column.java +++ b/src/java/com/healthmarketscience/jackcess/Column.java @@ -54,6 +54,13 @@ import org.apache.commons.logging.LogFactory; 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 @@ -78,12 +85,23 @@ public class Column implements Comparable<Column> { */ 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 */ @@ -147,8 +165,9 @@ public class Column implements Comparable<Column> { _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); @@ -173,6 +192,14 @@ public class Column implements Comparable<Column> { public void setVariableLength(boolean variableLength) { _variableLength = variableLength; } + + public boolean isAutoNumber() { + return _autoNumber; + } + + public void setAutoNumber(boolean autoNumber) { + _autoNumber = autoNumber; + } public short getColumnNumber() { return _columnNumber; @@ -316,6 +343,13 @@ public class Column implements Comparable<Column> { getType().getMaxPrecision() + " inclusive"); } } + + if(isAutoNumber()) { + if(getType() != DataType.LONG) { + throw new IllegalArgumentException( + "Auto number column must be long integer"); + } + } } /** @@ -359,15 +393,13 @@ public class Column implements Comparable<Column> { } 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) { @@ -988,10 +1020,10 @@ public class Column implements Comparable<Column> { return textBuf.toString(); - } else { - return decodeUncompressedText(data); } + return decodeUncompressedText(data); + } catch (IllegalInputException e) { throw (IOException) new IOException("Can't expand text column").initCause(e); @@ -1052,7 +1084,8 @@ public class Column implements Comparable<Column> { return _format.CHARSET.decode(ByteBuffer.wrap(textBytes, startPost, length)); } - + + @Override public String toString() { StringBuilder rtn = new StringBuilder(); rtn.append("\tName: " + _name); diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java index 58dc6fe..d161d4e 100644 --- a/src/java/com/healthmarketscience/jackcess/Database.java +++ b/src/java/com/healthmarketscience/jackcess/Database.java @@ -317,7 +317,7 @@ public class Database 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); @@ -386,12 +386,12 @@ public class Database 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); } /** @@ -442,9 +442,10 @@ public class Database 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())) { @@ -476,9 +477,10 @@ public class Database 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)) { @@ -689,11 +691,11 @@ public class Database 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); } diff --git a/src/java/com/healthmarketscience/jackcess/JetFormat.java b/src/java/com/healthmarketscience/jackcess/JetFormat.java index 0eac460..53f69aa 100644 --- a/src/java/com/healthmarketscience/jackcess/JetFormat.java +++ b/src/java/com/healthmarketscience/jackcess/JetFormat.java @@ -67,6 +67,7 @@ public abstract class JetFormat { 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; @@ -83,7 +84,7 @@ public abstract class JetFormat { 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; @@ -149,6 +150,7 @@ public abstract class JetFormat { 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(); @@ -165,7 +167,7 @@ public abstract class JetFormat { 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(); @@ -209,6 +211,7 @@ public abstract class JetFormat { protected abstract int defineOffsetNextTableDefPage(); protected abstract int defineOffsetNumRows(); + protected abstract int defineOffsetNextAutoNumber(); protected abstract int defineOffsetTableType(); protected abstract int defineOffsetMaxCols(); protected abstract int defineOffsetNumVarCols(); @@ -225,7 +228,7 @@ public abstract class JetFormat { 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(); @@ -284,6 +287,8 @@ public abstract class JetFormat { @Override protected int defineOffsetNumRows() { return 16; } @Override + protected int defineOffsetNextAutoNumber() { return 20; } + @Override protected int defineOffsetTableType() { return 40; } @Override protected int defineOffsetMaxCols() { return 41; } @@ -314,7 +319,7 @@ public abstract class JetFormat { @Override protected int defineOffsetColumnScale() { return 12; } @Override - protected int defineOffsetColumnVariable() { return 15; } + protected int defineOffsetColumnFlags() { return 15; } @Override protected int defineOffsetColumnCompressedUnicode() { return 16; } @Override diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index bb392b0..3bed3cb 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -88,6 +88,8 @@ public class Table 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 */ @@ -664,7 +666,7 @@ public class Table 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); } @@ -741,11 +743,7 @@ public class Table 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 { @@ -776,6 +774,20 @@ public class Table 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 @@ -827,6 +839,7 @@ public class Table _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); @@ -921,7 +934,7 @@ public class Table 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(); @@ -932,7 +945,7 @@ public class Table 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 @@ -971,7 +984,7 @@ public class Table 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"); @@ -1022,8 +1035,8 @@ public class Table // 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); @@ -1032,11 +1045,12 @@ public class Table 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); @@ -1096,18 +1110,26 @@ public class Table 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(); + } } } } @@ -1172,6 +1194,11 @@ public class Table 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(); @@ -1181,14 +1208,12 @@ public class Table 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(); @@ -1208,20 +1233,18 @@ public class Table 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; diff --git a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java index c46ee00..80c65cb 100644 --- a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java @@ -699,6 +699,57 @@ public class DatabaseTest extends TestCase { 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, @@ -767,6 +818,30 @@ public class DatabaseTest extends TestCase { 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)); } |