blob: 52cfad910463eea26ec7ea9b7e61e46cd08271c1 [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.phoenix.iterate;
import static org.apache.phoenix.util.EncodedColumnsUtil.getMinMaxQualifiersFromScan;
import static org.apache.phoenix.util.ScanUtil.getDummyResult;
import static org.apache.phoenix.util.ScanUtil.getPageSizeMsForRegionScanner;
import static org.apache.phoenix.util.ScanUtil.isDummy;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.regionserver.Region;
import org.apache.hadoop.hbase.regionserver.RegionScanner;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.WritableUtils;
import org.apache.phoenix.cache.GlobalCache;
import org.apache.phoenix.cache.TenantCache;
import org.apache.phoenix.coprocessor.BaseRegionScanner;
import org.apache.phoenix.coprocessor.HashJoinRegionScanner;
import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants;
import org.apache.phoenix.coprocessorclient.MetaDataProtocol;
import org.apache.phoenix.execute.TupleProjector;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.KeyValueColumnExpression;
import org.apache.phoenix.expression.OrderByExpression;
import org.apache.phoenix.expression.SingleCellColumnExpression;
import org.apache.phoenix.expression.function.ArrayIndexFunction;
import org.apache.phoenix.expression.function.JsonQueryFunction;
import org.apache.phoenix.expression.function.JsonValueFunction;
import org.apache.phoenix.expression.function.ScalarFunction;
import org.apache.phoenix.hbase.index.covered.update.ColumnReference;
import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.hbase.index.util.VersionUtil;
import org.apache.phoenix.index.IndexMaintainer;
import org.apache.phoenix.join.HashJoinInfo;
import org.apache.phoenix.mapreduce.util.PhoenixConfigurationUtil;
import org.apache.phoenix.memory.MemoryManager;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.query.QueryServices;
import org.apache.phoenix.query.QueryServicesOptions;
import org.apache.phoenix.schema.KeyValueSchema;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.ValueBitSet;
import org.apache.phoenix.schema.tuple.ResultTuple;
import org.apache.phoenix.schema.tuple.Tuple;
import org.apache.phoenix.schema.types.PInteger;
import org.apache.phoenix.transaction.PhoenixTransactionContext;
import org.apache.phoenix.transaction.TransactionFactory;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.ClientUtil;
import org.apache.phoenix.util.EncodedColumnsUtil;
import org.apache.phoenix.util.IndexUtil;
import org.apache.phoenix.util.ScanUtil;
import org.apache.phoenix.thirdparty.com.google.common.annotations.VisibleForTesting;
import org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
import org.apache.phoenix.thirdparty.com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NonAggregateRegionScannerFactory extends RegionScannerFactory {
private static final Logger LOGGER =
LoggerFactory.getLogger(NonAggregateRegionScannerFactory.class);
public NonAggregateRegionScannerFactory(RegionCoprocessorEnvironment env) {
this.env = env;
}
@Override
public RegionScanner getRegionScanner(final Scan scan, final RegionScanner s) throws Throwable {
ImmutableBytesWritable ptr = new ImmutableBytesWritable();
int offset = 0;
if (ScanUtil.isLocalIndex(scan)) {
/*
* For local indexes, we need to set an offset on row key expressions to skip
* the region start key.
*/
Region region = getRegion();
offset = region.getRegionInfo().getStartKey().length != 0 ?
region.getRegionInfo().getStartKey().length :
region.getRegionInfo().getEndKey().length;
ScanUtil.setRowKeyOffset(scan, offset);
}
byte[] scanOffsetBytes = scan.getAttribute(BaseScannerRegionObserverConstants.SCAN_OFFSET);
Integer scanOffset = null;
if (scanOffsetBytes != null) {
scanOffset = (Integer)PInteger.INSTANCE.toObject(scanOffsetBytes);
}
RegionScanner innerScanner = s;
PTable.QualifierEncodingScheme encodingScheme = EncodedColumnsUtil.getQualifierEncodingScheme(scan);
boolean useNewValueColumnQualifier = EncodedColumnsUtil.useNewValueColumnQualifier(scan);
Set<KeyValueColumnExpression> serverParsedKVRefs = Sets.newHashSet();
KeyValueSchema kvSchema = null;
ValueBitSet kvSchemaBitSet = null;
List<Expression> resultList = getServerParsedExpressions(scan, serverParsedKVRefs);
Expression[] serverParsedFuncRefs = resultList.toArray(new Expression[0]);
if (serverParsedFuncRefs != null && serverParsedFuncRefs.length > 0) {
KeyValueSchema.KeyValueSchemaBuilder
builder =
new KeyValueSchema.KeyValueSchemaBuilder(0);
for (Expression expression : serverParsedFuncRefs) {
builder.addField(expression);
}
kvSchema = builder.build();
kvSchemaBitSet = ValueBitSet.newInstance(kvSchema);
}
TupleProjector tupleProjector = null;
Region dataRegion = null;
IndexMaintainer indexMaintainer = null;
byte[][] viewConstants = null;
PhoenixTransactionContext tx = null;
ColumnReference[] dataColumns = IndexUtil.deserializeDataTableColumnsToJoin(scan);
if (dataColumns != null || ScanUtil.isUncoveredGlobalIndex(scan)) {
if (dataColumns != null) {
tupleProjector = IndexUtil.getTupleProjector(scan, dataColumns);
}
dataRegion = env.getRegion();
int clientVersion = ScanUtil.getClientVersion(scan);
List<IndexMaintainer> indexMaintainers =
IndexUtil.deSerializeIndexMaintainersFromScan(scan);
indexMaintainer = indexMaintainers.get(0);
viewConstants = IndexUtil.deserializeViewConstantsFromScan(scan);
byte[] txState = scan.getAttribute(BaseScannerRegionObserverConstants.TX_STATE);
tx = TransactionFactory.getTransactionContext(txState, clientVersion);
}
final TupleProjector p = TupleProjector.deserializeProjectorFromScan(scan);
final HashJoinInfo j = HashJoinInfo.deserializeHashJoinFromScan(scan);
boolean useQualifierAsIndex = EncodedColumnsUtil.useQualifierAsIndex(getMinMaxQualifiersFromScan(scan))
&& scan.getAttribute(BaseScannerRegionObserverConstants.TOPN) != null;
// setting dataRegion in case of a non-coprocessor environment
if (dataRegion == null &&
env.getConfiguration().get(PhoenixConfigurationUtil.SNAPSHOT_NAME_KEY) != null) {
dataRegion = env.getRegion();
}
innerScanner =
getWrappedScanner(env, innerScanner, serverParsedKVRefs, serverParsedFuncRefs,
offset, scan, dataColumns, tupleProjector, dataRegion, indexMaintainer, tx,
viewConstants, kvSchema, kvSchemaBitSet, j == null ? p : null, ptr,
useQualifierAsIndex);
final ImmutableBytesPtr tenantId = ScanUtil.getTenantId(scan);
if (j != null) {
innerScanner =
new HashJoinRegionScanner(env, innerScanner, scan, serverParsedKVRefs,
serverParsedFuncRefs, p, j, tenantId, useQualifierAsIndex,
useNewValueColumnQualifier);
}
if (scanOffset != null) {
final boolean isIncompatibleClient =
ScanUtil.isIncompatibleClientForServerReturnValidRowKey(scan);
innerScanner = getOffsetScanner(
innerScanner,
new OffsetResultIterator(
new RegionScannerResultIterator(
innerScanner,
getMinMaxQualifiersFromScan(scan),
encodingScheme),
scanOffset,
getPageSizeMsForRegionScanner(scan),
isIncompatibleClient),
scan.getAttribute(QueryConstants.LAST_SCAN) != null,
isIncompatibleClient,
scan);
}
boolean spoolingEnabled =
env.getConfiguration().getBoolean(
QueryServices.SERVER_ORDERBY_SPOOLING_ENABLED_ATTRIB,
QueryServicesOptions.DEFAULT_SERVER_ORDERBY_SPOOLING_ENABLED);
long thresholdBytes =
env.getConfiguration()
.getLongBytes(QueryServices.SERVER_SPOOL_THRESHOLD_BYTES_ATTRIB,
QueryServicesOptions.DEFAULT_SERVER_SPOOL_THRESHOLD_BYTES);
final OrderedResultIterator iterator =
deserializeFromScan(scan, innerScanner, spoolingEnabled, thresholdBytes);
if (iterator == null) {
return innerScanner;
}
// TODO:the above wrapped scanner should be used here also
return getTopNScanner(env, innerScanner, iterator, tenantId);
}
private List<Expression> getServerParsedExpressions(Scan scan,
Set<KeyValueColumnExpression> serverParsedKVRefs) {
Expression[] serverParsedArrayFuncRefs = null;
if (scan.getAttribute(BaseScannerRegionObserverConstants.SPECIFIC_ARRAY_INDEX) != null) {
serverParsedArrayFuncRefs =
deserializeServerParsedPositionalExpressionInfoFromScan(scan,
BaseScannerRegionObserverConstants.SPECIFIC_ARRAY_INDEX, serverParsedKVRefs);
}
List<Expression> resultList = new ArrayList<>();
if (serverParsedArrayFuncRefs != null) {
Collections.addAll(resultList, serverParsedArrayFuncRefs);
}
Expression[] serverParsedJsonValueFuncRefs = null;
if (scan.getAttribute(BaseScannerRegionObserverConstants.JSON_VALUE_FUNCTION) != null) {
serverParsedJsonValueFuncRefs =
deserializeServerParsedPositionalExpressionInfoFromScan(scan,
BaseScannerRegionObserverConstants.JSON_VALUE_FUNCTION, serverParsedKVRefs);
}
if (serverParsedJsonValueFuncRefs != null) {
Collections.addAll(resultList, serverParsedJsonValueFuncRefs);
}
Expression[] serverParsedJsonQueryFuncRefs = null;
if (scan.getAttribute(BaseScannerRegionObserverConstants.JSON_QUERY_FUNCTION) != null) {
serverParsedJsonQueryFuncRefs =
deserializeServerParsedPositionalExpressionInfoFromScan(scan,
BaseScannerRegionObserverConstants.JSON_QUERY_FUNCTION, serverParsedKVRefs);
}
if (serverParsedJsonQueryFuncRefs != null) {
Collections.addAll(resultList, serverParsedJsonQueryFuncRefs);
}
return resultList;
}
@VisibleForTesting
static OrderedResultIterator deserializeFromScan(Scan scan, RegionScanner s,
boolean spoolingEnabled, long thresholdBytes) {
byte[] topN = scan.getAttribute(BaseScannerRegionObserverConstants.TOPN);
if (topN == null) {
return null;
}
int clientVersion = ScanUtil.getClientVersion(scan);
// Client including and after 4.15 and 5.1 are not going to serialize thresholdBytes
// so we need to decode this only for older clients to not break wire compat
boolean shouldDecodeSpoolThreshold =
(scan.getAttribute(BaseScannerRegionObserverConstants.CLIENT_VERSION) == null)
|| (VersionUtil.decodeMajorVersion(clientVersion) > 5)
|| (VersionUtil.decodeMajorVersion(clientVersion) == 5
&& clientVersion < MetaDataProtocol.MIN_5_x_DISABLE_SERVER_SPOOL_THRESHOLD)
|| (VersionUtil.decodeMajorVersion(clientVersion) == 4
&& clientVersion < MetaDataProtocol.MIN_4_x_DISABLE_SERVER_SPOOL_THRESHOLD);
ByteArrayInputStream stream = new ByteArrayInputStream(topN); // TODO: size?
try {
DataInputStream input = new DataInputStream(stream);
if (shouldDecodeSpoolThreshold) {
// Read off the scan but ignore, we won't honor client sent thresholdbytes, but the
// one set on server
WritableUtils.readVInt(input);
}
int limit = WritableUtils.readVInt(input);
int estimatedRowSize = WritableUtils.readVInt(input);
int size = WritableUtils.readVInt(input);
List<OrderByExpression> orderByExpressions = Lists.newArrayListWithExpectedSize(size);
for (int i = 0; i < size; i++) {
OrderByExpression orderByExpression = new OrderByExpression();
orderByExpression.readFields(input);
orderByExpressions.add(orderByExpression);
}
PTable.QualifierEncodingScheme encodingScheme =
EncodedColumnsUtil.getQualifierEncodingScheme(scan);
ResultIterator inner = new RegionScannerResultIterator(s,
EncodedColumnsUtil.getMinMaxQualifiersFromScan(scan), encodingScheme);
return new OrderedResultIterator(inner, orderByExpressions, spoolingEnabled,
thresholdBytes, limit >= 0 ? limit : null, null, estimatedRowSize,
getPageSizeMsForRegionScanner(scan), scan, s.getRegionInfo());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
stream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private Expression[] deserializeServerParsedPositionalExpressionInfoFromScan(Scan scan,
String scanAttribute, Set<KeyValueColumnExpression> serverParsedKVRefs) {
byte[] specificArrayIdx = scan.getAttribute(scanAttribute);
if (specificArrayIdx == null) {
return null;
}
ByteArrayInputStream stream = new ByteArrayInputStream(specificArrayIdx);
try {
DataInputStream input = new DataInputStream(stream);
int kvRefSize = WritableUtils.readVInt(input);
for (int i = 0; i < kvRefSize; i++) {
PTable.ImmutableStorageScheme scheme = EncodedColumnsUtil.getImmutableStorageScheme(scan);
KeyValueColumnExpression kvExp = scheme != PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN ? new SingleCellColumnExpression(scheme)
: new KeyValueColumnExpression();
kvExp.readFields(input);
serverParsedKVRefs.add(kvExp);
}
int kvFuncSize = WritableUtils.readVInt(input);
Expression[] funcRefs = new Expression[kvFuncSize];
for (int i = 0; i < kvFuncSize; i++) {
ScalarFunction func = null;
if (scanAttribute.equals(BaseScannerRegionObserverConstants.SPECIFIC_ARRAY_INDEX)) {
func = new ArrayIndexFunction();
} else if (scanAttribute.equals(BaseScannerRegionObserverConstants.JSON_VALUE_FUNCTION)) {
func = new JsonValueFunction();
} else if (scanAttribute.equals(BaseScannerRegionObserverConstants.JSON_QUERY_FUNCTION)) {
func = new JsonQueryFunction();
}
if (func != null) {
func.readFields(input);
funcRefs[i] = func;
}
}
return funcRefs;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
stream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private RegionScanner getOffsetScanner(final RegionScanner s,
final OffsetResultIterator iterator,
final boolean isLastScan,
final boolean incompatibleClient,
final Scan scan)
throws IOException {
final Tuple firstTuple;
final Region region = getRegion();
region.startRegionOperation();
final byte[] initStartRowKey = scan.getStartRow().length > 0 ? scan.getStartRow() :
(scan.isReversed() ? region.getRegionInfo().getEndKey() :
region.getRegionInfo().getStartKey());
byte[] prevScanStartRowKey =
scan.getAttribute(BaseScannerRegionObserverConstants.SCAN_ACTUAL_START_ROW);
// If the region has moved after server has returned dummy or valid row to client,
// prevScanStartRowKey would be different from actual scan start rowkey.
// If the region moves after dummy was returned, we do not need to set row count to
// offset. However, if the region moves after valid row was returned, we do need to
// set row count to offset because we return valid row only after offset num of rows
// are skipped.
if (Bytes.compareTo(prevScanStartRowKey, initStartRowKey) != 0 && Bytes.compareTo(
ByteUtil.concat(prevScanStartRowKey, ByteUtil.ZERO_BYTE),
initStartRowKey) != 0) {
iterator.setRowCountToOffset();
}
try {
Tuple tuple = iterator.next();
if (tuple == null && !isLastScan) {
List<Cell> kvList = new ArrayList<>(1);
KeyValue kv;
byte[] remainingOffset =
PInteger.INSTANCE.toBytes(iterator.getRemainingOffset());
if (incompatibleClient) {
kv = new KeyValue(
QueryConstants.OFFSET_ROW_KEY_BYTES,
QueryConstants.OFFSET_FAMILY,
QueryConstants.OFFSET_COLUMN,
remainingOffset);
} else {
Tuple lastScannedTuple = iterator.getLastScannedTuple();
if (lastScannedTuple != null) {
kv = getOffsetKvWithLastScannedRowKey(remainingOffset, lastScannedTuple);
} else {
byte[] rowKey;
byte[] startKey = scan.getStartRow().length > 0 ? scan.getStartRow() :
region.getRegionInfo().getStartKey();
byte[] endKey = scan.getStopRow().length > 0 ? scan.getStopRow() :
region.getRegionInfo().getEndKey();
rowKey = ByteUtil.getLargestPossibleRowKeyInRange(startKey, endKey);
if (rowKey == null) {
if (scan.includeStartRow()) {
rowKey = startKey;
} else if (scan.includeStopRow()) {
rowKey = endKey;
} else {
rowKey = HConstants.EMPTY_END_ROW;
}
}
kv = new KeyValue(
rowKey,
QueryConstants.OFFSET_FAMILY,
QueryConstants.OFFSET_COLUMN,
remainingOffset);
}
}
kvList.add(kv);
Result r = Result.create(kvList);
firstTuple = new ResultTuple(r);
} else {
firstTuple = tuple;
}
} catch (Throwable t) {
ClientUtil.throwIOException(getRegion().getRegionInfo().getRegionNameAsString(), t);
return null;
} finally {
region.closeRegionOperation();
}
return new BaseRegionScanner(s) {
private Tuple tuple = firstTuple;
private byte[] previousResultRowKey;
@Override
public boolean isFilterDone() {
return tuple == null;
}
@Override
public boolean next(List<Cell> results) throws IOException {
try {
if (isFilterDone()) {
return false;
}
Tuple nextTuple = iterator.next();
if (tuple.size() > 0 && !isDummy(tuple)) {
for (int i = 0; i < tuple.size(); i++) {
results.add(tuple.getValue(i));
if (i == 0) {
previousResultRowKey = CellUtil.cloneRow(tuple.getValue(i));
}
}
} else {
if (nextTuple == null) {
byte[] remainingOffset =
PInteger.INSTANCE.toBytes(iterator.getRemainingOffset());
KeyValue kv;
if (incompatibleClient) {
kv = new KeyValue(
QueryConstants.OFFSET_ROW_KEY_BYTES,
QueryConstants.OFFSET_FAMILY,
QueryConstants.OFFSET_COLUMN,
remainingOffset);
} else {
kv = getOffsetKvWithLastScannedRowKey(remainingOffset, tuple);
}
results.add(kv);
} else {
updateDummyWithPrevRowKey(results, initStartRowKey,
previousResultRowKey);
}
}
tuple = nextTuple;
return !isFilterDone();
} catch (Throwable t) {
LOGGER.error("Error while iterating Offset scanner.", t);
ClientUtil.throwIOException(getRegion().getRegionInfo().getRegionNameAsString(), t);
return false;
}
}
@Override
public void close() throws IOException {
try {
s.close();
} finally {
try {
iterator.close();
} catch (SQLException e) {
ClientUtil.throwIOException(getRegion().getRegionInfo().getRegionNameAsString(), e);
}
}
}
};
}
/**
* Add dummy cell to the result list based on either the previous rowkey returned to the
* client or the start rowkey of the scan or region start key.
*
* @param result result row.
* @param initStartRowKey scan start rowkey.
* @param previousResultRowKey previous result rowkey returned to client.
*/
private void updateDummyWithPrevRowKey(final List<Cell> result,
final byte[] initStartRowKey,
final byte[] previousResultRowKey) {
result.clear();
if (previousResultRowKey != null) {
getDummyResult(previousResultRowKey, result);
} else {
getDummyResult(initStartRowKey, result);
}
}
private static KeyValue getOffsetKvWithLastScannedRowKey(byte[] value, Tuple tuple) {
ImmutableBytesWritable ptr = new ImmutableBytesWritable();
tuple.getKey(ptr);
byte[] rowKey = new byte[ptr.getLength()];
System.arraycopy(ptr.get(), ptr.getOffset(), rowKey, 0,
rowKey.length);
return new KeyValue(
rowKey,
QueryConstants.OFFSET_FAMILY,
QueryConstants.OFFSET_COLUMN,
value);
}
/**
* Return region scanner that does TopN.
* We only need to call startRegionOperation and closeRegionOperation when
* getting the first Tuple (which forces running through the entire region)
* since after this everything is held in memory
*/
private RegionScanner getTopNScanner(RegionCoprocessorEnvironment env, final RegionScanner s,
final OrderedResultIterator iterator, ImmutableBytesPtr tenantId) throws Throwable {
final Tuple firstTuple;
TenantCache tenantCache = GlobalCache.getTenantCache(env, tenantId);
long estSize = iterator.getEstimatedByteSize();
final MemoryManager.MemoryChunk chunk = tenantCache.getMemoryManager().allocate(estSize);
final Region region = getRegion();
region.startRegionOperation();
try {
// Once we return from the first call to next, we've run through and cached
// the topN rows, so we no longer need to start/stop a region operation.
firstTuple = iterator.next();
// Now that the topN are cached, we can resize based on the real size
long actualSize = iterator.getByteSize();
chunk.resize(actualSize);
} catch (Throwable t) {
ClientUtil.throwIOException(region.getRegionInfo().getRegionNameAsString(), t);
return null;
} finally {
region.closeRegionOperation();
}
return new BaseRegionScanner(s) {
private Tuple tuple = firstTuple;
@Override
public boolean isFilterDone() {
return tuple == null;
}
@Override
public boolean next(List<Cell> results) throws IOException {
try {
if (isFilterDone()) {
return false;
}
if (isDummy(tuple)) {
ScanUtil.getDummyResult(CellUtil.cloneRow(tuple.getValue(0)), results);
} else {
for (int i = 0; i < tuple.size(); i++) {
results.add(tuple.getValue(i));
}
}
tuple = iterator.next();
return !isFilterDone();
} catch (Throwable t) {
ClientUtil.throwIOException(region.getRegionInfo().getRegionNameAsString(), t);
return false;
}
}
@Override
public void close() throws IOException {
try {
s.close();
} finally {
try {
if(iterator != null) {
iterator.close();
}
} catch (SQLException e) {
ClientUtil.throwIOException(region.getRegionInfo().getRegionNameAsString(), e);
} finally {
chunk.close();
}
}
}
};
}
}