/*
* 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.Iterator;
import java.util.List;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.IgniteCacheOffheapManager;
import org.apache.ignite.internal.processors.cache.KeyCacheObject;
import org.apache.ignite.internal.processors.cache.database.CacheDataRow;
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.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.message.DbException;
import org.h2.result.Row;
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.jetbrains.annotations.Nullable;
/**
*
*/
public class H2PkHashIndex extends GridH2IndexBase {
/** */
private final GridH2Table tbl;
/** */
private final GridCacheContext cctx;
/**
* @param cctx Cache context.
* @param tbl Table.
* @param name Index name.
* @param colsList Index columns.
*/
public H2PkHashIndex(
GridCacheContext<?, ?> cctx,
GridH2Table tbl,
String name,
List<IndexColumn> colsList
) {
IndexColumn[] cols = colsList.toArray(new IndexColumn[colsList.size()]);
IndexColumn.mapColumns(cols, tbl);
initBaseIndex(tbl, 0, name, cols, IndexType.createPrimaryKey(false, true));
this.tbl = tbl;
this.cctx = cctx;
}
/** {@inheritDoc} */
@Override protected int segmentsCount() {
return 1;
}
/** {@inheritDoc} */
@Override public Cursor find(Session ses, final SearchRow lower, final SearchRow upper) {
IndexingQueryFilter f = threadLocalFilter();
IgniteBiPredicate<Object, Object> p = null;
if (f != null) {
String spaceName = getTable().spaceName();
p = f.forSpace(spaceName);
}
KeyCacheObject lowerObj = null;
KeyCacheObject upperObj = null;
if (lower != null)
lowerObj = cctx.toCacheKeyObject(lower.getValue(0).getObject());
if (upper != null)
upperObj = cctx.toCacheKeyObject(upper.getValue(0).getObject());
try {
List<GridCursor<? extends CacheDataRow>> cursors = new ArrayList<>();
for (IgniteCacheOffheapManager.CacheDataStore store : cctx.offheap().cacheDataStores())
cursors.add(store.cursor(lowerObj, upperObj));
return new H2Cursor(new CompositeGridCursor<>(cursors.iterator()), p);
}
catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
}
/** {@inheritDoc} */
@Override public boolean canScan() {
return false;
}
/** {@inheritDoc} */
@Override public GridH2Row findOne(GridH2Row row) {
try {
for (IgniteCacheOffheapManager.CacheDataStore store : cctx.offheap().cacheDataStores()) {
CacheDataRow found = store.find(row.key);
if (found != null)
tbl.rowDescriptor().createRow(row.key(), row.partition(), row.value(), row.version(), 0);
}
return null;
}
catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
}
/** {@inheritDoc} */
@SuppressWarnings("StatementWithEmptyBody")
@Override public GridH2Row put(GridH2Row row) {
// Should not be called directly. Rows are inserted into underlying cache data stores.
assert false;
throw DbException.getUnsupportedException("put");
}
/** {@inheritDoc} */
@Override public GridH2Row remove(SearchRow row) {
// Should not be called directly. Rows are removed from underlying cache data stores.
assert false;
throw DbException.getUnsupportedException("remove");
}
/** {@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);
// TODO : How to calculate cost?
// return mul * baseCost;
return Double.MAX_VALUE;
}
/** {@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 false;
}
/** {@inheritDoc} */
@Override public Cursor findFirstOrLast(Session ses, boolean b) {
throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
@Nullable @Override protected IgniteTree doTakeSnapshot() {
throw new AssertionError("This method must not be called for PK index");
}
/**
* Cursor.
*/
private class H2Cursor implements Cursor {
/** */
final GridCursor<? extends CacheDataRow> cursor;
/** */
final IgniteBiPredicate<Object, Object> filter;
/**
* @param cursor Cursor.
* @param filter Filter.
*/
private H2Cursor(GridCursor<? extends CacheDataRow> cursor, IgniteBiPredicate<Object, Object> filter) {
assert cursor != null;
this.cursor = cursor;
this.filter = filter;
}
/** {@inheritDoc} */
@Override public Row get() {
try {
CacheDataRow dataRow = cursor.get();
GridH2Row row = tbl.rowDescriptor().createRow(dataRow.key(), dataRow.partition(), dataRow.value(), dataRow.version(), 0);
row.link(dataRow.link());
return row;
}
catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
}
/** {@inheritDoc} */
@Override public SearchRow getSearchRow() {
return get();
}
/** {@inheritDoc} */
@Override public boolean next() {
try {
while (cursor.next()) {
if (filter == null)
return true;
CacheDataRow dataRow = cursor.get();
GridH2Row row = tbl.rowDescriptor().createRow(dataRow.key(), dataRow.partition(), dataRow.value(), dataRow.version(), 0);
row.link(dataRow.link());
Object key = row.getValue(0).getObject();
Object val = row.getValue(1).getObject();
assert key != null;
assert val != null;
if (filter.apply(key, val))
return true;
}
return false;
}
catch (IgniteCheckedException e) {
throw DbException.convert(e);
}
}
/** {@inheritDoc} */
@Override public boolean previous() {
throw DbException.getUnsupportedException("previous");
}
}
/**
*
*/
private static class CompositeGridCursor<T> implements GridCursor<T> {
/** */
private final Iterator<GridCursor<? extends T>> iter;
/** */
private GridCursor<? extends T> curr;
/**
*
*/
public CompositeGridCursor(Iterator<GridCursor<? extends T>> iter) {
this.iter = iter;
if (iter.hasNext())
curr = iter.next();
}
/** {@inheritDoc} */
@Override public boolean next() throws IgniteCheckedException {
if (curr.next())
return true;
while (iter.hasNext()) {
curr = iter.next();
if (curr.next())
return true;
}
return false;
}
/** {@inheritDoc} */
@Override public T get() throws IgniteCheckedException {
return curr.get();
}
}
}