ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sboi...@apache.org
Subject [48/50] [abbrv] ignite git commit: ignite-db-x Made H2RowStore abstract to be reused from core module.
Date Tue, 19 Apr 2016 12:59:05 GMT
ignite-db-x Made H2RowStore abstract to be reused from core module.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/2f7a22c1
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/2f7a22c1
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/2f7a22c1

Branch: refs/heads/ignite-db-x-10884
Commit: 2f7a22c11abc6c2bcba34ef9c902c9f0c607c85c
Parents: 4282d80
Author: sboikov <sboikov@gridgain.com>
Authored: Tue Apr 19 12:03:13 2016 +0300
Committer: sboikov <sboikov@gridgain.com>
Committed: Tue Apr 19 12:03:13 2016 +0300

----------------------------------------------------------------------
 .../processors/cache/database/CacheDataRow.java |  38 +++
 .../processors/cache/database/RowStore.java     | 273 +++++++++++++++++++
 .../cache/database/freelist/FreeItem.java       |  90 ++++++
 .../cache/database/freelist/FreeList.java       | 196 +++++++++++++
 .../cache/database/freelist/FreeTree.java       |  96 +++++++
 .../cache/database/freelist/io/FreeIO.java      |  39 +++
 .../cache/database/freelist/io/FreeInnerIO.java |  67 +++++
 .../cache/database/freelist/io/FreeLeafIO.java  |  70 +++++
 .../query/h2/database/H2RowStore.java           | 234 ++--------------
 .../query/h2/database/freelist/FreeItem.java    |  90 ------
 .../query/h2/database/freelist/FreeList.java    | 196 -------------
 .../query/h2/database/freelist/FreeTree.java    |  96 -------
 .../query/h2/database/freelist/io/FreeIO.java   |  39 ---
 .../h2/database/freelist/io/FreeInnerIO.java    |  67 -----
 .../h2/database/freelist/io/FreeLeafIO.java     |  70 -----
 .../processors/query/h2/opt/GridH2Row.java      |  33 ++-
 16 files changed, 928 insertions(+), 766 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java
new file mode 100644
index 0000000..a4cbd32
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/CacheDataRow.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.database;
+
+import org.apache.ignite.internal.processors.cache.CacheObject;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+
+/**
+ *
+ */
+public interface CacheDataRow {
+    public CacheObject key();
+
+    public CacheObject value();
+
+    public GridCacheVersion version();
+
+    public int partition();
+
+    public long link();
+
+    public void link(long link);
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/RowStore.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/RowStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/RowStore.java
new file mode 100644
index 0000000..50bf490
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/RowStore.java
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.database;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.pagemem.FullPageId;
+import org.apache.ignite.internal.pagemem.Page;
+import org.apache.ignite.internal.pagemem.PageIdAllocator;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.CacheObject;
+import org.apache.ignite.internal.processors.cache.CacheObjectContext;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.database.freelist.FreeList;
+import org.apache.ignite.internal.processors.cache.database.tree.io.DataPageIO;
+import org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+
+import static org.apache.ignite.internal.pagemem.PageIdUtils.dwordsOffset;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.linkFromDwordOffset;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.pageId;
+import static org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler.writePage;
+
+/**
+ * Data store for H2 rows.
+ */
+public class RowStore<T extends CacheDataRow> {
+    /** */
+    private final FreeList freeList;
+
+    /** */
+    private final PageMemory pageMem;
+
+    /** */
+    private final GridCacheContext<?,?> cctx;
+
+    /** */
+    private final CacheObjectContext coctx;
+
+    /** */
+    private volatile long lastDataPageId;
+
+    /** */
+    private final RowFactory<T> rowFactory;
+
+    /** */
+    private final PageHandler<CacheDataRow> writeRow = new PageHandler<CacheDataRow>() {
+        @Override public int run(Page page, ByteBuffer buf, CacheDataRow row, int ignore) throws IgniteCheckedException {
+            int entrySize = DataPageIO.getEntrySize(coctx, row.key(), row.value());
+
+            DataPageIO io = DataPageIO.VERSIONS.forPage(buf);
+
+            int idx = io.addRow(coctx, buf, row.key(), row.value(), row.version(), entrySize);
+
+            if (idx != -1) {
+                row.link(linkFromDwordOffset(page.id(), idx));
+
+                assert row.link() != 0;
+            }
+
+            return idx;
+        }
+    };
+
+    /** */
+    private final PageHandler<Void> rmvRow = new PageHandler<Void>() {
+        @Override public int run(Page page, ByteBuffer buf, Void ignore, int itemId) throws IgniteCheckedException {
+            DataPageIO io = DataPageIO.VERSIONS.forPage(buf);
+
+            assert DataPageIO.check(itemId): itemId;
+
+            io.removeRow(buf, (byte)itemId);
+
+            return 0;
+        }
+    };
+
+    /**
+     * @param cctx Cache context.
+     */
+    public RowStore(GridCacheContext<?,?> cctx, RowFactory<T> rowFactory, FreeList freeList) {
+        assert rowFactory != null;
+        assert cctx != null;
+
+        this.cctx = cctx;
+        this.freeList = freeList;
+        this.rowFactory = rowFactory;
+
+        coctx = cctx.cacheObjectContext();
+        pageMem = cctx.shared().database().pageMemory();
+    }
+
+    /**
+     * @param pageId Page ID.
+     * @return Page.
+     * @throws IgniteCheckedException If failed.
+     */
+    private Page page(long pageId) throws IgniteCheckedException {
+        return pageMem.page(new FullPageId(pageId, cctx.cacheId()));
+    }
+
+    /**
+     * @param part Partition.
+     * @return Allocated page.
+     * @throws IgniteCheckedException if failed.
+     */
+    private Page allocatePage(int part) throws IgniteCheckedException {
+        FullPageId fullPageId = pageMem.allocatePage(cctx.cacheId(), part, PageIdAllocator.FLAG_DATA);
+
+        return pageMem.page(fullPageId);
+    }
+
+    /**
+     * @param link Row link.
+     * @throws IgniteCheckedException If failed.
+     */
+    public void removeRow(long link) throws IgniteCheckedException {
+        assert link != 0;
+
+        try (Page page = page(pageId(link))) {
+            writePage(page, rmvRow, null, dwordsOffset(link), 0);
+        }
+    }
+
+    /**
+     * !!! This method must be invoked in read or write lock of referring index page. It is needed to
+     * !!! make sure that row at this link will be invisible, when the link will be removed from
+     * !!! from all the index pages, so that row can be safely erased from the data page.
+     *
+     * @param link Link.
+     * @return Row.
+     * @throws IgniteCheckedException If failed.
+     */
+    public T getRow(long link) throws IgniteCheckedException {
+        try (Page page = page(pageId(link))) {
+            ByteBuffer buf = page.getForRead();
+
+            try {
+                T existing = rowFactory.cachedRow(link);
+
+                if (existing != null)
+                    return existing;
+
+                DataPageIO io = DataPageIO.VERSIONS.forPage(buf);
+
+                int dataOff = io.getDataOffset(buf, dwordsOffset(link));
+
+                buf.position(dataOff);
+
+                // Skip entry size.
+                buf.getShort();
+
+                CacheObject key = coctx.processor().toCacheObject(coctx, buf);
+                CacheObject val = coctx.processor().toCacheObject(coctx, buf);
+
+                int topVer = buf.getInt();
+                int nodeOrderDrId = buf.getInt();
+                long globalTime = buf.getLong();
+                long order = buf.getLong();
+
+                GridCacheVersion ver = new GridCacheVersion(topVer, nodeOrderDrId, globalTime, order);
+
+                T row;
+
+                try {
+                    row = rowFactory.createRow(key, val, ver, link, 0);
+                }
+                catch (IgniteCheckedException e) {
+                    throw new IgniteException(e);
+                }
+
+                assert row.version() != null : row;
+
+                return row;
+            }
+            finally {
+                page.releaseRead();
+            }
+        }
+    }
+
+    /**
+     * @param expLastDataPageId Expected last data page ID.
+     * @return Next data page ID.
+     */
+    private synchronized long nextDataPage(long expLastDataPageId, int partId) throws IgniteCheckedException {
+        if (expLastDataPageId != lastDataPageId)
+            return lastDataPageId;
+
+        long pageId;
+
+        try (Page page = allocatePage(partId)) {
+            pageId = page.id();
+
+            ByteBuffer buf = page.getForInitialWrite();
+
+            DataPageIO.VERSIONS.latest().initNewPage(buf, page.id());
+        }
+
+        return lastDataPageId = pageId;
+    }
+
+    /**
+     * @param row Row.
+     */
+    public void addRow(CacheDataRow row) throws IgniteCheckedException {
+        if (freeList == null)
+            writeRowData0(row);
+        else
+            freeList.writeRowData(row);
+    }
+
+    /**
+     * @param row Row.
+     * @throws IgniteCheckedException If failed.
+     */
+    private void writeRowData0(CacheDataRow row) throws IgniteCheckedException {
+        assert row.link() == 0;
+
+        while (row.link() == 0) {
+            long pageId = lastDataPageId;
+
+            if (pageId == 0)
+                pageId = nextDataPage(0, row.partition());
+
+            try (Page page = page(pageId)) {
+                if (writePage(page, writeRow, row, -1, -1) >= 0)
+                    return; // Successful write.
+            }
+
+            nextDataPage(pageId, row.partition());
+        }
+    }
+
+    /**
+     *
+     */
+    protected interface RowFactory<T> {
+        /**
+         * @param link Link.
+         * @return Row.
+         */
+        T cachedRow(long link);
+
+        /**
+         * @param key Key.
+         * @param val Value.
+         * @param ver Version.
+         * @param link Link.
+         * @param expirationTime Expiration time.
+         * @return Row.
+         * @throws IgniteCheckedException If failed.
+         */
+        T createRow(CacheObject key, CacheObject val, GridCacheVersion ver, long link, long expirationTime)
+            throws IgniteCheckedException;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeItem.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeItem.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeItem.java
new file mode 100644
index 0000000..7de30c7
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeItem.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.database.freelist;
+
+import org.apache.ignite.internal.pagemem.FullPageId;
+
+/**
+ * Free list item.
+ */
+public class FreeItem extends FullPageId {
+    /** */
+    private short freeSpace;
+
+    /** */
+    private short dispersion;
+
+    /**
+     * @param freeSpace Free space.
+     * @param dispersion Dispersion.
+     * @param pageId  Page ID.
+     * @param cacheId Cache ID.
+     */
+    public FreeItem(short freeSpace, short dispersion, long pageId, int cacheId) {
+        super(pageId, cacheId);
+
+        assert freeSpace >= 0: freeSpace;
+
+        this.freeSpace = freeSpace;
+        this.dispersion = dispersion;
+    }
+
+    /**
+     * @param freeSpace Free space.
+     * @param dispersion Dispersion.
+     * @return Dispersed free space.
+     */
+    public static int disperse(int freeSpace, int dispersion) {
+        return (freeSpace << 16) | dispersion;
+    }
+
+    /**
+     * @return Dispersed free space.
+     */
+    public int dispersedFreeSpace() {
+        return disperse(freeSpace, dispersion);
+    }
+
+    /**
+     * @return Free space in the page.
+     */
+    public short freeSpace() {
+        return freeSpace;
+    }
+
+    /**
+     * @param freeSpace Free space.
+     */
+    public void freeSpace(short freeSpace) {
+        this.freeSpace = freeSpace;
+    }
+
+    /**
+     * @return Dispersion.
+     */
+    public short dispersion() {
+        return dispersion;
+    }
+
+    /**
+     * @param dispersion Dispersion.
+     */
+    public void dispersion(short dispersion) {
+        this.dispersion = dispersion;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeList.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeList.java
new file mode 100644
index 0000000..0f94bb1
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeList.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.database.freelist;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.pagemem.FullPageId;
+import org.apache.ignite.internal.pagemem.Page;
+import org.apache.ignite.internal.pagemem.PageIdAllocator;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.database.CacheDataRow;
+import org.apache.ignite.internal.processors.cache.database.tree.io.DataPageIO;
+import org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler;
+import org.apache.ignite.internal.util.future.GridFutureAdapter;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.jsr166.ConcurrentHashMap8;
+
+import static org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler.writePage;
+
+/**
+ * Free data page list.
+ */
+public class FreeList {
+    /** */
+    private final GridCacheContext<?,?> cctx;
+
+    /** */
+    private final PageMemory pageMem;
+
+    /** */
+    private final ConcurrentHashMap8<Integer,GridFutureAdapter<FreeTree>> trees = new ConcurrentHashMap8<>();
+
+    /** */
+    private final PageHandler<CacheDataRow> writeRow = new PageHandler<CacheDataRow>() {
+        @Override public int run(Page page, ByteBuffer buf, CacheDataRow row, int entrySize)
+            throws IgniteCheckedException {
+            DataPageIO io = DataPageIO.VERSIONS.forPage(buf);
+
+            int idx = io.addRow(cctx.cacheObjectContext(), buf, row.key(), row.value(), row.version(), entrySize);
+
+            assert idx >= 0;
+
+            return io.getFreeSpace(buf);
+        }
+    };
+
+    /**
+     * @param cctx Cache context.
+     */
+    public FreeList(GridCacheContext<?,?> cctx) {
+        assert cctx != null;
+
+        this.cctx = cctx;
+
+        pageMem = cctx.shared().database().pageMemory();
+
+        assert pageMem != null;
+    }
+
+    /**
+     * @param tree Tree.
+     * @param neededSpace Needed free space.
+     * @return Free item or {@code null} if it was impossible to find one.
+     * @throws IgniteCheckedException If failed.
+     */
+    private FreeItem take(FreeTree tree, short neededSpace) throws IgniteCheckedException {
+        assert neededSpace > 0 && neededSpace < Short.MAX_VALUE: neededSpace;
+
+        FreeItem res = tree.removeCeil(new FreeItem(neededSpace, dispersion(), 0, 0));
+
+        assert res == null || (res.pageId() != 0 && res.cacheId() == cctx.cacheId()): res;
+
+        return res;
+    }
+
+    /**
+     * @return Random dispersion value.
+     */
+    private static short dispersion() {
+        return (short)ThreadLocalRandom.current().nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
+    }
+
+    /**
+     * @param part Partition.
+     * @return Tree.
+     * @throws IgniteCheckedException If failed.
+     */
+    private FreeTree tree(Integer part) throws IgniteCheckedException {
+        assert part >= 0 && part < Short.MAX_VALUE: part;
+
+        GridFutureAdapter<FreeTree> fut = trees.get(part);
+
+        if (fut == null) {
+            fut = new GridFutureAdapter<>();
+
+            if (trees.putIfAbsent(part, fut) != null)
+                fut = trees.get(part);
+            else {
+                // Index name will be the same across restarts.
+                String idxName = part + "$$" + cctx.cacheId() + "_free";
+
+                IgniteBiTuple<FullPageId,Boolean> t = cctx.shared().database().meta()
+                    .getOrAllocateForIndex(cctx.cacheId(), idxName);
+
+                fut.onDone(new FreeTree(cctx.cacheId(), pageMem, t.get1(), t.get2()));
+            }
+        }
+
+        return fut.get();
+    }
+
+    /**
+     * @param row Row.
+     * @throws IgniteCheckedException If failed.
+     */
+    public void writeRowData(CacheDataRow row) throws IgniteCheckedException {
+        // assert row.link == 0;
+
+        int entrySize = DataPageIO.getEntrySize(cctx.cacheObjectContext(), row.key(), row.value());
+
+        assert entrySize > 0 && entrySize < Short.MAX_VALUE: entrySize;
+
+        FreeTree tree = tree(row.partition());
+        FreeItem item = take(tree, (short)entrySize);
+
+        Page page = null;
+        int freeSpace = -1;
+
+        try {
+            if (item == null) {
+                DataPageIO io = DataPageIO.VERSIONS.latest();
+
+                page = allocatePage(row.partition());
+
+                ByteBuffer buf = page.getForInitialWrite();
+
+                io.initNewPage(buf, page.id());
+
+                freeSpace = writeRow.run(page, buf, row, entrySize);
+            }
+            else {
+                page = pageMem.page(item);
+
+                freeSpace = writePage(page, writeRow, row, entrySize, -1);
+            }
+        }
+        finally {
+            if (page != null) {
+                page.close();
+
+                if (freeSpace != -1) { // Put back to the tree.
+                    assert freeSpace >= 0 && freeSpace < Short.MAX_VALUE: freeSpace;
+
+                    if (item == null)
+                        item = new FreeItem((short)freeSpace, dispersion(), page.id(), cctx.cacheId());
+                    else {
+                        item.freeSpace((short)freeSpace);
+                        item.dispersion(dispersion());
+                    }
+
+                    FreeItem old = tree.put(item);
+
+                    assert old == null;
+                }
+            }
+        }
+    }
+
+    /**
+     * @param part Partition.
+     * @return Page.
+     * @throws IgniteCheckedException If failed.
+     */
+    private Page allocatePage(int part) throws IgniteCheckedException {
+        FullPageId pageId = pageMem.allocatePage(cctx.cacheId(), part, PageIdAllocator.FLAG_DATA);
+
+        return pageMem.page(pageId);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeTree.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeTree.java
new file mode 100644
index 0000000..31f9a1d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/FreeTree.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.database.freelist;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.pagemem.FullPageId;
+import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.database.tree.BPlusTree;
+import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusIO;
+import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusInnerIO;
+import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusLeafIO;
+import org.apache.ignite.internal.processors.cache.database.tree.io.PageIO;
+import org.apache.ignite.internal.processors.cache.database.freelist.io.FreeIO;
+import org.apache.ignite.internal.processors.cache.database.freelist.io.FreeInnerIO;
+import org.apache.ignite.internal.processors.cache.database.freelist.io.FreeLeafIO;
+
+/**
+ * Data structure for data pages and their free spaces.
+ */
+public class FreeTree extends BPlusTree<FreeItem, FreeItem> {
+    /**
+     * @param cacheId Cache ID.
+     * @param pageMem Page memory.
+     * @param metaPageId Meta page ID.
+     * @param initNew    Initialize new index.
+     * @throws IgniteCheckedException If failed.
+     */
+    public FreeTree(int cacheId, PageMemory pageMem, FullPageId metaPageId, boolean initNew)
+        throws IgniteCheckedException {
+        super(cacheId, pageMem, metaPageId);
+
+        assert pageMem != null;
+
+        if (initNew)
+            initNew();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected BPlusIO<FreeItem> io(int type, int ver) {
+        if (type == PageIO.T_FREE_INNER)
+            return FreeInnerIO.VERSIONS.forVersion(ver);
+
+        assert type == PageIO.T_FREE_LEAF: type;
+
+        return FreeLeafIO.VERSIONS.forVersion(ver);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected BPlusInnerIO<FreeItem> latestInnerIO() {
+        return FreeInnerIO.VERSIONS.latest();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected BPlusLeafIO<FreeItem> latestLeafIO() {
+        return FreeLeafIO.VERSIONS.latest();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected int compare(BPlusIO<FreeItem> io, ByteBuffer buf, int idx, FreeItem row)
+        throws IgniteCheckedException {
+        if (io.isLeaf()) // In a leaf we can do a fair compare.
+            return Short.compare(((FreeIO)io).freeSpace(buf, idx), row.freeSpace());
+
+        // In inner pages we do compare on dispersed free space to avoid contention on a single page
+        // when all the entries are equal and many pages have the same free space.
+        return Integer.compare(((FreeIO)io).dispersedFreeSpace(buf, idx), row.dispersedFreeSpace());
+    }
+
+    /** {@inheritDoc} */
+    @Override protected FreeItem getRow(BPlusIO<FreeItem> io, ByteBuffer buf, int idx) throws IgniteCheckedException {
+        assert io.isLeaf();
+
+        FreeItem row = io.getLookupRow(this, buf, idx);
+
+        assert row.pageId() != 0;
+        assert row.cacheId() == cacheId;
+
+        return row;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeIO.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeIO.java
new file mode 100644
index 0000000..de28b22
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeIO.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.database.freelist.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Common routines for free list pages.
+ */
+public interface FreeIO {
+    /**
+     * @param buf Buffer.
+     * @param idx Index.
+     * @return Dispersed free space.
+     */
+    public int dispersedFreeSpace(ByteBuffer buf, int idx);
+
+    /**
+     * @param buf Buffer.
+     * @param idx Index.
+     * @return Free space.
+     */
+    public short freeSpace(ByteBuffer buf, int idx);
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeInnerIO.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeInnerIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeInnerIO.java
new file mode 100644
index 0000000..bfaaaa2
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeInnerIO.java
@@ -0,0 +1,67 @@
+package org.apache.ignite.internal.processors.cache.database.freelist.io;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.processors.cache.database.freelist.FreeItem;
+import org.apache.ignite.internal.processors.cache.database.tree.BPlusTree;
+import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusIO;
+import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusInnerIO;
+import org.apache.ignite.internal.processors.cache.database.tree.io.IOVersions;
+
+/**
+ * Routines for free list inner pages.
+ */
+public class FreeInnerIO extends BPlusInnerIO<FreeItem> implements FreeIO {
+    /** */
+    public static final IOVersions<FreeInnerIO> VERSIONS = new IOVersions<>(
+        new FreeInnerIO(1)
+    );
+
+    /**
+     * @param ver Page format version.
+     */
+    protected FreeInnerIO(int ver) {
+        super(T_FREE_INNER, ver, false, 4); // freeSpace(2) + dispersion(2)
+    }
+
+    /** {@inheritDoc} */
+    @Override public void store(ByteBuffer buf, int idx, FreeItem row) {
+        store(buf, idx, row.dispersedFreeSpace());
+    }
+
+    /** {@inheritDoc} */
+    @Override public void store(ByteBuffer dst, int dstIdx, BPlusIO<FreeItem> srcIo, ByteBuffer src, int srcIdx) {
+        store(dst, dstIdx, ((FreeIO)srcIo).dispersedFreeSpace(src, srcIdx));
+    }
+
+    /**
+     * @param buf Buffer.
+     * @param idx Index.
+     * @param dispersedFreeSpace Dispersed free space.
+     */
+    private void store(ByteBuffer buf, int idx, int dispersedFreeSpace) {
+        int off = offset(idx, SHIFT_LINK);
+
+        buf.putInt(off, dispersedFreeSpace);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int dispersedFreeSpace(ByteBuffer buf, int idx) {
+        int off = offset(idx, SHIFT_LINK);
+
+        return buf.getInt(off);
+    }
+
+    /** {@inheritDoc} */
+    @Override public short freeSpace(ByteBuffer buf, int idx) {
+        int off = offset(idx, SHIFT_LINK);
+
+        return buf.getShort(off);
+    }
+
+    /** {@inheritDoc} */
+    @Override public FreeItem getLookupRow(BPlusTree<FreeItem, ?> tree, ByteBuffer buf, int idx) {
+        int off = offset(idx, SHIFT_LINK);
+
+        return new FreeItem(buf.getShort(off), buf.getShort(off + 2), 0, 0);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeLeafIO.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeLeafIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeLeafIO.java
new file mode 100644
index 0000000..5496897
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/database/freelist/io/FreeLeafIO.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.database.freelist.io;
+
+import java.nio.ByteBuffer;
+import org.apache.ignite.internal.processors.cache.database.tree.BPlusTree;
+import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusLeafIO;
+import org.apache.ignite.internal.processors.cache.database.tree.io.IOVersions;
+import org.apache.ignite.internal.processors.cache.database.freelist.FreeItem;
+
+/**
+ * Routines for free list leaf pages.
+ */
+public class FreeLeafIO extends BPlusLeafIO<FreeItem> implements FreeIO {
+    /** */
+    public static final IOVersions<FreeLeafIO> VERSIONS = new IOVersions<>(
+        new FreeLeafIO(1)
+    );
+
+    /**
+     * @param ver Page format version.
+     */
+    protected FreeLeafIO(int ver) {
+        super(T_FREE_LEAF, ver, 12); // freeSpace(2) + dispersion(2) + pageId(8)
+    }
+
+    /** {@inheritDoc} */
+    @Override public final void store(ByteBuffer buf, int idx, FreeItem row) {
+        int off = offset(idx);
+
+        buf.putInt(off, row.dispersedFreeSpace());
+        buf.putLong(off + 4, row.pageId());
+    }
+
+    /** {@inheritDoc} */
+    @Override public int dispersedFreeSpace(ByteBuffer buf, int idx) {
+        int off = offset(idx);
+
+        return buf.getInt(off);
+    }
+
+    /** {@inheritDoc} */
+    @Override public short freeSpace(ByteBuffer buf, int idx) {
+        int off = offset(idx);
+
+        return buf.getShort(off);
+    }
+
+    /** {@inheritDoc} */
+    @Override public FreeItem getLookupRow(BPlusTree<FreeItem, ?> tree, ByteBuffer buf, int idx) {
+        int off = offset(idx);
+
+        return new FreeItem(buf.getShort(off), buf.getShort(off + 2), buf.getLong(off + 4), tree.getCacheId());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2RowStore.java
----------------------------------------------------------------------
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 f545c24..2495237 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
@@ -17,242 +17,62 @@
 
 package org.apache.ignite.internal.processors.query.h2.database;
 
-import java.nio.ByteBuffer;
 import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.IgniteException;
-import org.apache.ignite.internal.pagemem.FullPageId;
-import org.apache.ignite.internal.pagemem.Page;
-import org.apache.ignite.internal.pagemem.PageIdAllocator;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
-import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.processors.cache.CacheObject;
-import org.apache.ignite.internal.processors.cache.CacheObjectContext;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
-import org.apache.ignite.internal.processors.query.h2.database.freelist.FreeList;
-import org.apache.ignite.internal.processors.cache.database.tree.io.DataPageIO;
-import org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.database.RowStore;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.cache.database.freelist.FreeList;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2Row;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
 
-import static org.apache.ignite.internal.pagemem.PageIdUtils.dwordsOffset;
-import static org.apache.ignite.internal.pagemem.PageIdUtils.linkFromDwordOffset;
-import static org.apache.ignite.internal.pagemem.PageIdUtils.pageId;
-import static org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler.writePage;
-
 /**
  * Data store for H2 rows.
  */
-public class H2RowStore {
-    /** */
-    private final FreeList freeList;
-
-    /** */
-    private final PageMemory pageMem;
-
-    /** */
-    private final GridH2RowDescriptor rowDesc;
-
-    /** */
-    private final GridCacheContext<?,?> cctx;
-
-    /** */
-    private final CacheObjectContext coctx;
-
-    /** */
-    private volatile long lastDataPageId;
-
-    /** */
-    private final PageHandler<GridH2Row> writeRow = new PageHandler<GridH2Row>() {
-        @Override public int run(Page page, ByteBuffer buf, GridH2Row row, int ignore) throws IgniteCheckedException {
-            int entrySize = DataPageIO.getEntrySize(coctx, row.key, row.val);
-
-            DataPageIO io = DataPageIO.VERSIONS.forPage(buf);
-
-            int idx = io.addRow(coctx, buf, row.key, row.val, row.ver, entrySize);
-
-            if (idx != -1) {
-                row.link = linkFromDwordOffset(page.id(), idx);
-
-                assert row.link != 0;
-            }
-
-            return idx;
-        }
-    };
-
-    /** */
-    private final PageHandler<Void> removeRow = new PageHandler<Void>() {
-        @Override public int run(Page page, ByteBuffer buf, Void ignore, int itemId) throws IgniteCheckedException {
-            DataPageIO io = DataPageIO.VERSIONS.forPage(buf);
-
-            assert DataPageIO.check(itemId): itemId;
-
-            io.removeRow(buf, (byte)itemId);
-
-            return 0;
-        }
-    };
-
+public class H2RowStore extends RowStore<GridH2Row> {
     /**
      * @param rowDesc Row descriptor.
      * @param cctx Cache context.
+     * @param freeList Free list.
      */
     public H2RowStore(GridH2RowDescriptor rowDesc, GridCacheContext<?,?> cctx, FreeList freeList) {
-        assert rowDesc != null;
-        assert cctx != null;
-
-        this.rowDesc = rowDesc;
-        this.cctx = cctx;
-        this.freeList = freeList;
-
-        coctx = cctx.cacheObjectContext();
-        pageMem = cctx.shared().database().pageMemory();
-    }
-
-    /**
-     * @param pageId Page ID.
-     * @return Page.
-     * @throws IgniteCheckedException If failed.
-     */
-    private Page page(long pageId) throws IgniteCheckedException {
-        return pageMem.page(new FullPageId(pageId, cctx.cacheId()));
-    }
-
-    /**
-     * @param part Partition.
-     * @return Allocated page.
-     * @throws IgniteCheckedException if failed.
-     */
-    private Page allocatePage(int part) throws IgniteCheckedException {
-        FullPageId fullPageId = pageMem.allocatePage(cctx.cacheId(), part, PageIdAllocator.FLAG_DATA);
-
-        return pageMem.page(fullPageId);
-    }
-
-    /**
-     * @param link Row link.
-     * @throws IgniteCheckedException If failed.
-     */
-    public void removeRow(long link) throws IgniteCheckedException {
-        assert link != 0;
-
-        try (Page page = page(pageId(link))) {
-            writePage(page, removeRow, null, dwordsOffset(link), 0);
-        }
+        super(cctx, new H2RowFactory(rowDesc), freeList);
     }
-
     /**
-     * !!! This method must be invoked in read or write lock of referring index page. It is needed to
-     * !!! make sure that row at this link will be invisible, when the link will be removed from
-     * !!! from all the index pages, so that row can be safely erased from the data page.
      *
-     * @param link Link.
-     * @return Row.
-     * @throws IgniteCheckedException If failed.
      */
-    public GridH2Row getRow(long link) throws IgniteCheckedException {
-        try (Page page = page(pageId(link))) {
-            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));
-
-                buf.position(dataOff);
-
-                // Skip entry size.
-                buf.getShort();
-
-                CacheObject key = coctx.processor().toCacheObject(coctx, buf);
-                CacheObject val = coctx.processor().toCacheObject(coctx, buf);
-
-                int topVer = buf.getInt();
-                int nodeOrderDrId = buf.getInt();
-                long globalTime = buf.getLong();
-                long order = buf.getLong();
-
-                GridCacheVersion ver = new GridCacheVersion(topVer, nodeOrderDrId, globalTime, order);
-
-                GridH2Row row;
-
-                try {
-                    row = rowDesc.createRow(key, PageIdUtils.partId(link), val, ver, 0);
-
-                    row.link = link;
-                }
-                catch (IgniteCheckedException e) {
-                    throw new IgniteException(e);
-                }
-
-                assert row.ver != null;
+    static class H2RowFactory implements RowFactory<GridH2Row> {
+        /** */
+        private final GridH2RowDescriptor rowDesc;
 
-                rowDesc.cache(row);
+        /**
+         * @param rowDesc Row descriptor.
+         */
+        public H2RowFactory(GridH2RowDescriptor rowDesc) {
+            assert rowDesc != null;
 
-                return row;
-            }
-            finally {
-                page.releaseRead();
-            }
+            this.rowDesc = rowDesc;
         }
-    }
-
-    /**
-     * @param expLastDataPageId Expected last data page ID.
-     * @return Next data page ID.
-     */
-    private synchronized long nextDataPage(long expLastDataPageId, int partId) throws IgniteCheckedException {
-        if (expLastDataPageId != lastDataPageId)
-            return lastDataPageId;
 
-        long pageId;
-
-        try (Page page = allocatePage(partId)) {
-            pageId = page.id();
-
-            ByteBuffer buf = page.getForInitialWrite();
-
-            DataPageIO.VERSIONS.latest().initNewPage(buf, page.id());
+        /** {@inheritDoc} */
+        @Override public GridH2Row cachedRow(long link) {
+            return rowDesc.cachedRow(link);
         }
 
-        return lastDataPageId = pageId;
-    }
-
-    /**
-     * @param row Row.
-     */
-    public void addRow(GridH2Row row) throws IgniteCheckedException {
-        if (freeList == null)
-            writeRowData0(row);
-        else
-            freeList.writeRowData(row);
-    }
-
-    /**
-     * @param row Row.
-     * @throws IgniteCheckedException If failed.
-     */
-    private void writeRowData0(GridH2Row row) throws IgniteCheckedException {
-        assert row.link == 0;
-
-        while (row.link == 0) {
-            long pageId = lastDataPageId;
+        /** {@inheritDoc} */
+        @Override public GridH2Row createRow(CacheObject key,
+            CacheObject val,
+            GridCacheVersion ver,
+            long link,
+            long expirationTime) throws IgniteCheckedException {
+            GridH2Row row = rowDesc.createRow(key, PageIdUtils.partId(link), val, ver, 0);
 
-            if (pageId == 0)
-                pageId = nextDataPage(0, row.partId);
+            row.link = link;
 
-            try (Page page = page(pageId)) {
-                if (writePage(page, writeRow, row, -1, -1) >= 0)
-                    return; // Successful write.
-            }
+            rowDesc.cache(row);
 
-            nextDataPage(pageId, row.partId);
+            return row;
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeItem.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeItem.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeItem.java
deleted file mode 100644
index 71bb32e..0000000
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeItem.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.h2.database.freelist;
-
-import org.apache.ignite.internal.pagemem.FullPageId;
-
-/**
- * Free list item.
- */
-public class FreeItem extends FullPageId {
-    /** */
-    private short freeSpace;
-
-    /** */
-    private short dispersion;
-
-    /**
-     * @param freeSpace Free space.
-     * @param dispersion Dispersion.
-     * @param pageId  Page ID.
-     * @param cacheId Cache ID.
-     */
-    public FreeItem(short freeSpace, short dispersion, long pageId, int cacheId) {
-        super(pageId, cacheId);
-
-        assert freeSpace >= 0: freeSpace;
-
-        this.freeSpace = freeSpace;
-        this.dispersion = dispersion;
-    }
-
-    /**
-     * @param freeSpace Free space.
-     * @param dispersion Dispersion.
-     * @return Dispersed free space.
-     */
-    public static int disperse(int freeSpace, int dispersion) {
-        return (freeSpace << 16) | dispersion;
-    }
-
-    /**
-     * @return Dispersed free space.
-     */
-    public int dispersedFreeSpace() {
-        return disperse(freeSpace, dispersion);
-    }
-
-    /**
-     * @return Free space in the page.
-     */
-    public short freeSpace() {
-        return freeSpace;
-    }
-
-    /**
-     * @param freeSpace Free space.
-     */
-    public void freeSpace(short freeSpace) {
-        this.freeSpace = freeSpace;
-    }
-
-    /**
-     * @return Dispersion.
-     */
-    public short dispersion() {
-        return dispersion;
-    }
-
-    /**
-     * @param dispersion Dispersion.
-     */
-    public void dispersion(short dispersion) {
-        this.dispersion = dispersion;
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeList.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeList.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeList.java
deleted file mode 100644
index 43b0dcc..0000000
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeList.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.h2.database.freelist;
-
-import java.nio.ByteBuffer;
-import java.util.concurrent.ThreadLocalRandom;
-import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.internal.pagemem.FullPageId;
-import org.apache.ignite.internal.pagemem.Page;
-import org.apache.ignite.internal.pagemem.PageIdAllocator;
-import org.apache.ignite.internal.pagemem.PageMemory;
-import org.apache.ignite.internal.processors.cache.GridCacheContext;
-import org.apache.ignite.internal.processors.cache.database.tree.io.DataPageIO;
-import org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler;
-import org.apache.ignite.internal.processors.query.h2.opt.GridH2Row;
-import org.apache.ignite.internal.util.future.GridFutureAdapter;
-import org.apache.ignite.lang.IgniteBiTuple;
-import org.jsr166.ConcurrentHashMap8;
-
-import static org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler.writePage;
-
-/**
- * Free data page list.
- */
-public class FreeList {
-    /** */
-    private final GridCacheContext<?,?> cctx;
-
-    /** */
-    private final PageMemory pageMem;
-
-    /** */
-    private final ConcurrentHashMap8<Integer,GridFutureAdapter<FreeTree>> trees = new ConcurrentHashMap8<>();
-
-    /** */
-    private final PageHandler<GridH2Row> writeRow = new PageHandler<GridH2Row>() {
-        @Override public int run(Page page, ByteBuffer buf, GridH2Row row, int entrySize)
-            throws IgniteCheckedException {
-            DataPageIO io = DataPageIO.VERSIONS.forPage(buf);
-
-            int idx = io.addRow(cctx.cacheObjectContext(), buf, row.key, row.val, row.ver, entrySize);
-
-            assert idx >= 0;
-
-            return io.getFreeSpace(buf);
-        }
-    };
-
-    /**
-     * @param cctx Cache context.
-     */
-    public FreeList(GridCacheContext<?,?> cctx) {
-        assert cctx != null;
-
-        this.cctx = cctx;
-
-        pageMem = cctx.shared().database().pageMemory();
-
-        assert pageMem != null;
-    }
-
-    /**
-     * @param tree Tree.
-     * @param neededSpace Needed free space.
-     * @return Free item or {@code null} if it was impossible to find one.
-     * @throws IgniteCheckedException If failed.
-     */
-    private FreeItem take(FreeTree tree, short neededSpace) throws IgniteCheckedException {
-        assert neededSpace > 0 && neededSpace < Short.MAX_VALUE: neededSpace;
-
-        FreeItem res = tree.removeCeil(new FreeItem(neededSpace, dispersion(), 0, 0));
-
-        assert res == null || (res.pageId() != 0 && res.cacheId() == cctx.cacheId()): res;
-
-        return res;
-    }
-
-    /**
-     * @return Random dispersion value.
-     */
-    private static short dispersion() {
-        return (short)ThreadLocalRandom.current().nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
-    }
-
-    /**
-     * @param part Partition.
-     * @return Tree.
-     * @throws IgniteCheckedException If failed.
-     */
-    private FreeTree tree(Integer part) throws IgniteCheckedException {
-        assert part >= 0 && part < Short.MAX_VALUE: part;
-
-        GridFutureAdapter<FreeTree> fut = trees.get(part);
-
-        if (fut == null) {
-            fut = new GridFutureAdapter<>();
-
-            if (trees.putIfAbsent(part, fut) != null)
-                fut = trees.get(part);
-            else {
-                // Index name will be the same across restarts.
-                String idxName = part + "$$" + cctx.cacheId() + "_free";
-
-                IgniteBiTuple<FullPageId,Boolean> t = cctx.shared().database().meta()
-                    .getOrAllocateForIndex(cctx.cacheId(), idxName);
-
-                fut.onDone(new FreeTree(cctx.cacheId(), pageMem, t.get1(), t.get2()));
-            }
-        }
-
-        return fut.get();
-    }
-
-    /**
-     * @param row Row.
-     * @throws IgniteCheckedException If failed.
-     */
-    public void writeRowData(GridH2Row row) throws IgniteCheckedException {
-        assert row.link == 0;
-
-        int entrySize = DataPageIO.getEntrySize(cctx.cacheObjectContext(), row.key, row.val);
-
-        assert entrySize > 0 && entrySize < Short.MAX_VALUE: entrySize;
-
-        FreeTree tree = tree(row.partId);
-        FreeItem item = take(tree, (short)entrySize);
-
-        Page page = null;
-        int freeSpace = -1;
-
-        try {
-            if (item == null) {
-                DataPageIO io = DataPageIO.VERSIONS.latest();
-
-                page = allocatePage(row.partId);
-
-                ByteBuffer buf = page.getForInitialWrite();
-
-                io.initNewPage(buf, page.id());
-
-                freeSpace = writeRow.run(page, buf, row, entrySize);
-            }
-            else {
-                page = pageMem.page(item);
-
-                freeSpace = writePage(page, writeRow, row, entrySize, -1);
-            }
-        }
-        finally {
-            if (page != null) {
-                page.close();
-
-                if (freeSpace != -1) { // Put back to the tree.
-                    assert freeSpace >= 0 && freeSpace < Short.MAX_VALUE: freeSpace;
-
-                    if (item == null)
-                        item = new FreeItem((short)freeSpace, dispersion(), page.id(), cctx.cacheId());
-                    else {
-                        item.freeSpace((short)freeSpace);
-                        item.dispersion(dispersion());
-                    }
-
-                    FreeItem old = tree.put(item);
-
-                    assert old == null;
-                }
-            }
-        }
-    }
-
-    /**
-     * @param part Partition.
-     * @return Page.
-     * @throws IgniteCheckedException If failed.
-     */
-    private Page allocatePage(int part) throws IgniteCheckedException {
-        FullPageId pageId = pageMem.allocatePage(cctx.cacheId(), part, PageIdAllocator.FLAG_DATA);
-
-        return pageMem.page(pageId);
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeTree.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeTree.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeTree.java
deleted file mode 100644
index 6d61bd7..0000000
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/FreeTree.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.h2.database.freelist;
-
-import java.nio.ByteBuffer;
-import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.internal.pagemem.FullPageId;
-import org.apache.ignite.internal.pagemem.PageMemory;
-import org.apache.ignite.internal.processors.cache.database.tree.BPlusTree;
-import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusIO;
-import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusInnerIO;
-import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusLeafIO;
-import org.apache.ignite.internal.processors.cache.database.tree.io.PageIO;
-import org.apache.ignite.internal.processors.query.h2.database.freelist.io.FreeIO;
-import org.apache.ignite.internal.processors.query.h2.database.freelist.io.FreeInnerIO;
-import org.apache.ignite.internal.processors.query.h2.database.freelist.io.FreeLeafIO;
-
-/**
- * Data structure for data pages and their free spaces.
- */
-public class FreeTree extends BPlusTree<FreeItem, FreeItem> {
-    /**
-     * @param cacheId Cache ID.
-     * @param pageMem Page memory.
-     * @param metaPageId Meta page ID.
-     * @param initNew    Initialize new index.
-     * @throws IgniteCheckedException If failed.
-     */
-    public FreeTree(int cacheId, PageMemory pageMem, FullPageId metaPageId, boolean initNew)
-        throws IgniteCheckedException {
-        super(cacheId, pageMem, metaPageId);
-
-        assert pageMem != null;
-
-        if (initNew)
-            initNew();
-    }
-
-    /** {@inheritDoc} */
-    @Override protected BPlusIO<FreeItem> io(int type, int ver) {
-        if (type == PageIO.T_FREE_INNER)
-            return FreeInnerIO.VERSIONS.forVersion(ver);
-
-        assert type == PageIO.T_FREE_LEAF: type;
-
-        return FreeLeafIO.VERSIONS.forVersion(ver);
-    }
-
-    /** {@inheritDoc} */
-    @Override protected BPlusInnerIO<FreeItem> latestInnerIO() {
-        return FreeInnerIO.VERSIONS.latest();
-    }
-
-    /** {@inheritDoc} */
-    @Override protected BPlusLeafIO<FreeItem> latestLeafIO() {
-        return FreeLeafIO.VERSIONS.latest();
-    }
-
-    /** {@inheritDoc} */
-    @Override protected int compare(BPlusIO<FreeItem> io, ByteBuffer buf, int idx, FreeItem row)
-        throws IgniteCheckedException {
-        if (io.isLeaf()) // In a leaf we can do a fair compare.
-            return Short.compare(((FreeIO)io).freeSpace(buf, idx), row.freeSpace());
-
-        // In inner pages we do compare on dispersed free space to avoid contention on a single page
-        // when all the entries are equal and many pages have the same free space.
-        return Integer.compare(((FreeIO)io).dispersedFreeSpace(buf, idx), row.dispersedFreeSpace());
-    }
-
-    /** {@inheritDoc} */
-    @Override protected FreeItem getRow(BPlusIO<FreeItem> io, ByteBuffer buf, int idx) throws IgniteCheckedException {
-        assert io.isLeaf();
-
-        FreeItem row = io.getLookupRow(this, buf, idx);
-
-        assert row.pageId() != 0;
-        assert row.cacheId() == cacheId;
-
-        return row;
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeIO.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeIO.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeIO.java
deleted file mode 100644
index 5af351c..0000000
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeIO.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.h2.database.freelist.io;
-
-import java.nio.ByteBuffer;
-
-/**
- * Common routines for free list pages.
- */
-public interface FreeIO {
-    /**
-     * @param buf Buffer.
-     * @param idx Index.
-     * @return Dispersed free space.
-     */
-    public int dispersedFreeSpace(ByteBuffer buf, int idx);
-
-    /**
-     * @param buf Buffer.
-     * @param idx Index.
-     * @return Free space.
-     */
-    public short freeSpace(ByteBuffer buf, int idx);
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeInnerIO.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeInnerIO.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeInnerIO.java
deleted file mode 100644
index b8a3872..0000000
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeInnerIO.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.apache.ignite.internal.processors.query.h2.database.freelist.io;
-
-import java.nio.ByteBuffer;
-import org.apache.ignite.internal.processors.query.h2.database.freelist.FreeItem;
-import org.apache.ignite.internal.processors.cache.database.tree.BPlusTree;
-import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusIO;
-import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusInnerIO;
-import org.apache.ignite.internal.processors.cache.database.tree.io.IOVersions;
-
-/**
- * Routines for free list inner pages.
- */
-public class FreeInnerIO extends BPlusInnerIO<FreeItem> implements FreeIO {
-    /** */
-    public static final IOVersions<FreeInnerIO> VERSIONS = new IOVersions<>(
-        new FreeInnerIO(1)
-    );
-
-    /**
-     * @param ver Page format version.
-     */
-    protected FreeInnerIO(int ver) {
-        super(T_FREE_INNER, ver, false, 4); // freeSpace(2) + dispersion(2)
-    }
-
-    /** {@inheritDoc} */
-    @Override public void store(ByteBuffer buf, int idx, FreeItem row) {
-        store(buf, idx, row.dispersedFreeSpace());
-    }
-
-    /** {@inheritDoc} */
-    @Override public void store(ByteBuffer dst, int dstIdx, BPlusIO<FreeItem> srcIo, ByteBuffer src, int srcIdx) {
-        store(dst, dstIdx, ((FreeIO)srcIo).dispersedFreeSpace(src, srcIdx));
-    }
-
-    /**
-     * @param buf Buffer.
-     * @param idx Index.
-     * @param dispersedFreeSpace Dispersed free space.
-     */
-    private void store(ByteBuffer buf, int idx, int dispersedFreeSpace) {
-        int off = offset(idx, SHIFT_LINK);
-
-        buf.putInt(off, dispersedFreeSpace);
-    }
-
-    /** {@inheritDoc} */
-    @Override public int dispersedFreeSpace(ByteBuffer buf, int idx) {
-        int off = offset(idx, SHIFT_LINK);
-
-        return buf.getInt(off);
-    }
-
-    /** {@inheritDoc} */
-    @Override public short freeSpace(ByteBuffer buf, int idx) {
-        int off = offset(idx, SHIFT_LINK);
-
-        return buf.getShort(off);
-    }
-
-    /** {@inheritDoc} */
-    @Override public FreeItem getLookupRow(BPlusTree<FreeItem, ?> tree, ByteBuffer buf, int idx) {
-        int off = offset(idx, SHIFT_LINK);
-
-        return new FreeItem(buf.getShort(off), buf.getShort(off + 2), 0, 0);
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeLeafIO.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeLeafIO.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeLeafIO.java
deleted file mode 100644
index ee71f18..0000000
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/freelist/io/FreeLeafIO.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.h2.database.freelist.io;
-
-import java.nio.ByteBuffer;
-import org.apache.ignite.internal.processors.cache.database.tree.BPlusTree;
-import org.apache.ignite.internal.processors.cache.database.tree.io.BPlusLeafIO;
-import org.apache.ignite.internal.processors.cache.database.tree.io.IOVersions;
-import org.apache.ignite.internal.processors.query.h2.database.freelist.FreeItem;
-
-/**
- * Routines for free list leaf pages.
- */
-public class FreeLeafIO extends BPlusLeafIO<FreeItem> implements FreeIO {
-    /** */
-    public static final IOVersions<FreeLeafIO> VERSIONS = new IOVersions<>(
-        new FreeLeafIO(1)
-    );
-
-    /**
-     * @param ver Page format version.
-     */
-    protected FreeLeafIO(int ver) {
-        super(T_FREE_LEAF, ver, 12); // freeSpace(2) + dispersion(2) + pageId(8)
-    }
-
-    /** {@inheritDoc} */
-    @Override public final void store(ByteBuffer buf, int idx, FreeItem row) {
-        int off = offset(idx);
-
-        buf.putInt(off, row.dispersedFreeSpace());
-        buf.putLong(off + 4, row.pageId());
-    }
-
-    /** {@inheritDoc} */
-    @Override public int dispersedFreeSpace(ByteBuffer buf, int idx) {
-        int off = offset(idx);
-
-        return buf.getInt(off);
-    }
-
-    /** {@inheritDoc} */
-    @Override public short freeSpace(ByteBuffer buf, int idx) {
-        int off = offset(idx);
-
-        return buf.getShort(off);
-    }
-
-    /** {@inheritDoc} */
-    @Override public FreeItem getLookupRow(BPlusTree<FreeItem, ?> tree, ByteBuffer buf, int idx) {
-        int off = offset(idx);
-
-        return new FreeItem(buf.getShort(off), buf.getShort(off + 2), buf.getLong(off + 4), tree.getCacheId());
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/2f7a22c1/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Row.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Row.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Row.java
index 52b9b5f..a84ad20 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Row.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/opt/GridH2Row.java
@@ -19,13 +19,14 @@ package org.apache.ignite.internal.processors.query.h2.opt;
 
 import org.apache.ignite.internal.processors.cache.CacheObject;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.processors.cache.database.CacheDataRow;
 import org.h2.result.Row;
 import org.h2.value.Value;
 
 /**
  * Row with locking support needed for unique key conflicts resolution.
  */
-public class GridH2Row extends Row implements GridSearchRowPointer {
+public class GridH2Row extends Row implements GridSearchRowPointer, CacheDataRow {
     /** */
     public long link; // TODO remove
 
@@ -62,4 +63,34 @@ public class GridH2Row extends Row implements GridSearchRowPointer {
     @Override public void decrementRefCount() {
         throw new IllegalStateException();
     }
+
+    /** {@inheritDoc} */
+    @Override public CacheObject key() {
+        return key;
+    }
+
+    /** {@inheritDoc} */
+    @Override public CacheObject value() {
+        return val;
+    }
+
+    /** {@inheritDoc} */
+    @Override public GridCacheVersion version() {
+        return ver;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int partition() {
+        return partId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long link() {
+        return link;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void link(long link) {
+        this.link = link;
+    }
 }
\ No newline at end of file


Mime
View raw message