blob: 531bd8ac87230617e836b8949760d520dd036cdc [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.asterix.optimizer.rules;
import java.util.ArrayList;
import java.util.List;
import org.apache.asterix.common.exceptions.CompilationException;
import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.common.metadata.DataverseName;
import org.apache.asterix.common.metadata.Namespace;
import org.apache.asterix.metadata.declared.MetadataProvider;
import org.apache.asterix.metadata.utils.FullTextUtil;
import org.apache.asterix.om.base.ARecord;
import org.apache.asterix.om.base.AString;
import org.apache.asterix.om.constants.AsterixConstantValue;
import org.apache.asterix.om.functions.BuiltinFunctions;
import org.apache.asterix.om.types.ARecordType;
import org.apache.asterix.om.types.ATypeTag;
import org.apache.asterix.om.utils.ConstantExpressionUtil;
import org.apache.asterix.runtime.evaluators.functions.FullTextContainsFunctionDescriptor;
import org.apache.commons.lang3.mutable.Mutable;
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
import org.apache.hyracks.algebricks.core.algebra.expressions.ConstantExpression;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
import org.apache.hyracks.algebricks.core.algebra.util.OperatorPropertiesUtil;
import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionReferenceTransform;
import org.apache.hyracks.algebricks.core.rewriter.base.IAlgebraicRewriteRule;
import org.apache.hyracks.api.exceptions.SourceLocation;
import com.google.common.base.Strings;
/**
* Checks whether the given parameters of the ftcontains() function are correct during the compilation,
* and fetch the full-text config from metadata which is necessary for the ftcontains() function
*/
public class FullTextContainsParameterCheckAndSetRule implements IAlgebraicRewriteRule {
// Visitor for checking and transforming ftcontains() expression
protected FullTextContainsExpressionVisitor ftcontainsExprVisitor = new FullTextContainsExpressionVisitor();
@Override
public boolean rewritePre(Mutable<ILogicalOperator> opRef, IOptimizationContext context)
throws AlgebricksException {
if (context.checkIfInDontApplySet(this, opRef.getValue())) {
return false;
}
if (checkAndSetParameter(opRef, context)) {
OperatorPropertiesUtil.typeOpRec(opRef, context);
return true;
}
return false;
}
/**
* Check the correctness of the parameters of the ftcontains(). Also rearrange options as arguments.
* The expected form of ftcontains() is ftcontains(expression1, expression2, parameters as a record).
*
* If ftcontains() has the full-text config argument, this method will also fetch it (FullTextConfigDescriptor) from metadata
* and set it in the function expression so that the full-text config can be utilized later at run-time.
*/
private boolean checkAndSetParameter(Mutable<ILogicalOperator> opRef, IOptimizationContext context)
throws AlgebricksException {
AbstractLogicalOperator op = (AbstractLogicalOperator) opRef.getValue();
ftcontainsExprVisitor.setContext(context);
boolean modified = op.acceptExpressionTransform(ftcontainsExprVisitor);
if (modified) {
context.addToDontApplySet(this, op);
}
return modified;
}
/**
* This visitor class handles actual checking and transformation.
*/
protected class FullTextContainsExpressionVisitor implements ILogicalExpressionReferenceTransform {
// the last expression position before the option argument in the arguments array
private static final int LAST_EXPRESSION_POS_BEFORE_OPTION = 1;
// The number of anticipated arguments for a full-text query when a user doesn't provide any option.
private static final int FULLTEXT_QUERY_WITHOUT_OPTION_NO_OF_ARGUMENTS = 2;
// The number of anticipated arguments for a full-text query when a user provide option(s) as a record.
private static final int FULLTEXT_QUERY_WITH_OPTION_NO_OF_ARGUMENTS = 3;
private IOptimizationContext context;
public FullTextContainsExpressionVisitor() {
}
public void setContext(IOptimizationContext context) {
this.context = context;
}
@Override
public boolean transform(Mutable<ILogicalExpression> exprRef) throws AlgebricksException {
ILogicalExpression e = exprRef.getValue();
switch (e.getExpressionTag()) {
case FUNCTION_CALL:
return transformFunctionCallExpression((AbstractFunctionCallExpression) e);
default:
return false;
}
}
private boolean transformFunctionCallExpression(AbstractFunctionCallExpression fce) throws AlgebricksException {
boolean modified = false;
FunctionIdentifier fi = fce.getFunctionIdentifier();
if (fi != BuiltinFunctions.FULLTEXT_CONTAINS && fi != BuiltinFunctions.FULLTEXT_CONTAINS_WO_OPTION) {
for (Mutable<ILogicalExpression> arg : fce.getArguments()) {
if (transform(arg)) {
modified = true;
}
}
} else {
modified = checkParameterForFuncExpr(fce, fi);
}
return modified;
}
private boolean checkParameterForFuncExpr(AbstractFunctionCallExpression funcExpr, FunctionIdentifier fi)
throws AlgebricksException {
// Collects the correct number of arguments - it can be 2 if a user doesn't provide any option.
int numberOfCorrectArguments = 0;
String functionName = "";
if (fi == BuiltinFunctions.FULLTEXT_CONTAINS) {
numberOfCorrectArguments = FULLTEXT_QUERY_WITH_OPTION_NO_OF_ARGUMENTS;
functionName = BuiltinFunctions.FULLTEXT_CONTAINS.getName();
} else if (fi == BuiltinFunctions.FULLTEXT_CONTAINS_WO_OPTION) {
numberOfCorrectArguments = FULLTEXT_QUERY_WITHOUT_OPTION_NO_OF_ARGUMENTS;
functionName = BuiltinFunctions.FULLTEXT_CONTAINS_WO_OPTION.getName();
}
// If numberOfCorrectArguments is greater than zero, then this is a full-text search query.
if (numberOfCorrectArguments > 0) {
List<Mutable<ILogicalExpression>> oldExprs = funcExpr.getArguments();
List<Mutable<ILogicalExpression>> newExprs = new ArrayList<>();
// The number of parameters should be three: exp1, exp2, and the option
if (oldExprs.size() != numberOfCorrectArguments) {
throw CompilationException.create(ErrorCode.COMPILATION_INVALID_PARAMETER_NUMBER,
funcExpr.getSourceLocation(), fi.getName(), oldExprs.size());
}
// The last expression before the option needs to be copied first.
for (int i = 0; i <= LAST_EXPRESSION_POS_BEFORE_OPTION; i++) {
newExprs.add(new MutableObject<ILogicalExpression>(oldExprs.get(i).getValue()));
}
// Sanity check for the types of the first two parameters
checkFirstAndSecondParamter(oldExprs, functionName);
// We cannot make ftConfigName a class-level variable in the visitor class
// because the visitor may be shared between multiple threads and such a variable may be corrupted
String ftConfigName = null;
// Checks and transforms the actual full-text parameters.
if (numberOfCorrectArguments == FULLTEXT_QUERY_WITH_OPTION_NO_OF_ARGUMENTS) {
ftConfigName =
checkValueForThirdParameterAndGetFullTextConfig(oldExprs.get(2), newExprs, functionName);
} else {
// no option provided case: sets the default option here.
setDefaultValueForThirdParameter(newExprs);
}
MetadataProvider metadataProvider = (MetadataProvider) context.getMetadataProvider();
Namespace defaultNamespace = metadataProvider.getDefaultNamespace();
String database = defaultNamespace.getDatabaseName();
DataverseName dataverseName = defaultNamespace.getDataverseName();
funcExpr.setOpaqueParameters(
new Object[] { FullTextUtil.fetchFilterAndCreateConfigEvaluator(metadataProvider, database,
dataverseName, ftConfigName) });
// Resets the last argument.
funcExpr.getArguments().clear();
funcExpr.getArguments().addAll(newExprs);
return true;
}
return false;
}
/**
* Checks the correctness of the first and second argument. If the argument is a constant, we can check
* it now. If the argument is not a constant, we will defer the checking until run-time.
*/
private void checkFirstAndSecondParamter(List<Mutable<ILogicalExpression>> exprs, String functionName)
throws AlgebricksException {
// Check the first parameter - Expression1. If it's a constant, then we can check the type here.
ILogicalExpression firstExpr = exprs.get(0).getValue();
if (firstExpr.getExpressionTag() == LogicalExpressionTag.CONSTANT
&& ConstantExpressionUtil.getConstantIaObjectType(firstExpr) != ATypeTag.STRING) {
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED, firstExpr.getSourceLocation(),
functionName, ConstantExpressionUtil.getConstantIaObjectType(firstExpr));
}
// Check the second parameter - Expression2. If it's a constant, then we can check the type here.
ILogicalExpression secondExpr = exprs.get(1).getValue();
if (secondExpr.getExpressionTag() == LogicalExpressionTag.CONSTANT) {
ATypeTag exprTypeTag = ConstantExpressionUtil.getConstantIaObjectType(secondExpr);
switch (exprTypeTag) {
case STRING:
case MULTISET:
case ARRAY:
break;
default:
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED, secondExpr.getSourceLocation(),
functionName, exprTypeTag);
}
}
}
/**
* Checks the option of the given ftcontains() function. Also, sets default value.
*
* @param expr
* @throws AlgebricksException
*
* @return the full-text config name if specified in the function option,
* null if not specified which implies the default full-text config will be utilized later
*/
private String checkValueForThirdParameterAndGetFullTextConfig(Mutable<ILogicalExpression> expr,
List<Mutable<ILogicalExpression>> newArgs, String functionName) throws AlgebricksException {
String ftConfigName = null;
// Get the last parameter - this should be a record-constructor or a constant expression.
if (expr.getValue().getExpressionTag() == LogicalExpressionTag.CONSTANT) {
ConstantExpression constantExpression = (ConstantExpression) expr.getValue();
ARecord record =
(ARecord) ConstantExpressionUtil.getConstantIaObject(constantExpression, ATypeTag.OBJECT);
if (record == null) {
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED,
constantExpression.getSourceLocation(), functionName,
constantExpression.getExpressionTag());
}
ARecordType recordType = record.getType();
if (record.numberOfFields() > FullTextContainsFunctionDescriptor.getParamTypeMap().size()) {
throw CompilationException.create(ErrorCode.TOO_MANY_OPTIONS_FOR_FUNCTION,
constantExpression.getSourceLocation(), functionName);
}
for (int i = 0; i < record.numberOfFields(); i++) {
String option = recordType.getFieldNames()[i].toLowerCase();
ILogicalExpression optionExpr =
new ConstantExpression(new AsterixConstantValue(new AString(option)));
ILogicalExpression optionExprVal =
new ConstantExpression(new AsterixConstantValue(record.getValueByPos(i)));
ftConfigName = handleThirdParameterOptions(optionExpr, optionExprVal, newArgs, functionName);
}
} else {
AbstractFunctionCallExpression openRecConsExpr = (AbstractFunctionCallExpression) expr.getValue();
FunctionIdentifier openRecConsFi = openRecConsExpr.getFunctionIdentifier();
if (openRecConsFi != BuiltinFunctions.OPEN_RECORD_CONSTRUCTOR
&& openRecConsFi != BuiltinFunctions.CLOSED_RECORD_CONSTRUCTOR) {
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED, openRecConsExpr.getSourceLocation(),
functionName, openRecConsFi);
}
// We multiply 2 because the layout of the arguments are: [expr, val, expr1, val1, ...]
if (openRecConsExpr.getArguments().size() > FullTextContainsFunctionDescriptor.getParamTypeMap().size()
* 2) {
throw CompilationException.create(ErrorCode.TOO_MANY_OPTIONS_FOR_FUNCTION,
openRecConsExpr.getSourceLocation(), functionName);
}
if (openRecConsExpr.getArguments().size() % 2 != 0) {
throw CompilationException.create(ErrorCode.COMPILATION_INVALID_PARAMETER_NUMBER,
openRecConsExpr.getSourceLocation(), functionName);
}
for (int i = 0; i < openRecConsExpr.getArguments().size(); i = i + 2) {
ILogicalExpression optionExpr = openRecConsExpr.getArguments().get(i).getValue();
ILogicalExpression optionExprVal = openRecConsExpr.getArguments().get(i + 1).getValue();
ftConfigName = handleThirdParameterOptions(optionExpr, optionExprVal, newArgs, functionName);
}
}
return ftConfigName;
}
private String handleThirdParameterOptions(ILogicalExpression optionExpr, ILogicalExpression optionExprVal,
List<Mutable<ILogicalExpression>> newArgs, String functionName) throws AlgebricksException {
String ftConfigName = null;
String option = ConstantExpressionUtil.getStringConstant(optionExpr);
if (optionExpr.getExpressionTag() != LogicalExpressionTag.CONSTANT || option == null) {
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED, optionExpr.getSourceLocation(),
functionName, optionExpr.getExpressionTag());
}
option = option.toLowerCase();
if (!FullTextContainsFunctionDescriptor.getParamTypeMap().containsKey(option)) {
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED, optionExprVal.getSourceLocation(),
functionName, option);
}
String optionTypeStringVal = null;
// If the option value is a constant, then we can check here.
if (optionExprVal.getExpressionTag() == LogicalExpressionTag.CONSTANT) {
switch (FullTextContainsFunctionDescriptor.getParamTypeMap().get(option)) {
case STRING:
optionTypeStringVal = ConstantExpressionUtil.getStringConstant(optionExprVal);
if (optionTypeStringVal == null) {
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED,
optionExprVal.getSourceLocation(), functionName, option);
}
optionTypeStringVal = optionTypeStringVal.toLowerCase();
break;
default:
// Currently, we only have a string parameter. So, the flow doesn't reach here.
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED, optionExprVal.getSourceLocation(),
functionName, option);
}
// Check the validity of option value
switch (option) {
case FullTextContainsFunctionDescriptor.SEARCH_MODE_OPTION:
checkSearchModeOption(optionTypeStringVal, functionName, optionExprVal.getSourceLocation());
break;
case FullTextContainsFunctionDescriptor.FULLTEXT_CONFIG_OPTION:
checkFullTextConfigOption(optionTypeStringVal, functionName, optionExprVal.getSourceLocation());
ftConfigName = optionTypeStringVal;
break;
default:
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED, optionExprVal.getSourceLocation(),
functionName, option);
}
}
// Add this option as arguments to the ftcontains().
newArgs.add(new MutableObject<>(optionExpr));
newArgs.add(new MutableObject<>(optionExprVal));
return ftConfigName;
}
private void checkSearchModeOption(String optionVal, String functionName, SourceLocation sourceLoc)
throws AlgebricksException {
if (optionVal.equals(FullTextContainsFunctionDescriptor.SearchMode.ALL.getValue())
|| optionVal.equals(FullTextContainsFunctionDescriptor.SearchMode.ANY.getValue())) {
return;
} else {
throw CompilationException.create(ErrorCode.TYPE_UNSUPPORTED, sourceLoc, functionName, optionVal);
}
}
private void checkFullTextConfigOption(String optionVal, String functionName, SourceLocation sourceLoc)
throws AlgebricksException {
// Currently, here we only check if the full-text config is null or empty string
// We will check if the full-text config exists at run time
if (Strings.isNullOrEmpty(optionVal)) {
throw CompilationException.create(ErrorCode.COMPILATION_INVALID_EXPRESSION, sourceLoc, functionName,
FullTextContainsFunctionDescriptor.FULLTEXT_CONFIG_OPTION, "not-null", "null");
} else {
return;
}
}
/**
* Sets the default option value(s) when a user doesn't provide any option.
*/
void setDefaultValueForThirdParameter(List<Mutable<ILogicalExpression>> newArgs) {
// Sets the search mode option: the default option is conjunctive search.
ILogicalExpression searchModeOptionExpr = new ConstantExpression(
new AsterixConstantValue(new AString(FullTextContainsFunctionDescriptor.SEARCH_MODE_OPTION)));
ILogicalExpression searchModeValExpr = new ConstantExpression(new AsterixConstantValue(
new AString(FullTextContainsFunctionDescriptor.SearchMode.ALL.getValue())));
// Add this option as arguments to the ftcontains().
newArgs.add(new MutableObject<ILogicalExpression>(searchModeOptionExpr));
newArgs.add(new MutableObject<ILogicalExpression>(searchModeValExpr));
// We don't set the full-text config option here because the default value should be null
}
}
}