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

org.apache.flink.table.planner.plan.rules.logical.PushProjectIntoLegacyTableSourceScanRule.scala Maven / Gradle / Ivy

/*
 * 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.flink.table.planner.plan.rules.logical

import org.apache.flink.table.planner.plan.schema.LegacyTableSourceTable
import org.apache.flink.table.planner.plan.utils._
import org.apache.flink.table.sources._
import org.apache.flink.util.CollectionUtil

import org.apache.calcite.plan.RelOptRule.{none, operand}
import org.apache.calcite.plan.{RelOptRule, RelOptRuleCall}
import org.apache.calcite.rel.logical.{LogicalProject, LogicalTableScan}
import org.apache.calcite.rel.rules.ProjectRemoveRule
import org.apache.flink.table.api.TableException

/**
  * Planner rule that pushes a [[LogicalProject]] into a [[LogicalTableScan]]
  * which wraps a [[ProjectableTableSource]] or a [[NestedFieldsProjectableTableSource]].
  */
class PushProjectIntoLegacyTableSourceScanRule extends RelOptRule(
  operand(classOf[LogicalProject],
    operand(classOf[LogicalTableScan], none)),
  "PushProjectIntoLegacyTableSourceScanRule") {

  override def matches(call: RelOptRuleCall): Boolean = {
    val scan: LogicalTableScan = call.rel(1)
    scan.getTable.unwrap(classOf[LegacyTableSourceTable[_]]) match {
      case table: LegacyTableSourceTable[_] =>
        table.tableSource match {
          // projection pushdown is not supported for sources that provide time indicators
          case r: DefinedRowtimeAttributes if !CollectionUtil.isNullOrEmpty(
            r.getRowtimeAttributeDescriptors) => false
          case p: DefinedProctimeAttribute if p.getProctimeAttribute != null => false
          case _: ProjectableTableSource[_] => true
          case _: NestedFieldsProjectableTableSource[_] => true
          case _ => false
        }
      case _ => false
    }
  }

  override def onMatch(call: RelOptRuleCall) {
    val project: LogicalProject = call.rel(0)
    val scan: LogicalTableScan = call.rel(1)

    val usedFields = RexNodeExtractor.extractRefInputFields(project.getProjects)
    // if no fields can be projected, we keep the original plan.
    if (scan.getRowType.getFieldCount == usedFields.length) {
      return
    }

    val tableSourceTable = scan.getTable.unwrap(classOf[LegacyTableSourceTable[_]])
    val oldTableSource = tableSourceTable.tableSource
    val (newTableSource, isProjectSuccess) = oldTableSource match {
      case nested: NestedFieldsProjectableTableSource[_] =>
        val nestedFields = RexNodeExtractor.extractRefNestedInputFields(
          project.getProjects, usedFields)
        (nested.projectNestedFields(usedFields,
          nestedFields.toStream.map(
            row => row.toStream.map(field => String.join(".", field)).toArray)
            .toArray), true)
      case projecting: ProjectableTableSource[_] =>
        (projecting.projectFields(usedFields), true)
      case nonProjecting: TableSource[_] =>
        // projection cannot be pushed to TableSource
        (nonProjecting, false)
    }

    if (isProjectSuccess
      && newTableSource.explainSource().equals(oldTableSource.explainSource())) {
      throw new TableException("Failed to push project into table source! "
        + "table source with pushdown capability must override and change "
        + "explainSource() API to explain the pushdown applied!")
    }

    // check that table schema of the new table source is identical to original
    if (oldTableSource.getTableSchema != newTableSource.getTableSchema) {
      throw new TableException("TableSchema of ProjectableTableSource must not be modified " +
        "by projectFields() call. This is a bug in the implementation of the TableSource " +
        s"${oldTableSource.getClass.getCanonicalName}.")
    }

    // project push down does not change the statistic, we can reuse origin statistic
    val newTableSourceTable = tableSourceTable.copy(
      newTableSource,
      usedFields)
    // row type is changed after project push down
    val newScan = new LogicalTableScan(scan.getCluster, scan.getTraitSet, newTableSourceTable)

    // rewrite input field in projections
    val newProjects = RexNodeRewriter.rewriteWithNewFieldInput(project.getProjects, usedFields)
    val newProject = project.copy(
      project.getTraitSet,
      newScan,
      newProjects,
      project.getRowType)

    if (ProjectRemoveRule.isTrivial(newProject)) {
      // drop project if the transformed program merely returns its input
      call.transformTo(newScan)
    } else {
      call.transformTo(newProject)
    }
  }
}

object PushProjectIntoLegacyTableSourceScanRule {
  val INSTANCE: RelOptRule = new PushProjectIntoLegacyTableSourceScanRule
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy