blob: 8ef3ad3106f34323cd0e7047aa9f392e0b12b420 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.druid.sql.calcite.planner;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.rel.logical.LogicalTableScan;
import org.apache.calcite.rel.rules.ProjectMergeRule;
import org.apache.calcite.schema.Schema;
import org.apache.druid.guice.LazySingleton;
import org.apache.druid.jackson.DefaultObjectMapper;
import org.apache.druid.jackson.JacksonModule;
import org.apache.druid.math.expr.ExprMacroTable;
import org.apache.druid.segment.join.JoinableFactoryWrapper;
import org.apache.druid.server.QueryLifecycleFactory;
import org.apache.druid.server.security.AuthConfig;
import org.apache.druid.server.security.AuthorizerMapper;
import org.apache.druid.server.security.ResourceType;
import org.apache.druid.sql.calcite.aggregation.SqlAggregator;
import org.apache.druid.sql.calcite.expression.SqlOperatorConversion;
import org.apache.druid.sql.calcite.rule.ExtensionCalciteRuleProvider;
import org.apache.druid.sql.calcite.run.NativeSqlEngine;
import org.apache.druid.sql.calcite.schema.DruidSchemaCatalog;
import org.apache.druid.sql.calcite.schema.DruidSchemaName;
import org.apache.druid.sql.calcite.schema.NamedSchema;
import org.apache.druid.sql.calcite.util.CalciteTestBase;
import org.apache.druid.sql.calcite.util.CalciteTests;
import org.easymock.EasyMock;
import org.easymock.EasyMockExtension;
import org.easymock.Mock;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import static org.apache.calcite.plan.RelOptRule.any;
import static org.apache.calcite.plan.RelOptRule.operand;
import static org.apache.druid.sql.calcite.planner.CalciteRulesManager.BLOAT_PROPERTY;
import static org.apache.druid.sql.calcite.planner.CalciteRulesManager.DEFAULT_BLOAT;
@ExtendWith(EasyMockExtension.class)
public class CalcitePlannerModuleTest extends CalciteTestBase
{
private static final String SCHEMA_1 = "SCHEMA_1";
private static final String SCHEMA_2 = "SCHEMA_2";
private static final String DRUID_SCHEMA_NAME = "DRUID_SCHEMA_NAME";
private static final int BLOAT = 1200;
@Mock
private NamedSchema druidSchema1;
@Mock
private NamedSchema druidSchema2;
@Mock
private Schema schema1;
@Mock
private Schema schema2;
@Mock
private QueryLifecycleFactory queryLifecycleFactory;
@Mock
private ExprMacroTable macroTable;
@Mock
private JoinableFactoryWrapper joinableFactoryWrapper;
@Mock
private AuthorizerMapper authorizerMapper;
@Mock
private DruidSchemaCatalog rootSchema;
private Set<SqlAggregator> aggregators;
private Set<SqlOperatorConversion> operatorConversions;
private CalcitePlannerModule target;
private Injector injector;
private RelOptRule customRule;
@BeforeEach
public void setUp()
{
EasyMock.expect(druidSchema1.getSchema()).andStubReturn(schema1);
EasyMock.expect(druidSchema2.getSchema()).andStubReturn(schema2);
EasyMock.expect(druidSchema1.getSchemaName()).andStubReturn(SCHEMA_1);
EasyMock.expect(druidSchema2.getSchemaName()).andStubReturn(SCHEMA_2);
EasyMock.expect(druidSchema1.getSchemaResourceType(EasyMock.anyString())).andStubReturn(ResourceType.DATASOURCE);
EasyMock.expect(druidSchema2.getSchemaResourceType(EasyMock.anyString())).andStubReturn("test");
EasyMock.replay(druidSchema1, druidSchema2);
aggregators = ImmutableSet.of();
operatorConversions = ImmutableSet.of();
target = new CalcitePlannerModule();
customRule = new RelOptRule(operand(LogicalTableScan.class, any()), "customRule")
{
@Override
public void onMatch(RelOptRuleCall call)
{
}
};
injector = Guice.createInjector(
new JacksonModule(),
binder -> {
binder.bind(Validator.class).toInstance(Validation.buildDefaultValidatorFactory().getValidator());
binder.bindScope(LazySingleton.class, Scopes.SINGLETON);
binder.bind(QueryLifecycleFactory.class).toInstance(queryLifecycleFactory);
binder.bind(ExprMacroTable.class).toInstance(macroTable);
binder.bind(AuthorizerMapper.class).toInstance(authorizerMapper);
binder.bind(String.class).annotatedWith(DruidSchemaName.class).toInstance(DRUID_SCHEMA_NAME);
binder.bind(Key.get(new TypeLiteral<Set<SqlAggregator>>() {})).toInstance(aggregators);
binder.bind(Key.get(new TypeLiteral<Set<SqlOperatorConversion>>() {})).toInstance(operatorConversions);
binder.bind(DruidSchemaCatalog.class).toInstance(rootSchema);
binder.bind(JoinableFactoryWrapper.class).toInstance(joinableFactoryWrapper);
binder.bind(CatalogResolver.class).toInstance(CatalogResolver.NULL_RESOLVER);
},
target,
binder -> {
Multibinder.newSetBinder(binder, ExtensionCalciteRuleProvider.class)
.addBinding()
.toInstance(plannerContext -> customRule);
}
);
}
@Test
public void testDruidOperatorTableIsInjectable()
{
DruidOperatorTable operatorTable = injector.getInstance(DruidOperatorTable.class);
Assert.assertNotNull(operatorTable);
// Should be a singleton.
DruidOperatorTable other = injector.getInstance(DruidOperatorTable.class);
Assert.assertSame(other, operatorTable);
}
@Test
public void testPlannerFactoryIsInjectable()
{
PlannerFactory plannerFactory = injector.getInstance(PlannerFactory.class);
Assert.assertNotNull(PlannerFactory.class);
// Should be a singleton.
PlannerFactory other = injector.getInstance(PlannerFactory.class);
Assert.assertSame(other, plannerFactory);
}
@Test
public void testPlannerConfigIsInjected()
{
PlannerConfig plannerConfig = injector.getInstance(PlannerConfig.class);
Assert.assertNotNull(plannerConfig);
}
@Test
public void testExtensionCalciteRule()
{
ObjectMapper mapper = new DefaultObjectMapper();
PlannerToolbox toolbox = new PlannerToolbox(
injector.getInstance(DruidOperatorTable.class),
macroTable,
mapper,
injector.getInstance(PlannerConfig.class),
rootSchema,
joinableFactoryWrapper,
CatalogResolver.NULL_RESOLVER,
"druid",
new CalciteRulesManager(ImmutableSet.of()),
CalciteTests.TEST_AUTHORIZER_MAPPER,
AuthConfig.newBuilder().build()
);
PlannerContext context = PlannerContext.create(
toolbox,
"SELECT 1",
new NativeSqlEngine(queryLifecycleFactory, mapper),
Collections.emptyMap(),
null
);
boolean containsCustomRule = injector.getInstance(CalciteRulesManager.class)
.druidConventionRuleSet(context)
.contains(customRule);
Assert.assertTrue(containsCustomRule);
}
@Test
public void testConfigurableBloat()
{
ObjectMapper mapper = new DefaultObjectMapper();
PlannerToolbox toolbox = new PlannerToolbox(
injector.getInstance(DruidOperatorTable.class),
macroTable,
mapper,
injector.getInstance(PlannerConfig.class),
rootSchema,
joinableFactoryWrapper,
CatalogResolver.NULL_RESOLVER,
"druid",
new CalciteRulesManager(ImmutableSet.of()),
CalciteTests.TEST_AUTHORIZER_MAPPER,
AuthConfig.newBuilder().build()
);
PlannerContext contextWithBloat = PlannerContext.create(
toolbox,
"SELECT 1",
new NativeSqlEngine(queryLifecycleFactory, mapper),
Collections.singletonMap(BLOAT_PROPERTY, BLOAT),
null
);
PlannerContext contextWithoutBloat = PlannerContext.create(
toolbox,
"SELECT 1",
new NativeSqlEngine(queryLifecycleFactory, mapper),
Collections.emptyMap(),
null
);
assertBloat(contextWithBloat, BLOAT);
assertBloat(contextWithoutBloat, DEFAULT_BLOAT);
}
private void assertBloat(PlannerContext context, int expectedBloat)
{
Optional<ProjectMergeRule> firstProjectMergeRule = injector.getInstance(CalciteRulesManager.class).baseRuleSet(context).stream()
.filter(rule -> rule instanceof ProjectMergeRule)
.map(rule -> (ProjectMergeRule) rule)
.findAny();
Assert.assertTrue(firstProjectMergeRule.isPresent() && firstProjectMergeRule.get().config.bloat() == expectedBloat);
}
}