andygrove commented on PR #5234:
URL: 
https://github.com/apache/datafusion-comet/pull/5234#issuecomment-5170492552

   Thanks for picking this up. Inverting the order so the Spark cast owns the 
value semantics and Arrow owns the dictionary construction is the right shape, 
and deleting the parquet branch is a nice cleanup.
   
   I spent some time verifying the two preconditions the issue asked about, and 
both hold up:
   
   - `can_cast_types(Dictionary(_, value_type), to)` in arrow-cast 58.4 
delegates straight to `can_cast_types(value_type, to)`, and the dict-to-dict 
arm delegates to the value types. So the `can_cast_types` fallback in 
`parquet_convert_array` covers exactly the same set of combinations the old 
recursion covered through its own arrow fallback. No combination that used to 
convert now falls through to `_ => Ok(array)`.
   - None of the Comet-specific arms in `parquet_convert_array` has `Utf8` or 
`LargeUtf8` as its from-type. They are `Struct`, `List`, `Timestamp(us, None)`, 
`Map`, and `FixedSizeBinary(16)`. Since the deleted branch restricted values to 
`Utf8`/`LargeUtf8`, the recursion could only ever reach identity or the arrow 
fallback, so nothing Spark-specific is lost by deleting it.
   
   Dropping the `spark_cast_postprocess` call on the new return also looks 
safe. All four of its arms require `to_type` to be `Int64` or `Utf8`, so with a 
`Dictionary` target it was always the identity.
   
   I also liked the test data choice in 
`test_cast_to_dictionary_deduplicates_casted_values`. `"0.2"` and `"."` both 
cast to `0`, matching Spark's `UTF8String.toInt` truncation behavior, so it is 
a real post-cast collision rather than a contrived one.
   
   A few things I would like to work through before this merges.
   
   ### The new path supports fewer dictionary value types than the old one
   
   `arrow::compute::cast` reaches `cast_to_dictionary` 
(`arrow-cast-58.4.0/src/cast/dictionary.rs:178`), and that function has no arm 
for `Boolean`. It also has none for `Null` or any nested type. Those all land 
on `_ => Err("Unsupported output type for dictionary packing: 
{dict_value_type}")`.
   
   Under the old code, `cast_array(StringArray, Dictionary(Int32, Boolean))` 
worked. `dict_from_values` wrapped the strings into `Dictionary(Int32, Utf8)`, 
the recursion then hit the from-dictionary arm, and 
`spark_cast_utf8_to_boolean` was applied to the values. Under the new code the 
same call Spark-casts to `Boolean` correctly and then fails at the packing step.
   
   What makes this awkward to guard against is that `can_cast_types` disagrees 
with `cast` here. `can_cast_types(Boolean, Dictionary(Int32, Boolean))` returns 
`true`, because the `(_, Dictionary(_, value_type))` arm just recurses on the 
value type. So a caller cannot probe for support first.
   
   Could you add a test for `StringArray -> Dictionary(Int32, Boolean)` so we 
pin down what the behavior is either way? None of the existing tests cover a 
boolean dictionary target, which is why CI is green on this. If it does 
regress, one option is to keep constructing the dictionary manually when Arrow 
cannot pack the value type.
   
   ### The value cast is threaded with cast options but the packing call is not
   
   Would you mind using `cast_with_options(&values, to_type, 
&native_cast_options)` instead of the bare `arrow::compute::cast`? Every other 
arrow call in `cast_array` threads the options through, and the bare `cast` 
silently picks up `CastOptions::default()`. I do not think it changes results 
today, since `values` already has the target value type and the inner cast 
inside `pack_numeric_to_dictionary` is an identity, but it is the one call in 
the function that drops the eval mode.
   
   ### The surviving from-dictionary arm still hand-rolls, and it ignores the 
requested key type
   
   This one predates the PR, but it is the same function and the same class of 
bug you just fixed on the parquet side, so I would rather not leave it floating.
   
   The `Dictionary(Int32, Utf8 | LargeUtf8 | Binary | LargeBinary)` arm in 
`cast_array` is hardcoded to `DictionaryArray::<Int32Type>`. For `to_type = 
Dictionary(Int16, Int32)` it returns `Dictionary(Int32, Int32)`, so the 
returned `DataType` does not match what was requested. After this PR, 
`cast_array` honours the requested key type when the source is plain and 
ignores it when the source is a string or binary dictionary, which is a 
confusing split.
   
   Arrow's `dictionary_to_dictionary_cast` already handles both the key cast 
and the value cast, and it errors cleanly when keys do not fit rather than 
silently returning the wrong type, so this arm may be able to collapse the same 
way. If that is out of scope here, could you file a tracking issue and link it 
from this PR? There is also an `Arc::new(casted_dictionary.clone())` in that 
arm cloning something already owned, if you end up touching it.
   
   ### Test coverage
   
   The two new tests are well targeted at what changed. Two gaps I would like 
closed:
   
   - Could you add a case with a non-`Int32` key type on the target, say 
`Dictionary(Int8, Utf8)`? Your parquet test covers `Int32 -> Int16` keys but 
the cast test does not, and that is exactly where the key-type question above 
lives.
   - A case for a non-string source would be good too, for example `Int32Array 
-> Dictionary(Int32, Int64)`. The old path could not reach that, so it is new 
behavior worth pinning down.
   
   ### One question on scope
   
   It would help if the description said whether any real query plan can 
request a `Dictionary` cast target. The doc comment on `spark_cast_postprocess` 
says "Spark cannot specify Dictionary as to_type", and the required schemas 
that reach `SparkSchemaAdapter` come from Spark types, which never carry 
dictionaries. If that is right, the nested-dictionary bug you fixed on the 
parquet side is not user-reachable today and this is defensive cleanup, which 
is worth stating so reviewers know why there is no SQL-level test. If there is 
a reachable path, an end-to-end test would be valuable.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to