/* * 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.charset.StandardCharsets; import java.util.concurrent.atomic.AtomicLong; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.internal.pagemem.FullPageId; import org.apache.ignite.internal.pagemem.PageMemory; import org.apache.ignite.internal.pagemem.PageUtils; import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager; 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.IOVersions; import org.apache.ignite.internal.processors.cache.database.tree.reuse.ReuseList; import org.apache.ignite.internal.processors.cache.database.tree.util.PageHandler; import org.apache.ignite.internal.util.typedef.internal.U; /** * Metadata storage. */ public class MetadataStorage implements MetaStore { /** Max index name length (bytes num) */ public static final int MAX_IDX_NAME_LEN = 768; /** Bytes in byte. */ private static final int BYTE_LEN = 1; /** Page memory. */ private final PageMemory pageMem; /** Index tree. */ private final MetaTree metaTree; /** Meta page reuse tree. */ private final ReuseList reuseList; /** Cache ID. */ private final int cacheId; /** */ private final int allocPartId; /** */ private final byte allocSpace; /** * @param pageMem Page memory. * @param wal Write ahead log manager. */ public MetadataStorage( final PageMemory pageMem, final IgniteWriteAheadLogManager wal, final AtomicLong globalRmvId, final int cacheId, final int allocPartId, final byte allocSpace, final ReuseList reuseList, final long rootPageId, final boolean initNew ) { try { this.pageMem = pageMem; this.cacheId = cacheId; this.allocPartId = allocPartId; this.allocSpace = allocSpace; this.reuseList = reuseList; metaTree = new MetaTree(cacheId, allocPartId, allocSpace, pageMem, wal, globalRmvId, rootPageId, reuseList, MetaStoreInnerIO.VERSIONS, MetaStoreLeafIO.VERSIONS, initNew); } catch (IgniteCheckedException e) { throw new IgniteException(e); } } /** {@inheritDoc} */ @Override public RootPage getOrAllocateForTree(final String idxName) throws IgniteCheckedException { final MetaTree tree = metaTree; synchronized (this) { byte[] idxNameBytes = idxName.getBytes(StandardCharsets.UTF_8); if (idxNameBytes.length > MAX_IDX_NAME_LEN) throw new IllegalArgumentException("Too long encoded indexName [maxAllowed=" + MAX_IDX_NAME_LEN + ", currentLength=" + idxNameBytes.length + ", name=" + idxName + "]"); final IndexItem row = tree.findOne(new IndexItem(idxNameBytes, 0)); if (row == null) { long pageId = 0; if (reuseList != null) pageId = reuseList.takeRecycledPage(); pageId = pageId == 0 ? pageMem.allocatePage(cacheId, allocPartId, allocSpace) : pageId; tree.put(new IndexItem(idxNameBytes, pageId)); return new RootPage(new FullPageId(pageId, cacheId), true); } else { final FullPageId pageId = new FullPageId(row.pageId, cacheId); return new RootPage(pageId, false); } } } /** {@inheritDoc} */ @Override public RootPage dropRootPage(final String idxName) throws IgniteCheckedException { byte[] idxNameBytes = idxName.getBytes(StandardCharsets.UTF_8); final IndexItem row = metaTree.remove(new IndexItem(idxNameBytes, 0)); if (row != null) { if (reuseList == null) pageMem.freePage(cacheId, row.pageId); } return row != null ? new RootPage(new FullPageId(row.pageId, cacheId), false) : null; } /** {@inheritDoc} */ @Override public void destroy() throws IgniteCheckedException { metaTree.destroy(); } /** * */ private static class MetaTree extends BPlusTree<IndexItem, IndexItem> { /** */ private final int allocPartId; /** */ private final byte allocSpace; /** * @param pageMem Page memory. * @param metaPageId Meta page ID. * @param reuseList Reuse list. * @param innerIos Inner IOs. * @param leafIos Leaf IOs. * @throws IgniteCheckedException If failed. */ private MetaTree( final int cacheId, final int allocPartId, final byte allocSpace, final PageMemory pageMem, final IgniteWriteAheadLogManager wal, final AtomicLong globalRmvId, final long metaPageId, final ReuseList reuseList, final IOVersions<? extends BPlusInnerIO<IndexItem>> innerIos, final IOVersions<? extends BPlusLeafIO<IndexItem>> leafIos, final boolean initNew ) throws IgniteCheckedException { super(treeName("meta", "Meta"), cacheId, pageMem, wal, globalRmvId, metaPageId, reuseList, innerIos, leafIos); this.allocPartId = allocPartId; this.allocSpace = allocSpace; initTree(initNew); } /** {@inheritDoc} */ @Override protected long allocatePageNoReuse() throws IgniteCheckedException { return pageMem.allocatePage(getCacheId(), allocPartId, allocSpace); } /** {@inheritDoc} */ @Override protected int compare(final BPlusIO<IndexItem> io, final long pageAddr, final int idx, final IndexItem row) throws IgniteCheckedException { final int off = ((IndexIO)io).getOffset(pageAddr, idx); int shift = 0; // Compare index names. final byte len = PageUtils.getByte(pageAddr, off + shift); shift += BYTE_LEN; for (int i = 0; i < len && i < row.idxName.length; i++) { final int cmp = Byte.compare(PageUtils.getByte(pageAddr, off + i + shift), row.idxName[i]); if (cmp != 0) return cmp; } return Integer.compare(len, row.idxName.length); } /** {@inheritDoc} */ @Override protected IndexItem getRow(final BPlusIO<IndexItem> io, final long pageAddr, final int idx, Object ignore) throws IgniteCheckedException { return readRow(pageAddr, ((IndexIO)io).getOffset(pageAddr, idx)); } } /** * */ private static class IndexItem { /** */ private byte[] idxName; /** */ private long pageId; /** * @param idxName Index name. * @param pageId Page ID. */ private IndexItem(final byte[] idxName, final long pageId) { this.idxName = idxName; this.pageId = pageId; } /** {@inheritDoc} */ @Override public String toString() { return "I [idxName=" + new String(idxName) + ", pageId=" + U.hexLong(pageId) + ']'; } } /** * Store row to buffer. * * @param pageAddr Page address. * @param off Offset in buf. * @param row Row to store. */ private static void storeRow( final long pageAddr, int off, final IndexItem row ) { // Index name length. PageUtils.putByte(pageAddr, off, (byte)row.idxName.length); off++; // Index name. PageUtils.putBytes(pageAddr, off, row.idxName); off += row.idxName.length; // Page ID. PageUtils.putLong(pageAddr, off, row.pageId); } /** * Copy row data. * * @param dstPageAddr Destination page address. * @param dstOff Destination buf offset. * @param srcPageAddr Source page address. * @param srcOff Src buf offset. */ private static void storeRow( final long dstPageAddr, int dstOff, final long srcPageAddr, int srcOff ) { // Index name length. final byte len = PageUtils.getByte(srcPageAddr, srcOff); srcOff++; PageUtils.putByte(dstPageAddr, dstOff, len); dstOff++; PageHandler.copyMemory(srcPageAddr, srcOff, dstPageAddr, dstOff, len); srcOff += len; dstOff += len; // Page ID. PageUtils.putLong(dstPageAddr, dstOff, PageUtils.getLong(srcPageAddr, srcOff)); } /** * Read row from buffer. * * @param pageAddr Page address. * @param off Offset. * @return Read row. */ private static IndexItem readRow(final long pageAddr, int off) { // Index name length. final int len = PageUtils.getByte(pageAddr, off) & 0xFF; off++; // Index name. final byte[] idxName = PageUtils.getBytes(pageAddr, off, len); off += len; // Page ID. final long pageId = PageUtils.getLong(pageAddr, off); return new IndexItem(idxName, pageId); } /** * */ private interface IndexIO { /** * @param pageAddr Page address. * @param idx Index. * @return Offset in buffer according to {@code idx}. */ int getOffset(long pageAddr, int idx); } /** * */ public static final class MetaStoreInnerIO extends BPlusInnerIO<IndexItem> implements IndexIO { /** */ public static final IOVersions<MetaStoreInnerIO> VERSIONS = new IOVersions<>( new MetaStoreInnerIO(1) ); /** * @param ver Version. */ private MetaStoreInnerIO(final int ver) { // name bytes and 1 byte for length, 8 bytes pageId super(T_METASTORE_INNER, ver, false, MAX_IDX_NAME_LEN + 1 + 8); } /** {@inheritDoc} */ @Override public void storeByOffset(long pageAddr, int off, IndexItem row) throws IgniteCheckedException { storeRow(pageAddr, off, row); } /** {@inheritDoc} */ @Override public void store(final long dstPageAddr, final int dstIdx, final BPlusIO<IndexItem> srcIo, final long srcPageAddr, final int srcIdx) throws IgniteCheckedException { storeRow(dstPageAddr, offset(dstIdx), srcPageAddr, ((IndexIO)srcIo).getOffset(srcPageAddr, srcIdx)); } /** {@inheritDoc} */ @Override public IndexItem getLookupRow(final BPlusTree<IndexItem, ?> tree, final long pageAddr, final int idx) throws IgniteCheckedException { return readRow(pageAddr, offset(idx)); } /** {@inheritDoc} */ @Override public int getOffset(long pageAddr, final int idx) { return offset(idx); } } /** * */ public static final class MetaStoreLeafIO extends BPlusLeafIO<IndexItem> implements IndexIO { /** */ public static final IOVersions<MetaStoreLeafIO> VERSIONS = new IOVersions<>( new MetaStoreLeafIO(1) ); /** * @param ver Version. */ private MetaStoreLeafIO(final int ver) { // 4 byte cache ID, UTF-16 symbols and 1 byte for length, 8 bytes pageId super(T_METASTORE_LEAF, ver, MAX_IDX_NAME_LEN + 1 + 8); } /** {@inheritDoc} */ @Override public void storeByOffset(long buf, int off, IndexItem row) throws IgniteCheckedException { storeRow(buf, off, row); } /** {@inheritDoc} */ @Override public void store(final long dstPageAddr, final int dstIdx, final BPlusIO<IndexItem> srcIo, final long srcPageAddr, final int srcIdx) throws IgniteCheckedException { storeRow(dstPageAddr, offset(dstIdx), srcPageAddr, ((IndexIO)srcIo).getOffset(srcPageAddr, srcIdx)); } /** {@inheritDoc} */ @Override public IndexItem getLookupRow(final BPlusTree<IndexItem, ?> tree, final long pageAddr, final int idx) throws IgniteCheckedException { return readRow(pageAddr, offset(idx)); } /** {@inheritDoc} */ @Override public int getOffset(long pageAddr, final int idx) { return offset(idx); } } }