public void add(Session session, Row row) {
   boolean retry = false;
   if (mainIndexColumn != -1) {
     row.setKey(row.getValue(mainIndexColumn).getLong());
   } else {
     if (row.getKey() == 0) {
       row.setKey((int) ++lastKey);
       retry = true;
     }
   }
   if (tableData.getContainsLargeObject()) {
     for (int i = 0, len = row.getColumnCount(); i < len; i++) {
       Value v = row.getValue(i);
       Value v2 = v.link(database, getId());
       if (v2.isLinked()) {
         session.unlinkAtCommitStop(v2);
       }
       if (v != v2) {
         row.setValue(i, v2);
       }
     }
   }
   // when using auto-generated values, it's possible that multiple
   // tries are required (specially if there was originally a primary key)
   if (trace.isDebugEnabled()) {
     trace.debug("{0} add {1}", getName(), row);
   }
   long add = 0;
   while (true) {
     try {
       addTry(session, row);
       break;
     } catch (DbException e) {
       if (e != fastDuplicateKeyException) {
         throw e;
       }
       if (!retry) {
         throw getNewDuplicateKeyException();
       }
       if (add == 0) {
         // in the first re-try add a small random number,
         // to avoid collisions after a re-start
         row.setKey((long) (row.getKey() + Math.random() * 10000));
       } else {
         row.setKey(row.getKey() + add);
       }
       add++;
     } finally {
       store.incrementChangeCount();
     }
   }
   lastKey = Math.max(lastKey, row.getKey());
 }
 public void remove(Session session, Row row) {
   if (tableData.getContainsLargeObject()) {
     for (int i = 0, len = row.getColumnCount(); i < len; i++) {
       Value v = row.getValue(i);
       if (v.isLinked()) {
         session.unlinkAtCommit(v);
       }
     }
   }
   if (trace.isDebugEnabled()) {
     trace.debug("{0} remove {1}", getName(), row);
   }
   if (rowCount == 1) {
     removeAllRows();
   } else {
     try {
       long key = row.getKey();
       PageData root = getPage(rootPageId, 0);
       root.remove(key);
       invalidateRowCount();
       rowCount--;
     } finally {
       store.incrementChangeCount();
     }
   }
   if (multiVersion) {
     // if storage is null, the delete flag is not yet set
     row.setDeleted(true);
     if (delta == null) {
       delta = New.hashSet();
     }
     boolean wasAdded = delta.remove(row);
     if (!wasAdded) {
       delta.add(row);
     }
     incrementRowCount(session.getId(), -1);
   }
   store.logAddOrRemoveRow(session, tableData.getId(), row, false);
 }
 private void addTry(Session session, Row row) {
   while (true) {
     PageData root = getPage(rootPageId, 0);
     int splitPoint = root.addRowTry(row);
     if (splitPoint == -1) {
       break;
     }
     if (trace.isDebugEnabled()) {
       trace.debug("{0} split", this);
     }
     long pivot = splitPoint == 0 ? row.getKey() : root.getKey(splitPoint - 1);
     PageData page1 = root;
     PageData page2 = root.split(splitPoint);
     int id = store.allocatePage();
     page1.setPageId(id);
     page1.setParentPageId(rootPageId);
     page2.setParentPageId(rootPageId);
     PageDataNode newRoot = PageDataNode.create(this, rootPageId, PageData.ROOT);
     newRoot.init(page1, pivot, page2);
     store.update(page1);
     store.update(page2);
     store.update(newRoot);
     root = newRoot;
   }
   row.setDeleted(false);
   if (multiVersion) {
     if (delta == null) {
       delta = New.hashSet();
     }
     boolean wasDeleted = delta.remove(row);
     if (!wasDeleted) {
       delta.add(row);
     }
     incrementRowCount(session.getId(), 1);
   }
   invalidateRowCount();
   rowCount++;
   store.logAddOrRemoveRow(session, tableData.getId(), row, true);
 }