blob: c8a56e2e9f2e1aa617ee5d35d9c9ab8f70817d58 [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.calcite.rel.metadata;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.Calc;
import org.apache.calcite.rel.core.Exchange;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.SetOp;
import org.apache.calcite.rel.core.Sort;
import org.apache.calcite.rel.core.TableFunctionScan;
import org.apache.calcite.rel.core.TableModify;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLocalRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.rex.RexVisitor;
import org.apache.calcite.rex.RexVisitorImpl;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* RelMdColumnOrigins supplies a default implementation of
* {@link RelMetadataQuery#getColumnOrigins} for the standard logical algebra.
*/
public class RelMdColumnOrigins
implements MetadataHandler<BuiltInMetadata.ColumnOrigin> {
public static final RelMetadataProvider SOURCE =
ReflectiveRelMetadataProvider.reflectiveSource(
new RelMdColumnOrigins(), BuiltInMetadata.ColumnOrigin.Handler.class);
//~ Constructors -----------------------------------------------------------
private RelMdColumnOrigins() {}
//~ Methods ----------------------------------------------------------------
@Override public MetadataDef<BuiltInMetadata.ColumnOrigin> getDef() {
return BuiltInMetadata.ColumnOrigin.DEF;
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(Aggregate rel,
RelMetadataQuery mq, int iOutputColumn) {
if (iOutputColumn < rel.getGroupCount()) {
// get actual index of Group columns.
return mq.getColumnOrigins(rel.getInput(), rel.getGroupSet().asList().get(iOutputColumn));
}
// Aggregate columns are derived from input columns
AggregateCall call =
rel.getAggCallList().get(iOutputColumn
- rel.getGroupCount());
final Set<RelColumnOrigin> set = new HashSet<>();
for (Integer iInput : call.getArgList()) {
Set<RelColumnOrigin> inputSet =
mq.getColumnOrigins(rel.getInput(), iInput);
inputSet = createDerivedColumnOrigins(inputSet);
if (inputSet != null) {
set.addAll(inputSet);
}
}
return set;
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(Join rel, RelMetadataQuery mq,
int iOutputColumn) {
int nLeftColumns = rel.getLeft().getRowType().getFieldList().size();
Set<RelColumnOrigin> set;
boolean derived = false;
if (iOutputColumn < nLeftColumns) {
set = mq.getColumnOrigins(rel.getLeft(), iOutputColumn);
if (rel.getJoinType().generatesNullsOnLeft()) {
derived = true;
}
} else {
set = mq.getColumnOrigins(rel.getRight(), iOutputColumn - nLeftColumns);
if (rel.getJoinType().generatesNullsOnRight()) {
derived = true;
}
}
if (derived) {
// nulls are generated due to outer join; that counts
// as derivation
set = createDerivedColumnOrigins(set);
}
return set;
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(SetOp rel,
RelMetadataQuery mq, int iOutputColumn) {
final Set<RelColumnOrigin> set = new HashSet<>();
for (RelNode input : rel.getInputs()) {
Set<RelColumnOrigin> inputSet = mq.getColumnOrigins(input, iOutputColumn);
if (inputSet == null) {
return null;
}
set.addAll(inputSet);
}
return set;
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(Project rel,
final RelMetadataQuery mq, int iOutputColumn) {
final RelNode input = rel.getInput();
RexNode rexNode = rel.getProjects().get(iOutputColumn);
if (rexNode instanceof RexInputRef) {
// Direct reference: no derivation added.
RexInputRef inputRef = (RexInputRef) rexNode;
return mq.getColumnOrigins(input, inputRef.getIndex());
}
// Anything else is a derivation, possibly from multiple columns.
final Set<RelColumnOrigin> set = getMultipleColumns(rexNode, input, mq);
return createDerivedColumnOrigins(set);
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(Calc rel,
final RelMetadataQuery mq, int iOutputColumn) {
final RelNode input = rel.getInput();
final RexShuttle rexShuttle = new RexShuttle() {
@Override public RexNode visitLocalRef(RexLocalRef localRef) {
return rel.getProgram().expandLocalRef(localRef);
}
};
final List<RexNode> projects = new ArrayList<>();
for (RexNode rex: rexShuttle.apply(rel.getProgram().getProjectList())) {
projects.add(rex);
}
final RexNode rexNode = projects.get(iOutputColumn);
if (rexNode instanceof RexInputRef) {
// Direct reference: no derivation added.
RexInputRef inputRef = (RexInputRef) rexNode;
return mq.getColumnOrigins(input, inputRef.getIndex());
}
// Anything else is a derivation, possibly from multiple columns.
final Set<RelColumnOrigin> set = getMultipleColumns(rexNode, input, mq);
return createDerivedColumnOrigins(set);
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(Filter rel,
RelMetadataQuery mq, int iOutputColumn) {
return mq.getColumnOrigins(rel.getInput(), iOutputColumn);
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(Sort rel, RelMetadataQuery mq,
int iOutputColumn) {
return mq.getColumnOrigins(rel.getInput(), iOutputColumn);
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(TableModify rel, RelMetadataQuery mq,
int iOutputColumn) {
return mq.getColumnOrigins(rel.getInput(), iOutputColumn);
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(Exchange rel,
RelMetadataQuery mq, int iOutputColumn) {
return mq.getColumnOrigins(rel.getInput(), iOutputColumn);
}
public @Nullable Set<RelColumnOrigin> getColumnOrigins(TableFunctionScan rel,
RelMetadataQuery mq, int iOutputColumn) {
final Set<RelColumnOrigin> set = new HashSet<>();
Set<RelColumnMapping> mappings = rel.getColumnMappings();
if (mappings == null) {
if (rel.getInputs().size() > 0) {
// This is a non-leaf transformation: say we don't
// know about origins, because there are probably
// columns below.
return null;
} else {
// This is a leaf transformation: say there are fer sure no
// column origins.
return set;
}
}
for (RelColumnMapping mapping : mappings) {
if (mapping.iOutputColumn != iOutputColumn) {
continue;
}
final RelNode input = rel.getInputs().get(mapping.iInputRel);
final int column = mapping.iInputColumn;
Set<RelColumnOrigin> origins = mq.getColumnOrigins(input, column);
if (origins == null) {
return null;
}
if (mapping.derived) {
origins = createDerivedColumnOrigins(origins);
}
set.addAll(origins);
}
return set;
}
// Catch-all rule when none of the others apply.
public @Nullable Set<RelColumnOrigin> getColumnOrigins(RelNode rel,
RelMetadataQuery mq, int iOutputColumn) {
// NOTE jvs 28-Mar-2006: We may get this wrong for a physical table
// expression which supports projections. In that case,
// it's up to the plugin writer to override with the
// correct information.
if (rel.getInputs().size() > 0) {
// No generic logic available for non-leaf rels.
return null;
}
final Set<RelColumnOrigin> set = new HashSet<>();
RelOptTable table = rel.getTable();
if (table == null) {
// Somebody is making column values up out of thin air, like a
// VALUES clause, so we return an empty set.
return set;
}
// Detect the case where a physical table expression is performing
// projection, and say we don't know instead of making any assumptions.
// (Theoretically we could try to map the projection using column
// names.) This detection assumes the table expression doesn't handle
// rename as well.
if (table.getRowType() != rel.getRowType()) {
return null;
}
set.add(new RelColumnOrigin(table, iOutputColumn, false));
return set;
}
private static @PolyNull Set<RelColumnOrigin> createDerivedColumnOrigins(
@PolyNull Set<RelColumnOrigin> inputSet) {
if (inputSet == null) {
return null;
}
final Set<RelColumnOrigin> set = new HashSet<>();
for (RelColumnOrigin rco : inputSet) {
RelColumnOrigin derived =
new RelColumnOrigin(
rco.getOriginTable(),
rco.getOriginColumnOrdinal(),
true);
set.add(derived);
}
return set;
}
private static Set<RelColumnOrigin> getMultipleColumns(RexNode rexNode, RelNode input,
final RelMetadataQuery mq) {
final Set<RelColumnOrigin> set = new HashSet<>();
final RexVisitor<Void> visitor =
new RexVisitorImpl<Void>(true) {
@Override public Void visitInputRef(RexInputRef inputRef) {
Set<RelColumnOrigin> inputSet =
mq.getColumnOrigins(input, inputRef.getIndex());
if (inputSet != null) {
set.addAll(inputSet);
}
return null;
}
};
rexNode.accept(visitor);
return set;
}
}