All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.druid.sql.calcite.planner.RelParameterizerShuttle Maven / Gradle / Ivy

There is a newer version: 31.0.0
Show newest version
/*
 * 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 org.apache.calcite.avatica.remote.TypedValue;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelShuttle;
import org.apache.calcite.rel.RelVisitor;
import org.apache.calcite.rel.core.TableFunctionScan;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalCalc;
import org.apache.calcite.rel.logical.LogicalCorrelate;
import org.apache.calcite.rel.logical.LogicalExchange;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalIntersect;
import org.apache.calcite.rel.logical.LogicalJoin;
import org.apache.calcite.rel.logical.LogicalMatch;
import org.apache.calcite.rel.logical.LogicalMinus;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalSort;
import org.apache.calcite.rel.logical.LogicalTableModify;
import org.apache.calcite.rel.logical.LogicalUnion;
import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexDynamicParam;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.druid.error.DruidException;
import org.apache.druid.error.InvalidSqlInput;

/**
 * Traverse {@link RelNode} tree and replaces all {@link RexDynamicParam} with {@link org.apache.calcite.rex.RexLiteral}
 * using {@link RexBuilder} if a value binding exists for the parameter. All parameters must have a value by the time
 * {@link RelParameterizerShuttle} is run, or else it will throw an exception.
 *
 * Note: none of the tests currently hit this anymore since {@link SqlParameterizerShuttle} has been modified to handle
 * most common jdbc types, but leaving this here provides a safety net to try again to convert parameters
 * to literal values in case {@link SqlParameterizerShuttle} fails.
 */
public class RelParameterizerShuttle implements RelShuttle
{
  private final PlannerContext plannerContext;

  public RelParameterizerShuttle(PlannerContext plannerContext)
  {
    this.plannerContext = plannerContext;
  }

  @Override
  public RelNode visit(TableScan scan)
  {
    return bindRel(scan);
  }

  @Override
  public RelNode visit(TableFunctionScan scan)
  {
    return bindRel(scan);
  }

  @Override
  public RelNode visit(LogicalValues values)
  {
    return bindRel(values);
  }

  @Override
  public RelNode visit(LogicalFilter filter)
  {
    return bindRel(filter);
  }

  @Override
  public RelNode visit(LogicalProject project)
  {
    return bindRel(project);
  }

  @Override
  public RelNode visit(LogicalJoin join)
  {
    return bindRel(join);
  }

  @Override
  public RelNode visit(LogicalCorrelate correlate)
  {
    return bindRel(correlate);
  }

  @Override
  public RelNode visit(LogicalUnion union)
  {
    return bindRel(union);
  }

  @Override
  public RelNode visit(LogicalIntersect intersect)
  {
    return bindRel(intersect);
  }

  @Override
  public RelNode visit(LogicalMinus minus)
  {
    return bindRel(minus);
  }

  @Override
  public RelNode visit(LogicalAggregate aggregate)
  {
    return bindRel(aggregate);
  }

  @Override
  public RelNode visit(LogicalMatch match)
  {
    return bindRel(match);
  }

  @Override
  public RelNode visit(LogicalSort sort)
  {
    final RexBuilder builder = sort.getCluster().getRexBuilder();
    final RelDataTypeFactory typeFactory = sort.getCluster().getTypeFactory();
    RexNode newFetch = bind(sort.fetch, builder, typeFactory);
    RexNode newOffset = bind(sort.offset, builder, typeFactory);
    sort = (LogicalSort) sort.copy(sort.getTraitSet(), sort.getInput(), sort.getCollation(), newOffset, newFetch);
    return bindRel(sort, builder, typeFactory);
  }

  @Override
  public RelNode visit(LogicalExchange exchange)
  {
    return bindRel(exchange);
  }

  @Override
  public RelNode visit(LogicalCalc calc)
  {
    return bindRel(calc);
  }

  @Override
  public RelNode visit(LogicalTableModify modify)
  {
    return bindRel(modify);
  }

  @Override
  public RelNode visit(RelNode other)
  {
    return bindRel(other);
  }

  private RelNode bindRel(RelNode node)
  {
    final RexBuilder builder = node.getCluster().getRexBuilder();
    final RelDataTypeFactory typeFactory = node.getCluster().getTypeFactory();
    return bindRel(node, builder, typeFactory);
  }

  private RelNode bindRel(RelNode node, RexBuilder builder, RelDataTypeFactory typeFactory)
  {
    final RexShuttle binder = new RexShuttle()
    {
      @Override
      public RexNode visitDynamicParam(RexDynamicParam dynamicParam)
      {
        return bind(dynamicParam, builder, typeFactory);
      }
    };
    node = node.accept(binder);
    node.childrenAccept(new RelVisitor()
    {
      @Override
      public void visit(RelNode node, int ordinal, RelNode parent)
      {
        super.visit(node, ordinal, parent);
        RelNode transformed = node.accept(binder);
        if (!node.equals(transformed)) {
          parent.replaceInput(ordinal, transformed);
        }
      }
    });
    return node;
  }

  private RexNode bind(RexNode node, RexBuilder builder, RelDataTypeFactory typeFactory)
  {
    if (node instanceof RexDynamicParam) {
      RexDynamicParam dynamicParam = (RexDynamicParam) node;
      // if we have a value for dynamic parameter, replace with a literal, else add to list of unbound parameters
      if (plannerContext.getParameters().size() > dynamicParam.getIndex()) {
        TypedValue param = plannerContext.getParameters().get(dynamicParam.getIndex());
        if (param == null) {
          throw unbound(dynamicParam);
        }
        if (param.value == null) {
          return builder.makeNullLiteral(typeFactory.createSqlType(SqlTypeName.NULL));
        }
        SqlTypeName typeName = SqlTypeName.getNameForJdbcType(param.type.typeId);
        return builder.makeLiteral(
            param.value,
            typeFactory.createSqlType(typeName),
            true
        );
      } else {
        throw unbound(dynamicParam);
      }
    }
    return node;
  }

  private static DruidException unbound(RexDynamicParam dynamicParam)
  {
    return InvalidSqlInput.exception("No value bound for parameter (position [%s])", dynamicParam.getIndex() + 1);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy