WithClause.java

// 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.
// This file is copied from
// https://github.com/apache/impala/blob/branch-2.9.0/fe/src/main/java/org/apache/impala/WithClause.java
// and modified by Doris

package org.apache.doris.analysis;

import org.apache.doris.catalog.TableIf;
import org.apache.doris.catalog.View;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.UserException;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Representation of the WITH clause that may appear before a query statement or insert
 * statement. A WITH clause contains a list of named view definitions that may be
 * referenced in the query statement that follows it.
 *
 * Scoping rules:
 * A WITH-clause view is visible inside the query statement that it belongs to.
 * This includes inline views and nested WITH clauses inside the query statement.
 *
 * Each WITH clause establishes a new analysis scope. A WITH-clause view definition
 * may refer to views from the same WITH-clause appearing to its left, and to all
 * WITH-clause views from outer scopes.
 *
 * References to WITH-clause views are resolved inside out, i.e., a match is found by
 * first looking in the current scope and then in the enclosing scope(s).
 *
 * Views defined within the same WITH-clause may not use the same alias.
 */
public class WithClause implements ParseNode {
    /////////////////////////////////////////
    // BEGIN: Members that need to be reset()

    private final ArrayList<View> views;

    // END: Members that need to be reset()
    /////////////////////////////////////////

    public WithClause(ArrayList<View> views) {
        Preconditions.checkNotNull(views);
        Preconditions.checkState(!views.isEmpty());
        this.views = views;
    }

    /**
     * Analyzes all views and registers them with the analyzer. Enforces scoping rules.
     * All local views registered with the analyzer are have QueryStmts with resolved
     * TableRefs to simplify the analysis of view references.
     */
    @Override
    public void analyze(Analyzer analyzer) throws AnalysisException, UserException {
        // Create a new analyzer for the WITH clause with a new global state (IMPALA-1357)
        // but a child of 'analyzer' so that the global state for 'analyzer' is not polluted
        // during analysis of the WITH clause. withClauseAnalyzer is a child of 'analyzer' so
        // that local views registered in parent blocks are visible here.
        Analyzer withClauseAnalyzer = Analyzer.createWithNewGlobalState(analyzer);
        withClauseAnalyzer.setIsWithClause();
        if (analyzer.isExplain()) {
            withClauseAnalyzer.setIsExplain();
        }
        for (View view : views) {
            Analyzer viewAnalyzer = new Analyzer(withClauseAnalyzer);
            view.getQueryStmt().analyze(viewAnalyzer);
            // Register this view so that the next view can reference it.
            withClauseAnalyzer.registerLocalView(view);
        }
        // Register all local views with the analyzer.
        for (View localView : withClauseAnalyzer.getLocalViews().values()) {
            analyzer.registerLocalView(localView);
        }
    }

    /**
     * C'tor for cloning.
     */
    private WithClause(WithClause other) {
        Preconditions.checkNotNull(other);
        views = Lists.newArrayList();
        for (View view : other.views) {
            views.add(new View(view.getName(), view.getQueryStmt().clone(), view.getOriginalColLabels()));
        }
    }

    public void reset() {
        for (View view : views) {
            view.getQueryStmt().reset();
        }
    }

    public void getTables(Analyzer analyzer, boolean expandView, Map<Long, TableIf> tableMap,
            Set<String> parentViewNameSet) throws AnalysisException {
        for (View view : views) {
            QueryStmt stmt = view.getQueryStmt();
            parentViewNameSet.add(view.getName());
            stmt.getTables(analyzer, expandView, tableMap, parentViewNameSet);
        }
    }

    public void getTableRefs(Analyzer analyzer, List<TableRef> tblRefs, Set<String> parentViewNameSet) {
        for (View view : views) {
            QueryStmt stmt = view.getQueryStmt();
            parentViewNameSet.add(view.getName());
            stmt.getTableRefs(analyzer, tblRefs, parentViewNameSet);
        }
    }

    @Override
    public WithClause clone() {
        return new WithClause(this);
    }

    @Override
    public String toSql() {
        List<String> viewStrings = Lists.newArrayList();
        for (View view : views) {
            // Enclose the view alias and explicit labels in quotes if Hive cannot parse it
            // without quotes. This is needed for view compatibility between Impala and Hive.
            String aliasSql = ToSqlUtils.getIdentSql(view.getName());
            if (view.hasColLabels()) {
                aliasSql += "(" + Joiner.on(", ").join(
                        ToSqlUtils.getIdentSqlList(view.getOriginalColLabels())) + ")";
            }
            viewStrings.add(aliasSql + " AS (" + view.getQueryStmt().toSqlWithSelectList() + ")");
        }
        return "WITH " + Joiner.on(",").join(viewStrings);
    }

    public String toDigest() {
        List<String> viewStrings = Lists.newArrayList();
        for (View view : views) {
            // Enclose the view alias and explicit labels in quotes if Hive cannot parse it
            // without quotes. This is needed for view compatibility between Impala and Hive.
            String aliasSql = ToSqlUtils.getIdentSql(view.getName());
            if (view.hasColLabels()) {
                aliasSql += "(" + Joiner.on(", ").join(
                        ToSqlUtils.getIdentSqlList(view.getOriginalColLabels())) + ")";
            }
            viewStrings.add(aliasSql + " AS (" + view.getQueryStmt().toDigest() + ")");
        }
        return "WITH " + Joiner.on(",").join(viewStrings);
    }

    public List<View> getViews() {
        return views;
    }
}