Fix null list of structs being read and written as an empty list - #3834

Open
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct
Open

Fix null list of structs being read and written as an empty list#3834
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

A null list<struct<...>> was rebuilt as an empty list, losing the distinction
between the two. test_null_list_and_map has documented this since #252, asserting
the corrupted value with the correct assertion commented out pending
apache/arrow#38809.

That dependency turns out not to hold for this symptom: from_arrays takes a mask
argument, so carrying the validity bitmap over fixes the null loss on its own. The
rebuild itself is still needed for #38809 and is unchanged.

The write path is affected too, and there the loss is permanent — the Parquet file
pyiceberg produces contains an empty list, so no reader can tell it from a null. The
new unit test covers that direction; the integration assertion covers the read side.

Closes#3833, which has the full analysis and a standalone reproduction.

This change was created with AI assistance.

from_arrays() takes the offsets buffer alone, which cannot express a null
list, so rebuilding the array without a mask collapsed every null
list<struct<...>> into an empty one. On write that is unrecoverable: the
Parquet file itself then holds the empty list.
The rebuild still works around apache/arrow#38809 and stays; only the mask
is added. That restores the assertion test_null_list_and_map has carried
commented out since apache#252.
Closesapache#3833
@slachiewicz
slachiewicz marked this pull request as ready for review August 23, 2026 09:49
The comment claimed the branch could go once apache/arrow#38809 was
fixed. That is fixed in pyarrow 19, but the branch must stay: Iceberg
renames by field-id while cast matches element fields by name, so
dropping it nulls out any field renamed inside a list element.
Verified: the new test passes, and fails with the rebuild removed.
@slachiewicz

Copy link
Copy Markdown
MemberAuthor

Pushed a second commit that removes the # This can be removed once this has been fixed: https://github.com/apache/arrow/issues/38809 comment above the rebuild, and adds a test for what the rebuild is actually load-bearing for.

The comment is wrong twice over. apache/arrow#38809 is fixed as of pyarrow 19.0.0 (apache/arrow#44587 taught the struct cast to fill absent output fields with null), and a cast really does now handle a field superset, reordering, widening and a null list element. But the rebuild still cannot go, because Iceberg renames by field-id while cast matches struct fields by name. Dropping the rebuild and letting the trailing list_array.cast(arrow_field) do the work silently nulls out any field renamed inside a list element:

with rebuild: [[{'after': 1}], [], None, [{'after': 3}]]
rebuild removed: [[{'after': None}], [], None, [{'after': None}]]

That is test__to_requested_schema_renamed_field_in_list_of_structs, which fails without the rebuild. The null-list test alone does not catch it, since on pyarrow 25 the cast preserves the null on its own.

This comment was created with AI assistance.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null list<struct<...>> is written and read as an empty list

1 participant

@slachiewicz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix null list of structs being read and written as an empty list - #3834

Open
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct
Open

Fix null list of structs being read and written as an empty list#3834
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

A null list<struct<...>> was rebuilt as an empty list, losing the distinction
between the two. test_null_list_and_map has documented this since #252, asserting
the corrupted value with the correct assertion commented out pending
apache/arrow#38809.

That dependency turns out not to hold for this symptom: from_arrays takes a mask
argument, so carrying the validity bitmap over fixes the null loss on its own. The
rebuild itself is still needed for #38809 and is unchanged.

The write path is affected too, and there the loss is permanent — the Parquet file
pyiceberg produces contains an empty list, so no reader can tell it from a null. The
new unit test covers that direction; the integration assertion covers the read side.

Closes#3833, which has the full analysis and a standalone reproduction.

This change was created with AI assistance.

from_arrays() takes the offsets buffer alone, which cannot express a null
list, so rebuilding the array without a mask collapsed every null
list<struct<...>> into an empty one. On write that is unrecoverable: the
Parquet file itself then holds the empty list.
The rebuild still works around apache/arrow#38809 and stays; only the mask
is added. That restores the assertion test_null_list_and_map has carried
commented out since apache#252.
Closesapache#3833
@slachiewicz
slachiewicz marked this pull request as ready for review August 23, 2026 09:49
The comment claimed the branch could go once apache/arrow#38809 was
fixed. That is fixed in pyarrow 19, but the branch must stay: Iceberg
renames by field-id while cast matches element fields by name, so
dropping it nulls out any field renamed inside a list element.
Verified: the new test passes, and fails with the rebuild removed.
@slachiewicz

Copy link
Copy Markdown
MemberAuthor

Pushed a second commit that removes the # This can be removed once this has been fixed: https://github.com/apache/arrow/issues/38809 comment above the rebuild, and adds a test for what the rebuild is actually load-bearing for.

The comment is wrong twice over. apache/arrow#38809 is fixed as of pyarrow 19.0.0 (apache/arrow#44587 taught the struct cast to fill absent output fields with null), and a cast really does now handle a field superset, reordering, widening and a null list element. But the rebuild still cannot go, because Iceberg renames by field-id while cast matches struct fields by name. Dropping the rebuild and letting the trailing list_array.cast(arrow_field) do the work silently nulls out any field renamed inside a list element:

with rebuild: [[{'after': 1}], [], None, [{'after': 3}]]
rebuild removed: [[{'after': None}], [], None, [{'after': None}]]

That is test__to_requested_schema_renamed_field_in_list_of_structs, which fails without the rebuild. The null-list test alone does not catch it, since on pyarrow 25 the cast preserves the null on its own.

This comment was created with AI assistance.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null list<struct<...>> is written and read as an empty list

1 participant

@slachiewicz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix null list of structs being read and written as an empty list - #3834

Open
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct
Open

Fix null list of structs being read and written as an empty list#3834
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

A null list<struct<...>> was rebuilt as an empty list, losing the distinction
between the two. test_null_list_and_map has documented this since #252, asserting
the corrupted value with the correct assertion commented out pending
apache/arrow#38809.

That dependency turns out not to hold for this symptom: from_arrays takes a mask
argument, so carrying the validity bitmap over fixes the null loss on its own. The
rebuild itself is still needed for #38809 and is unchanged.

The write path is affected too, and there the loss is permanent — the Parquet file
pyiceberg produces contains an empty list, so no reader can tell it from a null. The
new unit test covers that direction; the integration assertion covers the read side.

Closes#3833, which has the full analysis and a standalone reproduction.

This change was created with AI assistance.

from_arrays() takes the offsets buffer alone, which cannot express a null
list, so rebuilding the array without a mask collapsed every null
list<struct<...>> into an empty one. On write that is unrecoverable: the
Parquet file itself then holds the empty list.
The rebuild still works around apache/arrow#38809 and stays; only the mask
is added. That restores the assertion test_null_list_and_map has carried
commented out since apache#252.
Closesapache#3833
@slachiewicz
slachiewicz marked this pull request as ready for review August 23, 2026 09:49
The comment claimed the branch could go once apache/arrow#38809 was
fixed. That is fixed in pyarrow 19, but the branch must stay: Iceberg
renames by field-id while cast matches element fields by name, so
dropping it nulls out any field renamed inside a list element.
Verified: the new test passes, and fails with the rebuild removed.
@slachiewicz

Copy link
Copy Markdown
MemberAuthor

Pushed a second commit that removes the # This can be removed once this has been fixed: https://github.com/apache/arrow/issues/38809 comment above the rebuild, and adds a test for what the rebuild is actually load-bearing for.

The comment is wrong twice over. apache/arrow#38809 is fixed as of pyarrow 19.0.0 (apache/arrow#44587 taught the struct cast to fill absent output fields with null), and a cast really does now handle a field superset, reordering, widening and a null list element. But the rebuild still cannot go, because Iceberg renames by field-id while cast matches struct fields by name. Dropping the rebuild and letting the trailing list_array.cast(arrow_field) do the work silently nulls out any field renamed inside a list element:

with rebuild: [[{'after': 1}], [], None, [{'after': 3}]]
rebuild removed: [[{'after': None}], [], None, [{'after': None}]]

That is test__to_requested_schema_renamed_field_in_list_of_structs, which fails without the rebuild. The null-list test alone does not catch it, since on pyarrow 25 the cast preserves the null on its own.

This comment was created with AI assistance.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null list<struct<...>> is written and read as an empty list

1 participant

@slachiewicz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix null list of structs being read and written as an empty list - #3834

Open
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct
Open

Fix null list of structs being read and written as an empty list#3834
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

A null list<struct<...>> was rebuilt as an empty list, losing the distinction
between the two. test_null_list_and_map has documented this since #252, asserting
the corrupted value with the correct assertion commented out pending
apache/arrow#38809.

That dependency turns out not to hold for this symptom: from_arrays takes a mask
argument, so carrying the validity bitmap over fixes the null loss on its own. The
rebuild itself is still needed for #38809 and is unchanged.

The write path is affected too, and there the loss is permanent — the Parquet file
pyiceberg produces contains an empty list, so no reader can tell it from a null. The
new unit test covers that direction; the integration assertion covers the read side.

Closes#3833, which has the full analysis and a standalone reproduction.

This change was created with AI assistance.

from_arrays() takes the offsets buffer alone, which cannot express a null
list, so rebuilding the array without a mask collapsed every null
list<struct<...>> into an empty one. On write that is unrecoverable: the
Parquet file itself then holds the empty list.
The rebuild still works around apache/arrow#38809 and stays; only the mask
is added. That restores the assertion test_null_list_and_map has carried
commented out since apache#252.
Closesapache#3833
@slachiewicz
slachiewicz marked this pull request as ready for review August 23, 2026 09:49
The comment claimed the branch could go once apache/arrow#38809 was
fixed. That is fixed in pyarrow 19, but the branch must stay: Iceberg
renames by field-id while cast matches element fields by name, so
dropping it nulls out any field renamed inside a list element.
Verified: the new test passes, and fails with the rebuild removed.
@slachiewicz

Copy link
Copy Markdown
MemberAuthor

Pushed a second commit that removes the # This can be removed once this has been fixed: https://github.com/apache/arrow/issues/38809 comment above the rebuild, and adds a test for what the rebuild is actually load-bearing for.

The comment is wrong twice over. apache/arrow#38809 is fixed as of pyarrow 19.0.0 (apache/arrow#44587 taught the struct cast to fill absent output fields with null), and a cast really does now handle a field superset, reordering, widening and a null list element. But the rebuild still cannot go, because Iceberg renames by field-id while cast matches struct fields by name. Dropping the rebuild and letting the trailing list_array.cast(arrow_field) do the work silently nulls out any field renamed inside a list element:

with rebuild: [[{'after': 1}], [], None, [{'after': 3}]]
rebuild removed: [[{'after': None}], [], None, [{'after': None}]]

That is test__to_requested_schema_renamed_field_in_list_of_structs, which fails without the rebuild. The null-list test alone does not catch it, since on pyarrow 25 the cast preserves the null on its own.

This comment was created with AI assistance.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null list<struct<...>> is written and read as an empty list

1 participant

@slachiewicz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix null list of structs being read and written as an empty list - #3834

Open
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct
Open

Fix null list of structs being read and written as an empty list#3834
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

A null list<struct<...>> was rebuilt as an empty list, losing the distinction
between the two. test_null_list_and_map has documented this since #252, asserting
the corrupted value with the correct assertion commented out pending
apache/arrow#38809.

That dependency turns out not to hold for this symptom: from_arrays takes a mask
argument, so carrying the validity bitmap over fixes the null loss on its own. The
rebuild itself is still needed for #38809 and is unchanged.

The write path is affected too, and there the loss is permanent — the Parquet file
pyiceberg produces contains an empty list, so no reader can tell it from a null. The
new unit test covers that direction; the integration assertion covers the read side.

Closes#3833, which has the full analysis and a standalone reproduction.

This change was created with AI assistance.

from_arrays() takes the offsets buffer alone, which cannot express a null
list, so rebuilding the array without a mask collapsed every null
list<struct<...>> into an empty one. On write that is unrecoverable: the
Parquet file itself then holds the empty list.
The rebuild still works around apache/arrow#38809 and stays; only the mask
is added. That restores the assertion test_null_list_and_map has carried
commented out since apache#252.
Closesapache#3833
@slachiewicz
slachiewicz marked this pull request as ready for review August 23, 2026 09:49
The comment claimed the branch could go once apache/arrow#38809 was
fixed. That is fixed in pyarrow 19, but the branch must stay: Iceberg
renames by field-id while cast matches element fields by name, so
dropping it nulls out any field renamed inside a list element.
Verified: the new test passes, and fails with the rebuild removed.
@slachiewicz

Copy link
Copy Markdown
MemberAuthor

Pushed a second commit that removes the # This can be removed once this has been fixed: https://github.com/apache/arrow/issues/38809 comment above the rebuild, and adds a test for what the rebuild is actually load-bearing for.

The comment is wrong twice over. apache/arrow#38809 is fixed as of pyarrow 19.0.0 (apache/arrow#44587 taught the struct cast to fill absent output fields with null), and a cast really does now handle a field superset, reordering, widening and a null list element. But the rebuild still cannot go, because Iceberg renames by field-id while cast matches struct fields by name. Dropping the rebuild and letting the trailing list_array.cast(arrow_field) do the work silently nulls out any field renamed inside a list element:

with rebuild: [[{'after': 1}], [], None, [{'after': 3}]]
rebuild removed: [[{'after': None}], [], None, [{'after': None}]]

That is test__to_requested_schema_renamed_field_in_list_of_structs, which fails without the rebuild. The null-list test alone does not catch it, since on pyarrow 25 the cast preserves the null on its own.

This comment was created with AI assistance.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null list<struct<...>> is written and read as an empty list

1 participant

@slachiewicz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix null list of structs being read and written as an empty list - #3834

Open
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct
Open

Fix null list of structs being read and written as an empty list#3834
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

A null list<struct<...>> was rebuilt as an empty list, losing the distinction
between the two. test_null_list_and_map has documented this since #252, asserting
the corrupted value with the correct assertion commented out pending
apache/arrow#38809.

That dependency turns out not to hold for this symptom: from_arrays takes a mask
argument, so carrying the validity bitmap over fixes the null loss on its own. The
rebuild itself is still needed for #38809 and is unchanged.

The write path is affected too, and there the loss is permanent — the Parquet file
pyiceberg produces contains an empty list, so no reader can tell it from a null. The
new unit test covers that direction; the integration assertion covers the read side.

Closes#3833, which has the full analysis and a standalone reproduction.

This change was created with AI assistance.

from_arrays() takes the offsets buffer alone, which cannot express a null
list, so rebuilding the array without a mask collapsed every null
list<struct<...>> into an empty one. On write that is unrecoverable: the
Parquet file itself then holds the empty list.
The rebuild still works around apache/arrow#38809 and stays; only the mask
is added. That restores the assertion test_null_list_and_map has carried
commented out since apache#252.
Closesapache#3833
@slachiewicz
slachiewicz marked this pull request as ready for review August 23, 2026 09:49
The comment claimed the branch could go once apache/arrow#38809 was
fixed. That is fixed in pyarrow 19, but the branch must stay: Iceberg
renames by field-id while cast matches element fields by name, so
dropping it nulls out any field renamed inside a list element.
Verified: the new test passes, and fails with the rebuild removed.
@slachiewicz

Copy link
Copy Markdown
MemberAuthor

Pushed a second commit that removes the # This can be removed once this has been fixed: https://github.com/apache/arrow/issues/38809 comment above the rebuild, and adds a test for what the rebuild is actually load-bearing for.

The comment is wrong twice over. apache/arrow#38809 is fixed as of pyarrow 19.0.0 (apache/arrow#44587 taught the struct cast to fill absent output fields with null), and a cast really does now handle a field superset, reordering, widening and a null list element. But the rebuild still cannot go, because Iceberg renames by field-id while cast matches struct fields by name. Dropping the rebuild and letting the trailing list_array.cast(arrow_field) do the work silently nulls out any field renamed inside a list element:

with rebuild: [[{'after': 1}], [], None, [{'after': 3}]]
rebuild removed: [[{'after': None}], [], None, [{'after': None}]]

That is test__to_requested_schema_renamed_field_in_list_of_structs, which fails without the rebuild. The null-list test alone does not catch it, since on pyarrow 25 the cast preserves the null on its own.

This comment was created with AI assistance.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null list<struct<...>> is written and read as an empty list

1 participant

@slachiewicz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix null list of structs being read and written as an empty list - #3834

Open
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct
Open

Fix null list of structs being read and written as an empty list#3834
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

A null list<struct<...>> was rebuilt as an empty list, losing the distinction
between the two. test_null_list_and_map has documented this since #252, asserting
the corrupted value with the correct assertion commented out pending
apache/arrow#38809.

That dependency turns out not to hold for this symptom: from_arrays takes a mask
argument, so carrying the validity bitmap over fixes the null loss on its own. The
rebuild itself is still needed for #38809 and is unchanged.

The write path is affected too, and there the loss is permanent — the Parquet file
pyiceberg produces contains an empty list, so no reader can tell it from a null. The
new unit test covers that direction; the integration assertion covers the read side.

Closes#3833, which has the full analysis and a standalone reproduction.

This change was created with AI assistance.

from_arrays() takes the offsets buffer alone, which cannot express a null
list, so rebuilding the array without a mask collapsed every null
list<struct<...>> into an empty one. On write that is unrecoverable: the
Parquet file itself then holds the empty list.
The rebuild still works around apache/arrow#38809 and stays; only the mask
is added. That restores the assertion test_null_list_and_map has carried
commented out since apache#252.
Closesapache#3833
@slachiewicz
slachiewicz marked this pull request as ready for review August 23, 2026 09:49
The comment claimed the branch could go once apache/arrow#38809 was
fixed. That is fixed in pyarrow 19, but the branch must stay: Iceberg
renames by field-id while cast matches element fields by name, so
dropping it nulls out any field renamed inside a list element.
Verified: the new test passes, and fails with the rebuild removed.
@slachiewicz

Copy link
Copy Markdown
MemberAuthor

Pushed a second commit that removes the # This can be removed once this has been fixed: https://github.com/apache/arrow/issues/38809 comment above the rebuild, and adds a test for what the rebuild is actually load-bearing for.

The comment is wrong twice over. apache/arrow#38809 is fixed as of pyarrow 19.0.0 (apache/arrow#44587 taught the struct cast to fill absent output fields with null), and a cast really does now handle a field superset, reordering, widening and a null list element. But the rebuild still cannot go, because Iceberg renames by field-id while cast matches struct fields by name. Dropping the rebuild and letting the trailing list_array.cast(arrow_field) do the work silently nulls out any field renamed inside a list element:

with rebuild: [[{'after': 1}], [], None, [{'after': 3}]]
rebuild removed: [[{'after': None}], [], None, [{'after': None}]]

That is test__to_requested_schema_renamed_field_in_list_of_structs, which fails without the rebuild. The null-list test alone does not catch it, since on pyarrow 25 the cast preserves the null on its own.

This comment was created with AI assistance.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null list<struct<...>> is written and read as an empty list

1 participant

@slachiewicz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix null list of structs being read and written as an empty list - #3834

Open
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct
Open

Fix null list of structs being read and written as an empty list#3834
slachiewicz wants to merge 2 commits into
apache:mainfrom
slachiewicz:fix-null-list-of-struct

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

A null list<struct<...>> was rebuilt as an empty list, losing the distinction
between the two. test_null_list_and_map has documented this since #252, asserting
the corrupted value with the correct assertion commented out pending
apache/arrow#38809.

That dependency turns out not to hold for this symptom: from_arrays takes a mask
argument, so carrying the validity bitmap over fixes the null loss on its own. The
rebuild itself is still needed for #38809 and is unchanged.

The write path is affected too, and there the loss is permanent — the Parquet file
pyiceberg produces contains an empty list, so no reader can tell it from a null. The
new unit test covers that direction; the integration assertion covers the read side.

Closes#3833, which has the full analysis and a standalone reproduction.

This change was created with AI assistance.

from_arrays() takes the offsets buffer alone, which cannot express a null
list, so rebuilding the array without a mask collapsed every null
list<struct<...>> into an empty one. On write that is unrecoverable: the
Parquet file itself then holds the empty list.
The rebuild still works around apache/arrow#38809 and stays; only the mask
is added. That restores the assertion test_null_list_and_map has carried
commented out since apache#252.
Closesapache#3833
@slachiewicz
slachiewicz marked this pull request as ready for review August 23, 2026 09:49
The comment claimed the branch could go once apache/arrow#38809 was
fixed. That is fixed in pyarrow 19, but the branch must stay: Iceberg
renames by field-id while cast matches element fields by name, so
dropping it nulls out any field renamed inside a list element.
Verified: the new test passes, and fails with the rebuild removed.
@slachiewicz

Copy link
Copy Markdown
MemberAuthor

Pushed a second commit that removes the # This can be removed once this has been fixed: https://github.com/apache/arrow/issues/38809 comment above the rebuild, and adds a test for what the rebuild is actually load-bearing for.

The comment is wrong twice over. apache/arrow#38809 is fixed as of pyarrow 19.0.0 (apache/arrow#44587 taught the struct cast to fill absent output fields with null), and a cast really does now handle a field superset, reordering, widening and a null list element. But the rebuild still cannot go, because Iceberg renames by field-id while cast matches struct fields by name. Dropping the rebuild and letting the trailing list_array.cast(arrow_field) do the work silently nulls out any field renamed inside a list element:

with rebuild: [[{'after': 1}], [], None, [{'after': 3}]]
rebuild removed: [[{'after': None}], [], None, [{'after': None}]]

That is test__to_requested_schema_renamed_field_in_list_of_structs, which fails without the rebuild. The null-list test alone does not catch it, since on pyarrow 25 the cast preserves the null on its own.

This comment was created with AI assistance.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null list<struct<...>> is written and read as an empty list

1 participant

@slachiewicz