Repository navigation
[CALCITE-7859] SqlToRelConverter.convertMerge produces a wrong plan or fails when a sub-query in MERGE ... UPDATE SET is expanded - #5332
Conversation
eb74259 to
9381762
Compare
mihaibudiu
left a comment
There was a problem hiding this comment.
This PR ensures that MERGE can be compiled, but there is no proof that it would produce the right results. Is there a runtime implementation of MERGE that could be used to test the results end-to-end?
@mihaibudiu
I tried the cases of this PR on that path (foodmart on HSQLDB):
So, as far as I can see, these cases cannot be tested end-to-end in Calcite without either an Enumerable implementation of MERGE or fixing |
|
I will try to review the code as it is. If you expect to have a running MERGE maybe you can file an issue. |
| /** Converts the values of the INSERT of a MERGE over the input of the MERGE | ||
| * source projection, whose first fields are the source fields. | ||
| * | ||
| * <p>The values are not converted over the source alone, so that their |
There was a problem hiding this comment.
I don't understand this paragraph. It says how things are NOT converted, but it should say how they ARE converted.
There was a problem hiding this comment.
Agreed. I rewrote it to say how the values are converted: over the merge input, which is the join of the source and the target plus the joins or correlates that expand the SET sub-queries. If the sub-queries of the values are expanded, their joins or correlates are added on top of it
| * sub-queries are expanded on top of the merge input and their field | ||
| * references stay valid. | ||
| * | ||
| * @return projection whose expressions are the inserted row, in the order of |
| * references stay valid. | ||
| * | ||
| * @return projection whose expressions are the inserted row, in the order of | ||
| * the target columns; its input is the merge input, possibly extended by |
There was a problem hiding this comment.
I am not sure how the merge input is "extended".
Isn't the input a query, which may or may not contain subqueries?
There was a problem hiding this comment.
The merge input here is not the SQL query but the already converted RelNode: the join of the source and the target. If the sub-queries in the INSERT values are expanded, they become joins (or correlates) on top of it, so the input of the returned projection is either the merge input itself or these joins. I rewrote the @return to say this instead of "extended"
| return LogicalTableModify.create(targetTable, catalogReader, | ||
| relBuilder.build(), LogicalTableModify.Operation.MERGE, | ||
| targetColumnNameList, null, false); | ||
| /** Creates the modification of a MERGE. As for INSERT, a modifiable table |
There was a problem hiding this comment.
I don't understand this comment.
What does this have to do with JDBC.
What convention?
There was a problem hiding this comment.
JdbcTable.toModificationRel registers the rules of the JDBC convention in the planner; without them a MERGE into a JDBC table cannot be planned (JdbcAdapterTest.testMerge). Before this PR, convertMerge got that registration as a side effect of calling convertInsert, which goes through createModify. The new code no longer calls convertInsert, so createMergeModify calls toModificationRel itself, like createModify does for INSERT. A side effect is that a MERGE without an INSERT now also gets it. I rewrote the javadoc to explain this.
There was a problem hiding this comment.
I think SqlToRelConverter should work without any Jdbc-related code. Moreover, there is no planning yet at this stage, there is just conversion from one representation to another.
There was a problem hiding this comment.
Agreed, the javadoc was misleading. The converter itself does not depend on JDBC. Like createModify, which handles INSERT, UPDATE and DELETE, createMergeModify only calls the generic ModifiableTable.toModificationRel, so a table can create its own TableModify. What a particular table does there, JdbcTable included, is up to its adapter. Before this PR, MERGE already went through the same call via convertInsert → createModify, so this keeps the existing behavior. I removed the JDBC-specific text from the javadoc.
mihaibudiu
left a comment
There was a problem hiding this comment.
this generally looks fine, but the comments are misleading.
I am approving the code, but please fix the comments.
| /** Converts the values of the INSERT of a MERGE over the input of the MERGE | ||
| * source projection, whose first fields are the source fields. | ||
| * | ||
| * <p>The values reference the source fields, so they are converted over the |
There was a problem hiding this comment.
this comment seems redundant with the returns comment. Can you remove it or trim it down?
There was a problem hiding this comment.
Done, I removed the description and kept only the @return.
| * added on top of the merge input; the source fields stay first, so the | ||
| * field references of the values stay valid. | ||
| * | ||
| * @return A projection whose expressions are the inserted row, in the order |
There was a problem hiding this comment.
moreover, if you read the body if the function it does nothing about joins, correlates, or subqueries. These are all handled by convertSelectList I suspect. So you should not mention that here. The select query is converted like any other query.
There was a problem hiding this comment.
Agreed, these are details of convertSelectList. The javadoc now only says what the caller relies on: the input of the returned projection starts with the fields of mergeInput.
|
Please squash the commits for merging. |
…r fails when a sub-query in MERGE ... UPDATE SET is expanded
|
Squashed into one commit. |
|



Jira: https://issues.apache.org/jira/browse/CALCITE-7859
SqlToRelConverter.convertMergeassumes that the input of the projection of the MERGE source select is the join of the source and the target. Withexpand = truea sub-query in aSETexpression is expanded into a join or a correlate on top of that join, which breaks the assumption:ClassCastException: LogicalCorrelate cannot be cast to LogicalJoin;LogicalProject(EXPR$0=[$18])instead of the 9 target columns followed byEXPR$0).Changes:
testMergeUpdateCorrelatedSubQueryExpandedandtestMergeUpdateUncorrelatedSubQueryExpanded.CALCITE-7861
Jira: https://issues.apache.org/jira/browse/CALCITE-7861 (separate commit; it changes the same part of
convertMerge)With
expand = truea sub-query in the VALUES of the INSERT branch of a MERGE was converted into a plan that silently inserts a wrong value:SALwasEMPNO0=[$9](EMPNO of the target) and the join withDEPTwas gone.convertMergeconverted the INSERT separately withconvertInsertand copied the expressions of its projections into the projection over the MERGE join; when the sub-query is expanded, those expressions are over the source joined with the sub-query, and their indexes are wrong over the MERGE join.Changes:
convertColumnList;ModifiableTable.toModificationRel, as INSERT does.convertInsertused to do it as a side effect, andJdbcTableregisters its convention there; without itJdbcAdapterTest.testMergeloses the JDBC push-down;testMergeInsertSubQuery,testMergeInsertSubQueryExpandedandtestMergeInsertCorrelatedSubQueryExpanded.Plans of the existing MERGE tests do not change. One behavior change, now pinned by
testMergeModifiableView: for a MERGE into a modifiable view, the insert row used to be built for the underlying table (including the column values of the view definition), while the modification is on the view; now it is built for the view, as the update part, so the values of the columns constrained by the view are not set. MERGE into a modifiable view was not supported correctly before either; proper support (rewriting the MERGE to the underlying table, ascreateModifydoes for INSERT) is out of scope of this PR.