You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by gi...@apache.org on 2026/09/03 16:15:31 UTC

(iceberg-rust) branch gh-pages updated: deploy: c8fc70e5dcf4591a4c0d834223b4c7078ddb28c2

This is an automated email from the ASF dual-hosted git repository.

github-actions[bot] pushed a commit to branch gh-pages
in repository https://gitbox.apache.org/repos/asf/iceberg-rust.git


The following commit(s) were added to refs/heads/gh-pages by this push:
     new 6ab3247d7 deploy: c8fc70e5dcf4591a4c0d834223b4c7078ddb28c2
6ab3247d7 is described below

commit 6ab3247d71ea8fd33cc9db9bcc1a31dc11e94f64
Author: kevinjqliu <ke...@users.noreply.github.com>
AuthorDate: Thu Sep 3 16:15:25 2026 +0000

    deploy: c8fc70e5dcf4591a4c0d834223b4c7078ddb28c2
---
 api/help.html                                      |    2 +-
 .../desc/{bbc4099aa8.js => d73bd18531.js}          |    2 +-
 .../entry/{97cdaa7033.js => 2aa3f5c66f.js}         |    2 +-
 .../entry/{50de0d5c5c.js => 35132d2fe4.js}         |    2 +-
 .../entry/{32709190ce.js => ad6541c9f6.js}         |    2 +-
 .../function/{3609a6435f.js => d4549d7461.js}      |    2 +-
 .../function/{e2b0ae1fdc.js => e98af89590.js}      |    2 +-
 api/search.index/path/3a2eb7bbbc.js                |    1 +
 api/search.index/path/9bda7dc8bb.js                |    1 -
 api/search.index/root.js                           |    2 +-
 .../type/{4b8a6e6ab4.js => 0a0b28a655.js}          |    2 +-
 api/settings.html                                  |    2 +-
 api/src/iceberg/arrow/reader/pipeline.rs.html      | 2437 ++++++++++----------
 .../arrow/reader/positional_deletes.rs.html        |    4 +-
 14 files changed, 1232 insertions(+), 1231 deletions(-)

diff --git a/api/help.html b/api/help.html
index d9f70ae96..113a18e54 100644
--- a/api/help.html
+++ b/api/help.html
@@ -1 +1 @@
-<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="rustdoc"><meta name="description" content="Documentation for Rustdoc"><title>Help</title><script>if(window.location.protocol!=="file:")document.head.insertAdjacentHTML("beforeend","SourceSerif4-Regular-6b053e98.ttf.woff2,FiraSans-Italic-81dc35de.woff2,FiraSans-Regular-0fe48ade.woff2,FiraSans-MediumItalic-ccf7e434.woff2,FiraSans-M [...]
\ No newline at end of file
+<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="rustdoc"><meta name="description" content="Documentation for Rustdoc"><title>Help</title><script>if(window.location.protocol!=="file:")document.head.insertAdjacentHTML("beforeend","SourceSerif4-Regular-6b053e98.ttf.woff2,FiraSans-Italic-81dc35de.woff2,FiraSans-Regular-0fe48ade.woff2,FiraSans-MediumItalic-ccf7e434.woff2,FiraSans-M [...]
\ No newline at end of file
diff --git a/api/search.index/desc/bbc4099aa8.js b/api/search.index/desc/d73bd18531.js
similarity index 99%
rename from api/search.index/desc/bbc4099aa8.js
rename to api/search.index/desc/d73bd18531.js
index 472674a73..ee21b1328 100644
--- a/api/search.index/desc/bbc4099aa8.js
+++ b/api/search.index/desc/d73bd18531.js
@@ -1 +1 @@
-rd_("BkThe catalog type that this builder creates.AkThe associated writer type.B`The associated file writer type.AlReturn type of this visitor.0BkReturn type of this visitor on arrow field.BlReturn type of this visitor on arrow schema.BaLocal filesystem storage factory.B`HuggingFace Hub storage factory.B`HuggingFace Hub storage variant.CgMatch fields by their ID stored in Arrow field metadataAjContains the success value0CoOr predicate, for example, <code>a &gt; 10 OR b &lt; 20</code>.EnA [...]
\ No newline at end of file
+rd_("BkThe catalog type that this builder creates.AkThe associated writer type.B`The associated file writer type.AlReturn type of this visitor.0BkReturn type of this visitor on arrow field.BlReturn type of this visitor on arrow schema.BaLocal filesystem storage factory.B`HuggingFace Hub storage factory.B`HuggingFace Hub storage variant.CgMatch fields by their ID stored in Arrow field metadataAjContains the success value0CoOr predicate, for example, <code>a &gt; 10 OR b &lt; 20</code>.EnA [...]
\ No newline at end of file
diff --git a/api/search.index/entry/97cdaa7033.js b/api/search.index/entry/2aa3f5c66f.js
similarity index 89%
rename from api/search.index/entry/97cdaa7033.js
rename to api/search.index/entry/2aa3f5c66f.js
index bd7544b29..ffa8713a3 100644
--- a/api/search.index/entry/97cdaa7033.js
+++ b/api/search.index/entry/2aa3f5c66f.js
@@ -1 +1 @@
-rd_("Ba[8528,13,8529,3240,8570,2711,0,0]Ba[8528,13,8529,3240,8850,2711,0,0]Ba[8528,13,8529,3240,8861,2711,0,0]Ba[8528,13,8529,3240,8238,2711,0,0]Ba[8528,13,8529,3240,8247,2711,0,0]Ba[8528,13,8529,3240,7656,2711,0,0]Ba[8528,13,8529,3240,8143,2711,0,0]Ba[8528,13,8529,3240,8225,2711,0,0]Ba[8528,13,8529,3240,8224,2711,0,0]Ba[8528,13,8529,3240,8328,2711,0,0]Ba[8528,13,8529,3240,8495,2711,0,0]Ba[8459,13,8460,4033,7388,2711,0,0]Ba[8298,13,3233,3233,8487,2711,0,0]Ba[8298,13,3233,3233,8758,2711,0 [...]
\ No newline at end of file
+rd_("Ba[8528,13,8529,3240,8570,2711,0,0]Ba[8528,13,8529,3240,8850,2711,0,0]Ba[8528,13,8529,3240,8861,2711,0,0]Ba[8528,13,8529,3240,8238,2711,0,0]Ba[8528,13,8529,3240,8247,2711,0,0]Ba[8528,13,8529,3240,7656,2711,0,0]Ba[8528,13,8529,3240,8143,2711,0,0]Ba[8528,13,8529,3240,8225,2711,0,0]Ba[8528,13,8529,3240,8224,2711,0,0]Ba[8528,13,8529,3240,8328,2711,0,0]Ba[8528,13,8529,3240,8495,2711,0,0]Ba[8459,13,8460,4033,7388,2711,0,0]Ba[8298,13,3233,3233,8487,2711,0,0]Ba[8298,13,3233,3233,8758,2711,0 [...]
\ No newline at end of file
diff --git a/api/search.index/entry/50de0d5c5c.js b/api/search.index/entry/35132d2fe4.js
similarity index 89%
rename from api/search.index/entry/50de0d5c5c.js
rename to api/search.index/entry/35132d2fe4.js
index ad04db9af..be792eee4 100644
--- a/api/search.index/entry/50de0d5c5c.js
+++ b/api/search.index/entry/35132d2fe4.js
@@ -1 +1 @@
-rd_("An[4110,17,4111,4025,7512,0,0,0]An[4110,17,3941,3941,8488,0,0,0]An[4110,17,7097,7097,8124,0,0,0]An[4110,17,2434,4398,7389,0,0,0]An[4110,17,2434,4398,8708,0,0,0]An[4110,17,2760,3903,8223,0,0,0]0An[4110,15,1584,1584,8133,0,0,0]An[8674,15,8675,8675,8576,0,0,0]0An[8674,15,8675,8675,7527,0,0,0]An[4110,15,2760,3244,7515,0,0,0]3An[4110,15,4111,3204,3304,0,0,0]An[8529,15,3203,3203,3305,0,0,0]An[4110,15,1584,5551,5221,0,0,0]An[4110,15,1584,5551,7509,0,0,0]65An[8459,15,8460,4033,7388,0,0,0]An [...]
\ No newline at end of file
+rd_("An[4110,17,4111,4025,7512,0,0,0]An[4110,17,3941,3941,8488,0,0,0]An[4110,17,7097,7097,8124,0,0,0]An[4110,17,2434,4398,7389,0,0,0]An[4110,17,2434,4398,8708,0,0,0]An[4110,17,2760,3902,8223,0,0,0]0An[4110,15,1584,1584,8133,0,0,0]An[8674,15,8675,8675,8576,0,0,0]0An[8674,15,8675,8675,7527,0,0,0]An[4110,15,2760,3244,7515,0,0,0]3An[4110,15,4111,3203,3304,0,0,0]An[8529,15,3205,3205,3305,0,0,0]An[4110,15,1584,5551,5221,0,0,0]An[4110,15,1584,5551,7509,0,0,0]65An[8459,15,8460,4033,7388,0,0,0]An [...]
\ No newline at end of file
diff --git a/api/search.index/entry/32709190ce.js b/api/search.index/entry/ad6541c9f6.js
similarity index 90%
rename from api/search.index/entry/32709190ce.js
rename to api/search.index/entry/ad6541c9f6.js
index 084f2fe14..21bb8156c 100644
--- a/api/search.index/entry/32709190ce.js
+++ b/api/search.index/entry/ad6541c9f6.js
@@ -1 +1 @@
-rd_("Ba[4110,13,4111,3204,5200,2700,0,0]Ba[4110,13,4111,8183,7923,2700,0,0]Ba[4110,13,4111,4131,7537,2700,0,0]Ba[4110,13,4111,4025,7524,2700,0,0]Ba[4110,13,4111,4025,5212,2700,0,0]Ba[4110,13,4111,4025,6015,2700,0,0]Ba[4110,13,4111,4025,7401,2700,0,0]Ba[4110,13,4111,4025,6971,2700,0,0]Ba[4110,13,4111,4025,7941,2700,0,0]Ba[4110,13,4111,4025,6973,2700,0,0]Ba[4110,13,4111,4025,7213,2700,0,0]Ba[4110,13,4111,4025,6018,2700,0,0]Ba[4110,13,4111,4129,7386,2700,0,0]Ba[4110,13,4111,4129,3985,2700,0 [...]
\ No newline at end of file
+rd_("Ba[4110,13,4111,3203,5200,2700,0,0]Ba[4110,13,4111,8183,7923,2700,0,0]Ba[4110,13,4111,4131,7537,2700,0,0]Ba[4110,13,4111,4025,7524,2700,0,0]Ba[4110,13,4111,4025,5212,2700,0,0]Ba[4110,13,4111,4025,6015,2700,0,0]Ba[4110,13,4111,4025,7401,2700,0,0]Ba[4110,13,4111,4025,6971,2700,0,0]Ba[4110,13,4111,4025,7941,2700,0,0]Ba[4110,13,4111,4025,6973,2700,0,0]Ba[4110,13,4111,4025,7213,2700,0,0]Ba[4110,13,4111,4025,6018,2700,0,0]Ba[4110,13,4111,4129,7386,2700,0,0]Ba[4110,13,4111,4129,3985,2700,0 [...]
\ No newline at end of file
diff --git a/api/search.index/function/3609a6435f.js b/api/search.index/function/d4549d7461.js
similarity index 96%
rename from api/search.index/function/3609a6435f.js
rename to api/search.index/function/d4549d7461.js
index 34cbaf0df..cdb1cddec 100644
--- a/api/search.index/function/3609a6435f.js
+++ b/api/search.index/function/d4549d7461.js
@@ -1 +1 @@
-rd_("Bf[\"{{{BKFl{BHJ`}}{BKFl{BHJ`}}}JFh}\",[]]Bf[\"{{{BKFl{CMNf}}{BKFl{CMNf}}}JFh}\",[]]Bf[\"{{{BKFl{CJLh}}{BKFl{CJLh}}}JFh}\",[]]Bf[\"{{{BKFl{BHKh}}{BKFl{BHKh}}}JFh}\",[]]Bf[\"{{{BKFl{BNOn}}{BKFl{BNOn}}}JFh}\",[]]Bf[\"{{{BKFl{CN@j}}{BKFl{CN@j}}}JFh}\",[]]Bf[\"{{{BKFl{CFGj}}{BKFl{CFGj}}}JFh}\",[]]Bf[\"{{{BKFl{BO@d}}{BKFl{BO@d}}}JFh}\",[]]Bf[\"{{{BKFl{BHM`}}{BKFl{BHM`}}}JFh}\",[]]Bf[\"{{{BKFl{CJJl}}{BKFl{CJJl}}}JFh}\",[]]Cg[\"{{{BKFl{{COH`{c}}}}{BKFl{{COH`{c}}}}}JFhBHLb}\",[\"T\"]]Cg[\"{ [...]
\ No newline at end of file
+rd_("Bf[\"{{{BKFl{BHJ`}}{BKFl{BHJ`}}}JFh}\",[]]Bf[\"{{{BKFl{CMNf}}{BKFl{CMNf}}}JFh}\",[]]Bf[\"{{{BKFl{CJLh}}{BKFl{CJLh}}}JFh}\",[]]Bf[\"{{{BKFl{BHKh}}{BKFl{BHKh}}}JFh}\",[]]Bf[\"{{{BKFl{BNOn}}{BKFl{BNOn}}}JFh}\",[]]Bf[\"{{{BKFl{CN@j}}{BKFl{CN@j}}}JFh}\",[]]Bf[\"{{{BKFl{CFGj}}{BKFl{CFGj}}}JFh}\",[]]Bf[\"{{{BKFl{BO@d}}{BKFl{BO@d}}}JFh}\",[]]Bf[\"{{{BKFl{BHM`}}{BKFl{BHM`}}}JFh}\",[]]Bf[\"{{{BKFl{CJJl}}{BKFl{CJJl}}}JFh}\",[]]Cg[\"{{{BKFl{{COH`{c}}}}{BKFl{{COH`{c}}}}}JFhBHLb}\",[\"T\"]]Cg[\"{ [...]
\ No newline at end of file
diff --git a/api/search.index/function/e2b0ae1fdc.js b/api/search.index/function/e98af89590.js
similarity index 94%
rename from api/search.index/function/e2b0ae1fdc.js
rename to api/search.index/function/e98af89590.js
index 0cb5cd9de..1ebdeaa3d 100644
--- a/api/search.index/function/e2b0ae1fdc.js
+++ b/api/search.index/function/e98af89590.js
@@ -1 +1 @@
-rd_("Bl[\"{{{BKFl{CIMf}}}{{AIM`{{BBFn{CGb}}}}}}\",[]]Be[\"{{{BKFl{CKNb}}}{{AIM`{AIEb}}}}\",[]]Cg[\"{{{CCh{CIHl}}{BKFl{AEFl}}}{{AILl{{CCh{CIHl}}}}}}\",[]]C`[\"{{DBFj{BKFl{{AICd{ICf}}}}}{{AIM`{DBFj}}}}\",[]]Be[\"{{{BKFl{AIMj}}}{{AIM`{AIMh}}}}\",[]]Aa[\"{BBI`AIKl}\",[]]B`[\"{{DACbICf}{{AIM`{DACb}}}}\",[]]B`[\"{{DBFjICh}{{AIM`{DBFj}}}}\",[]]Ca[\"{{{BKFl{CIMf}}{BKFl{AIMj}}}{{AIM`{BNOj}}}}\",[]]Cd[\"{{{BKFl{CHCb}}}{{AIKl{{BKFl{{CCh{BNOj}}}}}}}}\",[]]Ba[\"{{DBFjCII`}{{AIM`{DBFj}}}}\",[]]Ba[\"{{ [...]
\ No newline at end of file
+rd_("Bl[\"{{{BKFl{CIMf}}}{{AIM`{{BBFn{CGb}}}}}}\",[]]Be[\"{{{BKFl{CKNb}}}{{AIM`{AIEb}}}}\",[]]Cg[\"{{{CCh{CIHl}}{BKFl{AEFl}}}{{AILn{{CCh{CIHl}}}}}}\",[]]C`[\"{{DBFj{BKFl{{AICd{ICf}}}}}{{AIM`{DBFj}}}}\",[]]Be[\"{{{BKFl{AIMj}}}{{AIM`{AIMh}}}}\",[]]Aa[\"{BBI`AIKl}\",[]]B`[\"{{DACbICf}{{AIM`{DACb}}}}\",[]]B`[\"{{DBFjICh}{{AIM`{DBFj}}}}\",[]]Ca[\"{{{BKFl{CIMf}}{BKFl{AIMj}}}{{AIM`{BNOj}}}}\",[]]Cd[\"{{{BKFl{CHCb}}}{{AIKl{{BKFl{{CCh{BNOj}}}}}}}}\",[]]Ba[\"{{DBFjCII`}{{AIM`{DBFj}}}}\",[]]Ba[\"{{ [...]
\ No newline at end of file
diff --git a/api/search.index/path/3a2eb7bbbc.js b/api/search.index/path/3a2eb7bbbc.js
new file mode 100644
index 000000000..14fb30a8a
--- /dev/null
+++ b/api/search.index/path/3a2eb7bbbc.js
@@ -0,0 +1 @@
+rd_("g[17,\"\"]000f[1,\"\"]0Aa[10,\"bnum::cast\"]Bn[15,\"iceberg_storage_opendal::OpenDalStorage\"]Eg[5,\"tower_http::follow_redirect::policy::or\",\"tower_http::follow_redirect::policy::or\"]1Ce[15,\"iceberg_storage_opendal::OpenDalStorageFactory\"]4Bb[2,\"iceberg::io::storage::config\"]m[2,\"iceberg\"]Ai[2,\"iceberg::encryption\"]2Am[2,\"iceberg_storage_opendal\"]8Ei[5,\"tower_http::follow_redirect::policy::and\",\"tower_http::follow_redirect::policy::and\"]A`[10,\"core::any\"]Ao[5,\"a [...]
\ No newline at end of file
diff --git a/api/search.index/path/9bda7dc8bb.js b/api/search.index/path/9bda7dc8bb.js
deleted file mode 100644
index dcad809f3..000000000
--- a/api/search.index/path/9bda7dc8bb.js
+++ /dev/null
@@ -1 +0,0 @@
-rd_("g[17,\"\"]000f[1,\"\"]0Aa[10,\"bnum::cast\"]Bn[15,\"iceberg_storage_opendal::OpenDalStorage\"]Eg[5,\"tower_http::follow_redirect::policy::or\",\"tower_http::follow_redirect::policy::or\"]1Ce[15,\"iceberg_storage_opendal::OpenDalStorageFactory\"]4Bb[2,\"iceberg::io::storage::config\"]m[2,\"iceberg\"]Ai[2,\"iceberg::encryption\"]2Am[2,\"iceberg_storage_opendal\"]8Ei[5,\"tower_http::follow_redirect::policy::and\",\"tower_http::follow_redirect::policy::and\"]A`[10,\"core::any\"]Ao[5,\"a [...]
\ No newline at end of file
diff --git a/api/search.index/root.js b/api/search.index/root.js
index 76e8d074f..a13b48598 100644
--- a/api/search.index/root.js
+++ b/api/search.index/root.js
@@ -1 +1 @@
-rr_('{"normalizedName":{"I":"VYLAAAAUehSTAVsH1gxyD5kPJQHAAAAwrAObBAEFNP8EdA8lAcAAABB0DzZ0F8MiRQHAAAAS2AxypQGsA5sEAQV1/MBEAgAAQVsH1gxyD5kPEP8EEXMPHwAgACEAlAFUESIgYSIVAcAAACFLEe4cbqYBBQLAAAATDyAQ7RxhcwEBAcUAABxRZDswAAABAAD0AAMACwUAACIFAACXCfIABQLDAACnKyKiYCFkc0UBwgAAFRwbY44KjwqQCskKFQHAAAAS2AxypQEUAcJ0ZAAAFmVmYXVsdL4cZCUFdhsAAf4IThwAIalwHPnEAAAgfHMA0j8bAAC+FQHRAAAWzSJkoyIVAdYAABbbImTMIjHAAAAvEe8e8B7zgQJmcGUBwwAAFtUiZNEhXiKSIpMiuCK8IkUBwwAAFs8iZNcapB5CIqsiIwACxAAAITFhbvQqHAAApAFUBDcA+4YJAtA [...]
\ No newline at end of file
+rr_('{"normalizedName":{"I":"VYLAAAAUehSTAVsH1gxyD5kPJQHAAAAwrAObBAEFNP8EdA8lAcAAABB0DzZ0F8MiRQHAAAAS2AxypQGsA5sEAQV1/MBEAgAAQVsH1gxyD5kPEP8EEXMPHwAgACEAlAFUESIgYSIVAcAAACFLEe4cbqYBBQLAAAATDyAQ7RxhcwEBAcUAABxRZDswAAABAAD0AAMACwUAACIFAACXCfIABQLDAACnKyKiYCFkc0UBwgAAFRwbY44KjwqQCskKFQHAAAAS2AxypQEUAcJ0ZAAAFmVmYXVsdL4cZCUFdhsAAf4IThwAIalwHPnEAAAgfHMA0j8bAAC+FQHRAAAWzSJkoyIVAdYAABbbImTMIjHAAAAvEe8e8B7zgQJmcGUBwwAAFtUiZNEhXiKSIpMiuCK8IkUBwwAAFs8iZNcapB5CIqsiIwACxAAAITFhbvQqHAAApAFUBDcA+4YJAtA [...]
\ No newline at end of file
diff --git a/api/search.index/type/4b8a6e6ab4.js b/api/search.index/type/0a0b28a655.js
similarity index 98%
rename from api/search.index/type/4b8a6e6ab4.js
rename to api/search.index/type/0a0b28a655.js
index 4cb37b203..eb7aeebec 100644
--- a/api/search.index/type/4b8a6e6ab4.js
+++ b/api/search.index/type/0a0b28a655.js
@@ -1 +1 @@
-rd_("Ec[\"AAAAAAAAAAABshwAAAJqIQAA7yEAAAAAAAAAAAFJCQAA\",\"AAAAAAAAAAABshwAAAJqIQAA7yEAAA==\"]Bg[\"AAAAAAAAAAAAAAAAAdoKAAAB2woAAA==\",\"\"]Og[\"AAAAAAAAAAAAOjAAAAEAAAAAABEAEAAAACkRsRX1HPYcgR2CHfcd+B0JHwofRCBFIEogSyCcIJ0goiCjIANGCQAAhwwAAEEPAAA7MAAAAQAAJgAOAKYEAABgDwAAKhECALIVAQBFGwEAMxwBAPccAwCDHQMA+R0DAAsfAwBGIAMATCABAJ4gAwCkIAEABEcJAABICQAAiAwAAEIPAAAFpwQAAKgEAABMDwAAYQ8AAGIPAAA=\",\"\"]Gg[\"AAAAAAAAAAAAAAA6MAAAAQAAAAAAEgAQAAAAKxEsEbMVRRtGGzMcNBz5HPochR2GHfsd/B0NHw4fSCBJIKAgoSABSAkAAAO [...]
\ No newline at end of file
+rd_("Ec[\"AAAAAAAAAAABshwAAAJqIQAA7yEAAAAAAAAAAAFJCQAA\",\"AAAAAAAAAAABshwAAAJqIQAA7yEAAA==\"]Bg[\"AAAAAAAAAAAAAAAAAdoKAAAB2woAAA==\",\"\"]Og[\"AAAAAAAAAAAAOjAAAAEAAAAAABEAEAAAACkRsRX1HPYcgR2CHfcd+B0JHwofRCBFIEogSyCcIJ0goiCjIANGCQAAhwwAAEEPAAA7MAAAAQAAJgAOAKYEAABgDwAAKhECALIVAQBFGwEAMxwBAPccAwCDHQMA+R0DAAsfAwBGIAMATCABAJ4gAwCkIAEABEcJAABICQAAiAwAAEIPAAAFpwQAAKgEAABMDwAAYQ8AAGIPAAA=\",\"\"]Gg[\"AAAAAAAAAAAAAAA6MAAAAQAAAAAAEgAQAAAAKxEsEbMVRRtGGzMcNBz5HPochR2GHfsd/B0NHw4fSCBJIKAgoSABSAkAAAO [...]
\ No newline at end of file
diff --git a/api/settings.html b/api/settings.html
index 317f2288a..479bd18b6 100644
--- a/api/settings.html
+++ b/api/settings.html
@@ -1 +1 @@
-<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="rustdoc"><meta name="description" content="Settings of Rustdoc"><title>Settings</title><script>if(window.location.protocol!=="file:")document.head.insertAdjacentHTML("beforeend","SourceSerif4-Regular-6b053e98.ttf.woff2,FiraSans-Italic-81dc35de.woff2,FiraSans-Regular-0fe48ade.woff2,FiraSans-MediumItalic-ccf7e434.woff2,FiraSans-Med [...]
\ No newline at end of file
+<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="rustdoc"><meta name="description" content="Settings of Rustdoc"><title>Settings</title><script>if(window.location.protocol!=="file:")document.head.insertAdjacentHTML("beforeend","SourceSerif4-Regular-6b053e98.ttf.woff2,FiraSans-Italic-81dc35de.woff2,FiraSans-Regular-0fe48ade.woff2,FiraSans-MediumItalic-ccf7e434.woff2,FiraSans-Med [...]
\ No newline at end of file
diff --git a/api/src/iceberg/arrow/reader/pipeline.rs.html b/api/src/iceberg/arrow/reader/pipeline.rs.html
index b345323a2..095f8b995 100644
--- a/api/src/iceberg/arrow/reader/pipeline.rs.html
+++ b/api/src/iceberg/arrow/reader/pipeline.rs.html
@@ -2410,1222 +2410,1223 @@
 <a href=#2410 id=2410 data-nosnippet>2410</a>
 <a href=#2411 id=2411 data-nosnippet>2411</a>        <span class="kw">let </span>delete = FileScanTaskDeleteFile::builder()
 <a href=#2412 id=2412 data-nosnippet>2412</a>            .with_file_path(del_path.clone())
-<a href=#2413 id=2413 data-nosnippet>2413</a>            .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>del_path).unwrap().len())
-<a href=#2414 id=2414 data-nosnippet>2414</a>            .with_file_type(DataContentType::PositionDeletes)
-<a href=#2415 id=2415 data-nosnippet>2415</a>            .with_partition_spec_id(<span class="number">0</span>)
-<a href=#2416 id=2416 data-nosnippet>2416</a>            .build();
-<a href=#2417 id=2417 data-nosnippet>2417</a>        <span class="kw">let </span>task = row_id_task_with_options(data_path, <span class="prelude-val">Some</span>(<span class="number">100</span>), <span class="number">0</span>, <span class="number">0</span>, <span class="macro">vec!</span>[delete]);
-<a href=#2418 id=2418 data-nosnippet>2418</a>
-<a href=#2419 id=2419 data-nosnippet>2419</a>        <span class="kw">let </span>reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current()).build();
-<a href=#2420 id=2420 data-nosnippet>2420</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
-<a href=#2421 id=2421 data-nosnippet>2421</a>        <span class="kw">let </span>batches: Vec&lt;RecordBatch&gt; = reader
-<a href=#2422 id=2422 data-nosnippet>2422</a>            .read(tasks)
-<a href=#2423 id=2423 data-nosnippet>2423</a>            .unwrap()
-<a href=#2424 id=2424 data-nosnippet>2424</a>            .stream()
-<a href=#2425 id=2425 data-nosnippet>2425</a>            .try_collect()
-<a href=#2426 id=2426 data-nosnippet>2426</a>            .<span class="kw">await
-<a href=#2427 id=2427 data-nosnippet>2427</a>            </span>.unwrap();
-<a href=#2428 id=2428 data-nosnippet>2428</a>
-<a href=#2429 id=2429 data-nosnippet>2429</a>        <span class="comment">// Survivors keep their ABSOLUTE positions -- [100, 102], not renumbered [100, 101].
-<a href=#2430 id=2430 data-nosnippet>2430</a>        // Positional-delete selection reaches the reader via a different path than predicate
-<a href=#2431 id=2431 data-nosnippet>2431</a>        // selection, so this covers it explicitly.
-<a href=#2432 id=2432 data-nosnippet>2432</a>        </span>assert_row_id_column(<span class="kw-2">&amp;</span>batches, <span class="kw-2">&amp;</span>[<span class="prelude-val">Some</span>(<span class="number">100</span>), <span class="prelude-val">Some</span>(<span class="number">102</span>)]);
-<a href=#2433 id=2433 data-nosnippet>2433</a>    }
-<a href=#2434 id=2434 data-nosnippet>2434</a>
-<a href=#2435 id=2435 data-nosnippet>2435</a>    <span class="attr">#[tokio::test]
-<a href=#2436 id=2436 data-nosnippet>2436</a>    </span><span class="kw">async fn </span>test_row_id_name_collision_under_positional_fallback() {
-<a href=#2437 id=2437 data-nosnippet>2437</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#2438 id=2438 data-nosnippet>2438</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#2439 id=2439 data-nosnippet>2439</a>
-<a href=#2440 id=2440 data-nosnippet>2440</a>        <span class="comment">// A file with NO embedded field ids (positional fallback) whose columns include one
-<a href=#2441 id=2441 data-nosnippet>2441</a>        // literally named `_row_id` (user data). Projecting `_row_id` must NOT be rejected as
-<a href=#2442 id=2442 data-nosnippet>2442</a>        // an unthreadable physical metadata column -- under fallback the reserved column is
-<a href=#2443 id=2443 data-nosnippet>2443</a>        // synthesized and the same-named user column is just data.
-<a href=#2444 id=2444 data-nosnippet>2444</a>        </span><span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{dir}/fallback_row_id_name.parquet"</span>);
-<a href=#2445 id=2445 data-nosnippet>2445</a>        <span class="kw">let </span>arrow_schema = Arc::new(ArrowSchema::new(<span class="macro">vec!</span>[
-<a href=#2446 id=2446 data-nosnippet>2446</a>            Field::new(<span class="string">"id"</span>, DataType::Int32, <span class="bool-val">false</span>),
-<a href=#2447 id=2447 data-nosnippet>2447</a>            Field::new(RESERVED_COL_NAME_ROW_ID, DataType::Int64, <span class="bool-val">true</span>),
-<a href=#2448 id=2448 data-nosnippet>2448</a>        ]));
-<a href=#2449 id=2449 data-nosnippet>2449</a>        <span class="kw">let </span>batch = RecordBatch::try_new(arrow_schema.clone(), <span class="macro">vec!</span>[
-<a href=#2450 id=2450 data-nosnippet>2450</a>            Arc::new(Int32Array::from(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>])),
-<a href=#2451 id=2451 data-nosnippet>2451</a>            Arc::new(Int64Array::from(<span class="macro">vec!</span>[<span class="number">7i64</span>, <span class="number">8</span>, <span class="number">9</span>])),
-<a href=#2452 id=2452 data-nosnippet>2452</a>        ])
-<a href=#2453 id=2453 data-nosnippet>2453</a>        .unwrap();
-<a href=#2454 id=2454 data-nosnippet>2454</a>        <span class="kw">let </span>props = WriterProperties::builder()
-<a href=#2455 id=2455 data-nosnippet>2455</a>            .set_compression(Compression::SNAPPY)
-<a href=#2456 id=2456 data-nosnippet>2456</a>            .build();
-<a href=#2457 id=2457 data-nosnippet>2457</a>        <span class="kw">let </span>file = File::create(<span class="kw-2">&amp;</span>file_path).unwrap();
-<a href=#2458 id=2458 data-nosnippet>2458</a>        <span class="kw">let </span><span class="kw-2">mut </span>writer = ArrowWriter::try_new(file, arrow_schema, <span class="prelude-val">Some</span>(props)).unwrap();
-<a href=#2459 id=2459 data-nosnippet>2459</a>        writer.write(<span class="kw-2">&amp;</span>batch).unwrap();
-<a href=#2460 id=2460 data-nosnippet>2460</a>        writer.close().unwrap();
-<a href=#2461 id=2461 data-nosnippet>2461</a>
-<a href=#2462 id=2462 data-nosnippet>2462</a>        <span class="kw">let </span>task = row_id_task(file_path, <span class="prelude-val">Some</span>(<span class="number">100</span>));
-<a href=#2463 id=2463 data-nosnippet>2463</a>        <span class="kw">let </span>reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current()).build();
-<a href=#2464 id=2464 data-nosnippet>2464</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
-<a href=#2465 id=2465 data-nosnippet>2465</a>        <span class="kw">let </span>batches: Vec&lt;RecordBatch&gt; = reader
-<a href=#2466 id=2466 data-nosnippet>2466</a>            .read(tasks)
-<a href=#2467 id=2467 data-nosnippet>2467</a>            .unwrap()
-<a href=#2468 id=2468 data-nosnippet>2468</a>            .stream()
-<a href=#2469 id=2469 data-nosnippet>2469</a>            .try_collect()
-<a href=#2470 id=2470 data-nosnippet>2470</a>            .<span class="kw">await
-<a href=#2471 id=2471 data-nosnippet>2471</a>            </span>.unwrap();
-<a href=#2472 id=2472 data-nosnippet>2472</a>
-<a href=#2473 id=2473 data-nosnippet>2473</a>        <span class="comment">// Not rejected; `_row_id` is synthesized as first_row_id + pos.
-<a href=#2474 id=2474 data-nosnippet>2474</a>        </span>assert_row_id_column(<span class="kw-2">&amp;</span>batches, <span class="kw-2">&amp;</span>[<span class="prelude-val">Some</span>(<span class="number">100</span>), <span class="prelude-val">Some</span>(<span class="number">101</span>), <span class="prelude-val">Some</span>(<span class="number">102</span>)]);
-<a href=#2475 id=2475 data-nosnippet>2475</a>    }
-<a href=#2476 id=2476 data-nosnippet>2476</a>
-<a href=#2477 id=2477 data-nosnippet>2477</a>    <span class="attr">#[tokio::test]
-<a href=#2478 id=2478 data-nosnippet>2478</a>    </span><span class="kw">async fn </span>test_read_encrypted_parquet_with_wrong_key_fails() {
-<a href=#2479 id=2479 data-nosnippet>2479</a>        <span class="kw">let </span>encryption_key = <span class="string">b"0123456789abcdef"</span>;
-<a href=#2480 id=2480 data-nosnippet>2480</a>        <span class="kw">let </span>wrong_key = <span class="string">b"fedcba9876543210"</span>;
-<a href=#2481 id=2481 data-nosnippet>2481</a>
-<a href=#2482 id=2482 data-nosnippet>2482</a>        <span class="kw">let </span>schema = Arc::new(
-<a href=#2483 id=2483 data-nosnippet>2483</a>            Schema::builder()
-<a href=#2484 id=2484 data-nosnippet>2484</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#2485 id=2485 data-nosnippet>2485</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#2486 id=2486 data-nosnippet>2486</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
-<a href=#2487 id=2487 data-nosnippet>2487</a>                ])
-<a href=#2488 id=2488 data-nosnippet>2488</a>                .build()
-<a href=#2489 id=2489 data-nosnippet>2489</a>                .unwrap(),
-<a href=#2490 id=2490 data-nosnippet>2490</a>        );
-<a href=#2491 id=2491 data-nosnippet>2491</a>
-<a href=#2492 id=2492 data-nosnippet>2492</a>        <span class="kw">let </span>arrow_schema = Arc::new(ArrowSchema::new(<span class="macro">vec!</span>[
-<a href=#2493 id=2493 data-nosnippet>2493</a>            Field::new(<span class="string">"id"</span>, DataType::Int32, <span class="bool-val">false</span>).with_metadata(HashMap::from([(
-<a href=#2494 id=2494 data-nosnippet>2494</a>                PARQUET_FIELD_ID_META_KEY.to_string(),
-<a href=#2495 id=2495 data-nosnippet>2495</a>                <span class="string">"1"</span>.to_string(),
-<a href=#2496 id=2496 data-nosnippet>2496</a>            )])),
-<a href=#2497 id=2497 data-nosnippet>2497</a>        ]));
-<a href=#2498 id=2498 data-nosnippet>2498</a>
-<a href=#2499 id=2499 data-nosnippet>2499</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#2500 id=2500 data-nosnippet>2500</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
-<a href=#2501 id=2501 data-nosnippet>2501</a>        <span class="kw">let </span>file_io = FileIO::new_with_fs();
-<a href=#2502 id=2502 data-nosnippet>2502</a>
-<a href=#2503 id=2503 data-nosnippet>2503</a>        <span class="kw">let </span>id_data = Arc::new(Int32Array::from(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>])) <span class="kw">as </span>ArrayRef;
-<a href=#2504 id=2504 data-nosnippet>2504</a>        <span class="kw">let </span>batch = RecordBatch::try_new(arrow_schema.clone(), <span class="macro">vec!</span>[id_data]).unwrap();
-<a href=#2505 id=2505 data-nosnippet>2505</a>
-<a href=#2506 id=2506 data-nosnippet>2506</a>        <span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{table_location}/encrypted_wrong_key.parquet"</span>);
-<a href=#2507 id=2507 data-nosnippet>2507</a>        write_encrypted_parquet(<span class="kw-2">&amp;</span>file_path, <span class="kw-2">&amp;</span>batch, encryption_key, <span class="prelude-val">None</span>);
-<a href=#2508 id=2508 data-nosnippet>2508</a>
-<a href=#2509 id=2509 data-nosnippet>2509</a>        <span class="kw">let </span>wrong_key_metadata = <span class="kw">crate</span>::encryption::StandardKeyMetadata::try_new(wrong_key)
-<a href=#2510 id=2510 data-nosnippet>2510</a>            .unwrap()
-<a href=#2511 id=2511 data-nosnippet>2511</a>            .encode()
-<a href=#2512 id=2512 data-nosnippet>2512</a>            .unwrap();
-<a href=#2513 id=2513 data-nosnippet>2513</a>
-<a href=#2514 id=2514 data-nosnippet>2514</a>        <span class="kw">let </span>reader = ArrowReaderBuilder::new(file_io, Runtime::current()).build();
-<a href=#2515 id=2515 data-nosnippet>2515</a>
-<a href=#2516 id=2516 data-nosnippet>2516</a>        <span class="kw">let </span>task = FileScanTask::builder()
-<a href=#2517 id=2517 data-nosnippet>2517</a>            .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
-<a href=#2518 id=2518 data-nosnippet>2518</a>            .with_start(<span class="number">0</span>)
-<a href=#2519 id=2519 data-nosnippet>2519</a>            .with_length(<span class="number">0</span>)
-<a href=#2520 id=2520 data-nosnippet>2520</a>            .with_data_file_path(file_path)
-<a href=#2521 id=2521 data-nosnippet>2521</a>            .with_data_file_format(DataFileFormat::Parquet)
-<a href=#2522 id=2522 data-nosnippet>2522</a>            .with_schema(schema)
-<a href=#2523 id=2523 data-nosnippet>2523</a>            .with_project_field_ids(<span class="macro">vec!</span>[<span class="number">1</span>])
-<a href=#2524 id=2524 data-nosnippet>2524</a>            .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#2525 id=2525 data-nosnippet>2525</a>            .with_key_metadata(<span class="prelude-val">Some</span>(wrong_key_metadata))
-<a href=#2526 id=2526 data-nosnippet>2526</a>            .build()
-<a href=#2527 id=2527 data-nosnippet>2527</a>            .unwrap();
-<a href=#2528 id=2528 data-nosnippet>2528</a>
-<a href=#2529 id=2529 data-nosnippet>2529</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
-<a href=#2530 id=2530 data-nosnippet>2530</a>        <span class="kw">let </span>result: <span class="prelude-ty">Result</span>&lt;Vec&lt;RecordBatch&gt;, <span class="kw">_</span>&gt; =
-<a href=#2531 id=2531 data-nosnippet>2531</a>            reader.read(tasks).unwrap().stream().try_collect().<span class="kw">await</span>;
-<a href=#2532 id=2532 data-nosnippet>2532</a>
-<a href=#2533 id=2533 data-nosnippet>2533</a>        <span class="kw">let </span>err = result.unwrap_err();
-<a href=#2534 id=2534 data-nosnippet>2534</a>        <span class="macro">assert_eq!</span>(err.kind(), <span class="kw">crate</span>::ErrorKind::Unexpected);
-<a href=#2535 id=2535 data-nosnippet>2535</a>        <span class="kw">let </span>err_str = <span class="macro">format!</span>(<span class="string">"{err}"</span>);
-<a href=#2536 id=2536 data-nosnippet>2536</a>        <span class="macro">assert!</span>(
-<a href=#2537 id=2537 data-nosnippet>2537</a>            err_str.contains(<span class="string">"unable to decrypt parquet footer"</span>),
-<a href=#2538 id=2538 data-nosnippet>2538</a>            <span class="string">"Expected error about decryption failure, got: {err_str}"
-<a href=#2539 id=2539 data-nosnippet>2539</a>        </span>);
-<a href=#2540 id=2540 data-nosnippet>2540</a>    }
-<a href=#2541 id=2541 data-nosnippet>2541</a>
-<a href=#2542 id=2542 data-nosnippet>2542</a>    <span class="doccomment">/// Test that concurrency=1 reads all files correctly and in deterministic order.
-<a href=#2543 id=2543 data-nosnippet>2543</a>    /// This verifies the fast-path optimization for single concurrency.
-<a href=#2544 id=2544 data-nosnippet>2544</a>    </span><span class="attr">#[tokio::test]
-<a href=#2545 id=2545 data-nosnippet>2545</a>    </span><span class="kw">async fn </span>test_read_with_concurrency_one() {
-<a href=#2546 id=2546 data-nosnippet>2546</a>        <span class="kw">use </span>arrow_array::Int32Array;
-<a href=#2547 id=2547 data-nosnippet>2547</a>
-<a href=#2548 id=2548 data-nosnippet>2548</a>        <span class="kw">let </span>schema = Arc::new(
-<a href=#2549 id=2549 data-nosnippet>2549</a>            Schema::builder()
-<a href=#2550 id=2550 data-nosnippet>2550</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#2551 id=2551 data-nosnippet>2551</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#2552 id=2552 data-nosnippet>2552</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
-<a href=#2553 id=2553 data-nosnippet>2553</a>                    NestedField::required(<span class="number">2</span>, <span class="string">"file_num"</span>, Type::Primitive(PrimitiveType::Int))
-<a href=#2554 id=2554 data-nosnippet>2554</a>                        .into(),
-<a href=#2555 id=2555 data-nosnippet>2555</a>                ])
-<a href=#2556 id=2556 data-nosnippet>2556</a>                .build()
-<a href=#2557 id=2557 data-nosnippet>2557</a>                .unwrap(),
-<a href=#2558 id=2558 data-nosnippet>2558</a>        );
-<a href=#2559 id=2559 data-nosnippet>2559</a>
-<a href=#2560 id=2560 data-nosnippet>2560</a>        <span class="kw">let </span>arrow_schema = Arc::new(ArrowSchema::new(<span class="macro">vec!</span>[
-<a href=#2561 id=2561 data-nosnippet>2561</a>            Field::new(<span class="string">"id"</span>, DataType::Int32, <span class="bool-val">false</span>).with_metadata(HashMap::from([(
-<a href=#2562 id=2562 data-nosnippet>2562</a>                PARQUET_FIELD_ID_META_KEY.to_string(),
-<a href=#2563 id=2563 data-nosnippet>2563</a>                <span class="string">"1"</span>.to_string(),
-<a href=#2564 id=2564 data-nosnippet>2564</a>            )])),
-<a href=#2565 id=2565 data-nosnippet>2565</a>            Field::new(<span class="string">"file_num"</span>, DataType::Int32, <span class="bool-val">false</span>).with_metadata(HashMap::from([(
-<a href=#2566 id=2566 data-nosnippet>2566</a>                PARQUET_FIELD_ID_META_KEY.to_string(),
-<a href=#2567 id=2567 data-nosnippet>2567</a>                <span class="string">"2"</span>.to_string(),
-<a href=#2568 id=2568 data-nosnippet>2568</a>            )])),
-<a href=#2569 id=2569 data-nosnippet>2569</a>        ]));
-<a href=#2570 id=2570 data-nosnippet>2570</a>
-<a href=#2571 id=2571 data-nosnippet>2571</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#2572 id=2572 data-nosnippet>2572</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
-<a href=#2573 id=2573 data-nosnippet>2573</a>        <span class="kw">let </span>file_io = FileIO::new_with_fs();
-<a href=#2574 id=2574 data-nosnippet>2574</a>
-<a href=#2575 id=2575 data-nosnippet>2575</a>        <span class="comment">// Create 3 parquet files with different data
-<a href=#2576 id=2576 data-nosnippet>2576</a>        </span><span class="kw">let </span>props = WriterProperties::builder()
-<a href=#2577 id=2577 data-nosnippet>2577</a>            .set_compression(Compression::SNAPPY)
-<a href=#2578 id=2578 data-nosnippet>2578</a>            .build();
-<a href=#2579 id=2579 data-nosnippet>2579</a>
-<a href=#2580 id=2580 data-nosnippet>2580</a>        <span class="kw">for </span>file_num <span class="kw">in </span><span class="number">0</span>..<span class="number">3 </span>{
-<a href=#2581 id=2581 data-nosnippet>2581</a>            <span class="kw">let </span>id_data = Arc::new(Int32Array::from_iter_values(
-<a href=#2582 id=2582 data-nosnippet>2582</a>                file_num * <span class="number">10</span>..(file_num + <span class="number">1</span>) * <span class="number">10</span>,
-<a href=#2583 id=2583 data-nosnippet>2583</a>            )) <span class="kw">as </span>ArrayRef;
-<a href=#2584 id=2584 data-nosnippet>2584</a>            <span class="kw">let </span>file_num_data = Arc::new(Int32Array::from(<span class="macro">vec!</span>[file_num; <span class="number">10</span>])) <span class="kw">as </span>ArrayRef;
-<a href=#2585 id=2585 data-nosnippet>2585</a>
-<a href=#2586 id=2586 data-nosnippet>2586</a>            <span class="kw">let </span>to_write =
-<a href=#2587 id=2587 data-nosnippet>2587</a>                RecordBatch::try_new(arrow_schema.clone(), <span class="macro">vec!</span>[id_data, file_num_data]).unwrap();
-<a href=#2588 id=2588 data-nosnippet>2588</a>
-<a href=#2589 id=2589 data-nosnippet>2589</a>            <span class="kw">let </span>file = File::create(<span class="macro">format!</span>(<span class="string">"{table_location}/file_{file_num}.parquet"</span>)).unwrap();
-<a href=#2590 id=2590 data-nosnippet>2590</a>            <span class="kw">let </span><span class="kw-2">mut </span>writer =
-<a href=#2591 id=2591 data-nosnippet>2591</a>                ArrowWriter::try_new(file, to_write.schema(), <span class="prelude-val">Some</span>(props.clone())).unwrap();
-<a href=#2592 id=2592 data-nosnippet>2592</a>            writer.write(<span class="kw-2">&amp;</span>to_write).expect(<span class="string">"Writing batch"</span>);
-<a href=#2593 id=2593 data-nosnippet>2593</a>            writer.close().unwrap();
-<a href=#2594 id=2594 data-nosnippet>2594</a>        }
-<a href=#2595 id=2595 data-nosnippet>2595</a>
-<a href=#2596 id=2596 data-nosnippet>2596</a>        <span class="comment">// Read with concurrency=1 (fast-path)
-<a href=#2597 id=2597 data-nosnippet>2597</a>        </span><span class="kw">let </span>reader = ArrowReaderBuilder::new(file_io, Runtime::current())
-<a href=#2598 id=2598 data-nosnippet>2598</a>            .with_data_file_concurrency_limit(<span class="number">1</span>)
-<a href=#2599 id=2599 data-nosnippet>2599</a>            .build();
-<a href=#2600 id=2600 data-nosnippet>2600</a>
-<a href=#2601 id=2601 data-nosnippet>2601</a>        <span class="comment">// Create tasks in a specific order: file_0, file_1, file_2
-<a href=#2602 id=2602 data-nosnippet>2602</a>        </span><span class="kw">let </span>tasks = <span class="macro">vec!</span>[
-<a href=#2603 id=2603 data-nosnippet>2603</a>            <span class="prelude-val">Ok</span>(FileScanTask::builder()
-<a href=#2604 id=2604 data-nosnippet>2604</a>                .with_file_size_in_bytes(
-<a href=#2605 id=2605 data-nosnippet>2605</a>                    std::fs::metadata(<span class="macro">format!</span>(<span class="string">"{table_location}/file_0.parquet"</span>))
-<a href=#2606 id=2606 data-nosnippet>2606</a>                        .unwrap()
-<a href=#2607 id=2607 data-nosnippet>2607</a>                        .len(),
-<a href=#2608 id=2608 data-nosnippet>2608</a>                )
-<a href=#2609 id=2609 data-nosnippet>2609</a>                .with_start(<span class="number">0</span>)
-<a href=#2610 id=2610 data-nosnippet>2610</a>                .with_length(<span class="number">0</span>)
-<a href=#2611 id=2611 data-nosnippet>2611</a>                .with_data_file_path(<span class="macro">format!</span>(<span class="string">"{table_location}/file_0.parquet"</span>))
-<a href=#2612 id=2612 data-nosnippet>2612</a>                .with_data_file_format(DataFileFormat::Parquet)
-<a href=#2613 id=2613 data-nosnippet>2613</a>                .with_schema(schema.clone())
-<a href=#2614 id=2614 data-nosnippet>2614</a>                .with_project_field_ids(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>])
-<a href=#2615 id=2615 data-nosnippet>2615</a>                .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#2616 id=2616 data-nosnippet>2616</a>                .build()
-<a href=#2617 id=2617 data-nosnippet>2617</a>                .unwrap()),
-<a href=#2618 id=2618 data-nosnippet>2618</a>            <span class="prelude-val">Ok</span>(FileScanTask::builder()
-<a href=#2619 id=2619 data-nosnippet>2619</a>                .with_file_size_in_bytes(
-<a href=#2620 id=2620 data-nosnippet>2620</a>                    std::fs::metadata(<span class="macro">format!</span>(<span class="string">"{table_location}/file_1.parquet"</span>))
-<a href=#2621 id=2621 data-nosnippet>2621</a>                        .unwrap()
-<a href=#2622 id=2622 data-nosnippet>2622</a>                        .len(),
-<a href=#2623 id=2623 data-nosnippet>2623</a>                )
-<a href=#2624 id=2624 data-nosnippet>2624</a>                .with_start(<span class="number">0</span>)
-<a href=#2625 id=2625 data-nosnippet>2625</a>                .with_length(<span class="number">0</span>)
-<a href=#2626 id=2626 data-nosnippet>2626</a>                .with_data_file_path(<span class="macro">format!</span>(<span class="string">"{table_location}/file_1.parquet"</span>))
-<a href=#2627 id=2627 data-nosnippet>2627</a>                .with_data_file_format(DataFileFormat::Parquet)
-<a href=#2628 id=2628 data-nosnippet>2628</a>                .with_schema(schema.clone())
-<a href=#2629 id=2629 data-nosnippet>2629</a>                .with_project_field_ids(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>])
-<a href=#2630 id=2630 data-nosnippet>2630</a>                .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#2631 id=2631 data-nosnippet>2631</a>                .build()
-<a href=#2632 id=2632 data-nosnippet>2632</a>                .unwrap()),
-<a href=#2633 id=2633 data-nosnippet>2633</a>            <span class="prelude-val">Ok</span>(FileScanTask::builder()
-<a href=#2634 id=2634 data-nosnippet>2634</a>                .with_file_size_in_bytes(
-<a href=#2635 id=2635 data-nosnippet>2635</a>                    std::fs::metadata(<span class="macro">format!</span>(<span class="string">"{table_location}/file_2.parquet"</span>))
-<a href=#2636 id=2636 data-nosnippet>2636</a>                        .unwrap()
-<a href=#2637 id=2637 data-nosnippet>2637</a>                        .len(),
-<a href=#2638 id=2638 data-nosnippet>2638</a>                )
-<a href=#2639 id=2639 data-nosnippet>2639</a>                .with_start(<span class="number">0</span>)
-<a href=#2640 id=2640 data-nosnippet>2640</a>                .with_length(<span class="number">0</span>)
-<a href=#2641 id=2641 data-nosnippet>2641</a>                .with_data_file_path(<span class="macro">format!</span>(<span class="string">"{table_location}/file_2.parquet"</span>))
-<a href=#2642 id=2642 data-nosnippet>2642</a>                .with_data_file_format(DataFileFormat::Parquet)
-<a href=#2643 id=2643 data-nosnippet>2643</a>                .with_schema(schema.clone())
-<a href=#2644 id=2644 data-nosnippet>2644</a>                .with_project_field_ids(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>])
-<a href=#2645 id=2645 data-nosnippet>2645</a>                .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#2646 id=2646 data-nosnippet>2646</a>                .build()
-<a href=#2647 id=2647 data-nosnippet>2647</a>                .unwrap()),
-<a href=#2648 id=2648 data-nosnippet>2648</a>        ];
-<a href=#2649 id=2649 data-nosnippet>2649</a>
-<a href=#2650 id=2650 data-nosnippet>2650</a>        <span class="kw">let </span>tasks_stream = Box::pin(futures::stream::iter(tasks)) <span class="kw">as </span>FileScanTaskStream;
-<a href=#2651 id=2651 data-nosnippet>2651</a>
-<a href=#2652 id=2652 data-nosnippet>2652</a>        <span class="kw">let </span>result = reader
-<a href=#2653 id=2653 data-nosnippet>2653</a>            .read(tasks_stream)
-<a href=#2654 id=2654 data-nosnippet>2654</a>            .unwrap()
-<a href=#2655 id=2655 data-nosnippet>2655</a>            .stream()
-<a href=#2656 id=2656 data-nosnippet>2656</a>            .try_collect::&lt;Vec&lt;RecordBatch&gt;&gt;()
-<a href=#2657 id=2657 data-nosnippet>2657</a>            .<span class="kw">await
-<a href=#2658 id=2658 data-nosnippet>2658</a>            </span>.unwrap();
-<a href=#2659 id=2659 data-nosnippet>2659</a>
-<a href=#2660 id=2660 data-nosnippet>2660</a>        <span class="comment">// Verify we got all 30 rows (10 from each file)
-<a href=#2661 id=2661 data-nosnippet>2661</a>        </span><span class="kw">let </span>total_rows: usize = result.iter().map(|b| b.num_rows()).sum();
-<a href=#2662 id=2662 data-nosnippet>2662</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">30</span>, <span class="string">"Should have 30 total rows"</span>);
-<a href=#2663 id=2663 data-nosnippet>2663</a>
-<a href=#2664 id=2664 data-nosnippet>2664</a>        <span class="comment">// Collect all ids and file_nums to verify data
-<a href=#2665 id=2665 data-nosnippet>2665</a>        </span><span class="kw">let </span><span class="kw-2">mut </span>all_ids = Vec::new();
-<a href=#2666 id=2666 data-nosnippet>2666</a>        <span class="kw">let </span><span class="kw-2">mut </span>all_file_nums = Vec::new();
-<a href=#2667 id=2667 data-nosnippet>2667</a>
-<a href=#2668 id=2668 data-nosnippet>2668</a>        <span class="kw">for </span>batch <span class="kw">in </span><span class="kw-2">&amp;</span>result {
-<a href=#2669 id=2669 data-nosnippet>2669</a>            <span class="kw">let </span>id_col = batch
-<a href=#2670 id=2670 data-nosnippet>2670</a>                .column(<span class="number">0</span>)
-<a href=#2671 id=2671 data-nosnippet>2671</a>                .as_primitive::&lt;arrow_array::types::Int32Type&gt;();
-<a href=#2672 id=2672 data-nosnippet>2672</a>            <span class="kw">let </span>file_num_col = batch
-<a href=#2673 id=2673 data-nosnippet>2673</a>                .column(<span class="number">1</span>)
-<a href=#2674 id=2674 data-nosnippet>2674</a>                .as_primitive::&lt;arrow_array::types::Int32Type&gt;();
-<a href=#2675 id=2675 data-nosnippet>2675</a>
-<a href=#2676 id=2676 data-nosnippet>2676</a>            <span class="kw">for </span>i <span class="kw">in </span><span class="number">0</span>..batch.num_rows() {
-<a href=#2677 id=2677 data-nosnippet>2677</a>                all_ids.push(id_col.value(i));
-<a href=#2678 id=2678 data-nosnippet>2678</a>                all_file_nums.push(file_num_col.value(i));
-<a href=#2679 id=2679 data-nosnippet>2679</a>            }
-<a href=#2680 id=2680 data-nosnippet>2680</a>        }
-<a href=#2681 id=2681 data-nosnippet>2681</a>
-<a href=#2682 id=2682 data-nosnippet>2682</a>        <span class="macro">assert_eq!</span>(all_ids.len(), <span class="number">30</span>);
-<a href=#2683 id=2683 data-nosnippet>2683</a>        <span class="macro">assert_eq!</span>(all_file_nums.len(), <span class="number">30</span>);
-<a href=#2684 id=2684 data-nosnippet>2684</a>
-<a href=#2685 id=2685 data-nosnippet>2685</a>        <span class="comment">// With concurrency=1 and sequential processing, files should be processed in order
-<a href=#2686 id=2686 data-nosnippet>2686</a>        // file_0: ids 0-9, file_num=0
-<a href=#2687 id=2687 data-nosnippet>2687</a>        // file_1: ids 10-19, file_num=1
-<a href=#2688 id=2688 data-nosnippet>2688</a>        // file_2: ids 20-29, file_num=2
-<a href=#2689 id=2689 data-nosnippet>2689</a>        </span><span class="kw">for </span>i <span class="kw">in </span><span class="number">0</span>..<span class="number">10 </span>{
-<a href=#2690 id=2690 data-nosnippet>2690</a>            <span class="macro">assert_eq!</span>(all_file_nums[i], <span class="number">0</span>, <span class="string">"First 10 rows should be from file_0"</span>);
-<a href=#2691 id=2691 data-nosnippet>2691</a>            <span class="macro">assert_eq!</span>(all_ids[i], i <span class="kw">as </span>i32, <span class="string">"IDs should be 0-9"</span>);
-<a href=#2692 id=2692 data-nosnippet>2692</a>        }
-<a href=#2693 id=2693 data-nosnippet>2693</a>        <span class="kw">for </span>i <span class="kw">in </span><span class="number">10</span>..<span class="number">20 </span>{
-<a href=#2694 id=2694 data-nosnippet>2694</a>            <span class="macro">assert_eq!</span>(all_file_nums[i], <span class="number">1</span>, <span class="string">"Next 10 rows should be from file_1"</span>);
-<a href=#2695 id=2695 data-nosnippet>2695</a>            <span class="macro">assert_eq!</span>(all_ids[i], i <span class="kw">as </span>i32, <span class="string">"IDs should be 10-19"</span>);
-<a href=#2696 id=2696 data-nosnippet>2696</a>        }
-<a href=#2697 id=2697 data-nosnippet>2697</a>        <span class="kw">for </span>i <span class="kw">in </span><span class="number">20</span>..<span class="number">30 </span>{
-<a href=#2698 id=2698 data-nosnippet>2698</a>            <span class="macro">assert_eq!</span>(all_file_nums[i], <span class="number">2</span>, <span class="string">"Last 10 rows should be from file_2"</span>);
-<a href=#2699 id=2699 data-nosnippet>2699</a>            <span class="macro">assert_eq!</span>(all_ids[i], i <span class="kw">as </span>i32, <span class="string">"IDs should be 20-29"</span>);
-<a href=#2700 id=2700 data-nosnippet>2700</a>        }
-<a href=#2701 id=2701 data-nosnippet>2701</a>    }
-<a href=#2702 id=2702 data-nosnippet>2702</a>
-<a href=#2703 id=2703 data-nosnippet>2703</a>    <span class="attr">#[tokio::test]
-<a href=#2704 id=2704 data-nosnippet>2704</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_with_field_ids() {
-<a href=#2705 id=2705 data-nosnippet>2705</a>        <span class="kw">let </span>schema = Arc::new(
-<a href=#2706 id=2706 data-nosnippet>2706</a>            Schema::builder()
-<a href=#2707 id=2707 data-nosnippet>2707</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#2708 id=2708 data-nosnippet>2708</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#2709 id=2709 data-nosnippet>2709</a>                    NestedField::optional(<span class="number">1</span>, <span class="string">"ts"</span>, Type::Primitive(PrimitiveType::Timestamp))
-<a href=#2710 id=2710 data-nosnippet>2710</a>                        .into(),
-<a href=#2711 id=2711 data-nosnippet>2711</a>                    NestedField::required(<span class="number">2</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
-<a href=#2712 id=2712 data-nosnippet>2712</a>                ])
-<a href=#2713 id=2713 data-nosnippet>2713</a>                .build()
-<a href=#2714 id=2714 data-nosnippet>2714</a>                .unwrap(),
-<a href=#2715 id=2715 data-nosnippet>2715</a>        );
-<a href=#2716 id=2716 data-nosnippet>2716</a>
-<a href=#2717 id=2717 data-nosnippet>2717</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#2718 id=2718 data-nosnippet>2718</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
-<a href=#2719 id=2719 data-nosnippet>2719</a>        <span class="kw">let </span>(file_path, expected_micros) =
-<a href=#2720 id=2720 data-nosnippet>2720</a>            write_int96_parquet_file(<span class="kw-2">&amp;</span>table_location, <span class="string">"with_ids.parquet"</span>, <span class="bool-val">true</span>);
-<a href=#2721 id=2721 data-nosnippet>2721</a>
-<a href=#2722 id=2722 data-nosnippet>2722</a>        assert_int96_read_matches(<span class="kw-2">&amp;</span>file_path, schema, <span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>], <span class="kw-2">&amp;</span>expected_micros).<span class="kw">await</span>;
-<a href=#2723 id=2723 data-nosnippet>2723</a>    }
-<a href=#2724 id=2724 data-nosnippet>2724</a>
-<a href=#2725 id=2725 data-nosnippet>2725</a>    <span class="attr">#[tokio::test]
-<a href=#2726 id=2726 data-nosnippet>2726</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_without_field_ids() {
-<a href=#2727 id=2727 data-nosnippet>2727</a>        <span class="kw">let </span>schema = Arc::new(
-<a href=#2728 id=2728 data-nosnippet>2728</a>            Schema::builder()
-<a href=#2729 id=2729 data-nosnippet>2729</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#2730 id=2730 data-nosnippet>2730</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#2731 id=2731 data-nosnippet>2731</a>                    NestedField::optional(<span class="number">1</span>, <span class="string">"ts"</span>, Type::Primitive(PrimitiveType::Timestamp))
-<a href=#2732 id=2732 data-nosnippet>2732</a>                        .into(),
-<a href=#2733 id=2733 data-nosnippet>2733</a>                    NestedField::required(<span class="number">2</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
-<a href=#2734 id=2734 data-nosnippet>2734</a>                ])
-<a href=#2735 id=2735 data-nosnippet>2735</a>                .build()
-<a href=#2736 id=2736 data-nosnippet>2736</a>                .unwrap(),
-<a href=#2737 id=2737 data-nosnippet>2737</a>        );
-<a href=#2738 id=2738 data-nosnippet>2738</a>
-<a href=#2739 id=2739 data-nosnippet>2739</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#2740 id=2740 data-nosnippet>2740</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
-<a href=#2741 id=2741 data-nosnippet>2741</a>        <span class="kw">let </span>(file_path, expected_micros) =
-<a href=#2742 id=2742 data-nosnippet>2742</a>            write_int96_parquet_file(<span class="kw-2">&amp;</span>table_location, <span class="string">"no_ids.parquet"</span>, <span class="bool-val">false</span>);
-<a href=#2743 id=2743 data-nosnippet>2743</a>
-<a href=#2744 id=2744 data-nosnippet>2744</a>        assert_int96_read_matches(<span class="kw-2">&amp;</span>file_path, schema, <span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>], <span class="kw-2">&amp;</span>expected_micros).<span class="kw">await</span>;
-<a href=#2745 id=2745 data-nosnippet>2745</a>    }
-<a href=#2746 id=2746 data-nosnippet>2746</a>
-<a href=#2747 id=2747 data-nosnippet>2747</a>    <span class="attr">#[tokio::test]
-<a href=#2748 id=2748 data-nosnippet>2748</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_in_struct() {
-<a href=#2749 id=2749 data-nosnippet>2749</a>        <span class="kw">use </span>arrow_array::{StructArray, TimestampMicrosecondArray};
-<a href=#2750 id=2750 data-nosnippet>2750</a>        <span class="kw">use </span>parquet::basic::{Repetition, Type <span class="kw">as </span>PhysicalType};
-<a href=#2751 id=2751 data-nosnippet>2751</a>        <span class="kw">use </span>parquet::data_type::Int96Type;
-<a href=#2752 id=2752 data-nosnippet>2752</a>        <span class="kw">use </span>parquet::file::writer::SerializedFileWriter;
-<a href=#2753 id=2753 data-nosnippet>2753</a>        <span class="kw">use </span>parquet::schema::types::Type <span class="kw">as </span>SchemaType;
-<a href=#2754 id=2754 data-nosnippet>2754</a>
-<a href=#2755 id=2755 data-nosnippet>2755</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#2756 id=2756 data-nosnippet>2756</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
-<a href=#2757 id=2757 data-nosnippet>2757</a>        <span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{table_location}/struct_int96.parquet"</span>);
-<a href=#2758 id=2758 data-nosnippet>2758</a>
-<a href=#2759 id=2759 data-nosnippet>2759</a>        <span class="kw">let </span>ts_type = SchemaType::primitive_type_builder(<span class="string">"ts"</span>, PhysicalType::INT96)
-<a href=#2760 id=2760 data-nosnippet>2760</a>            .with_repetition(Repetition::OPTIONAL)
-<a href=#2761 id=2761 data-nosnippet>2761</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">2</span>))
-<a href=#2762 id=2762 data-nosnippet>2762</a>            .build()
-<a href=#2763 id=2763 data-nosnippet>2763</a>            .unwrap();
-<a href=#2764 id=2764 data-nosnippet>2764</a>
-<a href=#2765 id=2765 data-nosnippet>2765</a>        <span class="kw">let </span>struct_type = SchemaType::group_type_builder(<span class="string">"data"</span>)
-<a href=#2766 id=2766 data-nosnippet>2766</a>            .with_repetition(Repetition::REQUIRED)
-<a href=#2767 id=2767 data-nosnippet>2767</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">1</span>))
-<a href=#2768 id=2768 data-nosnippet>2768</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(ts_type)])
-<a href=#2769 id=2769 data-nosnippet>2769</a>            .build()
-<a href=#2770 id=2770 data-nosnippet>2770</a>            .unwrap();
-<a href=#2771 id=2771 data-nosnippet>2771</a>
-<a href=#2772 id=2772 data-nosnippet>2772</a>        <span class="kw">let </span>parquet_schema = SchemaType::group_type_builder(<span class="string">"schema"</span>)
-<a href=#2773 id=2773 data-nosnippet>2773</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(struct_type)])
-<a href=#2774 id=2774 data-nosnippet>2774</a>            .build()
-<a href=#2775 id=2775 data-nosnippet>2775</a>            .unwrap();
-<a href=#2776 id=2776 data-nosnippet>2776</a>
-<a href=#2777 id=2777 data-nosnippet>2777</a>        <span class="kw">let </span>(int96_val, expected_micros) = make_int96_test_value();
-<a href=#2778 id=2778 data-nosnippet>2778</a>
-<a href=#2779 id=2779 data-nosnippet>2779</a>        <span class="kw">let </span>file = File::create(<span class="kw-2">&amp;</span>file_path).unwrap();
-<a href=#2780 id=2780 data-nosnippet>2780</a>        <span class="kw">let </span><span class="kw-2">mut </span>writer =
-<a href=#2781 id=2781 data-nosnippet>2781</a>            SerializedFileWriter::new(file, Arc::new(parquet_schema), Default::default()).unwrap();
-<a href=#2782 id=2782 data-nosnippet>2782</a>
-<a href=#2783 id=2783 data-nosnippet>2783</a>        <span class="comment">// def=1: struct is REQUIRED so no level, ts is OPTIONAL and present (1).
-<a href=#2784 id=2784 data-nosnippet>2784</a>        // No repetition levels needed (no repeated groups).
-<a href=#2785 id=2785 data-nosnippet>2785</a>        </span><span class="kw">let </span><span class="kw-2">mut </span>row_group = writer.next_row_group().unwrap();
-<a href=#2786 id=2786 data-nosnippet>2786</a>        {
-<a href=#2787 id=2787 data-nosnippet>2787</a>            <span class="kw">let </span><span class="kw-2">mut </span>col = row_group.next_column().unwrap().unwrap();
-<a href=#2788 id=2788 data-nosnippet>2788</a>            col.typed::&lt;Int96Type&gt;()
-<a href=#2789 id=2789 data-nosnippet>2789</a>                .write_batch(<span class="kw-2">&amp;</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">1</span>]), <span class="prelude-val">None</span>)
-<a href=#2790 id=2790 data-nosnippet>2790</a>                .unwrap();
-<a href=#2791 id=2791 data-nosnippet>2791</a>            col.close().unwrap();
-<a href=#2792 id=2792 data-nosnippet>2792</a>        }
-<a href=#2793 id=2793 data-nosnippet>2793</a>        row_group.close().unwrap();
-<a href=#2794 id=2794 data-nosnippet>2794</a>        writer.close().unwrap();
-<a href=#2795 id=2795 data-nosnippet>2795</a>
-<a href=#2796 id=2796 data-nosnippet>2796</a>        <span class="kw">let </span>iceberg_schema = Arc::new(
-<a href=#2797 id=2797 data-nosnippet>2797</a>            Schema::builder()
-<a href=#2798 id=2798 data-nosnippet>2798</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#2799 id=2799 data-nosnippet>2799</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#2800 id=2800 data-nosnippet>2800</a>                    NestedField::required(
-<a href=#2801 id=2801 data-nosnippet>2801</a>                        <span class="number">1</span>,
-<a href=#2802 id=2802 data-nosnippet>2802</a>                        <span class="string">"data"</span>,
-<a href=#2803 id=2803 data-nosnippet>2803</a>                        Type::Struct(<span class="kw">crate</span>::spec::StructType::new(<span class="macro">vec!</span>[
-<a href=#2804 id=2804 data-nosnippet>2804</a>                            NestedField::optional(
-<a href=#2805 id=2805 data-nosnippet>2805</a>                                <span class="number">2</span>,
-<a href=#2806 id=2806 data-nosnippet>2806</a>                                <span class="string">"ts"</span>,
-<a href=#2807 id=2807 data-nosnippet>2807</a>                                Type::Primitive(PrimitiveType::Timestamp),
-<a href=#2808 id=2808 data-nosnippet>2808</a>                            )
-<a href=#2809 id=2809 data-nosnippet>2809</a>                            .into(),
-<a href=#2810 id=2810 data-nosnippet>2810</a>                        ])),
-<a href=#2811 id=2811 data-nosnippet>2811</a>                    )
-<a href=#2812 id=2812 data-nosnippet>2812</a>                    .into(),
-<a href=#2813 id=2813 data-nosnippet>2813</a>                ])
-<a href=#2814 id=2814 data-nosnippet>2814</a>                .build()
-<a href=#2815 id=2815 data-nosnippet>2815</a>                .unwrap(),
-<a href=#2816 id=2816 data-nosnippet>2816</a>        );
-<a href=#2817 id=2817 data-nosnippet>2817</a>
-<a href=#2818 id=2818 data-nosnippet>2818</a>        <span class="kw">let </span>batches = read_int96_batches(<span class="kw-2">&amp;</span>file_path, iceberg_schema, <span class="macro">vec!</span>[<span class="number">1</span>]).<span class="kw">await</span>;
-<a href=#2819 id=2819 data-nosnippet>2819</a>
-<a href=#2820 id=2820 data-nosnippet>2820</a>        <span class="macro">assert_eq!</span>(batches.len(), <span class="number">1</span>);
-<a href=#2821 id=2821 data-nosnippet>2821</a>        <span class="kw">let </span>struct_array = batches[<span class="number">0</span>]
-<a href=#2822 id=2822 data-nosnippet>2822</a>            .column(<span class="number">0</span>)
-<a href=#2823 id=2823 data-nosnippet>2823</a>            .as_any()
-<a href=#2824 id=2824 data-nosnippet>2824</a>            .downcast_ref::&lt;StructArray&gt;()
-<a href=#2825 id=2825 data-nosnippet>2825</a>            .expect(<span class="string">"Expected StructArray"</span>);
-<a href=#2826 id=2826 data-nosnippet>2826</a>        <span class="kw">let </span>ts_array = struct_array
-<a href=#2827 id=2827 data-nosnippet>2827</a>            .column(<span class="number">0</span>)
-<a href=#2828 id=2828 data-nosnippet>2828</a>            .as_any()
-<a href=#2829 id=2829 data-nosnippet>2829</a>            .downcast_ref::&lt;TimestampMicrosecondArray&gt;()
-<a href=#2830 id=2830 data-nosnippet>2830</a>            .expect(<span class="string">"Expected TimestampMicrosecondArray inside struct"</span>);
-<a href=#2831 id=2831 data-nosnippet>2831</a>
-<a href=#2832 id=2832 data-nosnippet>2832</a>        <span class="macro">assert_eq!</span>(
-<a href=#2833 id=2833 data-nosnippet>2833</a>            ts_array.value(<span class="number">0</span>),
-<a href=#2834 id=2834 data-nosnippet>2834</a>            expected_micros,
-<a href=#2835 id=2835 data-nosnippet>2835</a>            <span class="string">"INT96 in struct: got {}, expected {expected_micros}"</span>,
-<a href=#2836 id=2836 data-nosnippet>2836</a>            ts_array.value(<span class="number">0</span>)
-<a href=#2837 id=2837 data-nosnippet>2837</a>        );
-<a href=#2838 id=2838 data-nosnippet>2838</a>    }
-<a href=#2839 id=2839 data-nosnippet>2839</a>
-<a href=#2840 id=2840 data-nosnippet>2840</a>    <span class="attr">#[tokio::test]
-<a href=#2841 id=2841 data-nosnippet>2841</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_in_list() {
-<a href=#2842 id=2842 data-nosnippet>2842</a>        <span class="kw">use </span>arrow_array::{ListArray, TimestampMicrosecondArray};
-<a href=#2843 id=2843 data-nosnippet>2843</a>        <span class="kw">use </span>parquet::basic::{Repetition, Type <span class="kw">as </span>PhysicalType};
-<a href=#2844 id=2844 data-nosnippet>2844</a>        <span class="kw">use </span>parquet::data_type::Int96Type;
-<a href=#2845 id=2845 data-nosnippet>2845</a>        <span class="kw">use </span>parquet::file::writer::SerializedFileWriter;
-<a href=#2846 id=2846 data-nosnippet>2846</a>        <span class="kw">use </span>parquet::schema::types::Type <span class="kw">as </span>SchemaType;
-<a href=#2847 id=2847 data-nosnippet>2847</a>
-<a href=#2848 id=2848 data-nosnippet>2848</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#2849 id=2849 data-nosnippet>2849</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
-<a href=#2850 id=2850 data-nosnippet>2850</a>        <span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{table_location}/list_int96.parquet"</span>);
-<a href=#2851 id=2851 data-nosnippet>2851</a>
-<a href=#2852 id=2852 data-nosnippet>2852</a>        <span class="comment">// 3-level LIST encoding:
-<a href=#2853 id=2853 data-nosnippet>2853</a>        //   optional group timestamps (LIST) {
-<a href=#2854 id=2854 data-nosnippet>2854</a>        //     repeated group list {
-<a href=#2855 id=2855 data-nosnippet>2855</a>        //       optional int96 element;
-<a href=#2856 id=2856 data-nosnippet>2856</a>        //     }
-<a href=#2857 id=2857 data-nosnippet>2857</a>        //   }
-<a href=#2858 id=2858 data-nosnippet>2858</a>        </span><span class="kw">let </span>element_type = SchemaType::primitive_type_builder(<span class="string">"element"</span>, PhysicalType::INT96)
-<a href=#2859 id=2859 data-nosnippet>2859</a>            .with_repetition(Repetition::OPTIONAL)
-<a href=#2860 id=2860 data-nosnippet>2860</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">2</span>))
-<a href=#2861 id=2861 data-nosnippet>2861</a>            .build()
-<a href=#2862 id=2862 data-nosnippet>2862</a>            .unwrap();
-<a href=#2863 id=2863 data-nosnippet>2863</a>
-<a href=#2864 id=2864 data-nosnippet>2864</a>        <span class="kw">let </span>list_group = SchemaType::group_type_builder(<span class="string">"list"</span>)
-<a href=#2865 id=2865 data-nosnippet>2865</a>            .with_repetition(Repetition::REPEATED)
-<a href=#2866 id=2866 data-nosnippet>2866</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(element_type)])
-<a href=#2867 id=2867 data-nosnippet>2867</a>            .build()
-<a href=#2868 id=2868 data-nosnippet>2868</a>            .unwrap();
-<a href=#2869 id=2869 data-nosnippet>2869</a>
-<a href=#2870 id=2870 data-nosnippet>2870</a>        <span class="kw">let </span>list_type = SchemaType::group_type_builder(<span class="string">"timestamps"</span>)
-<a href=#2871 id=2871 data-nosnippet>2871</a>            .with_repetition(Repetition::OPTIONAL)
-<a href=#2872 id=2872 data-nosnippet>2872</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">1</span>))
-<a href=#2873 id=2873 data-nosnippet>2873</a>            .with_logical_type(<span class="prelude-val">Some</span>(parquet::basic::LogicalType::List))
-<a href=#2874 id=2874 data-nosnippet>2874</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(list_group)])
-<a href=#2875 id=2875 data-nosnippet>2875</a>            .build()
-<a href=#2876 id=2876 data-nosnippet>2876</a>            .unwrap();
-<a href=#2877 id=2877 data-nosnippet>2877</a>
-<a href=#2878 id=2878 data-nosnippet>2878</a>        <span class="kw">let </span>parquet_schema = SchemaType::group_type_builder(<span class="string">"schema"</span>)
-<a href=#2879 id=2879 data-nosnippet>2879</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(list_type)])
-<a href=#2880 id=2880 data-nosnippet>2880</a>            .build()
-<a href=#2881 id=2881 data-nosnippet>2881</a>            .unwrap();
-<a href=#2882 id=2882 data-nosnippet>2882</a>
-<a href=#2883 id=2883 data-nosnippet>2883</a>        <span class="kw">let </span>(int96_val, expected_micros) = make_int96_test_value();
-<a href=#2884 id=2884 data-nosnippet>2884</a>
-<a href=#2885 id=2885 data-nosnippet>2885</a>        <span class="kw">let </span>file = File::create(<span class="kw-2">&amp;</span>file_path).unwrap();
-<a href=#2886 id=2886 data-nosnippet>2886</a>        <span class="kw">let </span><span class="kw-2">mut </span>writer =
-<a href=#2887 id=2887 data-nosnippet>2887</a>            SerializedFileWriter::new(file, Arc::new(parquet_schema), Default::default()).unwrap();
-<a href=#2888 id=2888 data-nosnippet>2888</a>
-<a href=#2889 id=2889 data-nosnippet>2889</a>        <span class="comment">// Write a single row with a list containing one INT96 element.
-<a href=#2890 id=2890 data-nosnippet>2890</a>        // def=3: list present (1) + repeated group (2) + element present (3)
-<a href=#2891 id=2891 data-nosnippet>2891</a>        // rep=0: start of a new list
-<a href=#2892 id=2892 data-nosnippet>2892</a>        </span><span class="kw">let </span><span class="kw-2">mut </span>row_group = writer.next_row_group().unwrap();
-<a href=#2893 id=2893 data-nosnippet>2893</a>        {
-<a href=#2894 id=2894 data-nosnippet>2894</a>            <span class="kw">let </span><span class="kw-2">mut </span>col = row_group.next_column().unwrap().unwrap();
-<a href=#2895 id=2895 data-nosnippet>2895</a>            col.typed::&lt;Int96Type&gt;()
-<a href=#2896 id=2896 data-nosnippet>2896</a>                .write_batch(<span class="kw-2">&amp;</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">3</span>]), <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">0</span>]))
-<a href=#2897 id=2897 data-nosnippet>2897</a>                .unwrap();
-<a href=#2898 id=2898 data-nosnippet>2898</a>            col.close().unwrap();
-<a href=#2899 id=2899 data-nosnippet>2899</a>        }
-<a href=#2900 id=2900 data-nosnippet>2900</a>        row_group.close().unwrap();
-<a href=#2901 id=2901 data-nosnippet>2901</a>        writer.close().unwrap();
-<a href=#2902 id=2902 data-nosnippet>2902</a>
-<a href=#2903 id=2903 data-nosnippet>2903</a>        <span class="kw">let </span>iceberg_schema = Arc::new(
-<a href=#2904 id=2904 data-nosnippet>2904</a>            Schema::builder()
-<a href=#2905 id=2905 data-nosnippet>2905</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#2906 id=2906 data-nosnippet>2906</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#2907 id=2907 data-nosnippet>2907</a>                    NestedField::optional(
-<a href=#2908 id=2908 data-nosnippet>2908</a>                        <span class="number">1</span>,
-<a href=#2909 id=2909 data-nosnippet>2909</a>                        <span class="string">"timestamps"</span>,
-<a href=#2910 id=2910 data-nosnippet>2910</a>                        Type::List(<span class="kw">crate</span>::spec::ListType {
-<a href=#2911 id=2911 data-nosnippet>2911</a>                            element_field: NestedField::optional(
-<a href=#2912 id=2912 data-nosnippet>2912</a>                                <span class="number">2</span>,
-<a href=#2913 id=2913 data-nosnippet>2913</a>                                <span class="string">"element"</span>,
-<a href=#2914 id=2914 data-nosnippet>2914</a>                                Type::Primitive(PrimitiveType::Timestamp),
-<a href=#2915 id=2915 data-nosnippet>2915</a>                            )
-<a href=#2916 id=2916 data-nosnippet>2916</a>                            .into(),
-<a href=#2917 id=2917 data-nosnippet>2917</a>                        }),
-<a href=#2918 id=2918 data-nosnippet>2918</a>                    )
-<a href=#2919 id=2919 data-nosnippet>2919</a>                    .into(),
-<a href=#2920 id=2920 data-nosnippet>2920</a>                ])
-<a href=#2921 id=2921 data-nosnippet>2921</a>                .build()
-<a href=#2922 id=2922 data-nosnippet>2922</a>                .unwrap(),
-<a href=#2923 id=2923 data-nosnippet>2923</a>        );
-<a href=#2924 id=2924 data-nosnippet>2924</a>
-<a href=#2925 id=2925 data-nosnippet>2925</a>        <span class="kw">let </span>batches = read_int96_batches(<span class="kw-2">&amp;</span>file_path, iceberg_schema, <span class="macro">vec!</span>[<span class="number">1</span>]).<span class="kw">await</span>;
-<a href=#2926 id=2926 data-nosnippet>2926</a>
-<a href=#2927 id=2927 data-nosnippet>2927</a>        <span class="macro">assert_eq!</span>(batches.len(), <span class="number">1</span>);
-<a href=#2928 id=2928 data-nosnippet>2928</a>        <span class="kw">let </span>list_array = batches[<span class="number">0</span>]
-<a href=#2929 id=2929 data-nosnippet>2929</a>            .column(<span class="number">0</span>)
-<a href=#2930 id=2930 data-nosnippet>2930</a>            .as_any()
-<a href=#2931 id=2931 data-nosnippet>2931</a>            .downcast_ref::&lt;ListArray&gt;()
-<a href=#2932 id=2932 data-nosnippet>2932</a>            .expect(<span class="string">"Expected ListArray"</span>);
-<a href=#2933 id=2933 data-nosnippet>2933</a>        <span class="kw">let </span>ts_array = list_array
-<a href=#2934 id=2934 data-nosnippet>2934</a>            .values()
-<a href=#2935 id=2935 data-nosnippet>2935</a>            .as_any()
-<a href=#2936 id=2936 data-nosnippet>2936</a>            .downcast_ref::&lt;TimestampMicrosecondArray&gt;()
-<a href=#2937 id=2937 data-nosnippet>2937</a>            .expect(<span class="string">"Expected TimestampMicrosecondArray inside list"</span>);
-<a href=#2938 id=2938 data-nosnippet>2938</a>
-<a href=#2939 id=2939 data-nosnippet>2939</a>        <span class="macro">assert_eq!</span>(
-<a href=#2940 id=2940 data-nosnippet>2940</a>            ts_array.value(<span class="number">0</span>),
-<a href=#2941 id=2941 data-nosnippet>2941</a>            expected_micros,
-<a href=#2942 id=2942 data-nosnippet>2942</a>            <span class="string">"INT96 in list: got {}, expected {expected_micros}"</span>,
-<a href=#2943 id=2943 data-nosnippet>2943</a>            ts_array.value(<span class="number">0</span>)
-<a href=#2944 id=2944 data-nosnippet>2944</a>        );
-<a href=#2945 id=2945 data-nosnippet>2945</a>    }
-<a href=#2946 id=2946 data-nosnippet>2946</a>
-<a href=#2947 id=2947 data-nosnippet>2947</a>    <span class="attr">#[tokio::test]
-<a href=#2948 id=2948 data-nosnippet>2948</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_in_map() {
-<a href=#2949 id=2949 data-nosnippet>2949</a>        <span class="kw">use </span>arrow_array::{MapArray, TimestampMicrosecondArray};
-<a href=#2950 id=2950 data-nosnippet>2950</a>        <span class="kw">use </span>parquet::basic::{Repetition, Type <span class="kw">as </span>PhysicalType};
-<a href=#2951 id=2951 data-nosnippet>2951</a>        <span class="kw">use </span>parquet::data_type::{ByteArrayType, Int96Type};
-<a href=#2952 id=2952 data-nosnippet>2952</a>        <span class="kw">use </span>parquet::file::writer::SerializedFileWriter;
-<a href=#2953 id=2953 data-nosnippet>2953</a>        <span class="kw">use </span>parquet::schema::types::Type <span class="kw">as </span>SchemaType;
-<a href=#2954 id=2954 data-nosnippet>2954</a>
-<a href=#2955 id=2955 data-nosnippet>2955</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#2956 id=2956 data-nosnippet>2956</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
-<a href=#2957 id=2957 data-nosnippet>2957</a>        <span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{table_location}/map_int96.parquet"</span>);
-<a href=#2958 id=2958 data-nosnippet>2958</a>
-<a href=#2959 id=2959 data-nosnippet>2959</a>        <span class="comment">// MAP encoding:
-<a href=#2960 id=2960 data-nosnippet>2960</a>        //   optional group ts_map (MAP) {
-<a href=#2961 id=2961 data-nosnippet>2961</a>        //     repeated group key_value {
-<a href=#2962 id=2962 data-nosnippet>2962</a>        //       required binary key (UTF8);
-<a href=#2963 id=2963 data-nosnippet>2963</a>        //       optional int96 value;
-<a href=#2964 id=2964 data-nosnippet>2964</a>        //     }
-<a href=#2965 id=2965 data-nosnippet>2965</a>        //   }
-<a href=#2966 id=2966 data-nosnippet>2966</a>        </span><span class="kw">let </span>key_type = SchemaType::primitive_type_builder(<span class="string">"key"</span>, PhysicalType::BYTE_ARRAY)
-<a href=#2967 id=2967 data-nosnippet>2967</a>            .with_repetition(Repetition::REQUIRED)
-<a href=#2968 id=2968 data-nosnippet>2968</a>            .with_logical_type(<span class="prelude-val">Some</span>(parquet::basic::LogicalType::String))
-<a href=#2969 id=2969 data-nosnippet>2969</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">2</span>))
-<a href=#2970 id=2970 data-nosnippet>2970</a>            .build()
-<a href=#2971 id=2971 data-nosnippet>2971</a>            .unwrap();
-<a href=#2972 id=2972 data-nosnippet>2972</a>
-<a href=#2973 id=2973 data-nosnippet>2973</a>        <span class="kw">let </span>value_type = SchemaType::primitive_type_builder(<span class="string">"value"</span>, PhysicalType::INT96)
-<a href=#2974 id=2974 data-nosnippet>2974</a>            .with_repetition(Repetition::OPTIONAL)
-<a href=#2975 id=2975 data-nosnippet>2975</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">3</span>))
-<a href=#2976 id=2976 data-nosnippet>2976</a>            .build()
-<a href=#2977 id=2977 data-nosnippet>2977</a>            .unwrap();
-<a href=#2978 id=2978 data-nosnippet>2978</a>
-<a href=#2979 id=2979 data-nosnippet>2979</a>        <span class="kw">let </span>key_value_group = SchemaType::group_type_builder(<span class="string">"key_value"</span>)
-<a href=#2980 id=2980 data-nosnippet>2980</a>            .with_repetition(Repetition::REPEATED)
-<a href=#2981 id=2981 data-nosnippet>2981</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(key_type), Arc::new(value_type)])
-<a href=#2982 id=2982 data-nosnippet>2982</a>            .build()
-<a href=#2983 id=2983 data-nosnippet>2983</a>            .unwrap();
-<a href=#2984 id=2984 data-nosnippet>2984</a>
-<a href=#2985 id=2985 data-nosnippet>2985</a>        <span class="kw">let </span>map_type = SchemaType::group_type_builder(<span class="string">"ts_map"</span>)
-<a href=#2986 id=2986 data-nosnippet>2986</a>            .with_repetition(Repetition::OPTIONAL)
-<a href=#2987 id=2987 data-nosnippet>2987</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">1</span>))
-<a href=#2988 id=2988 data-nosnippet>2988</a>            .with_logical_type(<span class="prelude-val">Some</span>(parquet::basic::LogicalType::Map))
-<a href=#2989 id=2989 data-nosnippet>2989</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(key_value_group)])
-<a href=#2990 id=2990 data-nosnippet>2990</a>            .build()
-<a href=#2991 id=2991 data-nosnippet>2991</a>            .unwrap();
-<a href=#2992 id=2992 data-nosnippet>2992</a>
-<a href=#2993 id=2993 data-nosnippet>2993</a>        <span class="kw">let </span>parquet_schema = SchemaType::group_type_builder(<span class="string">"schema"</span>)
-<a href=#2994 id=2994 data-nosnippet>2994</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(map_type)])
-<a href=#2995 id=2995 data-nosnippet>2995</a>            .build()
-<a href=#2996 id=2996 data-nosnippet>2996</a>            .unwrap();
-<a href=#2997 id=2997 data-nosnippet>2997</a>
-<a href=#2998 id=2998 data-nosnippet>2998</a>        <span class="kw">let </span>(int96_val, expected_micros) = make_int96_test_value();
-<a href=#2999 id=2999 data-nosnippet>2999</a>
-<a href=#3000 id=3000 data-nosnippet>3000</a>        <span class="kw">let </span>file = File::create(<span class="kw-2">&amp;</span>file_path).unwrap();
-<a href=#3001 id=3001 data-nosnippet>3001</a>        <span class="kw">let </span><span class="kw-2">mut </span>writer =
-<a href=#3002 id=3002 data-nosnippet>3002</a>            SerializedFileWriter::new(file, Arc::new(parquet_schema), Default::default()).unwrap();
-<a href=#3003 id=3003 data-nosnippet>3003</a>
-<a href=#3004 id=3004 data-nosnippet>3004</a>        <span class="comment">// Write a single row with a map containing one key-value pair.
-<a href=#3005 id=3005 data-nosnippet>3005</a>        // rep=0 for both columns: start of a new map.
-<a href=#3006 id=3006 data-nosnippet>3006</a>        // key def=2: map present (1) + key_value entry present (2), key is REQUIRED.
-<a href=#3007 id=3007 data-nosnippet>3007</a>        // value def=3: map present (1) + key_value entry present (2) + value present (3).
-<a href=#3008 id=3008 data-nosnippet>3008</a>        </span><span class="kw">let </span><span class="kw-2">mut </span>row_group = writer.next_row_group().unwrap();
-<a href=#3009 id=3009 data-nosnippet>3009</a>        {
-<a href=#3010 id=3010 data-nosnippet>3010</a>            <span class="kw">let </span><span class="kw-2">mut </span>col = row_group.next_column().unwrap().unwrap();
-<a href=#3011 id=3011 data-nosnippet>3011</a>            col.typed::&lt;ByteArrayType&gt;()
-<a href=#3012 id=3012 data-nosnippet>3012</a>                .write_batch(
-<a href=#3013 id=3013 data-nosnippet>3013</a>                    <span class="kw-2">&amp;</span>[parquet::data_type::ByteArray::from(<span class="string">"event_time"</span>)],
-<a href=#3014 id=3014 data-nosnippet>3014</a>                    <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">2</span>]),
-<a href=#3015 id=3015 data-nosnippet>3015</a>                    <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">0</span>]),
-<a href=#3016 id=3016 data-nosnippet>3016</a>                )
-<a href=#3017 id=3017 data-nosnippet>3017</a>                .unwrap();
-<a href=#3018 id=3018 data-nosnippet>3018</a>            col.close().unwrap();
-<a href=#3019 id=3019 data-nosnippet>3019</a>        }
-<a href=#3020 id=3020 data-nosnippet>3020</a>        {
-<a href=#3021 id=3021 data-nosnippet>3021</a>            <span class="kw">let </span><span class="kw-2">mut </span>col = row_group.next_column().unwrap().unwrap();
-<a href=#3022 id=3022 data-nosnippet>3022</a>            col.typed::&lt;Int96Type&gt;()
-<a href=#3023 id=3023 data-nosnippet>3023</a>                .write_batch(<span class="kw-2">&amp;</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">3</span>]), <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">0</span>]))
-<a href=#3024 id=3024 data-nosnippet>3024</a>                .unwrap();
-<a href=#3025 id=3025 data-nosnippet>3025</a>            col.close().unwrap();
-<a href=#3026 id=3026 data-nosnippet>3026</a>        }
-<a href=#3027 id=3027 data-nosnippet>3027</a>        row_group.close().unwrap();
-<a href=#3028 id=3028 data-nosnippet>3028</a>        writer.close().unwrap();
-<a href=#3029 id=3029 data-nosnippet>3029</a>
-<a href=#3030 id=3030 data-nosnippet>3030</a>        <span class="kw">let </span>iceberg_schema = Arc::new(
-<a href=#3031 id=3031 data-nosnippet>3031</a>            Schema::builder()
-<a href=#3032 id=3032 data-nosnippet>3032</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#3033 id=3033 data-nosnippet>3033</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#3034 id=3034 data-nosnippet>3034</a>                    NestedField::optional(
-<a href=#3035 id=3035 data-nosnippet>3035</a>                        <span class="number">1</span>,
-<a href=#3036 id=3036 data-nosnippet>3036</a>                        <span class="string">"ts_map"</span>,
-<a href=#3037 id=3037 data-nosnippet>3037</a>                        Type::Map(<span class="kw">crate</span>::spec::MapType {
-<a href=#3038 id=3038 data-nosnippet>3038</a>                            key_field: NestedField::required(
-<a href=#3039 id=3039 data-nosnippet>3039</a>                                <span class="number">2</span>,
-<a href=#3040 id=3040 data-nosnippet>3040</a>                                <span class="string">"key"</span>,
-<a href=#3041 id=3041 data-nosnippet>3041</a>                                Type::Primitive(PrimitiveType::String),
-<a href=#3042 id=3042 data-nosnippet>3042</a>                            )
-<a href=#3043 id=3043 data-nosnippet>3043</a>                            .into(),
-<a href=#3044 id=3044 data-nosnippet>3044</a>                            value_field: NestedField::optional(
-<a href=#3045 id=3045 data-nosnippet>3045</a>                                <span class="number">3</span>,
-<a href=#3046 id=3046 data-nosnippet>3046</a>                                <span class="string">"value"</span>,
-<a href=#3047 id=3047 data-nosnippet>3047</a>                                Type::Primitive(PrimitiveType::Timestamp),
-<a href=#3048 id=3048 data-nosnippet>3048</a>                            )
-<a href=#3049 id=3049 data-nosnippet>3049</a>                            .into(),
-<a href=#3050 id=3050 data-nosnippet>3050</a>                        }),
-<a href=#3051 id=3051 data-nosnippet>3051</a>                    )
-<a href=#3052 id=3052 data-nosnippet>3052</a>                    .into(),
-<a href=#3053 id=3053 data-nosnippet>3053</a>                ])
-<a href=#3054 id=3054 data-nosnippet>3054</a>                .build()
-<a href=#3055 id=3055 data-nosnippet>3055</a>                .unwrap(),
-<a href=#3056 id=3056 data-nosnippet>3056</a>        );
-<a href=#3057 id=3057 data-nosnippet>3057</a>
-<a href=#3058 id=3058 data-nosnippet>3058</a>        <span class="kw">let </span>batches = read_int96_batches(<span class="kw-2">&amp;</span>file_path, iceberg_schema, <span class="macro">vec!</span>[<span class="number">1</span>]).<span class="kw">await</span>;
-<a href=#3059 id=3059 data-nosnippet>3059</a>
-<a href=#3060 id=3060 data-nosnippet>3060</a>        <span class="macro">assert_eq!</span>(batches.len(), <span class="number">1</span>);
-<a href=#3061 id=3061 data-nosnippet>3061</a>        <span class="kw">let </span>map_array = batches[<span class="number">0</span>]
-<a href=#3062 id=3062 data-nosnippet>3062</a>            .column(<span class="number">0</span>)
-<a href=#3063 id=3063 data-nosnippet>3063</a>            .as_any()
-<a href=#3064 id=3064 data-nosnippet>3064</a>            .downcast_ref::&lt;MapArray&gt;()
-<a href=#3065 id=3065 data-nosnippet>3065</a>            .expect(<span class="string">"Expected MapArray"</span>);
-<a href=#3066 id=3066 data-nosnippet>3066</a>        <span class="kw">let </span>ts_array = map_array
-<a href=#3067 id=3067 data-nosnippet>3067</a>            .values()
-<a href=#3068 id=3068 data-nosnippet>3068</a>            .as_any()
-<a href=#3069 id=3069 data-nosnippet>3069</a>            .downcast_ref::&lt;TimestampMicrosecondArray&gt;()
-<a href=#3070 id=3070 data-nosnippet>3070</a>            .expect(<span class="string">"Expected TimestampMicrosecondArray as map values"</span>);
-<a href=#3071 id=3071 data-nosnippet>3071</a>
-<a href=#3072 id=3072 data-nosnippet>3072</a>        <span class="macro">assert_eq!</span>(
-<a href=#3073 id=3073 data-nosnippet>3073</a>            ts_array.value(<span class="number">0</span>),
-<a href=#3074 id=3074 data-nosnippet>3074</a>            expected_micros,
-<a href=#3075 id=3075 data-nosnippet>3075</a>            <span class="string">"INT96 in map: got {}, expected {expected_micros}"</span>,
-<a href=#3076 id=3076 data-nosnippet>3076</a>            ts_array.value(<span class="number">0</span>)
-<a href=#3077 id=3077 data-nosnippet>3077</a>        );
-<a href=#3078 id=3078 data-nosnippet>3078</a>    }
-<a href=#3079 id=3079 data-nosnippet>3079</a>
-<a href=#3080 id=3080 data-nosnippet>3080</a>    <span class="doccomment">/// Writes `id` (Int32) plus a wide string column (field id 2) whose bytes dominate
-<a href=#3081 id=3081 data-nosnippet>3081</a>    /// the file, so that reading it is visible in `bytes_read`.
-<a href=#3082 id=3082 data-nosnippet>3082</a>    ///
-<a href=#3083 id=3083 data-nosnippet>3083</a>    /// `extra_fields`/`extra_columns` (e.g. a physical metadata leaf) are appended after
-<a href=#3084 id=3084 data-nosnippet>3084</a>    /// the `id` and wide columns, mirroring `write_plain_parquet`'s shape.
-<a href=#3085 id=3085 data-nosnippet>3085</a>    </span><span class="kw">fn </span>write_parquet_with_wide_column(
-<a href=#3086 id=3086 data-nosnippet>3086</a>        dir: <span class="kw-2">&amp;</span>str,
-<a href=#3087 id=3087 data-nosnippet>3087</a>        name: <span class="kw-2">&amp;</span>str,
-<a href=#3088 id=3088 data-nosnippet>3088</a>        extra_fields: Vec&lt;Field&gt;,
-<a href=#3089 id=3089 data-nosnippet>3089</a>        extra_columns: Vec&lt;ArrayRef&gt;,
-<a href=#3090 id=3090 data-nosnippet>3090</a>    ) -&gt; String {
-<a href=#3091 id=3091 data-nosnippet>3091</a>        <span class="kw">let </span>wide_field =
-<a href=#3092 id=3092 data-nosnippet>3092</a>            Field::new(<span class="string">"wide"</span>, DataType::Utf8, <span class="bool-val">false</span>).with_metadata(HashMap::from([(
-<a href=#3093 id=3093 data-nosnippet>3093</a>                PARQUET_FIELD_ID_META_KEY.to_string(),
-<a href=#3094 id=3094 data-nosnippet>3094</a>                <span class="string">"2"</span>.to_string(),
-<a href=#3095 id=3095 data-nosnippet>3095</a>            )]));
-<a href=#3096 id=3096 data-nosnippet>3096</a>        <span class="comment">// Varied bytes so the column chunk does not compress away under SNAPPY, keeping
-<a href=#3097 id=3097 data-nosnippet>3097</a>        // the `bytes_read` difference between projecting it and not unambiguous.
-<a href=#3098 id=3098 data-nosnippet>3098</a>        </span><span class="kw">let </span>wide_values: Vec&lt;String&gt; = (<span class="number">0</span>..<span class="number">3</span>)
-<a href=#3099 id=3099 data-nosnippet>3099</a>            .map(|i| {
-<a href=#3100 id=3100 data-nosnippet>3100</a>                (<span class="number">0</span>..<span class="number">2048</span>)
-<a href=#3101 id=3101 data-nosnippet>3101</a>                    .map(|j| ((i * <span class="number">2048 </span>+ j) % <span class="number">251</span>) <span class="kw">as </span>u8 <span class="kw">as </span>char)
-<a href=#3102 id=3102 data-nosnippet>3102</a>                    .collect()
-<a href=#3103 id=3103 data-nosnippet>3103</a>            })
-<a href=#3104 id=3104 data-nosnippet>3104</a>            .collect();
-<a href=#3105 id=3105 data-nosnippet>3105</a>
-<a href=#3106 id=3106 data-nosnippet>3106</a>        <span class="kw">let </span><span class="kw-2">mut </span>fields = <span class="macro">vec!</span>[wide_field];
-<a href=#3107 id=3107 data-nosnippet>3107</a>        fields.extend(extra_fields);
-<a href=#3108 id=3108 data-nosnippet>3108</a>        <span class="kw">let </span><span class="kw-2">mut </span>columns: Vec&lt;ArrayRef&gt; = <span class="macro">vec!</span>[Arc::new(StringArray::from(wide_values))];
-<a href=#3109 id=3109 data-nosnippet>3109</a>        columns.extend(extra_columns);
-<a href=#3110 id=3110 data-nosnippet>3110</a>        write_plain_parquet(dir, name, fields, columns)
-<a href=#3111 id=3111 data-nosnippet>3111</a>    }
-<a href=#3112 id=3112 data-nosnippet>3112</a>
-<a href=#3113 id=3113 data-nosnippet>3113</a>    <span class="doccomment">/// Schema with `id` (field 1, Int) and `wide` (field 2, String), matching
-<a href=#3114 id=3114 data-nosnippet>3114</a>    /// `write_parquet_with_wide_column`.
-<a href=#3115 id=3115 data-nosnippet>3115</a>    </span><span class="kw">fn </span>id_and_wide_schema() -&gt; SchemaRef {
-<a href=#3116 id=3116 data-nosnippet>3116</a>        Arc::new(
-<a href=#3117 id=3117 data-nosnippet>3117</a>            Schema::builder()
-<a href=#3118 id=3118 data-nosnippet>3118</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#3119 id=3119 data-nosnippet>3119</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#3120 id=3120 data-nosnippet>3120</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
-<a href=#3121 id=3121 data-nosnippet>3121</a>                    NestedField::required(<span class="number">2</span>, <span class="string">"wide"</span>, Type::Primitive(PrimitiveType::String)).into(),
-<a href=#3122 id=3122 data-nosnippet>3122</a>                ])
-<a href=#3123 id=3123 data-nosnippet>3123</a>                .build()
-<a href=#3124 id=3124 data-nosnippet>3124</a>                .unwrap(),
-<a href=#3125 id=3125 data-nosnippet>3125</a>        )
-<a href=#3126 id=3126 data-nosnippet>3126</a>    }
-<a href=#3127 id=3127 data-nosnippet>3127</a>
-<a href=#3128 id=3128 data-nosnippet>3128</a>    <span class="doccomment">/// Builds a scan task over `file_path` projecting `project_field_ids`.
-<a href=#3129 id=3129 data-nosnippet>3129</a>    </span><span class="kw">fn </span>metadata_projection_task(
-<a href=#3130 id=3130 data-nosnippet>3130</a>        file_path: String,
-<a href=#3131 id=3131 data-nosnippet>3131</a>        schema: SchemaRef,
-<a href=#3132 id=3132 data-nosnippet>3132</a>        project_field_ids: Vec&lt;i32&gt;,
-<a href=#3133 id=3133 data-nosnippet>3133</a>    ) -&gt; FileScanTask {
-<a href=#3134 id=3134 data-nosnippet>3134</a>        metadata_projection_task_with_first_row_id(file_path, schema, project_field_ids, <span class="prelude-val">None</span>)
-<a href=#3135 id=3135 data-nosnippet>3135</a>    }
-<a href=#3136 id=3136 data-nosnippet>3136</a>
-<a href=#3137 id=3137 data-nosnippet>3137</a>    <span class="kw">fn </span>metadata_projection_task_with_first_row_id(
-<a href=#3138 id=3138 data-nosnippet>3138</a>        file_path: String,
-<a href=#3139 id=3139 data-nosnippet>3139</a>        schema: SchemaRef,
-<a href=#3140 id=3140 data-nosnippet>3140</a>        project_field_ids: Vec&lt;i32&gt;,
-<a href=#3141 id=3141 data-nosnippet>3141</a>        first_row_id: <span class="prelude-ty">Option</span>&lt;i64&gt;,
-<a href=#3142 id=3142 data-nosnippet>3142</a>    ) -&gt; FileScanTask {
-<a href=#3143 id=3143 data-nosnippet>3143</a>        FileScanTask::builder()
-<a href=#3144 id=3144 data-nosnippet>3144</a>            .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
-<a href=#3145 id=3145 data-nosnippet>3145</a>            .with_start(<span class="number">0</span>)
-<a href=#3146 id=3146 data-nosnippet>3146</a>            .with_length(<span class="number">0</span>)
-<a href=#3147 id=3147 data-nosnippet>3147</a>            .with_data_file_path(file_path)
-<a href=#3148 id=3148 data-nosnippet>3148</a>            .with_data_file_format(DataFileFormat::Parquet)
-<a href=#3149 id=3149 data-nosnippet>3149</a>            .with_schema(schema)
-<a href=#3150 id=3150 data-nosnippet>3150</a>            .with_project_field_ids(project_field_ids)
-<a href=#3151 id=3151 data-nosnippet>3151</a>            .with_first_row_id(first_row_id)
-<a href=#3152 id=3152 data-nosnippet>3152</a>            .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#3153 id=3153 data-nosnippet>3153</a>            .build()
-<a href=#3154 id=3154 data-nosnippet>3154</a>            .unwrap()
-<a href=#3155 id=3155 data-nosnippet>3155</a>    }
-<a href=#3156 id=3156 data-nosnippet>3156</a>
-<a href=#3157 id=3157 data-nosnippet>3157</a>    <span class="doccomment">/// Runs a single-task scan and returns the batches plus the bytes read from storage.
-<a href=#3158 id=3158 data-nosnippet>3158</a>    </span><span class="kw">async fn </span>scan_task(task: FileScanTask) -&gt; (Vec&lt;RecordBatch&gt;, u64) {
-<a href=#3159 id=3159 data-nosnippet>3159</a>        <span class="kw">let </span>reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current()).build();
-<a href=#3160 id=3160 data-nosnippet>3160</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
-<a href=#3161 id=3161 data-nosnippet>3161</a>        <span class="kw">let </span>scan = reader.read(tasks).unwrap();
-<a href=#3162 id=3162 data-nosnippet>3162</a>        <span class="kw">let </span>metrics = scan.metrics().clone();
-<a href=#3163 id=3163 data-nosnippet>3163</a>        <span class="kw">let </span>batches = scan.stream().try_collect().<span class="kw">await</span>.unwrap();
-<a href=#3164 id=3164 data-nosnippet>3164</a>        (batches, metrics.bytes_read())
-<a href=#3165 id=3165 data-nosnippet>3165</a>    }
-<a href=#3166 id=3166 data-nosnippet>3166</a>
-<a href=#3167 id=3167 data-nosnippet>3167</a>    <span class="attr">#[tokio::test]
-<a href=#3168 id=3168 data-nosnippet>3168</a>    </span><span class="kw">async fn </span>test_pos_only_projection_reads_no_data_columns() {
-<a href=#3169 id=3169 data-nosnippet>3169</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3170 id=3170 data-nosnippet>3170</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3171 id=3171 data-nosnippet>3171</a>
-<a href=#3172 id=3172 data-nosnippet>3172</a>        <span class="kw">let </span>pos_only = metadata_projection_task(
-<a href=#3173 id=3173 data-nosnippet>3173</a>            write_parquet_with_wide_column(dir, <span class="string">"pos_only.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
-<a href=#3174 id=3174 data-nosnippet>3174</a>            id_and_wide_schema(),
-<a href=#3175 id=3175 data-nosnippet>3175</a>            <span class="macro">vec!</span>[RESERVED_FIELD_ID_POS],
-<a href=#3176 id=3176 data-nosnippet>3176</a>        );
-<a href=#3177 id=3177 data-nosnippet>3177</a>        <span class="kw">let </span>(batches, pos_only_bytes) = scan_task(pos_only).<span class="kw">await</span>;
-<a href=#3178 id=3178 data-nosnippet>3178</a>
-<a href=#3179 id=3179 data-nosnippet>3179</a>        <span class="comment">// Only `_pos` is materialized -- no data columns.
-<a href=#3180 id=3180 data-nosnippet>3180</a>        </span><span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">1</span>);
-<a href=#3181 id=3181 data-nosnippet>3181</a>        <span class="kw">let </span>pos_col = batches[<span class="number">0</span>]
-<a href=#3182 id=3182 data-nosnippet>3182</a>            .column_by_name(RESERVED_COL_NAME_POS)
-<a href=#3183 id=3183 data-nosnippet>3183</a>            .expect(<span class="string">"_pos column should be present"</span>)
-<a href=#3184 id=3184 data-nosnippet>3184</a>            .as_primitive::&lt;arrow_array::types::Int64Type&gt;();
-<a href=#3185 id=3185 data-nosnippet>3185</a>        <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&amp;</span>[<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]);
-<a href=#3186 id=3186 data-nosnippet>3186</a>
-<a href=#3187 id=3187 data-nosnippet>3187</a>        <span class="comment">// A scan of the same-shaped file that also projects the wide data column must read
-<a href=#3188 id=3188 data-nosnippet>3188</a>        // materially more, proving the wide column chunk was not fetched above.
-<a href=#3189 id=3189 data-nosnippet>3189</a>        </span><span class="kw">let </span>with_data = metadata_projection_task(
-<a href=#3190 id=3190 data-nosnippet>3190</a>            write_parquet_with_wide_column(dir, <span class="string">"pos_only_ref.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
-<a href=#3191 id=3191 data-nosnippet>3191</a>            id_and_wide_schema(),
-<a href=#3192 id=3192 data-nosnippet>3192</a>            <span class="macro">vec!</span>[<span class="number">2</span>, RESERVED_FIELD_ID_POS],
-<a href=#3193 id=3193 data-nosnippet>3193</a>        );
-<a href=#3194 id=3194 data-nosnippet>3194</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
-<a href=#3195 id=3195 data-nosnippet>3195</a>
-<a href=#3196 id=3196 data-nosnippet>3196</a>        <span class="macro">assert!</span>(
-<a href=#3197 id=3197 data-nosnippet>3197</a>            pos_only_bytes &lt; with_data_bytes,
-<a href=#3198 id=3198 data-nosnippet>3198</a>            <span class="string">"_pos-only scan should read fewer bytes than a scan of the wide column: \
-<a href=#3199 id=3199 data-nosnippet>3199</a>             {pos_only_bytes} vs {with_data_bytes}"
-<a href=#3200 id=3200 data-nosnippet>3200</a>        </span>);
-<a href=#3201 id=3201 data-nosnippet>3201</a>    }
-<a href=#3202 id=3202 data-nosnippet>3202</a>
-<a href=#3203 id=3203 data-nosnippet>3203</a>    <span class="attr">#[tokio::test]
-<a href=#3204 id=3204 data-nosnippet>3204</a>    </span><span class="kw">async fn </span>test_pos_only_projection_keeps_absolute_pos_under_predicate() {
-<a href=#3205 id=3205 data-nosnippet>3205</a>        <span class="kw">use </span><span class="kw">crate</span>::expr::{Bind, Reference};
-<a href=#3206 id=3206 data-nosnippet>3206</a>        <span class="kw">use </span><span class="kw">crate</span>::spec::Datum;
-<a href=#3207 id=3207 data-nosnippet>3207</a>
-<a href=#3208 id=3208 data-nosnippet>3208</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3209 id=3209 data-nosnippet>3209</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3210 id=3210 data-nosnippet>3210</a>        <span class="comment">// id = [1, 2, 3]; drop the middle physical row via a predicate + row selection.
-<a href=#3211 id=3211 data-nosnippet>3211</a>        </span><span class="kw">let </span>file_path = write_plain_parquet(dir, <span class="string">"pos_only_predicate.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]);
-<a href=#3212 id=3212 data-nosnippet>3212</a>
-<a href=#3213 id=3213 data-nosnippet>3213</a>        <span class="kw">let </span>schema = Arc::new(
-<a href=#3214 id=3214 data-nosnippet>3214</a>            Schema::builder()
-<a href=#3215 id=3215 data-nosnippet>3215</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#3216 id=3216 data-nosnippet>3216</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#3217 id=3217 data-nosnippet>3217</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
-<a href=#3218 id=3218 data-nosnippet>3218</a>                ])
-<a href=#3219 id=3219 data-nosnippet>3219</a>                .build()
-<a href=#3220 id=3220 data-nosnippet>3220</a>                .unwrap(),
-<a href=#3221 id=3221 data-nosnippet>3221</a>        );
-<a href=#3222 id=3222 data-nosnippet>3222</a>        <span class="kw">let </span>bound = Reference::new(<span class="string">"id"</span>)
-<a href=#3223 id=3223 data-nosnippet>3223</a>            .not_equal_to(Datum::int(<span class="number">2</span>))
-<a href=#3224 id=3224 data-nosnippet>3224</a>            .bind(Arc::clone(<span class="kw-2">&amp;</span>schema), <span class="bool-val">false</span>)
-<a href=#3225 id=3225 data-nosnippet>3225</a>            .unwrap();
-<a href=#3226 id=3226 data-nosnippet>3226</a>        <span class="kw">let </span>task = FileScanTask::builder()
-<a href=#3227 id=3227 data-nosnippet>3227</a>            .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
-<a href=#3228 id=3228 data-nosnippet>3228</a>            .with_start(<span class="number">0</span>)
-<a href=#3229 id=3229 data-nosnippet>3229</a>            .with_length(<span class="number">0</span>)
-<a href=#3230 id=3230 data-nosnippet>3230</a>            .with_data_file_path(file_path)
-<a href=#3231 id=3231 data-nosnippet>3231</a>            .with_data_file_format(DataFileFormat::Parquet)
-<a href=#3232 id=3232 data-nosnippet>3232</a>            .with_schema(schema)
-<a href=#3233 id=3233 data-nosnippet>3233</a>            .with_project_field_ids(<span class="macro">vec!</span>[RESERVED_FIELD_ID_POS])
-<a href=#3234 id=3234 data-nosnippet>3234</a>            .with_predicate(<span class="prelude-val">Some</span>(bound))
-<a href=#3235 id=3235 data-nosnippet>3235</a>            .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#3236 id=3236 data-nosnippet>3236</a>            .build()
-<a href=#3237 id=3237 data-nosnippet>3237</a>            .unwrap();
-<a href=#3238 id=3238 data-nosnippet>3238</a>
-<a href=#3239 id=3239 data-nosnippet>3239</a>        <span class="comment">// Row selection must be enabled for the predicate to filter rows. The row filter
-<a href=#3240 id=3240 data-nosnippet>3240</a>        // reads `id` for its own evaluation even though `id` is not projected; the surviving
-<a href=#3241 id=3241 data-nosnippet>3241</a>        // rows must keep their ABSOLUTE positions (0 and 2), not renumbered (0 and 1).
-<a href=#3242 id=3242 data-nosnippet>3242</a>        </span><span class="kw">let </span>reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current())
-<a href=#3243 id=3243 data-nosnippet>3243</a>            .with_row_selection_enabled(<span class="bool-val">true</span>)
-<a href=#3244 id=3244 data-nosnippet>3244</a>            .build();
-<a href=#3245 id=3245 data-nosnippet>3245</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
-<a href=#3246 id=3246 data-nosnippet>3246</a>        <span class="kw">let </span>batches: Vec&lt;RecordBatch&gt; = reader
-<a href=#3247 id=3247 data-nosnippet>3247</a>            .read(tasks)
-<a href=#3248 id=3248 data-nosnippet>3248</a>            .unwrap()
-<a href=#3249 id=3249 data-nosnippet>3249</a>            .stream()
-<a href=#3250 id=3250 data-nosnippet>3250</a>            .try_collect()
-<a href=#3251 id=3251 data-nosnippet>3251</a>            .<span class="kw">await
-<a href=#3252 id=3252 data-nosnippet>3252</a>            </span>.unwrap();
-<a href=#3253 id=3253 data-nosnippet>3253</a>
-<a href=#3254 id=3254 data-nosnippet>3254</a>        <span class="kw">let </span>pos: Vec&lt;i64&gt; = batches
-<a href=#3255 id=3255 data-nosnippet>3255</a>            .iter()
-<a href=#3256 id=3256 data-nosnippet>3256</a>            .flat_map(|b| {
-<a href=#3257 id=3257 data-nosnippet>3257</a>                b.column_by_name(RESERVED_COL_NAME_POS)
-<a href=#3258 id=3258 data-nosnippet>3258</a>                    .expect(<span class="string">"_pos column should be present"</span>)
-<a href=#3259 id=3259 data-nosnippet>3259</a>                    .as_primitive::&lt;arrow_array::types::Int64Type&gt;()
-<a href=#3260 id=3260 data-nosnippet>3260</a>                    .values()
-<a href=#3261 id=3261 data-nosnippet>3261</a>                    .to_vec()
-<a href=#3262 id=3262 data-nosnippet>3262</a>            })
-<a href=#3263 id=3263 data-nosnippet>3263</a>            .collect();
-<a href=#3264 id=3264 data-nosnippet>3264</a>        <span class="macro">assert_eq!</span>(pos, <span class="macro">vec!</span>[<span class="number">0</span>, <span class="number">2</span>]);
-<a href=#3265 id=3265 data-nosnippet>3265</a>    }
-<a href=#3266 id=3266 data-nosnippet>3266</a>
-<a href=#3267 id=3267 data-nosnippet>3267</a>    <span class="attr">#[tokio::test]
-<a href=#3268 id=3268 data-nosnippet>3268</a>    </span><span class="kw">async fn </span>test_pos_and_file_projection() {
-<a href=#3269 id=3269 data-nosnippet>3269</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::RESERVED_COL_NAME_FILE;
-<a href=#3270 id=3270 data-nosnippet>3270</a>
-<a href=#3271 id=3271 data-nosnippet>3271</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3272 id=3272 data-nosnippet>3272</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3273 id=3273 data-nosnippet>3273</a>        <span class="comment">// The motivating row-lineage shape: a synthesized position column (mask -&gt; none)
-<a href=#3274 id=3274 data-nosnippet>3274</a>        // alongside a materialized per-file constant.
-<a href=#3275 id=3275 data-nosnippet>3275</a>        </span><span class="kw">let </span>file_path = write_parquet_with_wide_column(dir, <span class="string">"pos_and_file.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]);
-<a href=#3276 id=3276 data-nosnippet>3276</a>        <span class="kw">let </span>task = metadata_projection_task(file_path.clone(), id_and_wide_schema(), <span class="macro">vec!</span>[
-<a href=#3277 id=3277 data-nosnippet>3277</a>            RESERVED_FIELD_ID_POS,
-<a href=#3278 id=3278 data-nosnippet>3278</a>            RESERVED_FIELD_ID_FILE,
-<a href=#3279 id=3279 data-nosnippet>3279</a>        ]);
-<a href=#3280 id=3280 data-nosnippet>3280</a>        <span class="kw">let </span>(batches, <span class="kw">_</span>) = scan_task(task).<span class="kw">await</span>;
-<a href=#3281 id=3281 data-nosnippet>3281</a>
-<a href=#3282 id=3282 data-nosnippet>3282</a>        <span class="comment">// Both metadata columns materialize; no data column is read.
-<a href=#3283 id=3283 data-nosnippet>3283</a>        </span><span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">2</span>);
-<a href=#3284 id=3284 data-nosnippet>3284</a>        <span class="kw">let </span>pos_col = batches[<span class="number">0</span>]
-<a href=#3285 id=3285 data-nosnippet>3285</a>            .column_by_name(RESERVED_COL_NAME_POS)
-<a href=#3286 id=3286 data-nosnippet>3286</a>            .expect(<span class="string">"_pos column should be present"</span>)
-<a href=#3287 id=3287 data-nosnippet>3287</a>            .as_primitive::&lt;arrow_array::types::Int64Type&gt;();
-<a href=#3288 id=3288 data-nosnippet>3288</a>        <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&amp;</span>[<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]);
-<a href=#3289 id=3289 data-nosnippet>3289</a>        <span class="kw">let </span>file_col = batches[<span class="number">0</span>]
-<a href=#3290 id=3290 data-nosnippet>3290</a>            .column_by_name(RESERVED_COL_NAME_FILE)
-<a href=#3291 id=3291 data-nosnippet>3291</a>            .expect(<span class="string">"_file column should be present"</span>);
-<a href=#3292 id=3292 data-nosnippet>3292</a>        <span class="kw">let </span>file_col = cast(file_col, <span class="kw-2">&amp;</span>DataType::Utf8).unwrap();
-<a href=#3293 id=3293 data-nosnippet>3293</a>        <span class="kw">let </span>file_col = file_col.as_any().downcast_ref::&lt;StringArray&gt;().unwrap();
-<a href=#3294 id=3294 data-nosnippet>3294</a>        <span class="macro">assert_eq!</span>(file_col.value(<span class="number">0</span>), file_path);
-<a href=#3295 id=3295 data-nosnippet>3295</a>    }
-<a href=#3296 id=3296 data-nosnippet>3296</a>
-<a href=#3297 id=3297 data-nosnippet>3297</a>    <span class="attr">#[tokio::test]
-<a href=#3298 id=3298 data-nosnippet>3298</a>    </span><span class="kw">async fn </span>test_pos_and_physical_seq_projection_reads_only_the_leaf() {
-<a href=#3299 id=3299 data-nosnippet>3299</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::{
-<a href=#3300 id=3300 data-nosnippet>3300</a>            RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3301 id=3301 data-nosnippet>3301</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3302 id=3302 data-nosnippet>3302</a>        };
-<a href=#3303 id=3303 data-nosnippet>3303</a>
-<a href=#3304 id=3304 data-nosnippet>3304</a>        <span class="comment">// A v3 rewrite that carried rows forward stores `_last_updated_sequence_number`
-<a href=#3305 id=3305 data-nosnippet>3305</a>        // per-row. Projecting only `_pos` + the sequence column must read just that one
-<a href=#3306 id=3306 data-nosnippet>3306</a>        // physical leaf, not every data column.
-<a href=#3307 id=3307 data-nosnippet>3307</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3308 id=3308 data-nosnippet>3308</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3309 id=3309 data-nosnippet>3309</a>
-<a href=#3310 id=3310 data-nosnippet>3310</a>        <span class="comment">// File: id (1), wide data column (2), physical _last_updated_sequence_number.
-<a href=#3311 id=3311 data-nosnippet>3311</a>        </span><span class="kw">let </span>write = |name: <span class="kw-2">&amp;</span>str| {
-<a href=#3312 id=3312 data-nosnippet>3312</a>            write_parquet_with_wide_column(
-<a href=#3313 id=3313 data-nosnippet>3313</a>                dir,
-<a href=#3314 id=3314 data-nosnippet>3314</a>                name,
-<a href=#3315 id=3315 data-nosnippet>3315</a>                <span class="macro">vec!</span>[physical_last_updated_seq_field()],
-<a href=#3316 id=3316 data-nosnippet>3316</a>                <span class="macro">vec!</span>[Arc::new(Int64Array::from(<span class="macro">vec!</span>[<span class="prelude-val">Some</span>(<span class="number">5</span>), <span class="prelude-val">None</span>, <span class="prelude-val">Some</span>(<span class="number">8</span>)])) <span class="kw">as </span>ArrayRef],
-<a href=#3317 id=3317 data-nosnippet>3317</a>            )
-<a href=#3318 id=3318 data-nosnippet>3318</a>        };
-<a href=#3319 id=3319 data-nosnippet>3319</a>
-<a href=#3320 id=3320 data-nosnippet>3320</a>        <span class="kw">let </span>seq_task = |path: String, ids: Vec&lt;i32&gt;| {
-<a href=#3321 id=3321 data-nosnippet>3321</a>            FileScanTask::builder()
-<a href=#3322 id=3322 data-nosnippet>3322</a>                .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>path).unwrap().len())
-<a href=#3323 id=3323 data-nosnippet>3323</a>                .with_start(<span class="number">0</span>)
-<a href=#3324 id=3324 data-nosnippet>3324</a>                .with_length(<span class="number">0</span>)
-<a href=#3325 id=3325 data-nosnippet>3325</a>                .with_data_file_path(path)
-<a href=#3326 id=3326 data-nosnippet>3326</a>                .with_data_file_format(DataFileFormat::Parquet)
-<a href=#3327 id=3327 data-nosnippet>3327</a>                .with_schema(id_and_wide_schema())
-<a href=#3328 id=3328 data-nosnippet>3328</a>                .with_project_field_ids(ids)
-<a href=#3329 id=3329 data-nosnippet>3329</a>                .with_first_row_id(<span class="prelude-val">Some</span>(<span class="number">100</span>))
-<a href=#3330 id=3330 data-nosnippet>3330</a>                .with_data_sequence_number(<span class="prelude-val">Some</span>(<span class="number">9</span>))
-<a href=#3331 id=3331 data-nosnippet>3331</a>                .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#3332 id=3332 data-nosnippet>3332</a>                .build()
-<a href=#3333 id=3333 data-nosnippet>3333</a>                .unwrap()
-<a href=#3334 id=3334 data-nosnippet>3334</a>        };
-<a href=#3335 id=3335 data-nosnippet>3335</a>
-<a href=#3336 id=3336 data-nosnippet>3336</a>        <span class="kw">let </span>meta_only = seq_task(write(<span class="string">"pos_seq.parquet"</span>), <span class="macro">vec!</span>[
-<a href=#3337 id=3337 data-nosnippet>3337</a>            RESERVED_FIELD_ID_POS,
-<a href=#3338 id=3338 data-nosnippet>3338</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3339 id=3339 data-nosnippet>3339</a>        ]);
-<a href=#3340 id=3340 data-nosnippet>3340</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
-<a href=#3341 id=3341 data-nosnippet>3341</a>
-<a href=#3342 id=3342 data-nosnippet>3342</a>        <span class="comment">// `_pos` and the coalesced sequence column materialize; the wide column does not.
-<a href=#3343 id=3343 data-nosnippet>3343</a>        </span><span class="kw">let </span>pos_col = batches[<span class="number">0</span>]
-<a href=#3344 id=3344 data-nosnippet>3344</a>            .column_by_name(RESERVED_COL_NAME_POS)
-<a href=#3345 id=3345 data-nosnippet>3345</a>            .expect(<span class="string">"_pos column should be present"</span>)
-<a href=#3346 id=3346 data-nosnippet>3346</a>            .as_primitive::&lt;arrow_array::types::Int64Type&gt;();
-<a href=#3347 id=3347 data-nosnippet>3347</a>        <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&amp;</span>[<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]);
-<a href=#3348 id=3348 data-nosnippet>3348</a>        <span class="kw">let </span>seq_col = batches[<span class="number">0</span>]
-<a href=#3349 id=3349 data-nosnippet>3349</a>            .column_by_name(RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER)
-<a href=#3350 id=3350 data-nosnippet>3350</a>            .expect(<span class="string">"_last_updated_sequence_number column should be present"</span>);
-<a href=#3351 id=3351 data-nosnippet>3351</a>        <span class="kw">let </span>seq_col = cast(seq_col, <span class="kw-2">&amp;</span>DataType::Int64).unwrap();
-<a href=#3352 id=3352 data-nosnippet>3352</a>        <span class="kw">let </span>seq_col = seq_col.as_any().downcast_ref::&lt;Int64Array&gt;().unwrap();
-<a href=#3353 id=3353 data-nosnippet>3353</a>        <span class="comment">// Per-row stored value where non-null, else the data sequence number (9).
-<a href=#3354 id=3354 data-nosnippet>3354</a>        </span><span class="macro">assert_eq!</span>(seq_col.value(<span class="number">0</span>), <span class="number">5</span>);
-<a href=#3355 id=3355 data-nosnippet>3355</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">1</span>), <span class="number">9</span>);
-<a href=#3356 id=3356 data-nosnippet>3356</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">2</span>), <span class="number">8</span>);
-<a href=#3357 id=3357 data-nosnippet>3357</a>        <span class="macro">assert!</span>(batches[<span class="number">0</span>].column_by_name(<span class="string">"wide"</span>).is_none());
-<a href=#3358 id=3358 data-nosnippet>3358</a>
-<a href=#3359 id=3359 data-nosnippet>3359</a>        <span class="comment">// A scan that also projects the wide data column must read materially more,
-<a href=#3360 id=3360 data-nosnippet>3360</a>        // proving the metadata-only scan pruned to just the sequence leaf.
-<a href=#3361 id=3361 data-nosnippet>3361</a>        </span><span class="kw">let </span>with_data = seq_task(write(<span class="string">"pos_seq_ref.parquet"</span>), <span class="macro">vec!</span>[
-<a href=#3362 id=3362 data-nosnippet>3362</a>            <span class="number">2</span>,
-<a href=#3363 id=3363 data-nosnippet>3363</a>            RESERVED_FIELD_ID_POS,
-<a href=#3364 id=3364 data-nosnippet>3364</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3365 id=3365 data-nosnippet>3365</a>        ]);
-<a href=#3366 id=3366 data-nosnippet>3366</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
-<a href=#3367 id=3367 data-nosnippet>3367</a>
-<a href=#3368 id=3368 data-nosnippet>3368</a>        <span class="macro">assert!</span>(
-<a href=#3369 id=3369 data-nosnippet>3369</a>            meta_only_bytes &lt; with_data_bytes,
-<a href=#3370 id=3370 data-nosnippet>3370</a>            <span class="string">"_pos + physical sequence scan should read fewer bytes than one that also \
-<a href=#3371 id=3371 data-nosnippet>3371</a>             reads the wide column: {meta_only_bytes} vs {with_data_bytes}"
-<a href=#3372 id=3372 data-nosnippet>3372</a>        </span>);
-<a href=#3373 id=3373 data-nosnippet>3373</a>    }
-<a href=#3374 id=3374 data-nosnippet>3374</a>
-<a href=#3375 id=3375 data-nosnippet>3375</a>    <span class="attr">#[tokio::test]
-<a href=#3376 id=3376 data-nosnippet>3376</a>    </span><span class="kw">async fn </span>test_seq_only_projection_reads_only_the_leaf() {
-<a href=#3377 id=3377 data-nosnippet>3377</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::{
-<a href=#3378 id=3378 data-nosnippet>3378</a>            RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3379 id=3379 data-nosnippet>3379</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3380 id=3380 data-nosnippet>3380</a>        };
-<a href=#3381 id=3381 data-nosnippet>3381</a>
-<a href=#3382 id=3382 data-nosnippet>3382</a>        <span class="comment">// `_last_updated_sequence_number` alone (no `_pos`, no data column). This is a
-<a href=#3383 id=3383 data-nosnippet>3383</a>        // metadata-only projection, so RowNumber drives the `none()` downgrade and the
-<a href=#3384 id=3384 data-nosnippet>3384</a>        // physical leaf is unioned back in, pruning the read to just that leaf.
-<a href=#3385 id=3385 data-nosnippet>3385</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3386 id=3386 data-nosnippet>3386</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3387 id=3387 data-nosnippet>3387</a>        <span class="kw">let </span>write = |name: <span class="kw-2">&amp;</span>str| {
-<a href=#3388 id=3388 data-nosnippet>3388</a>            write_parquet_with_wide_column(
-<a href=#3389 id=3389 data-nosnippet>3389</a>                dir,
-<a href=#3390 id=3390 data-nosnippet>3390</a>                name,
-<a href=#3391 id=3391 data-nosnippet>3391</a>                <span class="macro">vec!</span>[physical_last_updated_seq_field()],
-<a href=#3392 id=3392 data-nosnippet>3392</a>                <span class="macro">vec!</span>[Arc::new(Int64Array::from(<span class="macro">vec!</span>[<span class="prelude-val">Some</span>(<span class="number">5</span>), <span class="prelude-val">None</span>, <span class="prelude-val">Some</span>(<span class="number">8</span>)])) <span class="kw">as </span>ArrayRef],
-<a href=#3393 id=3393 data-nosnippet>3393</a>            )
-<a href=#3394 id=3394 data-nosnippet>3394</a>        };
-<a href=#3395 id=3395 data-nosnippet>3395</a>        <span class="kw">let </span>seq_task = |path: String, ids: Vec&lt;i32&gt;| {
-<a href=#3396 id=3396 data-nosnippet>3396</a>            FileScanTask::builder()
-<a href=#3397 id=3397 data-nosnippet>3397</a>                .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>path).unwrap().len())
-<a href=#3398 id=3398 data-nosnippet>3398</a>                .with_start(<span class="number">0</span>)
-<a href=#3399 id=3399 data-nosnippet>3399</a>                .with_length(<span class="number">0</span>)
-<a href=#3400 id=3400 data-nosnippet>3400</a>                .with_data_file_path(path)
-<a href=#3401 id=3401 data-nosnippet>3401</a>                .with_data_file_format(DataFileFormat::Parquet)
-<a href=#3402 id=3402 data-nosnippet>3402</a>                .with_schema(id_and_wide_schema())
-<a href=#3403 id=3403 data-nosnippet>3403</a>                .with_project_field_ids(ids)
-<a href=#3404 id=3404 data-nosnippet>3404</a>                .with_first_row_id(<span class="prelude-val">Some</span>(<span class="number">100</span>))
-<a href=#3405 id=3405 data-nosnippet>3405</a>                .with_data_sequence_number(<span class="prelude-val">Some</span>(<span class="number">9</span>))
-<a href=#3406 id=3406 data-nosnippet>3406</a>                .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#3407 id=3407 data-nosnippet>3407</a>                .build()
-<a href=#3408 id=3408 data-nosnippet>3408</a>                .unwrap()
-<a href=#3409 id=3409 data-nosnippet>3409</a>        };
-<a href=#3410 id=3410 data-nosnippet>3410</a>
-<a href=#3411 id=3411 data-nosnippet>3411</a>        <span class="kw">let </span>meta_only = seq_task(write(<span class="string">"seq_only.parquet"</span>), <span class="macro">vec!</span>[
-<a href=#3412 id=3412 data-nosnippet>3412</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3413 id=3413 data-nosnippet>3413</a>        ]);
-<a href=#3414 id=3414 data-nosnippet>3414</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
-<a href=#3415 id=3415 data-nosnippet>3415</a>
-<a href=#3416 id=3416 data-nosnippet>3416</a>        <span class="kw">let </span>seq_col = batches[<span class="number">0</span>]
-<a href=#3417 id=3417 data-nosnippet>3417</a>            .column_by_name(RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER)
-<a href=#3418 id=3418 data-nosnippet>3418</a>            .expect(<span class="string">"_last_updated_sequence_number column should be present"</span>);
-<a href=#3419 id=3419 data-nosnippet>3419</a>        <span class="kw">let </span>seq_col = cast(seq_col, <span class="kw-2">&amp;</span>DataType::Int64).unwrap();
-<a href=#3420 id=3420 data-nosnippet>3420</a>        <span class="kw">let </span>seq_col = seq_col.as_any().downcast_ref::&lt;Int64Array&gt;().unwrap();
-<a href=#3421 id=3421 data-nosnippet>3421</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">0</span>), <span class="number">5</span>);
-<a href=#3422 id=3422 data-nosnippet>3422</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">1</span>), <span class="number">9</span>);
-<a href=#3423 id=3423 data-nosnippet>3423</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">2</span>), <span class="number">8</span>);
-<a href=#3424 id=3424 data-nosnippet>3424</a>        <span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
-<a href=#3425 id=3425 data-nosnippet>3425</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
-<a href=#3426 id=3426 data-nosnippet>3426</a>        <span class="macro">assert!</span>(batches[<span class="number">0</span>].column_by_name(<span class="string">"wide"</span>).is_none());
-<a href=#3427 id=3427 data-nosnippet>3427</a>
-<a href=#3428 id=3428 data-nosnippet>3428</a>        <span class="kw">let </span>with_data = seq_task(write(<span class="string">"seq_only_ref.parquet"</span>), <span class="macro">vec!</span>[
-<a href=#3429 id=3429 data-nosnippet>3429</a>            <span class="number">2</span>,
-<a href=#3430 id=3430 data-nosnippet>3430</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3431 id=3431 data-nosnippet>3431</a>        ]);
-<a href=#3432 id=3432 data-nosnippet>3432</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
-<a href=#3433 id=3433 data-nosnippet>3433</a>
-<a href=#3434 id=3434 data-nosnippet>3434</a>        <span class="macro">assert!</span>(
-<a href=#3435 id=3435 data-nosnippet>3435</a>            meta_only_bytes &lt; with_data_bytes,
-<a href=#3436 id=3436 data-nosnippet>3436</a>            <span class="string">"seq-only scan should read fewer bytes than one that also reads the wide \
-<a href=#3437 id=3437 data-nosnippet>3437</a>             column: {meta_only_bytes} vs {with_data_bytes}"
-<a href=#3438 id=3438 data-nosnippet>3438</a>        </span>);
-<a href=#3439 id=3439 data-nosnippet>3439</a>    }
-<a href=#3440 id=3440 data-nosnippet>3440</a>
-<a href=#3441 id=3441 data-nosnippet>3441</a>    <span class="attr">#[tokio::test]
-<a href=#3442 id=3442 data-nosnippet>3442</a>    </span><span class="kw">async fn </span>test_seq_only_null_first_row_id_reads_no_data_columns() {
-<a href=#3443 id=3443 data-nosnippet>3443</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::{
-<a href=#3444 id=3444 data-nosnippet>3444</a>            RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3445 id=3445 data-nosnippet>3445</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3446 id=3446 data-nosnippet>3446</a>        };
-<a href=#3447 id=3447 data-nosnippet>3447</a>
-<a href=#3448 id=3448 data-nosnippet>3448</a>        <span class="comment">// Seq-only projection with a null first_row_id: the column is nulled and the
-<a href=#3449 id=3449 data-nosnippet>3449</a>        // physical leaf is NOT read (the gated `coalesce_last_updated_seq_leaf` is None).
-<a href=#3450 id=3450 data-nosnippet>3450</a>        // This is a metadata-only projection, so RowNumber supplies the row count and the
-<a href=#3451 id=3451 data-nosnippet>3451</a>        // data columns are pruned; the row count is still 3 and the values all null.
-<a href=#3452 id=3452 data-nosnippet>3452</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3453 id=3453 data-nosnippet>3453</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3454 id=3454 data-nosnippet>3454</a>        <span class="kw">let </span>seq_task = |name: <span class="kw-2">&amp;</span>str, ids: Vec&lt;i32&gt;| {
-<a href=#3455 id=3455 data-nosnippet>3455</a>            <span class="kw">let </span>file_path = write_parquet_with_wide_column(
-<a href=#3456 id=3456 data-nosnippet>3456</a>                dir,
-<a href=#3457 id=3457 data-nosnippet>3457</a>                name,
-<a href=#3458 id=3458 data-nosnippet>3458</a>                <span class="macro">vec!</span>[physical_last_updated_seq_field()],
-<a href=#3459 id=3459 data-nosnippet>3459</a>                <span class="macro">vec!</span>[Arc::new(Int64Array::from(<span class="macro">vec!</span>[<span class="prelude-val">Some</span>(<span class="number">5</span>), <span class="prelude-val">Some</span>(<span class="number">6</span>), <span class="prelude-val">Some</span>(<span class="number">7</span>)])) <span class="kw">as </span>ArrayRef],
-<a href=#3460 id=3460 data-nosnippet>3460</a>            );
-<a href=#3461 id=3461 data-nosnippet>3461</a>            FileScanTask::builder()
-<a href=#3462 id=3462 data-nosnippet>3462</a>                .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
-<a href=#3463 id=3463 data-nosnippet>3463</a>                .with_start(<span class="number">0</span>)
-<a href=#3464 id=3464 data-nosnippet>3464</a>                .with_length(<span class="number">0</span>)
-<a href=#3465 id=3465 data-nosnippet>3465</a>                .with_data_file_path(file_path)
-<a href=#3466 id=3466 data-nosnippet>3466</a>                .with_data_file_format(DataFileFormat::Parquet)
-<a href=#3467 id=3467 data-nosnippet>3467</a>                .with_schema(id_and_wide_schema())
-<a href=#3468 id=3468 data-nosnippet>3468</a>                .with_project_field_ids(ids)
-<a href=#3469 id=3469 data-nosnippet>3469</a>                .with_first_row_id(<span class="prelude-val">None</span>)
-<a href=#3470 id=3470 data-nosnippet>3470</a>                .with_data_sequence_number(<span class="prelude-val">Some</span>(<span class="number">9</span>))
-<a href=#3471 id=3471 data-nosnippet>3471</a>                .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#3472 id=3472 data-nosnippet>3472</a>                .build()
-<a href=#3473 id=3473 data-nosnippet>3473</a>                .unwrap()
-<a href=#3474 id=3474 data-nosnippet>3474</a>        };
-<a href=#3475 id=3475 data-nosnippet>3475</a>
-<a href=#3476 id=3476 data-nosnippet>3476</a>        <span class="kw">let </span>meta_only = seq_task(<span class="string">"seq_only_null_first.parquet"</span>, <span class="macro">vec!</span>[
-<a href=#3477 id=3477 data-nosnippet>3477</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3478 id=3478 data-nosnippet>3478</a>        ]);
-<a href=#3479 id=3479 data-nosnippet>3479</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
-<a href=#3480 id=3480 data-nosnippet>3480</a>
-<a href=#3481 id=3481 data-nosnippet>3481</a>        <span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">1</span>);
-<a href=#3482 id=3482 data-nosnippet>3482</a>        <span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
-<a href=#3483 id=3483 data-nosnippet>3483</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
-<a href=#3484 id=3484 data-nosnippet>3484</a>        <span class="kw">let </span>seq_col = batches[<span class="number">0</span>]
-<a href=#3485 id=3485 data-nosnippet>3485</a>            .column_by_name(RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER)
-<a href=#3486 id=3486 data-nosnippet>3486</a>            .expect(<span class="string">"_last_updated_sequence_number column should be present"</span>);
-<a href=#3487 id=3487 data-nosnippet>3487</a>        <span class="kw">let </span>seq_col = cast(seq_col, <span class="kw-2">&amp;</span>DataType::Int64).unwrap();
-<a href=#3488 id=3488 data-nosnippet>3488</a>        <span class="kw">let </span>seq_col = seq_col.as_any().downcast_ref::&lt;Int64Array&gt;().unwrap();
-<a href=#3489 id=3489 data-nosnippet>3489</a>        <span class="macro">assert!</span>((<span class="number">0</span>..<span class="number">3</span>).all(|i| seq_col.is_null(i)));
-<a href=#3490 id=3490 data-nosnippet>3490</a>
-<a href=#3491 id=3491 data-nosnippet>3491</a>        <span class="kw">let </span>with_data = seq_task(<span class="string">"seq_only_null_first_ref.parquet"</span>, <span class="macro">vec!</span>[
-<a href=#3492 id=3492 data-nosnippet>3492</a>            <span class="number">2</span>,
-<a href=#3493 id=3493 data-nosnippet>3493</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
-<a href=#3494 id=3494 data-nosnippet>3494</a>        ]);
-<a href=#3495 id=3495 data-nosnippet>3495</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
-<a href=#3496 id=3496 data-nosnippet>3496</a>
-<a href=#3497 id=3497 data-nosnippet>3497</a>        <span class="macro">assert!</span>(
-<a href=#3498 id=3498 data-nosnippet>3498</a>            meta_only_bytes &lt; with_data_bytes,
-<a href=#3499 id=3499 data-nosnippet>3499</a>            <span class="string">"seq-only scan with a null first_row_id should read fewer bytes than a scan of \
-<a href=#3500 id=3500 data-nosnippet>3500</a>             the wide column: {meta_only_bytes} vs {with_data_bytes}"
-<a href=#3501 id=3501 data-nosnippet>3501</a>        </span>);
-<a href=#3502 id=3502 data-nosnippet>3502</a>    }
-<a href=#3503 id=3503 data-nosnippet>3503</a>
-<a href=#3504 id=3504 data-nosnippet>3504</a>    <span class="attr">#[tokio::test]
-<a href=#3505 id=3505 data-nosnippet>3505</a>    </span><span class="kw">async fn </span>test_file_only_reads_no_data_columns() {
-<a href=#3506 id=3506 data-nosnippet>3506</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::RESERVED_COL_NAME_FILE;
-<a href=#3507 id=3507 data-nosnippet>3507</a>
-<a href=#3508 id=3508 data-nosnippet>3508</a>        <span class="comment">// `SELECT _file` materializes a per-file string constant with no physical column to
-<a href=#3509 id=3509 data-nosnippet>3509</a>        // read. The metadata-only RowNumber counter sizes it, so the scan prunes the data
-<a href=#3510 id=3510 data-nosnippet>3510</a>        // columns rather than reading them all just to recover the row count.
-<a href=#3511 id=3511 data-nosnippet>3511</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3512 id=3512 data-nosnippet>3512</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3513 id=3513 data-nosnippet>3513</a>
-<a href=#3514 id=3514 data-nosnippet>3514</a>        <span class="kw">let </span>file_path = write_parquet_with_wide_column(dir, <span class="string">"file_only.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]);
-<a href=#3515 id=3515 data-nosnippet>3515</a>        <span class="kw">let </span>meta_only = metadata_projection_task(file_path.clone(), id_and_wide_schema(), <span class="macro">vec!</span>[
-<a href=#3516 id=3516 data-nosnippet>3516</a>            RESERVED_FIELD_ID_FILE,
-<a href=#3517 id=3517 data-nosnippet>3517</a>        ]);
-<a href=#3518 id=3518 data-nosnippet>3518</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
-<a href=#3519 id=3519 data-nosnippet>3519</a>
-<a href=#3520 id=3520 data-nosnippet>3520</a>        <span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">1</span>);
-<a href=#3521 id=3521 data-nosnippet>3521</a>        <span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
-<a href=#3522 id=3522 data-nosnippet>3522</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
-<a href=#3523 id=3523 data-nosnippet>3523</a>        <span class="kw">let </span>file_col = batches[<span class="number">0</span>]
-<a href=#3524 id=3524 data-nosnippet>3524</a>            .column_by_name(RESERVED_COL_NAME_FILE)
-<a href=#3525 id=3525 data-nosnippet>3525</a>            .expect(<span class="string">"_file column should be present"</span>);
-<a href=#3526 id=3526 data-nosnippet>3526</a>        <span class="kw">let </span>file_col = cast(file_col, <span class="kw-2">&amp;</span>DataType::Utf8).unwrap();
-<a href=#3527 id=3527 data-nosnippet>3527</a>        <span class="kw">let </span>file_col = file_col.as_any().downcast_ref::&lt;StringArray&gt;().unwrap();
-<a href=#3528 id=3528 data-nosnippet>3528</a>        <span class="macro">assert_eq!</span>(file_col.value(<span class="number">0</span>), file_path);
-<a href=#3529 id=3529 data-nosnippet>3529</a>
-<a href=#3530 id=3530 data-nosnippet>3530</a>        <span class="kw">let </span>with_data = metadata_projection_task(
-<a href=#3531 id=3531 data-nosnippet>3531</a>            write_parquet_with_wide_column(dir, <span class="string">"file_only_ref.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
-<a href=#3532 id=3532 data-nosnippet>3532</a>            id_and_wide_schema(),
-<a href=#3533 id=3533 data-nosnippet>3533</a>            <span class="macro">vec!</span>[<span class="number">2</span>, RESERVED_FIELD_ID_FILE],
-<a href=#3534 id=3534 data-nosnippet>3534</a>        );
-<a href=#3535 id=3535 data-nosnippet>3535</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
-<a href=#3536 id=3536 data-nosnippet>3536</a>
-<a href=#3537 id=3537 data-nosnippet>3537</a>        <span class="macro">assert!</span>(
-<a href=#3538 id=3538 data-nosnippet>3538</a>            meta_only_bytes &lt; with_data_bytes,
-<a href=#3539 id=3539 data-nosnippet>3539</a>            <span class="string">"_file-only scan should read fewer bytes than a scan of the wide column: \
-<a href=#3540 id=3540 data-nosnippet>3540</a>             {meta_only_bytes} vs {with_data_bytes}"
-<a href=#3541 id=3541 data-nosnippet>3541</a>        </span>);
-<a href=#3542 id=3542 data-nosnippet>3542</a>    }
-<a href=#3543 id=3543 data-nosnippet>3543</a>
-<a href=#3544 id=3544 data-nosnippet>3544</a>    <span class="attr">#[tokio::test]
-<a href=#3545 id=3545 data-nosnippet>3545</a>    </span><span class="kw">async fn </span>test_spec_id_only_reads_no_data_columns() {
-<a href=#3546 id=3546 data-nosnippet>3546</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::{RESERVED_COL_NAME_SPEC_ID, RESERVED_FIELD_ID_SPEC_ID};
-<a href=#3547 id=3547 data-nosnippet>3547</a>        <span class="kw">use </span><span class="kw">crate</span>::spec::{Literal, PartitionSpec, Struct, Transform};
-<a href=#3548 id=3548 data-nosnippet>3548</a>
-<a href=#3549 id=3549 data-nosnippet>3549</a>        <span class="comment">// `SELECT _spec_id` materializes an int constant from the task's partition spec id,
-<a href=#3550 id=3550 data-nosnippet>3550</a>        // with no physical column to read. The RowNumber counter sizes it, so the scan
-<a href=#3551 id=3551 data-nosnippet>3551</a>        // prunes the data columns rather than reading them all to recover the row count.
-<a href=#3552 id=3552 data-nosnippet>3552</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3553 id=3553 data-nosnippet>3553</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3554 id=3554 data-nosnippet>3554</a>        <span class="kw">let </span>schema = id_and_wide_schema();
-<a href=#3555 id=3555 data-nosnippet>3555</a>        <span class="kw">let </span>spec = Arc::new(
-<a href=#3556 id=3556 data-nosnippet>3556</a>            PartitionSpec::builder(schema.clone())
-<a href=#3557 id=3557 data-nosnippet>3557</a>                .with_spec_id(<span class="number">7</span>)
-<a href=#3558 id=3558 data-nosnippet>3558</a>                .add_partition_field(<span class="string">"id"</span>, <span class="string">"id"</span>, Transform::Identity)
-<a href=#3559 id=3559 data-nosnippet>3559</a>                .unwrap()
-<a href=#3560 id=3560 data-nosnippet>3560</a>                .build()
-<a href=#3561 id=3561 data-nosnippet>3561</a>                .unwrap(),
-<a href=#3562 id=3562 data-nosnippet>3562</a>        );
-<a href=#3563 id=3563 data-nosnippet>3563</a>
-<a href=#3564 id=3564 data-nosnippet>3564</a>        <span class="kw">let </span>spec_id_task = |file_path: String, project_field_ids: Vec&lt;i32&gt;| {
-<a href=#3565 id=3565 data-nosnippet>3565</a>            FileScanTask::builder()
-<a href=#3566 id=3566 data-nosnippet>3566</a>                .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
-<a href=#3567 id=3567 data-nosnippet>3567</a>                .with_start(<span class="number">0</span>)
-<a href=#3568 id=3568 data-nosnippet>3568</a>                .with_length(<span class="number">0</span>)
-<a href=#3569 id=3569 data-nosnippet>3569</a>                .with_data_file_path(file_path)
-<a href=#3570 id=3570 data-nosnippet>3570</a>                .with_data_file_format(DataFileFormat::Parquet)
-<a href=#3571 id=3571 data-nosnippet>3571</a>                .with_schema(schema.clone())
-<a href=#3572 id=3572 data-nosnippet>3572</a>                .with_project_field_ids(project_field_ids)
-<a href=#3573 id=3573 data-nosnippet>3573</a>                .with_partition(<span class="prelude-val">Some</span>(Struct::from_iter([<span class="prelude-val">Some</span>(Literal::int(<span class="number">42</span>))])))
-<a href=#3574 id=3574 data-nosnippet>3574</a>                .with_partition_spec(<span class="prelude-val">Some</span>(spec.clone()))
-<a href=#3575 id=3575 data-nosnippet>3575</a>                .with_case_sensitive(<span class="bool-val">false</span>)
-<a href=#3576 id=3576 data-nosnippet>3576</a>                .build()
-<a href=#3577 id=3577 data-nosnippet>3577</a>                .unwrap()
-<a href=#3578 id=3578 data-nosnippet>3578</a>        };
-<a href=#3579 id=3579 data-nosnippet>3579</a>
-<a href=#3580 id=3580 data-nosnippet>3580</a>        <span class="kw">let </span>meta_only = spec_id_task(
-<a href=#3581 id=3581 data-nosnippet>3581</a>            write_parquet_with_wide_column(dir, <span class="string">"spec_id_only.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
-<a href=#3582 id=3582 data-nosnippet>3582</a>            <span class="macro">vec!</span>[RESERVED_FIELD_ID_SPEC_ID],
-<a href=#3583 id=3583 data-nosnippet>3583</a>        );
-<a href=#3584 id=3584 data-nosnippet>3584</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
-<a href=#3585 id=3585 data-nosnippet>3585</a>
-<a href=#3586 id=3586 data-nosnippet>3586</a>        <span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">1</span>);
-<a href=#3587 id=3587 data-nosnippet>3587</a>        <span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
-<a href=#3588 id=3588 data-nosnippet>3588</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
-<a href=#3589 id=3589 data-nosnippet>3589</a>        <span class="kw">let </span>spec_id_col = batches[<span class="number">0</span>]
-<a href=#3590 id=3590 data-nosnippet>3590</a>            .column_by_name(RESERVED_COL_NAME_SPEC_ID)
-<a href=#3591 id=3591 data-nosnippet>3591</a>            .expect(<span class="string">"_spec_id column should be present"</span>);
-<a href=#3592 id=3592 data-nosnippet>3592</a>        <span class="kw">let </span>spec_id_col = cast(spec_id_col, <span class="kw-2">&amp;</span>DataType::Int32).unwrap();
-<a href=#3593 id=3593 data-nosnippet>3593</a>        <span class="kw">let </span>spec_id_col = spec_id_col.as_any().downcast_ref::&lt;Int32Array&gt;().unwrap();
-<a href=#3594 id=3594 data-nosnippet>3594</a>        <span class="macro">assert_eq!</span>(spec_id_col.values(), <span class="kw-2">&amp;</span>[<span class="number">7</span>, <span class="number">7</span>, <span class="number">7</span>]);
-<a href=#3595 id=3595 data-nosnippet>3595</a>
-<a href=#3596 id=3596 data-nosnippet>3596</a>        <span class="kw">let </span>with_data = spec_id_task(
-<a href=#3597 id=3597 data-nosnippet>3597</a>            write_parquet_with_wide_column(dir, <span class="string">"spec_id_only_ref.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
-<a href=#3598 id=3598 data-nosnippet>3598</a>            <span class="macro">vec!</span>[<span class="number">2</span>, RESERVED_FIELD_ID_SPEC_ID],
-<a href=#3599 id=3599 data-nosnippet>3599</a>        );
-<a href=#3600 id=3600 data-nosnippet>3600</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
-<a href=#3601 id=3601 data-nosnippet>3601</a>
-<a href=#3602 id=3602 data-nosnippet>3602</a>        <span class="macro">assert!</span>(
-<a href=#3603 id=3603 data-nosnippet>3603</a>            meta_only_bytes &lt; with_data_bytes,
-<a href=#3604 id=3604 data-nosnippet>3604</a>            <span class="string">"_spec_id-only scan should read fewer bytes than a scan of the wide column: \
-<a href=#3605 id=3605 data-nosnippet>3605</a>             {meta_only_bytes} vs {with_data_bytes}"
-<a href=#3606 id=3606 data-nosnippet>3606</a>        </span>);
-<a href=#3607 id=3607 data-nosnippet>3607</a>    }
-<a href=#3608 id=3608 data-nosnippet>3608</a>
-<a href=#3609 id=3609 data-nosnippet>3609</a>    <span class="attr">#[tokio::test]
-<a href=#3610 id=3610 data-nosnippet>3610</a>    </span><span class="kw">async fn </span>test_empty_projection_preserves_row_count() {
-<a href=#3611 id=3611 data-nosnippet>3611</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
-<a href=#3612 id=3612 data-nosnippet>3612</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
-<a href=#3613 id=3613 data-nosnippet>3613</a>        <span class="kw">let </span>file_path = write_plain_parquet(dir, <span class="string">"empty_projection.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]);
-<a href=#3614 id=3614 data-nosnippet>3614</a>        <span class="kw">let </span>schema = Arc::new(
-<a href=#3615 id=3615 data-nosnippet>3615</a>            Schema::builder()
-<a href=#3616 id=3616 data-nosnippet>3616</a>                .with_schema_id(<span class="number">1</span>)
-<a href=#3617 id=3617 data-nosnippet>3617</a>                .with_fields(<span class="macro">vec!</span>[
-<a href=#3618 id=3618 data-nosnippet>3618</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
-<a href=#3619 id=3619 data-nosnippet>3619</a>                ])
-<a href=#3620 id=3620 data-nosnippet>3620</a>                .build()
-<a href=#3621 id=3621 data-nosnippet>3621</a>                .unwrap(),
-<a href=#3622 id=3622 data-nosnippet>3622</a>        );
-<a href=#3623 id=3623 data-nosnippet>3623</a>        <span class="kw">let </span>task = metadata_projection_task(file_path, schema, <span class="macro">vec!</span>[]);
-<a href=#3624 id=3624 data-nosnippet>3624</a>        <span class="kw">let </span>(batches, <span class="kw">_</span>) = scan_task(task).<span class="kw">await</span>;
-<a href=#3625 id=3625 data-nosnippet>3625</a>
-<a href=#3626 id=3626 data-nosnippet>3626</a>        <span class="comment">// A bare COUNT(*)-style empty projection must still report the row count.
-<a href=#3627 id=3627 data-nosnippet>3627</a>        </span><span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
-<a href=#3628 id=3628 data-nosnippet>3628</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
-<a href=#3629 id=3629 data-nosnippet>3629</a>    }
-<a href=#3630 id=3630 data-nosnippet>3630</a>}
+<a href=#2413 id=2413 data-nosnippet>2413</a>            .with_file_format(DataFileFormat::Parquet)
+<a href=#2414 id=2414 data-nosnippet>2414</a>            .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>del_path).unwrap().len())
+<a href=#2415 id=2415 data-nosnippet>2415</a>            .with_file_type(DataContentType::PositionDeletes)
+<a href=#2416 id=2416 data-nosnippet>2416</a>            .with_partition_spec_id(<span class="number">0</span>)
+<a href=#2417 id=2417 data-nosnippet>2417</a>            .build();
+<a href=#2418 id=2418 data-nosnippet>2418</a>        <span class="kw">let </span>task = row_id_task_with_options(data_path, <span class="prelude-val">Some</span>(<span class="number">100</span>), <span class="number">0</span>, <span class="number">0</span>, <span class="macro">vec!</span>[delete]);
+<a href=#2419 id=2419 data-nosnippet>2419</a>
+<a href=#2420 id=2420 data-nosnippet>2420</a>        <span class="kw">let </span>reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current()).build();
+<a href=#2421 id=2421 data-nosnippet>2421</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
+<a href=#2422 id=2422 data-nosnippet>2422</a>        <span class="kw">let </span>batches: Vec&lt;RecordBatch&gt; = reader
+<a href=#2423 id=2423 data-nosnippet>2423</a>            .read(tasks)
+<a href=#2424 id=2424 data-nosnippet>2424</a>            .unwrap()
+<a href=#2425 id=2425 data-nosnippet>2425</a>            .stream()
+<a href=#2426 id=2426 data-nosnippet>2426</a>            .try_collect()
+<a href=#2427 id=2427 data-nosnippet>2427</a>            .<span class="kw">await
+<a href=#2428 id=2428 data-nosnippet>2428</a>            </span>.unwrap();
+<a href=#2429 id=2429 data-nosnippet>2429</a>
+<a href=#2430 id=2430 data-nosnippet>2430</a>        <span class="comment">// Survivors keep their ABSOLUTE positions -- [100, 102], not renumbered [100, 101].
+<a href=#2431 id=2431 data-nosnippet>2431</a>        // Positional-delete selection reaches the reader via a different path than predicate
+<a href=#2432 id=2432 data-nosnippet>2432</a>        // selection, so this covers it explicitly.
+<a href=#2433 id=2433 data-nosnippet>2433</a>        </span>assert_row_id_column(<span class="kw-2">&amp;</span>batches, <span class="kw-2">&amp;</span>[<span class="prelude-val">Some</span>(<span class="number">100</span>), <span class="prelude-val">Some</span>(<span class="number">102</span>)]);
+<a href=#2434 id=2434 data-nosnippet>2434</a>    }
+<a href=#2435 id=2435 data-nosnippet>2435</a>
+<a href=#2436 id=2436 data-nosnippet>2436</a>    <span class="attr">#[tokio::test]
+<a href=#2437 id=2437 data-nosnippet>2437</a>    </span><span class="kw">async fn </span>test_row_id_name_collision_under_positional_fallback() {
+<a href=#2438 id=2438 data-nosnippet>2438</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#2439 id=2439 data-nosnippet>2439</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#2440 id=2440 data-nosnippet>2440</a>
+<a href=#2441 id=2441 data-nosnippet>2441</a>        <span class="comment">// A file with NO embedded field ids (positional fallback) whose columns include one
+<a href=#2442 id=2442 data-nosnippet>2442</a>        // literally named `_row_id` (user data). Projecting `_row_id` must NOT be rejected as
+<a href=#2443 id=2443 data-nosnippet>2443</a>        // an unthreadable physical metadata column -- under fallback the reserved column is
+<a href=#2444 id=2444 data-nosnippet>2444</a>        // synthesized and the same-named user column is just data.
+<a href=#2445 id=2445 data-nosnippet>2445</a>        </span><span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{dir}/fallback_row_id_name.parquet"</span>);
+<a href=#2446 id=2446 data-nosnippet>2446</a>        <span class="kw">let </span>arrow_schema = Arc::new(ArrowSchema::new(<span class="macro">vec!</span>[
+<a href=#2447 id=2447 data-nosnippet>2447</a>            Field::new(<span class="string">"id"</span>, DataType::Int32, <span class="bool-val">false</span>),
+<a href=#2448 id=2448 data-nosnippet>2448</a>            Field::new(RESERVED_COL_NAME_ROW_ID, DataType::Int64, <span class="bool-val">true</span>),
+<a href=#2449 id=2449 data-nosnippet>2449</a>        ]));
+<a href=#2450 id=2450 data-nosnippet>2450</a>        <span class="kw">let </span>batch = RecordBatch::try_new(arrow_schema.clone(), <span class="macro">vec!</span>[
+<a href=#2451 id=2451 data-nosnippet>2451</a>            Arc::new(Int32Array::from(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>])),
+<a href=#2452 id=2452 data-nosnippet>2452</a>            Arc::new(Int64Array::from(<span class="macro">vec!</span>[<span class="number">7i64</span>, <span class="number">8</span>, <span class="number">9</span>])),
+<a href=#2453 id=2453 data-nosnippet>2453</a>        ])
+<a href=#2454 id=2454 data-nosnippet>2454</a>        .unwrap();
+<a href=#2455 id=2455 data-nosnippet>2455</a>        <span class="kw">let </span>props = WriterProperties::builder()
+<a href=#2456 id=2456 data-nosnippet>2456</a>            .set_compression(Compression::SNAPPY)
+<a href=#2457 id=2457 data-nosnippet>2457</a>            .build();
+<a href=#2458 id=2458 data-nosnippet>2458</a>        <span class="kw">let </span>file = File::create(<span class="kw-2">&amp;</span>file_path).unwrap();
+<a href=#2459 id=2459 data-nosnippet>2459</a>        <span class="kw">let </span><span class="kw-2">mut </span>writer = ArrowWriter::try_new(file, arrow_schema, <span class="prelude-val">Some</span>(props)).unwrap();
+<a href=#2460 id=2460 data-nosnippet>2460</a>        writer.write(<span class="kw-2">&amp;</span>batch).unwrap();
+<a href=#2461 id=2461 data-nosnippet>2461</a>        writer.close().unwrap();
+<a href=#2462 id=2462 data-nosnippet>2462</a>
+<a href=#2463 id=2463 data-nosnippet>2463</a>        <span class="kw">let </span>task = row_id_task(file_path, <span class="prelude-val">Some</span>(<span class="number">100</span>));
+<a href=#2464 id=2464 data-nosnippet>2464</a>        <span class="kw">let </span>reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current()).build();
+<a href=#2465 id=2465 data-nosnippet>2465</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
+<a href=#2466 id=2466 data-nosnippet>2466</a>        <span class="kw">let </span>batches: Vec&lt;RecordBatch&gt; = reader
+<a href=#2467 id=2467 data-nosnippet>2467</a>            .read(tasks)
+<a href=#2468 id=2468 data-nosnippet>2468</a>            .unwrap()
+<a href=#2469 id=2469 data-nosnippet>2469</a>            .stream()
+<a href=#2470 id=2470 data-nosnippet>2470</a>            .try_collect()
+<a href=#2471 id=2471 data-nosnippet>2471</a>            .<span class="kw">await
+<a href=#2472 id=2472 data-nosnippet>2472</a>            </span>.unwrap();
+<a href=#2473 id=2473 data-nosnippet>2473</a>
+<a href=#2474 id=2474 data-nosnippet>2474</a>        <span class="comment">// Not rejected; `_row_id` is synthesized as first_row_id + pos.
+<a href=#2475 id=2475 data-nosnippet>2475</a>        </span>assert_row_id_column(<span class="kw-2">&amp;</span>batches, <span class="kw-2">&amp;</span>[<span class="prelude-val">Some</span>(<span class="number">100</span>), <span class="prelude-val">Some</span>(<span class="number">101</span>), <span class="prelude-val">Some</span>(<span class="number">102</span>)]);
+<a href=#2476 id=2476 data-nosnippet>2476</a>    }
+<a href=#2477 id=2477 data-nosnippet>2477</a>
+<a href=#2478 id=2478 data-nosnippet>2478</a>    <span class="attr">#[tokio::test]
+<a href=#2479 id=2479 data-nosnippet>2479</a>    </span><span class="kw">async fn </span>test_read_encrypted_parquet_with_wrong_key_fails() {
+<a href=#2480 id=2480 data-nosnippet>2480</a>        <span class="kw">let </span>encryption_key = <span class="string">b"0123456789abcdef"</span>;
+<a href=#2481 id=2481 data-nosnippet>2481</a>        <span class="kw">let </span>wrong_key = <span class="string">b"fedcba9876543210"</span>;
+<a href=#2482 id=2482 data-nosnippet>2482</a>
+<a href=#2483 id=2483 data-nosnippet>2483</a>        <span class="kw">let </span>schema = Arc::new(
+<a href=#2484 id=2484 data-nosnippet>2484</a>            Schema::builder()
+<a href=#2485 id=2485 data-nosnippet>2485</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#2486 id=2486 data-nosnippet>2486</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#2487 id=2487 data-nosnippet>2487</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
+<a href=#2488 id=2488 data-nosnippet>2488</a>                ])
+<a href=#2489 id=2489 data-nosnippet>2489</a>                .build()
+<a href=#2490 id=2490 data-nosnippet>2490</a>                .unwrap(),
+<a href=#2491 id=2491 data-nosnippet>2491</a>        );
+<a href=#2492 id=2492 data-nosnippet>2492</a>
+<a href=#2493 id=2493 data-nosnippet>2493</a>        <span class="kw">let </span>arrow_schema = Arc::new(ArrowSchema::new(<span class="macro">vec!</span>[
+<a href=#2494 id=2494 data-nosnippet>2494</a>            Field::new(<span class="string">"id"</span>, DataType::Int32, <span class="bool-val">false</span>).with_metadata(HashMap::from([(
+<a href=#2495 id=2495 data-nosnippet>2495</a>                PARQUET_FIELD_ID_META_KEY.to_string(),
+<a href=#2496 id=2496 data-nosnippet>2496</a>                <span class="string">"1"</span>.to_string(),
+<a href=#2497 id=2497 data-nosnippet>2497</a>            )])),
+<a href=#2498 id=2498 data-nosnippet>2498</a>        ]));
+<a href=#2499 id=2499 data-nosnippet>2499</a>
+<a href=#2500 id=2500 data-nosnippet>2500</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#2501 id=2501 data-nosnippet>2501</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
+<a href=#2502 id=2502 data-nosnippet>2502</a>        <span class="kw">let </span>file_io = FileIO::new_with_fs();
+<a href=#2503 id=2503 data-nosnippet>2503</a>
+<a href=#2504 id=2504 data-nosnippet>2504</a>        <span class="kw">let </span>id_data = Arc::new(Int32Array::from(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>])) <span class="kw">as </span>ArrayRef;
+<a href=#2505 id=2505 data-nosnippet>2505</a>        <span class="kw">let </span>batch = RecordBatch::try_new(arrow_schema.clone(), <span class="macro">vec!</span>[id_data]).unwrap();
+<a href=#2506 id=2506 data-nosnippet>2506</a>
+<a href=#2507 id=2507 data-nosnippet>2507</a>        <span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{table_location}/encrypted_wrong_key.parquet"</span>);
+<a href=#2508 id=2508 data-nosnippet>2508</a>        write_encrypted_parquet(<span class="kw-2">&amp;</span>file_path, <span class="kw-2">&amp;</span>batch, encryption_key, <span class="prelude-val">None</span>);
+<a href=#2509 id=2509 data-nosnippet>2509</a>
+<a href=#2510 id=2510 data-nosnippet>2510</a>        <span class="kw">let </span>wrong_key_metadata = <span class="kw">crate</span>::encryption::StandardKeyMetadata::try_new(wrong_key)
+<a href=#2511 id=2511 data-nosnippet>2511</a>            .unwrap()
+<a href=#2512 id=2512 data-nosnippet>2512</a>            .encode()
+<a href=#2513 id=2513 data-nosnippet>2513</a>            .unwrap();
+<a href=#2514 id=2514 data-nosnippet>2514</a>
+<a href=#2515 id=2515 data-nosnippet>2515</a>        <span class="kw">let </span>reader = ArrowReaderBuilder::new(file_io, Runtime::current()).build();
+<a href=#2516 id=2516 data-nosnippet>2516</a>
+<a href=#2517 id=2517 data-nosnippet>2517</a>        <span class="kw">let </span>task = FileScanTask::builder()
+<a href=#2518 id=2518 data-nosnippet>2518</a>            .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
+<a href=#2519 id=2519 data-nosnippet>2519</a>            .with_start(<span class="number">0</span>)
+<a href=#2520 id=2520 data-nosnippet>2520</a>            .with_length(<span class="number">0</span>)
+<a href=#2521 id=2521 data-nosnippet>2521</a>            .with_data_file_path(file_path)
+<a href=#2522 id=2522 data-nosnippet>2522</a>            .with_data_file_format(DataFileFormat::Parquet)
+<a href=#2523 id=2523 data-nosnippet>2523</a>            .with_schema(schema)
+<a href=#2524 id=2524 data-nosnippet>2524</a>            .with_project_field_ids(<span class="macro">vec!</span>[<span class="number">1</span>])
+<a href=#2525 id=2525 data-nosnippet>2525</a>            .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#2526 id=2526 data-nosnippet>2526</a>            .with_key_metadata(<span class="prelude-val">Some</span>(wrong_key_metadata))
+<a href=#2527 id=2527 data-nosnippet>2527</a>            .build()
+<a href=#2528 id=2528 data-nosnippet>2528</a>            .unwrap();
+<a href=#2529 id=2529 data-nosnippet>2529</a>
+<a href=#2530 id=2530 data-nosnippet>2530</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
+<a href=#2531 id=2531 data-nosnippet>2531</a>        <span class="kw">let </span>result: <span class="prelude-ty">Result</span>&lt;Vec&lt;RecordBatch&gt;, <span class="kw">_</span>&gt; =
+<a href=#2532 id=2532 data-nosnippet>2532</a>            reader.read(tasks).unwrap().stream().try_collect().<span class="kw">await</span>;
+<a href=#2533 id=2533 data-nosnippet>2533</a>
+<a href=#2534 id=2534 data-nosnippet>2534</a>        <span class="kw">let </span>err = result.unwrap_err();
+<a href=#2535 id=2535 data-nosnippet>2535</a>        <span class="macro">assert_eq!</span>(err.kind(), <span class="kw">crate</span>::ErrorKind::Unexpected);
+<a href=#2536 id=2536 data-nosnippet>2536</a>        <span class="kw">let </span>err_str = <span class="macro">format!</span>(<span class="string">"{err}"</span>);
+<a href=#2537 id=2537 data-nosnippet>2537</a>        <span class="macro">assert!</span>(
+<a href=#2538 id=2538 data-nosnippet>2538</a>            err_str.contains(<span class="string">"unable to decrypt parquet footer"</span>),
+<a href=#2539 id=2539 data-nosnippet>2539</a>            <span class="string">"Expected error about decryption failure, got: {err_str}"
+<a href=#2540 id=2540 data-nosnippet>2540</a>        </span>);
+<a href=#2541 id=2541 data-nosnippet>2541</a>    }
+<a href=#2542 id=2542 data-nosnippet>2542</a>
+<a href=#2543 id=2543 data-nosnippet>2543</a>    <span class="doccomment">/// Test that concurrency=1 reads all files correctly and in deterministic order.
+<a href=#2544 id=2544 data-nosnippet>2544</a>    /// This verifies the fast-path optimization for single concurrency.
+<a href=#2545 id=2545 data-nosnippet>2545</a>    </span><span class="attr">#[tokio::test]
+<a href=#2546 id=2546 data-nosnippet>2546</a>    </span><span class="kw">async fn </span>test_read_with_concurrency_one() {
+<a href=#2547 id=2547 data-nosnippet>2547</a>        <span class="kw">use </span>arrow_array::Int32Array;
+<a href=#2548 id=2548 data-nosnippet>2548</a>
+<a href=#2549 id=2549 data-nosnippet>2549</a>        <span class="kw">let </span>schema = Arc::new(
+<a href=#2550 id=2550 data-nosnippet>2550</a>            Schema::builder()
+<a href=#2551 id=2551 data-nosnippet>2551</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#2552 id=2552 data-nosnippet>2552</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#2553 id=2553 data-nosnippet>2553</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
+<a href=#2554 id=2554 data-nosnippet>2554</a>                    NestedField::required(<span class="number">2</span>, <span class="string">"file_num"</span>, Type::Primitive(PrimitiveType::Int))
+<a href=#2555 id=2555 data-nosnippet>2555</a>                        .into(),
+<a href=#2556 id=2556 data-nosnippet>2556</a>                ])
+<a href=#2557 id=2557 data-nosnippet>2557</a>                .build()
+<a href=#2558 id=2558 data-nosnippet>2558</a>                .unwrap(),
+<a href=#2559 id=2559 data-nosnippet>2559</a>        );
+<a href=#2560 id=2560 data-nosnippet>2560</a>
+<a href=#2561 id=2561 data-nosnippet>2561</a>        <span class="kw">let </span>arrow_schema = Arc::new(ArrowSchema::new(<span class="macro">vec!</span>[
+<a href=#2562 id=2562 data-nosnippet>2562</a>            Field::new(<span class="string">"id"</span>, DataType::Int32, <span class="bool-val">false</span>).with_metadata(HashMap::from([(
+<a href=#2563 id=2563 data-nosnippet>2563</a>                PARQUET_FIELD_ID_META_KEY.to_string(),
+<a href=#2564 id=2564 data-nosnippet>2564</a>                <span class="string">"1"</span>.to_string(),
+<a href=#2565 id=2565 data-nosnippet>2565</a>            )])),
+<a href=#2566 id=2566 data-nosnippet>2566</a>            Field::new(<span class="string">"file_num"</span>, DataType::Int32, <span class="bool-val">false</span>).with_metadata(HashMap::from([(
+<a href=#2567 id=2567 data-nosnippet>2567</a>                PARQUET_FIELD_ID_META_KEY.to_string(),
+<a href=#2568 id=2568 data-nosnippet>2568</a>                <span class="string">"2"</span>.to_string(),
+<a href=#2569 id=2569 data-nosnippet>2569</a>            )])),
+<a href=#2570 id=2570 data-nosnippet>2570</a>        ]));
+<a href=#2571 id=2571 data-nosnippet>2571</a>
+<a href=#2572 id=2572 data-nosnippet>2572</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#2573 id=2573 data-nosnippet>2573</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
+<a href=#2574 id=2574 data-nosnippet>2574</a>        <span class="kw">let </span>file_io = FileIO::new_with_fs();
+<a href=#2575 id=2575 data-nosnippet>2575</a>
+<a href=#2576 id=2576 data-nosnippet>2576</a>        <span class="comment">// Create 3 parquet files with different data
+<a href=#2577 id=2577 data-nosnippet>2577</a>        </span><span class="kw">let </span>props = WriterProperties::builder()
+<a href=#2578 id=2578 data-nosnippet>2578</a>            .set_compression(Compression::SNAPPY)
+<a href=#2579 id=2579 data-nosnippet>2579</a>            .build();
+<a href=#2580 id=2580 data-nosnippet>2580</a>
+<a href=#2581 id=2581 data-nosnippet>2581</a>        <span class="kw">for </span>file_num <span class="kw">in </span><span class="number">0</span>..<span class="number">3 </span>{
+<a href=#2582 id=2582 data-nosnippet>2582</a>            <span class="kw">let </span>id_data = Arc::new(Int32Array::from_iter_values(
+<a href=#2583 id=2583 data-nosnippet>2583</a>                file_num * <span class="number">10</span>..(file_num + <span class="number">1</span>) * <span class="number">10</span>,
+<a href=#2584 id=2584 data-nosnippet>2584</a>            )) <span class="kw">as </span>ArrayRef;
+<a href=#2585 id=2585 data-nosnippet>2585</a>            <span class="kw">let </span>file_num_data = Arc::new(Int32Array::from(<span class="macro">vec!</span>[file_num; <span class="number">10</span>])) <span class="kw">as </span>ArrayRef;
+<a href=#2586 id=2586 data-nosnippet>2586</a>
+<a href=#2587 id=2587 data-nosnippet>2587</a>            <span class="kw">let </span>to_write =
+<a href=#2588 id=2588 data-nosnippet>2588</a>                RecordBatch::try_new(arrow_schema.clone(), <span class="macro">vec!</span>[id_data, file_num_data]).unwrap();
+<a href=#2589 id=2589 data-nosnippet>2589</a>
+<a href=#2590 id=2590 data-nosnippet>2590</a>            <span class="kw">let </span>file = File::create(<span class="macro">format!</span>(<span class="string">"{table_location}/file_{file_num}.parquet"</span>)).unwrap();
+<a href=#2591 id=2591 data-nosnippet>2591</a>            <span class="kw">let </span><span class="kw-2">mut </span>writer =
+<a href=#2592 id=2592 data-nosnippet>2592</a>                ArrowWriter::try_new(file, to_write.schema(), <span class="prelude-val">Some</span>(props.clone())).unwrap();
+<a href=#2593 id=2593 data-nosnippet>2593</a>            writer.write(<span class="kw-2">&amp;</span>to_write).expect(<span class="string">"Writing batch"</span>);
+<a href=#2594 id=2594 data-nosnippet>2594</a>            writer.close().unwrap();
+<a href=#2595 id=2595 data-nosnippet>2595</a>        }
+<a href=#2596 id=2596 data-nosnippet>2596</a>
+<a href=#2597 id=2597 data-nosnippet>2597</a>        <span class="comment">// Read with concurrency=1 (fast-path)
+<a href=#2598 id=2598 data-nosnippet>2598</a>        </span><span class="kw">let </span>reader = ArrowReaderBuilder::new(file_io, Runtime::current())
+<a href=#2599 id=2599 data-nosnippet>2599</a>            .with_data_file_concurrency_limit(<span class="number">1</span>)
+<a href=#2600 id=2600 data-nosnippet>2600</a>            .build();
+<a href=#2601 id=2601 data-nosnippet>2601</a>
+<a href=#2602 id=2602 data-nosnippet>2602</a>        <span class="comment">// Create tasks in a specific order: file_0, file_1, file_2
+<a href=#2603 id=2603 data-nosnippet>2603</a>        </span><span class="kw">let </span>tasks = <span class="macro">vec!</span>[
+<a href=#2604 id=2604 data-nosnippet>2604</a>            <span class="prelude-val">Ok</span>(FileScanTask::builder()
+<a href=#2605 id=2605 data-nosnippet>2605</a>                .with_file_size_in_bytes(
+<a href=#2606 id=2606 data-nosnippet>2606</a>                    std::fs::metadata(<span class="macro">format!</span>(<span class="string">"{table_location}/file_0.parquet"</span>))
+<a href=#2607 id=2607 data-nosnippet>2607</a>                        .unwrap()
+<a href=#2608 id=2608 data-nosnippet>2608</a>                        .len(),
+<a href=#2609 id=2609 data-nosnippet>2609</a>                )
+<a href=#2610 id=2610 data-nosnippet>2610</a>                .with_start(<span class="number">0</span>)
+<a href=#2611 id=2611 data-nosnippet>2611</a>                .with_length(<span class="number">0</span>)
+<a href=#2612 id=2612 data-nosnippet>2612</a>                .with_data_file_path(<span class="macro">format!</span>(<span class="string">"{table_location}/file_0.parquet"</span>))
+<a href=#2613 id=2613 data-nosnippet>2613</a>                .with_data_file_format(DataFileFormat::Parquet)
+<a href=#2614 id=2614 data-nosnippet>2614</a>                .with_schema(schema.clone())
+<a href=#2615 id=2615 data-nosnippet>2615</a>                .with_project_field_ids(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>])
+<a href=#2616 id=2616 data-nosnippet>2616</a>                .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#2617 id=2617 data-nosnippet>2617</a>                .build()
+<a href=#2618 id=2618 data-nosnippet>2618</a>                .unwrap()),
+<a href=#2619 id=2619 data-nosnippet>2619</a>            <span class="prelude-val">Ok</span>(FileScanTask::builder()
+<a href=#2620 id=2620 data-nosnippet>2620</a>                .with_file_size_in_bytes(
+<a href=#2621 id=2621 data-nosnippet>2621</a>                    std::fs::metadata(<span class="macro">format!</span>(<span class="string">"{table_location}/file_1.parquet"</span>))
+<a href=#2622 id=2622 data-nosnippet>2622</a>                        .unwrap()
+<a href=#2623 id=2623 data-nosnippet>2623</a>                        .len(),
+<a href=#2624 id=2624 data-nosnippet>2624</a>                )
+<a href=#2625 id=2625 data-nosnippet>2625</a>                .with_start(<span class="number">0</span>)
+<a href=#2626 id=2626 data-nosnippet>2626</a>                .with_length(<span class="number">0</span>)
+<a href=#2627 id=2627 data-nosnippet>2627</a>                .with_data_file_path(<span class="macro">format!</span>(<span class="string">"{table_location}/file_1.parquet"</span>))
+<a href=#2628 id=2628 data-nosnippet>2628</a>                .with_data_file_format(DataFileFormat::Parquet)
+<a href=#2629 id=2629 data-nosnippet>2629</a>                .with_schema(schema.clone())
+<a href=#2630 id=2630 data-nosnippet>2630</a>                .with_project_field_ids(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>])
+<a href=#2631 id=2631 data-nosnippet>2631</a>                .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#2632 id=2632 data-nosnippet>2632</a>                .build()
+<a href=#2633 id=2633 data-nosnippet>2633</a>                .unwrap()),
+<a href=#2634 id=2634 data-nosnippet>2634</a>            <span class="prelude-val">Ok</span>(FileScanTask::builder()
+<a href=#2635 id=2635 data-nosnippet>2635</a>                .with_file_size_in_bytes(
+<a href=#2636 id=2636 data-nosnippet>2636</a>                    std::fs::metadata(<span class="macro">format!</span>(<span class="string">"{table_location}/file_2.parquet"</span>))
+<a href=#2637 id=2637 data-nosnippet>2637</a>                        .unwrap()
+<a href=#2638 id=2638 data-nosnippet>2638</a>                        .len(),
+<a href=#2639 id=2639 data-nosnippet>2639</a>                )
+<a href=#2640 id=2640 data-nosnippet>2640</a>                .with_start(<span class="number">0</span>)
+<a href=#2641 id=2641 data-nosnippet>2641</a>                .with_length(<span class="number">0</span>)
+<a href=#2642 id=2642 data-nosnippet>2642</a>                .with_data_file_path(<span class="macro">format!</span>(<span class="string">"{table_location}/file_2.parquet"</span>))
+<a href=#2643 id=2643 data-nosnippet>2643</a>                .with_data_file_format(DataFileFormat::Parquet)
+<a href=#2644 id=2644 data-nosnippet>2644</a>                .with_schema(schema.clone())
+<a href=#2645 id=2645 data-nosnippet>2645</a>                .with_project_field_ids(<span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>])
+<a href=#2646 id=2646 data-nosnippet>2646</a>                .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#2647 id=2647 data-nosnippet>2647</a>                .build()
+<a href=#2648 id=2648 data-nosnippet>2648</a>                .unwrap()),
+<a href=#2649 id=2649 data-nosnippet>2649</a>        ];
+<a href=#2650 id=2650 data-nosnippet>2650</a>
+<a href=#2651 id=2651 data-nosnippet>2651</a>        <span class="kw">let </span>tasks_stream = Box::pin(futures::stream::iter(tasks)) <span class="kw">as </span>FileScanTaskStream;
+<a href=#2652 id=2652 data-nosnippet>2652</a>
+<a href=#2653 id=2653 data-nosnippet>2653</a>        <span class="kw">let </span>result = reader
+<a href=#2654 id=2654 data-nosnippet>2654</a>            .read(tasks_stream)
+<a href=#2655 id=2655 data-nosnippet>2655</a>            .unwrap()
+<a href=#2656 id=2656 data-nosnippet>2656</a>            .stream()
+<a href=#2657 id=2657 data-nosnippet>2657</a>            .try_collect::&lt;Vec&lt;RecordBatch&gt;&gt;()
+<a href=#2658 id=2658 data-nosnippet>2658</a>            .<span class="kw">await
+<a href=#2659 id=2659 data-nosnippet>2659</a>            </span>.unwrap();
+<a href=#2660 id=2660 data-nosnippet>2660</a>
+<a href=#2661 id=2661 data-nosnippet>2661</a>        <span class="comment">// Verify we got all 30 rows (10 from each file)
+<a href=#2662 id=2662 data-nosnippet>2662</a>        </span><span class="kw">let </span>total_rows: usize = result.iter().map(|b| b.num_rows()).sum();
+<a href=#2663 id=2663 data-nosnippet>2663</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">30</span>, <span class="string">"Should have 30 total rows"</span>);
+<a href=#2664 id=2664 data-nosnippet>2664</a>
+<a href=#2665 id=2665 data-nosnippet>2665</a>        <span class="comment">// Collect all ids and file_nums to verify data
+<a href=#2666 id=2666 data-nosnippet>2666</a>        </span><span class="kw">let </span><span class="kw-2">mut </span>all_ids = Vec::new();
+<a href=#2667 id=2667 data-nosnippet>2667</a>        <span class="kw">let </span><span class="kw-2">mut </span>all_file_nums = Vec::new();
+<a href=#2668 id=2668 data-nosnippet>2668</a>
+<a href=#2669 id=2669 data-nosnippet>2669</a>        <span class="kw">for </span>batch <span class="kw">in </span><span class="kw-2">&amp;</span>result {
+<a href=#2670 id=2670 data-nosnippet>2670</a>            <span class="kw">let </span>id_col = batch
+<a href=#2671 id=2671 data-nosnippet>2671</a>                .column(<span class="number">0</span>)
+<a href=#2672 id=2672 data-nosnippet>2672</a>                .as_primitive::&lt;arrow_array::types::Int32Type&gt;();
+<a href=#2673 id=2673 data-nosnippet>2673</a>            <span class="kw">let </span>file_num_col = batch
+<a href=#2674 id=2674 data-nosnippet>2674</a>                .column(<span class="number">1</span>)
+<a href=#2675 id=2675 data-nosnippet>2675</a>                .as_primitive::&lt;arrow_array::types::Int32Type&gt;();
+<a href=#2676 id=2676 data-nosnippet>2676</a>
+<a href=#2677 id=2677 data-nosnippet>2677</a>            <span class="kw">for </span>i <span class="kw">in </span><span class="number">0</span>..batch.num_rows() {
+<a href=#2678 id=2678 data-nosnippet>2678</a>                all_ids.push(id_col.value(i));
+<a href=#2679 id=2679 data-nosnippet>2679</a>                all_file_nums.push(file_num_col.value(i));
+<a href=#2680 id=2680 data-nosnippet>2680</a>            }
+<a href=#2681 id=2681 data-nosnippet>2681</a>        }
+<a href=#2682 id=2682 data-nosnippet>2682</a>
+<a href=#2683 id=2683 data-nosnippet>2683</a>        <span class="macro">assert_eq!</span>(all_ids.len(), <span class="number">30</span>);
+<a href=#2684 id=2684 data-nosnippet>2684</a>        <span class="macro">assert_eq!</span>(all_file_nums.len(), <span class="number">30</span>);
+<a href=#2685 id=2685 data-nosnippet>2685</a>
+<a href=#2686 id=2686 data-nosnippet>2686</a>        <span class="comment">// With concurrency=1 and sequential processing, files should be processed in order
+<a href=#2687 id=2687 data-nosnippet>2687</a>        // file_0: ids 0-9, file_num=0
+<a href=#2688 id=2688 data-nosnippet>2688</a>        // file_1: ids 10-19, file_num=1
+<a href=#2689 id=2689 data-nosnippet>2689</a>        // file_2: ids 20-29, file_num=2
+<a href=#2690 id=2690 data-nosnippet>2690</a>        </span><span class="kw">for </span>i <span class="kw">in </span><span class="number">0</span>..<span class="number">10 </span>{
+<a href=#2691 id=2691 data-nosnippet>2691</a>            <span class="macro">assert_eq!</span>(all_file_nums[i], <span class="number">0</span>, <span class="string">"First 10 rows should be from file_0"</span>);
+<a href=#2692 id=2692 data-nosnippet>2692</a>            <span class="macro">assert_eq!</span>(all_ids[i], i <span class="kw">as </span>i32, <span class="string">"IDs should be 0-9"</span>);
+<a href=#2693 id=2693 data-nosnippet>2693</a>        }
+<a href=#2694 id=2694 data-nosnippet>2694</a>        <span class="kw">for </span>i <span class="kw">in </span><span class="number">10</span>..<span class="number">20 </span>{
+<a href=#2695 id=2695 data-nosnippet>2695</a>            <span class="macro">assert_eq!</span>(all_file_nums[i], <span class="number">1</span>, <span class="string">"Next 10 rows should be from file_1"</span>);
+<a href=#2696 id=2696 data-nosnippet>2696</a>            <span class="macro">assert_eq!</span>(all_ids[i], i <span class="kw">as </span>i32, <span class="string">"IDs should be 10-19"</span>);
+<a href=#2697 id=2697 data-nosnippet>2697</a>        }
+<a href=#2698 id=2698 data-nosnippet>2698</a>        <span class="kw">for </span>i <span class="kw">in </span><span class="number">20</span>..<span class="number">30 </span>{
+<a href=#2699 id=2699 data-nosnippet>2699</a>            <span class="macro">assert_eq!</span>(all_file_nums[i], <span class="number">2</span>, <span class="string">"Last 10 rows should be from file_2"</span>);
+<a href=#2700 id=2700 data-nosnippet>2700</a>            <span class="macro">assert_eq!</span>(all_ids[i], i <span class="kw">as </span>i32, <span class="string">"IDs should be 20-29"</span>);
+<a href=#2701 id=2701 data-nosnippet>2701</a>        }
+<a href=#2702 id=2702 data-nosnippet>2702</a>    }
+<a href=#2703 id=2703 data-nosnippet>2703</a>
+<a href=#2704 id=2704 data-nosnippet>2704</a>    <span class="attr">#[tokio::test]
+<a href=#2705 id=2705 data-nosnippet>2705</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_with_field_ids() {
+<a href=#2706 id=2706 data-nosnippet>2706</a>        <span class="kw">let </span>schema = Arc::new(
+<a href=#2707 id=2707 data-nosnippet>2707</a>            Schema::builder()
+<a href=#2708 id=2708 data-nosnippet>2708</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#2709 id=2709 data-nosnippet>2709</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#2710 id=2710 data-nosnippet>2710</a>                    NestedField::optional(<span class="number">1</span>, <span class="string">"ts"</span>, Type::Primitive(PrimitiveType::Timestamp))
+<a href=#2711 id=2711 data-nosnippet>2711</a>                        .into(),
+<a href=#2712 id=2712 data-nosnippet>2712</a>                    NestedField::required(<span class="number">2</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
+<a href=#2713 id=2713 data-nosnippet>2713</a>                ])
+<a href=#2714 id=2714 data-nosnippet>2714</a>                .build()
+<a href=#2715 id=2715 data-nosnippet>2715</a>                .unwrap(),
+<a href=#2716 id=2716 data-nosnippet>2716</a>        );
+<a href=#2717 id=2717 data-nosnippet>2717</a>
+<a href=#2718 id=2718 data-nosnippet>2718</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#2719 id=2719 data-nosnippet>2719</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
+<a href=#2720 id=2720 data-nosnippet>2720</a>        <span class="kw">let </span>(file_path, expected_micros) =
+<a href=#2721 id=2721 data-nosnippet>2721</a>            write_int96_parquet_file(<span class="kw-2">&amp;</span>table_location, <span class="string">"with_ids.parquet"</span>, <span class="bool-val">true</span>);
+<a href=#2722 id=2722 data-nosnippet>2722</a>
+<a href=#2723 id=2723 data-nosnippet>2723</a>        assert_int96_read_matches(<span class="kw-2">&amp;</span>file_path, schema, <span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>], <span class="kw-2">&amp;</span>expected_micros).<span class="kw">await</span>;
+<a href=#2724 id=2724 data-nosnippet>2724</a>    }
+<a href=#2725 id=2725 data-nosnippet>2725</a>
+<a href=#2726 id=2726 data-nosnippet>2726</a>    <span class="attr">#[tokio::test]
+<a href=#2727 id=2727 data-nosnippet>2727</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_without_field_ids() {
+<a href=#2728 id=2728 data-nosnippet>2728</a>        <span class="kw">let </span>schema = Arc::new(
+<a href=#2729 id=2729 data-nosnippet>2729</a>            Schema::builder()
+<a href=#2730 id=2730 data-nosnippet>2730</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#2731 id=2731 data-nosnippet>2731</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#2732 id=2732 data-nosnippet>2732</a>                    NestedField::optional(<span class="number">1</span>, <span class="string">"ts"</span>, Type::Primitive(PrimitiveType::Timestamp))
+<a href=#2733 id=2733 data-nosnippet>2733</a>                        .into(),
+<a href=#2734 id=2734 data-nosnippet>2734</a>                    NestedField::required(<span class="number">2</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
+<a href=#2735 id=2735 data-nosnippet>2735</a>                ])
+<a href=#2736 id=2736 data-nosnippet>2736</a>                .build()
+<a href=#2737 id=2737 data-nosnippet>2737</a>                .unwrap(),
+<a href=#2738 id=2738 data-nosnippet>2738</a>        );
+<a href=#2739 id=2739 data-nosnippet>2739</a>
+<a href=#2740 id=2740 data-nosnippet>2740</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#2741 id=2741 data-nosnippet>2741</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
+<a href=#2742 id=2742 data-nosnippet>2742</a>        <span class="kw">let </span>(file_path, expected_micros) =
+<a href=#2743 id=2743 data-nosnippet>2743</a>            write_int96_parquet_file(<span class="kw-2">&amp;</span>table_location, <span class="string">"no_ids.parquet"</span>, <span class="bool-val">false</span>);
+<a href=#2744 id=2744 data-nosnippet>2744</a>
+<a href=#2745 id=2745 data-nosnippet>2745</a>        assert_int96_read_matches(<span class="kw-2">&amp;</span>file_path, schema, <span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>], <span class="kw-2">&amp;</span>expected_micros).<span class="kw">await</span>;
+<a href=#2746 id=2746 data-nosnippet>2746</a>    }
+<a href=#2747 id=2747 data-nosnippet>2747</a>
+<a href=#2748 id=2748 data-nosnippet>2748</a>    <span class="attr">#[tokio::test]
+<a href=#2749 id=2749 data-nosnippet>2749</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_in_struct() {
+<a href=#2750 id=2750 data-nosnippet>2750</a>        <span class="kw">use </span>arrow_array::{StructArray, TimestampMicrosecondArray};
+<a href=#2751 id=2751 data-nosnippet>2751</a>        <span class="kw">use </span>parquet::basic::{Repetition, Type <span class="kw">as </span>PhysicalType};
+<a href=#2752 id=2752 data-nosnippet>2752</a>        <span class="kw">use </span>parquet::data_type::Int96Type;
+<a href=#2753 id=2753 data-nosnippet>2753</a>        <span class="kw">use </span>parquet::file::writer::SerializedFileWriter;
+<a href=#2754 id=2754 data-nosnippet>2754</a>        <span class="kw">use </span>parquet::schema::types::Type <span class="kw">as </span>SchemaType;
+<a href=#2755 id=2755 data-nosnippet>2755</a>
+<a href=#2756 id=2756 data-nosnippet>2756</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#2757 id=2757 data-nosnippet>2757</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
+<a href=#2758 id=2758 data-nosnippet>2758</a>        <span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{table_location}/struct_int96.parquet"</span>);
+<a href=#2759 id=2759 data-nosnippet>2759</a>
+<a href=#2760 id=2760 data-nosnippet>2760</a>        <span class="kw">let </span>ts_type = SchemaType::primitive_type_builder(<span class="string">"ts"</span>, PhysicalType::INT96)
+<a href=#2761 id=2761 data-nosnippet>2761</a>            .with_repetition(Repetition::OPTIONAL)
+<a href=#2762 id=2762 data-nosnippet>2762</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">2</span>))
+<a href=#2763 id=2763 data-nosnippet>2763</a>            .build()
+<a href=#2764 id=2764 data-nosnippet>2764</a>            .unwrap();
+<a href=#2765 id=2765 data-nosnippet>2765</a>
+<a href=#2766 id=2766 data-nosnippet>2766</a>        <span class="kw">let </span>struct_type = SchemaType::group_type_builder(<span class="string">"data"</span>)
+<a href=#2767 id=2767 data-nosnippet>2767</a>            .with_repetition(Repetition::REQUIRED)
+<a href=#2768 id=2768 data-nosnippet>2768</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">1</span>))
+<a href=#2769 id=2769 data-nosnippet>2769</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(ts_type)])
+<a href=#2770 id=2770 data-nosnippet>2770</a>            .build()
+<a href=#2771 id=2771 data-nosnippet>2771</a>            .unwrap();
+<a href=#2772 id=2772 data-nosnippet>2772</a>
+<a href=#2773 id=2773 data-nosnippet>2773</a>        <span class="kw">let </span>parquet_schema = SchemaType::group_type_builder(<span class="string">"schema"</span>)
+<a href=#2774 id=2774 data-nosnippet>2774</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(struct_type)])
+<a href=#2775 id=2775 data-nosnippet>2775</a>            .build()
+<a href=#2776 id=2776 data-nosnippet>2776</a>            .unwrap();
+<a href=#2777 id=2777 data-nosnippet>2777</a>
+<a href=#2778 id=2778 data-nosnippet>2778</a>        <span class="kw">let </span>(int96_val, expected_micros) = make_int96_test_value();
+<a href=#2779 id=2779 data-nosnippet>2779</a>
+<a href=#2780 id=2780 data-nosnippet>2780</a>        <span class="kw">let </span>file = File::create(<span class="kw-2">&amp;</span>file_path).unwrap();
+<a href=#2781 id=2781 data-nosnippet>2781</a>        <span class="kw">let </span><span class="kw-2">mut </span>writer =
+<a href=#2782 id=2782 data-nosnippet>2782</a>            SerializedFileWriter::new(file, Arc::new(parquet_schema), Default::default()).unwrap();
+<a href=#2783 id=2783 data-nosnippet>2783</a>
+<a href=#2784 id=2784 data-nosnippet>2784</a>        <span class="comment">// def=1: struct is REQUIRED so no level, ts is OPTIONAL and present (1).
+<a href=#2785 id=2785 data-nosnippet>2785</a>        // No repetition levels needed (no repeated groups).
+<a href=#2786 id=2786 data-nosnippet>2786</a>        </span><span class="kw">let </span><span class="kw-2">mut </span>row_group = writer.next_row_group().unwrap();
+<a href=#2787 id=2787 data-nosnippet>2787</a>        {
+<a href=#2788 id=2788 data-nosnippet>2788</a>            <span class="kw">let </span><span class="kw-2">mut </span>col = row_group.next_column().unwrap().unwrap();
+<a href=#2789 id=2789 data-nosnippet>2789</a>            col.typed::&lt;Int96Type&gt;()
+<a href=#2790 id=2790 data-nosnippet>2790</a>                .write_batch(<span class="kw-2">&amp;</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">1</span>]), <span class="prelude-val">None</span>)
+<a href=#2791 id=2791 data-nosnippet>2791</a>                .unwrap();
+<a href=#2792 id=2792 data-nosnippet>2792</a>            col.close().unwrap();
+<a href=#2793 id=2793 data-nosnippet>2793</a>        }
+<a href=#2794 id=2794 data-nosnippet>2794</a>        row_group.close().unwrap();
+<a href=#2795 id=2795 data-nosnippet>2795</a>        writer.close().unwrap();
+<a href=#2796 id=2796 data-nosnippet>2796</a>
+<a href=#2797 id=2797 data-nosnippet>2797</a>        <span class="kw">let </span>iceberg_schema = Arc::new(
+<a href=#2798 id=2798 data-nosnippet>2798</a>            Schema::builder()
+<a href=#2799 id=2799 data-nosnippet>2799</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#2800 id=2800 data-nosnippet>2800</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#2801 id=2801 data-nosnippet>2801</a>                    NestedField::required(
+<a href=#2802 id=2802 data-nosnippet>2802</a>                        <span class="number">1</span>,
+<a href=#2803 id=2803 data-nosnippet>2803</a>                        <span class="string">"data"</span>,
+<a href=#2804 id=2804 data-nosnippet>2804</a>                        Type::Struct(<span class="kw">crate</span>::spec::StructType::new(<span class="macro">vec!</span>[
+<a href=#2805 id=2805 data-nosnippet>2805</a>                            NestedField::optional(
+<a href=#2806 id=2806 data-nosnippet>2806</a>                                <span class="number">2</span>,
+<a href=#2807 id=2807 data-nosnippet>2807</a>                                <span class="string">"ts"</span>,
+<a href=#2808 id=2808 data-nosnippet>2808</a>                                Type::Primitive(PrimitiveType::Timestamp),
+<a href=#2809 id=2809 data-nosnippet>2809</a>                            )
+<a href=#2810 id=2810 data-nosnippet>2810</a>                            .into(),
+<a href=#2811 id=2811 data-nosnippet>2811</a>                        ])),
+<a href=#2812 id=2812 data-nosnippet>2812</a>                    )
+<a href=#2813 id=2813 data-nosnippet>2813</a>                    .into(),
+<a href=#2814 id=2814 data-nosnippet>2814</a>                ])
+<a href=#2815 id=2815 data-nosnippet>2815</a>                .build()
+<a href=#2816 id=2816 data-nosnippet>2816</a>                .unwrap(),
+<a href=#2817 id=2817 data-nosnippet>2817</a>        );
+<a href=#2818 id=2818 data-nosnippet>2818</a>
+<a href=#2819 id=2819 data-nosnippet>2819</a>        <span class="kw">let </span>batches = read_int96_batches(<span class="kw-2">&amp;</span>file_path, iceberg_schema, <span class="macro">vec!</span>[<span class="number">1</span>]).<span class="kw">await</span>;
+<a href=#2820 id=2820 data-nosnippet>2820</a>
+<a href=#2821 id=2821 data-nosnippet>2821</a>        <span class="macro">assert_eq!</span>(batches.len(), <span class="number">1</span>);
+<a href=#2822 id=2822 data-nosnippet>2822</a>        <span class="kw">let </span>struct_array = batches[<span class="number">0</span>]
+<a href=#2823 id=2823 data-nosnippet>2823</a>            .column(<span class="number">0</span>)
+<a href=#2824 id=2824 data-nosnippet>2824</a>            .as_any()
+<a href=#2825 id=2825 data-nosnippet>2825</a>            .downcast_ref::&lt;StructArray&gt;()
+<a href=#2826 id=2826 data-nosnippet>2826</a>            .expect(<span class="string">"Expected StructArray"</span>);
+<a href=#2827 id=2827 data-nosnippet>2827</a>        <span class="kw">let </span>ts_array = struct_array
+<a href=#2828 id=2828 data-nosnippet>2828</a>            .column(<span class="number">0</span>)
+<a href=#2829 id=2829 data-nosnippet>2829</a>            .as_any()
+<a href=#2830 id=2830 data-nosnippet>2830</a>            .downcast_ref::&lt;TimestampMicrosecondArray&gt;()
+<a href=#2831 id=2831 data-nosnippet>2831</a>            .expect(<span class="string">"Expected TimestampMicrosecondArray inside struct"</span>);
+<a href=#2832 id=2832 data-nosnippet>2832</a>
+<a href=#2833 id=2833 data-nosnippet>2833</a>        <span class="macro">assert_eq!</span>(
+<a href=#2834 id=2834 data-nosnippet>2834</a>            ts_array.value(<span class="number">0</span>),
+<a href=#2835 id=2835 data-nosnippet>2835</a>            expected_micros,
+<a href=#2836 id=2836 data-nosnippet>2836</a>            <span class="string">"INT96 in struct: got {}, expected {expected_micros}"</span>,
+<a href=#2837 id=2837 data-nosnippet>2837</a>            ts_array.value(<span class="number">0</span>)
+<a href=#2838 id=2838 data-nosnippet>2838</a>        );
+<a href=#2839 id=2839 data-nosnippet>2839</a>    }
+<a href=#2840 id=2840 data-nosnippet>2840</a>
+<a href=#2841 id=2841 data-nosnippet>2841</a>    <span class="attr">#[tokio::test]
+<a href=#2842 id=2842 data-nosnippet>2842</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_in_list() {
+<a href=#2843 id=2843 data-nosnippet>2843</a>        <span class="kw">use </span>arrow_array::{ListArray, TimestampMicrosecondArray};
+<a href=#2844 id=2844 data-nosnippet>2844</a>        <span class="kw">use </span>parquet::basic::{Repetition, Type <span class="kw">as </span>PhysicalType};
+<a href=#2845 id=2845 data-nosnippet>2845</a>        <span class="kw">use </span>parquet::data_type::Int96Type;
+<a href=#2846 id=2846 data-nosnippet>2846</a>        <span class="kw">use </span>parquet::file::writer::SerializedFileWriter;
+<a href=#2847 id=2847 data-nosnippet>2847</a>        <span class="kw">use </span>parquet::schema::types::Type <span class="kw">as </span>SchemaType;
+<a href=#2848 id=2848 data-nosnippet>2848</a>
+<a href=#2849 id=2849 data-nosnippet>2849</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#2850 id=2850 data-nosnippet>2850</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
+<a href=#2851 id=2851 data-nosnippet>2851</a>        <span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{table_location}/list_int96.parquet"</span>);
+<a href=#2852 id=2852 data-nosnippet>2852</a>
+<a href=#2853 id=2853 data-nosnippet>2853</a>        <span class="comment">// 3-level LIST encoding:
+<a href=#2854 id=2854 data-nosnippet>2854</a>        //   optional group timestamps (LIST) {
+<a href=#2855 id=2855 data-nosnippet>2855</a>        //     repeated group list {
+<a href=#2856 id=2856 data-nosnippet>2856</a>        //       optional int96 element;
+<a href=#2857 id=2857 data-nosnippet>2857</a>        //     }
+<a href=#2858 id=2858 data-nosnippet>2858</a>        //   }
+<a href=#2859 id=2859 data-nosnippet>2859</a>        </span><span class="kw">let </span>element_type = SchemaType::primitive_type_builder(<span class="string">"element"</span>, PhysicalType::INT96)
+<a href=#2860 id=2860 data-nosnippet>2860</a>            .with_repetition(Repetition::OPTIONAL)
+<a href=#2861 id=2861 data-nosnippet>2861</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">2</span>))
+<a href=#2862 id=2862 data-nosnippet>2862</a>            .build()
+<a href=#2863 id=2863 data-nosnippet>2863</a>            .unwrap();
+<a href=#2864 id=2864 data-nosnippet>2864</a>
+<a href=#2865 id=2865 data-nosnippet>2865</a>        <span class="kw">let </span>list_group = SchemaType::group_type_builder(<span class="string">"list"</span>)
+<a href=#2866 id=2866 data-nosnippet>2866</a>            .with_repetition(Repetition::REPEATED)
+<a href=#2867 id=2867 data-nosnippet>2867</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(element_type)])
+<a href=#2868 id=2868 data-nosnippet>2868</a>            .build()
+<a href=#2869 id=2869 data-nosnippet>2869</a>            .unwrap();
+<a href=#2870 id=2870 data-nosnippet>2870</a>
+<a href=#2871 id=2871 data-nosnippet>2871</a>        <span class="kw">let </span>list_type = SchemaType::group_type_builder(<span class="string">"timestamps"</span>)
+<a href=#2872 id=2872 data-nosnippet>2872</a>            .with_repetition(Repetition::OPTIONAL)
+<a href=#2873 id=2873 data-nosnippet>2873</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">1</span>))
+<a href=#2874 id=2874 data-nosnippet>2874</a>            .with_logical_type(<span class="prelude-val">Some</span>(parquet::basic::LogicalType::List))
+<a href=#2875 id=2875 data-nosnippet>2875</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(list_group)])
+<a href=#2876 id=2876 data-nosnippet>2876</a>            .build()
+<a href=#2877 id=2877 data-nosnippet>2877</a>            .unwrap();
+<a href=#2878 id=2878 data-nosnippet>2878</a>
+<a href=#2879 id=2879 data-nosnippet>2879</a>        <span class="kw">let </span>parquet_schema = SchemaType::group_type_builder(<span class="string">"schema"</span>)
+<a href=#2880 id=2880 data-nosnippet>2880</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(list_type)])
+<a href=#2881 id=2881 data-nosnippet>2881</a>            .build()
+<a href=#2882 id=2882 data-nosnippet>2882</a>            .unwrap();
+<a href=#2883 id=2883 data-nosnippet>2883</a>
+<a href=#2884 id=2884 data-nosnippet>2884</a>        <span class="kw">let </span>(int96_val, expected_micros) = make_int96_test_value();
+<a href=#2885 id=2885 data-nosnippet>2885</a>
+<a href=#2886 id=2886 data-nosnippet>2886</a>        <span class="kw">let </span>file = File::create(<span class="kw-2">&amp;</span>file_path).unwrap();
+<a href=#2887 id=2887 data-nosnippet>2887</a>        <span class="kw">let </span><span class="kw-2">mut </span>writer =
+<a href=#2888 id=2888 data-nosnippet>2888</a>            SerializedFileWriter::new(file, Arc::new(parquet_schema), Default::default()).unwrap();
+<a href=#2889 id=2889 data-nosnippet>2889</a>
+<a href=#2890 id=2890 data-nosnippet>2890</a>        <span class="comment">// Write a single row with a list containing one INT96 element.
+<a href=#2891 id=2891 data-nosnippet>2891</a>        // def=3: list present (1) + repeated group (2) + element present (3)
+<a href=#2892 id=2892 data-nosnippet>2892</a>        // rep=0: start of a new list
+<a href=#2893 id=2893 data-nosnippet>2893</a>        </span><span class="kw">let </span><span class="kw-2">mut </span>row_group = writer.next_row_group().unwrap();
+<a href=#2894 id=2894 data-nosnippet>2894</a>        {
+<a href=#2895 id=2895 data-nosnippet>2895</a>            <span class="kw">let </span><span class="kw-2">mut </span>col = row_group.next_column().unwrap().unwrap();
+<a href=#2896 id=2896 data-nosnippet>2896</a>            col.typed::&lt;Int96Type&gt;()
+<a href=#2897 id=2897 data-nosnippet>2897</a>                .write_batch(<span class="kw-2">&amp;</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">3</span>]), <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">0</span>]))
+<a href=#2898 id=2898 data-nosnippet>2898</a>                .unwrap();
+<a href=#2899 id=2899 data-nosnippet>2899</a>            col.close().unwrap();
+<a href=#2900 id=2900 data-nosnippet>2900</a>        }
+<a href=#2901 id=2901 data-nosnippet>2901</a>        row_group.close().unwrap();
+<a href=#2902 id=2902 data-nosnippet>2902</a>        writer.close().unwrap();
+<a href=#2903 id=2903 data-nosnippet>2903</a>
+<a href=#2904 id=2904 data-nosnippet>2904</a>        <span class="kw">let </span>iceberg_schema = Arc::new(
+<a href=#2905 id=2905 data-nosnippet>2905</a>            Schema::builder()
+<a href=#2906 id=2906 data-nosnippet>2906</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#2907 id=2907 data-nosnippet>2907</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#2908 id=2908 data-nosnippet>2908</a>                    NestedField::optional(
+<a href=#2909 id=2909 data-nosnippet>2909</a>                        <span class="number">1</span>,
+<a href=#2910 id=2910 data-nosnippet>2910</a>                        <span class="string">"timestamps"</span>,
+<a href=#2911 id=2911 data-nosnippet>2911</a>                        Type::List(<span class="kw">crate</span>::spec::ListType {
+<a href=#2912 id=2912 data-nosnippet>2912</a>                            element_field: NestedField::optional(
+<a href=#2913 id=2913 data-nosnippet>2913</a>                                <span class="number">2</span>,
+<a href=#2914 id=2914 data-nosnippet>2914</a>                                <span class="string">"element"</span>,
+<a href=#2915 id=2915 data-nosnippet>2915</a>                                Type::Primitive(PrimitiveType::Timestamp),
+<a href=#2916 id=2916 data-nosnippet>2916</a>                            )
+<a href=#2917 id=2917 data-nosnippet>2917</a>                            .into(),
+<a href=#2918 id=2918 data-nosnippet>2918</a>                        }),
+<a href=#2919 id=2919 data-nosnippet>2919</a>                    )
+<a href=#2920 id=2920 data-nosnippet>2920</a>                    .into(),
+<a href=#2921 id=2921 data-nosnippet>2921</a>                ])
+<a href=#2922 id=2922 data-nosnippet>2922</a>                .build()
+<a href=#2923 id=2923 data-nosnippet>2923</a>                .unwrap(),
+<a href=#2924 id=2924 data-nosnippet>2924</a>        );
+<a href=#2925 id=2925 data-nosnippet>2925</a>
+<a href=#2926 id=2926 data-nosnippet>2926</a>        <span class="kw">let </span>batches = read_int96_batches(<span class="kw-2">&amp;</span>file_path, iceberg_schema, <span class="macro">vec!</span>[<span class="number">1</span>]).<span class="kw">await</span>;
+<a href=#2927 id=2927 data-nosnippet>2927</a>
+<a href=#2928 id=2928 data-nosnippet>2928</a>        <span class="macro">assert_eq!</span>(batches.len(), <span class="number">1</span>);
+<a href=#2929 id=2929 data-nosnippet>2929</a>        <span class="kw">let </span>list_array = batches[<span class="number">0</span>]
+<a href=#2930 id=2930 data-nosnippet>2930</a>            .column(<span class="number">0</span>)
+<a href=#2931 id=2931 data-nosnippet>2931</a>            .as_any()
+<a href=#2932 id=2932 data-nosnippet>2932</a>            .downcast_ref::&lt;ListArray&gt;()
+<a href=#2933 id=2933 data-nosnippet>2933</a>            .expect(<span class="string">"Expected ListArray"</span>);
+<a href=#2934 id=2934 data-nosnippet>2934</a>        <span class="kw">let </span>ts_array = list_array
+<a href=#2935 id=2935 data-nosnippet>2935</a>            .values()
+<a href=#2936 id=2936 data-nosnippet>2936</a>            .as_any()
+<a href=#2937 id=2937 data-nosnippet>2937</a>            .downcast_ref::&lt;TimestampMicrosecondArray&gt;()
+<a href=#2938 id=2938 data-nosnippet>2938</a>            .expect(<span class="string">"Expected TimestampMicrosecondArray inside list"</span>);
+<a href=#2939 id=2939 data-nosnippet>2939</a>
+<a href=#2940 id=2940 data-nosnippet>2940</a>        <span class="macro">assert_eq!</span>(
+<a href=#2941 id=2941 data-nosnippet>2941</a>            ts_array.value(<span class="number">0</span>),
+<a href=#2942 id=2942 data-nosnippet>2942</a>            expected_micros,
+<a href=#2943 id=2943 data-nosnippet>2943</a>            <span class="string">"INT96 in list: got {}, expected {expected_micros}"</span>,
+<a href=#2944 id=2944 data-nosnippet>2944</a>            ts_array.value(<span class="number">0</span>)
+<a href=#2945 id=2945 data-nosnippet>2945</a>        );
+<a href=#2946 id=2946 data-nosnippet>2946</a>    }
+<a href=#2947 id=2947 data-nosnippet>2947</a>
+<a href=#2948 id=2948 data-nosnippet>2948</a>    <span class="attr">#[tokio::test]
+<a href=#2949 id=2949 data-nosnippet>2949</a>    </span><span class="kw">async fn </span>test_read_int96_timestamps_in_map() {
+<a href=#2950 id=2950 data-nosnippet>2950</a>        <span class="kw">use </span>arrow_array::{MapArray, TimestampMicrosecondArray};
+<a href=#2951 id=2951 data-nosnippet>2951</a>        <span class="kw">use </span>parquet::basic::{Repetition, Type <span class="kw">as </span>PhysicalType};
+<a href=#2952 id=2952 data-nosnippet>2952</a>        <span class="kw">use </span>parquet::data_type::{ByteArrayType, Int96Type};
+<a href=#2953 id=2953 data-nosnippet>2953</a>        <span class="kw">use </span>parquet::file::writer::SerializedFileWriter;
+<a href=#2954 id=2954 data-nosnippet>2954</a>        <span class="kw">use </span>parquet::schema::types::Type <span class="kw">as </span>SchemaType;
+<a href=#2955 id=2955 data-nosnippet>2955</a>
+<a href=#2956 id=2956 data-nosnippet>2956</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#2957 id=2957 data-nosnippet>2957</a>        <span class="kw">let </span>table_location = tmp_dir.path().to_str().unwrap().to_string();
+<a href=#2958 id=2958 data-nosnippet>2958</a>        <span class="kw">let </span>file_path = <span class="macro">format!</span>(<span class="string">"{table_location}/map_int96.parquet"</span>);
+<a href=#2959 id=2959 data-nosnippet>2959</a>
+<a href=#2960 id=2960 data-nosnippet>2960</a>        <span class="comment">// MAP encoding:
+<a href=#2961 id=2961 data-nosnippet>2961</a>        //   optional group ts_map (MAP) {
+<a href=#2962 id=2962 data-nosnippet>2962</a>        //     repeated group key_value {
+<a href=#2963 id=2963 data-nosnippet>2963</a>        //       required binary key (UTF8);
+<a href=#2964 id=2964 data-nosnippet>2964</a>        //       optional int96 value;
+<a href=#2965 id=2965 data-nosnippet>2965</a>        //     }
+<a href=#2966 id=2966 data-nosnippet>2966</a>        //   }
+<a href=#2967 id=2967 data-nosnippet>2967</a>        </span><span class="kw">let </span>key_type = SchemaType::primitive_type_builder(<span class="string">"key"</span>, PhysicalType::BYTE_ARRAY)
+<a href=#2968 id=2968 data-nosnippet>2968</a>            .with_repetition(Repetition::REQUIRED)
+<a href=#2969 id=2969 data-nosnippet>2969</a>            .with_logical_type(<span class="prelude-val">Some</span>(parquet::basic::LogicalType::String))
+<a href=#2970 id=2970 data-nosnippet>2970</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">2</span>))
+<a href=#2971 id=2971 data-nosnippet>2971</a>            .build()
+<a href=#2972 id=2972 data-nosnippet>2972</a>            .unwrap();
+<a href=#2973 id=2973 data-nosnippet>2973</a>
+<a href=#2974 id=2974 data-nosnippet>2974</a>        <span class="kw">let </span>value_type = SchemaType::primitive_type_builder(<span class="string">"value"</span>, PhysicalType::INT96)
+<a href=#2975 id=2975 data-nosnippet>2975</a>            .with_repetition(Repetition::OPTIONAL)
+<a href=#2976 id=2976 data-nosnippet>2976</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">3</span>))
+<a href=#2977 id=2977 data-nosnippet>2977</a>            .build()
+<a href=#2978 id=2978 data-nosnippet>2978</a>            .unwrap();
+<a href=#2979 id=2979 data-nosnippet>2979</a>
+<a href=#2980 id=2980 data-nosnippet>2980</a>        <span class="kw">let </span>key_value_group = SchemaType::group_type_builder(<span class="string">"key_value"</span>)
+<a href=#2981 id=2981 data-nosnippet>2981</a>            .with_repetition(Repetition::REPEATED)
+<a href=#2982 id=2982 data-nosnippet>2982</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(key_type), Arc::new(value_type)])
+<a href=#2983 id=2983 data-nosnippet>2983</a>            .build()
+<a href=#2984 id=2984 data-nosnippet>2984</a>            .unwrap();
+<a href=#2985 id=2985 data-nosnippet>2985</a>
+<a href=#2986 id=2986 data-nosnippet>2986</a>        <span class="kw">let </span>map_type = SchemaType::group_type_builder(<span class="string">"ts_map"</span>)
+<a href=#2987 id=2987 data-nosnippet>2987</a>            .with_repetition(Repetition::OPTIONAL)
+<a href=#2988 id=2988 data-nosnippet>2988</a>            .with_id(<span class="prelude-val">Some</span>(<span class="number">1</span>))
+<a href=#2989 id=2989 data-nosnippet>2989</a>            .with_logical_type(<span class="prelude-val">Some</span>(parquet::basic::LogicalType::Map))
+<a href=#2990 id=2990 data-nosnippet>2990</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(key_value_group)])
+<a href=#2991 id=2991 data-nosnippet>2991</a>            .build()
+<a href=#2992 id=2992 data-nosnippet>2992</a>            .unwrap();
+<a href=#2993 id=2993 data-nosnippet>2993</a>
+<a href=#2994 id=2994 data-nosnippet>2994</a>        <span class="kw">let </span>parquet_schema = SchemaType::group_type_builder(<span class="string">"schema"</span>)
+<a href=#2995 id=2995 data-nosnippet>2995</a>            .with_fields(<span class="macro">vec!</span>[Arc::new(map_type)])
+<a href=#2996 id=2996 data-nosnippet>2996</a>            .build()
+<a href=#2997 id=2997 data-nosnippet>2997</a>            .unwrap();
+<a href=#2998 id=2998 data-nosnippet>2998</a>
+<a href=#2999 id=2999 data-nosnippet>2999</a>        <span class="kw">let </span>(int96_val, expected_micros) = make_int96_test_value();
+<a href=#3000 id=3000 data-nosnippet>3000</a>
+<a href=#3001 id=3001 data-nosnippet>3001</a>        <span class="kw">let </span>file = File::create(<span class="kw-2">&amp;</span>file_path).unwrap();
+<a href=#3002 id=3002 data-nosnippet>3002</a>        <span class="kw">let </span><span class="kw-2">mut </span>writer =
+<a href=#3003 id=3003 data-nosnippet>3003</a>            SerializedFileWriter::new(file, Arc::new(parquet_schema), Default::default()).unwrap();
+<a href=#3004 id=3004 data-nosnippet>3004</a>
+<a href=#3005 id=3005 data-nosnippet>3005</a>        <span class="comment">// Write a single row with a map containing one key-value pair.
+<a href=#3006 id=3006 data-nosnippet>3006</a>        // rep=0 for both columns: start of a new map.
+<a href=#3007 id=3007 data-nosnippet>3007</a>        // key def=2: map present (1) + key_value entry present (2), key is REQUIRED.
+<a href=#3008 id=3008 data-nosnippet>3008</a>        // value def=3: map present (1) + key_value entry present (2) + value present (3).
+<a href=#3009 id=3009 data-nosnippet>3009</a>        </span><span class="kw">let </span><span class="kw-2">mut </span>row_group = writer.next_row_group().unwrap();
+<a href=#3010 id=3010 data-nosnippet>3010</a>        {
+<a href=#3011 id=3011 data-nosnippet>3011</a>            <span class="kw">let </span><span class="kw-2">mut </span>col = row_group.next_column().unwrap().unwrap();
+<a href=#3012 id=3012 data-nosnippet>3012</a>            col.typed::&lt;ByteArrayType&gt;()
+<a href=#3013 id=3013 data-nosnippet>3013</a>                .write_batch(
+<a href=#3014 id=3014 data-nosnippet>3014</a>                    <span class="kw-2">&amp;</span>[parquet::data_type::ByteArray::from(<span class="string">"event_time"</span>)],
+<a href=#3015 id=3015 data-nosnippet>3015</a>                    <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">2</span>]),
+<a href=#3016 id=3016 data-nosnippet>3016</a>                    <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">0</span>]),
+<a href=#3017 id=3017 data-nosnippet>3017</a>                )
+<a href=#3018 id=3018 data-nosnippet>3018</a>                .unwrap();
+<a href=#3019 id=3019 data-nosnippet>3019</a>            col.close().unwrap();
+<a href=#3020 id=3020 data-nosnippet>3020</a>        }
+<a href=#3021 id=3021 data-nosnippet>3021</a>        {
+<a href=#3022 id=3022 data-nosnippet>3022</a>            <span class="kw">let </span><span class="kw-2">mut </span>col = row_group.next_column().unwrap().unwrap();
+<a href=#3023 id=3023 data-nosnippet>3023</a>            col.typed::&lt;Int96Type&gt;()
+<a href=#3024 id=3024 data-nosnippet>3024</a>                .write_batch(<span class="kw-2">&amp;</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">3</span>]), <span class="prelude-val">Some</span>(<span class="kw-2">&amp;</span>[<span class="number">0</span>]))
+<a href=#3025 id=3025 data-nosnippet>3025</a>                .unwrap();
+<a href=#3026 id=3026 data-nosnippet>3026</a>            col.close().unwrap();
+<a href=#3027 id=3027 data-nosnippet>3027</a>        }
+<a href=#3028 id=3028 data-nosnippet>3028</a>        row_group.close().unwrap();
+<a href=#3029 id=3029 data-nosnippet>3029</a>        writer.close().unwrap();
+<a href=#3030 id=3030 data-nosnippet>3030</a>
+<a href=#3031 id=3031 data-nosnippet>3031</a>        <span class="kw">let </span>iceberg_schema = Arc::new(
+<a href=#3032 id=3032 data-nosnippet>3032</a>            Schema::builder()
+<a href=#3033 id=3033 data-nosnippet>3033</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#3034 id=3034 data-nosnippet>3034</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#3035 id=3035 data-nosnippet>3035</a>                    NestedField::optional(
+<a href=#3036 id=3036 data-nosnippet>3036</a>                        <span class="number">1</span>,
+<a href=#3037 id=3037 data-nosnippet>3037</a>                        <span class="string">"ts_map"</span>,
+<a href=#3038 id=3038 data-nosnippet>3038</a>                        Type::Map(<span class="kw">crate</span>::spec::MapType {
+<a href=#3039 id=3039 data-nosnippet>3039</a>                            key_field: NestedField::required(
+<a href=#3040 id=3040 data-nosnippet>3040</a>                                <span class="number">2</span>,
+<a href=#3041 id=3041 data-nosnippet>3041</a>                                <span class="string">"key"</span>,
+<a href=#3042 id=3042 data-nosnippet>3042</a>                                Type::Primitive(PrimitiveType::String),
+<a href=#3043 id=3043 data-nosnippet>3043</a>                            )
+<a href=#3044 id=3044 data-nosnippet>3044</a>                            .into(),
+<a href=#3045 id=3045 data-nosnippet>3045</a>                            value_field: NestedField::optional(
+<a href=#3046 id=3046 data-nosnippet>3046</a>                                <span class="number">3</span>,
+<a href=#3047 id=3047 data-nosnippet>3047</a>                                <span class="string">"value"</span>,
+<a href=#3048 id=3048 data-nosnippet>3048</a>                                Type::Primitive(PrimitiveType::Timestamp),
+<a href=#3049 id=3049 data-nosnippet>3049</a>                            )
+<a href=#3050 id=3050 data-nosnippet>3050</a>                            .into(),
+<a href=#3051 id=3051 data-nosnippet>3051</a>                        }),
+<a href=#3052 id=3052 data-nosnippet>3052</a>                    )
+<a href=#3053 id=3053 data-nosnippet>3053</a>                    .into(),
+<a href=#3054 id=3054 data-nosnippet>3054</a>                ])
+<a href=#3055 id=3055 data-nosnippet>3055</a>                .build()
+<a href=#3056 id=3056 data-nosnippet>3056</a>                .unwrap(),
+<a href=#3057 id=3057 data-nosnippet>3057</a>        );
+<a href=#3058 id=3058 data-nosnippet>3058</a>
+<a href=#3059 id=3059 data-nosnippet>3059</a>        <span class="kw">let </span>batches = read_int96_batches(<span class="kw-2">&amp;</span>file_path, iceberg_schema, <span class="macro">vec!</span>[<span class="number">1</span>]).<span class="kw">await</span>;
+<a href=#3060 id=3060 data-nosnippet>3060</a>
+<a href=#3061 id=3061 data-nosnippet>3061</a>        <span class="macro">assert_eq!</span>(batches.len(), <span class="number">1</span>);
+<a href=#3062 id=3062 data-nosnippet>3062</a>        <span class="kw">let </span>map_array = batches[<span class="number">0</span>]
+<a href=#3063 id=3063 data-nosnippet>3063</a>            .column(<span class="number">0</span>)
+<a href=#3064 id=3064 data-nosnippet>3064</a>            .as_any()
+<a href=#3065 id=3065 data-nosnippet>3065</a>            .downcast_ref::&lt;MapArray&gt;()
+<a href=#3066 id=3066 data-nosnippet>3066</a>            .expect(<span class="string">"Expected MapArray"</span>);
+<a href=#3067 id=3067 data-nosnippet>3067</a>        <span class="kw">let </span>ts_array = map_array
+<a href=#3068 id=3068 data-nosnippet>3068</a>            .values()
+<a href=#3069 id=3069 data-nosnippet>3069</a>            .as_any()
+<a href=#3070 id=3070 data-nosnippet>3070</a>            .downcast_ref::&lt;TimestampMicrosecondArray&gt;()
+<a href=#3071 id=3071 data-nosnippet>3071</a>            .expect(<span class="string">"Expected TimestampMicrosecondArray as map values"</span>);
+<a href=#3072 id=3072 data-nosnippet>3072</a>
+<a href=#3073 id=3073 data-nosnippet>3073</a>        <span class="macro">assert_eq!</span>(
+<a href=#3074 id=3074 data-nosnippet>3074</a>            ts_array.value(<span class="number">0</span>),
+<a href=#3075 id=3075 data-nosnippet>3075</a>            expected_micros,
+<a href=#3076 id=3076 data-nosnippet>3076</a>            <span class="string">"INT96 in map: got {}, expected {expected_micros}"</span>,
+<a href=#3077 id=3077 data-nosnippet>3077</a>            ts_array.value(<span class="number">0</span>)
+<a href=#3078 id=3078 data-nosnippet>3078</a>        );
+<a href=#3079 id=3079 data-nosnippet>3079</a>    }
+<a href=#3080 id=3080 data-nosnippet>3080</a>
+<a href=#3081 id=3081 data-nosnippet>3081</a>    <span class="doccomment">/// Writes `id` (Int32) plus a wide string column (field id 2) whose bytes dominate
+<a href=#3082 id=3082 data-nosnippet>3082</a>    /// the file, so that reading it is visible in `bytes_read`.
+<a href=#3083 id=3083 data-nosnippet>3083</a>    ///
+<a href=#3084 id=3084 data-nosnippet>3084</a>    /// `extra_fields`/`extra_columns` (e.g. a physical metadata leaf) are appended after
+<a href=#3085 id=3085 data-nosnippet>3085</a>    /// the `id` and wide columns, mirroring `write_plain_parquet`'s shape.
+<a href=#3086 id=3086 data-nosnippet>3086</a>    </span><span class="kw">fn </span>write_parquet_with_wide_column(
+<a href=#3087 id=3087 data-nosnippet>3087</a>        dir: <span class="kw-2">&amp;</span>str,
+<a href=#3088 id=3088 data-nosnippet>3088</a>        name: <span class="kw-2">&amp;</span>str,
+<a href=#3089 id=3089 data-nosnippet>3089</a>        extra_fields: Vec&lt;Field&gt;,
+<a href=#3090 id=3090 data-nosnippet>3090</a>        extra_columns: Vec&lt;ArrayRef&gt;,
+<a href=#3091 id=3091 data-nosnippet>3091</a>    ) -&gt; String {
+<a href=#3092 id=3092 data-nosnippet>3092</a>        <span class="kw">let </span>wide_field =
+<a href=#3093 id=3093 data-nosnippet>3093</a>            Field::new(<span class="string">"wide"</span>, DataType::Utf8, <span class="bool-val">false</span>).with_metadata(HashMap::from([(
+<a href=#3094 id=3094 data-nosnippet>3094</a>                PARQUET_FIELD_ID_META_KEY.to_string(),
+<a href=#3095 id=3095 data-nosnippet>3095</a>                <span class="string">"2"</span>.to_string(),
+<a href=#3096 id=3096 data-nosnippet>3096</a>            )]));
+<a href=#3097 id=3097 data-nosnippet>3097</a>        <span class="comment">// Varied bytes so the column chunk does not compress away under SNAPPY, keeping
+<a href=#3098 id=3098 data-nosnippet>3098</a>        // the `bytes_read` difference between projecting it and not unambiguous.
+<a href=#3099 id=3099 data-nosnippet>3099</a>        </span><span class="kw">let </span>wide_values: Vec&lt;String&gt; = (<span class="number">0</span>..<span class="number">3</span>)
+<a href=#3100 id=3100 data-nosnippet>3100</a>            .map(|i| {
+<a href=#3101 id=3101 data-nosnippet>3101</a>                (<span class="number">0</span>..<span class="number">2048</span>)
+<a href=#3102 id=3102 data-nosnippet>3102</a>                    .map(|j| ((i * <span class="number">2048 </span>+ j) % <span class="number">251</span>) <span class="kw">as </span>u8 <span class="kw">as </span>char)
+<a href=#3103 id=3103 data-nosnippet>3103</a>                    .collect()
+<a href=#3104 id=3104 data-nosnippet>3104</a>            })
+<a href=#3105 id=3105 data-nosnippet>3105</a>            .collect();
+<a href=#3106 id=3106 data-nosnippet>3106</a>
+<a href=#3107 id=3107 data-nosnippet>3107</a>        <span class="kw">let </span><span class="kw-2">mut </span>fields = <span class="macro">vec!</span>[wide_field];
+<a href=#3108 id=3108 data-nosnippet>3108</a>        fields.extend(extra_fields);
+<a href=#3109 id=3109 data-nosnippet>3109</a>        <span class="kw">let </span><span class="kw-2">mut </span>columns: Vec&lt;ArrayRef&gt; = <span class="macro">vec!</span>[Arc::new(StringArray::from(wide_values))];
+<a href=#3110 id=3110 data-nosnippet>3110</a>        columns.extend(extra_columns);
+<a href=#3111 id=3111 data-nosnippet>3111</a>        write_plain_parquet(dir, name, fields, columns)
+<a href=#3112 id=3112 data-nosnippet>3112</a>    }
+<a href=#3113 id=3113 data-nosnippet>3113</a>
+<a href=#3114 id=3114 data-nosnippet>3114</a>    <span class="doccomment">/// Schema with `id` (field 1, Int) and `wide` (field 2, String), matching
+<a href=#3115 id=3115 data-nosnippet>3115</a>    /// `write_parquet_with_wide_column`.
+<a href=#3116 id=3116 data-nosnippet>3116</a>    </span><span class="kw">fn </span>id_and_wide_schema() -&gt; SchemaRef {
+<a href=#3117 id=3117 data-nosnippet>3117</a>        Arc::new(
+<a href=#3118 id=3118 data-nosnippet>3118</a>            Schema::builder()
+<a href=#3119 id=3119 data-nosnippet>3119</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#3120 id=3120 data-nosnippet>3120</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#3121 id=3121 data-nosnippet>3121</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
+<a href=#3122 id=3122 data-nosnippet>3122</a>                    NestedField::required(<span class="number">2</span>, <span class="string">"wide"</span>, Type::Primitive(PrimitiveType::String)).into(),
+<a href=#3123 id=3123 data-nosnippet>3123</a>                ])
+<a href=#3124 id=3124 data-nosnippet>3124</a>                .build()
+<a href=#3125 id=3125 data-nosnippet>3125</a>                .unwrap(),
+<a href=#3126 id=3126 data-nosnippet>3126</a>        )
+<a href=#3127 id=3127 data-nosnippet>3127</a>    }
+<a href=#3128 id=3128 data-nosnippet>3128</a>
+<a href=#3129 id=3129 data-nosnippet>3129</a>    <span class="doccomment">/// Builds a scan task over `file_path` projecting `project_field_ids`.
+<a href=#3130 id=3130 data-nosnippet>3130</a>    </span><span class="kw">fn </span>metadata_projection_task(
+<a href=#3131 id=3131 data-nosnippet>3131</a>        file_path: String,
+<a href=#3132 id=3132 data-nosnippet>3132</a>        schema: SchemaRef,
+<a href=#3133 id=3133 data-nosnippet>3133</a>        project_field_ids: Vec&lt;i32&gt;,
+<a href=#3134 id=3134 data-nosnippet>3134</a>    ) -&gt; FileScanTask {
+<a href=#3135 id=3135 data-nosnippet>3135</a>        metadata_projection_task_with_first_row_id(file_path, schema, project_field_ids, <span class="prelude-val">None</span>)
+<a href=#3136 id=3136 data-nosnippet>3136</a>    }
+<a href=#3137 id=3137 data-nosnippet>3137</a>
+<a href=#3138 id=3138 data-nosnippet>3138</a>    <span class="kw">fn </span>metadata_projection_task_with_first_row_id(
+<a href=#3139 id=3139 data-nosnippet>3139</a>        file_path: String,
+<a href=#3140 id=3140 data-nosnippet>3140</a>        schema: SchemaRef,
+<a href=#3141 id=3141 data-nosnippet>3141</a>        project_field_ids: Vec&lt;i32&gt;,
+<a href=#3142 id=3142 data-nosnippet>3142</a>        first_row_id: <span class="prelude-ty">Option</span>&lt;i64&gt;,
+<a href=#3143 id=3143 data-nosnippet>3143</a>    ) -&gt; FileScanTask {
+<a href=#3144 id=3144 data-nosnippet>3144</a>        FileScanTask::builder()
+<a href=#3145 id=3145 data-nosnippet>3145</a>            .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
+<a href=#3146 id=3146 data-nosnippet>3146</a>            .with_start(<span class="number">0</span>)
+<a href=#3147 id=3147 data-nosnippet>3147</a>            .with_length(<span class="number">0</span>)
+<a href=#3148 id=3148 data-nosnippet>3148</a>            .with_data_file_path(file_path)
+<a href=#3149 id=3149 data-nosnippet>3149</a>            .with_data_file_format(DataFileFormat::Parquet)
+<a href=#3150 id=3150 data-nosnippet>3150</a>            .with_schema(schema)
+<a href=#3151 id=3151 data-nosnippet>3151</a>            .with_project_field_ids(project_field_ids)
+<a href=#3152 id=3152 data-nosnippet>3152</a>            .with_first_row_id(first_row_id)
+<a href=#3153 id=3153 data-nosnippet>3153</a>            .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#3154 id=3154 data-nosnippet>3154</a>            .build()
+<a href=#3155 id=3155 data-nosnippet>3155</a>            .unwrap()
+<a href=#3156 id=3156 data-nosnippet>3156</a>    }
+<a href=#3157 id=3157 data-nosnippet>3157</a>
+<a href=#3158 id=3158 data-nosnippet>3158</a>    <span class="doccomment">/// Runs a single-task scan and returns the batches plus the bytes read from storage.
+<a href=#3159 id=3159 data-nosnippet>3159</a>    </span><span class="kw">async fn </span>scan_task(task: FileScanTask) -&gt; (Vec&lt;RecordBatch&gt;, u64) {
+<a href=#3160 id=3160 data-nosnippet>3160</a>        <span class="kw">let </span>reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current()).build();
+<a href=#3161 id=3161 data-nosnippet>3161</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
+<a href=#3162 id=3162 data-nosnippet>3162</a>        <span class="kw">let </span>scan = reader.read(tasks).unwrap();
+<a href=#3163 id=3163 data-nosnippet>3163</a>        <span class="kw">let </span>metrics = scan.metrics().clone();
+<a href=#3164 id=3164 data-nosnippet>3164</a>        <span class="kw">let </span>batches = scan.stream().try_collect().<span class="kw">await</span>.unwrap();
+<a href=#3165 id=3165 data-nosnippet>3165</a>        (batches, metrics.bytes_read())
+<a href=#3166 id=3166 data-nosnippet>3166</a>    }
+<a href=#3167 id=3167 data-nosnippet>3167</a>
+<a href=#3168 id=3168 data-nosnippet>3168</a>    <span class="attr">#[tokio::test]
+<a href=#3169 id=3169 data-nosnippet>3169</a>    </span><span class="kw">async fn </span>test_pos_only_projection_reads_no_data_columns() {
+<a href=#3170 id=3170 data-nosnippet>3170</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3171 id=3171 data-nosnippet>3171</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3172 id=3172 data-nosnippet>3172</a>
+<a href=#3173 id=3173 data-nosnippet>3173</a>        <span class="kw">let </span>pos_only = metadata_projection_task(
+<a href=#3174 id=3174 data-nosnippet>3174</a>            write_parquet_with_wide_column(dir, <span class="string">"pos_only.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
+<a href=#3175 id=3175 data-nosnippet>3175</a>            id_and_wide_schema(),
+<a href=#3176 id=3176 data-nosnippet>3176</a>            <span class="macro">vec!</span>[RESERVED_FIELD_ID_POS],
+<a href=#3177 id=3177 data-nosnippet>3177</a>        );
+<a href=#3178 id=3178 data-nosnippet>3178</a>        <span class="kw">let </span>(batches, pos_only_bytes) = scan_task(pos_only).<span class="kw">await</span>;
+<a href=#3179 id=3179 data-nosnippet>3179</a>
+<a href=#3180 id=3180 data-nosnippet>3180</a>        <span class="comment">// Only `_pos` is materialized -- no data columns.
+<a href=#3181 id=3181 data-nosnippet>3181</a>        </span><span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">1</span>);
+<a href=#3182 id=3182 data-nosnippet>3182</a>        <span class="kw">let </span>pos_col = batches[<span class="number">0</span>]
+<a href=#3183 id=3183 data-nosnippet>3183</a>            .column_by_name(RESERVED_COL_NAME_POS)
+<a href=#3184 id=3184 data-nosnippet>3184</a>            .expect(<span class="string">"_pos column should be present"</span>)
+<a href=#3185 id=3185 data-nosnippet>3185</a>            .as_primitive::&lt;arrow_array::types::Int64Type&gt;();
+<a href=#3186 id=3186 data-nosnippet>3186</a>        <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&amp;</span>[<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]);
+<a href=#3187 id=3187 data-nosnippet>3187</a>
+<a href=#3188 id=3188 data-nosnippet>3188</a>        <span class="comment">// A scan of the same-shaped file that also projects the wide data column must read
+<a href=#3189 id=3189 data-nosnippet>3189</a>        // materially more, proving the wide column chunk was not fetched above.
+<a href=#3190 id=3190 data-nosnippet>3190</a>        </span><span class="kw">let </span>with_data = metadata_projection_task(
+<a href=#3191 id=3191 data-nosnippet>3191</a>            write_parquet_with_wide_column(dir, <span class="string">"pos_only_ref.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
+<a href=#3192 id=3192 data-nosnippet>3192</a>            id_and_wide_schema(),
+<a href=#3193 id=3193 data-nosnippet>3193</a>            <span class="macro">vec!</span>[<span class="number">2</span>, RESERVED_FIELD_ID_POS],
+<a href=#3194 id=3194 data-nosnippet>3194</a>        );
+<a href=#3195 id=3195 data-nosnippet>3195</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
+<a href=#3196 id=3196 data-nosnippet>3196</a>
+<a href=#3197 id=3197 data-nosnippet>3197</a>        <span class="macro">assert!</span>(
+<a href=#3198 id=3198 data-nosnippet>3198</a>            pos_only_bytes &lt; with_data_bytes,
+<a href=#3199 id=3199 data-nosnippet>3199</a>            <span class="string">"_pos-only scan should read fewer bytes than a scan of the wide column: \
+<a href=#3200 id=3200 data-nosnippet>3200</a>             {pos_only_bytes} vs {with_data_bytes}"
+<a href=#3201 id=3201 data-nosnippet>3201</a>        </span>);
+<a href=#3202 id=3202 data-nosnippet>3202</a>    }
+<a href=#3203 id=3203 data-nosnippet>3203</a>
+<a href=#3204 id=3204 data-nosnippet>3204</a>    <span class="attr">#[tokio::test]
+<a href=#3205 id=3205 data-nosnippet>3205</a>    </span><span class="kw">async fn </span>test_pos_only_projection_keeps_absolute_pos_under_predicate() {
+<a href=#3206 id=3206 data-nosnippet>3206</a>        <span class="kw">use </span><span class="kw">crate</span>::expr::{Bind, Reference};
+<a href=#3207 id=3207 data-nosnippet>3207</a>        <span class="kw">use </span><span class="kw">crate</span>::spec::Datum;
+<a href=#3208 id=3208 data-nosnippet>3208</a>
+<a href=#3209 id=3209 data-nosnippet>3209</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3210 id=3210 data-nosnippet>3210</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3211 id=3211 data-nosnippet>3211</a>        <span class="comment">// id = [1, 2, 3]; drop the middle physical row via a predicate + row selection.
+<a href=#3212 id=3212 data-nosnippet>3212</a>        </span><span class="kw">let </span>file_path = write_plain_parquet(dir, <span class="string">"pos_only_predicate.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]);
+<a href=#3213 id=3213 data-nosnippet>3213</a>
+<a href=#3214 id=3214 data-nosnippet>3214</a>        <span class="kw">let </span>schema = Arc::new(
+<a href=#3215 id=3215 data-nosnippet>3215</a>            Schema::builder()
+<a href=#3216 id=3216 data-nosnippet>3216</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#3217 id=3217 data-nosnippet>3217</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#3218 id=3218 data-nosnippet>3218</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
+<a href=#3219 id=3219 data-nosnippet>3219</a>                ])
+<a href=#3220 id=3220 data-nosnippet>3220</a>                .build()
+<a href=#3221 id=3221 data-nosnippet>3221</a>                .unwrap(),
+<a href=#3222 id=3222 data-nosnippet>3222</a>        );
+<a href=#3223 id=3223 data-nosnippet>3223</a>        <span class="kw">let </span>bound = Reference::new(<span class="string">"id"</span>)
+<a href=#3224 id=3224 data-nosnippet>3224</a>            .not_equal_to(Datum::int(<span class="number">2</span>))
+<a href=#3225 id=3225 data-nosnippet>3225</a>            .bind(Arc::clone(<span class="kw-2">&amp;</span>schema), <span class="bool-val">false</span>)
+<a href=#3226 id=3226 data-nosnippet>3226</a>            .unwrap();
+<a href=#3227 id=3227 data-nosnippet>3227</a>        <span class="kw">let </span>task = FileScanTask::builder()
+<a href=#3228 id=3228 data-nosnippet>3228</a>            .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
+<a href=#3229 id=3229 data-nosnippet>3229</a>            .with_start(<span class="number">0</span>)
+<a href=#3230 id=3230 data-nosnippet>3230</a>            .with_length(<span class="number">0</span>)
+<a href=#3231 id=3231 data-nosnippet>3231</a>            .with_data_file_path(file_path)
+<a href=#3232 id=3232 data-nosnippet>3232</a>            .with_data_file_format(DataFileFormat::Parquet)
+<a href=#3233 id=3233 data-nosnippet>3233</a>            .with_schema(schema)
+<a href=#3234 id=3234 data-nosnippet>3234</a>            .with_project_field_ids(<span class="macro">vec!</span>[RESERVED_FIELD_ID_POS])
+<a href=#3235 id=3235 data-nosnippet>3235</a>            .with_predicate(<span class="prelude-val">Some</span>(bound))
+<a href=#3236 id=3236 data-nosnippet>3236</a>            .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#3237 id=3237 data-nosnippet>3237</a>            .build()
+<a href=#3238 id=3238 data-nosnippet>3238</a>            .unwrap();
+<a href=#3239 id=3239 data-nosnippet>3239</a>
+<a href=#3240 id=3240 data-nosnippet>3240</a>        <span class="comment">// Row selection must be enabled for the predicate to filter rows. The row filter
+<a href=#3241 id=3241 data-nosnippet>3241</a>        // reads `id` for its own evaluation even though `id` is not projected; the surviving
+<a href=#3242 id=3242 data-nosnippet>3242</a>        // rows must keep their ABSOLUTE positions (0 and 2), not renumbered (0 and 1).
+<a href=#3243 id=3243 data-nosnippet>3243</a>        </span><span class="kw">let </span>reader = ArrowReaderBuilder::new(FileIO::new_with_fs(), Runtime::current())
+<a href=#3244 id=3244 data-nosnippet>3244</a>            .with_row_selection_enabled(<span class="bool-val">true</span>)
+<a href=#3245 id=3245 data-nosnippet>3245</a>            .build();
+<a href=#3246 id=3246 data-nosnippet>3246</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
+<a href=#3247 id=3247 data-nosnippet>3247</a>        <span class="kw">let </span>batches: Vec&lt;RecordBatch&gt; = reader
+<a href=#3248 id=3248 data-nosnippet>3248</a>            .read(tasks)
+<a href=#3249 id=3249 data-nosnippet>3249</a>            .unwrap()
+<a href=#3250 id=3250 data-nosnippet>3250</a>            .stream()
+<a href=#3251 id=3251 data-nosnippet>3251</a>            .try_collect()
+<a href=#3252 id=3252 data-nosnippet>3252</a>            .<span class="kw">await
+<a href=#3253 id=3253 data-nosnippet>3253</a>            </span>.unwrap();
+<a href=#3254 id=3254 data-nosnippet>3254</a>
+<a href=#3255 id=3255 data-nosnippet>3255</a>        <span class="kw">let </span>pos: Vec&lt;i64&gt; = batches
+<a href=#3256 id=3256 data-nosnippet>3256</a>            .iter()
+<a href=#3257 id=3257 data-nosnippet>3257</a>            .flat_map(|b| {
+<a href=#3258 id=3258 data-nosnippet>3258</a>                b.column_by_name(RESERVED_COL_NAME_POS)
+<a href=#3259 id=3259 data-nosnippet>3259</a>                    .expect(<span class="string">"_pos column should be present"</span>)
+<a href=#3260 id=3260 data-nosnippet>3260</a>                    .as_primitive::&lt;arrow_array::types::Int64Type&gt;()
+<a href=#3261 id=3261 data-nosnippet>3261</a>                    .values()
+<a href=#3262 id=3262 data-nosnippet>3262</a>                    .to_vec()
+<a href=#3263 id=3263 data-nosnippet>3263</a>            })
+<a href=#3264 id=3264 data-nosnippet>3264</a>            .collect();
+<a href=#3265 id=3265 data-nosnippet>3265</a>        <span class="macro">assert_eq!</span>(pos, <span class="macro">vec!</span>[<span class="number">0</span>, <span class="number">2</span>]);
+<a href=#3266 id=3266 data-nosnippet>3266</a>    }
+<a href=#3267 id=3267 data-nosnippet>3267</a>
+<a href=#3268 id=3268 data-nosnippet>3268</a>    <span class="attr">#[tokio::test]
+<a href=#3269 id=3269 data-nosnippet>3269</a>    </span><span class="kw">async fn </span>test_pos_and_file_projection() {
+<a href=#3270 id=3270 data-nosnippet>3270</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::RESERVED_COL_NAME_FILE;
+<a href=#3271 id=3271 data-nosnippet>3271</a>
+<a href=#3272 id=3272 data-nosnippet>3272</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3273 id=3273 data-nosnippet>3273</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3274 id=3274 data-nosnippet>3274</a>        <span class="comment">// The motivating row-lineage shape: a synthesized position column (mask -&gt; none)
+<a href=#3275 id=3275 data-nosnippet>3275</a>        // alongside a materialized per-file constant.
+<a href=#3276 id=3276 data-nosnippet>3276</a>        </span><span class="kw">let </span>file_path = write_parquet_with_wide_column(dir, <span class="string">"pos_and_file.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]);
+<a href=#3277 id=3277 data-nosnippet>3277</a>        <span class="kw">let </span>task = metadata_projection_task(file_path.clone(), id_and_wide_schema(), <span class="macro">vec!</span>[
+<a href=#3278 id=3278 data-nosnippet>3278</a>            RESERVED_FIELD_ID_POS,
+<a href=#3279 id=3279 data-nosnippet>3279</a>            RESERVED_FIELD_ID_FILE,
+<a href=#3280 id=3280 data-nosnippet>3280</a>        ]);
+<a href=#3281 id=3281 data-nosnippet>3281</a>        <span class="kw">let </span>(batches, <span class="kw">_</span>) = scan_task(task).<span class="kw">await</span>;
+<a href=#3282 id=3282 data-nosnippet>3282</a>
+<a href=#3283 id=3283 data-nosnippet>3283</a>        <span class="comment">// Both metadata columns materialize; no data column is read.
+<a href=#3284 id=3284 data-nosnippet>3284</a>        </span><span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">2</span>);
+<a href=#3285 id=3285 data-nosnippet>3285</a>        <span class="kw">let </span>pos_col = batches[<span class="number">0</span>]
+<a href=#3286 id=3286 data-nosnippet>3286</a>            .column_by_name(RESERVED_COL_NAME_POS)
+<a href=#3287 id=3287 data-nosnippet>3287</a>            .expect(<span class="string">"_pos column should be present"</span>)
+<a href=#3288 id=3288 data-nosnippet>3288</a>            .as_primitive::&lt;arrow_array::types::Int64Type&gt;();
+<a href=#3289 id=3289 data-nosnippet>3289</a>        <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&amp;</span>[<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]);
+<a href=#3290 id=3290 data-nosnippet>3290</a>        <span class="kw">let </span>file_col = batches[<span class="number">0</span>]
+<a href=#3291 id=3291 data-nosnippet>3291</a>            .column_by_name(RESERVED_COL_NAME_FILE)
+<a href=#3292 id=3292 data-nosnippet>3292</a>            .expect(<span class="string">"_file column should be present"</span>);
+<a href=#3293 id=3293 data-nosnippet>3293</a>        <span class="kw">let </span>file_col = cast(file_col, <span class="kw-2">&amp;</span>DataType::Utf8).unwrap();
+<a href=#3294 id=3294 data-nosnippet>3294</a>        <span class="kw">let </span>file_col = file_col.as_any().downcast_ref::&lt;StringArray&gt;().unwrap();
+<a href=#3295 id=3295 data-nosnippet>3295</a>        <span class="macro">assert_eq!</span>(file_col.value(<span class="number">0</span>), file_path);
+<a href=#3296 id=3296 data-nosnippet>3296</a>    }
+<a href=#3297 id=3297 data-nosnippet>3297</a>
+<a href=#3298 id=3298 data-nosnippet>3298</a>    <span class="attr">#[tokio::test]
+<a href=#3299 id=3299 data-nosnippet>3299</a>    </span><span class="kw">async fn </span>test_pos_and_physical_seq_projection_reads_only_the_leaf() {
+<a href=#3300 id=3300 data-nosnippet>3300</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::{
+<a href=#3301 id=3301 data-nosnippet>3301</a>            RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3302 id=3302 data-nosnippet>3302</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3303 id=3303 data-nosnippet>3303</a>        };
+<a href=#3304 id=3304 data-nosnippet>3304</a>
+<a href=#3305 id=3305 data-nosnippet>3305</a>        <span class="comment">// A v3 rewrite that carried rows forward stores `_last_updated_sequence_number`
+<a href=#3306 id=3306 data-nosnippet>3306</a>        // per-row. Projecting only `_pos` + the sequence column must read just that one
+<a href=#3307 id=3307 data-nosnippet>3307</a>        // physical leaf, not every data column.
+<a href=#3308 id=3308 data-nosnippet>3308</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3309 id=3309 data-nosnippet>3309</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3310 id=3310 data-nosnippet>3310</a>
+<a href=#3311 id=3311 data-nosnippet>3311</a>        <span class="comment">// File: id (1), wide data column (2), physical _last_updated_sequence_number.
+<a href=#3312 id=3312 data-nosnippet>3312</a>        </span><span class="kw">let </span>write = |name: <span class="kw-2">&amp;</span>str| {
+<a href=#3313 id=3313 data-nosnippet>3313</a>            write_parquet_with_wide_column(
+<a href=#3314 id=3314 data-nosnippet>3314</a>                dir,
+<a href=#3315 id=3315 data-nosnippet>3315</a>                name,
+<a href=#3316 id=3316 data-nosnippet>3316</a>                <span class="macro">vec!</span>[physical_last_updated_seq_field()],
+<a href=#3317 id=3317 data-nosnippet>3317</a>                <span class="macro">vec!</span>[Arc::new(Int64Array::from(<span class="macro">vec!</span>[<span class="prelude-val">Some</span>(<span class="number">5</span>), <span class="prelude-val">None</span>, <span class="prelude-val">Some</span>(<span class="number">8</span>)])) <span class="kw">as </span>ArrayRef],
+<a href=#3318 id=3318 data-nosnippet>3318</a>            )
+<a href=#3319 id=3319 data-nosnippet>3319</a>        };
+<a href=#3320 id=3320 data-nosnippet>3320</a>
+<a href=#3321 id=3321 data-nosnippet>3321</a>        <span class="kw">let </span>seq_task = |path: String, ids: Vec&lt;i32&gt;| {
+<a href=#3322 id=3322 data-nosnippet>3322</a>            FileScanTask::builder()
+<a href=#3323 id=3323 data-nosnippet>3323</a>                .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>path).unwrap().len())
+<a href=#3324 id=3324 data-nosnippet>3324</a>                .with_start(<span class="number">0</span>)
+<a href=#3325 id=3325 data-nosnippet>3325</a>                .with_length(<span class="number">0</span>)
+<a href=#3326 id=3326 data-nosnippet>3326</a>                .with_data_file_path(path)
+<a href=#3327 id=3327 data-nosnippet>3327</a>                .with_data_file_format(DataFileFormat::Parquet)
+<a href=#3328 id=3328 data-nosnippet>3328</a>                .with_schema(id_and_wide_schema())
+<a href=#3329 id=3329 data-nosnippet>3329</a>                .with_project_field_ids(ids)
+<a href=#3330 id=3330 data-nosnippet>3330</a>                .with_first_row_id(<span class="prelude-val">Some</span>(<span class="number">100</span>))
+<a href=#3331 id=3331 data-nosnippet>3331</a>                .with_data_sequence_number(<span class="prelude-val">Some</span>(<span class="number">9</span>))
+<a href=#3332 id=3332 data-nosnippet>3332</a>                .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#3333 id=3333 data-nosnippet>3333</a>                .build()
+<a href=#3334 id=3334 data-nosnippet>3334</a>                .unwrap()
+<a href=#3335 id=3335 data-nosnippet>3335</a>        };
+<a href=#3336 id=3336 data-nosnippet>3336</a>
+<a href=#3337 id=3337 data-nosnippet>3337</a>        <span class="kw">let </span>meta_only = seq_task(write(<span class="string">"pos_seq.parquet"</span>), <span class="macro">vec!</span>[
+<a href=#3338 id=3338 data-nosnippet>3338</a>            RESERVED_FIELD_ID_POS,
+<a href=#3339 id=3339 data-nosnippet>3339</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3340 id=3340 data-nosnippet>3340</a>        ]);
+<a href=#3341 id=3341 data-nosnippet>3341</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
+<a href=#3342 id=3342 data-nosnippet>3342</a>
+<a href=#3343 id=3343 data-nosnippet>3343</a>        <span class="comment">// `_pos` and the coalesced sequence column materialize; the wide column does not.
+<a href=#3344 id=3344 data-nosnippet>3344</a>        </span><span class="kw">let </span>pos_col = batches[<span class="number">0</span>]
+<a href=#3345 id=3345 data-nosnippet>3345</a>            .column_by_name(RESERVED_COL_NAME_POS)
+<a href=#3346 id=3346 data-nosnippet>3346</a>            .expect(<span class="string">"_pos column should be present"</span>)
+<a href=#3347 id=3347 data-nosnippet>3347</a>            .as_primitive::&lt;arrow_array::types::Int64Type&gt;();
+<a href=#3348 id=3348 data-nosnippet>3348</a>        <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&amp;</span>[<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]);
+<a href=#3349 id=3349 data-nosnippet>3349</a>        <span class="kw">let </span>seq_col = batches[<span class="number">0</span>]
+<a href=#3350 id=3350 data-nosnippet>3350</a>            .column_by_name(RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER)
+<a href=#3351 id=3351 data-nosnippet>3351</a>            .expect(<span class="string">"_last_updated_sequence_number column should be present"</span>);
+<a href=#3352 id=3352 data-nosnippet>3352</a>        <span class="kw">let </span>seq_col = cast(seq_col, <span class="kw-2">&amp;</span>DataType::Int64).unwrap();
+<a href=#3353 id=3353 data-nosnippet>3353</a>        <span class="kw">let </span>seq_col = seq_col.as_any().downcast_ref::&lt;Int64Array&gt;().unwrap();
+<a href=#3354 id=3354 data-nosnippet>3354</a>        <span class="comment">// Per-row stored value where non-null, else the data sequence number (9).
+<a href=#3355 id=3355 data-nosnippet>3355</a>        </span><span class="macro">assert_eq!</span>(seq_col.value(<span class="number">0</span>), <span class="number">5</span>);
+<a href=#3356 id=3356 data-nosnippet>3356</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">1</span>), <span class="number">9</span>);
+<a href=#3357 id=3357 data-nosnippet>3357</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">2</span>), <span class="number">8</span>);
+<a href=#3358 id=3358 data-nosnippet>3358</a>        <span class="macro">assert!</span>(batches[<span class="number">0</span>].column_by_name(<span class="string">"wide"</span>).is_none());
+<a href=#3359 id=3359 data-nosnippet>3359</a>
+<a href=#3360 id=3360 data-nosnippet>3360</a>        <span class="comment">// A scan that also projects the wide data column must read materially more,
+<a href=#3361 id=3361 data-nosnippet>3361</a>        // proving the metadata-only scan pruned to just the sequence leaf.
+<a href=#3362 id=3362 data-nosnippet>3362</a>        </span><span class="kw">let </span>with_data = seq_task(write(<span class="string">"pos_seq_ref.parquet"</span>), <span class="macro">vec!</span>[
+<a href=#3363 id=3363 data-nosnippet>3363</a>            <span class="number">2</span>,
+<a href=#3364 id=3364 data-nosnippet>3364</a>            RESERVED_FIELD_ID_POS,
+<a href=#3365 id=3365 data-nosnippet>3365</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3366 id=3366 data-nosnippet>3366</a>        ]);
+<a href=#3367 id=3367 data-nosnippet>3367</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
+<a href=#3368 id=3368 data-nosnippet>3368</a>
+<a href=#3369 id=3369 data-nosnippet>3369</a>        <span class="macro">assert!</span>(
+<a href=#3370 id=3370 data-nosnippet>3370</a>            meta_only_bytes &lt; with_data_bytes,
+<a href=#3371 id=3371 data-nosnippet>3371</a>            <span class="string">"_pos + physical sequence scan should read fewer bytes than one that also \
+<a href=#3372 id=3372 data-nosnippet>3372</a>             reads the wide column: {meta_only_bytes} vs {with_data_bytes}"
+<a href=#3373 id=3373 data-nosnippet>3373</a>        </span>);
+<a href=#3374 id=3374 data-nosnippet>3374</a>    }
+<a href=#3375 id=3375 data-nosnippet>3375</a>
+<a href=#3376 id=3376 data-nosnippet>3376</a>    <span class="attr">#[tokio::test]
+<a href=#3377 id=3377 data-nosnippet>3377</a>    </span><span class="kw">async fn </span>test_seq_only_projection_reads_only_the_leaf() {
+<a href=#3378 id=3378 data-nosnippet>3378</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::{
+<a href=#3379 id=3379 data-nosnippet>3379</a>            RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3380 id=3380 data-nosnippet>3380</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3381 id=3381 data-nosnippet>3381</a>        };
+<a href=#3382 id=3382 data-nosnippet>3382</a>
+<a href=#3383 id=3383 data-nosnippet>3383</a>        <span class="comment">// `_last_updated_sequence_number` alone (no `_pos`, no data column). This is a
+<a href=#3384 id=3384 data-nosnippet>3384</a>        // metadata-only projection, so RowNumber drives the `none()` downgrade and the
+<a href=#3385 id=3385 data-nosnippet>3385</a>        // physical leaf is unioned back in, pruning the read to just that leaf.
+<a href=#3386 id=3386 data-nosnippet>3386</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3387 id=3387 data-nosnippet>3387</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3388 id=3388 data-nosnippet>3388</a>        <span class="kw">let </span>write = |name: <span class="kw-2">&amp;</span>str| {
+<a href=#3389 id=3389 data-nosnippet>3389</a>            write_parquet_with_wide_column(
+<a href=#3390 id=3390 data-nosnippet>3390</a>                dir,
+<a href=#3391 id=3391 data-nosnippet>3391</a>                name,
+<a href=#3392 id=3392 data-nosnippet>3392</a>                <span class="macro">vec!</span>[physical_last_updated_seq_field()],
+<a href=#3393 id=3393 data-nosnippet>3393</a>                <span class="macro">vec!</span>[Arc::new(Int64Array::from(<span class="macro">vec!</span>[<span class="prelude-val">Some</span>(<span class="number">5</span>), <span class="prelude-val">None</span>, <span class="prelude-val">Some</span>(<span class="number">8</span>)])) <span class="kw">as </span>ArrayRef],
+<a href=#3394 id=3394 data-nosnippet>3394</a>            )
+<a href=#3395 id=3395 data-nosnippet>3395</a>        };
+<a href=#3396 id=3396 data-nosnippet>3396</a>        <span class="kw">let </span>seq_task = |path: String, ids: Vec&lt;i32&gt;| {
+<a href=#3397 id=3397 data-nosnippet>3397</a>            FileScanTask::builder()
+<a href=#3398 id=3398 data-nosnippet>3398</a>                .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>path).unwrap().len())
+<a href=#3399 id=3399 data-nosnippet>3399</a>                .with_start(<span class="number">0</span>)
+<a href=#3400 id=3400 data-nosnippet>3400</a>                .with_length(<span class="number">0</span>)
+<a href=#3401 id=3401 data-nosnippet>3401</a>                .with_data_file_path(path)
+<a href=#3402 id=3402 data-nosnippet>3402</a>                .with_data_file_format(DataFileFormat::Parquet)
+<a href=#3403 id=3403 data-nosnippet>3403</a>                .with_schema(id_and_wide_schema())
+<a href=#3404 id=3404 data-nosnippet>3404</a>                .with_project_field_ids(ids)
+<a href=#3405 id=3405 data-nosnippet>3405</a>                .with_first_row_id(<span class="prelude-val">Some</span>(<span class="number">100</span>))
+<a href=#3406 id=3406 data-nosnippet>3406</a>                .with_data_sequence_number(<span class="prelude-val">Some</span>(<span class="number">9</span>))
+<a href=#3407 id=3407 data-nosnippet>3407</a>                .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#3408 id=3408 data-nosnippet>3408</a>                .build()
+<a href=#3409 id=3409 data-nosnippet>3409</a>                .unwrap()
+<a href=#3410 id=3410 data-nosnippet>3410</a>        };
+<a href=#3411 id=3411 data-nosnippet>3411</a>
+<a href=#3412 id=3412 data-nosnippet>3412</a>        <span class="kw">let </span>meta_only = seq_task(write(<span class="string">"seq_only.parquet"</span>), <span class="macro">vec!</span>[
+<a href=#3413 id=3413 data-nosnippet>3413</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3414 id=3414 data-nosnippet>3414</a>        ]);
+<a href=#3415 id=3415 data-nosnippet>3415</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
+<a href=#3416 id=3416 data-nosnippet>3416</a>
+<a href=#3417 id=3417 data-nosnippet>3417</a>        <span class="kw">let </span>seq_col = batches[<span class="number">0</span>]
+<a href=#3418 id=3418 data-nosnippet>3418</a>            .column_by_name(RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER)
+<a href=#3419 id=3419 data-nosnippet>3419</a>            .expect(<span class="string">"_last_updated_sequence_number column should be present"</span>);
+<a href=#3420 id=3420 data-nosnippet>3420</a>        <span class="kw">let </span>seq_col = cast(seq_col, <span class="kw-2">&amp;</span>DataType::Int64).unwrap();
+<a href=#3421 id=3421 data-nosnippet>3421</a>        <span class="kw">let </span>seq_col = seq_col.as_any().downcast_ref::&lt;Int64Array&gt;().unwrap();
+<a href=#3422 id=3422 data-nosnippet>3422</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">0</span>), <span class="number">5</span>);
+<a href=#3423 id=3423 data-nosnippet>3423</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">1</span>), <span class="number">9</span>);
+<a href=#3424 id=3424 data-nosnippet>3424</a>        <span class="macro">assert_eq!</span>(seq_col.value(<span class="number">2</span>), <span class="number">8</span>);
+<a href=#3425 id=3425 data-nosnippet>3425</a>        <span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
+<a href=#3426 id=3426 data-nosnippet>3426</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
+<a href=#3427 id=3427 data-nosnippet>3427</a>        <span class="macro">assert!</span>(batches[<span class="number">0</span>].column_by_name(<span class="string">"wide"</span>).is_none());
+<a href=#3428 id=3428 data-nosnippet>3428</a>
+<a href=#3429 id=3429 data-nosnippet>3429</a>        <span class="kw">let </span>with_data = seq_task(write(<span class="string">"seq_only_ref.parquet"</span>), <span class="macro">vec!</span>[
+<a href=#3430 id=3430 data-nosnippet>3430</a>            <span class="number">2</span>,
+<a href=#3431 id=3431 data-nosnippet>3431</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3432 id=3432 data-nosnippet>3432</a>        ]);
+<a href=#3433 id=3433 data-nosnippet>3433</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
+<a href=#3434 id=3434 data-nosnippet>3434</a>
+<a href=#3435 id=3435 data-nosnippet>3435</a>        <span class="macro">assert!</span>(
+<a href=#3436 id=3436 data-nosnippet>3436</a>            meta_only_bytes &lt; with_data_bytes,
+<a href=#3437 id=3437 data-nosnippet>3437</a>            <span class="string">"seq-only scan should read fewer bytes than one that also reads the wide \
+<a href=#3438 id=3438 data-nosnippet>3438</a>             column: {meta_only_bytes} vs {with_data_bytes}"
+<a href=#3439 id=3439 data-nosnippet>3439</a>        </span>);
+<a href=#3440 id=3440 data-nosnippet>3440</a>    }
+<a href=#3441 id=3441 data-nosnippet>3441</a>
+<a href=#3442 id=3442 data-nosnippet>3442</a>    <span class="attr">#[tokio::test]
+<a href=#3443 id=3443 data-nosnippet>3443</a>    </span><span class="kw">async fn </span>test_seq_only_null_first_row_id_reads_no_data_columns() {
+<a href=#3444 id=3444 data-nosnippet>3444</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::{
+<a href=#3445 id=3445 data-nosnippet>3445</a>            RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3446 id=3446 data-nosnippet>3446</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3447 id=3447 data-nosnippet>3447</a>        };
+<a href=#3448 id=3448 data-nosnippet>3448</a>
+<a href=#3449 id=3449 data-nosnippet>3449</a>        <span class="comment">// Seq-only projection with a null first_row_id: the column is nulled and the
+<a href=#3450 id=3450 data-nosnippet>3450</a>        // physical leaf is NOT read (the gated `coalesce_last_updated_seq_leaf` is None).
+<a href=#3451 id=3451 data-nosnippet>3451</a>        // This is a metadata-only projection, so RowNumber supplies the row count and the
+<a href=#3452 id=3452 data-nosnippet>3452</a>        // data columns are pruned; the row count is still 3 and the values all null.
+<a href=#3453 id=3453 data-nosnippet>3453</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3454 id=3454 data-nosnippet>3454</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3455 id=3455 data-nosnippet>3455</a>        <span class="kw">let </span>seq_task = |name: <span class="kw-2">&amp;</span>str, ids: Vec&lt;i32&gt;| {
+<a href=#3456 id=3456 data-nosnippet>3456</a>            <span class="kw">let </span>file_path = write_parquet_with_wide_column(
+<a href=#3457 id=3457 data-nosnippet>3457</a>                dir,
+<a href=#3458 id=3458 data-nosnippet>3458</a>                name,
+<a href=#3459 id=3459 data-nosnippet>3459</a>                <span class="macro">vec!</span>[physical_last_updated_seq_field()],
+<a href=#3460 id=3460 data-nosnippet>3460</a>                <span class="macro">vec!</span>[Arc::new(Int64Array::from(<span class="macro">vec!</span>[<span class="prelude-val">Some</span>(<span class="number">5</span>), <span class="prelude-val">Some</span>(<span class="number">6</span>), <span class="prelude-val">Some</span>(<span class="number">7</span>)])) <span class="kw">as </span>ArrayRef],
+<a href=#3461 id=3461 data-nosnippet>3461</a>            );
+<a href=#3462 id=3462 data-nosnippet>3462</a>            FileScanTask::builder()
+<a href=#3463 id=3463 data-nosnippet>3463</a>                .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
+<a href=#3464 id=3464 data-nosnippet>3464</a>                .with_start(<span class="number">0</span>)
+<a href=#3465 id=3465 data-nosnippet>3465</a>                .with_length(<span class="number">0</span>)
+<a href=#3466 id=3466 data-nosnippet>3466</a>                .with_data_file_path(file_path)
+<a href=#3467 id=3467 data-nosnippet>3467</a>                .with_data_file_format(DataFileFormat::Parquet)
+<a href=#3468 id=3468 data-nosnippet>3468</a>                .with_schema(id_and_wide_schema())
+<a href=#3469 id=3469 data-nosnippet>3469</a>                .with_project_field_ids(ids)
+<a href=#3470 id=3470 data-nosnippet>3470</a>                .with_first_row_id(<span class="prelude-val">None</span>)
+<a href=#3471 id=3471 data-nosnippet>3471</a>                .with_data_sequence_number(<span class="prelude-val">Some</span>(<span class="number">9</span>))
+<a href=#3472 id=3472 data-nosnippet>3472</a>                .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#3473 id=3473 data-nosnippet>3473</a>                .build()
+<a href=#3474 id=3474 data-nosnippet>3474</a>                .unwrap()
+<a href=#3475 id=3475 data-nosnippet>3475</a>        };
+<a href=#3476 id=3476 data-nosnippet>3476</a>
+<a href=#3477 id=3477 data-nosnippet>3477</a>        <span class="kw">let </span>meta_only = seq_task(<span class="string">"seq_only_null_first.parquet"</span>, <span class="macro">vec!</span>[
+<a href=#3478 id=3478 data-nosnippet>3478</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3479 id=3479 data-nosnippet>3479</a>        ]);
+<a href=#3480 id=3480 data-nosnippet>3480</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
+<a href=#3481 id=3481 data-nosnippet>3481</a>
+<a href=#3482 id=3482 data-nosnippet>3482</a>        <span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">1</span>);
+<a href=#3483 id=3483 data-nosnippet>3483</a>        <span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
+<a href=#3484 id=3484 data-nosnippet>3484</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
+<a href=#3485 id=3485 data-nosnippet>3485</a>        <span class="kw">let </span>seq_col = batches[<span class="number">0</span>]
+<a href=#3486 id=3486 data-nosnippet>3486</a>            .column_by_name(RESERVED_COL_NAME_LAST_UPDATED_SEQUENCE_NUMBER)
+<a href=#3487 id=3487 data-nosnippet>3487</a>            .expect(<span class="string">"_last_updated_sequence_number column should be present"</span>);
+<a href=#3488 id=3488 data-nosnippet>3488</a>        <span class="kw">let </span>seq_col = cast(seq_col, <span class="kw-2">&amp;</span>DataType::Int64).unwrap();
+<a href=#3489 id=3489 data-nosnippet>3489</a>        <span class="kw">let </span>seq_col = seq_col.as_any().downcast_ref::&lt;Int64Array&gt;().unwrap();
+<a href=#3490 id=3490 data-nosnippet>3490</a>        <span class="macro">assert!</span>((<span class="number">0</span>..<span class="number">3</span>).all(|i| seq_col.is_null(i)));
+<a href=#3491 id=3491 data-nosnippet>3491</a>
+<a href=#3492 id=3492 data-nosnippet>3492</a>        <span class="kw">let </span>with_data = seq_task(<span class="string">"seq_only_null_first_ref.parquet"</span>, <span class="macro">vec!</span>[
+<a href=#3493 id=3493 data-nosnippet>3493</a>            <span class="number">2</span>,
+<a href=#3494 id=3494 data-nosnippet>3494</a>            RESERVED_FIELD_ID_LAST_UPDATED_SEQUENCE_NUMBER,
+<a href=#3495 id=3495 data-nosnippet>3495</a>        ]);
+<a href=#3496 id=3496 data-nosnippet>3496</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
+<a href=#3497 id=3497 data-nosnippet>3497</a>
+<a href=#3498 id=3498 data-nosnippet>3498</a>        <span class="macro">assert!</span>(
+<a href=#3499 id=3499 data-nosnippet>3499</a>            meta_only_bytes &lt; with_data_bytes,
+<a href=#3500 id=3500 data-nosnippet>3500</a>            <span class="string">"seq-only scan with a null first_row_id should read fewer bytes than a scan of \
+<a href=#3501 id=3501 data-nosnippet>3501</a>             the wide column: {meta_only_bytes} vs {with_data_bytes}"
+<a href=#3502 id=3502 data-nosnippet>3502</a>        </span>);
+<a href=#3503 id=3503 data-nosnippet>3503</a>    }
+<a href=#3504 id=3504 data-nosnippet>3504</a>
+<a href=#3505 id=3505 data-nosnippet>3505</a>    <span class="attr">#[tokio::test]
+<a href=#3506 id=3506 data-nosnippet>3506</a>    </span><span class="kw">async fn </span>test_file_only_reads_no_data_columns() {
+<a href=#3507 id=3507 data-nosnippet>3507</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::RESERVED_COL_NAME_FILE;
+<a href=#3508 id=3508 data-nosnippet>3508</a>
+<a href=#3509 id=3509 data-nosnippet>3509</a>        <span class="comment">// `SELECT _file` materializes a per-file string constant with no physical column to
+<a href=#3510 id=3510 data-nosnippet>3510</a>        // read. The metadata-only RowNumber counter sizes it, so the scan prunes the data
+<a href=#3511 id=3511 data-nosnippet>3511</a>        // columns rather than reading them all just to recover the row count.
+<a href=#3512 id=3512 data-nosnippet>3512</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3513 id=3513 data-nosnippet>3513</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3514 id=3514 data-nosnippet>3514</a>
+<a href=#3515 id=3515 data-nosnippet>3515</a>        <span class="kw">let </span>file_path = write_parquet_with_wide_column(dir, <span class="string">"file_only.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]);
+<a href=#3516 id=3516 data-nosnippet>3516</a>        <span class="kw">let </span>meta_only = metadata_projection_task(file_path.clone(), id_and_wide_schema(), <span class="macro">vec!</span>[
+<a href=#3517 id=3517 data-nosnippet>3517</a>            RESERVED_FIELD_ID_FILE,
+<a href=#3518 id=3518 data-nosnippet>3518</a>        ]);
+<a href=#3519 id=3519 data-nosnippet>3519</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
+<a href=#3520 id=3520 data-nosnippet>3520</a>
+<a href=#3521 id=3521 data-nosnippet>3521</a>        <span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">1</span>);
+<a href=#3522 id=3522 data-nosnippet>3522</a>        <span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
+<a href=#3523 id=3523 data-nosnippet>3523</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
+<a href=#3524 id=3524 data-nosnippet>3524</a>        <span class="kw">let </span>file_col = batches[<span class="number">0</span>]
+<a href=#3525 id=3525 data-nosnippet>3525</a>            .column_by_name(RESERVED_COL_NAME_FILE)
+<a href=#3526 id=3526 data-nosnippet>3526</a>            .expect(<span class="string">"_file column should be present"</span>);
+<a href=#3527 id=3527 data-nosnippet>3527</a>        <span class="kw">let </span>file_col = cast(file_col, <span class="kw-2">&amp;</span>DataType::Utf8).unwrap();
+<a href=#3528 id=3528 data-nosnippet>3528</a>        <span class="kw">let </span>file_col = file_col.as_any().downcast_ref::&lt;StringArray&gt;().unwrap();
+<a href=#3529 id=3529 data-nosnippet>3529</a>        <span class="macro">assert_eq!</span>(file_col.value(<span class="number">0</span>), file_path);
+<a href=#3530 id=3530 data-nosnippet>3530</a>
+<a href=#3531 id=3531 data-nosnippet>3531</a>        <span class="kw">let </span>with_data = metadata_projection_task(
+<a href=#3532 id=3532 data-nosnippet>3532</a>            write_parquet_with_wide_column(dir, <span class="string">"file_only_ref.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
+<a href=#3533 id=3533 data-nosnippet>3533</a>            id_and_wide_schema(),
+<a href=#3534 id=3534 data-nosnippet>3534</a>            <span class="macro">vec!</span>[<span class="number">2</span>, RESERVED_FIELD_ID_FILE],
+<a href=#3535 id=3535 data-nosnippet>3535</a>        );
+<a href=#3536 id=3536 data-nosnippet>3536</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
+<a href=#3537 id=3537 data-nosnippet>3537</a>
+<a href=#3538 id=3538 data-nosnippet>3538</a>        <span class="macro">assert!</span>(
+<a href=#3539 id=3539 data-nosnippet>3539</a>            meta_only_bytes &lt; with_data_bytes,
+<a href=#3540 id=3540 data-nosnippet>3540</a>            <span class="string">"_file-only scan should read fewer bytes than a scan of the wide column: \
+<a href=#3541 id=3541 data-nosnippet>3541</a>             {meta_only_bytes} vs {with_data_bytes}"
+<a href=#3542 id=3542 data-nosnippet>3542</a>        </span>);
+<a href=#3543 id=3543 data-nosnippet>3543</a>    }
+<a href=#3544 id=3544 data-nosnippet>3544</a>
+<a href=#3545 id=3545 data-nosnippet>3545</a>    <span class="attr">#[tokio::test]
+<a href=#3546 id=3546 data-nosnippet>3546</a>    </span><span class="kw">async fn </span>test_spec_id_only_reads_no_data_columns() {
+<a href=#3547 id=3547 data-nosnippet>3547</a>        <span class="kw">use </span><span class="kw">crate</span>::metadata_columns::{RESERVED_COL_NAME_SPEC_ID, RESERVED_FIELD_ID_SPEC_ID};
+<a href=#3548 id=3548 data-nosnippet>3548</a>        <span class="kw">use </span><span class="kw">crate</span>::spec::{Literal, PartitionSpec, Struct, Transform};
+<a href=#3549 id=3549 data-nosnippet>3549</a>
+<a href=#3550 id=3550 data-nosnippet>3550</a>        <span class="comment">// `SELECT _spec_id` materializes an int constant from the task's partition spec id,
+<a href=#3551 id=3551 data-nosnippet>3551</a>        // with no physical column to read. The RowNumber counter sizes it, so the scan
+<a href=#3552 id=3552 data-nosnippet>3552</a>        // prunes the data columns rather than reading them all to recover the row count.
+<a href=#3553 id=3553 data-nosnippet>3553</a>        </span><span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3554 id=3554 data-nosnippet>3554</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3555 id=3555 data-nosnippet>3555</a>        <span class="kw">let </span>schema = id_and_wide_schema();
+<a href=#3556 id=3556 data-nosnippet>3556</a>        <span class="kw">let </span>spec = Arc::new(
+<a href=#3557 id=3557 data-nosnippet>3557</a>            PartitionSpec::builder(schema.clone())
+<a href=#3558 id=3558 data-nosnippet>3558</a>                .with_spec_id(<span class="number">7</span>)
+<a href=#3559 id=3559 data-nosnippet>3559</a>                .add_partition_field(<span class="string">"id"</span>, <span class="string">"id"</span>, Transform::Identity)
+<a href=#3560 id=3560 data-nosnippet>3560</a>                .unwrap()
+<a href=#3561 id=3561 data-nosnippet>3561</a>                .build()
+<a href=#3562 id=3562 data-nosnippet>3562</a>                .unwrap(),
+<a href=#3563 id=3563 data-nosnippet>3563</a>        );
+<a href=#3564 id=3564 data-nosnippet>3564</a>
+<a href=#3565 id=3565 data-nosnippet>3565</a>        <span class="kw">let </span>spec_id_task = |file_path: String, project_field_ids: Vec&lt;i32&gt;| {
+<a href=#3566 id=3566 data-nosnippet>3566</a>            FileScanTask::builder()
+<a href=#3567 id=3567 data-nosnippet>3567</a>                .with_file_size_in_bytes(std::fs::metadata(<span class="kw-2">&amp;</span>file_path).unwrap().len())
+<a href=#3568 id=3568 data-nosnippet>3568</a>                .with_start(<span class="number">0</span>)
+<a href=#3569 id=3569 data-nosnippet>3569</a>                .with_length(<span class="number">0</span>)
+<a href=#3570 id=3570 data-nosnippet>3570</a>                .with_data_file_path(file_path)
+<a href=#3571 id=3571 data-nosnippet>3571</a>                .with_data_file_format(DataFileFormat::Parquet)
+<a href=#3572 id=3572 data-nosnippet>3572</a>                .with_schema(schema.clone())
+<a href=#3573 id=3573 data-nosnippet>3573</a>                .with_project_field_ids(project_field_ids)
+<a href=#3574 id=3574 data-nosnippet>3574</a>                .with_partition(<span class="prelude-val">Some</span>(Struct::from_iter([<span class="prelude-val">Some</span>(Literal::int(<span class="number">42</span>))])))
+<a href=#3575 id=3575 data-nosnippet>3575</a>                .with_partition_spec(<span class="prelude-val">Some</span>(spec.clone()))
+<a href=#3576 id=3576 data-nosnippet>3576</a>                .with_case_sensitive(<span class="bool-val">false</span>)
+<a href=#3577 id=3577 data-nosnippet>3577</a>                .build()
+<a href=#3578 id=3578 data-nosnippet>3578</a>                .unwrap()
+<a href=#3579 id=3579 data-nosnippet>3579</a>        };
+<a href=#3580 id=3580 data-nosnippet>3580</a>
+<a href=#3581 id=3581 data-nosnippet>3581</a>        <span class="kw">let </span>meta_only = spec_id_task(
+<a href=#3582 id=3582 data-nosnippet>3582</a>            write_parquet_with_wide_column(dir, <span class="string">"spec_id_only.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
+<a href=#3583 id=3583 data-nosnippet>3583</a>            <span class="macro">vec!</span>[RESERVED_FIELD_ID_SPEC_ID],
+<a href=#3584 id=3584 data-nosnippet>3584</a>        );
+<a href=#3585 id=3585 data-nosnippet>3585</a>        <span class="kw">let </span>(batches, meta_only_bytes) = scan_task(meta_only).<span class="kw">await</span>;
+<a href=#3586 id=3586 data-nosnippet>3586</a>
+<a href=#3587 id=3587 data-nosnippet>3587</a>        <span class="macro">assert_eq!</span>(batches[<span class="number">0</span>].num_columns(), <span class="number">1</span>);
+<a href=#3588 id=3588 data-nosnippet>3588</a>        <span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
+<a href=#3589 id=3589 data-nosnippet>3589</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
+<a href=#3590 id=3590 data-nosnippet>3590</a>        <span class="kw">let </span>spec_id_col = batches[<span class="number">0</span>]
+<a href=#3591 id=3591 data-nosnippet>3591</a>            .column_by_name(RESERVED_COL_NAME_SPEC_ID)
+<a href=#3592 id=3592 data-nosnippet>3592</a>            .expect(<span class="string">"_spec_id column should be present"</span>);
+<a href=#3593 id=3593 data-nosnippet>3593</a>        <span class="kw">let </span>spec_id_col = cast(spec_id_col, <span class="kw-2">&amp;</span>DataType::Int32).unwrap();
+<a href=#3594 id=3594 data-nosnippet>3594</a>        <span class="kw">let </span>spec_id_col = spec_id_col.as_any().downcast_ref::&lt;Int32Array&gt;().unwrap();
+<a href=#3595 id=3595 data-nosnippet>3595</a>        <span class="macro">assert_eq!</span>(spec_id_col.values(), <span class="kw-2">&amp;</span>[<span class="number">7</span>, <span class="number">7</span>, <span class="number">7</span>]);
+<a href=#3596 id=3596 data-nosnippet>3596</a>
+<a href=#3597 id=3597 data-nosnippet>3597</a>        <span class="kw">let </span>with_data = spec_id_task(
+<a href=#3598 id=3598 data-nosnippet>3598</a>            write_parquet_with_wide_column(dir, <span class="string">"spec_id_only_ref.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]),
+<a href=#3599 id=3599 data-nosnippet>3599</a>            <span class="macro">vec!</span>[<span class="number">2</span>, RESERVED_FIELD_ID_SPEC_ID],
+<a href=#3600 id=3600 data-nosnippet>3600</a>        );
+<a href=#3601 id=3601 data-nosnippet>3601</a>        <span class="kw">let </span>(<span class="kw">_</span>, with_data_bytes) = scan_task(with_data).<span class="kw">await</span>;
+<a href=#3602 id=3602 data-nosnippet>3602</a>
+<a href=#3603 id=3603 data-nosnippet>3603</a>        <span class="macro">assert!</span>(
+<a href=#3604 id=3604 data-nosnippet>3604</a>            meta_only_bytes &lt; with_data_bytes,
+<a href=#3605 id=3605 data-nosnippet>3605</a>            <span class="string">"_spec_id-only scan should read fewer bytes than a scan of the wide column: \
+<a href=#3606 id=3606 data-nosnippet>3606</a>             {meta_only_bytes} vs {with_data_bytes}"
+<a href=#3607 id=3607 data-nosnippet>3607</a>        </span>);
+<a href=#3608 id=3608 data-nosnippet>3608</a>    }
+<a href=#3609 id=3609 data-nosnippet>3609</a>
+<a href=#3610 id=3610 data-nosnippet>3610</a>    <span class="attr">#[tokio::test]
+<a href=#3611 id=3611 data-nosnippet>3611</a>    </span><span class="kw">async fn </span>test_empty_projection_preserves_row_count() {
+<a href=#3612 id=3612 data-nosnippet>3612</a>        <span class="kw">let </span>tmp_dir = TempDir::new().unwrap();
+<a href=#3613 id=3613 data-nosnippet>3613</a>        <span class="kw">let </span>dir = tmp_dir.path().to_str().unwrap();
+<a href=#3614 id=3614 data-nosnippet>3614</a>        <span class="kw">let </span>file_path = write_plain_parquet(dir, <span class="string">"empty_projection.parquet"</span>, <span class="macro">vec!</span>[], <span class="macro">vec!</span>[]);
+<a href=#3615 id=3615 data-nosnippet>3615</a>        <span class="kw">let </span>schema = Arc::new(
+<a href=#3616 id=3616 data-nosnippet>3616</a>            Schema::builder()
+<a href=#3617 id=3617 data-nosnippet>3617</a>                .with_schema_id(<span class="number">1</span>)
+<a href=#3618 id=3618 data-nosnippet>3618</a>                .with_fields(<span class="macro">vec!</span>[
+<a href=#3619 id=3619 data-nosnippet>3619</a>                    NestedField::required(<span class="number">1</span>, <span class="string">"id"</span>, Type::Primitive(PrimitiveType::Int)).into(),
+<a href=#3620 id=3620 data-nosnippet>3620</a>                ])
+<a href=#3621 id=3621 data-nosnippet>3621</a>                .build()
+<a href=#3622 id=3622 data-nosnippet>3622</a>                .unwrap(),
+<a href=#3623 id=3623 data-nosnippet>3623</a>        );
+<a href=#3624 id=3624 data-nosnippet>3624</a>        <span class="kw">let </span>task = metadata_projection_task(file_path, schema, <span class="macro">vec!</span>[]);
+<a href=#3625 id=3625 data-nosnippet>3625</a>        <span class="kw">let </span>(batches, <span class="kw">_</span>) = scan_task(task).<span class="kw">await</span>;
+<a href=#3626 id=3626 data-nosnippet>3626</a>
+<a href=#3627 id=3627 data-nosnippet>3627</a>        <span class="comment">// A bare COUNT(*)-style empty projection must still report the row count.
+<a href=#3628 id=3628 data-nosnippet>3628</a>        </span><span class="kw">let </span>total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
+<a href=#3629 id=3629 data-nosnippet>3629</a>        <span class="macro">assert_eq!</span>(total_rows, <span class="number">3</span>);
+<a href=#3630 id=3630 data-nosnippet>3630</a>    }
+<a href=#3631 id=3631 data-nosnippet>3631</a>}
 </code></pre></div></section></main></body></html>
\ No newline at end of file
diff --git a/api/src/iceberg/arrow/reader/positional_deletes.rs.html b/api/src/iceberg/arrow/reader/positional_deletes.rs.html
index 55bb14c8a..99b09783f 100644
--- a/api/src/iceberg/arrow/reader/positional_deletes.rs.html
+++ b/api/src/iceberg/arrow/reader/positional_deletes.rs.html
@@ -1010,7 +1010,7 @@
 <a href=#1010 id=1010 data-nosnippet>1010</a>            .with_case_sensitive(<span class="bool-val">false</span>)
 <a href=#1011 id=1011 data-nosnippet>1011</a>            .build();
 <a href=#1012 id=1012 data-nosnippet>1012</a>
-<a href=#1013 id=1013 data-nosnippet>1013</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
+<a href=#1013 id=1013 data-nosnippet>1013</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[task])) <span class="kw">as </span>FileScanTaskStream;
 <a href=#1014 id=1014 data-nosnippet>1014</a>        <span class="kw">let </span>result = reader
 <a href=#1015 id=1015 data-nosnippet>1015</a>            .read(tasks)
 <a href=#1016 id=1016 data-nosnippet>1016</a>            .unwrap()
@@ -1107,7 +1107,7 @@
 <a href=#1107 id=1107 data-nosnippet>1107</a>            .with_case_sensitive(<span class="bool-val">false</span>)
 <a href=#1108 id=1108 data-nosnippet>1108</a>            .build();
 <a href=#1109 id=1109 data-nosnippet>1109</a>
-<a href=#1110 id=1110 data-nosnippet>1110</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[<span class="prelude-val">Ok</span>(task)])) <span class="kw">as </span>FileScanTaskStream;
+<a href=#1110 id=1110 data-nosnippet>1110</a>        <span class="kw">let </span>tasks = Box::pin(futures::stream::iter(<span class="macro">vec!</span>[task])) <span class="kw">as </span>FileScanTaskStream;
 <a href=#1111 id=1111 data-nosnippet>1111</a>        <span class="kw">let </span>result: <span class="prelude-ty">Result</span>&lt;Vec&lt;RecordBatch&gt;, <span class="kw">_</span>&gt; =
 <a href=#1112 id=1112 data-nosnippet>1112</a>            reader.read(tasks).unwrap().stream().try_collect().<span class="kw">await</span>;
 <a href=#1113 id=1113 data-nosnippet>1113</a>