/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Jul 29, 2011 */ package com.bigdata.bop.solutions; import java.util.LinkedHashMap; import junit.framework.TestCase2; import org.openrdf.query.algebra.Compare.CompareOp; import com.bigdata.bop.Bind; import com.bigdata.bop.Constant; import com.bigdata.bop.IConstant; import com.bigdata.bop.IConstraint; import com.bigdata.bop.IValueExpression; import com.bigdata.bop.IVariable; import com.bigdata.bop.IVariableFactory; import com.bigdata.bop.Var; import com.bigdata.bop.aggregate.IAggregate; import com.bigdata.bop.rdf.aggregate.MIN; import com.bigdata.bop.rdf.aggregate.SUM; import com.bigdata.journal.ITx; import com.bigdata.rdf.internal.IV; import com.bigdata.rdf.internal.constraints.CompareBOp; import com.bigdata.rdf.internal.constraints.MathBOp; import com.bigdata.rdf.internal.constraints.SPARQLConstraint; import com.bigdata.rdf.internal.impl.literal.XSDNumericIV; import com.bigdata.rdf.sparql.ast.GlobalAnnotations; /** * Test suite for {@link GroupByRewriter}. * <p> * Note: This test suite is actually written at the * {@link GroupByRewriter#rewrite(IValueExpression, IVariableFactory, LinkedHashMap)} * and * {@link GroupByRewriter#rewrite(IConstraint, IVariableFactory, LinkedHashMap)} * level (those are static methods). This is good, but we do not have unit tests * at the {@link GroupByRewriter} level. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class TestGroupByRewriter extends TestCase2 { /** * */ public TestGroupByRewriter() { } /** * @param name */ public TestGroupByRewriter(String name) { super(name); } /** The lexicon namespace - required for {@link CompareBOp}. */ private GlobalAnnotations globals; protected void setUp() throws Exception { super.setUp(); globals = new GlobalAnnotations(getName(), ITx.READ_COMMITTED); } protected void tearDown() throws Exception { globals = null; super.tearDown(); } // private static class MockRewriteState implements IGroupByRewriteState { // // private final LinkedHashMap<IAggregate<?>,IVariable<?>> aggExpr; // private final IConstraint[] having2; // private final IValueExpression<?>[] select2; // // public LinkedHashMap<IAggregate<?>,IVariable<?>> getAggExpr() { // return aggExpr; // } // // public IConstraint[] getHaving2() { // return having2; // } // // public IValueExpression<?>[] getSelect2() { // return select2; // } // // public MockRewriteState( // final LinkedHashMap<IAggregate<?>,IVariable<?>> aggExpr, // final IConstraint[] having2, // final IValueExpression<?>[] select2 // ) { // this.aggExpr = aggExpr; // this.having2 = having2; // this.select2 = select2; // } // // } // // static void assertSameState(final IGroupByRewriteState expected, // final IGroupByRewriteState actual) { // // assertEquals("aggExpr", expected.getAggExpr(), actual.getAggExpr()); // // assertEquals("having2", expected.getHaving2(), actual.getHaving2()); // // assertEquals("select2", expected.getSelect2(), actual.getSelect2()); // // } /** * Provides sequential (and predictable) variable names. */ private static class MockVariableFactory implements IVariableFactory { int i = 0; public IVariable<?> var() { return Var.var("_" + i++); } } private IGroupByRewriteState newFixture(final IGroupByState groupByState) { return new GroupByRewriter(groupByState) { private IVariableFactory vf; /** * Overridden to provide sequential (and predictable) variable * names. */ @Override public IVariable<?> var() { if(vf == null) // odd, but otherwise not initalized?!? vf = new MockVariableFactory(); return vf.var(); } }; } /* * Unit tests at the rewrite() level. */ /** * Test that a bare variable is NOT rewritten. * <pre> * SELECT ?x GROUP BY ?x * </pre> */ public void test_variable_not_rewritten() { final IVariable<?> x = Var.var("x"); final LinkedHashMap<IAggregate<?>, IVariable<?>> actualAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); assertTrue(x == GroupByRewriter.rewrite(x, new MockVariableFactory(), actualAggExpr)); assertTrue(actualAggExpr.isEmpty()); } /** * Test that a bare constant is NOT rewritten. * <pre> * SELECT 12 * </pre> */ public void test_constant_not_rewritten() { final IConstant<?> x = new Constant(12); final LinkedHashMap<IAggregate<?>, IVariable<?>> actualAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); assertTrue(x == GroupByRewriter.rewrite(x, new MockVariableFactory(), actualAggExpr)); assertTrue(actualAggExpr.isEmpty()); } /** * Test lifting of bare {@link IAggregate} expression: * <pre> * SELECT SUM(?x) as ?y * </pre> * is rewritten as * <pre> * aggExp := SUM(?x) as ?_0 * select := ?_0 as ?y * </pre> */ public void test_rewrite_lift_bare_aggregate_expression() { /* * Setup the aggregation operation. */ final IVariable<?> x = Var.var("x"); final IVariable<?> y = Var.var("y"); final IValueExpression<?> sumX = new /* Conditional */Bind(y, new SUM( false/* distinct */, (IValueExpression<IV>) x)); /* * Set up the expected answer. */ final IAggregate<?> _sumX = new SUM(false/* distinct */, (IValueExpression<IV>) x); final IVariable<?> _0 = Var.var("_0"); final IValueExpression<?> expectedExpr = new Bind(y, _0); final LinkedHashMap<IAggregate<?>, IVariable<?>> expectedAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); expectedAggExpr.put(_sumX, _0); /* * Verify the actual outcome against the expected answer. */ final LinkedHashMap<IAggregate<?>, IVariable<?>> actualAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); final IValueExpression<?> actualExpr = GroupByRewriter.rewrite(sumX, new MockVariableFactory(), actualAggExpr); assertEquals(expectedExpr, actualExpr); assertEquals(expectedAggExpr, actualAggExpr); } /** * Test that {@link IAggregate} expressions are also lifted out of a HAVING * clause. * <pre> * SELECT ?org * GROUP BY ?org * HAVING sum(?x)>10 * </pre> */ public void test_with_having() { /* * Setup the aggregation operation. */ final IVariable<IV> x = Var.var("x"); final IConstraint constraint = new SPARQLConstraint( new CompareBOp(new SUM(false/* distinct */, (IValueExpression<IV>) x), new Constant<IV>( new XSDNumericIV(10)), CompareOp.GT)); /* * Set up the expected answer. */ final IAggregate<?> _sumX = new SUM(false/* distinct */, (IValueExpression<IV>) x); final IVariable<IV> _0 = Var.var("_0"); final IConstraint expectedExpr = new SPARQLConstraint(new CompareBOp( _0, new Constant<IV>(new XSDNumericIV(10)), CompareOp.GT)); final LinkedHashMap<IAggregate<?>, IVariable<?>> expectedAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); expectedAggExpr.put(_sumX, _0); /* * Verify the actual outcome against the expected answer. */ final LinkedHashMap<IAggregate<?>, IVariable<?>> actualAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); final IConstraint actualExpr = GroupByRewriter.rewrite(constraint, new MockVariableFactory(), actualAggExpr); assertEquals(expectedExpr, actualExpr); assertEquals(expectedAggExpr, actualAggExpr); } /** * Test elimination of duplicate {@link IAggregate} expressions.</br> * <pre> * SELECT SUM(?x) as ?y, SUM(x) as ?z * </pre> */ public void test_eliminate_duplicate_aggregate_expressions() { /* * Setup the aggregation operation. */ final IVariable<?> x = Var.var("x"); final IVariable<?> y = Var.var("y"); final IVariable<?> z = Var.var("z"); final IValueExpression<?> sumX1 = new /* Conditional */Bind(y, new SUM( false/* distinct */, (IValueExpression<IV>) x)); final IValueExpression<?> sumX2 = new /* Conditional */Bind(z, new SUM( false/* distinct */, (IValueExpression<IV>) x)); /* * Set up the expected answer. */ final IAggregate<?> _sumX = new SUM(false/* distinct */, (IValueExpression<IV>) x); final IVariable<?> _0 = Var.var("_0"); final IValueExpression<?> expectedExpr1 = new Bind(y, _0); final IValueExpression<?> expectedExpr2 = new Bind(z, _0); final LinkedHashMap<IAggregate<?>, IVariable<?>> expectedAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); expectedAggExpr.put(_sumX, _0); /* * Verify the actual outcome against the expected answer. */ final LinkedHashMap<IAggregate<?>, IVariable<?>> actualAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); final IValueExpression<?> actualExpr1 = GroupByRewriter.rewrite(sumX1, new MockVariableFactory(), actualAggExpr); final IValueExpression<?> actualExpr2 = GroupByRewriter.rewrite(sumX2, new MockVariableFactory(), actualAggExpr); assertEquals(expectedExpr1, actualExpr1); assertEquals(expectedExpr2, actualExpr2); assertEquals(expectedAggExpr, actualAggExpr); } /** * Test lifting of {@link IAggregate} expressions within value expressions: * * <pre> * SELECT 1+SUM(?x) as ?y * </pre> */ public void test_lift_aggregate_expression_nested_in_plain_valueExpr() { /* * Setup the aggregation operation. */ final IVariable<IV> x = Var.var("x"); final IVariable<IV> y = Var.var("y"); final IValueExpression<?> expr = new /* Conditional */Bind(y, new MathBOp(new Constant<IV>(new XSDNumericIV(1)), new SUM( false/* distinct */, (IValueExpression<IV>) x), MathBOp.MathOp.PLUS, globals)); /* * Set up the expected answer. */ final IAggregate<IV> _sumX = new SUM(false/* distinct */, (IValueExpression<IV>) x); final IVariable<IV> _0 = Var.var("_0"); final IValueExpression<IV> expectedExpr = new Bind(y, new MathBOp( new Constant<IV>(new XSDNumericIV(1)), _0, MathBOp.MathOp.PLUS, globals)); final LinkedHashMap<IAggregate<?>, IVariable<?>> expectedAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); expectedAggExpr.put(_sumX, _0); /* * Verify the actual outcome against the expected answer. */ final LinkedHashMap<IAggregate<?>, IVariable<?>> actualAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); final IValueExpression<?> actualExpr = GroupByRewriter.rewrite(expr, new MockVariableFactory(), actualAggExpr); assertEquals(expectedExpr, actualExpr); assertEquals(expectedAggExpr, actualAggExpr); } /** * Test lifting of nested {@link IAggregate} expressions: * * <pre> * SELECT SUM(x+MIN(x)) * </pre> */ public void test_lift_aggregate_expression_nested_in_aggregateExpr() { /* * Setup the aggregation operation. */ final IVariable<IV> x = Var.var("x"); final IVariable<IV> y = Var.var("y"); final IValueExpression<IV> sumX = new /* Conditional */Bind(y, new SUM( false/* distinct */, new MathBOp(x, new MIN( false/* distinct */, x), MathBOp.MathOp.PLUS, globals))); /* * Set up the expected answer. */ final IVariable<IV> _0 = Var.var("_0"); final IVariable<IV> _1 = Var.var("_1"); final IAggregate<IV> _minX = new MIN(false/* distinct */, x); final IAggregate<IV> _sumX = new SUM(false/* distinct */, new MathBOp( x, _0, MathBOp.MathOp.PLUS,globals)); final IValueExpression<IV> expectedExpr = new Bind(y, _1); final LinkedHashMap<IAggregate<?>, IVariable<?>> expectedAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); expectedAggExpr.put(_minX, _0); expectedAggExpr.put(_sumX, _1); /* * Verify the actual outcome against the expected answer. */ final LinkedHashMap<IAggregate<?>, IVariable<?>> actualAggExpr = new LinkedHashMap<IAggregate<?>, IVariable<?>>(); final IValueExpression<?> actualExpr = GroupByRewriter.rewrite(sumX, new MockVariableFactory(), actualAggExpr); assertEquals(expectedAggExpr, actualAggExpr); assertEquals(expectedExpr, actualExpr); } }