com.tesora.dve.sql.expression.ScopeEntry.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.sql.expression.ScopeEntry.java

Source

package com.tesora.dve.sql.expression;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import com.tesora.dve.common.MultiMap;
import com.tesora.dve.errmap.AvailableErrors;
import com.tesora.dve.errmap.ErrorInfo;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.node.LanguageNode;
import com.tesora.dve.sql.node.expression.Alias;
import com.tesora.dve.sql.node.expression.AliasInstance;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionAlias;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.FunctionCall;
import com.tesora.dve.sql.node.expression.NameInstance;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.node.expression.VariableInstance;
import com.tesora.dve.sql.node.expression.WildcardTable;
import com.tesora.dve.sql.node.structural.JoinedTable;
import com.tesora.dve.sql.parser.LexicalLocation;
import com.tesora.dve.sql.parser.SourceLocation;
import com.tesora.dve.sql.schema.Capability;
import com.tesora.dve.sql.schema.Column;
import com.tesora.dve.sql.schema.LockInfo;
import com.tesora.dve.sql.schema.MultiMapLookup;
import com.tesora.dve.sql.schema.Name;
import com.tesora.dve.sql.schema.PEColumn;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.QualifiedName;
import com.tesora.dve.sql.schema.Schema;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.SchemaLookup;
import com.tesora.dve.sql.schema.SubqueryTable;
import com.tesora.dve.sql.schema.Table;
import com.tesora.dve.sql.schema.TableResolver;
import com.tesora.dve.sql.schema.TableResolver.MissingTableFunction;
import com.tesora.dve.sql.schema.TempTable;
import com.tesora.dve.sql.schema.UnqualifiedName;
import com.tesora.dve.sql.statement.dml.ProjectingStatement;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListSet;
import com.tesora.dve.sql.util.Pair;
import com.tesora.dve.sql.util.UnaryFunction;

public class ScopeEntry implements Scope {

    // table namespace
    private MultiMap<Name, TableInstance> tableNamespace;
    // derived column namespace: aliases declared in the projection
    private MultiMap<Name, ExpressionAlias> columnNamespace;
    // columns explicitly referenced in the projection at the top level
    // used to resolve ambiguity on order by, having, group by  which may reference columns from the projection
    private ProjectionMap projectionNamespace;
    // names referenced in the group bys
    private MultiMap<Name, ExpressionNode> groupByNamespace;

    private List<ExpressionNode> projection;

    private final int scopeID;

    // if in the context of building a table definition, record columns by name as they are created here.
    // we need them when we look up columns for keys, or for distribution vectors, etc.
    private SchemaLookup<PEColumn> columnLookup;
    private Table<?> tableInProcess;

    private ScopeParsePhase phase;
    private ListSet<NameInstance> unresolved;
    private ListSet<NameInstance> unresolvedChildren;

    private List<Scope> nested;
    private ListSet<ProjectingStatement> nestedQueries;
    private ListSet<VariableInstance> variables;

    private Map<Name, FunctionCall> functions;

    public ScopeEntry(boolean doresolution, int id) {
        scopeID = id;
        tableNamespace = new MultiMap<Name, TableInstance>();
        columnNamespace = new MultiMap<Name, ExpressionAlias>();
        projectionNamespace = new ProjectionMap();
        groupByNamespace = new MultiMap<Name, ExpressionNode>();
        columnLookup = new SchemaLookup<PEColumn>(null, false, false);
        nested = new ArrayList<Scope>();
        nestedQueries = new ListSet<ProjectingStatement>();
        variables = new ListSet<VariableInstance>();
        unresolved = new ListSet<NameInstance>();
        phase = (doresolution ? ScopeParsePhase.RESOLVING : ScopeParsePhase.UNRESOLVING);
        functions = new HashMap<Name, FunctionCall>();
    }

    public int getID() {
        return scopeID;
    }

    @Override
    public ScopeParsePhase getPhase() {
        return phase;
    }

    @Override
    public void setPhase(ScopeParsePhase spp) {
        phase = spp;
    }

    @Override
    public void storeProjection(List<ExpressionNode> proj) {
        projection = proj;
        phase = ScopeParsePhase.RESOLVING_CURRENT;
    }

    @Override
    public List<Scope> getNested() {
        return nested;
    }

    @Override
    public ListSet<ProjectingStatement> getNestedQueries() {
        return nestedQueries;
    }

    @Override
    public ListSet<VariableInstance> getVariables() {
        return variables;
    }

    // the errors we throw.
    private void objectAmbiguous(String what, Name origName) throws SchemaException {
        throw new SchemaException(Pass.SECOND, "Ambiguous " + what + " reference: " + origName.getSQL());
    }

    private void throwNonUniqueTableException(final Name ambiguousTableName) {
        throw new SchemaException(
                new ErrorInfo(AvailableErrors.NON_UNIQUE_TABLE, ambiguousTableName.getUnquotedName().get()));
    }

    private static void tableNotFound(SchemaContext sc, Schema<?> schema, Name givenName) throws SchemaException {
        ErrorInfo ei = null;
        if (givenName.isQualified()) {
            QualifiedName qn = (QualifiedName) givenName;
            ei = new ErrorInfo(AvailableErrors.TABLE_DNE, qn.getNamespace().getUnquotedName().get(),
                    qn.getUnqualified().getUnquotedName().get());
        } else {
            UnqualifiedName db = schema.getSchemaName(sc);
            ei = new ErrorInfo(AvailableErrors.TABLE_DNE, db.getUnquotedName().get(),
                    givenName.getUnquotedName().get());
        }
        throw new SchemaException(ei);
    }

    private static void columnNotFound(Name columnName, LexicalLocation location) throws SchemaException {
        throw new SchemaException(new ErrorInfo(AvailableErrors.COLUMN_DNE, columnName.getUnquotedName().get(),
                location.getExternal()));
    }

    public static final TableResolver resolver = new TableResolver().withMTChecks()
            .withMissingTableFunction(new MissingTableFunction() {

                @Override
                public void onMissingTable(SchemaContext sc, Schema<?> schema, Name name) {
                    tableNotFound(sc, schema, name);
                }

            });

    // add a declared table to the scope, using any alias or the table name as the key.  if the key
    // is not unique, emit an error
    @Override
    public TableInstance buildTableInstance(Name inTableName, UnqualifiedName alias, Schema<?> inSchema,
            SchemaContext sc, LockInfo info) {
        TableInstance ti = null;
        if (sc.getCapability() == Capability.PARSING_ONLY) {
            ti = new TableInstance(null, inTableName, alias, false);
        } else {
            TableInstance raw = resolver.lookupShowTable(sc, inSchema, inTableName, info);
            ti = raw.adapt(inTableName.getUnqualified(), alias, (sc == null ? 0 : sc.getNextTable()),
                    (sc != null && sc.getOptions().isResolve()));
        }
        insertTable(ti, alias, inTableName.getUnqualified());
        return ti;
    }

    @Override
    public TableInstance buildTableInstance(Name inTableName, UnqualifiedName alias, SchemaContext sc,
            LockInfo info) {
        TableInstance raw = resolver.lookupTable(sc, inTableName, info);
        TableInstance ti = raw.adapt(inTableName.getUnqualified(), alias, (sc == null ? 0 : sc.getNextTable()),
                (sc != null && sc.getOptions().isResolve()));
        insertTable(ti, alias, inTableName.getUnqualified());
        return ti;
    }

    @Override
    public void pushVirtualTable(SubqueryTable sqt, UnqualifiedName alias, SchemaContext sc) {
        TableInstance ti = new TableInstance(sqt, alias, alias, sc.getNextTable(), false);
        insertTable(ti, alias, sqt.getName());
    }

    private void insertTable(TableInstance ti, Name alias, Name tableName) {
        if (alias != null) {
            if (tableNamespace.containsKey(alias))
                throwNonUniqueTableException(alias);
            tableNamespace.put(alias, ti);
        } else {
            if (tableNamespace.containsKey(tableName))
                throwNonUniqueTableException(tableName);
            tableNamespace.put(tableName, ti);
        }
    }

    @Override
    public void insertTable(TableInstance ti) {
        if (ti.getAlias() != null) {
            if (tableNamespace.containsKey(ti.getAlias()))
                throwNonUniqueTableException(ti.getAlias());
            tableNamespace.put(ti.getAlias(), ti);
        } else {
            if (tableNamespace.containsKey(ti.getTable().getName()))
                throwNonUniqueTableException(ti.getTable().getName());
            tableNamespace.put(ti.getTable().getName(), ti);
        }
    }

    @Override
    public TableInstance lookupTableInstance(SchemaContext sc, Name given, boolean required) {
        if (!given.isQualified()) {
            Collection<TableInstance> sub = tableNamespace.get(given);
            if (sub == null || sub.isEmpty()) {
                if (required) {
                    tableNotFound(sc, sc.getCurrentDatabase().getSchema(), given);
                } else {
                    return null;
                }
            } else if (sub.size() > 1) {
                throwNonUniqueTableException(given);
            } else {
                return sub.iterator().next();
            }
        } else {
            throw new SchemaException(Pass.SECOND, "Internal error: qualified table name: " + given.getSQL());
        }
        return null;
    }

    private ColumnInstance buildColumnInstance(SchemaContext sc, Name given, Column<?> c, TableInstance ti) {
        if (sc != null) {
            if (sc.getOptions().isRawPlanStep() && ti.getTable() instanceof TempTable)
                return new ColumnInstance(c, ti);
        }
        return new ColumnInstance(given, c, ti);
    }

    @Override
    public ExpressionNode buildColumnInstance(SchemaContext sc, Name igiven) {
        Name given = igiven;
        // still building the projection - delay resolution until later
        if (phase == ScopeParsePhase.UNRESOLVING) {
            NameInstance ni = new NameInstance(given, null);
            unresolved.add(ni);
            return ni;
        }
        if (given.isQualified()) {
            QualifiedName qn = (QualifiedName) given;
            UnqualifiedName tableName = qn.getNamespace();
            UnqualifiedName columnName = given.getUnqualified();
            TableInstance ti = lookupTableInstance(sc, tableName, false);
            if (ti == null)
                columnNotFound(given, phase.getLocation());
            @SuppressWarnings("null")
            Column<?> c = ti.getTable().lookup(sc, given.getUnqualified());
            if (columnName.isAsterisk()) {
                return new WildcardTable(tableName, ti);
            }
            if (c == null)
                columnNotFound(given, phase.getLocation());
            return buildColumnInstance(sc, given, c, ti);
        }
        // if we're in a restricted namespace, try to build by derived first, then try by table
        ExpressionNode any = null;
        // the order we search depends on the phase
        switch (phase) {
        case RESOLVING_CURRENT:
        case RESOLVING:
            // from and where clauses, can only build by table
            any = buildColumnRefByTable(sc, given);
            break;
        case GROUPBY:
            // start from table, then look at column ref by projection, finally column ref by derived
            // once found, put into the group by namespace
            any = buildColumnRefByDerived(given);
            if (any == null)
                any = buildColumnRefByProjection(given);
            if (any == null)
                any = buildColumnRefByTable(sc, given);
            if (any != null)
                groupByNamespace.put(given, any);
            break;
        case HAVING:
            // start from group by namespace, column ref by projection, column ref by derived
            // if we have an expression, we can't use the group by column
            any = buildColumnRefByTable(sc, given);
            if (any == null)
                any = buildColumnRefByProjection(given);
            if (any == null)
                any = buildColumnRefByDerived(given/*, false*/);
            if (any == null)
                any = buildColumnRefByGroupBy(given);
            break;
        case TRAILING:
            // start from column ref by derived, then column ref by projection
            any = buildColumnRefByDerived(given);
            if (any == null)
                any = buildColumnRefByProjection(given);
            if (any == null)
                any = buildColumnRefByTable(sc, given);
            break;
        default:
            throw new IllegalArgumentException("Invalid scope parse phase: " + phase);
        }
        if (any == null)
            columnNotFound(given, phase.getLocation());
        return any;
    }

    @Override
    public void resolveProjection(SchemaContext sc) {

        if (unresolved != null && unresolved.size() > 0 && tableNamespace.keySet().size() == 0) {
            // will not resolve so try later
            return;
        }

        phase = ScopeParsePhase.RESOLVING;
        // look for any top level name instances
        HashMap<NameInstance, Integer> offsets = new HashMap<NameInstance, Integer>();
        for (int i = 0; i < projection.size(); i++) {
            ExpressionNode en = projection.get(i);
            if (en instanceof NameInstance) {
                offsets.put((NameInstance) en, i);
            }
        }
        for (NameInstance ni : unresolved) {
            Name embedded = ni.getName();
            ExpressionNode ci = buildColumnInstance(sc, embedded);
            if (ni.isNegated()) {
                ci.setNegated();
            }
            if (ni.getParent() == null) {
                Integer index = offsets.get(ni);
                if (index != null) {
                    projection.remove(index.intValue());
                    projection.add(index.intValue(), ci);

                    if (ci instanceof ColumnInstance) {
                        ColumnInstance nci = (ColumnInstance) ci;
                        projectionNamespace.add(nci);
                    } else if (ci instanceof WildcardTable) {
                        WildcardTable wct = (WildcardTable) ci;
                        for (Column<?> c : wct.getTableInstance().getTable().getColumns(sc)) {
                            projectionNamespace.add(new ColumnInstance(c, wct.getTableInstance()));
                        }
                    }
                } else {
                    index = null;
                    for (int i = 0; i < getUnresolvedChildren().size(); i++) {
                        NameInstance urcni = getUnresolvedChildren().get(i);
                        if (StringUtils.equals(urcni.getName().get(), ni.getName().get())) {
                            index = i;
                            urcni.getParentEdge().set(ci);
                            getUnresolvedChildren().remove(index.intValue());
                            break;
                        }
                    }
                }
                if (index == null)
                    throw new SchemaException(Pass.SECOND, "Lost unresolved column: " + embedded.getSQL());
            } else {
                ni.getParentEdge().set(ci);
            }
        }
        unresolved.clear();
    }

    // find a column ref by looking at ref'd tables first
    private ExpressionNode buildColumnRefByTable(SchemaContext sc, Name given) {
        // look up among the table namespaces
        // we might have two unaliased tables with the same name - so keep looking after we find our first candidate
        Pair<TableInstance, Column<?>> candidate = null;
        for (TableInstance ti : tableNamespace.values()) {
            Column<?> c = ti.getTable().lookup(sc, given);
            if (c != null) {
                if (candidate != null) {
                    final LanguageNode parent = ti.getParent();
                    /*
                     * MySQL now treats the common columns of NATURAL or USING
                     * joins as a single column, so when a query refers to such
                     * columns, the query compiler does not consider them as
                     * ambiguous.
                     */
                    if ((parent instanceof JoinedTable) && ((JoinedTable) parent).getJoinType().isNaturalJoin()) {
                        continue;
                    }
                    objectAmbiguous("Column", given);
                } else {
                    candidate = new Pair<TableInstance, Column<?>>(ti, c);
                }
            }
        }
        if (candidate == null)
            return null;
        // build the reference
        return buildColumnInstance(sc, given, candidate.getSecond(), candidate.getFirst());
    }

    private ExpressionNode buildColumnRefByDerived(Name given) {
        Collection<ExpressionAlias> sub = columnNamespace.get(given);
        if (sub == null || sub.isEmpty())
            return null;
        if (sub.size() > 1)
            objectAmbiguous("Derived column", given);

        ExpressionAlias targ = sub.iterator().next();
        return new AliasInstance(targ, given.getOrig());
    }

    private ExpressionNode buildColumnRefByProjection(Name given) {
        Collection<ColumnInstance> sub = projectionNamespace.lookup(given);
        if (sub == null || sub.isEmpty())
            return null;
        if (sub.size() > 1)
            objectAmbiguous("Referenced column", given);
        ColumnInstance ci = sub.iterator().next();
        return (ExpressionNode) ci.copy(null);
    }

    private ExpressionNode buildColumnRefByGroupBy(Name given) {
        Collection<ExpressionNode> sub = groupByNamespace.get(given);
        if (sub == null || sub.isEmpty())
            return null;
        if (sub.size() > 1)
            objectAmbiguous("Referenced column", given);
        ExpressionNode en = sub.iterator().next();
        return (ExpressionNode) en.copy(null);
    }

    @Override
    public void insertColumn(UnqualifiedName alias, ExpressionAlias e) {
        columnNamespace.put(alias, e);
    }

    @Override
    public ExpressionNode buildExpressionAlias(ExpressionNode e, Alias alias, SourceLocation sloc) {
        ExpressionAlias ea = new ExpressionAlias(e, alias, sloc);
        if (alias.isName()) {
            columnNamespace.put(alias.getNameAlias(), ea);
        }
        return ea;
    }

    @Override
    public Set<String> getAllAliases() {
        HashSet<String> ret = new HashSet<String>();
        ret.addAll(Functional.apply(tableNamespace.keySet(), buildUnquoted));
        ret.addAll(Functional.apply(columnNamespace.keySet(), buildUnquoted));
        return ret;
    }

    @Override
    public ListSet<TableKey> getLocalTables() {
        ListSet<TableKey> tabs = new ListSet<TableKey>();
        for (TableInstance ti : tableNamespace.values()) {
            if (ti.getTable() instanceof PETable) {
                PETable pet = (PETable) ti.getTable();
                if (pet.isVirtualTable())
                    continue;
            }
            tabs.add(ti.getTableKey());
        }
        return tabs;
    }

    @Override
    public ListSet<TableKey> getAllVisibleTables() {
        ListSet<TableKey> out = new ListSet<TableKey>();
        out.addAll(getLocalTables());
        for (Scope c : nested)
            out.addAll(c.getAllVisibleTables());
        return out;
    }

    @Override
    public ListSet<FunctionCall> getFunctions() {
        ListSet<FunctionCall> funcs = new ListSet<FunctionCall>(functions.values());
        return funcs;
    }

    @Override
    public void registerFunction(FunctionCall fc) {
        functions.put(fc.getFunctionName().getUnquotedName(), fc);
    }

    @Override
    public PEColumn registerColumn(PEColumn c) {
        columnLookup.add(c);
        return c;
    }

    @Override
    public void registerAlterColumns(SchemaContext sc, PETable tab) {
        tableInProcess = tab;
        for (PEColumn c : tab.getColumns(sc)) {
            registerColumn(c);
        }
    }

    @Override
    public PEColumn lookupInProcessColumn(Name n) {
        return columnLookup.lookup(n);
    }

    @Override
    public Table<?> getAlteredTable() {
        return tableInProcess;
    }

    public ListSet<NameInstance> getUnresolved() {
        return unresolved;
    }

    @Override
    public ListSet<NameInstance> getUnresolvedChildren() {
        if (unresolvedChildren == null) {
            unresolvedChildren = new ListSet<NameInstance>();
        }
        return unresolvedChildren;
    }

    private static final UnaryFunction<String, Name> buildUnquoted = new UnaryFunction<String, Name>() {

        @Override
        public String evaluate(Name object) {
            return object.get();
        }

    };

    private static class ProjectionMap extends MultiMapLookup<ColumnInstance> {

        public ProjectionMap() {
            super(false, false, new UnaryFunction<Name[], ColumnInstance>() {

                @Override
                public Name[] evaluate(ColumnInstance object) {
                    return new Name[] { object.getColumn().getName() };
                }

            });
        }

        public void add(ColumnInstance ci) {
            add(Collections.singleton(ci));
        }
    }

}