blob: 6e181ce8250365f8ff519a96f8a9f9437599dee8 [file] [log] [blame]
/*
* 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.cache.QueryIndexType;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
import org.apache.ignite.internal.processors.query.GridQueryProperty;
import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.processors.query.h2.database.H2PkHashIndex;
import org.apache.ignite.internal.processors.query.h2.database.H2TreeIndexBase;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2IndexBase;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2RowDescriptor;
import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
import org.apache.ignite.internal.processors.query.h2.opt.GridLuceneIndex;
import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitor;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.h2.index.Index;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Information about table in database.
*/
public class H2TableDescriptor {
/** PK index name. */
public static final String PK_IDX_NAME = "_key_PK";
/** PK hash index name. */
public static final String PK_HASH_IDX_NAME = "_key_PK_hash";
/** Affinity key index name. */
public static final String AFFINITY_KEY_IDX_NAME = "AFFINITY_KEY";
/** Indexing. */
private final IgniteH2Indexing idx;
/** */
private final String fullTblName;
/** */
private final GridQueryTypeDescriptor type;
/** Schema name. */
private final String schemaName;
/** Cache context info. */
private final GridCacheContextInfo cacheInfo;
/** */
private GridH2Table tbl;
/** */
private GridLuceneIndex luceneIdx;
/** */
private H2PkHashIndex pkHashIdx;
/** Flag of table has been created from SQL*/
private boolean isSql;
/**
* Constructor.
*
* @param idx Indexing.
* @param schemaName Schema name.
* @param type Type descriptor.
* @param cacheInfo Cache context info.
* @param isSql {@code true} in case table has been created from SQL.
*/
public H2TableDescriptor(IgniteH2Indexing idx, String schemaName, GridQueryTypeDescriptor type,
GridCacheContextInfo cacheInfo, boolean isSql) {
this.idx = idx;
this.type = type;
this.schemaName = schemaName;
this.cacheInfo = cacheInfo;
this.isSql = isSql;
fullTblName = H2Utils.withQuotes(schemaName) + "." + H2Utils.withQuotes(type.tableName());
}
/**
* @return {@code true} In case table was created from SQL.
*/
public boolean sql() {
return isSql;
}
/**
* @return Indexing.
*/
public IgniteH2Indexing indexing() {
return idx;
}
/**
* @return Table.
*/
public GridH2Table table() {
return tbl;
}
/**
* @param tbl Table.
*/
public void table(GridH2Table tbl) {
this.tbl = tbl;
}
/**
* @return Schema name.
*/
public String schemaName() {
return schemaName;
}
/**
* @return Table name.
*/
String tableName() {
return type.tableName();
}
/**
* @return Database full table name.
*/
String fullTableName() {
return fullTblName;
}
/**
* @return type name.
*/
String typeName() {
return type.name();
}
/**
* @return Cache name.
*/
public String cacheName() {
return cacheInfo.name();
}
/**
* @return Cache context info.
*/
public GridCacheContextInfo cacheInfo() {
return cacheInfo;
}
/**
* @return Cache context.
*/
public GridCacheContext cache() {
return cacheInfo.cacheContext();
}
/**
* @return Type.
*/
GridQueryTypeDescriptor type() {
return type;
}
/**
* @return Lucene index.
*/
GridLuceneIndex luceneIndex() {
return luceneIdx;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(H2TableDescriptor.class, this);
}
/**
* Create list of indexes. First must be primary key, after that all unique indexes and only then non-unique
* indexes. All indexes must be subtypes of {@link H2TreeIndexBase}.
*
* @param tbl Table to create indexes for.
* @return List of indexes.
*/
public ArrayList<Index> createSystemIndexes(GridH2Table tbl) {
ArrayList<Index> idxs = new ArrayList<>();
IndexColumn keyCol = tbl.indexColumn(QueryUtils.KEY_COL, SortOrder.ASCENDING);
IndexColumn affCol = tbl.getAffinityKeyColumn();
if (affCol != null && H2Utils.equals(affCol, keyCol))
affCol = null;
List<IndexColumn> unwrappedKeyAndAffinityCols = extractKeyColumns(tbl, keyCol, affCol);
List<IndexColumn> wrappedKeyCols = H2Utils.treeIndexColumns(tbl.rowDescriptor(),
new ArrayList<>(2), keyCol, affCol);
Index hashIdx = createHashIndex(
tbl,
wrappedKeyCols
);
if (hashIdx != null)
idxs.add(hashIdx);
// Add primary key index.
Index pkIdx = idx.createSortedIndex(
PK_IDX_NAME,
tbl,
true,
false,
unwrappedKeyAndAffinityCols,
wrappedKeyCols,
-1,
null
);
idxs.add(pkIdx);
if (type().valueClass() == String.class) {
try {
luceneIdx = new GridLuceneIndex(idx.kernalContext(), tbl.cacheName(), type);
}
catch (IgniteCheckedException e1) {
throw new IgniteException(e1);
}
}
GridQueryIndexDescriptor textIdx = type.textIndex();
if (textIdx != null) {
try {
luceneIdx = new GridLuceneIndex(idx.kernalContext(), tbl.cacheName(), type);
}
catch (IgniteCheckedException e1) {
throw new IgniteException(e1);
}
}
// Locate index where affinity column is first (if any).
if (affCol != null) {
boolean affIdxFound = false;
for (GridQueryIndexDescriptor idxDesc : type.indexes().values()) {
if (idxDesc.type() != QueryIndexType.SORTED)
continue;
String firstField = idxDesc.fields().iterator().next();
Column col = tbl.getColumn(firstField);
IndexColumn idxCol = tbl.indexColumn(col.getColumnId(),
idxDesc.descending(firstField) ? SortOrder.DESCENDING : SortOrder.ASCENDING);
affIdxFound |= H2Utils.equals(idxCol, affCol);
}
// Add explicit affinity key index if nothing alike was found.
if (!affIdxFound) {
List<IndexColumn> unwrappedKeyCols = extractKeyColumns(tbl, keyCol, null);
ArrayList<IndexColumn> colsWithUnwrappedKey = new ArrayList<>(unwrappedKeyCols.size());
colsWithUnwrappedKey.add(affCol);
//We need to reorder PK columns to have affinity key as first column, that's why we can't use simple PK columns
H2Utils.addUniqueColumns(colsWithUnwrappedKey, unwrappedKeyCols);
List<IndexColumn> cols = H2Utils.treeIndexColumns(tbl.rowDescriptor(), new ArrayList<>(2), affCol, keyCol);
idxs.add(idx.createSortedIndex(
AFFINITY_KEY_IDX_NAME,
tbl,
false,
true,
colsWithUnwrappedKey,
cols,
-1,
null)
);
}
}
return idxs;
}
/**
* Create list of affinity and key index columns. Key, if it possible, partitions into simple components.
*
* @param tbl GridH2Table instance
* @param keyCol Key index column.
* @param affCol Affinity index column.
*
* @return List of key and affinity columns. Key's, if it possible, splitted into simple components.
*/
@NotNull private List<IndexColumn> extractKeyColumns(GridH2Table tbl, IndexColumn keyCol, IndexColumn affCol) {
ArrayList<IndexColumn> keyCols;
if (isSql) {
keyCols = new ArrayList<>(type.fields().size() + 1);
// Check if key is simple type.
if (QueryUtils.isSqlType(type.keyClass()))
keyCols.add(keyCol);
else {
if (!type.primaryKeyFields().isEmpty()) {
for (String keyName : type.primaryKeyFields()) {
GridQueryProperty prop = type.property(keyName);
assert prop.key() : keyName + " is not a key field";
Column col = tbl.getColumn(prop.name());
keyCols.add(tbl.indexColumn(col.getColumnId(), SortOrder.ASCENDING));
}
}
else {
for (String propName : type.fields().keySet()) {
GridQueryProperty prop = type.property(propName);
if (prop.key()) {
Column col = tbl.getColumn(propName);
keyCols.add(tbl.indexColumn(col.getColumnId(), SortOrder.ASCENDING));
}
}
}
// If key is object but the user has not specified any particular columns,
// we have to fall back to whole-key index.
if (keyCols.isEmpty())
keyCols.add(keyCol);
}
}
else {
keyCols = new ArrayList<>(2);
keyCols.add(keyCol);
}
if (affCol != null && !H2Utils.containsColumn(keyCols, affCol))
keyCols.add(affCol);
else
keyCols.trimToSize();
return Collections.unmodifiableList(keyCols);
}
/**
* Get collection of user indexes.
*
* @return User indexes.
*/
public Collection<GridH2IndexBase> createUserIndexes() {
assert tbl != null;
ArrayList<GridH2IndexBase> res = new ArrayList<>();
for (GridQueryIndexDescriptor idxDesc : type.indexes().values()) {
GridH2IndexBase idx = createUserIndex(idxDesc, null);
res.add(idx);
}
return res;
}
/**
* Create user index.
*
* @param idxDesc Index descriptor.
* @param cacheVisitor Cache visitor.
* @return Index.
*/
@SuppressWarnings("ZeroLengthArrayAllocation")
public GridH2IndexBase createUserIndex(GridQueryIndexDescriptor idxDesc, @Nullable SchemaIndexCacheVisitor cacheVisitor) {
IndexColumn keyCol = tbl.indexColumn(QueryUtils.KEY_COL, SortOrder.ASCENDING);
IndexColumn affCol = tbl.getAffinityKeyColumn();
List<IndexColumn> cols = new ArrayList<>(idxDesc.fields().size() + 2);
for (String field : idxDesc.fields()) {
Column col = tbl.getColumn(field);
cols.add(tbl.indexColumn(col.getColumnId(),
idxDesc.descending(field) ? SortOrder.DESCENDING : SortOrder.ASCENDING));
}
GridH2RowDescriptor desc = tbl.rowDescriptor();
if (idxDesc.type() == QueryIndexType.SORTED) {
List<IndexColumn> unwrappedKeyCols = extractKeyColumns(tbl, keyCol, affCol);
List<IndexColumn> colsWithUnwrappedKey = new ArrayList<>(cols);
H2Utils.addUniqueColumns(colsWithUnwrappedKey, unwrappedKeyCols);
cols = H2Utils.treeIndexColumns(desc, cols, keyCol, affCol);
return idx.createSortedIndex(
idxDesc.name(),
tbl,
false,
false,
colsWithUnwrappedKey,
cols,
idxDesc.inlineSize(),
cacheVisitor
);
}
else if (idxDesc.type() == QueryIndexType.GEOSPATIAL)
return H2Utils.createSpatialIndex(tbl, idxDesc.name(), cols);
throw new IllegalStateException("Index type: " + idxDesc.type());
}
/**
* Create hash index.
*
* @param tbl Table.
* @param cols Columns.
* @return Index.
*/
private Index createHashIndex(GridH2Table tbl, List<IndexColumn> cols) {
if (cacheInfo.affinityNode()) {
assert pkHashIdx == null : pkHashIdx;
pkHashIdx = new H2PkHashIndex(cacheInfo.cacheContext(), tbl, PK_HASH_IDX_NAME, cols,
tbl.rowDescriptor().context().config().getQueryParallelism());
return pkHashIdx;
}
return null;
}
/**
* Handle drop.
*/
void onDrop() {
tbl.destroy();
U.closeQuiet(luceneIdx);
}
}