| /* |
| * 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. |
| */ |
| package org.apache.phoenix.compile; |
| |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.hadoop.hbase.util.Pair; |
| import org.apache.phoenix.compile.GroupByCompiler.GroupBy; |
| import org.apache.phoenix.compile.OrderByCompiler.OrderBy; |
| import org.apache.phoenix.execute.SortMergeJoinPlan; |
| import org.apache.phoenix.expression.CoerceExpression; |
| import org.apache.phoenix.expression.Expression; |
| import org.apache.phoenix.expression.KeyValueColumnExpression; |
| import org.apache.phoenix.expression.LiteralExpression; |
| import org.apache.phoenix.expression.OrderByExpression; |
| import org.apache.phoenix.expression.ProjectedColumnExpression; |
| import org.apache.phoenix.expression.RowKeyColumnExpression; |
| import org.apache.phoenix.expression.RowValueConstructorExpression; |
| import org.apache.phoenix.expression.function.FunctionExpression.OrderPreserving; |
| import org.apache.phoenix.expression.function.ScalarFunction; |
| import org.apache.phoenix.expression.visitor.StatelessTraverseAllExpressionVisitor; |
| import org.apache.phoenix.expression.visitor.StatelessTraverseNoExpressionVisitor; |
| import org.apache.phoenix.jdbc.PhoenixConnection; |
| import org.apache.phoenix.schema.PTable; |
| import org.apache.phoenix.schema.TableRef; |
| import org.apache.phoenix.util.ExpressionUtil; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterators; |
| import com.google.common.collect.Lists; |
| |
| /** |
| * <pre> |
| * Determines if the natural key order of the rows returned by the scan |
| * will match the order of the expressions. For GROUP BY, if order is preserved we can use |
| * an optimization during server-side aggregation to do the aggregation on-the-fly versus |
| * keeping track of each distinct group. We can only do this optimization if all the rows |
| * for each group will be contiguous. For ORDER BY, we can drop the ORDER BY statement if |
| * the order is preserved. |
| * |
| * There are mainly three changes for refactoring this class in PHOENIX-5148: |
| * 1.added a {@link #getInputOrderBys} method to determine the input OrderBys by the combination |
| * of innerQueryPlan's output OrderBys , GroupBy of current QueryPlan and the rowKeyColumns of current table. |
| * |
| * 2.because the innerQueryPlan may have multiple output OrderBys(for {@link SortMergeJoinPlan}), |
| * so I extracted many stateful member variables (such as orderPreservingTrackInfos, isOrderPreserving |
| * and isReverse etc.) to a new inner class {@link TrackOrderByContext}, {@link TrackOrderByContext} |
| * is used to track if a single Input OrderBy matches the target OrderByExpressions in {@link #isOrderPreserving()} |
| * method, and once we found a {@link TrackOrderByContext} satisfied {@link #isOrderPreserving()}, |
| * {@link #selectedTrackOrderByContext} member variable is set to this {@link TrackOrderByContext}, |
| * then we can use this {@link #selectedTrackOrderByContext} to implement the {@link #getOrderPreservingTrackInfos()} |
| * and {@link #isReverse()} methods etc. |
| * BTW. at most only one {@link TrackOrderByContext} can meet {@link #isOrderPreserving()} is true. |
| * |
| * 3.added ascending and nullsLast to the inner class {@link Info} , and extracted complete ordering |
| * information in {@link Info} class by the inner {@link TrackOrderPreservingExpressionVisitor} class, |
| * so we can inferring alignment between the target OrderByExpressions and the input OrderBys based on |
| * {@link Info} in {@link TrackOrderByContext#doTrack} method, not on the row keys like the original |
| * {@link #track} method does. |
| * </pre> |
| */ |
| public class OrderPreservingTracker { |
| public enum Ordering {ORDERED, UNORDERED}; |
| |
| public static class Info { |
| private final OrderPreserving orderPreserving; |
| private final int pkPosition; |
| private final int slotSpan; |
| private final boolean ascending; |
| private final boolean nullsLast; |
| private Expression expression; |
| |
| public Info(int pkPosition, boolean ascending, boolean nullsLast) { |
| this.pkPosition = pkPosition; |
| this.orderPreserving = OrderPreserving.YES; |
| this.slotSpan = 1; |
| this.ascending = ascending; |
| this.nullsLast = nullsLast; |
| } |
| |
| public Info(int rowKeyColumnPosition, int rowKeySlotSpan, OrderPreserving orderPreserving, boolean ascending, boolean nullsLast) { |
| this.pkPosition = rowKeyColumnPosition; |
| this.slotSpan = rowKeySlotSpan; |
| this.orderPreserving = orderPreserving; |
| this.ascending = ascending; |
| this.nullsLast = nullsLast; |
| } |
| |
| public static List<Expression> extractExpressions(List<Info> orderPreservingTrackInfos) { |
| List<Expression> newExpressions = new ArrayList<Expression>(orderPreservingTrackInfos.size()); |
| for(Info trackInfo : orderPreservingTrackInfos) { |
| newExpressions.add(trackInfo.expression); |
| } |
| return newExpressions; |
| } |
| |
| public Expression getExpression() { |
| return expression; |
| } |
| |
| public boolean isAscending() { |
| return ascending; |
| } |
| |
| public boolean isNullsLast() { |
| return nullsLast; |
| } |
| } |
| private final StatementContext context; |
| private final GroupBy groupBy; |
| private final Ordering ordering; |
| private int pkPositionOffset = 0; |
| private Expression whereExpression; |
| private List<TrackOrderByCell> trackOrderByCells = new LinkedList<TrackOrderByCell>(); |
| private List<TrackOrderByContext> trackOrderByContexts = Collections.<TrackOrderByContext> emptyList(); |
| private TrackOrderByContext selectedTrackOrderByContext = null; |
| private List<OrderBy> inputOrderBys = Collections.<OrderBy> emptyList(); |
| |
| public OrderPreservingTracker(StatementContext context, GroupBy groupBy, Ordering ordering, int nNodes) throws SQLException { |
| this(context, groupBy, ordering, nNodes, null, null, null); |
| } |
| |
| public OrderPreservingTracker( |
| StatementContext context, |
| GroupBy groupBy, |
| Ordering ordering, |
| int nNodes, |
| List<OrderBy> inputOrderBys, |
| QueryPlan innerQueryPlan, |
| Expression whereExpression) throws SQLException { |
| |
| this.context = context; |
| boolean isOrderPreserving = false; |
| if (groupBy.isEmpty() && inputOrderBys == null) { |
| PTable table = context.getResolver().getTables().get(0).getTable(); |
| isOrderPreserving = table.rowKeyOrderOptimizable(); |
| } else { |
| isOrderPreserving = true; |
| } |
| this.groupBy = groupBy; |
| this.ordering = ordering; |
| this.whereExpression = whereExpression; |
| if(inputOrderBys != null) { |
| this.inputOrderBys = inputOrderBys; |
| } else { |
| this.getInputOrderBys(innerQueryPlan, groupBy, context); |
| } |
| if(this.inputOrderBys.isEmpty()) { |
| return; |
| } |
| this.trackOrderByContexts = new ArrayList<TrackOrderByContext>(this.inputOrderBys.size()); |
| for(OrderBy inputOrderBy : this.inputOrderBys) { |
| this.trackOrderByContexts.add( |
| new TrackOrderByContext(isOrderPreserving, nNodes, inputOrderBy)); |
| } |
| } |
| |
| /** |
| * Infer input OrderBys, if the innerQueryPlan is null, we make the OrderBys from the pk columns of {@link PTable}. |
| * @param innerQueryPlan |
| * @param groupBy |
| * @param statementContext |
| * @throws SQLException |
| */ |
| private void getInputOrderBys(QueryPlan innerQueryPlan, GroupBy groupBy, StatementContext statementContext) throws SQLException { |
| if(!groupBy.isEmpty()) { |
| this.inputOrderBys = Collections.singletonList(ExpressionUtil.convertGroupByToOrderBy(groupBy, false)); |
| return; |
| } |
| if(innerQueryPlan != null) { |
| this.inputOrderBys = innerQueryPlan.getOutputOrderBys(); |
| return; |
| } |
| this.inputOrderBys = Collections.<OrderBy> emptyList(); |
| TableRef tableRef = statementContext.getResolver().getTables().get(0); |
| PhoenixConnection phoenixConnection = statementContext.getConnection(); |
| Pair<OrderBy,Integer> orderByAndRowKeyColumnOffset = |
| ExpressionUtil.getOrderByFromTable(tableRef, phoenixConnection, false); |
| OrderBy orderBy = orderByAndRowKeyColumnOffset.getFirst(); |
| this.pkPositionOffset = orderByAndRowKeyColumnOffset.getSecond(); |
| if(orderBy != OrderBy.EMPTY_ORDER_BY) { |
| this.inputOrderBys = Collections.singletonList(orderBy); |
| } |
| } |
| |
| private class TrackOrderByContext { |
| private List<Info> orderPreservingTrackInfos; |
| private boolean isOrderPreserving = true; |
| private Boolean isReverse = null; |
| private int orderPreservingColumnCount = 0; |
| private final TrackOrderPreservingExpressionVisitor trackOrderPreservingExpressionVisitor; |
| private OrderBy inputOrderBy = null; |
| |
| public TrackOrderByContext(boolean isOrderPreserving, int orderByNodeCount, OrderBy inputOrderBy) { |
| this.isOrderPreserving = isOrderPreserving; |
| this.trackOrderPreservingExpressionVisitor = new TrackOrderPreservingExpressionVisitor(inputOrderBy); |
| this.orderPreservingTrackInfos = Lists.newArrayListWithExpectedSize(orderByNodeCount); |
| this.inputOrderBy = inputOrderBy; |
| } |
| |
| public void track(List<TrackOrderByCell> trackOrderByCells) { |
| for(TrackOrderByCell trackOrderByCell : trackOrderByCells) { |
| doTrack(trackOrderByCell.expression, |
| trackOrderByCell.isAscending, |
| trackOrderByCell.isNullsLast); |
| } |
| } |
| |
| private void doTrack(Expression expression, Boolean isAscending, Boolean isNullsLast) { |
| if (!isOrderPreserving) { |
| return; |
| } |
| Info trackInfo = expression.accept(trackOrderPreservingExpressionVisitor); |
| if (trackInfo == null) { |
| isOrderPreserving = false; |
| return; |
| } |
| // If the expression is sorted in a different order than the specified sort order |
| // then the expressions are not order preserving. |
| if (isAscending != null && trackInfo.ascending != isAscending.booleanValue()) { |
| if (isReverse == null) { |
| isReverse = true; |
| } else if (!isReverse){ |
| isOrderPreserving = false; |
| isReverse = false; |
| return; |
| } |
| } else { |
| if (isReverse == null) { |
| isReverse = false; |
| } else if (isReverse){ |
| isOrderPreserving = false; |
| isReverse = false; |
| return; |
| } |
| } |
| |
| if (isNullsLast!=null && expression.isNullable()) { |
| if ((trackInfo.nullsLast == isNullsLast.booleanValue()) && isReverse.booleanValue() || |
| (trackInfo.nullsLast != isNullsLast.booleanValue()) && !isReverse.booleanValue()) { |
| isOrderPreserving = false; |
| isReverse = false; |
| return; |
| } |
| } |
| trackInfo.expression = expression; |
| orderPreservingTrackInfos.add(trackInfo); |
| } |
| |
| /* |
| * Only valid AFTER call to isOrderPreserving |
| */ |
| public int getOrderPreservingColumnCount() { |
| return orderPreservingColumnCount; |
| } |
| |
| /** |
| * Only valid AFTER call to isOrderPreserving |
| */ |
| public List<Info> getOrderPreservingTrackInfos() { |
| if(this.isOrderPreserving) { |
| return ImmutableList.copyOf(this.orderPreservingTrackInfos); |
| } |
| int orderPreservingColumnCountToUse = this.orderPreservingColumnCount - pkPositionOffset; |
| if(orderPreservingColumnCountToUse <= 0) { |
| return Collections.<Info> emptyList(); |
| } |
| return ImmutableList.copyOf(this.orderPreservingTrackInfos.subList(0, orderPreservingColumnCountToUse)); |
| } |
| |
| public boolean isOrderPreserving() { |
| if (!isOrderPreserving) { |
| return false; |
| } |
| if (ordering == Ordering.UNORDERED) { |
| // Sort by position |
| Collections.sort(orderPreservingTrackInfos, new Comparator<Info>() { |
| @Override |
| public int compare(Info o1, Info o2) { |
| int cmp = o1.pkPosition-o2.pkPosition; |
| if (cmp != 0) return cmp; |
| // After pk position, sort on reverse OrderPreserving ordinal: NO, YES_IF_LAST, YES |
| // In this way, if we have an ORDER BY over a YES_IF_LAST followed by a YES, we'll |
| // allow subsequent columns to be ordered. |
| return o2.orderPreserving.ordinal() - o1.orderPreserving.ordinal(); |
| } |
| }); |
| } |
| // Determine if there are any gaps in the PK columns (in which case we don't need |
| // to sort in the coprocessor because the keys will already naturally be in sorted |
| // order. |
| int prevSlotSpan = 1; |
| int prevPos = -1; |
| OrderPreserving prevOrderPreserving = OrderPreserving.YES; |
| for (int i = 0; i < orderPreservingTrackInfos.size(); i++) { |
| Info entry = orderPreservingTrackInfos.get(i); |
| int pos = entry.pkPosition; |
| isOrderPreserving &= entry.orderPreserving != OrderPreserving.NO && |
| prevOrderPreserving == OrderPreserving.YES && |
| (pos == prevPos || |
| pos - prevSlotSpan == prevPos || |
| hasEqualityConstraints(prevPos+prevSlotSpan, pos)); |
| if(!isOrderPreserving) { |
| break; |
| } |
| prevPos = pos; |
| prevSlotSpan = entry.slotSpan; |
| prevOrderPreserving = entry.orderPreserving; |
| } |
| orderPreservingColumnCount = prevPos + prevSlotSpan + pkPositionOffset; |
| return isOrderPreserving; |
| } |
| |
| private boolean hasEqualityConstraints(int startPos, int endPos) { |
| ScanRanges ranges = context.getScanRanges(); |
| // If a GROUP BY is being done, then the rows are ordered according to the GROUP BY key, |
| // not by the original row key order of the table (see PHOENIX-3451). |
| // We check each GROUP BY expression to see if it only references columns that are |
| // matched by equality constraints, in which case the expression itself would be constant. |
| for (int pos = startPos; pos < endPos; pos++) { |
| Expression expressionToCheckConstant = this.getExpressionToCheckConstant(pos); |
| IsConstantVisitor visitor = new IsConstantVisitor(ranges, whereExpression); |
| Boolean isConstant = expressionToCheckConstant.accept(visitor); |
| if (!Boolean.TRUE.equals(isConstant)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public boolean isReverse() { |
| return Boolean.TRUE.equals(isReverse); |
| } |
| |
| private Expression getExpressionToCheckConstant(int columnIndex) { |
| if (!groupBy.isEmpty()) { |
| List<Expression> groupByExpressions = groupBy.getExpressions(); |
| assert columnIndex < groupByExpressions.size(); |
| return groupByExpressions.get(columnIndex); |
| } |
| |
| assert columnIndex < inputOrderBy.getOrderByExpressions().size(); |
| return inputOrderBy.getOrderByExpressions().get(columnIndex).getExpression(); |
| } |
| } |
| |
| private static class TrackOrderByCell |
| { |
| private Expression expression; |
| private Boolean isAscending; |
| private Boolean isNullsLast; |
| |
| public TrackOrderByCell(Expression expression,Boolean isAscending, Boolean isNullsLast) { |
| this.expression = expression; |
| this.isAscending = isAscending; |
| this.isNullsLast = isNullsLast; |
| } |
| } |
| |
| public void track(Expression expression) { |
| track(expression, null, null); |
| } |
| |
| public void track(Expression expression, Boolean isAscending, Boolean isNullsLast) { |
| TrackOrderByCell trackOrderByContext = |
| new TrackOrderByCell(expression, isAscending, isNullsLast); |
| this.trackOrderByCells.add(trackOrderByContext); |
| } |
| |
| /* |
| * Only valid AFTER call to isOrderPreserving |
| */ |
| public int getOrderPreservingColumnCount() { |
| if(this.selectedTrackOrderByContext == null) { |
| return 0; |
| } |
| return this.selectedTrackOrderByContext.getOrderPreservingColumnCount(); |
| } |
| |
| /** |
| * Only valid AFTER call to isOrderPreserving |
| */ |
| public List<Info> getOrderPreservingTrackInfos() { |
| if(this.selectedTrackOrderByContext == null) { |
| return Collections.<Info> emptyList(); |
| } |
| return this.selectedTrackOrderByContext.getOrderPreservingTrackInfos(); |
| } |
| |
| public boolean isOrderPreserving() { |
| if(this.selectedTrackOrderByContext != null) { |
| throw new IllegalStateException("isOrderPreserving should be called only once"); |
| } |
| |
| if(this.trackOrderByContexts.isEmpty()) { |
| return false; |
| } |
| |
| if(this.trackOrderByCells.isEmpty()) { |
| return false; |
| } |
| |
| /** |
| * at most only one TrackOrderByContext can meet isOrderPreserving is true |
| */ |
| for(TrackOrderByContext trackOrderByContext : this.trackOrderByContexts) { |
| trackOrderByContext.track(trackOrderByCells); |
| if(trackOrderByContext.isOrderPreserving()) { |
| this.selectedTrackOrderByContext = trackOrderByContext; |
| break; |
| } |
| |
| if(this.selectedTrackOrderByContext == null) { |
| this.selectedTrackOrderByContext = trackOrderByContext; |
| } |
| } |
| return this.selectedTrackOrderByContext.isOrderPreserving; |
| } |
| |
| public boolean isReverse() { |
| if(this.selectedTrackOrderByContext == null) { |
| throw new IllegalStateException("isReverse should only be called when isOrderPreserving is true!"); |
| } |
| return this.selectedTrackOrderByContext.isReverse(); |
| } |
| |
| /** |
| * |
| * Determines if an expression is held constant. Only works for columns in the PK currently, |
| * as we only track whether PK columns are held constant. |
| * |
| */ |
| private static class IsConstantVisitor extends StatelessTraverseAllExpressionVisitor<Boolean> { |
| private final ScanRanges scanRanges; |
| private final Expression whereExpression; |
| |
| public IsConstantVisitor(ScanRanges scanRanges, Expression whereExpression) { |
| this.scanRanges = scanRanges; |
| this.whereExpression = whereExpression; |
| } |
| |
| @Override |
| public Boolean defaultReturn(Expression node, List<Boolean> returnValues) { |
| if (!ExpressionUtil.isContantForStatement(node) || |
| returnValues.size() < node.getChildren().size()) { |
| return Boolean.FALSE; |
| } |
| for (Boolean returnValue : returnValues) { |
| if (!returnValue) { |
| return Boolean.FALSE; |
| } |
| } |
| return Boolean.TRUE; |
| } |
| |
| @Override |
| public Boolean visit(RowKeyColumnExpression node) { |
| return scanRanges.hasEqualityConstraint(node.getPosition()); |
| } |
| |
| @Override |
| public Boolean visit(LiteralExpression node) { |
| return Boolean.TRUE; |
| } |
| |
| @Override |
| public Boolean visit(KeyValueColumnExpression keyValueColumnExpression) { |
| return ExpressionUtil.isColumnExpressionConstant(keyValueColumnExpression, whereExpression); |
| } |
| @Override |
| public Boolean visit(ProjectedColumnExpression projectedColumnExpression) { |
| return ExpressionUtil.isColumnExpressionConstant(projectedColumnExpression, whereExpression); |
| } |
| } |
| /** |
| * |
| * Visitor used to determine if order is preserved across a list of expressions (GROUP BY or ORDER BY expressions) |
| * |
| */ |
| private static class TrackOrderPreservingExpressionVisitor extends StatelessTraverseNoExpressionVisitor<Info> { |
| private Map<Expression, Pair<Integer,OrderByExpression>> expressionToPositionAndOrderByExpression; |
| |
| public TrackOrderPreservingExpressionVisitor(OrderBy orderBy) { |
| if(orderBy.isEmpty()) { |
| this.expressionToPositionAndOrderByExpression = Collections.<Expression, Pair<Integer,OrderByExpression>> emptyMap(); |
| return; |
| } |
| List<OrderByExpression> orderByExpressions = orderBy.getOrderByExpressions(); |
| this.expressionToPositionAndOrderByExpression = new HashMap<Expression, Pair<Integer,OrderByExpression>>(orderByExpressions.size()); |
| int index = 0; |
| for(OrderByExpression orderByExpression : orderByExpressions) { |
| this.expressionToPositionAndOrderByExpression.put( |
| orderByExpression.getExpression(), |
| new Pair<Integer,OrderByExpression>(index++, orderByExpression)); |
| } |
| } |
| |
| @Override |
| public Info defaultReturn(Expression expression, List<Info> childInfos) { |
| return match(expression); |
| } |
| |
| @Override |
| public Info visit(RowKeyColumnExpression rowKeyColumnExpression) { |
| return match(rowKeyColumnExpression); |
| } |
| |
| @Override |
| public Info visit(KeyValueColumnExpression keyValueColumnExpression) { |
| return match(keyValueColumnExpression); |
| } |
| |
| @Override |
| public Info visit(ProjectedColumnExpression projectedColumnExpression) { |
| return match(projectedColumnExpression); |
| } |
| |
| private Info match(Expression expression) |
| { |
| Pair<Integer,OrderByExpression> positionAndOrderByExpression = this.expressionToPositionAndOrderByExpression.get(expression); |
| if(positionAndOrderByExpression == null) { |
| return null; |
| } |
| return new Info( |
| positionAndOrderByExpression.getFirst(), |
| positionAndOrderByExpression.getSecond().isAscending(), |
| positionAndOrderByExpression.getSecond().isNullsLast()); |
| } |
| |
| @Override |
| public Iterator<Expression> visitEnter(ScalarFunction node) { |
| return node.preservesOrder() == OrderPreserving.NO ? Collections.<Expression> emptyIterator() : Iterators |
| .singletonIterator(node.getChildren().get(node.getKeyFormationTraversalIndex())); |
| } |
| |
| @Override |
| public Info visitLeave(ScalarFunction node, List<Info> l) { |
| if (l.isEmpty()) { return null; } |
| Info info = l.get(0); |
| // Keep the minimum value between this function and the current value, |
| // so that we never increase OrderPreserving from NO or YES_IF_LAST. |
| OrderPreserving orderPreserving = OrderPreserving.values()[Math.min(node.preservesOrder().ordinal(), info.orderPreserving.ordinal())]; |
| Expression childExpression = node.getChildren().get( |
| node.getKeyFormationTraversalIndex()); |
| boolean sortOrderIsSame = node.getSortOrder() == childExpression.getSortOrder(); |
| if (orderPreserving == info.orderPreserving && sortOrderIsSame) { |
| return info; |
| } |
| return new Info( |
| info.pkPosition, |
| info.slotSpan, |
| orderPreserving, |
| sortOrderIsSame ? info.ascending : !info.ascending, |
| info.nullsLast); |
| } |
| |
| @Override |
| public Iterator<Expression> visitEnter(CoerceExpression node) { |
| return node.getChildren().iterator(); |
| } |
| |
| @Override |
| public Info visitLeave(CoerceExpression node, List<Info> l) { |
| if (l.isEmpty()) { return null; } |
| return l.get(0); |
| } |
| |
| @Override |
| public Iterator<Expression> visitEnter(RowValueConstructorExpression node) { |
| return node.getChildren().iterator(); |
| } |
| |
| @Override |
| public Info visitLeave(RowValueConstructorExpression node, List<Info> l) { |
| // Child expression returned null and was filtered, so not order preserving |
| if (l.size() != node.getChildren().size()) { return null; } |
| Info firstInfo = l.get(0); |
| Info lastInfo = firstInfo; |
| // Check that pkPos are consecutive which is the only way a RVC can be order preserving |
| for (int i = 1; i < l.size(); i++) { |
| // not order preserving since it's not last |
| if (lastInfo.orderPreserving == OrderPreserving.YES_IF_LAST) { return null; } |
| Info info = l.get(i); |
| // not order preserving since there's a gap in the pk |
| if (info.pkPosition != lastInfo.pkPosition + 1) { |
| return null; |
| } |
| if(info.ascending != lastInfo.ascending) { |
| return null; |
| } |
| if(info.nullsLast != lastInfo.nullsLast) { |
| return null; |
| } |
| lastInfo = info; |
| } |
| return new Info(firstInfo.pkPosition, l.size(), lastInfo.orderPreserving, lastInfo.ascending, lastInfo.nullsLast); |
| } |
| } |
| } |