blob: 27a63d2d7a57eab7e86d39a6d68c07eb17b61e87 [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.lang.sqlpp.rewrites.visitor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.asterix.common.config.DatasetConfig;
import org.apache.asterix.common.exceptions.AsterixException;
import org.apache.asterix.common.exceptions.CompilationException;
import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.common.functions.FunctionSignature;
import org.apache.asterix.common.metadata.DatasetFullyQualifiedName;
import org.apache.asterix.common.metadata.DataverseName;
import org.apache.asterix.common.metadata.MetadataUtil;
import org.apache.asterix.common.metadata.Namespace;
import org.apache.asterix.lang.common.base.Expression;
import org.apache.asterix.lang.common.base.Expression.Kind;
import org.apache.asterix.lang.common.base.ILangExpression;
import org.apache.asterix.lang.common.context.Scope;
import org.apache.asterix.lang.common.expression.CallExpr;
import org.apache.asterix.lang.common.expression.FieldAccessor;
import org.apache.asterix.lang.common.expression.VariableExpr;
import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
import org.apache.asterix.lang.common.statement.ViewDecl;
import org.apache.asterix.lang.common.struct.Identifier;
import org.apache.asterix.lang.common.struct.VarIdentifier;
import org.apache.asterix.lang.common.util.FunctionUtil;
import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
import org.apache.asterix.lang.sqlpp.visitor.CheckDatasetOnlyResolutionVisitor;
import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
import org.apache.asterix.metadata.declared.MetadataProvider;
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.om.functions.BuiltinFunctions;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
import org.apache.hyracks.algebricks.common.utils.Pair;
import org.apache.hyracks.algebricks.common.utils.Quadruple;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
import org.apache.hyracks.api.exceptions.SourceLocation;
public class VariableCheckAndRewriteVisitor extends AbstractSqlppExpressionScopingVisitor {
protected final MetadataProvider metadataProvider;
/**
* @param context,
* manages ids of variables and guarantees uniqueness of variables.
*/
public VariableCheckAndRewriteVisitor(LangRewritingContext context, MetadataProvider metadataProvider,
Collection<VarIdentifier> externalVars) {
super(context, externalVars);
this.metadataProvider = metadataProvider;
}
@Override
public Expression visit(VariableExpr varExpr, ILangExpression parent) throws CompilationException {
if (resolveAsVariableReference(varExpr)) {
return varExpr;
}
Namespace defaultNamespace = metadataProvider.getDefaultNamespace();
DataverseName dataverseName = defaultNamespace.getDataverseName();
String databaseName = defaultNamespace.getDatabaseName();
String datasetName = SqlppVariableUtil.toUserDefinedVariableName(varExpr.getVar().getValue()).getValue();
CallExpr datasetExpr = resolveAsDataset(databaseName, dataverseName, datasetName, parent, varExpr);
return datasetExpr != null ? datasetExpr : resolveAsFieldAccessOverContextVar(varExpr);
}
@Override
public Expression visit(FieldAccessor fa, ILangExpression parent) throws CompilationException {
Expression leadingExpr = fa.getExpr();
if (leadingExpr.getKind() == Kind.VARIABLE_EXPRESSION) {
// resolving a.b
VariableExpr leadingVarExpr = (VariableExpr) leadingExpr;
if (resolveAsVariableReference(leadingVarExpr)) {
return fa;
} else {
String dataverseNamePart =
SqlppVariableUtil.toUserDefinedVariableName(leadingVarExpr.getVar().getValue()).getValue();
DataverseName dataverseName; // 1-part name
try {
dataverseName = DataverseName.createSinglePartName(dataverseNamePart);
} catch (AsterixException e) {
throw new CompilationException(ErrorCode.INVALID_DATABASE_OBJECT_NAME, fa.getSourceLocation(),
dataverseNamePart);
}
//TODO(DB): decide
String databaseName = MetadataUtil.resolveDatabase(null, dataverseName);
String datasetName = fa.getIdent().getValue();
CallExpr datasetExpr =
resolveAsDataset(databaseName, dataverseName, datasetName, parent, leadingVarExpr);
if (datasetExpr != null) {
return datasetExpr;
} else {
fa.setExpr(resolveAsFieldAccessOverContextVar(leadingVarExpr));
return fa;
}
}
} else {
List<String> dataverseNameParts = new ArrayList<>(4);
Pair<VariableExpr, FieldAccessor> topExprs = new Pair<>(null, null);
if (extractDataverseName(fa.getExpr(), dataverseNameParts, topExprs)) {
// resolving a.b.c(.x)*
VariableExpr topVarExpr = topExprs.getFirst(); // = a
if (resolveAsVariableReference(topVarExpr)) {
return fa;
} else {
DataverseName dataverseName;
try {
dataverseName = DataverseName.create(dataverseNameParts);
} catch (AsterixException e) {
throw new CompilationException(ErrorCode.INVALID_DATABASE_OBJECT_NAME, fa.getSourceLocation(),
dataverseNameParts.toString());
}
//TODO(DB): decide
String databaseName = MetadataUtil.resolveDatabase(null, dataverseName);
String datasetName = fa.getIdent().getValue();
CallExpr datasetExpr =
resolveAsDataset(databaseName, dataverseName, datasetName, parent, topVarExpr);
if (datasetExpr != null) {
return datasetExpr;
}
FieldAccessor topFaExpr = topExprs.getSecond(); // = a.b
topFaExpr.setExpr(resolveAsFieldAccessOverContextVar(topVarExpr));
return fa;
}
} else {
fa.setExpr(leadingExpr.accept(this, parent));
return fa;
}
}
}
private boolean resolveAsVariableReference(VariableExpr varExpr) throws CompilationException {
VarIdentifier varId = varExpr.getVar();
String varName = varId.getValue();
if (scopeChecker.isInForbiddenScopes(varName)) {
throw new CompilationException(ErrorCode.FORBIDDEN_SCOPE, varExpr.getSourceLocation());
}
Identifier ident = scopeChecker.lookupSymbol(varName);
if (ident == null) {
if (SqlppVariableUtil.isExternalVariableIdentifier(varId)) {
throw new CompilationException(ErrorCode.PARAMETER_NO_VALUE, varExpr.getSourceLocation(),
SqlppVariableUtil.variableNameToDisplayedFieldName(varId.getValue()));
} else {
return false;
}
}
// Exists such an identifier
varExpr.setIsNewVar(false);
varExpr.setVar((VarIdentifier) ident);
return true;
}
// try resolving the undefined identifier reference as a dataset access.
// for a From/Join/UNNEST/Quantifiers binding expression
private CallExpr resolveAsDataset(String databaseName, DataverseName dataverseName, String datasetName,
ILangExpression parent, VariableExpr varExpr) throws CompilationException {
if (!parent.accept(CheckDatasetOnlyResolutionVisitor.INSTANCE, varExpr)) {
return null;
}
SourceLocation sourceLoc = varExpr.getSourceLocation();
String resolvedDatabaseName;
DataverseName resolvedDataverseName;
String resolvedDatasetName;
boolean viaSynonym, isView;
ViewDecl viewDecl = findDeclaredView(databaseName, dataverseName, datasetName);
if (viewDecl != null) {
resolvedDatabaseName = viewDecl.getViewName().getDatabaseName();
resolvedDataverseName = viewDecl.getViewName().getDataverseName();
resolvedDatasetName = viewDecl.getViewName().getDatasetName();
viaSynonym = false;
isView = true;
} else {
Pair<Dataset, Boolean> p = findDataset(databaseName, dataverseName, datasetName, true, sourceLoc);
if (p == null) {
throw createUnresolvableError(dataverseName, datasetName, sourceLoc);
}
Dataset resolvedDataset = p.first;
resolvedDatabaseName = resolvedDataset.getDatabaseName();
resolvedDataverseName = resolvedDataset.getDataverseName();
resolvedDatasetName = resolvedDataset.getDatasetName();
viaSynonym = p.second;
isView = resolvedDataset.getDatasetType() == DatasetConfig.DatasetType.VIEW;
}
CallExpr callExpr;
if (viaSynonym) {
callExpr = FunctionUtil.makeSynonymDatasetCallExpr(resolvedDatabaseName, resolvedDataverseName,
resolvedDatasetName, isView, databaseName, dataverseName, datasetName);
} else {
callExpr = FunctionUtil.makeDatasetCallExpr(resolvedDatabaseName, resolvedDataverseName,
resolvedDatasetName, isView);
}
callExpr.addHints(varExpr.getHints());
callExpr.setSourceLocation(sourceLoc);
return callExpr;
}
// resolve the undefined identifier reference as a field access on a context variable
private FieldAccessor resolveAsFieldAccessOverContextVar(VariableExpr varExpr) throws CompilationException {
Map<VariableExpr, Set<? extends Scope.SymbolAnnotation>> localVars =
scopeChecker.getCurrentScope().getLiveVariables(scopeChecker.getPrecedingScope());
Set<VariableExpr> contextVars = Scope.findVariablesAnnotatedBy(localVars.keySet(),
SqlppVariableAnnotation.CONTEXT_VARIABLE, localVars, varExpr.getSourceLocation());
VariableExpr contextVar = pickContextVar(contextVars, varExpr);
return generateFieldAccess(contextVar, varExpr.getVar(), varExpr.getSourceLocation());
}
// Rewrites for a field access by name
static FieldAccessor generateFieldAccess(Expression sourceExpr, VarIdentifier fieldVar, SourceLocation sourceLoc) {
VarIdentifier fieldName = SqlppVariableUtil.toUserDefinedVariableName(fieldVar.getValue());
FieldAccessor fa = new FieldAccessor(sourceExpr, fieldName);
fa.setSourceLocation(sourceLoc);
return fa;
}
private static boolean extractDataverseName(Expression expr, List<String> outDataverseName,
Pair<VariableExpr, FieldAccessor> outTopExprs) {
switch (expr.getKind()) {
case VARIABLE_EXPRESSION:
VariableExpr varExpr = (VariableExpr) expr;
String varName = SqlppVariableUtil.toUserDefinedVariableName(varExpr.getVar().getValue()).getValue();
outDataverseName.add(varName);
outTopExprs.setFirst(varExpr);
return true;
case FIELD_ACCESSOR_EXPRESSION:
FieldAccessor faExpr = (FieldAccessor) expr;
if (extractDataverseName(faExpr.getExpr(), outDataverseName, outTopExprs)) {
outDataverseName.add(faExpr.getIdent().getValue());
if (outTopExprs.getSecond() == null) {
outTopExprs.setSecond(faExpr);
}
return true;
} else {
return false;
}
default:
return false;
}
}
private CompilationException createUnresolvableError(DataverseName dataverseName, String datasetName,
SourceLocation sourceLoc) {
DataverseName defaultDataverseName = metadataProvider.getDefaultNamespace().getDataverseName();
if (dataverseName == null && defaultDataverseName == null) {
return new CompilationException(ErrorCode.NAME_RESOLVE_UNKNOWN_DATASET, sourceLoc, datasetName);
}
//If no available dataset nor in-scope variable to resolve to, we throw an error.
return new CompilationException(ErrorCode.NAME_RESOLVE_UNKNOWN_DATASET_IN_DATAVERSE, sourceLoc, datasetName,
dataverseName == null ? defaultDataverseName : dataverseName);
}
private Pair<Dataset, Boolean> findDataset(String databaseName, DataverseName dataverseName, String datasetName,
boolean includingViews, SourceLocation sourceLoc) throws CompilationException {
try {
Boolean viaSynonym = false;
Quadruple<DataverseName, String, Boolean, String> dsName = metadataProvider
.resolveDatasetNameUsingSynonyms(databaseName, dataverseName, datasetName, includingViews);
if (dsName != null) {
dataverseName = dsName.getFirst();
databaseName = dsName.getFourth();
datasetName = dsName.getSecond();
viaSynonym = dsName.getThird();
}
Dataset dataset = metadataProvider.findDataset(databaseName, dataverseName, datasetName, includingViews);
return dataset == null ? null : new Pair<>(dataset, viaSynonym);
} catch (AlgebricksException e) {
throw new CompilationException(ErrorCode.COMPILATION_ERROR, e, sourceLoc, e.getMessage());
}
}
private ViewDecl findDeclaredView(String databaseName, DataverseName dataverseName, String viewName) {
Map<DatasetFullyQualifiedName, ViewDecl> declaredViews = context.getDeclaredViews();
return declaredViews.isEmpty() ? null
: declaredViews.get(new DatasetFullyQualifiedName(databaseName, dataverseName, viewName));
}
@Override
public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
// skip variables inside SQL-92 aggregates (they will be resolved by SqlppGroupByAggregationSugarVisitor)
if (FunctionMapUtil.isSql92AggregateFunction(callExpr.getFunctionSignature())) {
return callExpr;
}
return super.visit(callExpr, arg);
}
@Override
public Expression visit(WindowExpression winExpr, ILangExpression arg) throws CompilationException {
// skip variables inside list and agg-filter arguments of window functions
// (will be resolved by SqlppWindowExpressionVisitor)
FunctionSignature fs = winExpr.getFunctionSignature();
FunctionIdentifier winfi = FunctionMapUtil.getInternalWindowFunction(fs);
if (winfi != null) {
if (BuiltinFunctions.builtinFunctionHasProperty(winfi,
BuiltinFunctions.WindowFunctionProperty.HAS_LIST_ARG)) {
visitWindowExpressionExcludingExprListAndAggFilter(winExpr, arg);
List<Expression> exprList = winExpr.getExprList();
List<Expression> newExprList = new ArrayList<>(exprList.size());
Iterator<Expression> i = exprList.iterator();
newExprList.add(i.next()); // don't visit the list arg
while (i.hasNext()) {
newExprList.add(visit(i.next(), arg));
}
winExpr.setExprList(newExprList);
return winExpr;
} else {
return super.visit(winExpr, arg);
}
} else if (FunctionMapUtil.isSql92AggregateFunction(fs)) {
visitWindowExpressionExcludingExprListAndAggFilter(winExpr, arg);
return winExpr;
} else {
return super.visit(winExpr, arg);
}
}
static VariableExpr pickContextVar(Collection<VariableExpr> contextVars, VariableExpr usedVar)
throws CompilationException {
switch (contextVars.size()) {
case 0:
throw new CompilationException(ErrorCode.UNDEFINED_IDENTIFIER, usedVar.getSourceLocation(),
SqlppVariableUtil.toUserDefinedVariableName(usedVar.getVar().getValue()).getValue());
case 1:
return contextVars.iterator().next();
default:
throw new CompilationException(ErrorCode.AMBIGUOUS_IDENTIFIER, usedVar.getSourceLocation(),
SqlppVariableUtil.toUserDefinedVariableName(usedVar.getVar().getValue()).getValue());
}
}
}