Skip to content

[CALCITE-7757] RelMdFunctionalDependency can infer unsound dependencies and cause incorrect query results - #5235

Open
darpan-14 wants to merge 10 commits into
apache:mainfrom
darpan-14:CALCITE-7757
Open

darpan-14 wants to merge 10 commits into
apache:mainfrom
darpan-14:CALCITE-7757

Conversation

@darpan-14

Copy link
Copy Markdown
Contributor

Jira Link

CALCITE-7757

Changes Proposed

RelMdFunctionalDependency can currently expose unsound or incorrectly indexed dependencies, allowing AggregateRemoveDuplicateKeysRule to remove grouping keys that are still required.

This change:

  • Corrects equality-derived functional dependencies for INNER, LEFT, and RIGHT joins, respecting the null-generating side of outer joins.
  • Preserves valid input functional dependencies through outer joins without incorrectly deriving the reverse dependency from the join equality.
  • Maps aggregate input group ordinals to aggregate output ordinals before publishing functional-dependency metadata.
  • Avoids equality-derived dependencies for FLOAT, REAL, DOUBLE, and INTERVAL types, including nested occurrences.
  • Avoids unsafe expression-derived grouping dependencies for those same types.
  • Adds a reusable SqlTypeUtil predicate traversal for scalar and nested types.
  • Adds regression coverage for each correctness case.

A basic valid duplicate-key removal remains supported:

SELECT t1.col1, t2.col3, COUNT(*)
FROM t1
JOIN t2 ON t1.col1 = t2.col3
GROUP BY t1.col1, t2.col3;

For this inner join, either equivalent equality key may be removed. The new safeguards prevent that reasoning from being applied where null generation, ordinal mapping, or non-reflexive type semantics make it unsound.

Testing

  • ./gradlew :core:test --tests org.apache.calcite.sql.type.SqlTypeUtilTest --tests org.apache.calcite.test.AggregateRemoveDuplicateKeysRuleTest --tests org.apache.calcite.test.RelMetadataTest.testFunctionalDependency*
  • ./gradlew :core:checkstyleMain :core:checkstyleTest
  • git diff --check upstream/main...HEAD

The focused test run completed 49 tests with 0 failures.

@mihaibudiu

Copy link
Copy Markdown
Contributor

CI doesn't like your changes

@darpan-14

Copy link
Copy Markdown
Contributor Author

CI is happy now. 😄

}

/**
* Copies input dependencies into a join, optionally filtering dependencies

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 don't understand this javadoc. Can you please say what each parameter is and what the function actually computes? There's no "join" to be seen of here.


/**
* Returns whether a dependency's determinant contains a non-nullable input
* field. Such a determinant cannot collide with the all-NULL determinant of

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.

why does this comment mention a LEFT JOIN? There is no left join here. Please make the comment describe this function does.

* @param fdSet Functional dependency set
* @param offset Index offset
* @return Shifted functional dependency set
* Adds functional dependencies implied by a join condition.

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.

where does it add the dependencies?

* Extracts functional dependencies from equality and AND conditions.
* Handles col1 = col2, col1 IS NOT DISTINCT FROM col2, and AND conditions.
* Adds bidirectional dependencies for input-reference equalities in a
* condition. Callers are responsible for ensuring that every output row

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.

every output row of what?

@darpan-14

Copy link
Copy Markdown
Contributor Author

Updated all Javadocs raised in review. The helper methods now document their general behavior, parameters, return value where applicable, where inferred dependencies are recorded, and the relational-output requirement for equality-derived dependencies.

@Test void testKeepsDerivedGroupKeyForNestedDouble() {
// Determinism alone is insufficient for approximate values, including
// nested occurrences. Keep the derived key unless grouping equality is
// known to be congruent with the expression for ARRAY<DOUBLE>.

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 cannot read this comment. What is "congruent with the expression"?

@mihaibudiu

Copy link
Copy Markdown
Contributor

When this is ready please re-request a review

@sonarqubecloud

sonarqubecloud Bot commented Oct 2, 2026

Copy link
Copy Markdown

@darpan-14
darpan-14 requested a review from mihaibudiu October 16, 2026 05:22
@darpan-14

darpan-14 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor Author

When this is ready please re-request a review

I guess this is ready for review again @mihaibudiu. All posted comments are addressed.

@mihaibudiu mihaibudiu left a comment

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.

Can you add some quidem tests that produced wrong results without this fix?
These are the easiest to read by humans, especially if you cross-validate them using another database (e.g., postgres).

* Returns whether a type or any type nested within its fields, collection
* component, map key, or map value matches a predicate.
*/
public static boolean containsType(RelDataType type,

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.

containsMatchingType is a better name

assertThat(mq.determines(relNode, sumSal, count), is(Boolean.FALSE));
}

@Test void testFunctionalDependencyRollupAggregate() {

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.

Is this a test case for this issue? Then it needs JavaDoc with a specific format. There are lots of examples to follow.

assertThat(mq.determines(relNode, empNo, dname), is(Boolean.FALSE));
}

@Test void testFunctionalDependencyOuterJoinEqualityDirection() {

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.

Missing JavaDoc on test.

}

@Test void testKeepsFdFromNullGeneratingInputOfLeftJoin() {
// This FD comes from the right input rather than from the join condition.

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.

Which FD? Maybe you can explain what FDs are inferred.

}

@Test void testKeepsGroupKeyFromPreservedSideOfLeftJoin() {
// The null-generating right key cannot determine the preserved left key:

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.

For a reader it's not obvious at all how this test relates to this comment.
You need longer explanations on these tests.


@Test void testMapsFdForNonContiguousAggregateGroupSet() {
// AggregateProjectMergeRule changes the input group set to {1, 2, 3},
// whose keys occupy output positions {0, 1, 2}. Map the input FD 1 -> 2

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.

Is this comment valid before or after the fix? If it's before the fix, the comment is already wrong.

.check();
}

@Test void testKeepsGroupKeyFromPreservedSideOfLeftJoin() {

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.

no JavaDoc on tests.

@darpan-e6

Copy link
Copy Markdown
Contributor

Addressed all test related comments. I tested new QuidemTest results on Postgres 18.6. If you want me to verify on any other db engines, please let me know, I can do that as well.

@mihaibudiu
mihaibudiu requested review from xiedeyantu and removed request for xiedeyantu October 26, 2026 18:22
@mihaibudiu

Copy link
Copy Markdown
Contributor

@xiedeyantu you wrote most of this code, can you please check the core changes to RelMdFunctionalDependency?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants