]> source.dussan.org Git - jackcess.git/commitdiff
when writing table definition, set things up so long variable length columns are...
authorJames Ahlborn <jtahlborn@yahoo.com>
Tue, 8 May 2007 03:32:44 +0000 (03:32 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Tue, 8 May 2007 03:32:44 +0000 (03:32 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@143 f203690c-595d-4dc9-a70b-905162fa7fd2

src/java/com/healthmarketscience/jackcess/Column.java
src/java/com/healthmarketscience/jackcess/Database.java
src/java/com/healthmarketscience/jackcess/NullMask.java
src/java/com/healthmarketscience/jackcess/Table.java
test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java
test/src/java/com/healthmarketscience/jackcess/TableTest.java

index 259058420101170ae9825df5a60f88ea2bbffb0c..29dcb734a56b80f93bd5b3fbd648866384ffeeaa 100644 (file)
@@ -99,6 +99,8 @@ public class Column implements Comparable<Column> {
   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 */
@@ -157,13 +159,26 @@ public class Column implements Comparable<Column> {
   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
@@ -232,11 +247,19 @@ public class Column implements Comparable<Column> {
   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;
   }
@@ -1040,11 +1063,9 @@ public class Column implements Comparable<Column> {
    * @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++;
       }
@@ -1052,6 +1073,21 @@ public class Column implements Comparable<Column> {
     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
index 3468b4fc96d5fc8340f264685fa88ec225654e7c..492df5f39994d08d0797afd879b6feeb17e3630e 100644 (file)
@@ -501,14 +501,17 @@ public class Database
    * @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
@@ -516,7 +519,11 @@ public class Database
       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);
       }
@@ -552,14 +559,13 @@ public class Database
       } 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);
index 282c14478d66cea011389145c5affbe673c7d85d..4937f386146a6c6d555dfc7da9ace5a752bc5ec3 100644 (file)
@@ -44,13 +44,10 @@ public class NullMask {
    *    used for
    */
   public NullMask(int columnCount) {
+    // we leave everything initially marked as null so that we don't need to
+    // do anything for deleted columns (we only need to mark as non-null
+    // valid columns for which we actually have values).
     _mask = new byte[(columnCount + 7) / 8];
-    for (int i = 0; i < _mask.length; i++) {
-      _mask[i] = (byte) 0xff;
-    }
-    for (int i = columnCount; i < _mask.length * 8; i++) {
-      markNull(i);
-    }
   }
   
   /**
@@ -67,7 +64,8 @@ public class NullMask {
   /**
    * @param columnNumber 0-based column number in this mask's row
    * @return Whether or not the value for that column is null.  For boolean
-   *    columns, returns the actual value of the column.
+   *    columns, returns the actual value of the column (where
+   *    non-{@code null} == {@code true})
    */
   public boolean isNull(int columnNumber) {
     int maskIndex = columnNumber / 8;
@@ -79,10 +77,15 @@ public class NullMask {
     }
     return (_mask[maskIndex] & (byte) (1 << (columnNumber % 8))) == 0;
   }
-  
-  public void markNull(int columnNumber) {
+
+  /**
+   * Indicate that the column with the given number is not {@code null} (or a
+   * boolean value is {@code true}).
+   * @param columnNumber 0-based column number in this mask's row
+   */
+  public void markNotNull(int columnNumber) {
     int maskIndex = columnNumber / 8;
-    _mask[maskIndex] = (byte) (_mask[maskIndex] & (byte) ~(1 << (columnNumber % 8)));
+    _mask[maskIndex] = (byte) (_mask[maskIndex] | (byte) (1 << (columnNumber % 8)));
   }
   
   /**
index 9b60071d58d7d6cbc59d34c9f4245675a61b20c0..be801b029d62eae48ca31aaca1e987d800896c88 100644 (file)
@@ -34,6 +34,7 @@ import java.util.ArrayList;
 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;
@@ -66,6 +67,17 @@ public class Table
   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;
@@ -87,16 +99,14 @@ public class Table
   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 */
@@ -172,7 +182,22 @@ public class Table
    */
   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();
   }
   
   /**
@@ -567,12 +592,12 @@ public class Table
     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++) {
@@ -587,6 +612,16 @@ public class Table
     }
     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));
@@ -639,6 +674,24 @@ public class Table
     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.
@@ -768,73 +821,88 @@ public class Table
    */
   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
index 92cbb47c92ba720b2e40f8ce5b77c0db00a36d49..449424d0c36ccbe0cce06cf46d27afaddd714b1a 100644 (file)
@@ -12,6 +12,7 @@ import java.io.OutputStream;
 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;
@@ -555,6 +556,34 @@ public class DatabaseTest extends TestCase {
     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,
@@ -606,6 +635,15 @@ public class DatabaseTest extends TestCase {
     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) {
index 29178f9420850348aa1efac014f568cbe99e47c3..96d33fa02fdb1f860aae393c834b998cd22ff36a 100644 (file)
@@ -27,6 +27,8 @@ public class TableTest extends TestCase {
     col = new Column();
     col.setType(DataType.TEXT);
     columns.add(col);
+    col = new Column();
+    col.setType(DataType.TEXT);
     columns.add(col);
     table.setColumns(columns);
     int colCount = 3;