Java tutorial
/******************************************************************************* * Copyright (C) 2013, 2014, 2015 by Inria and Paris-Sud University * * 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 fr.inria.oak.paxquery.algebra.optimizer.rules; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.google.common.collect.Sets; import fr.inria.oak.paxquery.algebra.operators.BaseLogicalOperator; import fr.inria.oak.paxquery.algebra.operators.binary.BaseBinaryOperator; import fr.inria.oak.paxquery.algebra.operators.binary.CartesianProduct; import fr.inria.oak.paxquery.algebra.operators.binary.Join; import fr.inria.oak.paxquery.algebra.operators.binary.LeftOuterJoin; import fr.inria.oak.paxquery.algebra.operators.binary.LeftOuterNestedJoin; import fr.inria.oak.paxquery.algebra.operators.unary.BaseUnaryOperator; import fr.inria.oak.paxquery.algebra.operators.unary.Navigation; import fr.inria.oak.paxquery.algebra.operators.unary.Projection; import fr.inria.oak.paxquery.algebra.operators.unary.Selection; import fr.inria.oak.paxquery.common.exception.PAXQueryExecutionException; import fr.inria.oak.paxquery.common.predicates.BasePredicate; import fr.inria.oak.paxquery.common.predicates.ConjunctivePredicate; import fr.inria.oak.paxquery.common.predicates.DisjunctivePredicate; import fr.inria.oak.paxquery.common.predicates.SimplePredicate; /** * This class provides static methods for logical plan rewriting. It implements methods used by * {@link PushSelections} and {@link PushProjections}. * */ public class PushdownUtility { private static final Log LOG = LogFactory.getLog(PushdownUtility.class); /** * <p> * This method recreates an existing {@link LogicalOperator} to take into account any possible * changes to its children. It updates projection masks and predicates to match any column index * changes. * </p> * * <p> * Supported operators: {@link Selection}, {@link Sort}, {@link Projection}, {@link Join}, * {@link StructJoin}, {@link CartesianProduct}, {@link NaryAppend}, {@link SimpleUnion}<br /> * Unsupported operators are simply returned as they were given. * </p> * * @param op * the logical operator to be recreated * @param newChildren * the list of new children of the logical operator * @param myUpdatedColumns * an array of old column indices, indexed by the new column indices, or <tt>null</tt> if * an identical mapping is desired. * @return a logical operator that matches the given rewritings * @throws PAXQueryExecutionException * when the recreation of the logical operator itself failed */ public static BaseLogicalOperator recreateOp(BaseLogicalOperator op, List<BaseLogicalOperator> newChildren, List<Integer> myUpdatedColumns) { // Do not recreate operators unless their rewriting is explicitly implemented. BaseLogicalOperator newOp = op; // Compute the map of old-to-new indices. Map<Integer, Integer> oldColumnMap = new HashMap<Integer, Integer>(); if (myUpdatedColumns != null) { for (int i = 0; i < myUpdatedColumns.size(); i++) { oldColumnMap.put(myUpdatedColumns.get(i), i); } } else { for (int i = 0; i < op.getNRSMD().colNo; i++) { oldColumnMap.put(i, i); } } // Recreate logical operator with given children if (op instanceof BaseUnaryOperator) { assert newChildren.size() == 1; BaseLogicalOperator newChild = newChildren.get(0); // Create the new operator if (op instanceof Selection) { Selection sel = (Selection) op; newOp = new Selection(newChild, updatePredicate(sel.getPred(), oldColumnMap)); } else if (op instanceof Navigation) { Navigation pnop = (Navigation) op; // First we update the columns used for the navigation with the new // values contained in oldColumnMap int newColumnsPos = oldColumnMap.get(pnop.pos); // We copy the values contained in the oldColumnMap in int[] newColumns int[] newColumns = new int[oldColumnMap.size()]; Iterator<Integer> oldColumnMapKeysIterator = oldColumnMap.keySet().iterator(); for (int i = 0; i < oldColumnMap.size(); i++) { newColumns[i] = oldColumnMapKeysIterator.next(); } // Finally we create the PatternNavigate pointing to the new child, with the columns used // for the navigation updated as well as the NRSMD updated newOp = new Navigation(newChild, newColumnsPos, pnop.navigationTreePattern); } else if (op instanceof Projection) { Projection proj = (Projection) op; int[] projMask = updateProjMask(proj.columns, oldColumnMap); // An identical projection should be skipped. boolean identicalProjection = true; if (projMask.length < proj.getChild().getNRSMD().colNo) { identicalProjection = false; } else { for (int i = 0; i < projMask.length; i++) { if (i != projMask[i]) { identicalProjection = false; break; } } } if (!identicalProjection && !(newChild instanceof Projection)) { newOp = new Projection(newChild, projMask); } else { if (identicalProjection) { return newChild; } /* (newChild instanceof Projection) */ // The child is also a projection; it will be recreated and returned. // First, create the new projection mask by combining the current two, int[] childProjMask = new int[projMask.length]; for (int i = 0; i < projMask.length; i++) { childProjMask[i] = ((Projection) newChild).columns[projMask[i]]; } // then create the Projection operator, BaseLogicalOperator grandChild = newChild.getChildren().get(0); newChild = new Projection(grandChild, childProjMask); return newChild; } } } else if (op instanceof BaseBinaryOperator) { assert newChildren.size() == 2; BaseLogicalOperator left = newChildren.get(0); BaseLogicalOperator right = newChildren.get(1); // Create the new operator if (op instanceof Join) { Join join = (Join) op; newOp = new Join(left, right, updatePredicate(join.getPred(), oldColumnMap)); } else if (op instanceof LeftOuterJoin) { LeftOuterJoin join = (LeftOuterJoin) op; newOp = new LeftOuterJoin(left, right, updatePredicate(join.getPred(), oldColumnMap), oldColumnMap.get(join.getDocumentIDColumn()), updateProjMask(join.getNodeIDColumns(), oldColumnMap)); } else if (op instanceof LeftOuterNestedJoin) { LeftOuterNestedJoin join = (LeftOuterNestedJoin) op; newOp = new LeftOuterNestedJoin(left, right, updatePredicate(join.getPred(), oldColumnMap), oldColumnMap.get(join.getDocumentIDColumn()), updateProjMask(join.getNodeIDColumns(), oldColumnMap)); } else throw new PAXQueryExecutionException( "The type of binary operator is not supported by PushDown utilities."); } return newOp; } /** * This method updates a predicate to take into account any column index modification. * * @param pred * the existing predicate * @param oldColumnMap * the map of old column indices to updated ones * @return a new predicate with updated column indices */ public static BasePredicate updatePredicate(BasePredicate pred, Map<Integer, Integer> oldColumnMap) { if (pred instanceof DisjunctivePredicate) return updateDisjunctivePredicate((DisjunctivePredicate) pred, oldColumnMap); else if (pred instanceof ConjunctivePredicate) return updateConjunctivePredicate((ConjunctivePredicate) pred, oldColumnMap); else if (pred instanceof SimplePredicate) return updateSimplePredicate((SimplePredicate) pred, oldColumnMap); throw new PAXQueryExecutionException("Predicate type not supported!"); } private static DisjunctivePredicate updateDisjunctivePredicate(DisjunctivePredicate pred, Map<Integer, Integer> oldColumnMap) { ArrayList<ConjunctivePredicate> preds = new ArrayList<ConjunctivePredicate>(); for (ConjunctivePredicate conjPred : pred.getConjunctivePreds()) preds.add(updateConjunctivePredicate(conjPred, oldColumnMap)); return new DisjunctivePredicate(preds); } private static ConjunctivePredicate updateConjunctivePredicate(ConjunctivePredicate pred, Map<Integer, Integer> oldColumnMap) { ArrayList<SimplePredicate> preds = new ArrayList<SimplePredicate>(); for (SimplePredicate simplePred : pred.getSimplePreds()) preds.add(updateSimplePredicate(simplePred, oldColumnMap)); return new ConjunctivePredicate(preds); } /* * This method updates a simple predicate to take into account any column index modification. It * only works on {@link SimplePredicate} instances, and it is used by the {@link * #updatePredicate(Predicate, Map)} function. * * @param pred the existing simple predicate * * @param oldColumnMap the map of old column indices to updated ones * * @return a new predicate with updated column indices */ private static SimplePredicate updateSimplePredicate(SimplePredicate pred, Map<Integer, Integer> oldColumnMap) { if (pred.getStringConstant() != null) return new SimplePredicate( oldColumnMap.containsKey(pred.getColumn1()) ? oldColumnMap.get(pred.getColumn1()) : pred.getColumn1(), pred.getStringConstant(), pred.getPredCode()); else if (pred.getDoubleConstant() != -1) return new SimplePredicate( oldColumnMap.containsKey(pred.getColumn1()) ? oldColumnMap.get(pred.getColumn1()) : pred.getColumn1(), pred.getDoubleConstant(), pred.getPredCode()); else if (pred.getOperation1() != null || pred.getOperation2() != null) return new SimplePredicate( oldColumnMap.containsKey(pred.getColumn1()) ? oldColumnMap.get(pred.getColumn1()) : pred.getColumn1(), pred.getOperation1(), oldColumnMap.containsKey(pred.getColumn2()) ? oldColumnMap.get(pred.getColumn2()) : pred.getColumn2(), pred.getOperation2(), pred.getPredCode()); return new SimplePredicate( oldColumnMap.containsKey(pred.getColumn1()) ? oldColumnMap.get(pred.getColumn1()) : pred.getColumn1(), oldColumnMap.containsKey(pred.getColumn2()) ? oldColumnMap.get(pred.getColumn2()) : pred.getColumn2(), pred.getPredCode()); } /* * This method updates the columns of a {@link Projection} to take into account any column index * modification. * * @param oldColumns the array of column indices in the existing projection * * @param oldColumnMap the map of old column indices to updated ones * * @return an array of column indices representing the updated project mask */ private static int[] updateProjMask(int[] oldColumns, Map<Integer, Integer> oldColumnMap) { ArrayList<Integer> newColumnList = new ArrayList<Integer>(); for (int i = 0; i < oldColumns.length; i++) { if (oldColumnMap.containsKey(oldColumns[i])) { newColumnList.add(oldColumnMap.get(oldColumns[i])); } } int[] newColumns = new int[newColumnList.size()]; int i = 0; for (int j : newColumnList) { newColumns[i++] = j; } return newColumns; } /** * <p> * This method returns the columns that are referred to by a given predicate. * </p> * * <p> * Supported predicates: {@link SimplePredicate}, {@link ConjunctivePredicate} * </p> */ public static Set<Integer> getPredicateColumns(BasePredicate pred) { HashSet<Integer> predicateColumns = new HashSet<Integer>(); int[][] leftColumns = pred.getLeftColumns(); for (int i = 0; i < leftColumns.length; i++) { for (int j = 0; j < leftColumns[i].length; j++) { predicateColumns.add(leftColumns[i][j]); } } int[][] rightColumns = pred.getRightColumns(); if (rightColumns != null) { for (int i = 0; i < rightColumns.length; i++) { if (rightColumns[i] != null) { for (int j = 0; j < rightColumns[i].length; j++) { predicateColumns.add(rightColumns[i][j]); } } } } return predicateColumns; } /* * Verifies whether all of a predicate's columns are contained within a given column range. * * @param pred the predicate to check * @param firstCol the beginning of the range (inclusive) * @param lastCol the end of the range (inclusive) * @return true if the predicate is contained within the range, false otherwise */ private static boolean predicateMatchesRange(BasePredicate pred, int firstCol, int lastCol) { for (int i : PushdownUtility.getPredicateColumns(pred)) { if (i < firstCol || i > lastCol) return false; } return true; } protected static class ProjectColumn implements Comparable<ProjectColumn> { protected int pos; protected Set<ProjectColumn> nestedColumns; public ProjectColumn(int pos) { this(pos, new TreeSet<ProjectColumn>()); } public ProjectColumn(int pos, Set<ProjectColumn> nestedColumns) { this.pos = pos; this.nestedColumns = nestedColumns; } @Override public int compareTo(ProjectColumn o) { return Integer.compare(pos, o.pos); } public ProjectColumn copy() { return copy(pos); } public ProjectColumn copy(int pos) { Set<ProjectColumn> nestedColumnsCopy = Sets.newTreeSet(); for (ProjectColumn column : nestedColumns) { nestedColumnsCopy.add(column.copy()); } return new ProjectColumn(pos, nestedColumnsCopy); } @Override public String toString() { String result = String.valueOf(pos); if (!this.nestedColumns.isEmpty()) { result += this.nestedColumns.toString(); } return result; } } protected static class ColumnsMapping { protected Map<Integer, Integer> mappingColumns; protected Map<Integer, ColumnsMapping> nestedMappingColumns; public ColumnsMapping() { mappingColumns = new HashMap<Integer, Integer>(); nestedMappingColumns = new HashMap<Integer, ColumnsMapping>(); } } protected static class Pair<E, T> { protected E left; protected T right; public Pair(E left, T right) { this.left = left; this.right = right; } } }