]> source.dussan.org Git - jackcess.git/commitdiff
initial update row support
authorJames Ahlborn <jtahlborn@yahoo.com>
Mon, 16 Nov 2009 13:08:01 +0000 (13:08 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Mon, 16 Nov 2009 13:08:01 +0000 (13:08 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@409 f203690c-595d-4dc9-a70b-905162fa7fd2

src/java/com/healthmarketscience/jackcess/ByteUtil.java
src/java/com/healthmarketscience/jackcess/Column.java
src/java/com/healthmarketscience/jackcess/Cursor.java
src/java/com/healthmarketscience/jackcess/Index.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 95c2d8eeab7873172c0bc1bf5a70d05f2713330d..199926f159d265765963f2222c3e391da6bc0e8e 100644 (file)
@@ -252,6 +252,18 @@ public final class ByteUtil {
     }
   }
 
+  /**
+   * Sets all bits in the given remaining byte range to 0.
+   */
+  public static void clearRemaining(ByteBuffer buffer)
+  {
+    if(!buffer.hasRemaining()) {
+      return;
+    }
+    int pos = buffer.position();
+    clearRange(buffer, pos, pos + buffer.remaining());
+  }
+
   /**
    * Sets all bits in the given byte range to 0.
    */
@@ -445,5 +457,17 @@ public final class ByteUtil {
     bytes[offset + 0] = bytes[offset + 1];
     bytes[offset + 1] = b;
   }
-  
+
+  /**
+   * Moves the position of the given buffer the given count from the current
+   * position.
+   * @return the new buffer position
+   */
+  public static int forward(ByteBuffer buffer, int count)
+  {
+    int newPos = buffer.position() + count;
+    buffer.position(newPos);
+    return newPos;
+  }
+
 }
index 9f689b32c445b133cf436dbe958d137eac6d72d1..a41b450324be62690f3339c321daedb5f7eff80b 100644 (file)
@@ -64,6 +64,12 @@ public class Column implements Comparable<Column> {
    */
   public static final Object AUTO_NUMBER = "<AUTO_NUMBER>";
   
+  /**
+   * Meaningless placeholder object for updating rows which indicates that a
+   * given column should keep its existing value.
+   */
+  public static final Object KEEP_VALUE = "<KEEP_VALUE>";
+  
   /**
    * Access stores numeric dates in days.  Java stores them in milliseconds.
    */
@@ -351,6 +357,10 @@ public class Column implements Comparable<Column> {
     }
   }
 
+  /**
+   * Returns the AutoNumberGenerator for this column if this is an autonumber
+   * column, {@code null} otherwise.
+   */
   public AutoNumberGenerator getAutoNumberGenerator() {
     return _autoNumberGenerator;
   }
@@ -872,7 +882,7 @@ public class Column implements Comparable<Column> {
         lvalPage = getLongValuePage(value.length, lvalBufferH);
         firstLvalPageNum = lvalBufferH.getPageNumber();
         firstLvalRow = (byte)Table.addDataPageRow(lvalPage, value.length,
-                                                  getFormat());
+                                                  getFormat(), 0);
         lvalPage.put(value);
         getPageChannel().writePage(lvalPage, firstLvalPageNum);
         break;
@@ -910,7 +920,7 @@ public class Column implements Comparable<Column> {
 
           // add row to this page
           byte lvalRow = (byte)Table.addDataPageRow(lvalPage, chunkLength + 4,
-                                                    getFormat());
+                                                    getFormat(), 0);
           
           // write next page info (we'll always be writing into row 0 for
           // newly created pages)
@@ -1015,6 +1025,11 @@ public class Column implements Comparable<Column> {
   public ByteBuffer write(Object obj, int remainingRowLength, ByteOrder order)
     throws IOException
   {
+    if(isRawData(obj)) {
+      // just slap it right in (not for the faint of heart!)
+      return ByteBuffer.wrap(((RawData)obj).getBytes());
+    }
+
     if(!isVariableLength()) {
       return writeFixedLengthField(obj, order);
     }
@@ -1433,6 +1448,23 @@ public class Column implements Comparable<Column> {
     return obj;
   }
 
+  /**
+   * Returns a wrapper for raw column data that can be written without
+   * understanding the data.  Useful for wrapping unparseable data for
+   * re-writing.
+   */
+  static RawData rawDataWrapper(byte[] bytes) {
+    return new RawData(bytes);
+  }
+
+  /**
+   * Returs {@code true} if the given value is "raw" column data,
+   * {@code false} otherwise.
+   */
+  static boolean isRawData(Object value) {
+    return(value instanceof RawData);
+  }
+
   /**
    * Date subclass which stashes the original date bits, in case we attempt to
    * re-write the value (will not lose precision).
@@ -1460,6 +1492,27 @@ public class Column implements Comparable<Column> {
     }
   }
 
+  /**
+   * Wrapper for raw column data which can be re-written.
+   */
+  private static class RawData
+  {
+    private final byte[] _bytes;
+
+    private RawData(byte[] bytes) {
+      _bytes = bytes;
+    }
+
+    private byte[] getBytes() {
+      return _bytes;
+    }
+
+    @Override
+    public String toString() {
+      return "RawData: " + ByteUtil.toHexString(_bytes);
+    }
+  }
+
   /**
    * Base class for the supported autonumber types.
    */
@@ -1467,10 +1520,23 @@ public class Column implements Comparable<Column> {
   {
     protected AutoNumberGenerator() {}
 
+    /**
+     * Returns the last autonumber generated by this generator.  Only valid
+     * after a call to {@link Table#addRow}, otherwise undefined.
+     */
     public abstract Object getLast();
 
+    /**
+     * Returns the next autonumber for this generator.
+     * <p>
+     * <i>Warning, calling this externally will result in this value being
+     * "lost" for the table.</i>
+     */
     public abstract Object getNext();
 
+    /**
+     * Returns the flags used when writing this column.
+     */
     public abstract int getColumnFlags();
   }
 
index 5804eeaef975006f0da2df019e209729b6d3b978..8e7f61fc834a673d067e8eea9a8ba31390f3f37c 100644 (file)
@@ -510,6 +510,15 @@ public abstract class Cursor implements Iterable<Map<String, Object>>
     _table.deleteRow(_rowState, _curPos.getRowId());
   }
 
+  /**
+   * Update the current row.
+   * @throws IllegalStateException if the current row is not valid (at
+   *         beginning or end of table), or deleted.
+   */
+  public void updateCurrentRow(Object... row) throws IOException {
+    _table.updateRow(_rowState, _curPos.getRowId(), row);
+  }
+
   /**
    * Moves to the next row in the table and returns it.
    * @return The next row in this table (Column name -> Column value), or
index 97315d6b36dbb5d29e7f791fc5706479bc73a8fc..5300fe650ae0fadfe9d97a2eff7d9c5b1355afba 100644 (file)
@@ -357,7 +357,7 @@ public abstract class Index implements Comparable<Index> {
     _rootPageNumber = tableBuffer.getInt();
     tableBuffer.getInt(); //Forward past Unknown
     _indexFlags = tableBuffer.get();
-    tableBuffer.position(tableBuffer.position() + 5);  //Forward past other stuff
+    ByteUtil.forward(tableBuffer, 5);  //Forward past other stuff
   }
 
   /**
@@ -942,11 +942,13 @@ public abstract class Index implements Comparable<Index> {
     
     ByteArrayOutputStream bout = new ByteArrayOutputStream();
     
-    // annoyingly, the values array could come from different sources, one
-    // of which will make it a different size than the other.  we need to
-    // handle both situations.
     for(ColumnDescriptor col : _columns) {
       Object value = values[col.getColumnIndex()];
+      if(Column.isRawData(value)) {
+        // ignore it, we could not parse it
+        continue;
+      }
+
       col.writeValue(value, bout);
     }
     
index 585a619a4fafa729f22c46cbd8c06cf7ddff2423..efc9a1a21ed161698e46cb07586c4da4e825ec90 100644 (file)
@@ -403,16 +403,8 @@ public class Table
     ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
     requireNonDeletedRow(rowState, rowId);
     
-    Object value = getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column,
-                                rowState);
-
-    // cache the row values in order to be able to update the index on row
-    // deletion.  note, most of the returned values are immutable, except
-    // for binary data (returned as byte[]), but binary data shouldn't be
-    // indexed anyway.
-    rowState.setRowValue(column.getColumnIndex(), value);
-
-    return value;
+    return getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column,
+                        rowState);
   }
 
   /**
@@ -451,14 +443,8 @@ public class Table
 
       if((columnNames == null) || (columnNames.contains(column.getName()))) {
         // Add the value to the row data
-        Object value = getRowColumn(rowBuffer, nullMask, column, rowState);
-        rtn.put(column.getName(), value);
-
-        // cache the row values in order to be able to update the index on row
-        // deletion.  note, most of the returned values are immutable, except
-        // for binary data (returned as byte[]), but binary data shouldn't be
-        // indexed anyway.
-        rowState.setRowValue(column.getColumnIndex(), value);
+        rtn.put(column.getName(), 
+                getRowColumn(rowBuffer, nullMask, column, rowState));
       }
     }
     return rtn;
@@ -466,6 +452,7 @@ public class Table
   
   /**
    * Reads the column data from the given row buffer.  Leaves limit unchanged.
+   * Caches the returned value in the rowState.
    */
   private static Object getRowColumn(ByteBuffer rowBuffer,
                                      NullMask nullMask,
@@ -478,9 +465,12 @@ public class Table
 
       boolean isNull = nullMask.isNull(column);
       if(column.getType() == DataType.BOOLEAN) {
-        return Boolean.valueOf(!isNull);  //Boolean values are stored in the null mask
+          // Boolean values are stored in the null mask.  see note about
+          // caching below
+        return rowState.setRowValue(column.getColumnIndex(),
+                                    Boolean.valueOf(!isNull));
       } else if(isNull) {
-        // well, that's easy!
+        // well, that's easy! (no need to update cache w/ null)
         return null;
       }
 
@@ -516,10 +506,19 @@ public class Table
       rowBuffer.position(colDataPos);
       rowBuffer.get(columnData);
 
-      // parse the column data
-      return column.read(columnData);
+      // parse the column data.  we cache the row values in order to be able
+      // to update the index on row deletion.  note, most of the returned
+      // values are immutable, except for binary data (returned as byte[]),
+      // but binary data shouldn't be indexed anyway.
+      return rowState.setRowValue(column.getColumnIndex(), 
+                                  column.read(columnData));
 
     } catch(Exception e) {
+
+      // cache "raw" row value.  see note about caching above
+      rowState.setRowValue(column.getColumnIndex(), 
+                           Column.rawDataWrapper(columnData));
+
       return rowState.handleRowError(column, columnData, e);
     }
   }
@@ -755,7 +754,7 @@ public class Table
         int curTdefPageNumber = nextTdefPageNumber;
         int writeLen = Math.min(partialTdef.remaining(), buffer.remaining());
         partialTdef.put(buffer.array(), buffer.position(), writeLen);
-        buffer.position(buffer.position() + writeLen);
+        ByteUtil.forward(buffer, writeLen);
 
         if(buffer.hasRemaining()) {
           // need a next page
@@ -1046,9 +1045,9 @@ public class Table
       tableBuffer.getInt(); //Forward past Unknown
       tableBuffer.getInt(); //Forward past alternate index number
       int indexNumber = tableBuffer.getInt();
-      tableBuffer.position(tableBuffer.position() + 11);
+      ByteUtil.forward(tableBuffer, 11);
       byte indexType = tableBuffer.get();
-      tableBuffer.position(tableBuffer.position() + 4);
+      ByteUtil.forward(tableBuffer, 4);
 
       if(i < firstRealIdx) {
         // ignore this info
@@ -1134,7 +1133,7 @@ public class Table
    */
   private void skipName(ByteBuffer buffer) {
     int nameLength = ByteUtil.getUnsignedShort(buffer);
-    buffer.position(buffer.position() + nameLength);
+    ByteUtil.forward(buffer, nameLength);
   }
   
   /**
@@ -1193,6 +1192,10 @@ public class Table
                        TempBufferHolder writeRowBufferH)
     throws IOException
   {
+    if(inRows.isEmpty()) {
+      return;
+    }
+
     // copy the input rows to a modifiable list so we can update the elements
     List<Object[]> rows = new ArrayList<Object[]>(inRows);
     ByteBuffer[] rowData = new ByteBuffer[rows.size()];
@@ -1211,70 +1214,209 @@ public class Table
 
       // write the row of data to a temporary buffer
       rowData[i] = createRow(row, getFormat().MAX_ROW_SIZE,
-                             writeRowBufferH.getPageBuffer(getPageChannel()));
+                             writeRowBufferH.getPageBuffer(getPageChannel()),
+                             false, 0);
       
       if (rowData[i].limit() > getFormat().MAX_ROW_SIZE) {
         throw new IOException("Row size " + rowData[i].limit() +
                               " is too large");
       }
     }
-    
+
     ByteBuffer dataPage = null;
     int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
-
-    // find last data page (Not bothering to check other pages for free
-    // space.)
-    UsageMap.PageCursor revPageCursor = _ownedPages.cursor();
-    revPageCursor.afterLast();
-    while(true) {
-      int tmpPageNumber = revPageCursor.getPreviousPage();
-      if(tmpPageNumber < 0) {
-        break;
-      }
-      dataPage = _addRowBufferH.setPage(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;
-        }
-        break;
-      }
-    }
-
-    if(pageNumber == PageChannel.INVALID_PAGE_NUMBER) {
-      // No data pages exist (with free space).  Create a new one.
-      dataPage = newDataPage();
-      pageNumber = _addRowBufferH.getPageNumber();
-    }
     
     for (int i = 0; i < rowData.length; i++) {
       int rowSize = rowData[i].remaining();
-      if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
 
-        // Last data page is full.  Create a new one.
-        writeDataPage(dataPage, pageNumber);
-        _freeSpacePages.removePageNumber(pageNumber);
-
-        dataPage = newDataPage();
-        pageNumber = _addRowBufferH.getPageNumber();
-      }
+      // get page with space
+      dataPage = findFreeRowSpace(rowSize, dataPage, pageNumber);
+      pageNumber = _addRowBufferH.getPageNumber();
 
       // write out the row data
-      int rowNum = addDataPageRow(dataPage, rowSize, getFormat());
+      int rowNum = addDataPageRow(dataPage, rowSize, getFormat(), 0);
       dataPage.put(rowData[i]);
 
       // update the indexes
+      RowId rowId = new RowId(pageNumber, rowNum);
       for(Index index : _indexes) {
-        index.addRow(rows.get(i), new RowId(pageNumber, rowNum));
+        index.addRow(rows.get(i), rowId);
       }
     }
+
     writeDataPage(dataPage, pageNumber);
     
     // Update tdef page
     updateTableDefinition(rows.size());
   }
 
+  /**
+   * Updates the current row to the new values.
+   * <p>
+   * Note, if this table has an auto-number column(s), the existing value(s)
+   * will be maintained, unchanged.
+   *
+   * @param row new row values for the current row.
+   */
+  public void updateCurrentRow(Object... row) throws IOException {
+     _cursor.updateCurrentRow(row);
+  }
+  
+  /**
+   * Update the row on which the given rowState is currently positioned.
+   */
+  public void updateRow(RowState rowState, RowId rowId, Object... row) 
+    throws IOException 
+  {
+    requireValidRowId(rowId);
+    
+    // ensure that the relevant row state is up-to-date
+    ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
+    int oldRowSize = rowBuffer.remaining();
+
+    requireNonDeletedRow(rowState, rowId);
+
+    // we need to make sure the row is the right length (fill with null).
+    if(row.length < _columns.size()) {
+      row = dupeRow(row, _columns.size());
+    }
+
+    // fill in any auto-numbers (we don't allow autonumber values to be
+    // modified) or "keep value" fields
+    NullMask nullMask = getRowNullMask(rowBuffer);
+    for(Column column : _columns) {
+      if(column.isAutoNumber() || 
+         (row[column.getColumnIndex()] == Column.KEEP_VALUE)) {
+        row[column.getColumnIndex()] = getRowColumn(rowBuffer, nullMask,
+                                                    column, rowState);
+      }
+    }
+
+    // generate new row bytes
+    ByteBuffer newRowData = createRow(
+        row, getFormat().MAX_ROW_SIZE,
+        _singleRowBufferH.getPageBuffer(getPageChannel()), true, oldRowSize);
+
+    if (newRowData.limit() > getFormat().MAX_ROW_SIZE) {
+      throw new IOException("Row size " + newRowData.limit() + 
+                            " is too large");
+    }
+
+    Object[] oldRowValues = (!_indexes.isEmpty() ?
+                             rowState.getRowValues() : null);
+
+    // delete old values from indexes
+    for(Index index : _indexes) {
+      index.deleteRow(oldRowValues, rowId);
+    }
+    
+    // see if we can squeeze the new row data into the existing row
+    rowBuffer.reset();
+    int rowSize = newRowData.remaining();
+
+    ByteBuffer dataPage = null;
+    int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
+
+    if(oldRowSize >= rowSize) {
+
+      // awesome, slap it in!
+      rowBuffer.put(newRowData);
+
+      // grab the page we just updated
+      dataPage = rowState.getFinalPage();
+      pageNumber = rowState.getFinalRowId().getPageNumber();
+
+    } else {
+
+      // bummer, need to find a new page for the data
+      dataPage = findFreeRowSpace(rowSize, null, 
+                                  PageChannel.INVALID_PAGE_NUMBER);
+      pageNumber = _addRowBufferH.getPageNumber();
+
+      ByteBuffer oldDataPage = rowState.getFinalPage();
+      int oldPageNumber = rowState.getFinalRowId().getPageNumber();
+      if(pageNumber == oldPageNumber) {
+        // new row is on the same page as current row, share page
+        dataPage = oldDataPage;
+      }
+
+      // write out the new row data (set the deleted flag on the new data row)
+      int rowNum = addDataPageRow(dataPage, rowSize, getFormat(),
+                                  DELETED_ROW_MASK);
+      dataPage.put(newRowData);        
+
+      // write the overflow info into the old row and clear out the remaining
+      // old data
+      rowBuffer.put((byte)rowNum);
+      ByteUtil.put3ByteInt(rowBuffer, pageNumber);
+      ByteUtil.clearRemaining(rowBuffer);
+
+      // set the overflow flag on the old row
+      int oldRowNumber = rowState.getFinalRowId().getRowNumber();
+      int oldRowIndex = getRowStartOffset(oldRowNumber, getFormat());
+      oldDataPage.putShort(oldRowIndex,
+                           (short)(oldDataPage.getShort(oldRowIndex)
+                                   | OVERFLOW_ROW_MASK));
+      if(pageNumber != oldPageNumber) {
+        writeDataPage(oldDataPage, oldPageNumber);
+      }
+    }
+
+    // update the indexes
+    for(Index index : _indexes) {
+      index.addRow(row, rowId);
+    }
+
+    writeDataPage(dataPage, pageNumber);
+
+    updateTableDefinition(0);
+  }
+   
+  private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage, 
+                                      int pageNumber)
+    throws IOException
+  {
+    if(dataPage == null) {
+
+      // find last data page (Not bothering to check other pages for free
+      // space.)
+      UsageMap.PageCursor revPageCursor = _ownedPages.cursor();
+      revPageCursor.afterLast();
+      while(true) {
+        int tmpPageNumber = revPageCursor.getPreviousPage();
+        if(tmpPageNumber < 0) {
+          break;
+        }
+        dataPage = _addRowBufferH.setPage(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;
+          }
+          break;
+        }
+      }
+
+      if(pageNumber == PageChannel.INVALID_PAGE_NUMBER) {
+        // No data pages exist (with free space).  Create a new one.
+        return newDataPage();
+      }
+    
+    }
+
+    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.
    */
@@ -1290,9 +1432,7 @@ public class Table
     tdefPage.putInt(getFormat().OFFSET_NEXT_AUTO_NUMBER, _lastLongAutoNumber);
 
     // write any index changes
-    Iterator<Index> indIter = _indexes.iterator();
-    for (int i = 0; i < _indexes.size(); i++) {
-      Index index = indIter.next();
+    for (Index index : _indexes) {
       // write the unique entry count for the index to the table definition
       // page
       tdefPage.putInt(index.getUniqueEntryCountOffset(),
@@ -1338,7 +1478,8 @@ public class Table
    * @param buffer buffer to which to write the row data
    * @return the given buffer, filled with the row data
    */
-  ByteBuffer createRow(Object[] rowArray, int maxRowSize, ByteBuffer buffer)
+  ByteBuffer createRow(Object[] rowArray, int maxRowSize, ByteBuffer buffer,
+                       boolean isUpdate, int minRowSize)
     throws IOException
   {
     buffer.putShort(_maxColumnCount);
@@ -1362,7 +1503,7 @@ public class Table
         
         } else {
 
-          if(col.isAutoNumber()) {
+          if(col.isAutoNumber() && !isUpdate) {
             
             // ignore given row value, use next autonumber
             rowValue = col.getAutoNumberGenerator().getNext();
@@ -1400,7 +1541,8 @@ public class Table
       // account for already written space
       maxRowSize -= buffer.position();
       // now, account for trailer space
-      maxRowSize -= (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
+      int trailerSize = (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
+      maxRowSize -= trailerSize;
 
       // for each non-null long value column we need to reserve a small
       // amount of space so that we don't end up running out of row space
@@ -1443,13 +1585,25 @@ public class Table
         varColumnOffsets[varColumnOffsetsIndex++] = (short) buffer.position();
       }
 
-      buffer.putShort((short) buffer.position()); //EOD marker
+      // record where we stopped writing
+      int eod = buffer.position();
+
+      // insert padding if necessary
+      padRowBuffer(buffer, minRowSize, trailerSize);
+
+      buffer.putShort((short) eod); //EOD marker
+
       //Now write out variable length offsets
       //Offsets are stored in reverse order
       for (int i = _maxVarColumnCount - 1; i >= 0; i--) {
         buffer.putShort(varColumnOffsets[i]);
       }
       buffer.putShort(_maxVarColumnCount);  //Number of var length columns
+
+    } else {
+
+      // insert padding for row w/ no var cols
+      padRowBuffer(buffer, minRowSize, nullMask.byteSize());
     }
 
     nullMask.write(buffer);  //Null mask
@@ -1460,6 +1614,17 @@ public class Table
     return buffer;
   }
 
+  private void padRowBuffer(ByteBuffer buffer, int minRowSize, int trailerSize)
+  {
+    int pos = buffer.position();
+    if((pos + trailerSize) < minRowSize) {
+      // pad the row to get to the min byte size
+      int padSize = minRowSize - (pos + trailerSize);
+      ByteUtil.clearRange(buffer, pos, pos + padSize);
+      ByteUtil.forward(buffer, padSize);
+    }
+  }
+
   public int getRowCount() {
     return _rowCount;
   }
@@ -1549,7 +1714,8 @@ public class Table
    */
   public static int addDataPageRow(ByteBuffer dataPage,
                                    int rowSize,
-                                   JetFormat format)
+                                   JetFormat format, 
+                                   int rowFlags)
   {
     int rowSpaceUsage = getRowSpaceUsage(rowSize, format);
     
@@ -1568,7 +1734,8 @@ public class Table
     rowLocation -= rowSize;
 
     // write row position
-    dataPage.putShort(getRowStartOffset(rowCount, format), rowLocation);
+    dataPage.putShort(getRowStartOffset(rowCount, format), 
+                      (short)(rowLocation | rowFlags));
 
     // set position for row data
     dataPage.position(rowLocation);
@@ -1692,7 +1859,7 @@ public class Table
     System.arraycopy(row, 0, copy, 0, row.length);
     return copy;
   }
-  
+
   /** various statuses for the row data */
   private enum RowStatus {
     INIT, INVALID_PAGE, INVALID_ROW, VALID, DELETED, NORMAL, OVERFLOW;
@@ -1702,7 +1869,7 @@ public class Table
   private enum RowStateStatus {
     INIT, AT_HEADER, AT_FINAL;
   }
-  
+
   /**
    * Maintains the state of reading a row of data.
    */
@@ -1835,9 +2002,10 @@ public class Table
       return(_status.ordinal() >= RowStateStatus.AT_FINAL.ordinal());
     }
 
-    private void setRowValue(int idx, Object value) {
+    private Object setRowValue(int idx, Object value) {
       _haveRowValues = true;
       _rowValues[idx] = value;
+      return value;
     }
     
     public Object[] getRowValues() {
index 9055ed8f2fa4dbbbea1f07e1b73951b2fde768f8..fac88f33bcaa92a2d9a7ff07866349ceb0093531 100644 (file)
@@ -184,7 +184,7 @@ public class DatabaseTest extends TestCase {
   
   public void testGetNextRow() throws Exception {
     Database db = open();
-    assertEquals(3, db.getTableNames().size());
+    assertEquals(4, db.getTableNames().size());
     Table table = db.getTable("Table1");
     
     Map<String, Object> row = table.getNextRow();
@@ -913,6 +913,65 @@ public class DatabaseTest extends TestCase {
 
     db.close();
   }
+
+  public void testUpdateRow() throws Exception
+  {
+    Database db = create();
+
+    Table t = new TableBuilder("test")
+      .addColumn(new ColumnBuilder("name", DataType.TEXT).toColumn())
+      .addColumn(new ColumnBuilder("id", DataType.LONG)
+                 .setAutoNumber(true).toColumn())
+      .addColumn(new ColumnBuilder("data", DataType.TEXT)
+                 .setLength(JetFormat.TEXT_FIELD_MAX_LENGTH).toColumn())
+      .toTable(db);
+
+    for(int i = 0; i < 10; ++i) {
+      t.addRow("row" + i, Column.AUTO_NUMBER, "initial data");
+    }
+
+    t.reset();
+    t.getNextRow();
+    Map<String,Object> row = t.getNextRow();
+
+    assertEquals(createExpectedRow("name", "row1", 
+                                   "id", 2,
+                                   "data", "initial data"),
+                 row);
+
+    t.updateCurrentRow(Column.KEEP_VALUE, Column.AUTO_NUMBER, "new data");
+
+    t.getNextRow();
+    t.getNextRow();
+    row = t.getNextRow();
+
+    assertEquals(createExpectedRow("name", "row4", 
+                                   "id", 5,
+                                   "data", "initial data"),
+                 row);
+
+    t.updateCurrentRow(Column.KEEP_VALUE, Column.AUTO_NUMBER, "a larger amount of new data");
+
+    t.reset();
+    t.getNextRow();
+    row = t.getNextRow();
+
+    assertEquals(createExpectedRow("name", "row1", 
+                                   "id", 2,
+                                   "data", "new data"),
+                 row);
+
+    t.getNextRow();
+    t.getNextRow();
+    row = t.getNextRow();
+
+    assertEquals(createExpectedRow("name", "row4", 
+                                   "id", 5,
+                                   "data", "a larger amount of new data"),
+                 row);
+
+    db.close();
+  }
     
   static Object[] createTestRow(String col1Val) {
     return new Object[] {col1Val, "R", "McCune", 1234, (byte) 0xad, 555.66d,
index 31908469ab0e29664561830a344a53581130b6bd..54c819adefa36539c57ce39b9036a848ee180071 100644 (file)
@@ -119,7 +119,7 @@ public class TableTest extends TestCase {
   {
     return _testTable.createRow(
         row, _testTable.getFormat().MAX_ROW_SIZE,
-        _testTable.getPageChannel().createPageBuffer());
+        _testTable.getPageChannel().createPageBuffer(), false, 0);
   }
 
   private ByteBuffer[] encodeColumns(Object... row)