/** * 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.server.execution.dbl; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import org.diqube.data.column.ColumnType; import org.diqube.execution.ExecutablePlan; import org.diqube.function.aggregate.util.BigDecimalHelper; import org.diqube.server.execution.GroupDiqlExecutionTest; import org.diqube.util.DoubleUtil; import org.diqube.util.Pair; import org.diqube.util.Triple; import org.testng.Assert; import org.testng.annotations.Test; /** * * @author Bastian Gloeckle */ @Test public class DoubleGroupDiqlExecutionTest extends GroupDiqlExecutionTest<Double> { public DoubleGroupDiqlExecutionTest() { super(ColumnType.DOUBLE, new DoubleTestDataProvider()); } @Test public void simpleGroupAggregationAvgTest() throws InterruptedException, ExecutionException { Object[] colAValues = dp.a(1, 5, 100, 1, 99, 1, 100); Object[] colBValues = new Double[] { 0., 4.5, 5.1, 2., 100.01, 2., 5.3 }; initializeSimpleTable(colAValues, colBValues); // GIVEN // a simple select stmt ExecutablePlan executablePlan = buildExecutablePlan("Select " + COL_A + ", avg(" + COL_B + ") from " + TABLE + " group by " + COL_A); ExecutorService executor = executors.newTestExecutor(executablePlan.preferredExecutorServiceSize()); try { // WHEN // executing it on the sample table Future<Void> future = executablePlan.executeAsynchronously(executor); future.get(); // wait until done. // THEN Assert.assertTrue(columnValueConsumerIsDone, "Source should have reported 'done'"); Assert.assertTrue(future.isDone(), "Future should report done"); Assert.assertFalse(future.isCancelled(), "Future should not report cancelled"); Assert.assertTrue(resultValues.containsKey(COL_A), "Result values should be available for result column"); String resColName = functionBasedColumnNameBuilderFactory.create().withFunctionName("avg").addParameterColumnName(COL_B).build(); Assert.assertTrue(resultValues.containsKey(resColName), "Result values should be available for aggregated res column"); Assert.assertEquals(resultValues.size(), 2, "Result values should be available for one column only"); Map<Double, Double> expected = new HashMap<>(); expected.put(dp.v(1), new BigDecimal(0.).add(new BigDecimal(2.)).add(new BigDecimal(2.)) .divide(new BigDecimal(3.), BigDecimalHelper.defaultMathContext()).doubleValue()); expected.put(dp.v(5), 4.5); expected.put(dp.v(100), new BigDecimal(5.1).add(new BigDecimal(5.3)) .divide(new BigDecimal(2l), BigDecimalHelper.defaultMathContext()).doubleValue()); expected.put(dp.v(99), 100.01); Map<Double, Double> actual = new HashMap<>(); for (Entry<Long, Double> colAEntry : resultValues.get(COL_A).entrySet()) actual.put(colAEntry.getValue(), resultValues.get(resColName).get(colAEntry.getKey())); for (Entry<Double, Double> expectedEntry : expected.entrySet()) { if (!DoubleUtil.equals(expectedEntry.getValue(), actual.get(expectedEntry.getKey()))) Assert.fail("Incorrect aggregation result for key " + expectedEntry.getKey() + ": Expected " + expectedEntry.getValue() + " but was " + actual.get(expectedEntry.getKey())); } Assert.assertEquals(actual.size(), expected.size(), "Not correct number of results available"); } finally { executor.shutdownNow(); } } @Test public void simpleGroupOrderingTest() throws InterruptedException, ExecutionException { Object[] colAValues = dp.a(1, 5, 100, 1, 99, 1, 100); Object[] colBValues = new Double[] { 0., 4.5, 5.1, 2., 100.01, 2., 5.3 }; initializeSimpleTable(colAValues, colBValues); // GIVEN ExecutablePlan executablePlan = buildExecutablePlan("Select " + COL_A + ", avg(" + COL_B + ") from " + TABLE + // " group by " + COL_A + // " order by avg(" + COL_B + ") desc" // -> order by avg ); ExecutorService executor = executors.newTestExecutor(executablePlan.preferredExecutorServiceSize()); try { // WHEN // executing it on the sample table Future<Void> future = executablePlan.executeAsynchronously(executor); future.get(); // wait until done. // THEN Assert.assertTrue(columnValueConsumerIsDone, "Source should have reported 'done'"); Assert.assertTrue(future.isDone(), "Future should report done"); Assert.assertFalse(future.isCancelled(), "Future should not report cancelled"); Assert.assertTrue(resultValues.containsKey(COL_A), "Result values should be available for result column A"); String resColName = functionBasedColumnNameBuilderFactory.create().withFunctionName("avg").addParameterColumnName(COL_B).build(); Assert.assertTrue(resultValues.containsKey(resColName), "Result values should be available for result aggregated col"); Assert.assertEquals(resultValues.size(), 2, "Result values should be available for two columns only"); List<Pair<Double, Double>> expectedResult = new ArrayList<>(); // ColA: v(99), avg: 100.01 expectedResult.add(new Pair<>(dp.v(99), 100.01)); // ColA: v(100), avg: 5.2 expectedResult.add(new Pair<>(dp.v(100), new BigDecimal(5.1).add(new BigDecimal(5.3)) .divide(new BigDecimal(2), BigDecimalHelper.defaultMathContext()).doubleValue())); // ColA: v(5), avg: 4.5 expectedResult.add(new Pair<>(dp.v(5), 4.5)); // ColA: v(1), avg: 4/3 expectedResult.add(new Pair<>(dp.v(1), new BigDecimal(0.).add(new BigDecimal(2.)).add(new BigDecimal(2.)) .divide(new BigDecimal(3), BigDecimalHelper.defaultMathContext()).doubleValue())); for (int orderedRowId = 0; orderedRowId < expectedResult.size(); orderedRowId++) { long rowId = resultOrderRowIds.get(orderedRowId); Double colAValue = resultValues.get(COL_A).get(rowId); Double countValue = resultValues.get(resColName).get(rowId); Pair<Double, Double> actualValue = new Pair<>(colAValue, countValue); if (!DoubleUtil.equals(expectedResult.get(orderedRowId).getLeft(), actualValue.getLeft()) || !DoubleUtil.equals(expectedResult.get(orderedRowId).getRight(), actualValue.getRight())) Assert.fail("Expected correct result at ordered index " + orderedRowId + " (" + rowId + "): " + expectedResult.get(orderedRowId).getLeft() + "-" + expectedResult.get(orderedRowId).getRight() + " but was " + actualValue.getLeft() + "-" + actualValue.getRight()); } Assert.assertEquals(resultOrderRowIds.size(), expectedResult.size(), "Expected to receive correct number of rows"); } finally { executor.shutdownNow(); } } @Test public void aggregationFunctionWithConstantParam() throws InterruptedException, ExecutionException { Object[] colAValues = dp.a(1, 5, 100, 1, 99, 1); Object[] colBValues = dp.a(3, 0, 0, 2, 0, 10); initializeSimpleTable(colAValues, colBValues); // GIVEN ExecutablePlan executablePlan = buildExecutablePlan("Select " + COL_A + " from " + TABLE + " group by " + COL_A + " having any(" + dp.vDiql(10) + ", " + COL_B + ") = 1"); ExecutorService executor = executors.newTestExecutor(executablePlan.preferredExecutorServiceSize()); try { // WHEN // executing it on the sample table Future<Void> future = executablePlan.executeAsynchronously(executor); future.get(); // wait until done. // THEN Assert.assertTrue(columnValueConsumerIsDone, "Source should have reported 'done'"); Assert.assertTrue(future.isDone(), "Future should report done"); Assert.assertFalse(future.isCancelled(), "Future should not report cancelled"); Assert.assertEquals(resultHavingRowIds.length, 1, "Expected results for columns."); Assert.assertNotNull(resultValues.get(COL_A), "Expected results for col A."); Assert.assertEquals((double) resultValues.get(COL_A).get(resultHavingRowIds[0]), dp.v(1), "Expected correct value."); } finally { executor.shutdownNow(); } } @Test public void minMax() throws InterruptedException, ExecutionException { Object[] colAValues = dp.a(1, 5, 100, 1, 99, 1); Object[] colBValues = dp.a(3, 1, 50, 2, 0, 10); initializeSimpleTable(colAValues, colBValues); // GIVEN ExecutablePlan executablePlan = buildExecutablePlan( "Select " + COL_A + ", max(" + COL_B + "), min(" + COL_B + ") from " + TABLE + " group by " + COL_A); ExecutorService executor = executors.newTestExecutor(executablePlan.preferredExecutorServiceSize()); try { // WHEN // executing it on the sample table Future<Void> future = executablePlan.executeAsynchronously(executor); future.get(); // wait until done. // THEN Assert.assertTrue(columnValueConsumerIsDone, "Source should have reported 'done'"); Assert.assertTrue(future.isDone(), "Future should report done"); Assert.assertFalse(future.isCancelled(), "Future should not report cancelled"); String resMaxColName = functionBasedColumnNameBuilderFactory.create().withFunctionName("max").addParameterColumnName(COL_B).build(); String resMinColName = functionBasedColumnNameBuilderFactory.create().withFunctionName("min").addParameterColumnName(COL_B).build(); Assert.assertNotNull(resultValues.get(COL_A), "Col A expected to be abailable"); Assert.assertNotNull(resultValues.get(resMinColName), "Min col expected to be abailable"); Assert.assertNotNull(resultValues.get(resMaxColName), "Max col expected to be abailable"); Set<Triple<Double, Double, Double>> expected = new HashSet<>(); expected.add(new Triple<>(dp.v(1), dp.v(10), dp.v(2))); expected.add(new Triple<>(dp.v(5), dp.v(1), dp.v(1))); expected.add(new Triple<>(dp.v(100), dp.v(50), dp.v(50))); expected.add(new Triple<>(dp.v(99), dp.v(0), dp.v(0))); Set<Triple<Double, Double, Double>> actual = new HashSet<>(); for (long rowId : resultValues.get(COL_A).keySet()) { actual.add(new Triple<>(resultValues.get(COL_A).get(rowId), resultValues.get(resMaxColName).get(rowId), resultValues.get(resMinColName).get(rowId))); } Assert.assertEquals(actual, expected, "Expected correct values"); } finally { executor.shutdownNow(); } } @Test public void overflowAvgTest() throws InterruptedException, ExecutionException { Object[] colAValues = dp.a(1, 5, 1, 5, 99, 1); Object[] colBValues = new Double[] { Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE - 1, Double.MIN_VALUE, 0., Double.MAX_VALUE }; initializeSimpleTable(colAValues, colBValues); // GIVEN ExecutablePlan executablePlan = buildExecutablePlan("Select " + COL_A + ", avg(" + COL_B + ") from " + TABLE + " group by " + COL_A); ExecutorService executor = executors.newTestExecutor(executablePlan.preferredExecutorServiceSize()); try { // WHEN // executing it on the sample table Future<Void> future = executablePlan.executeAsynchronously(executor); future.get(); // wait until done. // THEN Assert.assertTrue(columnValueConsumerIsDone, "Source should have reported 'done'"); Assert.assertTrue(future.isDone(), "Future should report done"); Assert.assertFalse(future.isCancelled(), "Future should not report cancelled"); Assert.assertTrue(resultValues.containsKey(COL_A), "Result values should be available for result column a"); String resAvgColName = functionBasedColumnNameBuilderFactory.create().withFunctionName("avg").addParameterColumnName(COL_B).build(); Assert.assertTrue(resultValues.containsKey(resAvgColName), "Result values should be available for result avg column"); Assert.assertEquals(resultValues.size(), 2, "Result values should be available for two columns only"); BigDecimal sum = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.valueOf(Double.MAX_VALUE - 1)) .add(BigDecimal.valueOf(Double.MAX_VALUE)); Map<Double, Double> expected = new HashMap<>(); expected.put(dp.v(1), sum.divide(BigDecimal.valueOf(3l), BigDecimalHelper.defaultMathContext()).doubleValue()); expected.put(dp.v(5), new BigDecimal(Double.MAX_VALUE).subtract(new BigDecimal(Double.MIN_VALUE)) .divide(new BigDecimal(2), BigDecimalHelper.defaultMathContext()).doubleValue()); expected.put(dp.v(99), 0.); for (long rowId : resultValues.get(COL_A).keySet()) { Double colA = resultValues.get(COL_A).get(rowId); Double avg = resultValues.get(resAvgColName).get(rowId); Assert.assertTrue(DoubleUtil.equals(expected.get(colA), avg), "Expected correct value for colA " + colA + ". Expected " + expected.get(colA) + " but was " + avg); } } finally { executor.shutdownNow(); } } @Test public void sumTest() throws InterruptedException, ExecutionException { Object[] colAValues = dp.a(1, 5, 1, 5, 99, 1); Object[] colBValues = dp.a(1, 5, 2, 6, -500, 3); initializeSimpleTable(colAValues, colBValues); // GIVEN ExecutablePlan executablePlan = buildExecutablePlan("Select " + COL_A + ", sum(" + COL_B + ") from " + TABLE + " group by " + COL_A); ExecutorService executor = executors.newTestExecutor(executablePlan.preferredExecutorServiceSize()); try { // WHEN // executing it on the sample table Future<Void> future = executablePlan.executeAsynchronously(executor); future.get(); // wait until done. // THEN Assert.assertTrue(columnValueConsumerIsDone, "Source should have reported 'done'"); Assert.assertTrue(future.isDone(), "Future should report done"); Assert.assertFalse(future.isCancelled(), "Future should not report cancelled"); Assert.assertTrue(resultValues.containsKey(COL_A), "Result values should be available for result column a"); String resAvgColName = functionBasedColumnNameBuilderFactory.create().withFunctionName("sum").addParameterColumnName(COL_B).build(); Assert.assertTrue(resultValues.containsKey(resAvgColName), "Result values should be available for result avg column"); Assert.assertEquals(resultValues.size(), 2, "Result values should be available for two columns only"); Set<Pair<Double, Double>> expected = new HashSet<>(); expected.add(new Pair<>(dp.v(1), dp.v(1) + dp.v(2) + dp.v(3))); expected.add(new Pair<>(dp.v(5), dp.v(5) + dp.v(6))); expected.add(new Pair<>(dp.v(99), dp.v(-500))); Set<Pair<Double, Double>> actual = new HashSet<>(); for (long rowId : resultValues.get(COL_A).keySet()) actual.add(new Pair<>(resultValues.get(COL_A).get(rowId), resultValues.get(resAvgColName).get(rowId))); Assert.assertEquals(actual, expected, "Expected correct values."); } finally { executor.shutdownNow(); } } }