/** * diqube: Distributed Query Base. * * Copyright (C) 2015 Bastian Gloeckle * * This file is part of diqube. * * diqube is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * 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/>. */ package org.diqube.optimize; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.diqube.diql.request.ComparisonRequest; import org.diqube.diql.request.ComparisonRequest.And; import org.diqube.diql.request.ComparisonRequest.DelegateComparisonRequest; import org.diqube.diql.request.ComparisonRequest.Leaf; import org.diqube.diql.request.ComparisonRequest.Not; import org.diqube.diql.request.ComparisonRequest.Operator; import org.diqube.diql.request.ComparisonRequest.Or; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Optimizes the 'not's in a WHERE clause in that way, that it pushes the NOTs as far down the comparison tree as * possible. * * <p> * RowIdNotSteps block until their RowIdConsumer input is fully done - this means that they hinder simultaneous * execution. Using this approach, we might end up having more Not steps than before, but we should be able to * parallelize better. In addition to that, we end up having Not steps only on RowIdEqual steps, because we can just * switch the operator on RowIdInequal steps to build a NOT. Additionally, each Not step might take quite some amount of * memory, as it materializes all rowIDs except those that were reported by subsequent steps - which might be a few... * * @author Bastian Gloeckle */ public class PushToLeafsWhereNotOptimizer implements WhereNotOptimizer { private static final Logger logger = LoggerFactory.getLogger(PushToLeafsWhereNotOptimizer.class); /** * When arriving at inequality comparisons with a NOT operator in front, we can optimize that by removing the not and * changing the comparison operator. Example: * * <code>NOT a > b</code> == <code>a <= b</code> * * This map holds the mapping of the operators when removing the NOT. */ private static final Map<Operator, Operator> notOperator; @Override public ComparisonRequest optimize(ComparisonRequest request, Map<UUID, OptimizerComparisonInfo> info) { logger.debug("Optimizing Comparison Request: {}", request); ComparisonRequest res = optimizeRecursive(request, info); logger.debug("Optimized Comparison Request: {}", res); return res; } /** * @param info * Gets adjusted while optimizing. (Note: be sure that each recursive call uses the same object!). */ private ComparisonRequest optimizeRecursive(ComparisonRequest request, Map<UUID, OptimizerComparisonInfo> info) { if (info.get(request.getVirtualId()).isTransitivelyContainsOnlyEquals()) // do not "optimize" sub-trees which only contain "=" comparisons, as we cannot optimize those anyway. // TODO #2 STAT base this optimization on statistics about the amount of data to be expected return request; if (request instanceof Not) { Not not = (Not) request; if (not.getChild() instanceof Not) // double not -> remove. return optimizeRecursive(((Not) not.getChild()).getChild(), info); if (not.getChild() instanceof Leaf) { Leaf leaf = (Leaf) not.getChild(); if (leaf.getOp().equals(Operator.EQ)) // we cannot optimize "NOT a = b". return not; // It's a <, <=, >, >= comparison. Optimize by removing the NOT and switching the operator of the comparison. // TODO #29 this might actually lead to having more rowIds and more work to be done by the inequality steps. leaf.setOp(notOperator.get(leaf.getOp())); return leaf; } if (not.getChild() instanceof DelegateComparisonRequest) { // move NOT further down the tree. DelegateComparisonRequest del = (DelegateComparisonRequest) not.getChild(); Not notLeft = new Not(); notLeft.setChild(del.getLeft()); Not notRight = new Not(); notRight.setChild(del.getRight()); if (del instanceof And) del = new Or(); else del = new And(); // TODO do no full enhancement here. info.putAll(new OptimizerComparisonInfoBuilder().withComparisonRequest(notLeft).build()); info.putAll(new OptimizerComparisonInfoBuilder().withComparisonRequest(notRight).build()); del.setLeft(optimizeRecursive(notLeft, info)); del.setRight(optimizeRecursive(notRight, info)); info.putAll(new OptimizerComparisonInfoBuilder().withComparisonRequest(del).build()); return del; } } else if (request instanceof DelegateComparisonRequest) { DelegateComparisonRequest del = (DelegateComparisonRequest) request; del.setLeft(optimizeRecursive(del.getLeft(), info)); del.setRight(optimizeRecursive(del.getRight(), info)); info.putAll(new OptimizerComparisonInfoBuilder().withComparisonRequest(del).build()); return del; } return request; } static { notOperator = new HashMap<>(); notOperator.put(Operator.GT, Operator.LT_EQ); notOperator.put(Operator.GT_EQ, Operator.LT); notOperator.put(Operator.LT, Operator.GT_EQ); notOperator.put(Operator.LT_EQ, Operator.GT); } }