/*
* 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);
}
}