-
Notifications
You must be signed in to change notification settings - Fork 784
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
Implement ArrayEqual for UnionArray #1469
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1469 +/- ##
==========================================
+ Coverage 82.68% 82.75% +0.06%
==========================================
Files 188 190 +2
Lines 54361 54702 +341
==========================================
+ Hits 44951 45270 +319
- Misses 9410 9432 +22
Continue to review full report at Codecov.
|
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 think this is good, I've left some comments on how I think it could be made a lot faster which you can take or leave
arrow/src/array/equal/union.rs
Outdated
}; | ||
|
||
// Checks if corresponding slots in two UnionArrays are same data types | ||
fn equal_types( |
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 order to be equal the two arrays must have the same schema and therefore lhs_fields == rhs_fields
I think therefore this could simply just compare the type_id arrays for equality directly?
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.
Hmm, even type_ids are different, isn't it still possible that the union arrays are equal?
For example, the fields are int, int, float, string. If lhs type_ids is 1, but rhs type_id is 0, they are both int. We don't know if the actual element at child arrays are equal or not.
So here I compare if the type_ids are the same type or not, not its values.
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.
And two union arrays could be equal even if two fields/type_ids are different. For example, we can change the field position and type_id, but the resulting union array is still the same.
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 the case of two fields with the same type, I believe these must still have distinct field names and therefore there is a logical difference between values stored in one vs the other. I'm not sure about this though, possibly worth an upstream clarification 🤔
On the case of different field ordering the arrays can never compare equal as they have different data types.
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.
equal_types
was removed now.
} | ||
|
||
#[allow(clippy::too_many_arguments)] | ||
fn equal_sparse( |
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.
By the time we are calling this method we have established that
- the types are equal
- the null buffers are equal
- the type ids arrays are equal
As such I'm surprised this can't just do a standard equality comparison of the child data, which will take into null buffers of the children
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.
This is related to above question. As field and type_id can be changed in position, but the resulting union array is still the same, we cannot directly compare them, but can only compare child array corresponding to type_id.
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 suppose it really does come down to "what does it mean for two UnionArray
s to be equal"?
Can it ever be true that UnionArray(Int, Utf8)
be equal to UnionArray(Utf8, Int)
?
Given my reading of the cpp ArrayEquals
code in https://github.com/apache/arrow/blob/master/cpp/src/arrow/compare.cc it seems to me that two arrays are only ever "equal" if their datatypes are (exactly) equal.
Maybe we could follow the cpp implementation -- both for consistency as well as for simplicity?
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, if it is the case, we can simplify the comparison here. What I have in mind and implemented here can be thought as logically equal. That means corresponding slots of two union arrays are equal, but their physical layout may be different -- type_ids and fields can be different separately.
If we only want to compare the physical layout, then fields and type_ids are required to be exactly equal.
Thanks for pointing out the cpp implementation. We should follow it, I agree.
I will revise this change for that.
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.
equal_sparse
was simplified to compare corresponding child_data
at given positions.
lhs_slots_types.zip(rhs_slots_types).all(|(l, r)| l == r) | ||
} | ||
|
||
fn equal_dense( |
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.
Much like equal_sparse below, by the time we are calling this method we have already established equality of the type_ids, I therefore wonder if we can simply compute equality of the offsets arrays, followed by computing equality of the underlying child arrays.
This appears to be similar to what is done for list_equal 🤔
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.
Even type_ids equality is established, lhs and rhs start positions might be different. So we cannot simply compare offsets arrays and child arrays for dense case.
arrow/src/array/equal/utils.rs
Outdated
lhs.data_type() == rhs.data_type() && lhs.len() == rhs.len() | ||
let equal_type = match (lhs.data_type(), rhs.data_type()) { | ||
(DataType::Union(l_fields, l_mode), DataType::Union(r_fields, r_mode)) => { | ||
// Defer field datatype check to `union_equal` |
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.
Why?
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.
Because even the lhs fields and rhs fields are different, the two union arrays are still possible equal. We cannot use fields to decide if two union arrays are equal or not. Fields and type_ids are co-related.
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 spec describes a union as an ordered sequence of types with names. My interpretation of this is that the fields are order-sensitive and must be identical for the types to be the same...
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.
Revised to compare lhs and rhs fields here.
arrow/src/array/equal/utils.rs
Outdated
DataType::Union(_, mode) => { | ||
match mode { | ||
UnionMode::Sparse => { | ||
// See the logic of `DataType::Struct` in `child_logical_null_buffer`. |
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.
Perhaps this could be broken out into a function? I also wonder if it might be possible to implement more efficiently by truncating the longer buffer 🤔
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.
Made it as a function now.
I will try and review this PR more carefully tomorrow |
Seems I missed some comments, I will address them soon. Thanks. |
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.
Looks like a nice step forward to me. Thank you @viirya
Which issue does this PR close?
Closes #67.
Rationale for this change
What changes are included in this PR?
Are there any user-facing changes?