]> source.dussan.org Git - jackcess.git/commitdiff
merge trunk changes through r744
authorJames Ahlborn <jtahlborn@yahoo.com>
Tue, 9 Jul 2013 01:39:40 +0000 (01:39 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Tue, 9 Jul 2013 01:39:40 +0000 (01:39 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/jackcess-2@745 f203690c-595d-4dc9-a70b-905162fa7fd2

src/changes/changes.xml
src/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
src/java/com/healthmarketscience/jackcess/impl/IndexData.java
src/java/com/healthmarketscience/jackcess/impl/TableCreator.java
src/java/com/healthmarketscience/jackcess/impl/TableImpl.java
src/java/com/healthmarketscience/jackcess/impl/UsageMap.java

index a3e5c6f997a9c209d027d1d39768ce70e2ea2223..181fe3fa63732b4517a2346145e576433362a166 100644 (file)
@@ -4,7 +4,12 @@
     <author email="javajedi@users.sf.net">Tim McCune</author>
   </properties>
   <body>
-    <release version="1.2.13" date="TBD">
+    <release version="1.2.14" date="TBD">
+      <action dev="jahlborn" type="fix" system="SourceForge2" issue="95">
+        Implement handling of usagemaps for long value (MEMO/OLE) columns.
+      </action>
+    </release>
+    <release version="1.2.13" date="2013-06-18">
       <action dev="jahlborn" type="fix">
         Fix partial page updates when using CodecHandlers which can only do
         full page encoding.
index 154d939eb016d3fd1f4166a62325c724a6b5d56b..13eb3706f859fb5e7fff437d7702409b2086f51f 100644 (file)
@@ -218,6 +218,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
   private final ComplexColumnInfo<? extends ComplexValue> _complexInfo;
   /** properties for this column, if any */
   private PropertyMap _props;  
+  /** Holds additional info for writing long values */
+  private LongValueBufferHolder _lvalBufferH;
   
   /**
    * @usage _advanced_method_
@@ -325,10 +327,20 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     } 
   }
 
+   /**
+   * Sets the usage maps for this column.
+   */
+  void setUsageMaps(UsageMap ownedPages, UsageMap freeSpacePages) {
+    _lvalBufferH = new UmapLongValueBufferHolder(ownedPages, freeSpacePages);
+  }
+
   /**
    * Secondary column initialization after the table is fully loaded.
    */
   void postTableLoadInit() throws IOException {
+    if(getType().isLongValue() && (_lvalBufferH == null)) {
+      _lvalBufferH = new LegacyLongValueBufferHolder();
+    }
     if(_complexInfo != null) {
       ((ComplexColumnInfoImpl<? extends ComplexValue>)_complexInfo)
       .postTableLoadInit();
@@ -473,6 +485,14 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     return _textInfo._versionHistoryCol;
   }
 
+   /**
+   * Returns the number of database pages owned by this column.
+   * @usage _intermediate_method_
+   */
+  public int getOwnedPageCount() {
+    return ((_lvalBufferH == null) ? 0 : _lvalBufferH.getOwnedPageCount());
+  }
+
   /**
    * @usage _advanced_method_
    */
@@ -1054,7 +1074,6 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       def.put(value);
     } else {
       
-      TempPageHolder lvalBufferH = getTable().getLongValueBuffer();
       ByteBuffer lvalPage = null;
       int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER;
       byte firstLvalRow = 0;
@@ -1062,8 +1081,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       // write other page(s)
       switch(type) {
       case LONG_VALUE_TYPE_OTHER_PAGE:
-        lvalPage = getLongValuePage(value.length, lvalBufferH);
-        firstLvalPageNum = lvalBufferH.getPageNumber();
+        lvalPage = _lvalBufferH.getLongValuePage(value.length);
+        firstLvalPageNum = _lvalBufferH.getPageNumber();
         firstLvalRow = (byte)TableImpl.addDataPageRow(lvalPage, value.length,
                                                   getFormat(), 0);
         lvalPage.put(value);
@@ -1075,12 +1094,13 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
         ByteBuffer buffer = ByteBuffer.wrap(value);
         int remainingLen = buffer.remaining();
         buffer.limit(0);
-        lvalPage = getLongValuePage(getFormat().MAX_LONG_VALUE_ROW_SIZE,
-                                    lvalBufferH);
-        firstLvalPageNum = lvalBufferH.getPageNumber();
+        lvalPage = _lvalBufferH.getLongValuePage(remainingLen);
+        firstLvalPageNum = _lvalBufferH.getPageNumber();
+        firstLvalRow = (byte)TableImpl.getRowsOnDataPage(lvalPage, getFormat());
         int lvalPageNum = firstLvalPageNum;
         ByteBuffer nextLvalPage = null;
         int nextLvalPageNum = 0;
+        int nextLvalRowNum = 0;
         while(remainingLen > 0) {
           lvalPage.clear();
 
@@ -1091,23 +1111,25 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
 
           // figure out if we will need another page, and if so, allocate it
           if(chunkLength < remainingLen) {
-            // force a new page to be allocated
-            lvalBufferH.clear();
-            nextLvalPage = getLongValuePage(
-                getFormat().MAX_LONG_VALUE_ROW_SIZE, lvalBufferH);
-            nextLvalPageNum = lvalBufferH.getPageNumber();
+            // force a new page to be allocated for the chunk after this
+            _lvalBufferH.clear();
+            nextLvalPage = _lvalBufferH.getLongValuePage(
+                (remainingLen - chunkLength) + 4);
+            nextLvalPageNum = _lvalBufferH.getPageNumber();
+            nextLvalRowNum = TableImpl.getRowsOnDataPage(nextLvalPage, 
+                                                         getFormat());
           } else {
             nextLvalPage = null;
             nextLvalPageNum = 0;
+            nextLvalRowNum = 0;
           }
 
           // add row to this page
           byte lvalRow = (byte)TableImpl.addDataPageRow(lvalPage, chunkLength + 4,
-                                                    getFormat(), 0);
+                                                        getFormat(), 0);
           
-          // write next page info (we'll always be writing into row 0 for
-          // newly created pages)
-          lvalPage.put((byte)0); // row number
+          // write next page info
+          lvalPage.put((byte)nextLvalRowNum); // row number
           ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum); // page number
 
           // write this page's chunk of data
@@ -1118,17 +1140,6 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
           // write new page to database
           getPageChannel().writePage(lvalPage, lvalPageNum);
 
-          if(lvalPageNum == firstLvalPageNum) {
-            // save initial row info
-            firstLvalRow = lvalRow;
-          } else {
-            // check assertion that we wrote to row 0 for all subsequent pages
-            if(lvalRow != (byte)0) {
-              throw new IllegalStateException("Expected row 0, but was " +
-                                              lvalRow);
-            }
-          }
-          
           // move to next page
           lvalPage = nextLvalPage;
           lvalPageNum = nextLvalPageNum;
@@ -1166,28 +1177,6 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     lvalPage.putShort((short)0); // num rows in page
   }
 
-  /**
-   * Returns a long value data page with space for data of the given length.
-   */
-  private ByteBuffer getLongValuePage(int dataLength,
-                                      TempPageHolder lvalBufferH)
-    throws IOException
-  {
-    ByteBuffer lvalPage = null;
-    if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
-      lvalPage = lvalBufferH.getPage(getPageChannel());
-      if(TableImpl.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
-        // the current page has space
-        return lvalPage;
-      }
-    }
-
-    // need new page
-    lvalPage = lvalBufferH.setNewPage(getPageChannel());
-    writeLongValueHeader(lvalPage);
-    return lvalPage;
-  }
-  
   /**
    * Serialize an Object into a raw byte value for this column in little
    * endian order
@@ -1805,7 +1794,6 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     throws IOException
   {
     List<ColumnBuilder> columns = creator.getColumns();
-    short columnNumber = (short) 0;
     short fixedOffset = (short) 0;
     short variableOffset = (short) 0;
     // we specifically put the "long variable" values after the normal
@@ -1813,13 +1801,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
     // all (because "long variable" values can go in separate pages)
     short longVariableOffset = countNonLongVariableLength(columns);
     for (ColumnBuilder col : columns) {
-      // record this for later use when writing indexes
-      col.setColumnNumber(columnNumber);
 
-      int position = buffer.position();
       buffer.put(col.getType().getValue());
       buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER);  //constant magic number
-      buffer.putShort(columnNumber);  //Column Number
+      buffer.putShort(col.getColumnNumber());  //Column Number
       if (col.getType().isVariableLength()) {
         if(!col.getType().isLongValue()) {
           buffer.putShort(variableOffset++);
@@ -1829,7 +1814,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       } else {
         buffer.putShort((short) 0);
       }
-      buffer.putShort(columnNumber); //Column Number again
+      buffer.putShort(col.getColumnNumber()); //Column Number again
       if(col.getType().isTextual()) {
         // this will write 4 bytes (note we don't support writing dbs which
         // use the text code page)
@@ -1863,7 +1848,6 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       } else {
         buffer.putShort((short)0x0000); // unused
       }
-      columnNumber++;
     }
     for (ColumnBuilder col : columns) {
       TableImpl.writeName(buffer, col.getName(), creator.getCharset());
@@ -2165,4 +2149,132 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
         of type MEMO) */
     private boolean _hyperlink;
   }
+
+  /**
+   * Manages secondary page buffers for long value writing.
+   */
+  private abstract class LongValueBufferHolder
+  {
+    /**
+     * Returns a long value data page with space for data of the given length.
+     */
+    public ByteBuffer getLongValuePage(int dataLength) throws IOException {
+
+      TempPageHolder lvalBufferH = getBufferHolder();
+      dataLength = Math.min(dataLength, getFormat().MAX_LONG_VALUE_ROW_SIZE);
+
+      ByteBuffer lvalPage = null;
+      if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
+        lvalPage = lvalBufferH.getPage(getPageChannel());
+        if(TableImpl.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
+          // the current page has space
+          return lvalPage;
+}
+      }
+
+      // need new page
+      return findNewPage(dataLength);
+    }
+
+    protected ByteBuffer findNewPage(int dataLength) throws IOException {
+      ByteBuffer lvalPage = getBufferHolder().setNewPage(getPageChannel());
+      writeLongValueHeader(lvalPage);
+      return lvalPage;
+    }
+
+    public int getOwnedPageCount() {
+      return 0;
+    }
+
+    /**
+     * Returns the page number of the current long value data page.
+     */
+    public int getPageNumber() {
+      return getBufferHolder().getPageNumber();
+    }
+
+    /**
+     * Discards the current the current long value data page.
+     */
+    public void clear() throws IOException {
+      getBufferHolder().clear();
+    }
+
+    protected abstract TempPageHolder getBufferHolder();
+  }
+
+  /**
+   * Manages a common, shared extra page for long values.  This is legacy
+   * behavior from before it was understood that there were additional usage
+   * maps for each columns.
+   */
+  private final class LegacyLongValueBufferHolder extends LongValueBufferHolder
+  {
+    @Override
+    protected TempPageHolder getBufferHolder() {
+      return getTable().getLongValueBuffer();
+    }
+  }
+
+  /**
+   * Manages the column usage maps for long values.
+   */
+  private final class UmapLongValueBufferHolder extends LongValueBufferHolder
+  {
+    /** Usage map of pages that this column owns */
+    private final UsageMap _ownedPages;
+    /** Usage map of pages that this column owns with free space on them */
+    private final UsageMap _freeSpacePages;
+    /** page buffer used to write "long value" data */
+    private final TempPageHolder _longValueBufferH =
+      TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
+
+    private UmapLongValueBufferHolder(UsageMap ownedPages,
+                                      UsageMap freeSpacePages) {
+      _ownedPages = ownedPages;
+      _freeSpacePages = freeSpacePages;
+    }
+
+    @Override
+    protected TempPageHolder getBufferHolder() {
+      return _longValueBufferH;
+    }
+
+    @Override
+    public int getOwnedPageCount() {
+      return _ownedPages.getPageCount();
+    }
+
+    @Override
+    protected ByteBuffer findNewPage(int dataLength) throws IOException {
+
+      // grab last owned page and check for free space.  
+      ByteBuffer newPage = TableImpl.findFreeRowSpace(      
+          _ownedPages, _freeSpacePages, _longValueBufferH);
+      
+      if(newPage != null) {
+        if(TableImpl.rowFitsOnDataPage(dataLength, newPage, getFormat())) {
+          return newPage;
+        }
+        // discard this page and allocate a new one
+        clear();
+      }
+
+      // nothing found on current pages, need new page
+      newPage = super.findNewPage(dataLength);
+      int pageNumber = getPageNumber();
+      _ownedPages.addPageNumber(pageNumber);
+      _freeSpacePages.addPageNumber(pageNumber);
+      return newPage;
+    }
+
+    @Override
+    public void clear() throws IOException {
+      int pageNumber = getPageNumber();
+      if(pageNumber != PageChannel.INVALID_PAGE_NUMBER) {
+        _freeSpacePages.removePageNumber(pageNumber, true);
+      }
+      super.clear();
+    }
+  }
 }
index f4e95ee6301a8e0ef81994e5cbda0b0b375d3829..a1e945bcd968ef0141364f57e977dc35532891a8 100644 (file)
@@ -81,7 +81,7 @@ public class IndexData {
   
   protected static final byte[] EMPTY_PREFIX = new byte[0];
 
-  private static final short COLUMN_UNUSED = -1;
+  static final short COLUMN_UNUSED = -1;
 
   public static final byte ASCENDING_COLUMN_FLAG = (byte)0x01;
 
@@ -335,6 +335,7 @@ public class IndexData {
 
   /**
    * Returns the number of database pages owned by this index data.
+   * @usage _intermediate_method_
    */
   public int getOwnedPageCount() {
     return _ownedPages.getPageCount();
@@ -432,10 +433,7 @@ public class IndexData {
       }
     }
 
-    int umapRowNum = tableBuffer.get();
-    int umapPageNum = ByteUtil.get3ByteInt(tableBuffer);
-    _ownedPages = UsageMap.read(getTable().getDatabase(), umapPageNum,
-                                umapRowNum, false);
+    _ownedPages = UsageMap.read(getTable().getDatabase(), tableBuffer, false);
     
     _rootPageNumber = tableBuffer.getInt();
 
index 2311d3635657a2d049d6faad7a4fe53c06346aef..8828ac2b1857f77927c5a929238e4d1337ce87da 100644 (file)
@@ -24,15 +24,15 @@ import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import com.healthmarketscience.jackcess.ColumnBuilder;
-import com.healthmarketscience.jackcess.IndexBuilder;
 import com.healthmarketscience.jackcess.DataType;
+import com.healthmarketscience.jackcess.IndexBuilder;
 
 /**
  * Helper class used to maintain state during table creation.
@@ -47,7 +47,10 @@ class TableCreator
   private final List<ColumnBuilder> _columns;
   private final List<IndexBuilder> _indexes;
   private final Map<IndexBuilder,IndexState> _indexStates = 
-    new HashMap<IndexBuilder,IndexState>();
+    new IdentityHashMap<IndexBuilder,IndexState>();
+  private final Map<ColumnBuilder,ColumnState> _columnStates = 
+    new IdentityHashMap<ColumnBuilder,ColumnState>();
+  private final List<ColumnBuilder> _lvalCols = new ArrayList<ColumnBuilder>();
   private int _tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
   private int _umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
   private int _indexCount;
@@ -110,6 +113,14 @@ class TableCreator
     return getPageChannel().allocateNewPage();
   }
 
+  public ColumnState getColumnState(ColumnBuilder col) {
+    return _columnStates.get(col);
+  }
+
+  public List<ColumnBuilder> getLongValueColumns() {
+    return _lvalCols;
+  }
+
   /**
    * Creates the table in the database.
    * @usage _advanced_method_
@@ -118,6 +129,17 @@ class TableCreator
 
     validate();
 
+    // assign column numbers and do some assorted column bookkeeping
+    short columnNumber = (short) 0;
+    for(ColumnBuilder col : _columns) {
+      col.setColumnNumber(columnNumber++);
+      if(col.getType().isLongValue()) {
+        _lvalCols.add(col);
+        // only lval columns need extra state
+        _columnStates.put(col, new ColumnState());
+      }
+    }
+
     if(hasIndexes()) {
       // sort out index numbers.  for now, these values will always match
       // (until we support writing foreign key indexes)
@@ -291,6 +313,41 @@ class TableCreator
     public void setRootPageNumber(int newRootPageNumber) {
       _rootPageNumber = newRootPageNumber;
     }
+  }
     
+  /**
+   * Maintains additional state used during column creation.
+   * @usage _advanced_class_
+   */
+  static final class ColumnState
+  {
+    private byte _umapOwnedRowNumber;
+    private byte _umapFreeRowNumber;
+    // we always put both usage maps on the same page
+    private int _umapPageNumber;
+
+    public byte getUmapOwnedRowNumber() {
+      return _umapOwnedRowNumber;
+  }
+
+    public void setUmapOwnedRowNumber(byte newUmapOwnedRowNumber) {
+      _umapOwnedRowNumber = newUmapOwnedRowNumber;
+}
+
+    public byte getUmapFreeRowNumber() {
+      return _umapFreeRowNumber;
+    }
+
+    public void setUmapFreeRowNumber(byte newUmapFreeRowNumber) {
+      _umapFreeRowNumber = newUmapFreeRowNumber;
+    }
+
+    public int getUmapPageNumber() {
+      return _umapPageNumber;
+    }
+
+    public void setUmapPageNumber(int newUmapPageNumber) {
+      _umapPageNumber = newUmapPageNumber;
+    }
   }
 }
index f69e8dde567b94e5dd80a29a54cd230a5cdf8e05..a42b7c255c0448464b851e7a13fd73ea96aeec8e 100644 (file)
@@ -167,7 +167,7 @@ public class TableImpl implements Table
       every call) */
   private final TempBufferHolder _multiRowBufferH =
     TempBufferHolder.newHolder(TempBufferHolder.Type.NONE, true);
-  /** page buffer used to write out-of-line "long value" data */
+  /** page buffer used to write out-of-row "long value" data */
   private final TempPageHolder _longValueBufferH =
     TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
   /** optional error handler to use when row errors are encountered */
@@ -246,14 +246,10 @@ public class TableImpl implements Table
     _logicalIndexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEX_SLOTS);
     _indexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEXES);
 
-    int rowNum = ByteUtil.getUnsignedByte(
-        tableBuffer, getFormat().OFFSET_OWNED_PAGES);
-    int pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_OWNED_PAGES + 1);
-    _ownedPages = UsageMap.read(getDatabase(), pageNum, rowNum, false);
-    rowNum = ByteUtil.getUnsignedByte(
-        tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES);
-    pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES + 1);
-    _freeSpacePages = UsageMap.read(getDatabase(), pageNum, rowNum, false);
+    tableBuffer.position(getFormat().OFFSET_OWNED_PAGES);
+    _ownedPages = UsageMap.read(getDatabase(), tableBuffer, false);
+    tableBuffer.position(getFormat().OFFSET_FREE_SPACE_PAGES);
+    _freeSpacePages = UsageMap.read(getDatabase(), tableBuffer, false);
     
     for (int i = 0; i < _indexCount; i++) {
       _indexDatas.add(IndexData.create(this, tableBuffer, i, getFormat()));
@@ -263,6 +259,27 @@ public class TableImpl implements Table
 
     readIndexDefinitions(tableBuffer);
 
+    // read column usage map info
+    while(tableBuffer.remaining() >= 2) {
+
+      short umapColNum = tableBuffer.getShort();
+      if(umapColNum == IndexData.COLUMN_UNUSED) {
+        break;
+      }
+      
+      UsageMap colOwnedPages = UsageMap.read(
+          getDatabase(), tableBuffer, false);
+      UsageMap colFreeSpacePages = UsageMap.read(
+          getDatabase(), tableBuffer, false);
+    
+      for(ColumnImpl col : _columns) {
+        if(col.getColumnNumber() == umapColNum) {
+          col.setUsageMaps(colOwnedPages, colFreeSpacePages);
+          break;
+        }
+      }
+    }
+
     // re-sort columns if necessary
     if(getDatabase().getColumnOrder() != ColumnOrder.DATA) {
       Collections.sort(_columns, DISPLAY_ORDER_COMPARATOR);
@@ -354,14 +371,21 @@ public class TableImpl implements Table
    * @usage _intermediate_method_
    */
   public int getApproximateOwnedPageCount() {
+
     // add a page for the table def (although that might actually be more than
     // one page)
     int count = _ownedPages.getPageCount() + 1;
+
+    for(ColumnImpl col : _columns) {
+      count += col.getOwnedPageCount();
+    }
+
     // note, we count owned pages from _physical_ indexes, not logical indexes
     // (otherwise we could double count pages)
     for(IndexData indexData : _indexDatas) {
       count += indexData.getOwnedPageCount();
     }
+
     return count;
   }
   
@@ -920,9 +944,10 @@ public class TableImpl implements Table
                       (format.SIZE_INDEX_DEFINITION + 
                        format.SIZE_INDEX_COLUMN_BLOCK)) + 
       (creator.getLogicalIndexCount() * format.SIZE_INDEX_INFO_BLOCK);
+    int colUmapLen = creator.getLongValueColumns().size() * 10;
     int totalTableDefSize = format.SIZE_TDEF_HEADER +
       (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) + 
-      idxDataLen + format.SIZE_TDEF_TRAILER;
+      idxDataLen + colUmapLen + format.SIZE_TDEF_TRAILER;
 
     // total up the amount of space used by the column and index names (2
     // bytes per char + 2 bytes for the length)
@@ -959,6 +984,20 @@ public class TableImpl implements Table
       IndexImpl.writeDefinitions(creator, buffer);
     }
 
+    // write long value column usage map references
+    for(ColumnBuilder lvalCol : creator.getLongValueColumns()) {
+      buffer.putShort(lvalCol.getColumnNumber());
+      TableCreator.ColumnState colState = 
+        creator.getColumnState(lvalCol);
+      
+      // owned pages umap (both are on same page)
+      buffer.put(colState.getUmapOwnedRowNumber());
+      ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber());
+      // free space pages umap
+      buffer.put(colState.getUmapFreeRowNumber());
+      ByteUtil.put3ByteInt(buffer, colState.getUmapPageNumber());
+    }
+
     //End of tabledef
     buffer.put((byte) 0xff);
     buffer.put((byte) 0xff);
@@ -1083,53 +1122,70 @@ public class TableImpl implements Table
   private static void createUsageMapDefinitionBuffer(TableCreator creator)
     throws IOException
   {
-    // 2 table usage maps plus 1 for each index
-    int umapNum = 2 + creator.getIndexCount();
+    List<ColumnBuilder> lvalCols = creator.getLongValueColumns();
+
+    // 2 table usage maps plus 1 for each index and 2 for each lval col
+    int indexUmapEnd = 2 + creator.getIndexCount();
+    int umapNum = indexUmapEnd + (lvalCols.size() * 2);
 
     JetFormat format = creator.getFormat();
-    int usageMapRowLength = format.OFFSET_USAGE_MAP_START +
+    int umapRowLength = format.OFFSET_USAGE_MAP_START +
       format.USAGE_MAP_TABLE_BYTE_LENGTH;
-    int freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE
-      - (umapNum * getRowSpaceUsage(usageMapRowLength, format));
+    int umapSpaceUsage = getRowSpaceUsage(umapRowLength, format);
+    PageChannel pageChannel = creator.getPageChannel();
+    int umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
+    ByteBuffer umapBuf = null;
+    int freeSpace = 0;
+    int rowStart = 0;
+    int umapRowNum = 0;
     
-    // for now, don't handle writing that many indexes
-    if(freeSpace < 0) {
-      throw new IOException("FIXME attempting to write too many indexes");
+    for(int i = 0; i < umapNum; ++i) {
+
+      if(umapBuf == null) {
+
+        // need new page for usage maps
+        if(umapPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
+          // first umap page has already been reserved
+          umapPageNumber = creator.getUmapPageNumber();
+        } else {
+          // need another umap page
+          umapPageNumber = creator.reservePageNumber();
     }
 
-    int umapPageNumber = creator.getUmapPageNumber();
+        freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE;
 
-    PageChannel pageChannel = creator.getPageChannel();
-    ByteBuffer rtn = pageChannel.createPageBuffer();
-    rtn.put(PageTypes.DATA);
-    rtn.put((byte) 0x1);  //Unknown
-    rtn.putShort((short)freeSpace);  //Free space in page
-    rtn.putInt(0); //Table definition
-    rtn.putInt(0); //Unknown
-    rtn.putShort((short) umapNum); //Number of records on this page
-
-    // write two rows of usage map definitions for the table
-    int rowStart = findRowEnd(rtn, 0, format) - usageMapRowLength;
-    for(int i = 0; i < 2; ++i) {
-      rtn.putShort(getRowStartOffset(i, format), (short)rowStart);
-      if(i == 0) {
-        // initial "usage pages" map definition
-        rtn.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
-      } else {
-        // initial "pages with free space" map definition
-        rtn.put(rowStart, UsageMap.MAP_TYPE_INLINE);
+        umapBuf = pageChannel.createPageBuffer();
+        umapBuf.put(PageTypes.DATA);
+        umapBuf.put((byte) 0x1);  //Unknown
+        umapBuf.putShort((short)freeSpace);  //Free space in page
+        umapBuf.putInt(0); //Table definition
+        umapBuf.putInt(0); //Unknown
+        umapBuf.putShort((short)0); //Number of records on this page
+
+        rowStart = findRowEnd(umapBuf, 0, format) - umapRowLength;
+        umapRowNum = 0;
       }
-      rowStart -= usageMapRowLength;
-    }
 
-    if(creator.hasIndexes()) {
+      umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
       
-      for(int i = 0; i < creator.getIndexes().size(); ++i) {
-        IndexBuilder idx = creator.getIndexes().get(i);
+      if(i == 0) {
 
+        // table "owned pages" map definition
+        umapBuf.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
+
+      } else if(i == 1) {
+
+        // table "free space pages" map definition
+        umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
+
+      } else if(i < indexUmapEnd) {
+
+        // index umap
+        int indexIdx = i - 2;
+        IndexBuilder idx = creator.getIndexes().get(indexIdx);
+        
         // allocate root page for the index
         int rootPageNumber = pageChannel.allocateNewPage();
-        int umapRowNum = i + 2;
 
         // stash info for later use
         TableCreator.IndexState idxState = creator.getIndexState(idx);
@@ -1138,16 +1194,54 @@ public class TableImpl implements Table
         idxState.setUmapPageNumber(umapPageNumber);
 
         // index map definition, including initial root page
-        rtn.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
-        rtn.put(rowStart, UsageMap.MAP_TYPE_INLINE);
-        rtn.putInt(rowStart + 1, rootPageNumber);
-        rtn.put(rowStart + 5, (byte)1);
+        umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
+        umapBuf.putInt(rowStart + 1, rootPageNumber);
+        umapBuf.put(rowStart + 5, (byte)1);
+
+      } else {
+
+        // long value column umaps
+        int lvalColIdx = i - indexUmapEnd;
+        int umapType = lvalColIdx % 2;
+        lvalColIdx /= 2;
+
+        ColumnBuilder lvalCol = lvalCols.get(lvalColIdx);
+        TableCreator.ColumnState colState = 
+          creator.getColumnState(lvalCol);
+
+        umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
 
-        rowStart -= usageMapRowLength;
+        if((umapType == 1) && 
+           (umapPageNumber != colState.getUmapPageNumber())) {
+          // we want to force both usage maps for a column to be on the same
+          // data page, so just discard the previous one we wrote
+          --i;
+          umapType = 0;
       }
+        
+        if(umapType == 0) {
+          // lval column "owned pages" usage map
+          colState.setUmapOwnedRowNumber((byte)umapRowNum);
+          colState.setUmapPageNumber(umapPageNumber);
+        } else {
+          // lval column "free space pages" usage map (always on same page)
+          colState.setUmapFreeRowNumber((byte)umapRowNum);
     }
+      }
 
-    pageChannel.writePage(rtn, umapPageNumber);
+      rowStart -= umapRowLength;
+      freeSpace -= umapSpaceUsage;
+      ++umapRowNum;
+
+      if((freeSpace <= umapSpaceUsage) || (i == (umapNum - 1))) {
+        // finish current page
+        umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
+        umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE, 
+                         (short)umapRowNum);
+        pageChannel.writePage(umapBuf, umapPageNumber);
+        umapBuf = null;
+  }
+    }
   }
 
   /**
@@ -1647,47 +1741,68 @@ public class TableImpl implements Table
                                       int pageNumber)
     throws IOException
   {
+    // assume incoming page is modified
+    boolean modifiedPage = true;
+
     if(dataPage == null) {
 
+      // find owned page w/ free space
+      dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages, 
+                                  _addRowBufferH);
+
+      if(dataPage == null) {
+        // No data pages exist (with free space).  Create a new one.
+        return newDataPage();
+      }
+
+      // found a page, see if it will work
+      pageNumber = _addRowBufferH.getPageNumber();
+      // since we just loaded this page, it is not yet modified
+      modifiedPage = false;
+    }
+
+    if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
+
+      // Last data page is full.  Write old one and create a new one.
+      if(modifiedPage) {
+        writeDataPage(dataPage, pageNumber);
+      }
+      _freeSpacePages.removePageNumber(pageNumber, true);
+
+      dataPage = newDataPage();
+    }
+
+    return dataPage;
+  }
+
+  static ByteBuffer findFreeRowSpace(
+      UsageMap ownedPages, UsageMap freeSpacePages,
+      TempPageHolder rowBufferH)
+    throws IOException
+  {
       // find last data page (Not bothering to check other pages for free
       // space.)
-      UsageMap.PageCursor revPageCursor = _ownedPages.cursor();
+    UsageMap.PageCursor revPageCursor = ownedPages.cursor();
       revPageCursor.afterLast();
       while(true) {
         int tmpPageNumber = revPageCursor.getPreviousPage();
         if(tmpPageNumber < 0) {
           break;
         }
-        dataPage = _addRowBufferH.setPage(getPageChannel(), tmpPageNumber);
+      ByteBuffer dataPage = rowBufferH.setPage(ownedPages.getPageChannel(),
+                                               tmpPageNumber);
         if(dataPage.get() == PageTypes.DATA) {
           // found last data page, only use if actually listed in free space
           // pages
-          if(_freeSpacePages.containsPageNumber(tmpPageNumber)) {
-            pageNumber = tmpPageNumber;
+        if(freeSpacePages.containsPageNumber(tmpPageNumber)) {
+          return dataPage;
           }
-          break;
         }
       }
 
-      if(pageNumber == PageChannel.INVALID_PAGE_NUMBER) {
-        // No data pages exist (with free space).  Create a new one.
-        return newDataPage();
+    return null;
       }
     
-    }
-
-    if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
-
-      // Last data page is full.  Create a new one.
-      writeDataPage(dataPage, pageNumber);
-      _freeSpacePages.removePageNumber(pageNumber);
-
-      dataPage = newDataPage();
-    }
-
-    return dataPage;
-  }
   /**
    * Updates the table definition after rows are modified.
    */
@@ -2080,7 +2195,7 @@ public class TableImpl implements Table
    * Returns the row count for the current page.  If the page is invalid
    * ({@code null}) or the page is not a DATA page, 0 is returned.
    */
-  private static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
+  static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
     throws IOException
   {
     int rowsOnPage = 0;
index 4f4293ddb1a4ce8a9fe7dc6160fb5705aedf5551..6a80e044db96988338e24d4df68cd7014ee8028e 100644 (file)
@@ -107,6 +107,21 @@ public class UsageMap
     return getDatabase().getPageChannel();
   }
   
+  /**
+   * @param database database that contains this usage map
+   * @param buf buffer which contains the usage map row info
+   * @return Either an InlineUsageMap or a ReferenceUsageMap, depending on
+   *         which type of map is found
+   */
+  public static UsageMap read(DatabaseImpl database, ByteBuffer buf,
+                              boolean assumeOutOfRangeBitsOn)
+    throws IOException
+  {
+    int umapRowNum = buf.get();
+    int umapPageNum = ByteUtil.get3ByteInt(buf);
+    return read(database, umapPageNum, umapRowNum, false);
+  }
+  
   /**
    * @param database database that contains this usage map
    * @param pageNum Page number that this usage map is contained in