in core/src/main/java/org/apache/calcite/tools/RelBuilder.java [2080:2243]
private RelBuilder project_(
Iterable<? extends RexNode> nodes,
Iterable<? extends @Nullable String> fieldNames,
Iterable<RelHint> hints,
boolean force,
Iterable<CorrelationId> variablesSet) {
final Frame frame = requireNonNull(peek_(), "frame stack is empty");
final RelDataType inputRowType = frame.rel.getRowType();
final List<RexNode> nodeList = Lists.newArrayList(nodes);
final Set<CorrelationId> variables = ImmutableSet.copyOf(variablesSet);
// Perform a quick check for identity. We'll do a deeper check
// later when we've derived column names.
if (!force && Iterables.isEmpty(fieldNames)
&& RexUtil.isIdentity(nodeList, inputRowType)) {
return this;
}
final List<@Nullable String> fieldNameList = Lists.newArrayList(fieldNames);
while (fieldNameList.size() < nodeList.size()) {
fieldNameList.add(null);
}
// Do not merge projection when top projection has correlation variables
bloat:
if (frame.rel instanceof Project
&& config.bloat() >= 0
&& variables.isEmpty()) {
final Project project = (Project) frame.rel;
// Populate field names. If the upper expression is an input ref and does
// not have a recommended name, use the name of the underlying field.
for (int i = 0; i < fieldNameList.size(); i++) {
if (fieldNameList.get(i) == null) {
final RexNode node = nodeList.get(i);
if (node instanceof RexInputRef) {
final RexInputRef ref = (RexInputRef) node;
fieldNameList.set(i,
project.getRowType().getFieldNames().get(ref.getIndex()));
}
}
}
final List<RexNode> newNodes =
RelOptUtil.pushPastProjectUnlessBloat(nodeList, project,
config.bloat());
if (newNodes == null) {
// The merged expression is more complex than the input expressions.
// Do not merge.
break bloat;
}
// Carefully build a list of fields, so that table aliases from the input
// can be seen for fields that are based on a RexInputRef.
final Frame frame1 = stack.pop();
final PairList<ImmutableSet<String>, RelDataTypeField> fields =
PairList.of();
project.getInput().getRowType().getFieldList()
.forEach(f -> fields.add(ImmutableSet.of(), f));
for (Pair<RexNode, ImmutableSet<String>> pair
: Pair.zip(project.getProjects(), frame1.fields.leftList())) {
switch (pair.left.getKind()) {
case INPUT_REF:
final int i = ((RexInputRef) pair.left).getIndex();
fields.set(i, pair.right, fields.right(i));
break;
default:
break;
}
}
stack.push(new Frame(project.getInput(), fields));
final ImmutableSet.Builder<RelHint> mergedHints = ImmutableSet.builder();
mergedHints.addAll(project.getHints());
mergedHints.addAll(hints);
// Keep bottom projection's variablesSet.
return project_(newNodes, fieldNameList, mergedHints.build(), force,
ImmutableSet.copyOf(project.getVariablesSet()));
}
// Simplify expressions.
if (config.simplify()) {
nodeList.replaceAll(e -> simplifier.simplifyPreservingType(e));
}
// Replace null names with generated aliases.
for (int i = 0; i < fieldNameList.size(); i++) {
if (fieldNameList.get(i) == null) {
fieldNameList.set(i, inferAlias(nodeList, nodeList.get(i), i));
}
}
final PairList<ImmutableSet<String>, RelDataTypeField> fields =
PairList.of();
final Set<String> uniqueNameList =
getTypeFactory().getTypeSystem().isSchemaCaseSensitive()
? new HashSet<>()
: new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
// calculate final names and build field list
for (int i = 0; i < fieldNameList.size(); ++i) {
final RexNode node = nodeList.get(i);
String name = fieldNameList.get(i);
String originalName = name;
if (name == null || uniqueNameList.contains(name)) {
int j = 0;
if (name == null) {
j = i;
}
do {
name = SqlValidatorUtil.F_SUGGESTER.apply(originalName, j, j++);
} while (uniqueNameList.contains(name));
fieldNameList.set(i, name);
}
RelDataTypeField fieldType =
new RelDataTypeFieldImpl(name, i, node.getType());
switch (node.getKind()) {
case INPUT_REF:
// preserve rel aliases for INPUT_REF fields
final int index = ((RexInputRef) node).getIndex();
fields.add(frame.fields.left(index), fieldType);
break;
default:
fields.add(ImmutableSet.of(), fieldType);
break;
}
uniqueNameList.add(name);
}
if (!force && RexUtil.isIdentity(nodeList, inputRowType)) {
if (fieldNameList.equals(inputRowType.getFieldNames())) {
// Do not create an identity project if it does not rename any fields
return this;
} else {
// create "virtual" row type for project only rename fields
stack.pop();
// Ignore the hints.
stack.push(new Frame(frame.rel, fields));
}
return this;
}
// If the expressions are all literals, and the input is a Values with N
// rows (or an Aggregate with 1 row), replace with a Values with same tuple
// N times.
final int rowCount;
if (config.simplifyValues()
&& nodeList.stream().allMatch(e -> e instanceof RexLiteral)
&& (rowCount = fixedRowCount(frame)) >= 0) {
RelNode unused = build();
final RelDataTypeFactory.Builder typeBuilder = getTypeFactory().builder();
Pair.forEach(fieldNameList, nodeList, (name, expr) ->
typeBuilder.add(requireNonNull(name, "name"), expr.getType()));
@SuppressWarnings({"unchecked", "rawtypes"})
final List<RexLiteral> tuple = (List<RexLiteral>) (List) nodeList;
return values(Collections.nCopies(rowCount, tuple),
typeBuilder.build());
}
final RelNode project =
struct.projectFactory.createProject(frame.rel,
ImmutableList.copyOf(hints),
ImmutableList.copyOf(nodeList),
fieldNameList,
variables);
stack.pop();
stack.push(new Frame(project, fields));
return this;
}