]> source.dussan.org Git - jackcess.git/commitdiff
Add experimental support for auto-number columns, feature request
authorJames Ahlborn <jtahlborn@yahoo.com>
Fri, 16 Nov 2007 19:57:37 +0000 (19:57 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Fri, 16 Nov 2007 19:57:37 +0000 (19:57 +0000)
        #1565216.

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@173 f203690c-595d-4dc9-a70b-905162fa7fd2

src/changes/changes.xml
src/java/com/healthmarketscience/jackcess/Column.java
src/java/com/healthmarketscience/jackcess/Database.java
src/java/com/healthmarketscience/jackcess/JetFormat.java
src/java/com/healthmarketscience/jackcess/Table.java
test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java

index 979d68cd0892737cf5a4449610b4bba0ea8ff8a0..11d8e23c5a7d3cb44898d0a7921bcc3bc2d6a5af 100644 (file)
@@ -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.
index cef17ccf3281490fb011e15664b890efb6d58c36..20a26ae1670d0455ce0b33d89db6f468b7539dce 100644 (file)
@@ -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);
index 58dc6fe4cbeb2864dafa34daf95babde00bdf3de..d161d4eb0a88732e0e3491a52af9bb5d45eec792 100644 (file)
@@ -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);
   }
index 0eac46067e1673ca26dfbc8df65da379c273b64a..53f69aaeeec1050ca9cc6af7b430e8486a597fca 100644 (file)
@@ -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
index bb392b07f86f7e9b773fe6a1cdf941a3b2033bd1..3bed3cbe912d090f87497962f6d3d6c8711914c3 100644 (file)
@@ -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;
index c46ee003fe96ee98a86a02041ceddfb20b1b3688..80c65cb536a6392d77efea157a39920cf4b34014 100644 (file)
@@ -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));
   }