/* * 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; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteSystemProperties; import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.database.IgniteCacheDatabaseSharedManager; import org.apache.ignite.internal.processors.cache.database.RootPage; import org.apache.ignite.internal.processors.cache.database.tree.BPlusTree; import org.apache.ignite.internal.processors.cache.database.tree.io.PageIO; import org.apache.ignite.internal.processors.query.h2.H2Cursor; import org.apache.ignite.internal.processors.query.h2.opt.GridH2IndexBase; import org.apache.ignite.internal.processors.query.h2.opt.GridH2Row; import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table; import org.apache.ignite.internal.util.IgniteTree; import org.apache.ignite.internal.util.lang.GridCursor; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.lang.IgniteBiPredicate; import org.apache.ignite.spi.indexing.IndexingQueryFilter; import org.h2.engine.Session; import org.h2.index.Cursor; import org.h2.index.IndexType; import org.h2.index.SingleRowCursor; import org.h2.message.DbException; import org.h2.result.SearchRow; import org.h2.result.SortOrder; import org.h2.table.Column; import org.h2.table.IndexColumn; import org.h2.table.TableFilter; import org.h2.value.Value; import org.jetbrains.annotations.Nullable; /** * H2 Index over {@link BPlusTree}. */ @SuppressWarnings({"TypeMayBeWeakened", "unchecked"}) public class H2TreeIndex extends GridH2IndexBase { /** Default value for {@code IGNITE_MAX_INDEX_PAYLOAD_SIZE} */ public static final int IGNITE_MAX_INDEX_PAYLOAD_SIZE_DEFAULT = 10; /** */ private final H2Tree[] segments; /** */ private final List<InlineIndexHelper> inlineIdxs; /** Cache context. */ private GridCacheContext<?, ?> cctx; /** * @param cctx Cache context. * @param tbl Table. * @param name Index name. * @param pk Primary key. * @param colsList Index columns. * @param inlineSize Inline size. * @throws IgniteCheckedException If failed. */ public H2TreeIndex( GridCacheContext<?, ?> cctx, GridH2Table tbl, String name, boolean pk, List<IndexColumn> colsList, int inlineSize, int segmentsCnt ) throws IgniteCheckedException { assert segmentsCnt > 0 : segmentsCnt; this.cctx = cctx; IndexColumn[] cols = colsList.toArray(new IndexColumn[colsList.size()]); IndexColumn.mapColumns(cols, tbl); initBaseIndex(tbl, 0, name, cols, pk ? IndexType.createPrimaryKey(false, false) : IndexType.createNonUnique(false, false, false)); name = tbl.rowDescriptor() == null ? "_" + name : tbl.rowDescriptor().type().typeId() + "_" + name; name = BPlusTree.treeName(name, "H2Tree"); if (cctx.affinityNode()) { IgniteCacheDatabaseSharedManager dbMgr = cctx.shared().database(); inlineIdxs = getAvailableInlineColumns(cols); segments = new H2Tree[segmentsCnt]; for (int i = 0; i < segments.length; i++) { RootPage page = getMetaPage(name, i); segments[i] = new H2Tree( name,cctx.offheap().reuseListForIndex(name), cctx.cacheId(), cctx.memoryPolicy().pageMemory(), cctx.shared().wal(), cctx.offheap().globalRemoveId(), tbl.rowFactory(), page.pageId().pageId(), page.isAllocated(), cols, inlineIdxs, computeInlineSize(inlineIdxs, inlineSize)) { @Override public int compareValues(Value v1, Value v2) { return v1 == v2 ? 0 : table.compareTypeSafe(v1, v2); } }; } } else { // We need indexes on the client node, but index will not contain any data. segments = null; inlineIdxs = null; } initDistributedJoinMessaging(tbl); } /** * @param cols Columns array. * @return List of {@link InlineIndexHelper} objects. */ private List<InlineIndexHelper> getAvailableInlineColumns(IndexColumn[] cols) { List<InlineIndexHelper> res = new ArrayList<>(); for (IndexColumn col : cols) { if (!InlineIndexHelper.AVAILABLE_TYPES.contains(col.column.getType())) break; InlineIndexHelper idx = new InlineIndexHelper(col.column.getType(), col.column.getColumnId(), col.sortType); res.add(idx); } return res; } /** {@inheritDoc} */ @Override protected int segmentsCount() { return segments.length; } /** {@inheritDoc} */ @Override public Cursor find(Session ses, SearchRow lower, SearchRow upper) { try { IndexingQueryFilter f = threadLocalFilter(); IgniteBiPredicate<Object, Object> p = null; if (f != null) { String spaceName = getTable().spaceName(); p = f.forSpace(spaceName); } int seg = threadLocalSegment(); H2Tree tree = treeForRead(seg); return new H2Cursor(tree.find(lower, upper), p); } catch (IgniteCheckedException e) { throw DbException.convert(e); } } /** {@inheritDoc} */ @Override public GridH2Row findOne(GridH2Row row) { try { int seg = segmentForRow(row); H2Tree tree = treeForRead(seg); return tree.findOne(row); } catch (IgniteCheckedException e) { throw DbException.convert(e); } } /** {@inheritDoc} */ @Override public GridH2Row put(GridH2Row row) { try { InlineIndexHelper.setCurrentInlineIndexes(inlineIdxs); int seg = segmentForRow(row); H2Tree tree = treeForRead(seg); return tree.put(row); } catch (IgniteCheckedException e) { throw DbException.convert(e); } finally { InlineIndexHelper.clearCurrentInlineIndexes(); } } /** {@inheritDoc} */ @Override public boolean putx(GridH2Row row) { try { InlineIndexHelper.setCurrentInlineIndexes(inlineIdxs); int seg = segmentForRow(row); H2Tree tree = treeForRead(seg); return tree.putx(row); } catch (IgniteCheckedException e) { throw DbException.convert(e); } finally { InlineIndexHelper.clearCurrentInlineIndexes(); } } /** {@inheritDoc} */ @Override public GridH2Row remove(SearchRow row) { try { InlineIndexHelper.setCurrentInlineIndexes(inlineIdxs); int seg = segmentForRow(row); H2Tree tree = treeForRead(seg); return tree.remove(row); } catch (IgniteCheckedException e) { throw DbException.convert(e); } finally { InlineIndexHelper.clearCurrentInlineIndexes(); } } /** {@inheritDoc} */ @Override public void removex(SearchRow row) { try { InlineIndexHelper.setCurrentInlineIndexes(inlineIdxs); int seg = segmentForRow(row); H2Tree tree = treeForRead(seg); tree.removex(row); } catch (IgniteCheckedException e) { throw DbException.convert(e); } finally { InlineIndexHelper.clearCurrentInlineIndexes(); } } /** {@inheritDoc} */ @Override public double getCost(Session ses, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder, HashSet<Column> allColumnsSet) { long rowCnt = getRowCountApproximation(); double baseCost = getCostRangeIndex(masks, rowCnt, filters, filter, sortOrder, false, allColumnsSet); int mul = getDistributedMultiplier(ses, filters, filter); return mul * baseCost; } /** {@inheritDoc} */ @Override public long getRowCount(Session ses) { Cursor cursor = find(ses, null, null); long res = 0; while (cursor.next()) res++; return res; } /** {@inheritDoc} */ @Override public long getRowCountApproximation() { return 10_000; // TODO } /** {@inheritDoc} */ @Override public boolean canGetFirstOrLast() { return true; } /** {@inheritDoc} */ @Override public Cursor findFirstOrLast(Session session, boolean b) { try { int seg = threadLocalSegment(); H2Tree tree = treeForRead(seg); GridH2Row row = b ? tree.findFirst(): tree.findLast(); return new SingleRowCursor(row); } catch (IgniteCheckedException e) { throw DbException.convert(e); } } /** {@inheritDoc} */ @Override public void destroy() { try { if (cctx.affinityNode()) { for (H2Tree tree : segments) { tree.destroy(); cctx.offheap().dropRootPageForIndex(tree.getName()); } } } catch (IgniteCheckedException e) { throw new IgniteException(e); } finally { super.destroy(); } } /** {@inheritDoc} */ @Nullable @Override protected IgniteTree<SearchRow, GridH2Row> doTakeSnapshot() { int seg = threadLocalSegment(); return treeForRead(seg); } /** {@inheritDoc} */ @Override protected H2Tree treeForRead(int segment) { return segments[segment]; } /** {@inheritDoc} */ @Override protected GridCursor<GridH2Row> doFind0( IgniteTree t, @Nullable SearchRow first, boolean includeFirst, @Nullable SearchRow last, IndexingQueryFilter filter) { try { GridCursor<GridH2Row> range = t.find(first, last); if (range == null) return EMPTY_CURSOR; return filter(range, filter); } catch (IgniteCheckedException e) { throw DbException.convert(e); } } /** * @param inlineIdxs Inline index helpers. * @param cfgInlineSize Inline size from cache config. * @return Inline size. */ private int computeInlineSize(List<InlineIndexHelper> inlineIdxs, int cfgInlineSize) { int confSize = cctx.config().getSqlIndexMaxInlineSize(); int propSize = confSize == -1 ? IgniteSystemProperties.getInteger(IgniteSystemProperties.IGNITE_MAX_INDEX_PAYLOAD_SIZE, IGNITE_MAX_INDEX_PAYLOAD_SIZE_DEFAULT) : confSize; if (cfgInlineSize == 0) return 0; if (F.isEmpty(inlineIdxs)) return 0; if (cfgInlineSize == -1) { if (propSize == 0) return 0; int size = 0; for (InlineIndexHelper idxHelper : inlineIdxs) { if (idxHelper.size() <= 0) { size = propSize; break; } // 1 byte type + size size += idxHelper.size() + 1; } return Math.min(PageIO.MAX_PAYLOAD_SIZE, size); } else return Math.min(PageIO.MAX_PAYLOAD_SIZE, cfgInlineSize); } /** * @param name Name. * @return RootPage for meta page. * @throws IgniteCheckedException If failed. */ private RootPage getMetaPage(String name, int segIdx) throws IgniteCheckedException { return cctx.offheap().rootPageForIndex(name + "%" + segIdx); } }