Repository navigation
Improve type coercion and casting #8302
Description
Is your feature request related to a problem or challenge?
I think there is room for improvement in type coerceion or casting.
Background
comparison_coercion is widely used in datafusion, a lossless conversion
https://github.com/apache/arrow-datafusion/blob/main/datafusion/expr/src/type_coercion/binary.rs
can_coerce_from is used mainly for signature, a lossless conversion
https://github.com/apache/arrow-datafusion/blob/main/datafusion/expr/src/type_coercion/functions.rs
can_cast_types is from arrow-cast, which is a lossy conversion. It is also used in some comparison_coercion building block. https://github.com/apache/arrow-rs/blob/df69ef57d055453c399fa925ad315d19211d7ab2/arrow-cast/src/cast.rs#L76-L273
Not sure if there is other coercion I missed
Proposal
comparison_coercion and can_coerce_from seem like doing the similar thing, maybe we can just have one lossless conversion. If lossless conversion is useful for arrow-rs, we can introduce a lossless version of can_cast_types, then rely on it for datafusion.
Lossy conversion vs Lossless
I think the definition for lossy is that the value is not recoverable after casting back, otherwise it is lossless.
Lossy
- Int32 to Int16 / Int8
Lossless
- Int32 to Int64
Describe the solution you'd like
- Replace
can_coerce_fromwithcomparison_coercion's building blocknumeric coercion,list coercion,string coercion,null coercion, etc - Split
list_coercionfromstring_coercionto make each building block of coercion clear on the task it focus on. list_coercion dolist/fixed size list/large listcoercion,string_coerciondoutf/large utfcoercion. - Introduce these lossless coercion to arrow-rs?
Known issue or question I have
- Introduce
list_coercionthat currently exist instring_concat_coercion - No list coercion for
can_coerce_from - Decimal128 can cast to Float64 in
can_coerce_from, why?
Describe alternatives you've considered
If there are many customize conversion need, then this change might not be helpful at all. We need other approach to let type casting / coercion easy to use.
No response
Additional context
No response
Activity
jayzhan211 commentedon Nov 22, 2023
@tustvold Do you think introduce a lossless version of
can_cast_typeshelpful?cc @alamb
tustvold commentedon Nov 22, 2023
Floating points inherently permit precision loss. It is fairly standard that otherwise checked conversion permit precision loss when converting between floats.
For example - https://docs.rs/num-traits/latest/num_traits/cast/trait.FromPrimitive.html
I think this would make sense as an addition, the current support for nested types in arrow-cast is very limited.
In general I think it would be very confusing if coercion can result in silent data loss, perhaps you might articulate why this would be useful? My current thinking is casting should never silently lose data.
jayzhan211 commentedon Nov 22, 2023
https://github.com/apache/arrow-rs/blob/df69ef57d055453c399fa925ad315d19211d7ab2/arrow-cast/src/cast.rs#L76C2-L80
Comment in
can_cast_typesmention that cast is lossy.https://github.com/apache/arrow-rs/blob/df69ef57d055453c399fa925ad315d19211d7ab2/arrow-cast/src/cast.rs#L212-L216
Casting Int64 to Int32 return true. I'm not sure if the
actualcasting block this lossy conversion, but if not, could it silently lose data?tustvold commentedon Nov 22, 2023
That comment is incorrect and doesn't match the implementation. I believe it means that it supports conversions that are potentially fallible, e.g. Int32 to Int8, but it never silently looses data
jayzhan211 commentedon Nov 22, 2023
Any scenario that we should hint it as potentially fallible but actually do nothing for them? It seems to me return false is more straightforward if we do nothing.
jayzhan211 commentedon Nov 22, 2023
However, I think we can still cleanup
comparison_coercionandcan_coerce_fromand forget about lossy conversion.jayzhan211 commentedon Nov 22, 2023
Maybe Int64(1) cast to Int32(1) is one that is able to safely cast without data loss.
jayzhan211 commentedon Nov 22, 2023
Maybe we need another verion of
can_cast_typesthat no possible fallible hint. For i64, only u64 or larger type, no i32 or i16 since we don't know the actual data.tustvold commentedon Nov 22, 2023
Is that not what can_coerce_from is?
jayzhan211 commentedon Nov 22, 2023
Yes! Should we have it aside
can_cast_typesin arrow-rs? Or just let it stay in datafusion for now.tustvold commentedon Nov 22, 2023
Given the type coercion logic itself lives in DataFusion I personally think it makes sense for it to remain alongside it
jayzhan211 commentedon Nov 23, 2023
Short summary for the next step of this issue
can_coerce_fromwith component incomparison_coercion, so we dont need to think about which one to use. Reuse the code if possible, callcan_cast_typesif possible.TODO
coerce_arguments_for_signatureand introduce another one for ArrayAppend #8317coercion for MakeArray anf Expr::InList should be the same https://github.com/apache/arrow-datafusion/blob/06bbe1298fa8aa042b6a6462e55b2890969d884a/datafusion/optimizer/src/analyzer/type_coercion.rs#L284-L317Issue
Return Result forReturncomparison_coercion, so we can differentiateunimplemented castingornot possible castingOption<T>is probably enough.Consider having component of. No use case.comparison_coercionpublic, like Allow default value on lag/lead if they are coerceable to value #8308 can have numerice_coercion instead ofcomparison_coercioncomparison_coercion, while Decimal is coerced to Float incan_coerced_from. Confused 😕 😕 😕Note
can_coerce_from/comparison_coercionunlikearrow-castshould only check the casting to the larger type since the actual data is unknown.[-]Categorize lossless type coercion and lossy type coercion[/-][+]Unify type coercion and casting[/+]jayzhan211 commentedon Nov 29, 2023
https://github.com/apache/arrow-datafusion/blob/2a692446f46ef96f48eb9ba19231e9576be9ff5a/datafusion/expr/src/type_coercion/binary.rs#L388-L406
In
comparison_binary_numeric_coercion, signed type and u64 is coerced to i64, anyone knows why is this acceptable? what kind of cases are?for type u64 and f32, it will return f32 which is smaller than u64, not sure why is this too
alamb commentedon Nov 29, 2023
The comment in the code you linked seems to say "some information loss is inevitable":
It is likely for "usability" so that you can compare i64 and u64 without an explicit type cast 🤷
jayzhan211 commentedon Nov 30, 2023
I don't think that usability is the good reason :( It might cause data loss.
@alamb I would like to change them to the straightforward version, choose the larger type between two. I think it can also be used all the place in datafusion easily (one place is
can_coerce_from). Is there any concern about this change?If such implicit casting is found helpful in the end, we can have two version, one that lossy is acceptable, one is not.
alamb commentedon Nov 30, 2023
I would recommend you write some tests / try out the change before making a full featured PR. I am not sure what the implications are with the change you propose, but I suspect it could be non trivial and subtle
jayzhan211 commentedon Nov 30, 2023
I rethink about this. This is the best we can do, like the comment mentioned, it is inevitable :(
jayzhan211 commentedon Dec 2, 2023
After #8385, I found that it is not ideal to have one coercion for all the place (compare op, math op, signature coercion, etc... ).
[-]Unify type coercion and casting[/-][+]Improve type coercion and casting[/+]