blob: 522a8fb091c282f35b093825fbc61132558ef43e [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.drill.exec.planner.sql.conversion;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.calcite.adapter.java.JavaTypeFactory;
import org.apache.calcite.jdbc.DynamicSchema;
import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
import org.apache.calcite.plan.ConventionTraitDef;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCostFactory;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.volcano.VolcanoPlanner;
import org.apache.calcite.rel.RelCollationTraitDef;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelRoot;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.runtime.Hook;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.drill.common.config.DrillConfig;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.exec.ExecConstants;
import org.apache.drill.exec.expr.fn.FunctionImplementationRegistry;
import org.apache.drill.exec.ops.QueryContext;
import org.apache.drill.exec.ops.UdfUtilities;
import org.apache.drill.exec.planner.cost.DrillCostBase;
import org.apache.drill.exec.planner.logical.DrillConstExecutor;
import org.apache.drill.exec.planner.logical.DrillRelFactories;
import org.apache.drill.exec.planner.physical.DrillDistributionTraitDef;
import org.apache.drill.exec.planner.physical.PlannerSettings;
import org.apache.drill.exec.planner.sql.DrillConvertletTable;
import org.apache.drill.exec.planner.sql.DrillParserConfig;
import org.apache.drill.exec.planner.sql.SchemaUtilites;
import org.apache.drill.exec.planner.sql.parser.impl.DrillSqlParseException;
import org.apache.drill.exec.planner.types.DrillRelDataTypeSystem;
import org.apache.drill.exec.rpc.user.UserSession;
import org.apache.drill.exec.util.Utilities;
/**
* Class responsible for managing:
* <ul>
* <li>parsing - {@link #parse(String)}<li/>
* <li>validation - {@link #validate(SqlNode)}<li/>
* <li>conversion to rel - {@link #toRel(SqlNode)} (String)}<li/>
* <ul/>
*/
public class SqlConverter {
static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(SqlConverter.class);
private final JavaTypeFactory typeFactory;
private final SqlParser.Config parserConfig;
private final DrillCalciteCatalogReader catalog;
private final PlannerSettings settings;
private final SchemaPlus rootSchema;
private final SchemaPlus defaultSchema;
private final SqlOperatorTable opTab;
private final RelOptCostFactory costFactory;
private final DrillValidator validator;
private final boolean isInnerQuery;
private final boolean isExpandedView;
private final UdfUtilities util;
private final FunctionImplementationRegistry functions;
private final String temporarySchema;
private final UserSession session;
private final DrillConfig drillConfig;
// Allow the default config to be modified using immutable configs
private SqlToRelConverter.Config sqlToRelConverterConfig;
private RelOptCluster cluster;
private VolcanoPlanner planner;
private boolean useRootSchema = false;
static {
/*
* Sets value to false to avoid simplifying project expressions
* during creating new projects since it may cause changing data mode
* which causes to assertion errors during type validation
*/
Hook.REL_BUILDER_SIMPLIFY.add(Hook.propertyJ(false));
}
public SqlConverter(QueryContext context) {
this.settings = context.getPlannerSettings();
this.util = context;
this.functions = context.getFunctionRegistry();
this.parserConfig = new DrillParserConfig(settings);
this.sqlToRelConverterConfig = SqlToRelConverter.configBuilder()
.withInSubQueryThreshold((int) settings.getInSubqueryThreshold())
.withConvertTableAccess(false)
.withExpand(false)
.withRelBuilderFactory(DrillRelFactories.LOGICAL_BUILDER)
.build();
this.isInnerQuery = false;
this.isExpandedView = false;
this.typeFactory = new JavaTypeFactoryImpl(DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM);
this.defaultSchema = context.getNewDefaultSchema();
this.rootSchema = SchemaUtilites.rootSchema(defaultSchema);
this.temporarySchema = context.getConfig().getString(ExecConstants.DEFAULT_TEMPORARY_WORKSPACE);
this.session = context.getSession();
this.drillConfig = context.getConfig();
this.catalog = new DrillCalciteCatalogReader(
rootSchema,
parserConfig.caseSensitive(),
DynamicSchema.from(defaultSchema).path(null),
typeFactory,
drillConfig,
session,
temporarySchema,
this::useRootSchema,
this::getDefaultSchema);
this.opTab = new ChainedSqlOperatorTable(Arrays.asList(context.getDrillOperatorTable(), catalog));
this.costFactory = (settings.useDefaultCosting()) ? null : new DrillCostBase.DrillCostFactory();
this.validator = new DrillValidator(opTab, catalog, typeFactory, parserConfig.conformance(), session.getOptions());
validator.setIdentifierExpansion(true);
cluster = null;
}
SqlConverter(SqlConverter parent, SchemaPlus defaultSchema, SchemaPlus rootSchema,
DrillCalciteCatalogReader catalog) {
this.parserConfig = parent.parserConfig;
this.sqlToRelConverterConfig = parent.sqlToRelConverterConfig;
this.defaultSchema = defaultSchema;
this.functions = parent.functions;
this.util = parent.util;
this.isInnerQuery = true;
this.isExpandedView = true;
this.typeFactory = parent.typeFactory;
this.costFactory = parent.costFactory;
this.settings = parent.settings;
this.rootSchema = rootSchema;
this.catalog = catalog;
this.opTab = parent.opTab;
this.planner = parent.planner;
this.validator = new DrillValidator(opTab, catalog, typeFactory, parserConfig.conformance(), parent.session.getOptions());
this.temporarySchema = parent.temporarySchema;
this.session = parent.session;
this.drillConfig = parent.drillConfig;
validator.setIdentifierExpansion(true);
this.cluster = parent.cluster;
}
public SqlNode parse(String sql) {
try {
SqlParser parser = SqlParser.create(sql, parserConfig);
return parser.parseStmt();
} catch (SqlParseException parseError) {
DrillSqlParseException dex = new DrillSqlParseException(sql, parseError);
UserException.Builder builder = UserException
.parseError(dex)
.addContext(dex.getSqlWithErrorPointer());
if (isInnerQuery) {
builder.message("Failure parsing a view your query is dependent upon.");
}
throw builder.build(logger);
}
}
public SqlNode validate(final SqlNode parsedNode) {
try {
return validator.validate(parsedNode);
} catch (RuntimeException e) {
UserException.Builder builder = UserException
.validationError(e);
if (isInnerQuery) {
builder.message("Failure validating a view your query is dependent upon.");
}
throw builder.build(logger);
}
}
public RelRoot toRel(final SqlNode validatedNode) {
initCluster(initPlanner());
DrillViewExpander viewExpander = new DrillViewExpander(this);
final SqlToRelConverter sqlToRelConverter = new SqlToRelConverter(
viewExpander, validator, catalog, cluster,
DrillConvertletTable.INSTANCE, sqlToRelConverterConfig);
boolean topLevelQuery = !isInnerQuery || isExpandedView;
RelRoot rel = sqlToRelConverter.convertQuery(validatedNode, false, topLevelQuery);
// If extra expressions used in ORDER BY were added to the project list,
// add another project to remove them.
if (topLevelQuery && rel.rel.getRowType().getFieldCount() - rel.fields.size() > 0) {
RexBuilder builder = rel.rel.getCluster().getRexBuilder();
RelNode relNode = rel.rel;
List<RexNode> expressions = rel.fields.stream()
.map(f -> builder.makeInputRef(relNode, f.left))
.collect(Collectors.toList());
RelNode project = LogicalProject.create(rel.rel, expressions, rel.validatedRowType);
rel = RelRoot.of(project, rel.validatedRowType, rel.kind);
}
return rel.withRel(sqlToRelConverter.flattenTypes(rel.rel, true));
}
public RelDataType getOutputType(SqlNode validatedNode) {
return validator.getValidatedNodeType(validatedNode);
}
public JavaTypeFactory getTypeFactory() {
return typeFactory;
}
public DrillConfig getDrillConfig() {
return drillConfig;
}
public UserSession getSession() {
return session;
}
public RelOptCostFactory getCostFactory() {
return costFactory;
}
public SchemaPlus getRootSchema() {
return rootSchema;
}
public SchemaPlus getDefaultSchema() {
return defaultSchema;
}
public boolean isCaseSensitive() {
return parserConfig.caseSensitive();
}
/** Disallow temporary tables presence in sql statement (ex: in view definitions) */
public void disallowTemporaryTables() {
catalog.disallowTemporaryTables();
}
String getTemporarySchema() {
return temporarySchema;
}
boolean useRootSchema() {
return this.useRootSchema;
}
/**
* Is root schema path should be used as default schema path.
*
* @param useRoot flag
*/
public void useRootSchemaAsDefault(boolean useRoot) {
useRootSchema = useRoot;
}
private void initCluster(RelOptPlanner planner) {
if (cluster == null) {
cluster = RelOptCluster.create(planner, new DrillRexBuilder(typeFactory));
JaninoRelMetadataProvider relMetadataProvider = Utilities.registerJaninoRelMetadataProvider();
cluster.setMetadataProvider(relMetadataProvider);
}
}
private RelOptPlanner initPlanner() {
if (planner == null) {
planner = new VolcanoPlanner(costFactory, settings);
planner.setExecutor(new DrillConstExecutor(functions, util, settings));
planner.clearRelTraitDefs();
planner.addRelTraitDef(ConventionTraitDef.INSTANCE);
planner.addRelTraitDef(DrillDistributionTraitDef.INSTANCE);
planner.addRelTraitDef(RelCollationTraitDef.INSTANCE);
}
return planner;
}
}