blob: ffd447c255ff11027ec04a7056182eff5c6c4bd1 [file] [log] [blame]
/*
* Copyright 2009-2013 by The Regents of the University of California
* Licensed 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 from
*
* 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 edu.uci.ics.asterix.optimizer.rules.am;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
import edu.uci.ics.asterix.aql.util.FunctionUtils;
import edu.uci.ics.asterix.common.config.DatasetConfig.IndexType;
import edu.uci.ics.asterix.metadata.entities.Dataset;
import edu.uci.ics.asterix.metadata.entities.Index;
import edu.uci.ics.asterix.metadata.utils.DatasetUtils;
import edu.uci.ics.asterix.om.base.ABoolean;
import edu.uci.ics.asterix.om.base.AInt32;
import edu.uci.ics.asterix.om.base.AString;
import edu.uci.ics.asterix.om.base.IAObject;
import edu.uci.ics.asterix.om.constants.AsterixConstantValue;
import edu.uci.ics.asterix.om.functions.AsterixBuiltinFunctions;
import edu.uci.ics.asterix.om.types.ARecordType;
import edu.uci.ics.asterix.om.types.IAType;
import edu.uci.ics.asterix.om.util.NonTaggedFormatUtil;
import edu.uci.ics.hyracks.algebricks.common.exceptions.AlgebricksException;
import edu.uci.ics.hyracks.algebricks.common.utils.Pair;
import edu.uci.ics.hyracks.algebricks.core.algebra.base.ILogicalExpression;
import edu.uci.ics.hyracks.algebricks.core.algebra.base.ILogicalOperator;
import edu.uci.ics.hyracks.algebricks.core.algebra.base.IOptimizationContext;
import edu.uci.ics.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
import edu.uci.ics.hyracks.algebricks.core.algebra.base.LogicalVariable;
import edu.uci.ics.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
import edu.uci.ics.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
import edu.uci.ics.hyracks.algebricks.core.algebra.expressions.IAlgebricksConstantValue;
import edu.uci.ics.hyracks.algebricks.core.algebra.expressions.ScalarFunctionCallExpression;
import edu.uci.ics.hyracks.algebricks.core.algebra.expressions.UnnestingFunctionCallExpression;
import edu.uci.ics.hyracks.algebricks.core.algebra.expressions.VariableReferenceExpression;
import edu.uci.ics.hyracks.algebricks.core.algebra.functions.IFunctionInfo;
import edu.uci.ics.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator.ExecutionMode;
import edu.uci.ics.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
import edu.uci.ics.hyracks.algebricks.core.algebra.operators.logical.OrderOperator;
import edu.uci.ics.hyracks.algebricks.core.algebra.operators.logical.OrderOperator.IOrder;
import edu.uci.ics.hyracks.algebricks.core.algebra.operators.logical.UnnestMapOperator;
/**
* Static helper functions for rewriting plans using indexes.
*/
public class AccessMethodUtils {
public static void appendPrimaryIndexTypes(Dataset dataset, IAType itemType, List<Object> target)
throws IOException {
ARecordType recordType = (ARecordType) itemType;
List<String> partitioningKeys = DatasetUtils.getPartitioningKeys(dataset);
for (String partitioningKey : partitioningKeys) {
target.add(recordType.getFieldType(partitioningKey));
}
target.add(itemType);
}
public static ConstantExpression createStringConstant(String str) {
return new ConstantExpression(new AsterixConstantValue(new AString(str)));
}
public static ConstantExpression createInt32Constant(int i) {
return new ConstantExpression(new AsterixConstantValue(new AInt32(i)));
}
public static ConstantExpression createBooleanConstant(boolean b) {
if (b) {
return new ConstantExpression(new AsterixConstantValue(ABoolean.TRUE));
} else {
return new ConstantExpression(new AsterixConstantValue(ABoolean.FALSE));
}
}
public static String getStringConstant(Mutable<ILogicalExpression> expr) {
IAObject obj = ((AsterixConstantValue) ((ConstantExpression) expr.getValue()).getValue()).getObject();
return ((AString) obj).getStringValue();
}
public static int getInt32Constant(Mutable<ILogicalExpression> expr) {
IAObject obj = ((AsterixConstantValue) ((ConstantExpression) expr.getValue()).getValue()).getObject();
return ((AInt32) obj).getIntegerValue();
}
public static boolean getBooleanConstant(Mutable<ILogicalExpression> expr) {
IAObject obj = ((AsterixConstantValue) ((ConstantExpression) expr.getValue()).getValue()).getObject();
return ((ABoolean) obj).getBoolean();
}
public static boolean analyzeFuncExprArgsForOneConstAndVar(AbstractFunctionCallExpression funcExpr,
AccessMethodAnalysisContext analysisCtx) {
IAlgebricksConstantValue constFilterVal = null;
LogicalVariable fieldVar = null;
ILogicalExpression arg1 = funcExpr.getArguments().get(0).getValue();
ILogicalExpression arg2 = funcExpr.getArguments().get(1).getValue();
// One of the args must be a constant, and the other arg must be a variable.
if (arg1.getExpressionTag() == LogicalExpressionTag.CONSTANT
&& arg2.getExpressionTag() == LogicalExpressionTag.VARIABLE) {
ConstantExpression constExpr = (ConstantExpression) arg1;
constFilterVal = constExpr.getValue();
VariableReferenceExpression varExpr = (VariableReferenceExpression) arg2;
fieldVar = varExpr.getVariableReference();
} else if (arg1.getExpressionTag() == LogicalExpressionTag.VARIABLE
&& arg2.getExpressionTag() == LogicalExpressionTag.CONSTANT) {
ConstantExpression constExpr = (ConstantExpression) arg2;
constFilterVal = constExpr.getValue();
VariableReferenceExpression varExpr = (VariableReferenceExpression) arg1;
fieldVar = varExpr.getVariableReference();
} else {
return false;
}
analysisCtx.matchedFuncExprs.add(new OptimizableFuncExpr(funcExpr, fieldVar, constFilterVal));
return true;
}
public static boolean analyzeFuncExprArgsForTwoVars(AbstractFunctionCallExpression funcExpr,
AccessMethodAnalysisContext analysisCtx) {
LogicalVariable fieldVar1 = null;
LogicalVariable fieldVar2 = null;
ILogicalExpression arg1 = funcExpr.getArguments().get(0).getValue();
ILogicalExpression arg2 = funcExpr.getArguments().get(1).getValue();
if (arg1.getExpressionTag() == LogicalExpressionTag.VARIABLE
&& arg2.getExpressionTag() == LogicalExpressionTag.VARIABLE) {
fieldVar1 = ((VariableReferenceExpression) arg1).getVariableReference();
fieldVar2 = ((VariableReferenceExpression) arg2).getVariableReference();
} else {
return false;
}
analysisCtx.matchedFuncExprs.add(new OptimizableFuncExpr(funcExpr,
new LogicalVariable[] { fieldVar1, fieldVar2 }, null));
return true;
}
public static int getNumSecondaryKeys(Index index, ARecordType recordType) throws AlgebricksException {
switch (index.getIndexType()) {
case BTREE:
case WORD_INVIX:
case NGRAM_INVIX: {
return index.getKeyFieldNames().size();
}
case RTREE: {
Pair<IAType, Boolean> keyPairType = Index.getNonNullableKeyFieldType(index.getKeyFieldNames().get(0),
recordType);
IAType keyType = keyPairType.first;
int numDimensions = NonTaggedFormatUtil.getNumDimensions(keyType.getTypeTag());
return numDimensions * 2;
}
default: {
throw new AlgebricksException("Unknown index kind: " + index.getIndexType());
}
}
}
/**
* Appends the types of the fields produced by the given secondary index to dest.
*/
public static void appendSecondaryIndexTypes(Dataset dataset, ARecordType recordType, Index index,
boolean primaryKeysOnly, List<Object> dest) throws AlgebricksException {
if (!primaryKeysOnly) {
switch (index.getIndexType()) {
case BTREE:
case WORD_INVIX:
case NGRAM_INVIX: {
for (String sk : index.getKeyFieldNames()) {
Pair<IAType, Boolean> keyPairType = Index.getNonNullableKeyFieldType(sk, recordType);
dest.add(keyPairType.first);
}
break;
}
case RTREE: {
Pair<IAType, Boolean> keyPairType = Index.getNonNullableKeyFieldType(index.getKeyFieldNames()
.get(0), recordType);
IAType keyType = keyPairType.first;
IAType nestedKeyType = NonTaggedFormatUtil.getNestedSpatialType(keyType.getTypeTag());
int numKeys = getNumSecondaryKeys(index, recordType);
for (int i = 0; i < numKeys; i++) {
dest.add(nestedKeyType);
}
break;
}
}
}
// Primary keys.
List<String> partitioningKeys = DatasetUtils.getPartitioningKeys(dataset);
for (String partitioningKey : partitioningKeys) {
try {
dest.add(recordType.getFieldType(partitioningKey));
} catch (IOException e) {
throw new AlgebricksException(e);
}
}
}
public static void appendSecondaryIndexOutputVars(Dataset dataset, ARecordType recordType, Index index,
boolean primaryKeysOnly, IOptimizationContext context, List<LogicalVariable> dest)
throws AlgebricksException {
int numPrimaryKeys = DatasetUtils.getPartitioningKeys(dataset).size();
int numSecondaryKeys = getNumSecondaryKeys(index, recordType);
int numVars = (primaryKeysOnly) ? numPrimaryKeys : numPrimaryKeys + numSecondaryKeys;
for (int i = 0; i < numVars; i++) {
dest.add(context.newVar());
}
}
public static List<LogicalVariable> getPrimaryKeyVarsFromUnnestMap(Dataset dataset, ILogicalOperator unnestMapOp) {
int numPrimaryKeys = DatasetUtils.getPartitioningKeys(dataset).size();
List<LogicalVariable> primaryKeyVars = new ArrayList<LogicalVariable>();
List<LogicalVariable> sourceVars = ((UnnestMapOperator) unnestMapOp).getVariables();
// Assumes the primary keys are located at the end.
int start = sourceVars.size() - numPrimaryKeys;
int stop = sourceVars.size();
for (int i = start; i < stop; i++) {
primaryKeyVars.add(sourceVars.get(i));
}
return primaryKeyVars;
}
/**
* Returns the search key expression which feeds a secondary-index search. If we are optimizing a selection query then this method returns
* the a ConstantExpression from the first constant value in the optimizable function expression.
* If we are optimizing a join, then this method returns the VariableReferenceExpression that should feed the secondary index probe.
*/
public static ILogicalExpression createSearchKeyExpr(IOptimizableFuncExpr optFuncExpr,
OptimizableOperatorSubTree indexSubTree, OptimizableOperatorSubTree probeSubTree) {
if (probeSubTree == null) {
// We are optimizing a selection query. Search key is a constant.
return new ConstantExpression(optFuncExpr.getConstantVal(0));
} else {
// We are optimizing a join query. Determine which variable feeds the secondary index.
if (optFuncExpr.getOperatorSubTree(0) == null || optFuncExpr.getOperatorSubTree(0) == probeSubTree) {
return new VariableReferenceExpression(optFuncExpr.getLogicalVar(0));
} else {
return new VariableReferenceExpression(optFuncExpr.getLogicalVar(1));
}
}
}
/**
* Returns the first expr optimizable by this index.
*/
public static IOptimizableFuncExpr chooseFirstOptFuncExpr(Index chosenIndex, AccessMethodAnalysisContext analysisCtx) {
List<Integer> indexExprs = analysisCtx.getIndexExprs(chosenIndex);
int firstExprIndex = indexExprs.get(0);
return analysisCtx.matchedFuncExprs.get(firstExprIndex);
}
public static UnnestMapOperator createSecondaryIndexUnnestMap(Dataset dataset, ARecordType recordType, Index index,
ILogicalOperator inputOp, AccessMethodJobGenParams jobGenParams, IOptimizationContext context,
boolean outputPrimaryKeysOnly, boolean retainInput) throws AlgebricksException {
// The job gen parameters are transferred to the actual job gen via the UnnestMapOperator's function arguments.
ArrayList<Mutable<ILogicalExpression>> secondaryIndexFuncArgs = new ArrayList<Mutable<ILogicalExpression>>();
jobGenParams.writeToFuncArgs(secondaryIndexFuncArgs);
// Variables and types coming out of the secondary-index search.
List<LogicalVariable> secondaryIndexUnnestVars = new ArrayList<LogicalVariable>();
List<Object> secondaryIndexOutputTypes = new ArrayList<Object>();
// Append output variables/types generated by the secondary-index search (not forwarded from input).
appendSecondaryIndexOutputVars(dataset, recordType, index, outputPrimaryKeysOnly, context,
secondaryIndexUnnestVars);
appendSecondaryIndexTypes(dataset, recordType, index, outputPrimaryKeysOnly, secondaryIndexOutputTypes);
// An index search is expressed as an unnest over an index-search function.
IFunctionInfo secondaryIndexSearch = FunctionUtils.getFunctionInfo(AsterixBuiltinFunctions.INDEX_SEARCH);
UnnestingFunctionCallExpression secondaryIndexSearchFunc = new UnnestingFunctionCallExpression(
secondaryIndexSearch, secondaryIndexFuncArgs);
secondaryIndexSearchFunc.setReturnsUniqueValues(true);
// This is the operator that jobgen will be looking for. It contains an unnest function that has all necessary arguments to determine
// which index to use, which variables contain the index-search keys, what is the original dataset, etc.
UnnestMapOperator secondaryIndexUnnestOp = new UnnestMapOperator(secondaryIndexUnnestVars,
new MutableObject<ILogicalExpression>(secondaryIndexSearchFunc), secondaryIndexOutputTypes, retainInput);
secondaryIndexUnnestOp.getInputs().add(new MutableObject<ILogicalOperator>(inputOp));
context.computeAndSetTypeEnvironmentForOperator(secondaryIndexUnnestOp);
secondaryIndexUnnestOp.setExecutionMode(ExecutionMode.PARTITIONED);
return secondaryIndexUnnestOp;
}
public static UnnestMapOperator createPrimaryIndexUnnestMap(DataSourceScanOperator dataSourceScan, Dataset dataset,
ARecordType recordType, ILogicalOperator inputOp, IOptimizationContext context, boolean sortPrimaryKeys,
boolean retainInput, boolean requiresBroadcast) throws AlgebricksException {
List<LogicalVariable> primaryKeyVars = AccessMethodUtils.getPrimaryKeyVarsFromUnnestMap(dataset, inputOp);
// Optionally add a sort on the primary-index keys before searching the primary index.
OrderOperator order = null;
if (sortPrimaryKeys) {
order = new OrderOperator();
for (LogicalVariable pkVar : primaryKeyVars) {
Mutable<ILogicalExpression> vRef = new MutableObject<ILogicalExpression>(
new VariableReferenceExpression(pkVar));
order.getOrderExpressions().add(
new Pair<IOrder, Mutable<ILogicalExpression>>(OrderOperator.ASC_ORDER, vRef));
}
// The secondary-index search feeds into the sort.
order.getInputs().add(new MutableObject<ILogicalOperator>(inputOp));
order.setExecutionMode(ExecutionMode.LOCAL);
context.computeAndSetTypeEnvironmentForOperator(order);
}
// The job gen parameters are transferred to the actual job gen via the UnnestMapOperator's function arguments.
List<Mutable<ILogicalExpression>> primaryIndexFuncArgs = new ArrayList<Mutable<ILogicalExpression>>();
BTreeJobGenParams jobGenParams = new BTreeJobGenParams(dataset.getDatasetName(), IndexType.BTREE,
dataset.getDataverseName(), dataset.getDatasetName(), retainInput, requiresBroadcast);
// Set low/high inclusive to true for a point lookup.
jobGenParams.setLowKeyInclusive(true);
jobGenParams.setHighKeyInclusive(true);
jobGenParams.setLowKeyVarList(primaryKeyVars, 0, primaryKeyVars.size());
jobGenParams.setHighKeyVarList(primaryKeyVars, 0, primaryKeyVars.size());
jobGenParams.writeToFuncArgs(primaryIndexFuncArgs);
// Variables and types coming out of the primary-index search.
List<LogicalVariable> primaryIndexUnnestVars = new ArrayList<LogicalVariable>();
List<Object> primaryIndexOutputTypes = new ArrayList<Object>();
// Append output variables/types generated by the primary-index search (not forwarded from input).
primaryIndexUnnestVars.addAll(dataSourceScan.getVariables());
try {
appendPrimaryIndexTypes(dataset, recordType, primaryIndexOutputTypes);
} catch (IOException e) {
throw new AlgebricksException(e);
}
// An index search is expressed as an unnest over an index-search function.
IFunctionInfo primaryIndexSearch = FunctionUtils.getFunctionInfo(AsterixBuiltinFunctions.INDEX_SEARCH);
AbstractFunctionCallExpression primaryIndexSearchFunc = new ScalarFunctionCallExpression(primaryIndexSearch,
primaryIndexFuncArgs);
// This is the operator that jobgen will be looking for. It contains an unnest function that has all necessary arguments to determine
// which index to use, which variables contain the index-search keys, what is the original dataset, etc.
UnnestMapOperator primaryIndexUnnestOp = new UnnestMapOperator(primaryIndexUnnestVars,
new MutableObject<ILogicalExpression>(primaryIndexSearchFunc), primaryIndexOutputTypes, retainInput);
// Fed by the order operator or the secondaryIndexUnnestOp.
if (sortPrimaryKeys) {
primaryIndexUnnestOp.getInputs().add(new MutableObject<ILogicalOperator>(order));
} else {
primaryIndexUnnestOp.getInputs().add(new MutableObject<ILogicalOperator>(inputOp));
}
context.computeAndSetTypeEnvironmentForOperator(primaryIndexUnnestOp);
primaryIndexUnnestOp.setExecutionMode(ExecutionMode.PARTITIONED);
return primaryIndexUnnestOp;
}
}