/*
* 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.opt;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.cache.GridCacheContext;
import org.apache.ignite.internal.processors.cache.distributed.dht.GridReservable;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.spi.indexing.IndexingQueryFilter;
import org.jetbrains.annotations.Nullable;
import org.jsr166.ConcurrentHashMap8;
import static org.apache.ignite.internal.processors.query.h2.opt.DistributedJoinMode.OFF;
import static org.apache.ignite.internal.processors.query.h2.opt.GridH2QueryType.MAP;
/**
* Thread local SQL query context which is intended to be accessible from everywhere.
*/
public class GridH2QueryContext {
/** */
private static final ThreadLocal<GridH2QueryContext> qctx = new ThreadLocal<>();
/** */
private static final ConcurrentMap<Key, GridH2QueryContext> qctxs = new ConcurrentHashMap8<>();
/** */
private final Key key;
/** */
private volatile boolean cleared;
/** Index snapshots. */
@GridToStringInclude
private Map<Long, Object> snapshots;
/** */
private List<GridReservable> reservations;
/** Range streams for indexes. */
private Map<Integer, Object> streams;
/** Range sources for indexes. */
private Map<SourceKey, Object> sources;
/** */
private int batchLookupIdGen;
/** */
private IndexingQueryFilter filter;
/** */
private AffinityTopologyVersion topVer;
/** */
private Map<UUID, int[]> partsMap;
/** */
private UUID[] partsNodes;
/** */
private DistributedJoinMode distributedJoinMode;
/** */
private int pageSize;
/** */
private GridH2CollocationModel qryCollocationMdl;
/**
* @param locNodeId Local node ID.
* @param nodeId The node who initiated the query.
* @param qryId The query ID.
* @param type Query type.
*/
public GridH2QueryContext(UUID locNodeId, UUID nodeId, long qryId, GridH2QueryType type) {
assert type != MAP;
key = new Key(locNodeId, nodeId, qryId, 0, type);
}
/**
* @param locNodeId Local node ID.
* @param nodeId The node who initiated the query.
* @param qryId The query ID.
* @param segmentId Index segment ID.
* @param type Query type.
*/
public GridH2QueryContext(UUID locNodeId, UUID nodeId, long qryId, int segmentId, GridH2QueryType type) {
assert segmentId == 0 || type == MAP;
key = new Key(locNodeId, nodeId, qryId, segmentId, type);
}
/**
* @return Type.
*/
public GridH2QueryType type() {
return key.type;
}
/**
* @return Origin node ID.
*/
public UUID originNodeId() {
return key.nodeId;
}
/**
* @return Query request ID.
*/
public long queryId() {
return key.qryId;
}
/**
* @return Query collocation model.
*/
public GridH2CollocationModel queryCollocationModel() {
return qryCollocationMdl;
}
/**
* @param qryCollocationMdl Query collocation model.
*/
public void queryCollocationModel(GridH2CollocationModel qryCollocationMdl) {
this.qryCollocationMdl = qryCollocationMdl;
}
/**
* @param distributedJoinMode Distributed join mode.
* @return {@code this}.
*/
public GridH2QueryContext distributedJoinMode(DistributedJoinMode distributedJoinMode) {
this.distributedJoinMode = distributedJoinMode;
return this;
}
/**
* @return Distributed join mode.
*/
public DistributedJoinMode distributedJoinMode() {
return distributedJoinMode;
}
/**
* @param reservations Reserved partitions or group reservations.
* @return {@code this}.
*/
public GridH2QueryContext reservations(List<GridReservable> reservations) {
this.reservations = reservations;
return this;
}
/**
* @param topVer Topology version.
* @return {@code this}.
*/
public GridH2QueryContext topologyVersion(AffinityTopologyVersion topVer) {
this.topVer = topVer;
return this;
}
/**
* @return Topology version.
*/
public AffinityTopologyVersion topologyVersion() {
return topVer;
}
/**
* @param partsMap Partitions map.
* @return {@code this}.
*/
public GridH2QueryContext partitionsMap(Map<UUID,int[]> partsMap) {
this.partsMap = partsMap;
return this;
}
/**
* @return Partitions map.
*/
public Map<UUID,int[]> partitionsMap() {
return partsMap;
}
/**
* @param p Partition.
* @param cctx Cache context.
* @return Owning node ID.
*/
public UUID nodeForPartition(int p, GridCacheContext<?, ?> cctx) {
UUID[] nodeIds = partsNodes;
if (nodeIds == null) {
assert partsMap != null;
nodeIds = new UUID[cctx.affinity().partitions()];
for (Map.Entry<UUID, int[]> e : partsMap.entrySet()) {
UUID nodeId = e.getKey();
int[] nodeParts = e.getValue();
assert nodeId != null;
assert !F.isEmpty(nodeParts);
for (int part : nodeParts) {
assert nodeIds[part] == null;
nodeIds[part] = nodeId;
}
}
partsNodes = nodeIds;
}
return nodeIds[p];
}
/** @return index segment ID. */
public int segment() {
return key.segmentId;
}
/**
* @param idxId Index ID.
* @param snapshot Index snapshot.
*/
public void putSnapshot(long idxId, Object snapshot) {
assert snapshot != null;
assert get() == null : "need to snapshot indexes before setting query context for correct visibility";
if (snapshot instanceof GridReservable && !((GridReservable)snapshot).reserve())
throw new IllegalStateException("Must be already reserved before.");
if (snapshots == null)
snapshots = new HashMap<>();
if (snapshots.put(idxId, snapshot) != null)
throw new IllegalStateException("Index already snapshoted.");
}
/**
* Clear taken snapshots.
*/
public void clearSnapshots() {
if (F.isEmpty(snapshots))
return;
for (Object snapshot : snapshots.values()) {
if (snapshot instanceof GridReservable)
((GridReservable)snapshot).release();
}
snapshots = null;
}
/**
* @param idxId Index ID.
* @return Index snapshot or {@code null} if none.
*/
@SuppressWarnings("unchecked")
public <T> T getSnapshot(long idxId) {
if (snapshots == null)
return null;
return (T)snapshots.get(idxId);
}
/**
* @param batchLookupId Batch lookup ID.
* @param streams Range streams.
*/
public synchronized void putStreams(int batchLookupId, Object streams) {
if (this.streams == null) {
if (streams == null)
return;
this.streams = new HashMap<>();
}
if (streams == null)
this.streams.remove(batchLookupId);
else
this.streams.put(batchLookupId, streams);
}
/**
* @param batchLookupId Batch lookup ID.
* @return Range streams.
*/
@SuppressWarnings("unchecked")
public synchronized <T> T getStreams(int batchLookupId) {
if (streams == null)
return null;
return (T)streams.get(batchLookupId);
}
/**
* @param ownerId Owner node ID.
* @param segmentId Index segment ID.
* @param batchLookupId Batch lookup ID.
* @param src Range source.
*/
public synchronized void putSource(UUID ownerId, int segmentId, int batchLookupId, Object src) {
SourceKey srcKey = new SourceKey(ownerId, segmentId, batchLookupId);
if (src != null) {
if (sources == null)
sources = new HashMap<>();
sources.put(srcKey, src);
}
else if (sources != null)
sources.remove(srcKey);
}
/**
* @param ownerId Owner node ID.
* @param segmentId Index segment ID.
* @param batchLookupId Batch lookup ID.
* @return Range source.
*/
@SuppressWarnings("unchecked")
public synchronized <T> T getSource(UUID ownerId, int segmentId, int batchLookupId) {
if (sources == null)
return null;
return (T)sources.get(new SourceKey(ownerId, segmentId, batchLookupId));
}
/**
* @return Next batch ID.
*/
public int nextBatchLookupId() {
return ++batchLookupIdGen;
}
/**
* @return If indexes were snapshotted before query execution.
*/
public boolean hasIndexSnapshots() {
return snapshots != null;
}
/**
* Sets current thread local context. This method must be called when all the non-volatile properties are
* already set to ensure visibility for other threads.
*
* @param x Query context.
*/
public static void set(GridH2QueryContext x) {
assert qctx.get() == null;
// We need MAP query context to be available to other threads to run distributed joins.
if (x.key.type == MAP && x.distributedJoinMode() != OFF && qctxs.putIfAbsent(x.key, x) != null)
throw new IllegalStateException("Query context is already set.");
qctx.set(x);
}
/**
* Drops current thread local context.
*/
public static void clearThreadLocal() {
GridH2QueryContext x = qctx.get();
assert x != null;
qctx.remove();
}
/**
* @param locNodeId Local node ID.
* @param nodeId The node who initiated the query.
* @param qryId The query ID.
* @param type Query type.
* @return {@code True} if context was found.
*/
public static boolean clear(UUID locNodeId, UUID nodeId, long qryId, GridH2QueryType type) {
boolean res = false;
for (Key key : qctxs.keySet()) {
if (key.locNodeId.equals(locNodeId) && key.nodeId.equals(nodeId) && key.qryId == qryId && key.type == type)
res |= doClear(new Key(locNodeId, nodeId, qryId, key.segmentId, type), false);
}
return res;
}
/**
* @param key Context key.
* @param nodeStop Node is stopping.
* @return {@code True} if context was found.
*/
private static boolean doClear(Key key, boolean nodeStop) {
assert key.type == MAP : key.type;
GridH2QueryContext x = qctxs.remove(key);
if (x == null)
return false;
assert x.key.equals(key);
x.clearContext(nodeStop);
return true;
}
/**
* @param nodeStop Node is stopping.
*/
public void clearContext(boolean nodeStop) {
cleared = true;
clearSnapshots();
List<GridReservable> r = reservations;
if (!nodeStop && !F.isEmpty(r)) {
for (int i = 0; i < r.size(); i++)
r.get(i).release();
}
}
/**
* @return {@code true} If the context is cleared.
*/
public boolean isCleared() {
return cleared;
}
/**
* @param locNodeId Local node ID.
* @param nodeId Dead node ID.
*/
public static void clearAfterDeadNode(UUID locNodeId, UUID nodeId) {
for (Key key : qctxs.keySet()) {
if (key.locNodeId.equals(locNodeId) && key.nodeId.equals(nodeId))
doClear(key, false);
}
}
/**
* @param locNodeId Local node ID.
*/
public static void clearLocalNodeStop(UUID locNodeId) {
for (Key key : qctxs.keySet()) {
if (key.locNodeId.equals(locNodeId))
doClear(key, true);
}
}
/**
* Access current thread local query context (if it was set).
*
* @return Current thread local query context or {@code null} if the query runs outside of Ignite context.
*/
@Nullable public static GridH2QueryContext get() {
return qctx.get();
}
/**
* Access query context from another thread.
*
* @param locNodeId Local node ID.
* @param nodeId The node who initiated the query.
* @param qryId The query ID.
* @param segmentId Index segment ID.
* @param type Query type.
* @return Query context.
*/
@Nullable public static GridH2QueryContext get(
UUID locNodeId,
UUID nodeId,
long qryId,
int segmentId,
GridH2QueryType type
) {
return qctxs.get(new Key(locNodeId, nodeId, qryId, segmentId, type));
}
/**
* @return Filter.
*/
public IndexingQueryFilter filter() {
return filter;
}
/**
* @param filter Filter.
* @return {@code this}.
*/
public GridH2QueryContext filter(IndexingQueryFilter filter) {
this.filter = filter;
return this;
}
/**
* @return Page size.
*/
public int pageSize() {
return pageSize;
}
/**
* @param pageSize Page size.
* @return {@code this}.
*/
public GridH2QueryContext pageSize(int pageSize) {
this.pageSize = pageSize;
return this;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(GridH2QueryContext.class, this);
}
/**
* Unique key for the query context.
*/
private static class Key {
/** */
private final UUID locNodeId;
/** */
private final UUID nodeId;
/** */
private final long qryId;
/** */
private final int segmentId;
/** */
private final GridH2QueryType type;
/**
* @param locNodeId Local node ID.
* @param nodeId The node who initiated the query.
* @param qryId The query ID.
* @param segmentId Index segment ID.
* @param type Query type.
*/
private Key(UUID locNodeId, UUID nodeId, long qryId, int segmentId, GridH2QueryType type) {
assert locNodeId != null;
assert nodeId != null;
assert type != null;
this.locNodeId = locNodeId;
this.nodeId = nodeId;
this.qryId = qryId;
this.segmentId = segmentId;
this.type = type;
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Key key = (Key)o;
return qryId == key.qryId && nodeId.equals(key.nodeId) && type == key.type &&
locNodeId.equals(key.locNodeId) ;
}
/** {@inheritDoc} */
@Override public int hashCode() {
int res = locNodeId.hashCode();
res = 31 * res + nodeId.hashCode();
res = 31 * res + (int)(qryId ^ (qryId >>> 32));
res = 31 * res + type.hashCode();
res = 31 * res + segmentId;
return res;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(Key.class, this);
}
}
/**
* Key for source.
*/
private static final class SourceKey {
/** */
UUID ownerId;
/** */
int segmentId;
/** */
int batchLookupId;
/**
* @param ownerId Owner node ID.
* @param segmentId Index segment ID.
* @param batchLookupId Batch lookup ID.
*/
SourceKey(UUID ownerId, int segmentId, int batchLookupId) {
this.ownerId = ownerId;
this.segmentId = segmentId;
this.batchLookupId = batchLookupId;
}
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
SourceKey srcKey = (SourceKey)o;
return batchLookupId == srcKey.batchLookupId && segmentId == srcKey.segmentId &&
ownerId.equals(srcKey.ownerId);
}
/** {@inheritDoc} */
@Override public int hashCode() {
int hash = ownerId.hashCode();
hash = 31 * hash + segmentId;
return 31 * hash + batchLookupId;
}
}
}