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 > 10 OR b < 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 > 10 OR b < 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">&</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<RecordBatch> = 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">&</span>batches, <span class="kw-2">&</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">&</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">&</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<RecordBatch> = 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">&</span>batches, <span class="kw-2">&</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">&</span>file_path, <span class="kw-2">&</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">&</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><Vec<RecordBatch>, <span class="kw">_</span>> =
-<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">&</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::<Vec<RecordBatch>>()
-<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">&</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::<arrow_array::types::Int32Type>();
-<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::<arrow_array::types::Int32Type>();
-<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">&</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">&</span>file_path, schema, <span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>], <span class="kw-2">&</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">&</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">&</span>file_path, schema, <span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>], <span class="kw-2">&</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">&</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::<Int96Type>()
-<a href=#2789 id=2789 data-nosnippet>2789</a> .write_batch(<span class="kw-2">&</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&</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">&</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::<StructArray>()
-<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::<TimestampMicrosecondArray>()
-<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">&</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::<Int96Type>()
-<a href=#2896 id=2896 data-nosnippet>2896</a> .write_batch(<span class="kw-2">&</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&</span>[<span class="number">3</span>]), <span class="prelude-val">Some</span>(<span class="kw-2">&</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">&</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::<ListArray>()
-<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::<TimestampMicrosecondArray>()
-<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">&</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::<ByteArrayType>()
-<a href=#3012 id=3012 data-nosnippet>3012</a> .write_batch(
-<a href=#3013 id=3013 data-nosnippet>3013</a> <span class="kw-2">&</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">&</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">&</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::<Int96Type>()
-<a href=#3023 id=3023 data-nosnippet>3023</a> .write_batch(<span class="kw-2">&</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&</span>[<span class="number">3</span>]), <span class="prelude-val">Some</span>(<span class="kw-2">&</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">&</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::<MapArray>()
-<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::<TimestampMicrosecondArray>()
-<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">&</span>str,
-<a href=#3087 id=3087 data-nosnippet>3087</a> name: <span class="kw-2">&</span>str,
-<a href=#3088 id=3088 data-nosnippet>3088</a> extra_fields: Vec<Field>,
-<a href=#3089 id=3089 data-nosnippet>3089</a> extra_columns: Vec<ArrayRef>,
-<a href=#3090 id=3090 data-nosnippet>3090</a> ) -> 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<String> = (<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<ArrayRef> = <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() -> 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<i32>,
-<a href=#3133 id=3133 data-nosnippet>3133</a> ) -> 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<i32>,
-<a href=#3141 id=3141 data-nosnippet>3141</a> first_row_id: <span class="prelude-ty">Option</span><i64>,
-<a href=#3142 id=3142 data-nosnippet>3142</a> ) -> 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">&</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) -> (Vec<RecordBatch>, 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::<arrow_array::types::Int64Type>();
-<a href=#3185 id=3185 data-nosnippet>3185</a> <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&</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 < 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">&</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">&</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<RecordBatch> = 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<i64> = 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::<arrow_array::types::Int64Type>()
-<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 -> 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::<arrow_array::types::Int64Type>();
-<a href=#3288 id=3288 data-nosnippet>3288</a> <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&</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">&</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::<StringArray>().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">&</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<i32>| {
-<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">&</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::<arrow_array::types::Int64Type>();
-<a href=#3347 id=3347 data-nosnippet>3347</a> <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&</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">&</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::<Int64Array>().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 < 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">&</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<i32>| {
-<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">&</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">&</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::<Int64Array>().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 < 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">&</span>str, ids: Vec<i32>| {
-<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">&</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">&</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::<Int64Array>().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 < 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">&</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::<StringArray>().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 < 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<i32>| {
-<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">&</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">&</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::<Int32Array>().unwrap();
-<a href=#3594 id=3594 data-nosnippet>3594</a> <span class="macro">assert_eq!</span>(spec_id_col.values(), <span class="kw-2">&</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 < 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">&</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<RecordBatch> = 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">&</span>batches, <span class="kw-2">&</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">&</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">&</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<RecordBatch> = 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">&</span>batches, <span class="kw-2">&</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">&</span>file_path, <span class="kw-2">&</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">&</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><Vec<RecordBatch>, <span class="kw">_</span>> =
+<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">&</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::<Vec<RecordBatch>>()
+<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">&</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::<arrow_array::types::Int32Type>();
+<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::<arrow_array::types::Int32Type>();
+<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">&</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">&</span>file_path, schema, <span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>], <span class="kw-2">&</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">&</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">&</span>file_path, schema, <span class="macro">vec!</span>[<span class="number">1</span>, <span class="number">2</span>], <span class="kw-2">&</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">&</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::<Int96Type>()
+<a href=#2790 id=2790 data-nosnippet>2790</a> .write_batch(<span class="kw-2">&</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&</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">&</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::<StructArray>()
+<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::<TimestampMicrosecondArray>()
+<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">&</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::<Int96Type>()
+<a href=#2897 id=2897 data-nosnippet>2897</a> .write_batch(<span class="kw-2">&</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&</span>[<span class="number">3</span>]), <span class="prelude-val">Some</span>(<span class="kw-2">&</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">&</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::<ListArray>()
+<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::<TimestampMicrosecondArray>()
+<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">&</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::<ByteArrayType>()
+<a href=#3013 id=3013 data-nosnippet>3013</a> .write_batch(
+<a href=#3014 id=3014 data-nosnippet>3014</a> <span class="kw-2">&</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">&</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">&</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::<Int96Type>()
+<a href=#3024 id=3024 data-nosnippet>3024</a> .write_batch(<span class="kw-2">&</span>[int96_val], <span class="prelude-val">Some</span>(<span class="kw-2">&</span>[<span class="number">3</span>]), <span class="prelude-val">Some</span>(<span class="kw-2">&</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">&</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::<MapArray>()
+<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::<TimestampMicrosecondArray>()
+<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">&</span>str,
+<a href=#3088 id=3088 data-nosnippet>3088</a> name: <span class="kw-2">&</span>str,
+<a href=#3089 id=3089 data-nosnippet>3089</a> extra_fields: Vec<Field>,
+<a href=#3090 id=3090 data-nosnippet>3090</a> extra_columns: Vec<ArrayRef>,
+<a href=#3091 id=3091 data-nosnippet>3091</a> ) -> 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<String> = (<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<ArrayRef> = <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() -> 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<i32>,
+<a href=#3134 id=3134 data-nosnippet>3134</a> ) -> 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<i32>,
+<a href=#3142 id=3142 data-nosnippet>3142</a> first_row_id: <span class="prelude-ty">Option</span><i64>,
+<a href=#3143 id=3143 data-nosnippet>3143</a> ) -> 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">&</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) -> (Vec<RecordBatch>, 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::<arrow_array::types::Int64Type>();
+<a href=#3186 id=3186 data-nosnippet>3186</a> <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&</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 < 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">&</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">&</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<RecordBatch> = 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<i64> = 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::<arrow_array::types::Int64Type>()
+<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 -> 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::<arrow_array::types::Int64Type>();
+<a href=#3289 id=3289 data-nosnippet>3289</a> <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&</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">&</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::<StringArray>().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">&</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<i32>| {
+<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">&</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::<arrow_array::types::Int64Type>();
+<a href=#3348 id=3348 data-nosnippet>3348</a> <span class="macro">assert_eq!</span>(pos_col.values(), <span class="kw-2">&</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">&</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::<Int64Array>().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 < 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">&</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<i32>| {
+<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">&</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">&</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::<Int64Array>().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 < 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">&</span>str, ids: Vec<i32>| {
+<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">&</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">&</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::<Int64Array>().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 < 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">&</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::<StringArray>().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 < 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<i32>| {
+<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">&</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">&</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::<Int32Array>().unwrap();
+<a href=#3595 id=3595 data-nosnippet>3595</a> <span class="macro">assert_eq!</span>(spec_id_col.values(), <span class="kw-2">&</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 < 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><Vec<RecordBatch>, <span class="kw">_</span>> =
<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>