/** 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 27, 2011 */ package com.bigdata.rdf.sail.sparql; import java.util.HashMap; import java.util.Map; import junit.framework.TestCase2; import com.bigdata.bop.aggregate.AggregateBase.Annotations; import com.bigdata.bop.aggregate.IAggregate; import com.bigdata.journal.ITx; import com.bigdata.rdf.internal.impl.literal.XSDNumericIV; import com.bigdata.rdf.sparql.ast.AssignmentNode; import com.bigdata.rdf.sparql.ast.ConstantNode; import com.bigdata.rdf.sparql.ast.FunctionNode; import com.bigdata.rdf.sparql.ast.FunctionRegistry; import com.bigdata.rdf.sparql.ast.GlobalAnnotations; import com.bigdata.rdf.sparql.ast.GroupByNode; import com.bigdata.rdf.sparql.ast.HavingNode; import com.bigdata.rdf.sparql.ast.IValueExpressionNode; import com.bigdata.rdf.sparql.ast.ProjectionNode; import com.bigdata.rdf.sparql.ast.VarNode; /** * Test suite for {@link VerifyAggregates}. * * Note: This is a port of {@link com.bigdata.bop.solutions.TestGroupByState} that * does not depend on the blazegraph operator model. It was developed as part of * BLZG-1176 to decouple the SPARQL parser from the database. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ * * @see https://jira.blazegraph.com/browse/BLZG-1176 */ public class TestVerifyAggregates extends TestCase2 { /** * */ public TestVerifyAggregates() { } /** * @param name */ public TestVerifyAggregates(final String name) { super(name); } private GlobalAnnotations globals; @Override protected void setUp() throws Exception { super.setUp(); globals = new GlobalAnnotations(getName(), ITx.READ_COMMITTED); } @Override protected void tearDown() throws Exception { globals = null; super.tearDown(); } /** * Unit test with SELECT clause having one value expression, which is a * simple variable also appearing as the sole value expression in the * GROUP_BY clause. * <pre> * SELECT ?org * GROUP BY ?org * </pre> */ public void test_aggregateExpr_01() { final VarNode org = new VarNode("org"); final ProjectionNode select = new ProjectionNode(); select.addProjectionVar(org); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(org); final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * Unit test with SELECT clause having one value expression, which is a * simple variable also appearing as the sole value expression in the * GROUP_BY clause. However, in this case we rename the variable when it is * projected out of the SELECT expression. * * <pre> * SELECT ?org as ?newVar * GROUP BY ?org * </pre> */ public void test_aggregateExpr_02() { final VarNode org = new VarNode("org"); final VarNode newVar = new VarNode("newVar"); @SuppressWarnings({ "unchecked", "rawtypes" }) final ProjectionNode select = new ProjectionNode(); select.addProjectionExpression(new AssignmentNode(newVar, org)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(org); final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * Unit test with simple aggregate function in SELECT clause. * <pre> * SELECT ?org, SUM(?lprice) AS ?totalPrice * GROUP BY ?org * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_simpleAggregate() { final VarNode org = new VarNode("org"); final VarNode lprice = new VarNode("lprice"); final VarNode totalPrice = new VarNode("totalPrice"); final IValueExpressionNode totalPriceExprNode = new FunctionNode( FunctionRegistry.SUM, null, lprice); final ProjectionNode select = new ProjectionNode(); select.addProjectionVar(org); select.addProjectionExpression(new AssignmentNode(totalPrice, totalPriceExprNode)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(org); final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * Unit test with simple aggregate function in SELECT clause and no GROUP BY * clause (the aggregation is taken across all solutions as if they were a * single group). * * <pre> * SELECT SUM(?lprice) AS ?totalPrice * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_simpleAggregate_noGroupBy() { final VarNode lprice = new VarNode("lprice"); final VarNode totalPrice = new VarNode("totalPrice"); final IValueExpressionNode totalPriceExprNode = new FunctionNode( FunctionRegistry.SUM, null, lprice); final ProjectionNode select = new ProjectionNode(); select.addProjectionExpression(new AssignmentNode(totalPrice, totalPriceExprNode)); final GroupByNode groupBy = null; final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * Unit test for references to aggregate declared in GROUP_BY with AS. * <pre> * SELECT ?org2 * GROUP BY UCASE(?org) as ?org2 * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_aggregateExpr_03() { // Note: UcaseBOp is what we need here. final VarNode org = new VarNode("org"); final VarNode org2 = new VarNode("org2"); final IValueExpressionNode ucaseExpr = new FunctionNode(FunctionRegistry.UCASE, null, org); final ProjectionNode select = new ProjectionNode(); select.addProjectionVar(org2); final GroupByNode groupBy = new GroupByNode(); groupBy.addExpr(new AssignmentNode(org2, ucaseExpr)); final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * Unit test verifies that a constant within a group by clause does not * cause the group by clause to be interpreted as an aggregate. * * <pre> * select ?index * group by (?o + 1 AS ?index) * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_aggregateExpr_04() { final VarNode index = new VarNode("index"); final VarNode o = new VarNode("o"); final IValueExpressionNode mathExpr = FunctionNode.add( o, new ConstantNode(new XSDNumericIV(1))); final ProjectionNode select = new ProjectionNode(); select.addProjectionVar(index); final GroupByNode groupBy = new GroupByNode(); groupBy.addExpr(new AssignmentNode(index, mathExpr)); final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * <pre> * SELECT SUM(?y) as ?x * GROUP BY ?z * HAVING ?x > 10 * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_simpleHavingClause() { final VarNode y = new VarNode("y"); final VarNode x = new VarNode("x"); final VarNode z = new VarNode("z"); final IValueExpressionNode xExpr = new FunctionNode( FunctionRegistry.SUM, null, y); final ProjectionNode select = new ProjectionNode(); select.addExpr(new AssignmentNode(x, xExpr)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(z); final HavingNode having = new HavingNode(); having.addExpr(FunctionNode.GT( x, new ConstantNode(new XSDNumericIV(10)) )); new VerifyAggregates(select, groupBy, having); } /** * <pre> * SELECT SUM(?y) as ?x * GROUP BY ?z * HAVING SUM(?y) > 10 * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_complexHavingClause() { final VarNode y = new VarNode("y"); final VarNode x = new VarNode("x"); final VarNode z = new VarNode("z"); final FunctionNode xExpr = new FunctionNode( FunctionRegistry.SUM, null, y); final ProjectionNode select = new ProjectionNode(); select.addExpr(new AssignmentNode(x, xExpr)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(new VarNode(z)); final HavingNode having = new HavingNode(); having.addExpr(FunctionNode.GT( xExpr, new ConstantNode(new XSDNumericIV(10)) )); new VerifyAggregates(select, groupBy, having); } /** * <pre> * SELECT SUM(?x+MIN(?y)) as ?z * GROUP BY ?a * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_nestedAggregates() { final VarNode a = new VarNode("a"); final VarNode x = new VarNode("x"); final VarNode y = new VarNode("y"); final VarNode z = new VarNode("z"); final IValueExpressionNode zExpr = new FunctionNode(FunctionRegistry.SUM, null, FunctionNode.add(x, new FunctionNode( FunctionRegistry.MIN, null, y))); final ProjectionNode select = new ProjectionNode(); select.addExpr(new AssignmentNode(z, zExpr)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(a); final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * Verify that a reference to a variable defined by a previous select * expression is allowed and that the select dependency is recognized. * * <pre> * SELECT SUM(?y) as ?z, SUM(?x)+?z as ?a * GROUP BY ?b * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_reverseReference_allowed_aka_select_dependency() { final VarNode a = new VarNode("a"); final VarNode b = new VarNode("b"); final VarNode x = new VarNode("x"); final VarNode y = new VarNode("y"); final VarNode z = new VarNode("z"); final IValueExpressionNode zExpr = new FunctionNode(FunctionRegistry.SUM, null,y); final IValueExpressionNode aExpr = FunctionNode.add(new FunctionNode(FunctionRegistry.SUM, null,x), z); final ProjectionNode select = new ProjectionNode(); select.addExpr(new AssignmentNode(z, zExpr)); select.addExpr(new AssignmentNode(a, aExpr)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(b); final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * Forward references to a variable are not allowed. * * <pre> * SELECT SUM(?x)+?z as ?a, SUM(?y) as ?z ... (forward reference to z) * </pre> */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void test_forwardReference_not_allowed() { final VarNode a = new VarNode("a"); final VarNode b = new VarNode("b"); final VarNode x = new VarNode("x"); final VarNode y = new VarNode("y"); final VarNode z = new VarNode("z"); final IValueExpressionNode zExpr = new FunctionNode(FunctionRegistry.SUM, null,y); final IValueExpressionNode aExpr = FunctionNode.add(new FunctionNode(FunctionRegistry.SUM, null,x), z); final ProjectionNode select = new ProjectionNode(); select.addExpr(new AssignmentNode(a, aExpr)); select.addExpr(new AssignmentNode(z, zExpr)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(b); final HavingNode having = null; try { new VerifyAggregates(select, groupBy, having); fail("Expecting: " + IllegalArgumentException.class); } catch (final IllegalArgumentException ex) { if (log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex,ex); } } /** * Unit test for {@link IGroupByState#isAnyDistinct()) where the DISTINCT * keyword appears within an {@link IAggregate} in the SELECT clause. * <pre> * SELECT SUM(DISTINCT ?y) as ?x * GROUP BY ?z * HAVING ?x > 10 * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_isAnyDistinct_select() { final VarNode y = new VarNode("y"); final VarNode x = new VarNode("x"); final VarNode z = new VarNode("z"); final Map<String, Object> scalarValues = new HashMap<>(); scalarValues.put(Annotations.DISTINCT, Boolean.TRUE); final IValueExpressionNode xExpr = new FunctionNode(FunctionRegistry.SUM, scalarValues, y); final ProjectionNode select = new ProjectionNode(); select.addExpr(new AssignmentNode(x, xExpr)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(z); final HavingNode having = new HavingNode(); having.addExpr(FunctionNode.GT( x, new ConstantNode(new XSDNumericIV(10)) )); new VerifyAggregates(select, groupBy, having); } /** * Unit test for {@link IGroupByState#isAnyDistinct()) where the DISTINCT * keyword appears within an {@link IAggregate} in the SELECT clause. * <pre> * SELECT SUM(?y) as ?x * GROUP BY ?z * HAVING SUM(DISTINCT ?y) > 10 * </pre> */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_isAnyDistinct_having() { final VarNode y = new VarNode("y"); final VarNode x = new VarNode("x"); final VarNode z = new VarNode("z"); final IValueExpressionNode xExpr = new FunctionNode(FunctionRegistry.SUM, null, y); final ProjectionNode select = new ProjectionNode(); select.addExpr(new AssignmentNode(x, xExpr)); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(z); final HavingNode having = new HavingNode(); final Map<String, Object> scalarValues = new HashMap<>(); scalarValues.put(Annotations.DISTINCT, Boolean.TRUE); having.addExpr(FunctionNode.GT( new FunctionNode(FunctionRegistry.SUM, scalarValues, y), new ConstantNode(new XSDNumericIV(10)) )); new VerifyAggregates(select, groupBy, having); } /** * Unit test when projecting a constant * <pre> * SELECT 12 as ?x * </pre> */ public void test_with_constant() { @SuppressWarnings({ "unchecked", "rawtypes" }) final VarNode x = new VarNode("x"); @SuppressWarnings({ "rawtypes", "unchecked" }) final ConstantNode xExpr = new ConstantNode( new XSDNumericIV(12)); final ProjectionNode select = new ProjectionNode(); select.addExpr(new AssignmentNode(x, xExpr)); final GroupByNode groupBy = null; final HavingNode having = null; new VerifyAggregates(select, groupBy, having); } /** * Unit test for bad arguments. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void test_correctRejection() { final VarNode y = new VarNode("y"); final VarNode x = new VarNode("x"); final VarNode z = new VarNode("z"); final GroupByNode groupBy = new GroupByNode(); groupBy.addGroupByVar(z); final HavingNode having = new HavingNode(); having.addExpr(FunctionNode.LT( new FunctionNode(FunctionRegistry.SUM, null, y), new ConstantNode(new XSDNumericIV(10)) )); // SELECT may not be null. try { new VerifyAggregates(null/* select */, groupBy, having); fail("Expecting: " + IllegalArgumentException.class); } catch (final IllegalArgumentException ex) { if (log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex, ex); } // SELECT may not be empty. try { new VerifyAggregates(new ProjectionNode()/* select */, groupBy, having); fail("Expecting: " + IllegalArgumentException.class); } catch (final IllegalArgumentException ex) { if (log.isInfoEnabled()) log.info("Ignoring expected exception: " + ex, ex); } } }