Package adipe.translate.impl

Source Code of adipe.translate.impl.QueryExpressionTranslation$TablesRelation

package adipe.translate.impl;

import adipe.translate.sql.ColumnNamesImpl;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.util.Iterator;
import java.util.List;

import adipe.translate.AmbiguousNameException;
import adipe.translate.TranslationException;
import adipe.translate.TypeCheckException;
import adipe.translate.WrappedException;
import adipe.translate.ra.RaTermBuilder;
import adipe.translate.ra.Relation;
import adipe.translate.ra.Schema;
import adipe.translate.sql.ColumnIndexesImpl;
import adipe.translate.sql.ColumnIndexes;
import adipe.translate.sql.ColumnNamesLookup;
import adipe.translate.sql.Expression;
import adipe.translate.sql.SimpleColumn;
import adipe.translate.sql.State2;
import adipe.translate.sql.Type;
import adipe.translate.sql.parser.SqlParser.*;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.Lists;

/**
* Process a query expression during translation
*
*/
class QueryExpressionTranslation {

    private final State state;

    private Scope       scopeIncludingAliases;
    private final State2 state2;

    class TablesRelation
    {
        /** the formula representing the tables from the FROM clause */
        Relation value;

        TablesRelation(Relation tablesRelation) {
            this.value = tablesRelation;
        }

        Relation get() {
            return value.makeRef(null);
        }

        void set(Relation tablesRelation) {
            this.value = tablesRelation;
        }
    }

    private final TablesRelation tablesRelation;

    private final Schema schema;

    /**
     * @throws none
     */
    static QueryExpressionTranslation create(State state, Schema schema) {
        return new QueryExpressionTranslation(state, schema);
    }

    /**
     * @throws none
     */
    private QueryExpressionTranslation(State state, Schema schema) {
        this.state = state;
        this.tablesRelation = new TablesRelation(this.state.rela);
        this.schema = schema;
        this.state2 = new State2(this.tablesRelation.value, new ColumnIndexesImpl(), this.state.scope.columnScope().asColumnNames(), state.scope.tableScope());
    }

    /**
    * @throws IndexOutOfBoundsException when a table name is referenced, that is not found in the schema
    * @throws RuntimeException
    * @throws NumberFormatException when ORDER BY unsignedInteger number is too big
    * @throws {@link WrappedException}
    * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
    * TODO update
    */
    Relation visitSelectStatement(SelectStatementContext ctx)
            throws IndexOutOfBoundsException, RuntimeException, WrappedException, TranslationException
    {
        Relation unordered = visitQueryExpression(ctx.queryExpression());
        if (ctx.orderByClause() == null) {
            return unordered;
        }
        SortSpecificationListContext sslc = ctx.orderByClause().sortSpecificationList();
        //TODO support ORDER BY multiple columns
        checkState(sslc.COMMA().isEmpty());
        SortSpecificationContext ssc = sslc.sortSpecification(0);

        boolean asc = ssc.orderingSpecification() == null || ssc.orderingSpecification().ASC() != null;

        if (ssc.sortKey().columnName() != null) {
            SimpleColumn sc = scopeIncludingAliases.getColumnIcShdWrapNambNind(ssc.sortKey().columnName().getText());
            return unordered.orderBy(sc, asc);
        } else {
            int columnNo = visitUnsignedInteger(ssc.sortKey().unsignedInteger());
            return unordered.orderBy(columnNo, asc);
        }
    }

    /**
     * @throws NumberFormatException when number is too big
     * @return a non-negative number
     */
    private int visitUnsignedInteger(UnsignedIntegerContext ctx) {
        //TODO is the int type enough?
        try {
            int i = Integer.parseInt(ctx.getText());
            checkState(i >= 0);
            return i;
        } catch (NumberFormatException ex) {
            NumberFormatException e = new NumberFormatException(String.format("Couldn't parse unsignedInteger, %s", ex.getMessage()));
            e.addSuppressed(ex);
            throw e;
        }
    }

    /**
     * @throws IndexOutOfBoundsException when a table name is referenced, that is not found in the schema
     * @throws RuntimeException
     * @throws {@link WrappedException}
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     * TODO update
     */
    Relation visitQueryExpression(QueryExpressionContext ctx)
            throws IndexOutOfBoundsException, RuntimeException, WrappedException, TranslationException
    {
        if (ctx.joinedTable() == null)
        {
            nonJoinQueryExpression(ctx.nonJoinQueryTerm(), ctx.queryExpression(),
                    ctx.EXCEPT() == null, ctx.ALL() != null, ctx.correspondingSpec(), ctx.queryTerm());
        } else {
            // branch for "p JOIN p ON 1=1" (without SELECT)
            // SELECT * FROM pracownicy UNION pracownicy JOIN pracownicy ON 1=1
            joinedTable(ctx.joinedTable());
        }

        state.installMissingAttributes();

        return state.rela;
    }

    /**
     * @throws IndexOutOfBoundsException when a table name is referenced, that is not found in the schema
     * @throws RuntimeException
     * @throws {@link WrappedException}
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    private void nonJoinQueryExpression(NonJoinQueryTermContext nonJoinQueryTerm,
            QueryExpressionContext queryExpression, boolean union_notExcept, boolean all,
            CorrespondingSpecContext correspondingSpec,
            QueryTermContext queryTerm)
                    throws IndexOutOfBoundsException, WrappedException, RuntimeException, TranslationException
    {
        if (nonJoinQueryTerm != null) {
            visitNonJoinQueryTerm(nonJoinQueryTerm);
        } else {
            unionExcept(queryExpression, union_notExcept, all, correspondingSpec, queryTerm);
        }
    }

    /**
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    private void visitNonJoinQueryExpression(NonJoinQueryExpressionContext ctx)
        throws TranslationException
    {
        nonJoinQueryExpression(ctx.nonJoinQueryTerm(), ctx.queryExpression(),
                ctx.EXCEPT() == null, ctx.ALL() != null, ctx.correspondingSpec(), ctx.queryTerm());
    }

    /**
     * Create a new TranslationVisitor that can be used to translate a subquery.
     * Uses the current scope as its outer scope.
     * @param outerTablesFormula formula representing the outer tables as seen from the new {@link TranslationVisitor}
     */
    private TranslationVisitor subqueryTranslationVisitor(Relation outerTablesRelation) {
        return new TranslationVisitor(
                schema,
                state.scope.columnScope(),
                state.scope.attributes(), // pass scopes to be used as outer scopes to run the query in
                state.scope.tableScope(),
                outerTablesRelation
        );
    }

    /**
     * Create a new TranslationVisitor that can be used to translate a subquery.
     * Uses the currently outer scope as its own outer scope.
     * Thus any tables or columns added in this scope will not be visible.
     * Assumes {@link tablesFormula} was not updated with the new tables found, yet.
     */
    private TranslationVisitor subqueryTranslationVisitorWithOuterScope() {
        return new TranslationVisitor(
                schema,
                state.scope.columnScope().outer(),
                state.scope.attributes().outer()// pass scopes to be used as outer scopes to run the query in
                state.scope.tableScope().outer(),
                tablesRelation.get()
        );
    }

    /**
     * Creates a {@link TranslatesSubqueries} instance using a new {@link TranslationVisitor}
     * instance and the contexts, scope etc. of this {@link QueriesExpressionTranslation}
     */
    private TranslatesSubqueries newTranslatesSubqueries() {
        return new TranslatesSubqueries() {

            /**
             * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
             */
            @Override
            public Relation translate(SubqueryContext ctx, String newName, List<String> derivedColumnList, ColumnNamesImpl[] colNamesWr)
                throws TranslationException, IndexOutOfBoundsException, RuntimeException, WrappedException
            {
                TranslationVisitor tv;
                Relation formula;

                tv      = subqueryTranslationVisitorWithOuterScope();
                formula = tv.visitQueryExpression(ctx.queryExpression());

                if (derivedColumnList != null) {
                    colNamesWr[0] = formula.renameColumns(derivedColumnList);
                } else {
                    colNamesWr[0] = new ColumnNamesImpl(formula.columns());
                }
                return formula.withAlias(newName);
            }

        };
    }

    /**
     *
     * @throws IndexOutOfBoundsException when a table name is referenced, that is not found in the schema
     * @throws RuntimeException
     * @throws WrappedException
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    private void unionExcept(QueryExpressionContext queryExpression, boolean union_notExcept, boolean all,
            CorrespondingSpecContext correspondingSpec,
            QueryTermContext queryTerm)
                    throws IndexOutOfBoundsException, WrappedException, RuntimeException, TranslationException
    {

        TranslationVisitor rightTranslator = subqueryTranslationVisitorWithOuterScope();

        visitQueryExpression(queryExpression);
        rightTranslator.visitQueryTerm(queryTerm);

        final int leftSize  =            this.state.rela.columns().size();
        final int rightSize = rightTranslator.state.rela.columns().size();

        if (leftSize != rightSize)
        {
            throw new RuntimeException(String.format("Union: lhs has %d column(s), rhs has %d column(s)", leftSize, rightSize));
        }

        for (Iterator<SimpleColumn> li = this.state.rela.columns().iterator(),
                ri = rightTranslator.state.rela.columns().iterator();
                li.hasNext(); )
        {
            Type lt = li.next().type();
            Type rt = ri.next().type();
            lt.checkCanSetOperations(rt);
        }

        boolean removeDuplicates = ! all;

        Relation rightFormula = rightTranslator.state.rela;
        if (union_notExcept) {
            state.rela = state.rela.union(rightFormula, removeDuplicates);
        } else {
            state.rela = state.rela.except(rightFormula, removeDuplicates);
        }

        state.updateColumnScopeWithRelationsColumns();
    }

    /**
     * @throws IndexOutOfBoundsException when a table name is referenced that is not found in the schema
     * @throws RuntimeException
     * @throws WrappedException
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    void visitQueryTerm(QueryTermContext ctx)
            throws IndexOutOfBoundsException, RuntimeException, WrappedException, TranslationException
    {
        if (ctx.joinedTable() == null) {
            visitNonJoinQueryTerm(ctx.nonJoinQueryTerm());
        } else {
            // branch for "SELECT * FROM p UNION p JOIN p ON 1=1", compliant with SQL-92
            joinedTable(ctx.joinedTable());
        }

        state.installMissingAttributes();
    }

    /**
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    private void joinedTable(JoinedTableContext ctx)
        throws TranslationException
    {
        TableReferenceTranslation.of(
                ctx,
                StoresRelationsUtils.of(state),
                newTranslatesSubqueries(),
                schema
            ).interpret();
        updateTablesFormula();
    }

    /**
     * @throws IndexOutOfBoundsException When a table name is referenced, that is not found in the schema
     * @throws WrappedException
     * @throws RuntimeException
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    private void visitNonJoinQueryTerm(NonJoinQueryTermContext ctx)
            throws IndexOutOfBoundsException, RuntimeException, WrappedException, TranslationException
    {
        if (ctx.nonJoinQueryPrimary() != null) {
            visitNonJoinQueryPrimary(ctx.nonJoinQueryPrimary());
        } else {
            checkNotNull(ctx.nonJoinQueryPrimary());
            //TODO
            // zawodzi dla INTERSECT: np. SELECT * FROM p INTERSECT SELECT * FROM p
        }
    }

    /**
     * @throws IndexOutOfBoundsException when a table name is referenced, that is not found in the schema
     * @throws RuntimeException
     * @throws WrappedException
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    private void visitNonJoinQueryPrimary(NonJoinQueryPrimaryContext ctx)
            throws IndexOutOfBoundsException, RuntimeException, WrappedException, TranslationException
    {
        if (ctx.simpleTable() != null)
        {
            visitSimpleTable(ctx.simpleTable());
        } else {
            // branch for "(SELECT * FROM p)" - in parens, compliant with SQL-92
            // (SELECT * FROM p) UNION SELECT * FROM p
            // SELECT * FROM p UNION (SELECT * FROM p UNION SELECT * FROM p)
            visitNonJoinQueryExpression(ctx.nonJoinQueryExpression());
        }
    }

    /**
     * @throws IndexOutOfBoundsException when a table name is referenced, that is not found in the schema
     * @throws WrappedException
     * @throws {@link RuntimeException}
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    private void visitSimpleTable(SimpleTableContext ctx)
            throws IndexOutOfBoundsException, WrappedException, RuntimeException, TranslationException
    {
        if (ctx.querySpecification() != null) {
            visitQuerySpecification(ctx.querySpecification());
        } else if (ctx.explicitTable() != null) {
            visitExplicitTable(ctx.explicitTable());
            // This branch corresponds to the query "TABLE p"
        } else {
            checkNotNull(ctx.tableValueConstructor());
            visitTableValueConstructor(ctx.tableValueConstructor());
            // This branch corresponds to the query "VALUES (1,2),(2,3)"
        }
    }

    private void visitTableValueConstructor(TableValueConstructorContext tableValueConstructor) {
        throw new RuntimeException("tableValueConstructor not implemented");
    }

    /**
     * @throws IndexOutOfBoundsException when no table of this name is found in the schema
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     */
    private void visitExplicitTable(ExplicitTableContext explicitTable)
            throws IndexOutOfBoundsException, TranslationException
    {
        TableReferenceTranslation.of(
            explicitTable.tableName().getText(),
            StoresRelationsUtils.of(state),
            schema
        ).interpret();
        updateTablesFormula();
    }

    /**
     * Process the query specification, including the SELECT, FROM, WHERE, GROUP BY,
     * HAVING and ORDER clauses
     * @param ctx   querySpecification context
     * @side Updates the codomain, stored in {@code state.scope}
     * @throws WrappedException when an ambiguous column name is found in the SELECT clause
     * @throws WrappedException other reasons TODO update comment
     * @throws RuntimeException when the query has the form SELECT * FROM.. GROUP BY..
     * @throws IndexOutOfBoundsException when a table name is referenced that is not found in the schema
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     *
     * TODO respect setQuantifier
     */
    private void visitQuerySpecification(QuerySpecificationContext ctx)
            throws WrappedException, IndexOutOfBoundsException, TranslationException
    {
        visitFromClause(ctx.tableExpression().fromClause());
        /* {@code state.formula} now represents the tables in the FROM clause*/

        final RaTermBuilder raBuilder = RaTermBuilder.create();
        List<Expression> selected =
                processSelectList(raBuilder, ctx.selectList());

        boolean isSelectAsterisk = (selected == null);

        if (isSelectAsterisk) {
            // TODO refactor, move to {@link processSelectList}
            selected = state.nonParameterAttributes();
        }

        /* {@link #scopeIncludingAliases} now includes column aliases, as defined in the SELECT clause,
         * used in {@link #visitColumnReference} when interpreting references to columns.
         * Will be null for SELECT * queries
         */

        GroupByClauseContext groupByClause;
        HavingClauseContext havingClause;

        groupByClause = ctx.tableExpression().groupByClause(); /* null if no GROUP BY clause is present */
        havingClause = ctx.tableExpression().havingClause();


        if (groupByClause != null && isSelectAsterisk) {
            /* SELECT * FROM.. GROUP BY.. */
            throw new TranslationException("a query cannot have the form of 'SELECT * ... GROUP BY'");
        }
        if (havingClause != null && isSelectAsterisk) {
            /* SELECT * FROM.. HAVING.. */
            throw new TranslationException("a query cannot have the form of 'SELECT * ... HAVING'");
        }

        List<SimpleColumn> groupByExpressions = null;
        if (groupByClause != null) {
            groupByExpressions = Lists.newArrayList(visitGroupByClause(groupByClause));
        } else if (havingClause != null) {
            groupByExpressions = Lists.<SimpleColumn>newArrayList();
        }

        processWhereClause(ctx.tableExpression().whereClause());

        raBuilder.select(selected);
        raBuilder.groupBy(groupByExpressions);
        raBuilder.withBaseRelation(state.rela);
        raBuilder.withQueryParameters(state.parameterAttributes());

        if (havingClause != null) {
            // TODO do not use WrappedException in InterpretsColumnReferences
            Proposition havingCondition = ConditionTranslation.of(columnNamesLookupForHaving(raBuilder), raBuilder, havingClause.searchCondition()).proposition();
            raBuilder.withHaving(havingCondition);
        }

        state.rela = raBuilder.build();

        state.updateColumnScopeWithRelationsColumns();

        // TODO test this further in SelectFromTest.SelectDistinct
        boolean selectDistinct = ctx.setQuantifier() != null && ctx.setQuantifier().DISTINCT() != null;
        if (selectDistinct) {
            state.rela = state.rela.dupRem();
        }
    }

    /**
     * @side    Interpret all tableReference terms in the FROM clause, adds the tables to the scope
     *          and to the formula
     * @side    update {@link #tablesRelation} to contain the term describing the
     *          tables; update {@code state.formula} in the process to contain the alias
     * @side    clobbers {@link #scopeIncludingAliases} (used elsewhere, e.g., in {@link #processSelectList})
     * @throw {@link IndexOutOfBoundsException} when a table name is referenced that is not found in the schema
     * @throws TranslationException when a derived table is parsed, but has a derived column list of the wrong size
     * @param   ctx
     */
    private void visitFromClause(FromClauseContext ctx)
        throws IndexOutOfBoundsException, TranslationException
    {
        for (TableReferenceContext trctx : ctx.tableReference()) {
            TableReferenceTranslation.of(trctx, StoresRelationsUtils.of(state), newTranslatesSubqueries(), schema).interpret();
        }
        /* {@link state.formula} contains only the FROM clause at this point */
        updateTablesFormula();
    }

    /**
     * @side update {@link #tablesRelation} to contain the term describing the tables in {@code state.formula}
     * @side may update {@code state.formula} in the process to contain the alias
     */
    private void updateTablesFormula() {
        tablesRelation.set(state.rela);
    }

    private List<Expression> processSelectList(RaTermBuilder raBuilder, SelectListContext ctx) throws WrappedException, TypeCheckException {
        ColumnScope columnScopeIncludingAliases = state.scope.columnScope().copy();
        checkState(scopeIncludingAliases == null);
        scopeIncludingAliases = new Scope(columnScopeIncludingAliases, null, state.scope.tableScope());

        return new ProcessSelectListMethod(columnScopeIncludingAliases, columnNamesLookupForSelectSublist()).processSelectList(raBuilder, ctx);
    }

    private List<SimpleColumn> visitGroupByClause(GroupByClauseContext ctx) throws WrappedException {
        return new VisitGroupByClauseMethod(scopeIncludingAliases).visitGroupByClause(ctx);
    }

    VisitWhereClauseMethod visitWhereClauseMethod() {
        Function<Relation, TranslationVisitor> subqueryTranslationVisitor = new Function<Relation, TranslationVisitor>() {

            @Override
            public TranslationVisitor apply(Relation outerTablesRelation) {
                return subqueryTranslationVisitor(outerTablesRelation);
            }

        };
        return new VisitWhereClauseMethod(state, tablesRelation, columnNamesLookupForWhere(), subqueryTranslationVisitor);
    }

    @VisibleForTesting
    Proposition visitWhereClause(WhereClauseContext ctx) throws WrappedException, TypeCheckException, RuntimeException {
        return visitWhereClauseMethod().visitWhereClause(ctx);
    }

    private void processWhereClause(WhereClauseContext whereClause) throws WrappedException, RuntimeException, TranslationException {
        VisitWhereClauseMethod method = visitWhereClauseMethod();
        if (whereClause != null) {
            Proposition condition = method.visitWhereClause(whereClause);
            method.processCondition(condition, columnNamesLookupForWhere(), columnIndexesForWhere());
            // TODO consider moving {@link visitWhereClause} and {@link processCondition} logic
            // into {@link RaTermBuilder}
        }
    }

    private ColumnNamesLookup columnNamesLookupForWhere() {
        // TODO make a #of(Scope) method that returns the following instance
        return new ColumnNamesLookup() {
            @Override
            public SimpleColumn byColumnName(String columnNameIc) throws WrappedException {
                return state.scope.getColumnIcShdWrapNambNind(columnNameIc);
            }
        };
    }

    private ColumnIndexes columnIndexesForWhere() {
        return new ColumnIndexes() {
            @Override
            public int index(SimpleColumn column) {
                return state.rela.columns().find(column) + 1;
            }
        };
    }

    private ColumnNamesLookup columnNamesLookupForSelectSublist() {
        return new ColumnNamesLookup() {
            @Override
            public SimpleColumn byColumnName(String columnName) throws WrappedException {
                // TODO repeated code in TableReferenceTranslation and others
                try {
                    return state2.getColumnIcShdNwrapAmbInd(columnName);
                } catch (AmbiguousNameException|IndexOutOfBoundsException exc2) {
                    throw new WrappedException(exc2);
                }
            }
        };
    }

    private ColumnNamesLookup columnNamesLookupForHaving(final RaTermBuilder raBuilder) {
        return new ColumnNamesLookup() {

            @Override
            public SimpleColumn byColumnName(String columnName) throws WrappedException {
                return scopeIncludingAliases.getColumnIcShdWrapNambNind(columnName);
            }
        };
    }

    @Override
    public String toString() {
        return state.rela.toString();
    }
}


// TODO use Collections#unmodifiableCollection to pass views to collections
TOP

Related Classes of adipe.translate.impl.QueryExpressionTranslation$TablesRelation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.