New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[CALCITE-4409] Improve exception when RelBuilder tries to create a field on a non-struct expression #2272
Conversation
if (!e.getType().isStruct()) { | ||
throw new IllegalArgumentException("Requested field " + name + " in non-struct expression " | ||
+ e.toString() + " (type: " + e.getType() + ")"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering where is it better to add this check here or inside RexBuilder#makeFieldAccess
. Clients who call RexBuilder
direclty will still get the NPE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have the impression that the RelBuilder
build rel trees based on all kinds of assumptions, it assumes that the invoker knows how to behave with the valid operands. We may need to check all the interfaces in RelBuilder
, this PR is a good start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback. Looking again at this code in particular, I have the impression that a simpler fix for this specific issue would be leaving RelBuilder
untouched and modify this line in RelDataTypeImpl.getField
:
@Override public RelDataTypeField getField(String fieldName, boolean caseSensitive,
boolean elideRecord) {
for (RelDataTypeField field : fieldList) {
// =>
@Override public RelDataTypeField getField(String fieldName, boolean caseSensitive,
boolean elideRecord) {
for (RelDataTypeField field : getFieldList()) { // change here
With this change, the scenario in the Jira would fail with an AssertionError (thrown by getFieldList()
) instead of NPE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this check should be moved to org.apache.calcite.rel.type.RelDataTypeImpl#getField
Then all uses of RelDataTypeImpl#getField
would get the proper exception rather than NPE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vlsi we had the same thought :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I proposed IllegalArgumentException
, considering the first argument to be the illegal one (RexNode expr
, having a non-struct type). But I can see IllegalStateException
fitting here as well.
@zabetak @danny0405 what are your thoughts regarding the new proposal? Are you against adding the check in RexBuilder#makefieldAccess
(hence having an AssertionError
from RelDataTypleImpl
); or for adding check (in which case, would you see it as an IllegalArgumentException
or IllegalStateException
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, I do not like to make RexBuilder
aware of the ways getField
can fail.
In other words, it is not RexBuilder's business to tell if non-structs
are allowed to have fields or not.
I've had a very similar discussion with @julianhyde in https://issues.apache.org/jira/browse/CALCITE-4217, and the takeaway was "don't try to make sense of struct vs fieldList".
That is why I believe the necessary and sufficient solution here is:
-
Delegate the proper exception message to
org.apache.calcite.rel.type.RelDataType#getField(String fieldName, ...)
implementations. In other words, if someone implementsRelDataType
, then it is their business to throw the appropriate exception. -
org.apache.calcite.rex.RexBuilder#makeFieldAccess(org.apache.calcite.rex.RexNode, int)
is slightly different since it can't delegate toRelDataType
, and it has to calltype.getFieldList()
. It could be left as is, and the type implementation would throw "type ... has no fields" (which might be enough).
If you want to add clarification with the problematicRexNode
, then you could use
try {
final List<RelDataTypeField> fields = type.getFieldList();
} catch (RuntimeException e) {
e.addSuppressed(new Throwable("Unable to get field " + i + " from rexNode " + rexNode");
throw e
}
The important point is in both cases RexBuilder
makes no assumptions on struct
vs non-struct
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, indeed, RelCrossType
is a case when isStruct == false
and getFieldList()
returns non-null, so .isStruct()
check must not be added to RexBuilder
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, indeed, RelCrossType is a case when isStruct == false and getFieldList() returns non-null.
Well spotted, thanks for the feedback. I'll revert the changes in RexBuilder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Committed new proposal taking into account @vlsi 's input
@@ -79,6 +79,10 @@ protected RelDataTypeImpl() { | |||
|
|||
@Override public RelDataTypeField getField(String fieldName, boolean caseSensitive, | |||
boolean elideRecord) { | |||
if (fieldList == null) { | |||
throw new IllegalStateException("Requested field " + fieldName | |||
+ " in a type with null fieldList, type = " + this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In which case, user can pass in a null fieldList ? I would rather to move the check to the builder that generate the RelDataTypeImpl
instance.
The exception message is also confusing, we should tell user why he does wrong but not "we got null field list".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danny0405 any "simple" (i.e. non-struct) type will have a null fieldList.
The problem is not having a null fieldList (which is valid), the problem is trying to access this list (e.g. via getField, but also getFieldList, getFieldNames, getFieldCount...) when this list is null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that type with null fieldList
is too low-level for RelDataType
users, however, I don't know the better message.
The following might be slightly bit better: "The current type " + this + " does not support named field lookup, so field " + fieldName + " can't be located"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exception message reviewed to make it more high level
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’d prefer a simpler error message, such as “Type does not support fields”. Especially if it’s emitted by RelDataType, which is a low level class and not supposed to be user-friendly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exceptions should always be meaningful, even the low-level ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion is meaningful, too.
The verbose message says that the type doesn’t support named field lookup. So the reader will wonder whether it supports field lookup by other means. No, it doesn’t have fields. Just say that.
A simple message is sufficient for the user to diagnose their problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please suggest exact pattern to insert into new IllegalStateException(...)
@vlsi @zabetak @danny0405 are we satisfied with the current status of the PR? Any other remarks? Shall I go on and squash commits and then merge? |
3ead64c
to
367181d
Compare
@rubenada I think the message should be simple and terse. Less is more. |
No. You seem to have made a new rule “anyone who critiques a PR has to
write code to fix it”. That’s not how it works.
…On Wed, Nov 25, 2020 at 7:58 AM Vladimir Sitnikov ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
<#2272 (comment)>:
> @@ -79,6 +79,10 @@ protected RelDataTypeImpl() {
@OverRide public RelDataTypeField getField(String fieldName, boolean caseSensitive,
boolean elideRecord) {
+ if (fieldList == null) {
+ throw new IllegalStateException("Requested field " + fieldName
+ + " in a type with null fieldList, type = " + this);
Please suggest exact pattern to insert into new IllegalStateException(...)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2272 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAIUAOIUJYNEZQ3R736LZC3SRUSRBANCNFSM4T3TA5EQ>
.
|
0ad1bc4
to
7fc50b0
Compare
Let's not make a big fuss out of an exception message. I have committed a new proposal. I think it is simple, meaningful, not too low level. |
}, "Field should fail since we are trying access a field on expression with non-struct type"); | ||
assertThat(ex.getMessage(), | ||
allOf(containsString("Trying to access field"), | ||
containsString("in a type with no fields"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose to split into two containsString
here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simply because the actual fieldName value goes in between, and it is not really relevant in order to verify the exception message. I just followed the same pattern as the test just above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The field name does not change over time, so it would be better to use regular is("....")
matcher to get better feedback in IDE and CI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to confirm, the verification with is(...)
would look like:
assertThat(ex.getMessage(), is("Trying to access field abc in a type with no fields: SMALLINT"));
Is that what you both would prefer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, changed.
…eld on a non-struct expression
bcbf748
to
2264654
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, looks fine from my side.
Merged. |
Jira: CALCITE-4409