All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.facebook.presto.sql.analyzer.Analysis Maven / Gradle / Ivy

/*
 * 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 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 com.facebook.presto.sql.analyzer;

import com.facebook.presto.common.QualifiedObjectName;
import com.facebook.presto.common.Subfield;
import com.facebook.presto.common.transaction.TransactionId;
import com.facebook.presto.common.type.Type;
import com.facebook.presto.spi.ColumnHandle;
import com.facebook.presto.spi.ColumnMetadata;
import com.facebook.presto.spi.TableHandle;
import com.facebook.presto.spi.analyzer.AccessControlInfo;
import com.facebook.presto.spi.analyzer.AccessControlInfoForTable;
import com.facebook.presto.spi.analyzer.AccessControlReferences;
import com.facebook.presto.spi.analyzer.AccessControlRole;
import com.facebook.presto.spi.function.FunctionHandle;
import com.facebook.presto.spi.function.FunctionKind;
import com.facebook.presto.spi.security.AccessControl;
import com.facebook.presto.spi.security.AccessControlContext;
import com.facebook.presto.spi.security.AllowAllAccessControl;
import com.facebook.presto.spi.security.Identity;
import com.facebook.presto.sql.tree.ExistsPredicate;
import com.facebook.presto.sql.tree.Expression;
import com.facebook.presto.sql.tree.FunctionCall;
import com.facebook.presto.sql.tree.GroupingOperation;
import com.facebook.presto.sql.tree.Identifier;
import com.facebook.presto.sql.tree.InPredicate;
import com.facebook.presto.sql.tree.Join;
import com.facebook.presto.sql.tree.LambdaArgumentDeclaration;
import com.facebook.presto.sql.tree.Node;
import com.facebook.presto.sql.tree.NodeRef;
import com.facebook.presto.sql.tree.Offset;
import com.facebook.presto.sql.tree.OrderBy;
import com.facebook.presto.sql.tree.Parameter;
import com.facebook.presto.sql.tree.QuantifiedComparisonExpression;
import com.facebook.presto.sql.tree.Query;
import com.facebook.presto.sql.tree.QuerySpecification;
import com.facebook.presto.sql.tree.Relation;
import com.facebook.presto.sql.tree.SampledRelation;
import com.facebook.presto.sql.tree.Statement;
import com.facebook.presto.sql.tree.SubqueryExpression;
import com.facebook.presto.sql.tree.Table;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;

import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static com.facebook.presto.sql.analyzer.Analysis.MaterializedViewAnalysisState.NOT_VISITED;
import static com.facebook.presto.sql.analyzer.Analysis.MaterializedViewAnalysisState.VISITED;
import static com.facebook.presto.sql.analyzer.Analysis.MaterializedViewAnalysisState.VISITING;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Multimaps.forMap;
import static com.google.common.collect.Multimaps.unmodifiableMultimap;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull;

public class Analysis
{
    @Nullable
    private final Statement root;
    private final Map, Expression> parameters;
    private String updateType;

    private final Map, NamedQuery> namedQueries = new LinkedHashMap<>();

    private final Map, Scope> scopes = new LinkedHashMap<>();
    private final Multimap, FieldId> columnReferences = ArrayListMultimap.create();

    private final AccessControlReferences accessControlReferences = new AccessControlReferences();

    // a map of users to the columns per table that they access
    private final Map>> tableColumnReferences = new LinkedHashMap<>();
    private final Map>> utilizedTableColumnReferences = new LinkedHashMap<>();
    private final Map>> tableColumnAndSubfieldReferences = new LinkedHashMap<>();

    private final Map, List> aggregates = new LinkedHashMap<>();
    private final Map, List> orderByAggregates = new LinkedHashMap<>();
    private final Map, List> groupByExpressions = new LinkedHashMap<>();
    private final Map, GroupingSetAnalysis> groupingSets = new LinkedHashMap<>();

    private final Map, Expression> where = new LinkedHashMap<>();
    private final Map, Expression> having = new LinkedHashMap<>();
    private final Map, List> orderByExpressions = new LinkedHashMap<>();
    private final Set> redundantOrderBy = new HashSet<>();
    private final Map, List> outputExpressions = new LinkedHashMap<>();
    private final Map, List> windowFunctions = new LinkedHashMap<>();
    private final Map, List> orderByWindowFunctions = new LinkedHashMap<>();
    private final Map, Long> offset = new LinkedHashMap<>();

    private final Map, Expression> joins = new LinkedHashMap<>();
    private final Map, JoinUsingAnalysis> joinUsing = new LinkedHashMap<>();

    private final ListMultimap, InPredicate> inPredicatesSubqueries = ArrayListMultimap.create();
    private final ListMultimap, SubqueryExpression> scalarSubqueries = ArrayListMultimap.create();
    private final ListMultimap, ExistsPredicate> existsSubqueries = ArrayListMultimap.create();
    private final ListMultimap, QuantifiedComparisonExpression> quantifiedComparisonSubqueries = ArrayListMultimap.create();

    private final MetadataHandle metadataHandle = new MetadataHandle();
    private final Map, TableHandle> tables = new LinkedHashMap<>();

    private final Map, Type> types = new LinkedHashMap<>();
    private final Map, Type> coercions = new LinkedHashMap<>();
    // Coercions needed for window function frame of type RANGE.
    // These are coercions for the sort key, needed for frame bound calculation, identified by frame range offset expression.
    // Frame definition might contain two different offset expressions (for start and end), each requiring different coercion of the sort key.
    private final Map, Type> sortKeyCoercionsForFrameBoundCalculation = new LinkedHashMap<>();
    // Coercions needed for window function frame of type RANGE.
    // These are coercions for the sort key, needed for comparison of the sort key with precomputed frame bound, identified by frame range offset expression.
    private final Map, Type> sortKeyCoercionsForFrameBoundComparison = new LinkedHashMap<>();
    // Functions for calculating frame bounds for frame of type RANGE, identified by frame range offset expression.
    private final Map, FunctionHandle> frameBoundCalculations = new LinkedHashMap<>();
    private final Set> typeOnlyCoercions = new LinkedHashSet<>();
    private final Map, List> relationCoercions = new LinkedHashMap<>();
    private final Map, FunctionHandle> functionHandles = new LinkedHashMap<>();
    private final Map, LambdaArgumentDeclaration> lambdaArgumentReferences = new LinkedHashMap<>();

    private final Map columns = new LinkedHashMap<>();

    private final Map, Double> sampleRatios = new LinkedHashMap<>();

    private final Map, List> groupingOperations = new LinkedHashMap<>();

    // for create table
    private Optional createTableDestination = Optional.empty();
    private Map createTableProperties = ImmutableMap.of();
    private boolean createTableAsSelectWithData = true;
    private boolean createTableAsSelectNoOp;
    private Optional> createTableColumnAliases = Optional.empty();
    private Optional createTableComment = Optional.empty();

    private Optional insert = Optional.empty();
    private Optional refreshMaterializedViewAnalysis = Optional.empty();
    private Optional analyzeTarget = Optional.empty();

    private Optional> updatedColumns = Optional.empty();

    // for describe input and describe output
    private final boolean isDescribe;

    // for recursive view detection
    private final Deque tablesForView = new ArrayDeque<>();

    // To prevent recursive analyzing of one materialized view base table
    private final ListMultimap, Table> tablesForMaterializedView = ArrayListMultimap.create();

    // for materialized view analysis state detection, state is used to identify if materialized view has been expanded or in-process.
    private final Map materializedViewAnalysisStateMap = new HashMap<>();

    private final Map materializedViews = new LinkedHashMap<>();

    private Optional expandedQuery = Optional.empty();

    // Keeps track of the subquery we are visiting, so we have access to base query information when processing materialized view status
    private Optional currentQuerySpecification = Optional.empty();

    public Analysis(@Nullable Statement root, Map, Expression> parameters, boolean isDescribe)
    {
        this.root = root;
        this.parameters = ImmutableMap.copyOf(requireNonNull(parameters, "parameterMap is null"));
        this.isDescribe = isDescribe;
    }

    public Statement getStatement()
    {
        return root;
    }

    public String getUpdateType()
    {
        return updateType;
    }

    public void setUpdateType(String updateType)
    {
        this.updateType = updateType;
    }

    public boolean isCreateTableAsSelectWithData()
    {
        return createTableAsSelectWithData;
    }

    public void setCreateTableAsSelectWithData(boolean createTableAsSelectWithData)
    {
        this.createTableAsSelectWithData = createTableAsSelectWithData;
    }

    public boolean isCreateTableAsSelectNoOp()
    {
        return createTableAsSelectNoOp;
    }

    public void setCreateTableAsSelectNoOp(boolean createTableAsSelectNoOp)
    {
        this.createTableAsSelectNoOp = createTableAsSelectNoOp;
    }

    public void setAggregates(QuerySpecification node, List aggregates)
    {
        this.aggregates.put(NodeRef.of(node), ImmutableList.copyOf(aggregates));
    }

    public List getAggregates(QuerySpecification query)
    {
        return aggregates.get(NodeRef.of(query));
    }

    public void setOrderByAggregates(OrderBy node, List aggregates)
    {
        this.orderByAggregates.put(NodeRef.of(node), ImmutableList.copyOf(aggregates));
    }

    public List getOrderByAggregates(OrderBy node)
    {
        return orderByAggregates.get(NodeRef.of(node));
    }

    public Map, Type> getTypes()
    {
        return unmodifiableMap(types);
    }

    public Type getType(Expression expression)
    {
        Type type = types.get(NodeRef.of(expression));
        checkArgument(type != null, "Expression not analyzed: %s", expression);
        return type;
    }

    public Type getTypeWithCoercions(Expression expression)
    {
        NodeRef key = NodeRef.of(expression);
        checkArgument(types.containsKey(key), "Expression not analyzed: %s", expression);
        if (coercions.containsKey(key)) {
            return coercions.get(key);
        }
        return types.get(key);
    }

    public Type[] getRelationCoercion(Relation relation)
    {
        return Optional.ofNullable(relationCoercions.get(NodeRef.of(relation)))
                .map(types -> types.stream().toArray(Type[]::new))
                .orElse(null);
    }

    public void addRelationCoercion(Relation relation, Type[] types)
    {
        relationCoercions.put(NodeRef.of(relation), ImmutableList.copyOf(types));
    }

    public Map, Type> getCoercions()
    {
        return unmodifiableMap(coercions);
    }

    public Set> getTypeOnlyCoercions()
    {
        return unmodifiableSet(typeOnlyCoercions);
    }

    public Type getCoercion(Expression expression)
    {
        return coercions.get(NodeRef.of(expression));
    }

    public void addLambdaArgumentReferences(Map, LambdaArgumentDeclaration> lambdaArgumentReferences)
    {
        this.lambdaArgumentReferences.putAll(lambdaArgumentReferences);
    }

    public LambdaArgumentDeclaration getLambdaArgumentReference(Identifier identifier)
    {
        return lambdaArgumentReferences.get(NodeRef.of(identifier));
    }

    public Map, LambdaArgumentDeclaration> getLambdaArgumentReferences()
    {
        return unmodifiableMap(lambdaArgumentReferences);
    }

    public void setGroupingSets(QuerySpecification node, GroupingSetAnalysis groupingSets)
    {
        this.groupingSets.put(NodeRef.of(node), groupingSets);
    }

    public void setGroupByExpressions(QuerySpecification node, List expressions)
    {
        groupByExpressions.put(NodeRef.of(node), expressions);
    }

    public boolean isAggregation(QuerySpecification node)
    {
        return groupByExpressions.containsKey(NodeRef.of(node));
    }

    public boolean isTypeOnlyCoercion(Expression expression)
    {
        return typeOnlyCoercions.contains(NodeRef.of(expression));
    }

    public GroupingSetAnalysis getGroupingSets(QuerySpecification node)
    {
        return groupingSets.get(NodeRef.of(node));
    }

    public List getGroupByExpressions(QuerySpecification node)
    {
        return groupByExpressions.get(NodeRef.of(node));
    }

    public void setWhere(Node node, Expression expression)
    {
        where.put(NodeRef.of(node), expression);
    }

    public Expression getWhere(QuerySpecification node)
    {
        return where.get(NodeRef.of(node));
    }

    public void setOrderByExpressions(Node node, List items)
    {
        orderByExpressions.put(NodeRef.of(node), ImmutableList.copyOf(items));
    }

    public List getOrderByExpressions(Node node)
    {
        return orderByExpressions.get(NodeRef.of(node));
    }

    public void setOffset(Offset node, long rowCount)
    {
        offset.put(NodeRef.of(node), rowCount);
    }

    public long getOffset(Offset node)
    {
        checkState(offset.containsKey(NodeRef.of(node)), "missing OFFSET value for node %s", node);
        return offset.get(NodeRef.of(node));
    }

    public void setOutputExpressions(Node node, List expressions)
    {
        outputExpressions.put(NodeRef.of(node), ImmutableList.copyOf(expressions));
    }

    public List getOutputExpressions(Node node)
    {
        return outputExpressions.get(NodeRef.of(node));
    }

    public void setHaving(QuerySpecification node, Expression expression)
    {
        having.put(NodeRef.of(node), expression);
    }

    public void setJoinCriteria(Join node, Expression criteria)
    {
        joins.put(NodeRef.of(node), criteria);
    }

    public Expression getJoinCriteria(Join join)
    {
        return joins.get(NodeRef.of(join));
    }

    public void recordSubqueries(Node node, ExpressionAnalysis expressionAnalysis)
    {
        NodeRef key = NodeRef.of(node);
        this.inPredicatesSubqueries.putAll(key, dereference(expressionAnalysis.getSubqueryInPredicates()));
        this.scalarSubqueries.putAll(key, dereference(expressionAnalysis.getScalarSubqueries()));
        this.existsSubqueries.putAll(key, dereference(expressionAnalysis.getExistsSubqueries()));
        this.quantifiedComparisonSubqueries.putAll(key, dereference(expressionAnalysis.getQuantifiedComparisons()));
    }

    private  List dereference(Collection> nodeRefs)
    {
        return nodeRefs.stream()
                .map(NodeRef::getNode)
                .collect(toImmutableList());
    }

    public List getInPredicateSubqueries(Node node)
    {
        return ImmutableList.copyOf(inPredicatesSubqueries.get(NodeRef.of(node)));
    }

    public List getScalarSubqueries(Node node)
    {
        return ImmutableList.copyOf(scalarSubqueries.get(NodeRef.of(node)));
    }

    public boolean isScalarSubquery(SubqueryExpression subqueryExpression)
    {
        return scalarSubqueries.values().contains(subqueryExpression);
    }

    public List getExistsSubqueries(Node node)
    {
        return ImmutableList.copyOf(existsSubqueries.get(NodeRef.of(node)));
    }

    public List getQuantifiedComparisonSubqueries(Node node)
    {
        return unmodifiableList(quantifiedComparisonSubqueries.get(NodeRef.of(node)));
    }

    public void setWindowFunctions(QuerySpecification node, List functions)
    {
        windowFunctions.put(NodeRef.of(node), ImmutableList.copyOf(functions));
    }

    public List getWindowFunctions(QuerySpecification query)
    {
        return windowFunctions.get(NodeRef.of(query));
    }

    public void setOrderByWindowFunctions(OrderBy node, List functions)
    {
        orderByWindowFunctions.put(NodeRef.of(node), ImmutableList.copyOf(functions));
    }

    public List getOrderByWindowFunctions(OrderBy query)
    {
        return orderByWindowFunctions.get(NodeRef.of(query));
    }

    public void addColumnReferences(Map, FieldId> columnReferences)
    {
        this.columnReferences.putAll(forMap(columnReferences));
    }

    public void addColumnReference(NodeRef node, FieldId fieldId)
    {
        this.columnReferences.put(node, fieldId);
    }

    public Scope getScope(Node node)
    {
        return tryGetScope(node).orElseThrow(() -> new IllegalArgumentException(format("Analysis does not contain information for node: %s", node)));
    }

    public Optional tryGetScope(Node node)
    {
        NodeRef key = NodeRef.of(node);
        if (scopes.containsKey(key)) {
            return Optional.of(scopes.get(key));
        }

        return Optional.empty();
    }

    public Scope getRootScope()
    {
        return getScope(root);
    }

    public void setScope(Node node, Scope scope)
    {
        scopes.put(NodeRef.of(node), scope);
    }

    public RelationType getOutputDescriptor()
    {
        return getOutputDescriptor(root);
    }

    public RelationType getOutputDescriptor(Node node)
    {
        return getScope(node).getRelationType();
    }

    public MetadataHandle getMetadataHandle()
    {
        return metadataHandle;
    }

    public TableHandle getTableHandle(Table table)
    {
        return tables.get(NodeRef.of(table));
    }

    public Collection getTables()
    {
        return unmodifiableCollection(tables.values());
    }

    public List
getTableNodes() { return tables.keySet().stream().map(NodeRef::getNode).collect(toImmutableList()); } public void registerTable(Table table, TableHandle handle) { tables.put(NodeRef.of(table), handle); } public FunctionHandle getFunctionHandle(FunctionCall function) { return functionHandles.get(NodeRef.of(function)); } public Map, FunctionHandle> getFunctionHandles() { return ImmutableMap.copyOf(functionHandles); } public void addFunctionHandles(Map, FunctionHandle> infos) { functionHandles.putAll(infos); } public Set> getColumnReferences() { return unmodifiableSet(columnReferences.keySet()); } public Multimap, FieldId> getColumnReferenceFields() { return unmodifiableMultimap(columnReferences); } public boolean isColumnReference(Expression expression) { requireNonNull(expression, "expression is null"); checkArgument(getType(expression) != null, "expression %s has not been analyzed", expression); return columnReferences.containsKey(NodeRef.of(expression)); } public void addTypes(Map, Type> types) { this.types.putAll(types); } public void addCoercion(Expression expression, Type type, boolean isTypeOnlyCoercion) { this.coercions.put(NodeRef.of(expression), type); if (isTypeOnlyCoercion) { this.typeOnlyCoercions.add(NodeRef.of(expression)); } } public void addCoercions( Map, Type> coercions, Set> typeOnlyCoercions, Map, Type> sortKeyCoercionsForFrameBoundCalculation, Map, Type> sortKeyCoercionsForFrameBoundComparison) { this.coercions.putAll(coercions); this.typeOnlyCoercions.addAll(typeOnlyCoercions); this.sortKeyCoercionsForFrameBoundCalculation.putAll(sortKeyCoercionsForFrameBoundCalculation); this.sortKeyCoercionsForFrameBoundComparison.putAll(sortKeyCoercionsForFrameBoundComparison); } public Type getSortKeyCoercionForFrameBoundCalculation(Expression frameOffset) { return sortKeyCoercionsForFrameBoundCalculation.get(NodeRef.of(frameOffset)); } public Type getSortKeyCoercionForFrameBoundComparison(Expression frameOffset) { return sortKeyCoercionsForFrameBoundComparison.get(NodeRef.of(frameOffset)); } public void addFrameBoundCalculations(Map, FunctionHandle> frameBoundCalculations) { this.frameBoundCalculations.putAll(frameBoundCalculations); } public FunctionHandle getFrameBoundCalculation(Expression frameOffset) { return frameBoundCalculations.get(NodeRef.of(frameOffset)); } public Expression getHaving(QuerySpecification query) { return having.get(NodeRef.of(query)); } public void setColumn(Field field, ColumnHandle handle) { columns.put(field, handle); } public ColumnHandle getColumn(Field field) { return columns.get(field); } public void setCreateTableDestination(QualifiedObjectName destination) { this.createTableDestination = Optional.of(destination); } public Optional getCreateTableDestination() { return createTableDestination; } public Optional getAnalyzeTarget() { return analyzeTarget; } public void setAnalyzeTarget(TableHandle analyzeTarget) { this.analyzeTarget = Optional.of(analyzeTarget); } public void setCreateTableProperties(Map createTableProperties) { this.createTableProperties = ImmutableMap.copyOf(createTableProperties); } public Map getCreateTableProperties() { return createTableProperties; } public Optional> getColumnAliases() { return createTableColumnAliases; } public void setCreateTableColumnAliases(List createTableColumnAliases) { this.createTableColumnAliases = Optional.of(createTableColumnAliases); } public void setCreateTableComment(Optional createTableComment) { this.createTableComment = requireNonNull(createTableComment); } public Optional getCreateTableComment() { return createTableComment; } public void setInsert(Insert insert) { this.insert = Optional.of(insert); } public Optional getInsert() { return insert; } public void setUpdatedColumns(List updatedColumns) { this.updatedColumns = Optional.of(updatedColumns); } public Optional> getUpdatedColumns() { return updatedColumns; } public void setRefreshMaterializedViewAnalysis(RefreshMaterializedViewAnalysis refreshMaterializedViewAnalysis) { this.refreshMaterializedViewAnalysis = Optional.of(refreshMaterializedViewAnalysis); } public Optional getRefreshMaterializedViewAnalysis() { return refreshMaterializedViewAnalysis; } public NamedQuery getNamedQuery(Table table) { return namedQueries.get(NodeRef.of(table)); } public void registerNamedQuery(Table tableReference, Query query, boolean isFromView) { requireNonNull(tableReference, "tableReference is null"); requireNonNull(query, "query is null"); namedQueries.put(NodeRef.of(tableReference), new NamedQuery(query, isFromView)); } public void registerTableForView(Table tableReference) { tablesForView.push(requireNonNull(tableReference, "table is null")); } public void unregisterTableForView() { tablesForView.pop(); } public void registerMaterializedViewForAnalysis(QualifiedObjectName materializedViewName, Table materializedView, String materializedViewSql) { requireNonNull(materializedView, "materializedView is null"); if (materializedViewAnalysisStateMap.containsKey(materializedView)) { materializedViewAnalysisStateMap.put(materializedView, VISITED); } else { materializedViewAnalysisStateMap.put(materializedView, VISITING); } materializedViews.put(materializedViewName, materializedViewSql); } public void unregisterMaterializedViewForAnalysis(Table materializedView) { requireNonNull(materializedView, "materializedView is null"); checkState( materializedViewAnalysisStateMap.containsKey(materializedView), format("materializedViewAnalysisStateMap does not contain materialized view : %s", materializedView.getName())); materializedViewAnalysisStateMap.remove(materializedView); } public MaterializedViewAnalysisState getMaterializedViewAnalysisState(Table materializedView) { requireNonNull(materializedView, "materializedView is null"); return materializedViewAnalysisStateMap.getOrDefault(materializedView, NOT_VISITED); } public boolean hasTableInView(Table tableReference) { return tablesForView.contains(tableReference); } public void registerTableForMaterializedView(Table view, Table table) { requireNonNull(view, "view is null"); requireNonNull(table, "table is null"); tablesForMaterializedView.put(NodeRef.of(view), table); } public void unregisterTableForMaterializedView(Table view, Table table) { requireNonNull(view, "view is null"); requireNonNull(table, "table is null"); tablesForMaterializedView.remove(NodeRef.of(view), table); } public boolean hasTableRegisteredForMaterializedView(Table view, Table table) { requireNonNull(view, "view is null"); requireNonNull(table, "table is null"); return tablesForMaterializedView.containsEntry(NodeRef.of(view), table); } public void setSampleRatio(SampledRelation relation, double ratio) { sampleRatios.put(NodeRef.of(relation), ratio); } public double getSampleRatio(SampledRelation relation) { NodeRef key = NodeRef.of(relation); checkState(sampleRatios.containsKey(key), "Sample ratio missing for %s. Broken analysis?", relation); return sampleRatios.get(key); } public void setGroupingOperations(QuerySpecification querySpecification, List groupingOperations) { this.groupingOperations.put(NodeRef.of(querySpecification), ImmutableList.copyOf(groupingOperations)); } public List getGroupingOperations(QuerySpecification querySpecification) { return Optional.ofNullable(groupingOperations.get(NodeRef.of(querySpecification))) .orElse(emptyList()); } public Map, Expression> getParameters() { return parameters; } public boolean isDescribe() { return isDescribe; } public void setJoinUsing(Join node, JoinUsingAnalysis analysis) { joinUsing.put(NodeRef.of(node), analysis); } public JoinUsingAnalysis getJoinUsing(Join node) { return joinUsing.get(NodeRef.of(node)); } public AccessControlReferences getAccessControlReferences() { return accessControlReferences; } public void addAccessControlCheckForTable(AccessControlRole accessControlRole, AccessControlInfoForTable accessControlInfoForTable) { accessControlReferences.addTableReference(accessControlRole, accessControlInfoForTable); } public void addTableColumnAndSubfieldReferences( AccessControl accessControl, Identity identity, Optional transactionId, AccessControlContext accessControlContext, Multimap tableColumnMap, Multimap tableColumnMapForAccessControl) { AccessControlInfo accessControlInfo = new AccessControlInfo(accessControl, identity, transactionId, accessControlContext); Map> columnReferences = tableColumnReferences.computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>()); tableColumnMap.asMap() .forEach((key, value) -> columnReferences.computeIfAbsent(key, k -> new HashSet<>()).addAll(value.stream().map(Subfield::getRootName).collect(toImmutableSet()))); Map> columnAndSubfieldReferences = tableColumnAndSubfieldReferences.computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>()); tableColumnMapForAccessControl.asMap() .forEach((key, value) -> columnAndSubfieldReferences.computeIfAbsent(key, k -> new HashSet<>()).addAll(value)); } public void addEmptyColumnReferencesForTable(AccessControl accessControl, Identity identity, Optional transactionId, AccessControlContext accessControlContext, QualifiedObjectName table) { AccessControlInfo accessControlInfo = new AccessControlInfo(accessControl, identity, transactionId, accessControlContext); tableColumnReferences.computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>()).computeIfAbsent(table, k -> new HashSet<>()); tableColumnAndSubfieldReferences.computeIfAbsent(accessControlInfo, k -> new LinkedHashMap<>()).computeIfAbsent(table, k -> new HashSet<>()); } public Map>> getTableColumnReferences() { return tableColumnReferences; } public void addUtilizedTableColumnReferences(AccessControlInfo accessControlInfo, Map> utilizedTableColumns) { utilizedTableColumnReferences.put(accessControlInfo, utilizedTableColumns); } public Map>> getUtilizedTableColumnReferences() { return ImmutableMap.copyOf(utilizedTableColumnReferences); } public void populateTableColumnAndSubfieldReferencesForAccessControl(boolean checkAccessControlOnUtilizedColumnsOnly, boolean checkAccessControlWithSubfields) { accessControlReferences.addTableColumnAndSubfieldReferencesForAccessControl(getTableColumnAndSubfieldReferencesForAccessControl(checkAccessControlOnUtilizedColumnsOnly, checkAccessControlWithSubfields)); } private Map>> getTableColumnAndSubfieldReferencesForAccessControl(boolean checkAccessControlOnUtilizedColumnsOnly, boolean checkAccessControlWithSubfields) { Map>> references; if (!checkAccessControlWithSubfields) { references = (checkAccessControlOnUtilizedColumnsOnly ? utilizedTableColumnReferences : tableColumnReferences).entrySet().stream() .collect(toImmutableMap( Map.Entry::getKey, accessControlEntry -> accessControlEntry.getValue().entrySet().stream().collect(toImmutableMap( Map.Entry::getKey, tableEntry -> tableEntry.getValue().stream().map(column -> new Subfield(column, ImmutableList.of())).collect(toImmutableSet()))))); } else if (!checkAccessControlOnUtilizedColumnsOnly) { references = tableColumnAndSubfieldReferences; } else { // TODO: Properly support utilized column check. Currently, we prune whole columns, if they are not utilized. // We need to generalize it and exclude unutilized subfield references independently. references = tableColumnAndSubfieldReferences.entrySet().stream() .collect(toImmutableMap( Map.Entry::getKey, accessControlEntry -> accessControlEntry.getValue().entrySet().stream().collect(toImmutableMap( Map.Entry::getKey, tableEntry -> tableEntry.getValue().stream().filter( column -> { Map> utilizedTableReferences = utilizedTableColumnReferences.get(accessControlEntry.getKey()); if (utilizedTableReferences == null) { return false; } Set utilizedColumns = utilizedTableReferences.get(tableEntry.getKey()); return utilizedColumns != null && utilizedColumns.contains(column.getRootName()); }) .collect(toImmutableSet()))))); } return buildMaterializedViewAccessControl(references); } /** * For a query on materialized view, only check the actual required access controls for its base tables. For the materialized view, * will not check access control by replacing with AllowAllAccessControl. **/ private Map>> buildMaterializedViewAccessControl(Map>> tableColumnReferences) { if (!(getStatement() instanceof Query) || materializedViews.isEmpty()) { return tableColumnReferences; } Map>> newTableColumnReferences = new LinkedHashMap<>(); tableColumnReferences.forEach((accessControlInfo, references) -> { AccessControlInfo allowAllAccessControlInfo = new AccessControlInfo(new AllowAllAccessControl(), accessControlInfo.getIdentity(), accessControlInfo.getTransactionId(), accessControlInfo.getAccessControlContext()); Map> newAllowAllReferences = newTableColumnReferences.getOrDefault(allowAllAccessControlInfo, new LinkedHashMap<>()); Map> newOtherReferences = new LinkedHashMap<>(); references.forEach((table, columns) -> { if (materializedViews.containsKey(table)) { newAllowAllReferences.computeIfAbsent(table, key -> new HashSet<>()).addAll(columns); } else { newOtherReferences.put(table, columns); } }); if (!newAllowAllReferences.isEmpty()) { newTableColumnReferences.put(allowAllAccessControlInfo, newAllowAllReferences); } if (!newOtherReferences.isEmpty()) { newTableColumnReferences.put(accessControlInfo, newOtherReferences); } }); return newTableColumnReferences; } public void markRedundantOrderBy(OrderBy orderBy) { redundantOrderBy.add(NodeRef.of(orderBy)); } public boolean isOrderByRedundant(OrderBy orderBy) { return redundantOrderBy.contains(NodeRef.of(orderBy)); } public void setExpandedQuery(String expandedQuery) { this.expandedQuery = Optional.of(expandedQuery); } public Optional getExpandedQuery() { return expandedQuery; } public void setCurrentSubquery(QuerySpecification currentSubQuery) { this.currentQuerySpecification = Optional.of(currentSubQuery); } public Optional getCurrentQuerySpecification() { return currentQuerySpecification; } public Map> getInvokedFunctions() { Map> functionMap = new HashMap<>(); for (FunctionHandle functionHandle : functionHandles.values()) { functionMap.putIfAbsent(functionHandle.getKind(), new HashSet<>()); functionMap.get(functionHandle.getKind()).add(functionHandle.getName()); } return functionMap.entrySet().stream().collect(toImmutableMap(Map.Entry::getKey, entry -> ImmutableSet.copyOf(entry.getValue()))); } @Immutable public static final class Insert { private final TableHandle target; private final List columns; public Insert(TableHandle target, List columns) { this.target = requireNonNull(target, "target is null"); this.columns = requireNonNull(columns, "columns is null"); checkArgument(columns.size() > 0, "No columns given to insert"); } public List getColumns() { return columns; } public TableHandle getTarget() { return target; } } @Immutable public static final class RefreshMaterializedViewAnalysis { private final TableHandle target; private final List columns; private final Query query; public RefreshMaterializedViewAnalysis(TableHandle target, List columns, Query query) { this.target = requireNonNull(target, "target is null"); this.columns = requireNonNull(columns, "columns is null"); this.query = requireNonNull(query, "query is null"); checkArgument(columns.size() > 0, "No columns given to insert"); } public List getColumns() { return columns; } public TableHandle getTarget() { return target; } public Query getQuery() { return query; } } public static final class JoinUsingAnalysis { private final List leftJoinFields; private final List rightJoinFields; private final List otherLeftFields; private final List otherRightFields; JoinUsingAnalysis(List leftJoinFields, List rightJoinFields, List otherLeftFields, List otherRightFields) { this.leftJoinFields = ImmutableList.copyOf(leftJoinFields); this.rightJoinFields = ImmutableList.copyOf(rightJoinFields); this.otherLeftFields = ImmutableList.copyOf(otherLeftFields); this.otherRightFields = ImmutableList.copyOf(otherRightFields); checkArgument(leftJoinFields.size() == rightJoinFields.size(), "Expected join fields for left and right to have the same size"); } public List getLeftJoinFields() { return leftJoinFields; } public List getRightJoinFields() { return rightJoinFields; } public List getOtherLeftFields() { return otherLeftFields; } public List getOtherRightFields() { return otherRightFields; } } public static class GroupingSetAnalysis { private final List> cubes; private final List> rollups; private final List>> ordinarySets; private final List complexExpressions; public GroupingSetAnalysis( List> cubes, List> rollups, List>> ordinarySets, List complexExpressions) { this.cubes = ImmutableList.copyOf(cubes); this.rollups = ImmutableList.copyOf(rollups); this.ordinarySets = ImmutableList.copyOf(ordinarySets); this.complexExpressions = ImmutableList.copyOf(complexExpressions); } public List> getCubes() { return cubes; } public List> getRollups() { return rollups; } public List>> getOrdinarySets() { return ordinarySets; } public List getComplexExpressions() { return complexExpressions; } } public enum MaterializedViewAnalysisState { NOT_VISITED(0), VISITING(1), VISITED(2); private final int value; MaterializedViewAnalysisState(int value) { this.value = value; } public boolean isNotVisited() { return this.value == NOT_VISITED.value; } public boolean isVisited() { return this.value == VISITED.value; } public boolean isVisiting() { return this.value == VISITING.value; } } public class NamedQuery { private final Query query; private final boolean isFromView; public NamedQuery(Query query, boolean isFromView) { this.query = query; this.isFromView = isFromView; } public Query getQuery() { return query; } public boolean isFromView() { return isFromView; } } }