fix: keep None in an array wherever it sits - #1975
Open
shcheklein wants to merge 1 commit into
Open
Conversation
`list[int | None]` fails to save when the first element is None:
[1, None] -> [1, None]
[None, 2] -> UdfError: None incompatible with Int64
[None, None] -> UdfError
convert_type read val[0] to decide how to handle a whole array. A leading match
returned the array untouched and the None rode along with it; anything else
converted element by element, where a None had no type to convert and raised. So
the same annotation and the same values succeeded or failed on order alone.
Each element answers for itself now, and a None survives only where
item_type.dc_nullable says one belongs -- which also closes the other half of
it, a trailing None reaching a non-nullable column unchecked.
flatten decided the same way, from value[0]: a leading model meant model_dump()
on every element including the Nones, a leading None meant the models were left
raw, and one element could not answer for a list of mixed shapes. Every element
that carries a type has to agree before a shape is assumed.
Reading needed it too. TypeReadConverter.float turns None into nan, which is
right for a bare float column and wrong for a nullable element; without this
these arrays would save and then read back nan. A nullable element keeps its
None, as a nullable column already does.
Only arrays that hold a None change on disk, and of those only the order that
was already inconsistent with itself:
[{"k": 1}, None] [{"k":1},null] unchanged
[None, {"k": 1}] ["null","{\"k\":1}"] -> [null,{"k":1}]
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 2, 2026
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
list[int | None]fails to save when the first element isNone:Three places decided how to treat a whole array by looking at one element of it.
convert_typereadval[0]: a leading1matched the item type, so the arraywas stored untouched and the
Nonerode along; a leadingNonedid not match,so every element went to a converter — and
Nonehas nothing to convert.flattenreadvalue[0]the same way: a leading model meantmodel_dump()onevery element including the
Nones, a leadingNonemeant the models were leftraw, and neither could answer for a list of mixed shapes.
TypeReadConverter.floatmapsNonetonan, which is right for a bare floatcolumn and wrong for a nullable element — without that, these arrays would newly
save and then read back
[1.0, nan]. ScalarOptional[float]already returnsNone; arrays were the outlier.What that changes
list[int | None][1, None][1, None][1, None][None, 2]UdfError[None, 2][None, None]UdfError[None, None]list[float | None][1.0, None][1.0, nan][1.0, None][None, 2.0]UdfError[None, 2.0]A model nested inside a dict item was converted in one order and not the other:
list[dict[str, Item] | None][{"a": Item(n=1)}, None]TypeError: Object of type Item is not JSON serializable[{'a': Item(n=1)}, None][None, {"a": Item(n=1)}]And a
Nonereached a non-nullable column unchecked, again depending onwhere it sat:
On disk
Only arrays that contain a
Nonechange, and of those only the order that wasalready inconsistent with itself:
list[dict | None][{"k": 1}, None][{"k":1},null][None, {"k": 1}]["null","{\"k\":1}"][null,{"k":1}]Arrays without a
Nonetake exactly the path they took before —list[int],list[str],list[dict],list[Model],list[list[int]],tuple[int, ...],tuple[str, ...],tuple[Model, Model],tuple[int, int], and mixed arrayslike
({"k": 1}, 2)are byte-identical, checked by writing each with bothversions and diffing the stored column.
Left for #1968
How
mainbehaves today, untouched here, because fixing them changes the storedshape of arrays that already round-trip:
({"a": Item(n=2)}, 1)raises;reverse the order and it writes.
({"k": 1}, 2)stores[{'k': 1}, 2],(2, {"k": 1})stores['2', '{"k":1}'].list[int]stores[1,2];tuple[int, ...]stores["1","2"]— what#1963 runs into.
nullindictorAnyfields.Noneat the outer level.list[list[int] | None]writes[[1, 2], [3]]but not[None, [1, 2]].Stack
Noneanywhere in an arrayNoneReplaces #1970, which carried all three at once.