/**
* 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.query.validate;
import java.util.Arrays;
import java.util.function.Function;
import org.diqube.context.Profiles;
import org.diqube.metadata.DefaultTableMetadataManager;
import org.diqube.metadata.TableMetadataManager;
import org.diqube.plan.ExecutionPlanBuilder;
import org.diqube.plan.ExecutionPlanBuilderFactory;
import org.diqube.plan.exception.ValidationException;
import org.diqube.server.querymaster.query.validate.MasterExecutionRequestValidator;
import org.diqube.testutil.TestContextOverrideBean;
import org.diqube.thrift.base.thrift.AuthorizationException;
import org.diqube.thrift.base.thrift.FieldMetadata;
import org.diqube.thrift.base.thrift.FieldType;
import org.diqube.thrift.base.thrift.TableMetadata;
import org.mockito.Mockito;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* Tests validation of various diql queries that is implemented by {@link MasterExecutionRequestValidator}.
*
* @author Bastian Gloeckle
*/
public class MasterExecutionRequestValidatorTest {
private static final String TABLE = "tab";
private AnnotationConfigApplicationContext dataContext;
private ExecutionPlanBuilder executionPlanBuilder;
private TableMetadataManager metadataManagerMock;
@BeforeMethod
public void before() {
dataContext = new AnnotationConfigApplicationContext();
dataContext.getEnvironment().setActiveProfiles(Profiles.UNIT_TEST);
dataContext.scan("org.diqube");
TestContextOverrideBean.overrideBeanClass(dataContext, DefaultTableMetadataManager.class,
DelegatingTableMetadataManager.class);
dataContext.refresh();
metadataManagerMock = Mockito.mock(TableMetadataManager.class);
dataContext.getBean(DelegatingTableMetadataManager.class).setDelegate(metadataManagerMock);
ExecutionPlanBuilderFactory executionPlanBuilderFactory = dataContext.getBean(ExecutionPlanBuilderFactory.class);
executionPlanBuilder = executionPlanBuilderFactory.createExecutionPlanBuilder();
executionPlanBuilder.withAdditionalRequestValidator(dataContext.getBean(MasterExecutionRequestValidator.class));
}
@AfterMethod
public void after() throws Throwable {
dataContext.close();
}
@Test
public void simpleTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.LONG, false));
// WHEN
executionPlanBuilder.fromDiql("select a from " + TABLE + " where a = 1").build();
// THEN
// no validation exception
}
@Test(expectedExceptions = ValidationException.class)
public void simpleFailingTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.STRING, false));
// WHEN
executionPlanBuilder.fromDiql("select a from " + TABLE + " where a = 1").build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because a is compared to a LONG");
}
@Test(expectedExceptions = ValidationException.class)
public void projectionFailingTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.LONG, false));
// WHEN
executionPlanBuilder.fromDiql("select a from " + TABLE + " where log(a) = 1").build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because log(a) is compared to a LONG");
}
@Test
public void projectionTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.LONG, false));
// WHEN
executionPlanBuilder.fromDiql("select a from " + TABLE + " where log(a) = 1.").build();
// THEN
// no exception
}
@Test(expectedExceptions = ValidationException.class)
public void projectionUnavailableTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.LONG, false));
// WHEN
executionPlanBuilder.fromDiql("select a from " + TABLE + " where concat(a, a) = 'b'").build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because concat is called on two LONG cols");
}
@Test(expectedExceptions = ValidationException.class, enabled = false) // TODO #111
public void projectionUnavailable2Test() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.STRING, false));
// WHEN
executionPlanBuilder.fromDiql("select a from " + TABLE + " where concat(a, 5) = 'b'").build();
// THEN
Assert.fail(
"Expected a ValidationException to be thrown because concat is called on a STRING col and a constant LONG");
}
@Test(expectedExceptions = ValidationException.class)
public void projectionUnavailable3Test() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.LONG, false));
// WHEN
executionPlanBuilder.fromDiql("select concat(a, a) from " + TABLE).build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because concat is called on two LONG cols");
}
@Test(expectedExceptions = ValidationException.class)
public void projectionUnavailable4Test() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.DOUBLE, false), //
new FieldMetadata("b", FieldType.LONG, false));
// WHEN
executionPlanBuilder.fromDiql("select add(a, b) from " + TABLE).build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because add is called on different types of cols");
}
@Test(expectedExceptions = ValidationException.class)
public void projectionUnavailable5Test() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.DOUBLE, false));
// WHEN
executionPlanBuilder.fromDiql("select log('4') from " + TABLE).build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because log is called on a literal STRING");
}
@Test(expectedExceptions = ValidationException.class)
public void projectionUnavailable6Test() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.DOUBLE, false));
// WHEN
executionPlanBuilder.fromDiql("select concat('b', log(a)) from " + TABLE).build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because concat is called with a projected DOUBLE col");
}
@Test(expectedExceptions = ValidationException.class)
public void rootColUnvailableTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.DOUBLE, false));
// WHEN
executionPlanBuilder.fromDiql("select b from " + TABLE).build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because col b does not exist");
}
@Test(expectedExceptions = ValidationException.class)
public void rootColUnvailable2Test() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.DOUBLE, false));
// WHEN
executionPlanBuilder.fromDiql("select log(b) from " + TABLE).build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because col b does not exist");
}
@Test(expectedExceptions = ValidationException.class)
public void flattenedFieldUnavailableTest() throws AuthorizationException {
// GIVEN
Mockito.when(metadataManagerMock.getCurrentTableMetadata(TABLE)).then(invocation -> {
return new TableMetadata(TABLE, Arrays.asList(new FieldMetadata("a", FieldType.DOUBLE, false)));
});
// WHEN
executionPlanBuilder.fromDiql("select b from flatten(" + TABLE + ", c[*])").build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because col c[*] does not exist");
}
@Test
public void flattenedFieldAvailableTest() throws AuthorizationException {
// GIVEN
Mockito.when(metadataManagerMock.getCurrentTableMetadata(TABLE)).then(invocation -> {
return new TableMetadata(TABLE, Arrays.asList(new FieldMetadata("a", FieldType.DOUBLE, true)));
});
// WHEN
executionPlanBuilder.fromDiql("select b from flatten(" + TABLE + ", a[*])").build();
// THEN
// no exception
}
@Test(expectedExceptions = ValidationException.class)
public void flattenedFieldNotRepeatedAvailableTest() throws AuthorizationException {
// GIVEN
Mockito.when(metadataManagerMock.getCurrentTableMetadata(TABLE)).then(invocation -> {
return new TableMetadata(TABLE, Arrays.asList(new FieldMetadata("a", FieldType.DOUBLE, false)));
});
// WHEN
executionPlanBuilder.fromDiql("select b from flatten(" + TABLE + ", a)").build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because col a is not repeated");
}
@Test(expectedExceptions = ValidationException.class)
public void flattenedFieldNotRepeatedButReferencedRepeatedAvailableTest() throws AuthorizationException {
// GIVEN
Mockito.when(metadataManagerMock.getCurrentTableMetadata(TABLE)).then(invocation -> {
return new TableMetadata(TABLE, Arrays.asList(new FieldMetadata("a", FieldType.DOUBLE, false)));
});
// WHEN
executionPlanBuilder.fromDiql("select b from flatten(" + TABLE + ", a[*])").build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because col a is not repeated");
}
@Test(expectedExceptions = ValidationException.class)
public void repeatedFieldReferencedUnrepeatedTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.DOUBLE, true));
// WHEN
executionPlanBuilder.fromDiql("select a from " + TABLE).build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because col a is repeated");
}
@Test(expectedExceptions = ValidationException.class)
public void unrepeatedFieldReferencedRepeatedTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.DOUBLE, false));
// WHEN
executionPlanBuilder.fromDiql("select avg(a[*]) from " + TABLE).build();
// THEN
Assert.fail("Expected a ValidationException to be thrown because col a is not repeated");
}
@Test
public void validColAggregationTest() throws AuthorizationException {
// GIVEN
prepareMetadata(new FieldMetadata("a", FieldType.DOUBLE, true));
// WHEN
executionPlanBuilder.fromDiql("select avg(a[*]) from " + TABLE).build();
// THEN
// no excpetion
}
private void prepareMetadata(FieldMetadata... fields) throws AuthorizationException {
Mockito.when(metadataManagerMock.getCurrentTableMetadata(Mockito.anyString())).then(invocation -> {
return new TableMetadata(TABLE, Arrays.asList(fields));
});
}
public static class DelegatingTableMetadataManager implements TableMetadataManager {
private TableMetadataManager delegate;
public TableMetadataManager getDelegate() {
return delegate;
}
public void setDelegate(TableMetadataManager delegate) {
this.delegate = delegate;
}
@Override
public TableMetadata getCurrentTableMetadata(String tableName) throws AuthorizationException {
return delegate.getCurrentTableMetadata(tableName);
}
@Override
public void adjustTableMetadata(String tableName, Function<TableMetadata, TableMetadata> adjustFunction) {
delegate.adjustTableMetadata(tableName, adjustFunction);
}
@Override
public void startRecomputingTableMetadata(String tableName) {
delegate.startRecomputingTableMetadata(tableName);
}
}
}