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
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@
return false;
}

private static Map<String, String> getFieldAliases(final SelectScope scope) {

Check warning on line 571 in core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this method into "Expander".

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ37IBYeBGdPU2LStRPQ&open=AZ37IBYeBGdPU2LStRPQ&pullRequest=4912
final ImmutableMap.Builder<String, String> fieldAliases = new ImmutableMap.Builder<>();

for (SqlNode selectItem : scope.getNode().getSelectList()) {
Expand Down Expand Up @@ -642,6 +642,31 @@
}
}

/** Validates that a SQL node tree does not contain qualified references
* to common columns in a JOIN USING or NATURAL JOIN context.
* This is called before identifier expansion to catch user-written
* qualified common columns in conformances where they are disallowed
* (e.g. Oracle, Presto).
*
* @param nodeList The list of SQL nodes to check
* @param join The JOIN node containing USING/NATURAL condition
* @param scope The select scope for resolving identifiers
*/
private void validateNoQualifiedCommonColumns(SqlNodeList nodeList,
SqlJoin join, SelectScope scope) {
for (SqlNode item : nodeList) {
item.accept(new SqlShuttle() {
@Override public SqlNode visit(SqlIdentifier id) {
if (!id.isSimple()) {
validateQualifiedCommonColumn(join, id, scope,
SqlValidatorImpl.this);
}
return id;
}
});
}
}

private boolean expandStar(List<SqlNode> selectItems, Set<String> aliases,
PairList<String, RelDataType> fields, boolean includeSystemVars,
SelectScope scope, SqlNode node) {
Expand Down Expand Up @@ -5088,6 +5113,17 @@

// expand the expression in group list.
List<SqlNode> expandedList = new ArrayList<>();
// Validate that GROUP BY items do not qualify common columns
// in conformances where it is disallowed (e.g. Oracle, Presto).
// This must run before expansion, because expansion generates
// qualified identifiers that should not trigger this validation.
if (!config.conformance().allowQualifyingCommonColumn()) {
final SqlNode from = select.getFrom();
if (from instanceof SqlJoin) {
validateNoQualifiedCommonColumns(groupList,
(SqlJoin) from, getRawSelectScopeNonNull(select));
}
}
for (SqlNode groupItem : groupList) {
SqlNode expandedItem =
extendedExpand(groupItem, groupScope, select, Clause.GROUP_BY);
Expand Down Expand Up @@ -5239,6 +5275,19 @@
}
}

/** Validates that SELECT items do not qualify common columns
* in conformances where it is disallowed (e.g. Oracle, Presto). */
private void validateSelectCommonColumns(SqlNodeList selectItems,
SqlSelect select) {
if (!config().conformance().allowQualifyingCommonColumn()) {
final SqlNode from = select.getFrom();
if (from instanceof SqlJoin) {
validateNoQualifiedCommonColumns(selectItems,
(SqlJoin) from, getRawSelectScopeNonNull(select));
}
}
}

protected RelDataType validateSelectList(final SqlNodeList selectItems,
SqlSelect select, RelDataType targetRowType) {
// First pass, ensure that aliases are unique. "*" and "TABLE.*" items
Expand All @@ -5252,6 +5301,12 @@
// Populated during select expansion when SqlConformance.isSelectAlias != UNSUPPORTED
final Map<String, SqlNode> expansions = new HashMap<>();

// Validate that SELECT items do not qualify common columns
// in conformances where it is disallowed (e.g. Oracle, Presto).
// This must run before expansion, because expansion generates
// qualified identifiers that should not trigger this validation.
validateSelectCommonColumns(selectItems, select);

for (SqlNode selectItem : selectItems) {
if (selectItem instanceof SqlSelect) {
handleScalarSubQuery(select, (SqlSelect) selectItem,
Expand Down Expand Up @@ -7499,9 +7554,7 @@

final SqlIdentifier identifier = (SqlIdentifier) selectItem;
if (!identifier.isSimple()) {
if (!validator.config().conformance().allowQualifyingCommonColumn()) {
validateQualifiedCommonColumn((SqlJoin) from, identifier, scope, validator);
}
// No validation here - just return unchanged
return selectItem;
}

Expand Down Expand Up @@ -7530,7 +7583,7 @@
final SqlIdentifier exp =
new SqlIdentifier(
ImmutableList.of(child.name, name),
identifier.getParserPosition());
SqlParserPos.ZERO);
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.

This does not look like a robust way to carry information between compilation stages; in general, the parser position is best-effort in Calcite.

There must be a better way to do this. My intuition tells me that the unparser has to handle this case by stripping out the qualifying information. Alternatively, can the join type be changed to an INNER JOIN?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your suggestion. The original plan was indeed inadequate. I have now revised it to completely separate validation from expansion — validating the original AST once before expansion, and not performing validation during expansion.

qualifiedNode.add(exp);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6039,4 +6039,45 @@ void checkUserDefinedOrderByOver(NullCollation nullCollation) {
final String sql = "select distinct deptno, deptno, empno, 1, 'a' from emp order by rand(), 1";
sql(sql).ok();
}

/** Test case of
* <a href="https://issues.apache.org/jira/browse/CALCITE-7085">[CALCITE-7085]
* JOIN USING with unqualified common column fails in a conformance where
* allowQualifyingCommonColumn is false (e.g. Oracle, Presto)</a>. */
@Test void testJoinUsingWithConformanceOracle() {
final String sql = "SELECT deptno, name\n"
+ "FROM emp JOIN dept using (deptno)";
sql(sql).withConformance(SqlConformanceEnum.ORACLE_10).ok();
}


/** Test case of
* <a href="https://issues.apache.org/jira/browse/CALCITE-7085">[CALCITE-7085]
* JOIN USING with unqualified common column fails in a conformance where
* allowQualifyingCommonColumn is false (e.g. Oracle, Presto)</a>. */
@Test void testLeftJoinUsingWithConformanceOracle() {
final String sql = "SELECT deptno, name\n"
+ "FROM emp LEFT OUTER JOIN dept using (deptno)";
sql(sql).withConformance(SqlConformanceEnum.ORACLE_10).ok();
}

/** Test case of
* <a href="https://issues.apache.org/jira/browse/CALCITE-7085">[CALCITE-7085]
* JOIN USING with unqualified common column fails in a conformance where
* allowQualifyingCommonColumn is false (e.g. Oracle, Presto)</a>. */
@Test void testRightJoinUsingWithConformanceOracle() {
final String sql = "SELECT deptno, name\n"
+ "FROM emp RIGHT OUTER JOIN dept using (deptno)";
sql(sql).withConformance(SqlConformanceEnum.ORACLE_10).ok();
}

/** Test case of
* <a href="https://issues.apache.org/jira/browse/CALCITE-7085">[CALCITE-7085]
* JOIN USING with unqualified common column fails in a conformance where
* allowQualifyingCommonColumn is false (e.g. Oracle, Presto)</a>. */
@Test void testJoinUsingWithConformancePresto() {
final String sql = "SELECT deptno, name\n"
+ "FROM emp JOIN dept using (deptno)";
sql(sql).withConformance(SqlConformanceEnum.PRESTO).ok();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4317,6 +4317,34 @@ LogicalProject(DEPTNO=[$0], EXPR$1=[$1])
LogicalJoin(condition=[=($7, $9)], joinType=[full])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
]]>
</Resource>
</TestCase>
<TestCase name="testJoinUsingWithConformanceOracle">
<Resource name="sql">
<![CDATA[SELECT deptno, name
FROM emp JOIN dept using (deptno)]]>
</Resource>
<Resource name="plan">
<![CDATA[
LogicalProject(DEPTNO=[$7], NAME=[$10])
LogicalJoin(condition=[=($7, $9)], joinType=[inner])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
]]>
</Resource>
</TestCase>
<TestCase name="testJoinUsingWithConformancePresto">
<Resource name="sql">
<![CDATA[SELECT deptno, name
FROM emp JOIN dept using (deptno)]]>
</Resource>
<Resource name="plan">
<![CDATA[
LogicalProject(DEPTNO=[$7], NAME=[$10])
LogicalJoin(condition=[=($7, $9)], joinType=[inner])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
]]>
</Resource>
</TestCase>
Expand Down Expand Up @@ -4949,6 +4977,20 @@ LogicalProject(C=[$0], N=[$3])
LogicalAggregate(group=[{0}])
LogicalProject($f2=[+($0, 1)])
LogicalValues(tuples=[[{ 4 }]])
]]>
</Resource>
</TestCase>
<TestCase name="testLeftJoinUsingWithConformanceOracle">
<Resource name="sql">
<![CDATA[SELECT deptno, name
FROM emp LEFT OUTER JOIN dept using (deptno)]]>
</Resource>
<Resource name="plan">
<![CDATA[
LogicalProject(DEPTNO=[$7], NAME=[$10])
LogicalJoin(condition=[=($7, $9)], joinType=[left])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
]]>
</Resource>
</TestCase>
Expand Down Expand Up @@ -7345,6 +7387,20 @@ GROUP BY ROLLUP(deptno)]]>
LogicalAggregate(group=[{0}], groups=[[{0}, {}]], EXPR$1=[COUNT(DISTINCT $1)])
LogicalProject(DEPTNO=[$7], EMPNO=[$0])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
<TestCase name="testRightJoinUsingWithConformanceOracle">
<Resource name="sql">
<![CDATA[SELECT deptno, name
FROM emp RIGHT OUTER JOIN dept using (deptno)]]>
</Resource>
<Resource name="plan">
<![CDATA[
LogicalProject(DEPTNO=[COALESCE($7, $9)], NAME=[$10])
LogicalJoin(condition=[=($7, $9)], joinType=[right])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
]]>
</Resource>
</TestCase>
Expand Down
Loading