Package adipe.translate.ra

Source Code of adipe.translate.ra.Relation

package adipe.translate.ra;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Preconditions.checkArgument;

import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;

import ra.Term;
import ra.Utils;
import ra.operators.AggregT;
import adipe.translate.TranslationException;
import adipe.translate.impl.Proposition;
import adipe.translate.sql.Aggregated;
import adipe.translate.sql.ChainedColumnIndexes;
import adipe.translate.sql.ColumnIndexesImpl;
import adipe.translate.sql.ColumnIndexes;
import adipe.translate.sql.ColumnNamesImpl;
import adipe.translate.sql.Expression;
import adipe.translate.sql.NonAggregated;
import adipe.translate.sql.SimpleColumn;

/**
* Representation of a relation: the term defining the relation and a Columns object
* defining the attributes of the relation. Used as a partial result when parsing table
* references in the FROM clause of the sql query; and later used to build the part of the formula
* about the relation.
*/
public class Relation
{
    private final NamesRelations namesRelations;

    public NamesRelations namesRelations() {
        return namesRelations;
    }

    private final String alias;

    public String alias() {
        return alias;
    }

    private final Term formula;

    public Term formula() {
        return formula;
    }

    private final ColumnIndexesImpl columns;
    private final ColumnIndexesImpl expandedColumns;

    public ColumnIndexesImpl columns() {
        return columns;
    }

    public ColumnIndexesImpl expandedColumns() {
        return expandedColumns;
    }

    /** Returns the arity of the relation. */
    int arity() {
        return columns().size();
    }

    public boolean isEmpty() {
        return false;
    }

    @Override
    public String toString() {
        return Terms.indent(formula);
    }

    // TODO can {@link newName} be null?
    public Relation(NamesRelations namesRelations, String newName, Term formulaForTable, ColumnIndexesImpl columns, ColumnIndexesImpl expandedColumns) {
        this.namesRelations = namesRelations;
        this.alias          = newName;
        this.formula        = formulaForTable;
        this.columns        = columns;
        this.expandedColumns = expandedColumns;
    }

    Relation makeNew(Term formula, ColumnIndexesImpl columns, ColumnIndexesImpl expandedColumns) {
        return new Relation(namesRelations(), alias(), formula, columns, expandedColumns);
    }

    public Relation withAlias(String newName) {
        return new Relation(this.namesRelations(), newName, this.formula(), this.columns(), this.expandedColumns());
    }

    public Relation makeRef(Term[] ref) {
        if (ref == null) { ref = new Term[1]; }
        namesRelations.giveNameTo(this.formula(), ref);
        return new Relation(namesRelations, alias(), ref[0], columns(), expandedColumns());
    }

    public Relation expand() {
        return select(expandedColumns(), true);
    }

    public boolean isExpanded() {
        return columns().equals(expandedColumns());
    }

    /**
     * Returns a 'rel' term representing the table, which indicates for the relational algebra engine
     * that {@link arity} spreadsheets columns will be occupied with the table
     *
     * @param nameInSchema
     * @param arity          arity of relation, number of columns in table
     * @return               The term representing the formula
     */
    static Term relTableFormula(String nameInSchema, int arity) {
        return Utils.rel(nameInSchema, arity);
    }

    /**
     * Returns a 'ref' term representing the table, which have been earlier referenced with a 'rel' term
     * and are already marked for assigning spreadsheet columns
     *
     * @param nameInSchema
     * @return                  The term representing the formula
     */
    static Term refTableFormula(String nameInSchema) {
        return Utils.ref(nameInSchema);
    }

    public ColumnNamesImpl renameColumns(List<String> derivedColumnList) {
        checkState(columns().equals(expandedColumns()));

        ColumnNamesImpl ret = new ColumnNamesImpl();
        Iterator<String> sit = derivedColumnList.iterator();
        for (SimpleColumn sc : columns()) {
            ret.addColumn(sit.next(), sc);
        }
        return ret;
    }

    public static class EmptyRelation extends Relation {
        public EmptyRelation(NamesRelations namesRelations, String nameInSchema, String newName) {
            super(namesRelations, newName, null, null, null);
        }

        @Override public Relation makeRef(Term[] ref) { return this; }
        @Override public Relation multiply(Relation that) { return that; }
        @Override public Relation union(Relation that, boolean removeDuplicates) { return that; }
        @Override public boolean isEmpty() { return true; }
    }
    public Relation makeEmpty(String nameInSchema, String newName) {
        return new Relation.EmptyRelation(namesRelations, nameInSchema, newName);
    };

    public Relation leftOuterJoin(Relation that, String joinCondition, boolean conditionIsEq) {
        return outerJoin(that, joinCondition, true, false, conditionIsEq);
    }

    public Relation rightOuterJoin(Relation that, String joinCondition, boolean conditionIsEq) {
        return outerJoin(that, joinCondition, false, true, conditionIsEq);
    }

    public Relation fullOuterJoin(Relation that, String joinCondition, boolean conditionIsEq) {
        return outerJoin(that, joinCondition, true, true, conditionIsEq);
    }

    public Relation union(Relation that, boolean removeDuplicates) {
        checkState(this.isExpanded()); // TODO When they're not equal, we should
                // perform the expansion and then carry on with the union
        checkState(that.isExpanded()); // TODO
        checkArgument(this.expandedColumns().size() == that.expandedColumns().size());
        Relation res = makeNew(Utils.union(this.formula(), that.formula()), this.columns(), this.expandedColumns());
        if (removeDuplicates) {
            return res.dupRem();
        } else {
            return res;
        }
    }

    public Relation except(Relation that, boolean removeDuplicates) {
        checkState(this.isExpanded()); // TODO When they're not equal, we should
        // perform the expansion and then carry on with the 'except'
        checkState(that.isExpanded()); // TODO
        checkArgument(this.expandedColumns().size() == that.expandedColumns().size());
        Relation res = makeNew(Utils.diffSet(this.formula(), that.formula()), this.columns(), this.expandedColumns());
        if (removeDuplicates) {
            return res.dupRem();
        } else {
            return res;
        }
    }

    public Relation filter(Proposition condition, ColumnIndexes interprets) {
        return makeNew(Utils.filter(formula(), condition.code(interprets)), this.columns(), this.expandedColumns());
    }

    /**
     * @param indexes as numbered in {@link columns}, not {@link expandedColumns}
     * @return the result's {@link expandedColumns} are the projected-on columns
     */
    public Relation project(int[] indexes) {
        ColumnIndexesImpl c = new ColumnIndexesImpl(this.columns());
        c = c.select(indexes);
        return makeNew(Terms.project(this.formula(), indexes), c, c);
    }

    private Relation project(Iterable<Integer> projectionInts) {
        return project(Ints.toArray(Lists.newArrayList(projectionInts)));
    }

    public Relation orderBy(SimpleColumn sc, boolean asc) throws IndexOutOfBoundsException, TranslationException {
        return this.orderBy(1 + this.columns().find(sc), asc);
    }

    /**
     * @param columnNo  counting from 1
     * @throws TranslationException
     */
    public Relation orderBy(int columnNo, boolean asc) throws TranslationException {
        if (columnNo == 0) {
            throw new TranslationException("ORDER BY 0");
        }
        if (columnNo < 0) {
            throw new TranslationException("wrong sort key");
        }
        if (columnNo > this.columns().size()) {
            throw new TranslationException(String.format("ORDER BY %s, while there are only %s columns", columnNo, this.columns().size()));
        }
        checkArgument(columnNo <= this.columns().size());
        if (asc) {
            return makeNew(Utils.sortAsc(this.formula(), columnNo), this.columns(), this.expandedColumns());
        } else {
            return makeNew(Utils.sortDesc(this.formula(), columnNo), this.columns(), this.expandedColumns());
        }
    }

    public Relation multiply(Relation that) {
        ColumnIndexesImpl c = new ColumnIndexesImpl(this.columns());
        c.addAll(that.columns());
        ColumnIndexesImpl ec = new ColumnIndexesImpl(this.expandedColumns());
        ec.addAll(that.expandedColumns());
        return makeNew(Utils.cartProd(this.formula, that.formula), c, ec);
    }

    private Relation select(Iterable<String> sel, ColumnIndexesImpl c) {
        return this.select(Iterables.toArray(sel, String.class), c);
    }

    private Relation select(String[] sel, ColumnIndexesImpl c) {
        // TODO add check that columns present in {@link c} are also present in {@link columns()}
        return makeNew(Utils.select(this.formula(), sel), c, c);
    }

    public Relation select(Iterable<? extends Expression> selectElems, boolean allowOmit) {
        List<String> selectedStrs = Lists.newArrayList();
        List<Integer> projectionInts = Lists.newArrayList();
        boolean canUseProject = true;
        boolean canOmitProjectSelect = true;
        ColumnIndexesImpl attributesAfterSelection = new ColumnIndexesImpl();
        ChainedColumnIndexes attr =
                new ChainedColumnIndexes(new ChainedColumnIndexes(null, this.columns()));

        int i = 0;

        for (Expression expr : selectElems) {
            ++ i;
            if (expr.codeRefersToConjuredColumn()) {
                try {
                    // TODO fix this - do not even attempt to add a second time here
                    attr.find(expr.conjure().column());
                } catch (IndexOutOfBoundsException ex) {
                    attr.add(expr.conjure().column());
                }
            }
            if (! expr.isSuitableForProject()) {
                canUseProject = false;
            }
            projectionInts.add(expr.conjure().conjuredIndex(attr.columnIndexes()));
            attributesAfterSelection.add(expr.conjure().column());
            if (i > columns().size() || expr.conjure().column() != columns().get(i-1)) {
                canOmitProjectSelect = false;
            }
            selectedStrs.add(expr.code(attr.columnIndexes()));
        }

        if (attributesAfterSelection.size() != columns().size()) {
            canOmitProjectSelect = false;
        }

        if (canOmitProjectSelect && allowOmit) {
            return this;
        } else if (canUseProject) {
            return this.project(projectionInts);
        } else {
            // TODO make Terms.project(Utils.select(...)) and
            // Terms.select(Utils.select(...)) and Terms.select(Utils.project(...))
            // to Utils.project(...), Utils.select(...) optimisations, just like
            // Terms.project(Utils.project(...)) optimisation is done.
            return this.select(selectedStrs, attributesAfterSelection);
        }
    }

    public Relation groupBy(Iterable<? extends NonAggregated> exprs) {
        List<Integer> colIdxs = Lists.newArrayList();
        for (NonAggregated nc : exprs) {
            colIdxs.add(1 + columns.find(nc.conjure().column()));
        }
        int[] colsArr = Ints.toArray(colIdxs);
        ColumnIndexesImpl c = this.columns().select(exprs);
        if (colsArr.length == 0) {
          return makeNew(Utils.groupBy(this.formula()), c, c);
        } else {
          return makeNew(Utils.groupBy(this.formula(), colsArr), c, c);
        }
    }


    private final static Pattern EQ_PATTERN = Pattern.compile("#(\\d+)=#(\\d+)");

    public Relation join(Relation that, String pred, boolean conditionIsEq) {
        ColumnIndexesImpl attributesOrder = new ColumnIndexesImpl()// the columns that comprise the result of the join

        ColumnIndexesImpl attributesOrderInExpanded = new ColumnIndexesImpl();
        attributesOrderInExpanded.addAll(this.expandedColumns());
        attributesOrderInExpanded.addAll(that.expandedColumns());

        Term t1 = formula;
        Term t2 = that.formula();
        Term joined;

        if (! conditionIsEq) {
            joined = Utils.genJoin(t1, t2, pred);
            attributesOrder.addAll(this.columns());
            attributesOrder.addAll(that.columns());
        } else {
            // TODO pass index1, index2 from caller instead of reading it from the string
            Matcher eqMatcher = EQ_PATTERN.matcher(pred);
            checkState(eqMatcher.matches());
            int index1 = Integer.parseInt(eqMatcher.group(1));
            int index2 = Integer.parseInt(eqMatcher.group(2));

            if (index1 > index2) {
                int index;
                index = index2;
                index2 = index1;
                index1 = index;
            }

            index2 = index2 - this.columns().size();

            joined = Utils.eqJoin(t1, t2, index1, index2);

            attributesOrder.add(this.columns().get(index1-1));
            for (int i = 0; i < this.columns().size(); ++i) {
                if (i + 1 != index1) {
                    attributesOrder.add(this.columns().get(i));
                }
            }
            for (int i = 0; i < that.columns().size(); ++i) {
                if (i + 1 != index2) {
                    attributesOrder.add(that.columns().get(i));
                }
            }

            SimpleColumn replacedColumn = that.columns.get(index2-1);
            SimpleColumn replacedBy = this.columns.get(index1-1);
            attributesOrderInExpanded.replace(replacedColumn, replacedBy);
        }

        return new Relation(namesRelations, null, joined, attributesOrder, attributesOrderInExpanded);
    }

    private Relation outerJoin(Relation that, String joinCondition, boolean fullLeft, boolean fullRight, boolean conditionIsEq)
    {
        return this.expand().new OuterJoinMethod(that.expand(), joinCondition, fullLeft, fullRight, conditionIsEq).outerJoin();
    }


    private class OuterJoinMethod
    {
        private final Relation that;
        private final String joinCondition;
        private final boolean fullLeft;
        private final boolean fullRight;
        private final boolean conditionIsEq;


        public OuterJoinMethod(Relation that, String joinCondition, boolean fullLeft, boolean fullRight, boolean conditionIsEq) {
            checkState(Relation.this.isExpanded());
            checkArgument(that.isExpanded());
            this.that = that;
            this.joinCondition = joinCondition;
            this.fullLeft = fullLeft;
            this.fullRight = fullRight;
            this.conditionIsEq = conditionIsEq;
        }

        private final Term[] innerJoinedRef = new Term[1];
        private final Term[] leftRef = new Term[1];
        private final Term[] rightRef = new Term[1];

        private Relation innerJoinTuples() {
            return Relation.this.join(
                    that,
                    joinCondition,
                    conditionIsEq
                );
        }

        private Relation outerJoin()
        {
            Relation innerJoinTuples = innerJoinTuples().expand();
            if (fullLeft) { Relation.this.makeRef(leftRef); }
            if (fullRight) { that.makeRef(rightRef); }
            innerJoinTuples.makeRef(innerJoinedRef);

            int thatArity = that.arity();
            int[] rightColumns = new int[thatArity];
            for (int i = 0; i < rightColumns.length; ++i) {
                rightColumns[i] = arity()+i+1;
            }
            int[] leftColumns = new int[arity()];
            for (int i = 0; i < leftColumns.length; ++i) {
                leftColumns[i] = i+1;
            }
            Term leftNonInnerTuples = Terms.addTrailingNullColumns(
                    Utils.diffSet(
                        leftRef[0],
                        Terms.project(innerJoinedRef[0], leftColumns)
                    ),
                    arity(),
                    thatArity
                );
            Term rightNonInnerTuples = Terms.addLeadingNullColumns(
                Utils.diffSet(
                    rightRef[0],
                    Terms.project(innerJoinedRef[0], rightColumns)
                ),
                thatArity,
                arity()
            );
            Term nonInnerTuples = null;

            checkArgument(fullLeft || fullRight);
            if (fullLeft && fullRight) {
                    nonInnerTuples = Utils.union(
                        leftNonInnerTuples,
                        rightNonInnerTuples
                    );
            } else if (fullLeft) {
                nonInnerTuples = leftNonInnerTuples;
            } else if (fullRight) {
                nonInnerTuples = rightNonInnerTuples;
            }

            ColumnIndexesImpl expandedColumnsWithEqjoinColumnsNotMerged = new ColumnIndexesImpl(Relation.this.expandedColumns());
            expandedColumnsWithEqjoinColumnsNotMerged.addAll(that.expandedColumns());

            Relation ret = new Relation(
                        namesRelations,
                        null,
                        Utils.union(
                            innerJoinTuples.formula(),
                            nonInnerTuples
                        ),
                        expandedColumnsWithEqjoinColumnsNotMerged,
                        expandedColumnsWithEqjoinColumnsNotMerged
                    );
            return ret;
        }

    }

    public Relation applyAggregation(Aggregated ag, int columnIndex) {
        checkState(this.isExpanded()); // TODO
        checkState(this.formula() instanceof AggregT);

        ColumnIndexesImpl c = new ColumnIndexesImpl(this.columns());
        c.add(ag.conjure().column());

        return makeNew(ag.apply((AggregT) this.formula(), columnIndex), c, c);
    }

    public Relation withExpandedColumns(ColumnIndexesImpl ec) {
        // TODO this is low-level, the caller should be refactored
        return makeNew(formula, columns(), ec);
    }

  public Relation dupRem() {
    return makeNew(Utils.dupRem(formula), columns(), expandedColumns());
  }
};
TOP

Related Classes of adipe.translate.ra.Relation

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.