Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions core/src/main/java/org/apache/calcite/rel/core/Intersect.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.util.Util;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -79,4 +82,12 @@ protected Intersect(RelInput input) {
dRows *= 0.25;
return dRows;
}

@Override protected RelDataType deriveRowType() {
// An output column is only nullable if it is nullable in ALL the inputs.
return ReturnTypes.refineNullabilityForIntersect(
getCluster().getTypeFactory(),
deriveLeastRestrictiveRowType(),
Util.transform(getInputs(), RelNode::getRowType));
}
}
10 changes: 10 additions & 0 deletions core/src/main/java/org/apache/calcite/rel/core/Minus.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.metadata.RelMdUtil;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.type.ReturnTypes;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -60,4 +62,12 @@ protected Minus(RelInput input) {
@Override public double estimateRowCount(RelMetadataQuery mq) {
return RelMdUtil.getMinusRowCount(mq, this);
}

@Override protected RelDataType deriveRowType() {
// The nullability of the output columns is the same as that of the primary input.
return ReturnTypes.refineNullabilityForExcept(
getCluster().getTypeFactory(),
deriveLeastRestrictiveRowType(),
getInput(0).getRowType());
}
}
4 changes: 4 additions & 0 deletions core/src/main/java/org/apache/calcite/rel/core/SetOp.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ public abstract SetOp copy(
}

@Override protected RelDataType deriveRowType() {
return deriveLeastRestrictiveRowType();
}

protected RelDataType deriveLeastRestrictiveRowType() {
final List<RelDataType> inputRowTypes =
Util.transform(inputs, RelNode::getRowType);
final RelDataType rowType =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ public void onMatchAggregateOnUnion(RelOptRuleCall call) {

// Project all but the last added field (e.g. count_i{n})
relBuilder.project(skipLast(relBuilder.fields(), branchCount));

// ensure the nullabilities of columns in the new relation match those of the input relation
relBuilder.convert(intersect.getRowType(), false);

call.transformTo(relBuilder.build());
}

Expand Down Expand Up @@ -206,6 +210,9 @@ public void onMatchAggregatePushdown(RelOptRuleCall call) {
// Project all but the last field
relBuilder.project(Util.skipLast(relBuilder.fields()));

// ensure the nullabilities of columns in the new relation match those of the input relation
relBuilder.convert(intersect.getRowType(), false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that whether onMatchAggregateOnUnion need this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it would be safer that way.


// the schema for intersect distinct matches that of the relation,
// built here with an extra last column for the count,
// which is projected out by the final project we added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ public MinusToDistinctRule(Class<? extends Minus> minusClass,

relBuilder.filter(filters.build());
relBuilder.project(Util.first(relBuilder.fields(), originalFieldCnt));

// ensure the nullabilities of columns in the new relation match those of the minus output
relBuilder.convert(minus.getRowType(), false);

call.transformTo(relBuilder.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;

import org.checkerframework.checker.nullness.qual.Nullable;

/**
* SqlSetOperator represents a relational set theory operator (UNION, INTERSECT,
* MINUS). These are binary operators, but with an extra boolean attribute
Expand Down Expand Up @@ -59,7 +61,7 @@ public SqlSetOperator(
int prec,
boolean all,
SqlReturnTypeInference returnTypeInference,
SqlOperandTypeInference operandTypeInference,
@Nullable SqlOperandTypeInference operandTypeInference,
SqlOperandTypeChecker operandTypeChecker) {
super(
name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,20 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
new SqlSetOperator("UNION ALL", SqlKind.UNION, 12, true);

public static final SqlSetOperator EXCEPT =
new SqlSetOperator("EXCEPT", SqlKind.EXCEPT, 12, false);
new SqlSetOperator("EXCEPT", SqlKind.EXCEPT, 12, false,
ReturnTypes.LEAST_RESTRICTIVE_EXCEPT, null, OperandTypes.SET_OP);

public static final SqlSetOperator EXCEPT_ALL =
new SqlSetOperator("EXCEPT ALL", SqlKind.EXCEPT, 12, true);
new SqlSetOperator("EXCEPT ALL", SqlKind.EXCEPT, 12, true,
ReturnTypes.LEAST_RESTRICTIVE_EXCEPT, null, OperandTypes.SET_OP);

public static final SqlSetOperator INTERSECT =
new SqlSetOperator("INTERSECT", SqlKind.INTERSECT, 14, false);
new SqlSetOperator("INTERSECT", SqlKind.INTERSECT, 14, false,
ReturnTypes.LEAST_RESTRICTIVE_INTERSECT, null, OperandTypes.SET_OP);

public static final SqlSetOperator INTERSECT_ALL =
new SqlSetOperator("INTERSECT ALL", SqlKind.INTERSECT, 14, true);
new SqlSetOperator("INTERSECT ALL", SqlKind.INTERSECT, 14, true,
ReturnTypes.LEAST_RESTRICTIVE_INTERSECT, null, OperandTypes.SET_OP);

/**
* The {@code MULTISET UNION DISTINCT} operator.
Expand Down
75 changes: 75 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,81 @@ public static SqlCall stripSeparator(SqlCall call) {
.leastRestrictive(opBinding.collectOperandTypes());
}

/**
* Refines the nullability of {@code base} using INTERSECT semantics: a
* column is NOT NULL if it is NOT NULL in at least one of the
* {@code inputTypes} (AND-semantics across inputs).
*/
public static RelDataType refineNullabilityForIntersect(
RelDataTypeFactory typeFactory,
RelDataType base,
List<RelDataType> inputTypes) {
final RelDataTypeFactory.Builder builder =
new RelDataTypeFactory.Builder(typeFactory);
final List<RelDataTypeField> outputFields = base.getFieldList();
for (int i = 0; i < outputFields.size(); i++) {
boolean nullable = true;
for (RelDataType inputType : inputTypes) {
nullable &= inputType.getFieldList().get(i).getType().isNullable();
}
builder.add(outputFields.get(i)).nullable(nullable);
}
return builder.build();
}

/**
* Type-inference strategy for INTERSECT. Computes the least restrictive row
* type across all inputs, then refines nullability: a column is NOT NULL if
* it is NOT NULL in at least one input (AND semantics across inputs).
*/
public static final SqlReturnTypeInference LEAST_RESTRICTIVE_INTERSECT =
andThen(SqlTypeTransforms.FROM_MEASURE_IF::apply, opBinding -> {
final List<RelDataType> inputTypes = opBinding.collectOperandTypes();
final RelDataType base =
opBinding.getTypeFactory().leastRestrictive(inputTypes);
if (base == null) {
return null;
}
return refineNullabilityForIntersect(opBinding.getTypeFactory(), base, inputTypes);
});

/**
* Refines the nullability of {@code base} using EXCEPT/MINUS semantics: a
* column's nullability matches that of the first (primary) input.
*/
public static RelDataType refineNullabilityForExcept(
RelDataTypeFactory typeFactory,
RelDataType base,
RelDataType primaryInputType) {
final RelDataTypeFactory.Builder builder =
new RelDataTypeFactory.Builder(typeFactory);
final List<RelDataTypeField> outputFields = base.getFieldList();
final List<RelDataTypeField> primaryInputFields =
primaryInputType.getFieldList();
for (int i = 0; i < outputFields.size(); i++) {
builder.add(outputFields.get(i))
.nullable(primaryInputFields.get(i).getType().isNullable());
}
return builder.build();
}

/**
* Type-inference strategy for EXCEPT/MINUS. Computes the least restrictive
* row type across all inputs, then refines nullability: a column's
* nullability matches that of the first (primary) input.
*/
public static final SqlReturnTypeInference LEAST_RESTRICTIVE_EXCEPT =
andThen(SqlTypeTransforms.FROM_MEASURE_IF::apply, opBinding -> {
final List<RelDataType> inputTypes = opBinding.collectOperandTypes();
final RelDataType base =
opBinding.getTypeFactory().leastRestrictive(inputTypes);
if (base == null) {
return null;
}
return refineNullabilityForExcept(
opBinding.getTypeFactory(), base, inputTypes.get(0));
});

/**
* Type-inference strategy for NVL2 function. It returns the least restrictive type
* between the second and third operands.
Expand Down
Loading
Loading