| /* |
| * 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.calcite.adapter.jdbc; |
| |
| import org.apache.calcite.linq4j.Queryable; |
| import org.apache.calcite.linq4j.tree.Expression; |
| import org.apache.calcite.plan.Contexts; |
| import org.apache.calcite.plan.Convention; |
| import org.apache.calcite.plan.RelOptCluster; |
| import org.apache.calcite.plan.RelOptCost; |
| import org.apache.calcite.plan.RelOptPlanner; |
| import org.apache.calcite.plan.RelOptRule; |
| import org.apache.calcite.plan.RelOptRuleCall; |
| import org.apache.calcite.plan.RelOptTable; |
| import org.apache.calcite.plan.RelRule; |
| import org.apache.calcite.plan.RelTraitSet; |
| import org.apache.calcite.prepare.Prepare; |
| import org.apache.calcite.rel.InvalidRelException; |
| import org.apache.calcite.rel.RelCollation; |
| import org.apache.calcite.rel.RelNode; |
| import org.apache.calcite.rel.RelWriter; |
| import org.apache.calcite.rel.SingleRel; |
| import org.apache.calcite.rel.convert.ConverterRule; |
| import org.apache.calcite.rel.core.Aggregate; |
| import org.apache.calcite.rel.core.AggregateCall; |
| import org.apache.calcite.rel.core.CorrelationId; |
| import org.apache.calcite.rel.core.Filter; |
| import org.apache.calcite.rel.core.Intersect; |
| import org.apache.calcite.rel.core.Join; |
| import org.apache.calcite.rel.core.JoinRelType; |
| import org.apache.calcite.rel.core.Minus; |
| import org.apache.calcite.rel.core.Project; |
| import org.apache.calcite.rel.core.RelFactories; |
| import org.apache.calcite.rel.core.Sort; |
| import org.apache.calcite.rel.core.TableModify; |
| import org.apache.calcite.rel.core.Union; |
| import org.apache.calcite.rel.core.Values; |
| import org.apache.calcite.rel.metadata.RelMdUtil; |
| import org.apache.calcite.rel.metadata.RelMetadataQuery; |
| import org.apache.calcite.rel.rel2sql.SqlImplementor; |
| import org.apache.calcite.rel.type.RelDataType; |
| import org.apache.calcite.rex.RexCall; |
| import org.apache.calcite.rex.RexInputRef; |
| import org.apache.calcite.rex.RexLiteral; |
| import org.apache.calcite.rex.RexNode; |
| import org.apache.calcite.rex.RexProgram; |
| import org.apache.calcite.rex.RexUtil; |
| import org.apache.calcite.rex.RexVisitorImpl; |
| import org.apache.calcite.schema.ModifiableTable; |
| import org.apache.calcite.sql.SqlDialect; |
| import org.apache.calcite.sql.SqlFunction; |
| import org.apache.calcite.sql.SqlOperator; |
| import org.apache.calcite.sql.validate.SqlValidatorUtil; |
| import org.apache.calcite.tools.RelBuilder; |
| import org.apache.calcite.tools.RelBuilderFactory; |
| import org.apache.calcite.util.ImmutableBitSet; |
| import org.apache.calcite.util.Util; |
| import org.apache.calcite.util.trace.CalciteTrace; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.slf4j.Logger; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| /** |
| * Rules and relational operators for |
| * {@link JdbcConvention} |
| * calling convention. |
| */ |
| public class JdbcRules { |
| private JdbcRules() { |
| } |
| |
| protected static final Logger LOGGER = CalciteTrace.getPlannerTracer(); |
| |
| static final RelFactories.ProjectFactory PROJECT_FACTORY = |
| (input, hints, projects, fieldNames, variablesSet) -> { |
| Preconditions.checkArgument(variablesSet.isEmpty(), |
| "JdbcProject does not allow variables"); |
| final RelOptCluster cluster = input.getCluster(); |
| final RelDataType rowType = |
| RexUtil.createStructType(cluster.getTypeFactory(), projects, |
| fieldNames, SqlValidatorUtil.F_SUGGESTER); |
| return new JdbcProject(cluster, input.getTraitSet(), input, projects, |
| rowType); |
| }; |
| |
| static final RelFactories.FilterFactory FILTER_FACTORY = |
| (input, condition, variablesSet) -> { |
| Preconditions.checkArgument(variablesSet.isEmpty(), |
| "JdbcFilter does not allow variables"); |
| return new JdbcFilter(input.getCluster(), |
| input.getTraitSet(), input, condition); |
| }; |
| |
| static final RelFactories.JoinFactory JOIN_FACTORY = |
| (left, right, hints, condition, variablesSet, joinType, semiJoinDone) -> { |
| final RelOptCluster cluster = left.getCluster(); |
| final RelTraitSet traitSet = |
| cluster.traitSetOf( |
| requireNonNull(left.getConvention(), "left.getConvention()")); |
| try { |
| return new JdbcJoin(cluster, traitSet, left, right, condition, |
| variablesSet, joinType); |
| } catch (InvalidRelException e) { |
| throw new AssertionError(e); |
| } |
| }; |
| |
| static final RelFactories.CorrelateFactory CORRELATE_FACTORY = |
| (left, right, hints, correlationId, requiredColumns, joinType) -> { |
| throw new UnsupportedOperationException("JdbcCorrelate"); |
| }; |
| |
| public static final RelFactories.SortFactory SORT_FACTORY = |
| (input, collation, offset, fetch) -> { |
| throw new UnsupportedOperationException("JdbcSort"); |
| }; |
| |
| public static final RelFactories.ExchangeFactory EXCHANGE_FACTORY = |
| (input, distribution) -> { |
| throw new UnsupportedOperationException("JdbcExchange"); |
| }; |
| |
| public static final RelFactories.SortExchangeFactory SORT_EXCHANGE_FACTORY = |
| (input, distribution, collation) -> { |
| throw new UnsupportedOperationException("JdbcSortExchange"); |
| }; |
| |
| public static final RelFactories.AggregateFactory AGGREGATE_FACTORY = |
| (input, hints, groupSet, groupSets, aggCalls) -> { |
| final RelOptCluster cluster = input.getCluster(); |
| final RelTraitSet traitSet = |
| cluster.traitSetOf( |
| requireNonNull(input.getConvention(), "input.getConvention()")); |
| try { |
| return new JdbcAggregate(cluster, traitSet, input, groupSet, |
| groupSets, aggCalls); |
| } catch (InvalidRelException e) { |
| throw new AssertionError(e); |
| } |
| }; |
| |
| public static final RelFactories.MatchFactory MATCH_FACTORY = |
| (input, pattern, rowType, strictStart, strictEnd, patternDefinitions, |
| measures, after, subsets, allRows, partitionKeys, orderKeys, |
| interval) -> { |
| throw new UnsupportedOperationException("JdbcMatch"); |
| }; |
| |
| public static final RelFactories.SetOpFactory SET_OP_FACTORY = |
| (kind, inputs, all) -> { |
| RelNode input = inputs.get(0); |
| RelOptCluster cluster = input.getCluster(); |
| final RelTraitSet traitSet = |
| cluster.traitSetOf( |
| requireNonNull(input.getConvention(), "input.getConvention()")); |
| switch (kind) { |
| case UNION: |
| return new JdbcUnion(cluster, traitSet, inputs, all); |
| case INTERSECT: |
| return new JdbcIntersect(cluster, traitSet, inputs, all); |
| case EXCEPT: |
| return new JdbcMinus(cluster, traitSet, inputs, all); |
| default: |
| throw new AssertionError("unknown: " + kind); |
| } |
| }; |
| |
| public static final RelFactories.ValuesFactory VALUES_FACTORY = |
| (cluster, rowType, tuples) -> { |
| throw new UnsupportedOperationException(); |
| }; |
| |
| public static final RelFactories.TableScanFactory TABLE_SCAN_FACTORY = |
| (toRelContext, table) -> { |
| throw new UnsupportedOperationException(); |
| }; |
| |
| public static final RelFactories.SnapshotFactory SNAPSHOT_FACTORY = |
| (input, period) -> { |
| throw new UnsupportedOperationException(); |
| }; |
| |
| /** A {@link RelBuilderFactory} that creates a {@link RelBuilder} that will |
| * create JDBC relational expressions for everything. */ |
| public static final RelBuilderFactory JDBC_BUILDER = |
| RelBuilder.proto( |
| Contexts.of(PROJECT_FACTORY, |
| FILTER_FACTORY, |
| JOIN_FACTORY, |
| SORT_FACTORY, |
| EXCHANGE_FACTORY, |
| SORT_EXCHANGE_FACTORY, |
| AGGREGATE_FACTORY, |
| MATCH_FACTORY, |
| SET_OP_FACTORY, |
| VALUES_FACTORY, |
| TABLE_SCAN_FACTORY, |
| SNAPSHOT_FACTORY)); |
| |
| /** Creates a list of rules with the given JDBC convention instance. */ |
| public static List<RelOptRule> rules(JdbcConvention out) { |
| final ImmutableList.Builder<RelOptRule> b = ImmutableList.builder(); |
| foreachRule(out, b::add); |
| return b.build(); |
| } |
| |
| /** Creates a list of rules with the given JDBC convention instance |
| * and builder factory. */ |
| public static List<RelOptRule> rules(JdbcConvention out, |
| RelBuilderFactory relBuilderFactory) { |
| final ImmutableList.Builder<RelOptRule> b = ImmutableList.builder(); |
| foreachRule(out, r -> |
| b.add(r.config.withRelBuilderFactory(relBuilderFactory).toRule())); |
| return b.build(); |
| } |
| |
| private static void foreachRule(JdbcConvention out, |
| Consumer<RelRule<?>> consumer) { |
| consumer.accept(JdbcToEnumerableConverterRule.create(out)); |
| consumer.accept(JdbcJoinRule.create(out)); |
| consumer.accept(JdbcProjectRule.create(out)); |
| consumer.accept(JdbcFilterRule.create(out)); |
| consumer.accept(JdbcAggregateRule.create(out)); |
| consumer.accept(JdbcSortRule.create(out)); |
| consumer.accept(JdbcUnionRule.create(out)); |
| consumer.accept(JdbcIntersectRule.create(out)); |
| consumer.accept(JdbcMinusRule.create(out)); |
| consumer.accept(JdbcTableModificationRule.create(out)); |
| consumer.accept(JdbcValuesRule.create(out)); |
| } |
| |
| /** Abstract base class for rule that converts to JDBC. */ |
| abstract static class JdbcConverterRule extends ConverterRule { |
| protected JdbcConverterRule(Config config) { |
| super(config); |
| } |
| } |
| |
| /** Rule that converts a join to JDBC. */ |
| public static class JdbcJoinRule extends JdbcConverterRule { |
| /** Creates a JdbcJoinRule. */ |
| public static JdbcJoinRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Join.class, Convention.NONE, out, "JdbcJoinRule") |
| .withRuleFactory(JdbcJoinRule::new) |
| .toRule(JdbcJoinRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcJoinRule(Config config) { |
| super(config); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| final Join join = (Join) rel; |
| switch (join.getJoinType()) { |
| case SEMI: |
| case ANTI: |
| // It's not possible to convert semi-joins or anti-joins. They have fewer columns |
| // than regular joins. |
| return null; |
| default: |
| return convert(join, true); |
| } |
| } |
| |
| /** |
| * Converts a {@code Join} into a {@code JdbcJoin}. |
| * |
| * @param join Join operator to convert |
| * @param convertInputTraits Whether to convert input to {@code join}'s |
| * JDBC convention |
| * @return A new JdbcJoin |
| */ |
| public @Nullable RelNode convert(Join join, boolean convertInputTraits) { |
| final List<RelNode> newInputs = new ArrayList<>(); |
| for (RelNode input : join.getInputs()) { |
| if (convertInputTraits && input.getConvention() != getOutTrait()) { |
| input = |
| convert(input, |
| input.getTraitSet().replace(out)); |
| } |
| newInputs.add(input); |
| } |
| if (convertInputTraits && !canJoinOnCondition(join.getCondition())) { |
| return null; |
| } |
| try { |
| return new JdbcJoin( |
| join.getCluster(), |
| join.getTraitSet().replace(out), |
| newInputs.get(0), |
| newInputs.get(1), |
| join.getCondition(), |
| join.getVariablesSet(), |
| join.getJoinType()); |
| } catch (InvalidRelException e) { |
| LOGGER.debug(e.toString()); |
| return null; |
| } |
| } |
| |
| /** |
| * Returns whether a condition is supported by {@link JdbcJoin}. |
| * |
| * <p>Corresponds to the capabilities of |
| * {@link SqlImplementor#convertConditionToSqlNode}. |
| * |
| * @param node Condition |
| * @return Whether condition is supported |
| */ |
| private static boolean canJoinOnCondition(RexNode node) { |
| final List<RexNode> operands; |
| switch (node.getKind()) { |
| case LITERAL: |
| // literal on a join condition would be TRUE or FALSE |
| return true; |
| case AND: |
| case OR: |
| operands = ((RexCall) node).getOperands(); |
| for (RexNode operand : operands) { |
| if (!canJoinOnCondition(operand)) { |
| return false; |
| } |
| } |
| return true; |
| |
| case EQUALS: |
| case IS_NOT_DISTINCT_FROM: |
| case NOT_EQUALS: |
| case GREATER_THAN: |
| case GREATER_THAN_OR_EQUAL: |
| case LESS_THAN: |
| case LESS_THAN_OR_EQUAL: |
| operands = ((RexCall) node).getOperands(); |
| if ((operands.get(0) instanceof RexInputRef) |
| && (operands.get(1) instanceof RexInputRef)) { |
| return true; |
| } |
| // fall through |
| |
| default: |
| return false; |
| } |
| } |
| |
| @Override public boolean matches(RelOptRuleCall call) { |
| Join join = call.rel(0); |
| JoinRelType joinType = join.getJoinType(); |
| return ((JdbcConvention) getOutConvention()).dialect.supportsJoinType(joinType); |
| } |
| } |
| |
| /** Join operator implemented in JDBC convention. */ |
| public static class JdbcJoin extends Join implements JdbcRel { |
| /** Creates a JdbcJoin. */ |
| public JdbcJoin(RelOptCluster cluster, RelTraitSet traitSet, |
| RelNode left, RelNode right, RexNode condition, |
| Set<CorrelationId> variablesSet, JoinRelType joinType) |
| throws InvalidRelException { |
| super(cluster, traitSet, ImmutableList.of(), left, right, condition, variablesSet, joinType); |
| } |
| |
| @Deprecated // to be removed before 2.0 |
| protected JdbcJoin( |
| RelOptCluster cluster, |
| RelTraitSet traitSet, |
| RelNode left, |
| RelNode right, |
| RexNode condition, |
| JoinRelType joinType, |
| Set<String> variablesStopped) |
| throws InvalidRelException { |
| this(cluster, traitSet, left, right, condition, |
| CorrelationId.setOf(variablesStopped), joinType); |
| } |
| |
| @Override public JdbcJoin copy(RelTraitSet traitSet, RexNode condition, |
| RelNode left, RelNode right, JoinRelType joinType, |
| boolean semiJoinDone) { |
| try { |
| return new JdbcJoin(getCluster(), traitSet, left, right, |
| condition, variablesSet, joinType); |
| } catch (InvalidRelException e) { |
| // Semantic error not possible. Must be a bug. Convert to |
| // internal error. |
| throw new AssertionError(e); |
| } |
| } |
| |
| @Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, |
| RelMetadataQuery mq) { |
| // We always "build" the |
| double rowCount = mq.getRowCount(this); |
| |
| return planner.getCostFactory().makeCost(rowCount, 0, 0); |
| } |
| |
| @Override public double estimateRowCount(RelMetadataQuery mq) { |
| final double leftRowCount = mq.getRowCount(left); |
| final double rightRowCount = mq.getRowCount(right); |
| return Math.max(leftRowCount, rightRowCount); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** Calc operator implemented in JDBC convention. |
| * |
| * @see org.apache.calcite.rel.core.Calc |
| * */ |
| @Deprecated // to be removed before 2.0 |
| public static class JdbcCalc extends SingleRel implements JdbcRel { |
| private final RexProgram program; |
| |
| public JdbcCalc(RelOptCluster cluster, |
| RelTraitSet traitSet, |
| RelNode input, |
| RexProgram program) { |
| super(cluster, traitSet, input); |
| assert getConvention() instanceof JdbcConvention; |
| this.program = program; |
| this.rowType = program.getOutputRowType(); |
| } |
| |
| @Deprecated // to be removed before 2.0 |
| public JdbcCalc(RelOptCluster cluster, RelTraitSet traitSet, RelNode input, |
| RexProgram program, int flags) { |
| this(cluster, traitSet, input, program); |
| Util.discard(flags); |
| } |
| |
| @Override public RelWriter explainTerms(RelWriter pw) { |
| return program.explainCalc(super.explainTerms(pw)); |
| } |
| |
| @Override public double estimateRowCount(RelMetadataQuery mq) { |
| return RelMdUtil.estimateFilteredRows(getInput(), program, mq); |
| } |
| |
| @Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, |
| RelMetadataQuery mq) { |
| double dRows = mq.getRowCount(this); |
| double dCpu = mq.getRowCount(getInput()) |
| * program.getExprCount(); |
| double dIo = 0; |
| return planner.getCostFactory().makeCost(dRows, dCpu, dIo); |
| } |
| |
| @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { |
| return new JdbcCalc(getCluster(), traitSet, sole(inputs), program); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link org.apache.calcite.rel.core.Project} to |
| * an {@link org.apache.calcite.adapter.jdbc.JdbcRules.JdbcProject}. |
| */ |
| public static class JdbcProjectRule extends JdbcConverterRule { |
| /** Creates a JdbcProjectRule. */ |
| public static JdbcProjectRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Project.class, project -> |
| (out.dialect.supportsWindowFunctions() |
| || !project.containsOver()) |
| && !userDefinedFunctionInProject(project), |
| Convention.NONE, out, "JdbcProjectRule") |
| .withRuleFactory(JdbcProjectRule::new) |
| .toRule(JdbcProjectRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcProjectRule(Config config) { |
| super(config); |
| } |
| |
| private static boolean userDefinedFunctionInProject(Project project) { |
| CheckingUserDefinedFunctionVisitor visitor = new CheckingUserDefinedFunctionVisitor(); |
| for (RexNode node : project.getProjects()) { |
| node.accept(visitor); |
| if (visitor.containsUserDefinedFunction()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override public boolean matches(RelOptRuleCall call) { |
| Project project = call.rel(0); |
| return project.getVariablesSet().isEmpty(); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| final Project project = (Project) rel; |
| |
| return new JdbcProject( |
| rel.getCluster(), |
| rel.getTraitSet().replace(out), |
| convert( |
| project.getInput(), |
| project.getInput().getTraitSet().replace(out)), |
| project.getProjects(), |
| project.getRowType()); |
| } |
| } |
| |
| /** Implementation of {@link org.apache.calcite.rel.core.Project} in |
| * {@link JdbcConvention jdbc calling convention}. */ |
| public static class JdbcProject |
| extends Project |
| implements JdbcRel { |
| public JdbcProject( |
| RelOptCluster cluster, |
| RelTraitSet traitSet, |
| RelNode input, |
| List<? extends RexNode> projects, |
| RelDataType rowType) { |
| super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, ImmutableSet.of()); |
| assert getConvention() instanceof JdbcConvention; |
| } |
| |
| @Deprecated // to be removed before 2.0 |
| public JdbcProject(RelOptCluster cluster, RelTraitSet traitSet, |
| RelNode input, List<RexNode> projects, RelDataType rowType, int flags) { |
| this(cluster, traitSet, input, projects, rowType); |
| Util.discard(flags); |
| } |
| |
| @Override public JdbcProject copy(RelTraitSet traitSet, RelNode input, |
| List<RexNode> projects, RelDataType rowType) { |
| return new JdbcProject(getCluster(), traitSet, input, projects, rowType); |
| } |
| |
| @Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, |
| RelMetadataQuery mq) { |
| RelOptCost cost = super.computeSelfCost(planner, mq); |
| if (cost == null) { |
| return null; |
| } |
| return cost.multiplyBy(JdbcConvention.COST_MULTIPLIER); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link org.apache.calcite.rel.core.Filter} to |
| * an {@link org.apache.calcite.adapter.jdbc.JdbcRules.JdbcFilter}. |
| */ |
| public static class JdbcFilterRule extends JdbcConverterRule { |
| /** Creates a JdbcFilterRule. */ |
| public static JdbcFilterRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Filter.class, r -> !userDefinedFunctionInFilter(r), |
| Convention.NONE, out, "JdbcFilterRule") |
| .withRuleFactory(JdbcFilterRule::new) |
| .toRule(JdbcFilterRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcFilterRule(Config config) { |
| super(config); |
| } |
| |
| private static boolean userDefinedFunctionInFilter(Filter filter) { |
| CheckingUserDefinedFunctionVisitor visitor = new CheckingUserDefinedFunctionVisitor(); |
| filter.getCondition().accept(visitor); |
| return visitor.containsUserDefinedFunction(); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| final Filter filter = (Filter) rel; |
| |
| return new JdbcFilter( |
| rel.getCluster(), |
| rel.getTraitSet().replace(out), |
| convert(filter.getInput(), |
| filter.getInput().getTraitSet().replace(out)), |
| filter.getCondition()); |
| } |
| } |
| |
| /** Implementation of {@link org.apache.calcite.rel.core.Filter} in |
| * {@link JdbcConvention jdbc calling convention}. */ |
| public static class JdbcFilter extends Filter implements JdbcRel { |
| public JdbcFilter( |
| RelOptCluster cluster, |
| RelTraitSet traitSet, |
| RelNode input, |
| RexNode condition) { |
| super(cluster, traitSet, input, condition); |
| assert getConvention() instanceof JdbcConvention; |
| } |
| |
| @Override public JdbcFilter copy(RelTraitSet traitSet, RelNode input, |
| RexNode condition) { |
| return new JdbcFilter(getCluster(), traitSet, input, condition); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link org.apache.calcite.rel.core.Aggregate} |
| * to a {@link org.apache.calcite.adapter.jdbc.JdbcRules.JdbcAggregate}. |
| */ |
| public static class JdbcAggregateRule extends JdbcConverterRule { |
| /** Creates a JdbcAggregateRule. */ |
| public static JdbcAggregateRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Aggregate.class, Convention.NONE, out, |
| "JdbcAggregateRule") |
| .withRuleFactory(JdbcAggregateRule::new) |
| .toRule(JdbcAggregateRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcAggregateRule(Config config) { |
| super(config); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| final Aggregate agg = (Aggregate) rel; |
| if (agg.getGroupSets().size() != 1) { |
| // GROUPING SETS not supported; see |
| // [CALCITE-734] Push GROUPING SETS to underlying SQL via JDBC adapter |
| return null; |
| } |
| final RelTraitSet traitSet = |
| agg.getTraitSet().replace(out); |
| try { |
| return new JdbcAggregate(rel.getCluster(), traitSet, |
| convert(agg.getInput(), out), agg.getGroupSet(), |
| agg.getGroupSets(), agg.getAggCallList()); |
| } catch (InvalidRelException e) { |
| LOGGER.debug(e.toString()); |
| return null; |
| } |
| } |
| } |
| |
| /** Returns whether this JDBC data source can implement a given aggregate |
| * function. */ |
| private static boolean canImplement(AggregateCall aggregateCall, |
| SqlDialect sqlDialect) { |
| return sqlDialect.supportsAggregateFunction( |
| aggregateCall.getAggregation().getKind()) |
| && aggregateCall.distinctKeys == null; |
| } |
| |
| /** Aggregate operator implemented in JDBC convention. */ |
| public static class JdbcAggregate extends Aggregate implements JdbcRel { |
| public JdbcAggregate( |
| RelOptCluster cluster, |
| RelTraitSet traitSet, |
| RelNode input, |
| ImmutableBitSet groupSet, |
| @Nullable List<ImmutableBitSet> groupSets, |
| List<AggregateCall> aggCalls) |
| throws InvalidRelException { |
| super(cluster, traitSet, ImmutableList.of(), input, groupSet, groupSets, aggCalls); |
| assert getConvention() instanceof JdbcConvention; |
| assert this.groupSets.size() == 1 : "Grouping sets not supported"; |
| final SqlDialect dialect = ((JdbcConvention) getConvention()).dialect; |
| for (AggregateCall aggCall : aggCalls) { |
| if (!canImplement(aggCall, dialect)) { |
| throw new InvalidRelException("cannot implement aggregate function " |
| + aggCall); |
| } |
| if (aggCall.hasFilter() && !dialect.supportsAggregateFunctionFilter()) { |
| throw new InvalidRelException("dialect does not support aggregate " |
| + "functions FILTER clauses"); |
| } |
| } |
| } |
| |
| @Deprecated // to be removed before 2.0 |
| public JdbcAggregate(RelOptCluster cluster, RelTraitSet traitSet, |
| RelNode input, boolean indicator, ImmutableBitSet groupSet, |
| List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) |
| throws InvalidRelException { |
| this(cluster, traitSet, input, groupSet, groupSets, aggCalls); |
| checkIndicator(indicator); |
| } |
| |
| @Override public JdbcAggregate copy(RelTraitSet traitSet, RelNode input, |
| ImmutableBitSet groupSet, |
| @Nullable List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) { |
| try { |
| return new JdbcAggregate(getCluster(), traitSet, input, |
| groupSet, groupSets, aggCalls); |
| } catch (InvalidRelException e) { |
| // Semantic error not possible. Must be a bug. Convert to |
| // internal error. |
| throw new AssertionError(e); |
| } |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link org.apache.calcite.rel.core.Sort} to an |
| * {@link org.apache.calcite.adapter.jdbc.JdbcRules.JdbcSort}. |
| */ |
| public static class JdbcSortRule extends JdbcConverterRule { |
| /** Creates a JdbcSortRule. */ |
| public static JdbcSortRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Sort.class, Convention.NONE, out, "JdbcSortRule") |
| .withRuleFactory(JdbcSortRule::new) |
| .toRule(JdbcSortRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcSortRule(Config config) { |
| super(config); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| return convert((Sort) rel, true); |
| } |
| |
| /** |
| * Converts a {@code Sort} into a {@code JdbcSort}. |
| * |
| * @param sort Sort operator to convert |
| * @param convertInputTraits Whether to convert input to {@code sort}'s |
| * JDBC convention |
| * @return A new JdbcSort |
| */ |
| public RelNode convert(Sort sort, boolean convertInputTraits) { |
| final RelTraitSet traitSet = sort.getTraitSet().replace(out); |
| |
| final RelNode input; |
| if (convertInputTraits) { |
| final RelTraitSet inputTraitSet = sort.getInput().getTraitSet().replace(out); |
| input = convert(sort.getInput(), inputTraitSet); |
| } else { |
| input = sort.getInput(); |
| } |
| |
| return new JdbcSort(sort.getCluster(), traitSet, |
| input, sort.getCollation(), sort.offset, sort.fetch); |
| } |
| } |
| |
| /** Sort operator implemented in JDBC convention. */ |
| public static class JdbcSort |
| extends Sort |
| implements JdbcRel { |
| public JdbcSort( |
| RelOptCluster cluster, |
| RelTraitSet traitSet, |
| RelNode input, |
| RelCollation collation, |
| @Nullable RexNode offset, |
| @Nullable RexNode fetch) { |
| super(cluster, traitSet, input, collation, offset, fetch); |
| assert getConvention() instanceof JdbcConvention; |
| assert getConvention() == input.getConvention(); |
| } |
| |
| @Override public JdbcSort copy(RelTraitSet traitSet, RelNode newInput, |
| RelCollation newCollation, @Nullable RexNode offset, @Nullable RexNode fetch) { |
| return new JdbcSort(getCluster(), traitSet, newInput, newCollation, |
| offset, fetch); |
| } |
| |
| @Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, |
| RelMetadataQuery mq) { |
| RelOptCost cost = super.computeSelfCost(planner, mq); |
| if (cost == null) { |
| return null; |
| } |
| return cost.multiplyBy(0.9); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** |
| * Rule to convert an {@link org.apache.calcite.rel.core.Union} to a |
| * {@link org.apache.calcite.adapter.jdbc.JdbcRules.JdbcUnion}. |
| */ |
| public static class JdbcUnionRule extends JdbcConverterRule { |
| /** Creates a JdbcUnionRule. */ |
| public static JdbcUnionRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Union.class, Convention.NONE, out, "JdbcUnionRule") |
| .withRuleFactory(JdbcUnionRule::new) |
| .toRule(JdbcUnionRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcUnionRule(Config config) { |
| super(config); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| final Union union = (Union) rel; |
| final RelTraitSet traitSet = |
| union.getTraitSet().replace(out); |
| return new JdbcUnion(rel.getCluster(), traitSet, |
| convertList(union.getInputs(), out), union.all); |
| } |
| } |
| |
| /** Union operator implemented in JDBC convention. */ |
| public static class JdbcUnion extends Union implements JdbcRel { |
| public JdbcUnion( |
| RelOptCluster cluster, |
| RelTraitSet traitSet, |
| List<RelNode> inputs, |
| boolean all) { |
| super(cluster, traitSet, inputs, all); |
| } |
| |
| @Override public JdbcUnion copy( |
| RelTraitSet traitSet, List<RelNode> inputs, boolean all) { |
| return new JdbcUnion(getCluster(), traitSet, inputs, all); |
| } |
| |
| @Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, |
| RelMetadataQuery mq) { |
| RelOptCost cost = super.computeSelfCost(planner, mq); |
| if (cost == null) { |
| return null; |
| } |
| return cost.multiplyBy(.1); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link org.apache.calcite.rel.core.Intersect} |
| * to a {@link org.apache.calcite.adapter.jdbc.JdbcRules.JdbcIntersect}. |
| */ |
| public static class JdbcIntersectRule extends JdbcConverterRule { |
| /** Creates a JdbcIntersectRule. */ |
| public static JdbcIntersectRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Intersect.class, Convention.NONE, out, |
| "JdbcIntersectRule") |
| .withRuleFactory(JdbcIntersectRule::new) |
| .toRule(JdbcIntersectRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcIntersectRule(Config config) { |
| super(config); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| final Intersect intersect = (Intersect) rel; |
| if (intersect.all) { |
| return null; // INTERSECT ALL not implemented |
| } |
| final RelTraitSet traitSet = |
| intersect.getTraitSet().replace(out); |
| return new JdbcIntersect(rel.getCluster(), traitSet, |
| convertList(intersect.getInputs(), out), false); |
| } |
| } |
| |
| /** Intersect operator implemented in JDBC convention. */ |
| public static class JdbcIntersect |
| extends Intersect |
| implements JdbcRel { |
| public JdbcIntersect( |
| RelOptCluster cluster, |
| RelTraitSet traitSet, |
| List<RelNode> inputs, |
| boolean all) { |
| super(cluster, traitSet, inputs, all); |
| assert !all; |
| } |
| |
| @Override public JdbcIntersect copy( |
| RelTraitSet traitSet, List<RelNode> inputs, boolean all) { |
| return new JdbcIntersect(getCluster(), traitSet, inputs, all); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** |
| * Rule to convert a {@link org.apache.calcite.rel.core.Minus} to a |
| * {@link org.apache.calcite.adapter.jdbc.JdbcRules.JdbcMinus}. |
| */ |
| public static class JdbcMinusRule extends JdbcConverterRule { |
| /** Creates a JdbcMinusRule. */ |
| public static JdbcMinusRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Minus.class, Convention.NONE, out, "JdbcMinusRule") |
| .withRuleFactory(JdbcMinusRule::new) |
| .toRule(JdbcMinusRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcMinusRule(Config config) { |
| super(config); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| final Minus minus = (Minus) rel; |
| if (minus.all) { |
| return null; // EXCEPT ALL not implemented |
| } |
| final RelTraitSet traitSet = |
| rel.getTraitSet().replace(out); |
| return new JdbcMinus(rel.getCluster(), traitSet, |
| convertList(minus.getInputs(), out), false); |
| } |
| } |
| |
| /** Minus operator implemented in JDBC convention. */ |
| public static class JdbcMinus extends Minus implements JdbcRel { |
| public JdbcMinus(RelOptCluster cluster, RelTraitSet traitSet, |
| List<RelNode> inputs, boolean all) { |
| super(cluster, traitSet, inputs, all); |
| assert !all; |
| } |
| |
| @Override public JdbcMinus copy(RelTraitSet traitSet, List<RelNode> inputs, |
| boolean all) { |
| return new JdbcMinus(getCluster(), traitSet, inputs, all); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** Rule that converts a table-modification to JDBC. */ |
| public static class JdbcTableModificationRule extends JdbcConverterRule { |
| /** Creates a JdbcToEnumerableConverterRule. */ |
| public static JdbcTableModificationRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(TableModify.class, Convention.NONE, out, |
| "JdbcTableModificationRule") |
| .withRuleFactory(JdbcTableModificationRule::new) |
| .toRule(JdbcTableModificationRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcTableModificationRule(Config config) { |
| super(config); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| final TableModify modify = |
| (TableModify) rel; |
| final ModifiableTable modifiableTable = |
| modify.getTable().unwrap(ModifiableTable.class); |
| if (modifiableTable == null) { |
| return null; |
| } |
| final RelTraitSet traitSet = |
| modify.getTraitSet().replace(out); |
| return new JdbcTableModify( |
| modify.getCluster(), traitSet, |
| modify.getTable(), |
| modify.getCatalogReader(), |
| convert(modify.getInput(), traitSet), |
| modify.getOperation(), |
| modify.getUpdateColumnList(), |
| modify.getSourceExpressionList(), |
| modify.isFlattened()); |
| } |
| } |
| |
| /** Table-modification operator implemented in JDBC convention. */ |
| public static class JdbcTableModify extends TableModify implements JdbcRel { |
| public JdbcTableModify(RelOptCluster cluster, |
| RelTraitSet traitSet, |
| RelOptTable table, |
| Prepare.CatalogReader catalogReader, |
| RelNode input, |
| Operation operation, |
| @Nullable List<String> updateColumnList, |
| @Nullable List<RexNode> sourceExpressionList, |
| boolean flattened) { |
| super(cluster, traitSet, table, catalogReader, input, operation, |
| updateColumnList, sourceExpressionList, flattened); |
| assert input.getConvention() instanceof JdbcConvention; |
| assert getConvention() instanceof JdbcConvention; |
| final ModifiableTable modifiableTable = |
| table.unwrap(ModifiableTable.class); |
| if (modifiableTable == null) { |
| throw new AssertionError(); // TODO: user error in validator |
| } |
| Expression expression = table.getExpression(Queryable.class); |
| if (expression == null) { |
| throw new AssertionError(); // TODO: user error in validator |
| } |
| } |
| |
| @Override public @Nullable RelOptCost computeSelfCost(RelOptPlanner planner, |
| RelMetadataQuery mq) { |
| RelOptCost cost = super.computeSelfCost(planner, mq); |
| if (cost == null) { |
| return null; |
| } |
| return cost.multiplyBy(.1); |
| } |
| |
| @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { |
| return new JdbcTableModify( |
| getCluster(), traitSet, getTable(), getCatalogReader(), |
| sole(inputs), getOperation(), getUpdateColumnList(), |
| getSourceExpressionList(), isFlattened()); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** Rule that converts a values operator to JDBC. */ |
| public static class JdbcValuesRule extends JdbcConverterRule { |
| /** Creates a JdbcValuesRule. */ |
| public static JdbcValuesRule create(JdbcConvention out) { |
| return Config.INSTANCE |
| .withConversion(Values.class, Convention.NONE, out, "JdbcValuesRule") |
| .withRuleFactory(JdbcValuesRule::new) |
| .toRule(JdbcValuesRule.class); |
| } |
| |
| /** Called from the Config. */ |
| protected JdbcValuesRule(Config config) { |
| super(config); |
| } |
| |
| @Override public @Nullable RelNode convert(RelNode rel) { |
| Values values = (Values) rel; |
| return new JdbcValues(values.getCluster(), values.getRowType(), |
| values.getTuples(), values.getTraitSet().replace(out)); |
| } |
| } |
| |
| /** Values operator implemented in JDBC convention. */ |
| public static class JdbcValues extends Values implements JdbcRel { |
| JdbcValues(RelOptCluster cluster, RelDataType rowType, |
| ImmutableList<ImmutableList<RexLiteral>> tuples, RelTraitSet traitSet) { |
| super(cluster, rowType, tuples, traitSet); |
| } |
| |
| @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) { |
| assert inputs.isEmpty(); |
| return new JdbcValues(getCluster(), getRowType(), tuples, traitSet); |
| } |
| |
| @Override public JdbcImplementor.Result implement(JdbcImplementor implementor) { |
| return implementor.implement(this); |
| } |
| } |
| |
| /** Visitor that checks whether part of a projection is a user-defined |
| * function (UDF). */ |
| private static class CheckingUserDefinedFunctionVisitor |
| extends RexVisitorImpl<Void> { |
| |
| private boolean containsUsedDefinedFunction = false; |
| |
| CheckingUserDefinedFunctionVisitor() { |
| super(true); |
| } |
| |
| public boolean containsUserDefinedFunction() { |
| return containsUsedDefinedFunction; |
| } |
| |
| @Override public Void visitCall(RexCall call) { |
| SqlOperator operator = call.getOperator(); |
| if (operator instanceof SqlFunction |
| && ((SqlFunction) operator).getFunctionType().isUserDefined()) { |
| containsUsedDefinedFunction |= true; |
| } |
| return super.visitCall(call); |
| } |
| |
| } |
| |
| } |