Ignite-db - Fixes.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MetadataStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MetadataStorage.java
index 047c5f1..e0eccca 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MetadataStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/MetadataStorage.java
@@ -44,7 +44,7 @@
     }
 
     /** {@inheritDoc} */
-    @Override public IgniteBiTuple<FullPageId, Boolean> getOrAllocateForIndex(int cacheId, String idxName)
+    @Override public synchronized IgniteBiTuple<FullPageId, Boolean> getOrAllocateForIndex(int cacheId, String idxName)
         throws IgniteCheckedException {
         byte[] idxNameBytes = idxName.getBytes(StandardCharsets.UTF_8);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/tree/io/DataPageIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/tree/io/DataPageIO.java
index 54df845..c3c37f2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/tree/io/DataPageIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/tree/io/DataPageIO.java
@@ -239,6 +239,7 @@
     public String printPageLayout(ByteBuffer buf) {
         int directCnt = getDirectCount(buf);
         int indirectCnt = getIndirectCount(buf);
+        int free = getFreeSpace(buf);
 
         boolean valid = directCnt >= indirectCnt;
 
@@ -246,6 +247,8 @@
 
         b.appendHex(PageIO.getPageId(buf)).a(" [");
 
+        int entriesSize = 0;
+
         for (int i = 0; i < directCnt; i++) {
             if (i != 0)
                 b.a(", ");
@@ -255,6 +258,8 @@
             if (item < ITEMS_OFF || item >= buf.capacity())
                 valid = false;
 
+            entriesSize += getEntrySize(buf, item, false);
+
             b.a(item);
         }
 
@@ -286,7 +291,17 @@
             b.a(itemId).a('^').a(directIdx);
         }
 
-        b.a("]");
+        b.a("][free=").a(free);
+
+        int actualFree = buf.capacity() - ITEMS_OFF - (entriesSize + (directCnt + indirectCnt) * ITEM_SIZE);
+
+        if (free != actualFree) {
+            b.a(", actualFree=").a(actualFree);
+
+            valid = false;
+        }
+        else
+            b.a("]");
 
         assert valid : b.toString();
 
@@ -457,8 +472,11 @@
     public void removeRow(ByteBuffer buf, int itemId) {
         assert check(itemId) : itemId;
 
-        int directCnt = getDirectCount(buf);
-        int indirectCnt = getIndirectCount(buf);
+        // Record original counts to calculate delta in free space in the end of remove.
+        final int directCnt = getDirectCount(buf);
+        final int indirectCnt = getIndirectCount(buf);
+
+        int curIndirectCnt = indirectCnt;
 
         assert directCnt > 0 : directCnt; // Direct count always represents overall number of live items.
 
@@ -496,7 +514,7 @@
                 if (dropLast)
                     moveItems(buf, directCnt, indirectCnt, -1);
                 else
-                    indirectCnt++;
+                    curIndirectCnt++;
             }
             else {
                 if (dropLast)
@@ -505,10 +523,10 @@
                 moveItems(buf, indirectId + 1, directCnt + indirectCnt - indirectId - 1, dropLast ? -2 : -1);
 
                 if (dropLast)
-                    indirectCnt--;
+                    curIndirectCnt--;
             }
 
-            setIndirectCount(buf, indirectCnt);
+            setIndirectCount(buf, curIndirectCnt);
             setDirectCount(buf, directCnt - 1);
 
             assert getIndirectCount(buf) <= getDirectCount(buf);
@@ -599,8 +617,8 @@
         assert check(itemId): itemId;
         assert getIndirectCount(buf) <= getDirectCount(buf);
 
-        // Update free space. If number of direct items did not change, then we were able to reuse item slot.
-        setFreeSpace(buf, getFreeSpace(buf) - entrySizeWithItem + (getDirectCount(buf) == directCnt ? ITEM_SIZE : 0));
+        // Update free space. If number of indirect items changed, then we were able to reuse an item slot.
+        setFreeSpace(buf, getFreeSpace(buf) - entrySizeWithItem + (getIndirectCount(buf) != indirectCnt ? ITEM_SIZE : 0));
 
         assert getFreeSpace(buf) >= 0;
 
@@ -692,6 +710,28 @@
     }
 
     /**
+     * Full-scan free space calculation procedure.
+     *
+     * @param buf Buffer to scan.
+     * @return Actual free space in the buffer.
+     */
+    private int actualFreeSpace(ByteBuffer buf) {
+        int directCnt = getDirectCount(buf);
+
+        int entriesSize = 0;
+
+        for (int i = 0; i < directCnt; i++) {
+            int off = toOffset(getItem(buf, i));
+
+            int entrySize = getEntrySize(buf, off, false);
+
+            entriesSize += entrySize;
+        }
+
+        return buf.capacity() - ITEMS_OFF - entriesSize - (directCnt + getIndirectCount(buf)) * ITEM_SIZE;
+    }
+
+    /**
      * @param buf Buffer.
      * @param off Offset.
      * @param cnt Count.
@@ -700,7 +740,8 @@
     private static void moveBytes(ByteBuffer buf, int off, int cnt, int step) {
         assert step != 0: step;
         assert off + step >= 0;
-        assert off + step + cnt < buf.capacity();
+        assert off + step + cnt <= buf.capacity() : "[off=" + off + ", step=" + step + ", cnt=" + cnt +
+            ", cap=" + buf.capacity() + ']';
 
         PageHandler.copyMemory(buf, buf, off, off + step, cnt);
     }
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2RowStore.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2RowStore.java
index cd0cc4b..54a6c51 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2RowStore.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2RowStore.java
@@ -66,11 +66,6 @@
             ByteBuffer buf = page.getForRead();
 
             try {
-                GridH2Row existing = rowDesc.cachedRow(link);
-
-                if (existing != null)
-                    return existing;
-
                 DataPageIO io = DataPageIO.VERSIONS.forPage(buf);
 
                 int dataOff = io.getDataOffset(buf, dwordsOffset(link));
@@ -103,8 +98,6 @@
 
                 assert row.ver != null;
 
-                rowDesc.cache(row);
-
                 return row;
             }
             finally {
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java
index e149b9d..ad56b54 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Table.java
@@ -446,7 +446,7 @@
                     if (old2 != null) { // Row was replaced in index.
                         if (!eq(pk, old2, old))
                             throw new IllegalStateException("Row conflict should never happen, unique indexes are " +
-                                "not supported.");
+                                "not supported [idx=" + idx + ", old=" + old + ", old2=" + old2 + ']');
                     }
                     else if (old != null) // Row was not replaced, need to remove manually.
                         idx.remove(old);
@@ -464,14 +464,6 @@
                 }
 
                 if (old != null) {
-                    if (rowStore != null) {
-                        assert old.link != 0;
-
-                        rowStore.removeRow(old.link);
-                    }
-
-                    size.decrement();
-
                     // Remove row from all indexes.
                     // Start from 2 because 0 - Scan (don't need to update), 1 - PK (already updated).
                     for (int i = 2, len = idxs.size(); i < len; i++) {
@@ -479,6 +471,14 @@
 
                         assert eq(pk, res, old): "\n" + old + "\n" + res + "\n" + i + " -> " + index(i).getName();
                     }
+
+                    if (rowStore != null) {
+                        assert old.link != 0;
+
+                        rowStore.removeRow(old.link);
+                    }
+
+                    size.decrement();
                 }
                 else
                     return false;
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java
index 0a6c1c9..7536075 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/database/IgniteDbSingleNodePutGetSelfTest.java
@@ -11,9 +11,12 @@
 
 import java.io.Serializable;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.IgniteDataStreamer;
 import org.apache.ignite.cache.CacheAtomicityMode;
@@ -768,6 +771,51 @@
     /**
      * @throws Exception if failed.
      */
+    public void testIndexOverwrite() throws Exception {
+        IgniteEx ig = grid(0);
+
+        final IgniteCache<Integer, DbValue> cache = ig.cache(null);
+
+        GridCacheAdapter<Object, Object> internalCache = ig.context().cache().internalCache("non-primitive");
+
+        X.println("Put start");
+
+        int cnt = 10_000;
+
+        for (int a = 0; a < cnt; a++) {
+            DbValue v0 = new DbValue(a, "test-value-" + a, a);
+
+            DbKey k0 = new DbKey(a);
+
+            cache.put(a, v0);
+
+            checkEmpty(internalCache, k0);
+        }
+
+        info("Update start");
+
+        for (int k = 0; k < 4000; k++) {
+            int batchSize = 20;
+
+            LinkedHashMap<Integer, DbValue> batch = new LinkedHashMap<>();
+
+            for (int i = 0; i < batchSize; i++) {
+                int a = ThreadLocalRandom.current().nextInt(cnt);
+
+                DbValue v0 = new DbValue(a, "test-value-" + a, a);
+
+                batch.put(a, v0);
+            }
+
+            cache.putAll(batch);
+
+            cache.remove(ThreadLocalRandom.current().nextInt(cnt));
+        }
+    }
+
+    /**
+     * @throws Exception if failed.
+     */
     public void testObjectKey() throws Exception {
         IgniteEx ig = grid(0);
 
@@ -859,7 +907,7 @@
         private int iVal;
 
         /** */
-        @QuerySqlField
+        @QuerySqlField(index = true)
         private String sVal;
 
         /** */