From fc4b73c46c017faeafdba9908ed97ff236b0b9d5 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 8 Oct 2025 09:58:50 +0200 Subject: [PATCH 01/19] wip --- Cargo.lock | 476 +++++++++++++++++- Cargo.toml | 4 +- beacon-arrow-netcdf/.DS_Store | Bin 0 -> 8196 bytes beacon-arrow-zarr/Cargo.toml | 14 + beacon-arrow-zarr/src/attributes.rs | 49 ++ beacon-arrow-zarr/src/data_types.rs | 24 + beacon-arrow-zarr/src/lib.rs | 3 + beacon-arrow-zarr/src/reader.rs | 76 +++ .../gridded-example.zarr/analysed_sst/c/0/0/0 | Bin 0 -> 14245 bytes .../gridded-example.zarr/analysed_sst/c/0/0/1 | Bin 0 -> 7519 bytes .../gridded-example.zarr/analysed_sst/c/0/1/0 | Bin 0 -> 88789 bytes .../gridded-example.zarr/analysed_sst/c/0/1/1 | Bin 0 -> 111756 bytes .../gridded-example.zarr/analysed_sst/c/0/2/0 | Bin 0 -> 88648 bytes .../gridded-example.zarr/analysed_sst/c/0/2/1 | Bin 0 -> 76724 bytes .../gridded-example.zarr/analysed_sst/c/0/3/0 | Bin 0 -> 3820 bytes .../gridded-example.zarr/analysed_sst/c/0/3/1 | Bin 0 -> 17283 bytes .../analysed_sst/zarr.json | 61 +++ .../analysis_error/c/0/0/0 | Bin 0 -> 7677 bytes .../analysis_error/c/0/0/1 | Bin 0 -> 1236 bytes .../analysis_error/c/0/1/0 | Bin 0 -> 62623 bytes .../analysis_error/c/0/1/1 | Bin 0 -> 50664 bytes .../analysis_error/c/0/2/0 | Bin 0 -> 42761 bytes .../analysis_error/c/0/2/1 | Bin 0 -> 35027 bytes .../analysis_error/c/0/3/0 | Bin 0 -> 1775 bytes .../analysis_error/c/0/3/1 | Bin 0 -> 7738 bytes .../analysis_error/zarr.json | 59 +++ .../test_files/gridded-example.zarr/lat/c/0 | Bin 0 -> 3351 bytes .../gridded-example.zarr/lat/zarr.json | 52 ++ .../test_files/gridded-example.zarr/lon/c/0 | Bin 0 -> 5925 bytes .../gridded-example.zarr/lon/zarr.json | 52 ++ .../gridded-example.zarr/mask/c/0/0/0 | Bin 0 -> 1458 bytes .../gridded-example.zarr/mask/c/0/0/1 | Bin 0 -> 568 bytes .../gridded-example.zarr/mask/c/0/1/0 | Bin 0 -> 1367 bytes .../gridded-example.zarr/mask/c/0/1/1 | Bin 0 -> 1699 bytes .../gridded-example.zarr/mask/c/0/2/0 | Bin 0 -> 2407 bytes .../gridded-example.zarr/mask/c/0/2/1 | Bin 0 -> 2342 bytes .../gridded-example.zarr/mask/c/0/3/0 | Bin 0 -> 644 bytes .../gridded-example.zarr/mask/c/0/3/1 | Bin 0 -> 1160 bytes .../gridded-example.zarr/mask/zarr.json | 60 +++ .../sea_ice_fraction/c/0/0/0 | Bin 0 -> 30 bytes .../sea_ice_fraction/c/0/0/1 | Bin 0 -> 30 bytes .../sea_ice_fraction/c/0/1/0 | Bin 0 -> 30 bytes .../sea_ice_fraction/c/0/1/1 | Bin 0 -> 30 bytes .../sea_ice_fraction/c/0/2/0 | Bin 0 -> 30 bytes .../sea_ice_fraction/c/0/2/1 | Bin 0 -> 30 bytes .../sea_ice_fraction/c/0/3/0 | Bin 0 -> 30 bytes .../sea_ice_fraction/c/0/3/1 | Bin 0 -> 30 bytes .../sea_ice_fraction/zarr.json | 59 +++ .../test_files/gridded-example.zarr/time/c/0 | Bin 0 -> 13 bytes .../gridded-example.zarr/time/zarr.json | 50 ++ .../test_files/gridded-example.zarr/zarr.json | 454 +++++++++++++++++ 51 files changed, 1486 insertions(+), 7 deletions(-) create mode 100644 beacon-arrow-netcdf/.DS_Store create mode 100644 beacon-arrow-zarr/Cargo.toml create mode 100644 beacon-arrow-zarr/src/attributes.rs create mode 100644 beacon-arrow-zarr/src/data_types.rs create mode 100644 beacon-arrow-zarr/src/lib.rs create mode 100644 beacon-arrow-zarr/src/reader.rs create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/0/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/0/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/1/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/1/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/2/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/2/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/3/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/3/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/zarr.json create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/0/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/0/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/1/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/1/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/2/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/2/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/3/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/3/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/zarr.json create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/lat/c/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/lat/zarr.json create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/lon/c/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/lon/zarr.json create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/0/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/0/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/1/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/1/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/2/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/2/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/3/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/3/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/mask/zarr.json create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/c/0/0/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/c/0/0/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/c/0/1/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/c/0/1/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/c/0/2/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/c/0/2/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/c/0/3/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/c/0/3/1 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/sea_ice_fraction/zarr.json create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/time/c/0 create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/time/zarr.json create mode 100644 beacon-arrow-zarr/test_files/gridded-example.zarr/zarr.json diff --git a/Cargo.lock b/Cargo.lock index 052605f5..b5aa67be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,6 +356,28 @@ dependencies = [ "zstd-safe", ] +[[package]] +name = "async-generic" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3728566eefa873833159754f5732fb0951d3649e6e5b891cc70d56dd41673" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -419,6 +441,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -582,6 +615,20 @@ dependencies = [ "zstd", ] +[[package]] +name = "beacon-arrow-zarr" +version = "0.1.0" +dependencies = [ + "arrow", + "nd-arrow-array", + "object_store 0.12.3", + "serde_json", + "tokio", + "zarrs", + "zarrs_object_store", + "zarrs_storage", +] + [[package]] name = "beacon-common" version = "1.2.0" @@ -715,7 +762,7 @@ dependencies = [ "gsw", "inventory", "lazy_static", - "lru", + "lru 0.14.0", "ordered-float 5.0.0", "serde", "wkt 0.12.0", @@ -813,6 +860,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blosc-src" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68d27ab5ceb94ae9cd343f6fbc7bb84543496d547ed7c0db6718175fd41cb6" +dependencies = [ + "cc", + "libz-sys", + "lz4-sys", + "snappy_src", + "zstd-sys", +] + [[package]] name = "brotli" version = "8.0.2" @@ -840,6 +900,26 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -954,6 +1034,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-random" version = "0.1.18" @@ -1020,6 +1109,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1029,6 +1127,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -1792,6 +1899,27 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" @@ -1876,6 +2004,27 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -2267,6 +2416,7 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ + "bytemuck", "cfg-if", "crunchy", "num-traits", @@ -2308,6 +2458,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "hdf5-metno-sys" version = "0.9.1" @@ -2925,6 +3081,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -2963,12 +3128,31 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "lru" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe949189f46fabb938b3a9a0be30fdd93fd8a09260da863399a8cf3db756ec8" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "lz4_flex" version = "0.11.5" @@ -3076,6 +3260,49 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "moka" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "event-listener", + "futures-util", + "parking_lot", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "monostate" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "nd-arrow-array" version = "1.2.2" @@ -3172,6 +3399,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck", "num-traits", ] @@ -3365,6 +3593,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking" version = "2.2.1" @@ -3612,6 +3850,18 @@ dependencies = [ "serde", ] +[[package]] +name = "quick_cache" +version = "0.6.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba15f5bccfb18c666351668b97bbff66da5093f96757ca15299e4e594fe1316e" +dependencies = [ + "ahash", + "equivalent", + "hashbrown 0.16.0", + "parking_lot", +] + [[package]] name = "quinn" version = "0.11.8" @@ -3737,6 +3987,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rayon_iter_concurrent_limit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09ee01023de07fa073ce14c37cbe0a9e099c6b0b60a29cf4af6d04d9553fed7" +dependencies = [ + "rayon", +] + [[package]] name = "recursive" version = "0.1.1" @@ -4071,18 +4330,28 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" [[package]] name = "serde" -version = "1.0.200" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -4095,6 +4364,7 @@ version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ + "indexmap 2.10.0", "itoa", "ryu", "serde", @@ -4110,6 +4380,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4256,6 +4537,16 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" +[[package]] +name = "snappy_src" +version = "0.2.5+snappy.1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e1432067a55bcfb1fd522d2aca6537a4fcea32bba87ea86921226d14f9bad53" +dependencies = [ + "cc", + "link-cplusplus", +] + [[package]] name = "socket2" version = "0.5.10" @@ -4393,6 +4684,12 @@ dependencies = [ "windows", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tar" version = "0.4.44" @@ -4815,6 +5112,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe_cell_slice" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6659959f702dcdaad77bd6e42a9409a32ceccc06943ec93c8a4306be00eb6cf1" + [[package]] name = "untrusted" version = "0.9.0" @@ -5553,6 +5862,163 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zarrs" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad12c7c2b91d2f6871f21efc28fd5a302809d7246974fdd99ef55bd3b16b78a0" +dependencies = [ + "async-generic", + "async-lock", + "async-trait", + "blosc-src", + "bytemuck", + "bytes", + "crc32c", + "derive_more", + "flate2", + "futures", + "getrandom 0.3.3", + "half", + "inventory", + "itertools 0.14.0", + "itoa", + "lru 0.16.1", + "moka", + "ndarray", + "num", + "num-complex", + "quick_cache", + "rayon", + "rayon_iter_concurrent_limit", + "serde", + "serde_json", + "thiserror 2.0.15", + "thread_local", + "unsafe_cell_slice", + "uuid", + "zarrs_data_type", + "zarrs_filesystem", + "zarrs_metadata", + "zarrs_metadata_ext", + "zarrs_plugin", + "zarrs_registry", + "zarrs_storage", + "zstd", +] + +[[package]] +name = "zarrs_data_type" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e68a3b9e663cf4933afcd90f460ee72986fdf6c2b4d43d0441ad049b802342" +dependencies = [ + "derive_more", + "half", + "inventory", + "num", + "thiserror 2.0.15", + "zarrs_metadata", + "zarrs_plugin", +] + +[[package]] +name = "zarrs_filesystem" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e135c32621a3a5796d917768d5c7aa7f58be9480ae00778956b82ec6409150b" +dependencies = [ + "bytes", + "derive_more", + "itertools 0.14.0", + "libc", + "page_size", + "parking_lot", + "pathdiff", + "thiserror 2.0.15", + "walkdir", + "zarrs_storage", +] + +[[package]] +name = "zarrs_metadata" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b938e5af9e6564d7135fb2d1e05c0deff3d7124694ff6822aa01614a6c991" +dependencies = [ + "derive_more", + "half", + "monostate", + "serde", + "serde_json", + "thiserror 2.0.15", +] + +[[package]] +name = "zarrs_metadata_ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fb56ca32761b64c4b2a3db1097fbd29adfb321a129279b1db99be0a61d361a" +dependencies = [ + "derive_more", + "half", + "monostate", + "num", + "serde", + "serde_json", + "serde_repr", + "thiserror 2.0.15", + "zarrs_metadata", + "zarrs_registry", +] + +[[package]] +name = "zarrs_object_store" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9d0d3db426dd50dfb0b5d7cc1660bda368caeb5cd8645c60c46bc4f261a19" +dependencies = [ + "async-trait", + "futures", + "object_store 0.12.3", + "zarrs_storage", +] + +[[package]] +name = "zarrs_plugin" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3c9e0514d4c50f44d11285d5df70e4e586486a39826579c9d87ddc3f3dac561" +dependencies = [ + "thiserror 2.0.15", +] + +[[package]] +name = "zarrs_registry" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe4e55522eeb87eefab89017bef78cb823f86861fd8a3cc12e9f6538c348d57" +dependencies = [ + "regex", +] + +[[package]] +name = "zarrs_storage" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bc1037a8fa8c44ccb8f5c6c85753a63ddf296fb43280f28150f0f29fda8d301" +dependencies = [ + "async-trait", + "auto_impl", + "bytes", + "derive_more", + "futures", + "itertools 0.14.0", + "parking_lot", + "thiserror 2.0.15", + "unsafe_cell_slice", +] + [[package]] name = "zerocopy" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index c4a92503..e80d232b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["beacon-api", "beacon-arrow-netcdf", "beacon-arrow-odv", "beacon-common", "beacon-config", "beacon-core", "beacon-functions", "beacon-query", "beacon-planner", "beacon-data-lake", "beacon-formats"] +members = ["beacon-api", "beacon-arrow-netcdf", "beacon-arrow-odv", "beacon-common", "beacon-config", "beacon-core", "beacon-functions", "beacon-query", "beacon-planner", "beacon-data-lake", "beacon-formats", "beacon-arrow-zarr"] [workspace.dependencies] tokio = { version = "1.47.1", features = ["full"] } @@ -16,7 +16,7 @@ utoipa-axum = "0.2.0" utoipa-scalar = { version = "0.3.0", features = ["axum"] } utoipa-swagger-ui = { version = "9.0.0", features = ["axum"] } -serde = { version = "=1.0.200", features = ["rc", "derive"] } +serde = { version = "^1.0.2", features = ["rc", "derive"] } serde_json = "=1.0.120" anyhow = "1.0.95" thiserror = "2.0.12" diff --git a/beacon-arrow-netcdf/.DS_Store b/beacon-arrow-netcdf/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8c9b71d365b0e0e1d09d9f911c68522c5b3a21a4 GIT binary patch literal 8196 zcmeHMU2GIp6u#fIzzjoSiY;B}NEZtMb)iea0+yfcAC=!C+tO_duDhL~9huJ5o!Q+& zt?7e*!9Pui&YoSy7+Uk%X2v3nF_A7$Dl&D~Y2LhdUDJeMq?{zkpRqJM$qZ&PojKQ7 zhXWx3Ap#)+Ap#)+Ap$o70`$)InmEsUpU;MUh(L(IO^JYaK1AvAWFnB0T>3``b$$sz z(klV{M)y<)_&(7^6CMBD}oXO+@0p5o;k@xASb!x?hM?WA=nu~g#v$f z@{9WB3<)m7K13iy;93N{^jXYYzhZZy zEzR+LlYByyL@BLA#>cmBkH@0h;&)BNqT@Rn>3-{$rilqjtgdfpKAJvdj#&0t9u9&d zfmO^2@5%XMGo3;kkF9K7DYofSY|>(RiQ1d$lT#^U(6*As89l8mb11xhq#nuX1q!Qo z&^GfUmXlU02Q%hi-psgqi)nhsG`8F`=^Awn+j5TSIfrD*)yG_Ky5F&LZkz9&R+PM> zAJxg(DCq69gwvlH>kbU-6LB7o^mfIH)%ELRo12;w?fVYhJGFF~QYp(-)x8D7F|wxK zmvIJlOEavYo}!_-!~F!MdX`Q-8KCaQnI%I3!YMQ9LC;2mL)ivsXH`A8Wsn-IB4eA|ow?jdSN`@P5 zlpEykQ}nDQ#o;53@>aFS%w%;_QZ&BLHhG8ITi`fJ$^kyOQ{Jtn2JMj%3dNx)VtlxT zcD~Qs`C!IzyGC_uX7diUQ|)(#GdaDhNMsY0i`-*Bjr$LfW;~}3&2a6U_lXIR!)sKv z&s(lV)BKfI)}7VIy~93Y7uY5CCHszj&wgM(vY**+>@ole%Ta}DtUw)N*oX$iu>&30 zi%uLt4~`*)K4dY3VLX6SaB&9Xn83q$98cpJJd5Y>5?;k?coT2oU3`KI_!O7$HKy?m zzQs?tj6d+FAP7r@3Sp^GBSeH%Lane#Xb=*@KH)HrKqw{B!0aG}F4C`iNKhgLhlKFZ zbrY#}Z|6O7=Z%s`(-mIoEWf3ydPUvZ+c$05wzFl1Vw{sD8eUCqpV71Oz-P4oyysAH z%Fi+R*43q?x{_u*?RqE`^fA%nv$80#zO7!QR8STK>iP|&9#9r?wKldqWKcHXS?3i zS{aW%;?@5h^Z)*TQ)3hcA0qI7M*!vR$@W%?a=tUttF@za-A|X-JiAFQeHZHda-5`J tj+31E!;tQyWa{}O0y)VgjZpj7e+UTQ|32 Option { + match value { + serde_json::Value::String(s) => Some(AttributeValue::String(s.clone())), + serde_json::Value::Number(n) => n.as_f64().map(AttributeValue::Float64), + serde_json::Value::Bool(b) => Some(AttributeValue::Bool(*b)), + _ => None, + } + } + + pub fn as_nd_arrow_array(&self) -> NdArrowArray { + match self { + AttributeValue::String(s) => { + let array = arrow::array::StringArray::from(vec![s.as_str()]); + NdArrowArray::new( + Arc::new(array) as arrow::array::ArrayRef, + nd_arrow_array::dimensions::Dimensions::Scalar, + ) + .unwrap() + } + AttributeValue::Float64(f) => { + let array = arrow::array::Float64Array::from(vec![*f]); + NdArrowArray::new( + Arc::new(array) as arrow::array::ArrayRef, + nd_arrow_array::dimensions::Dimensions::Scalar, + ) + .unwrap() + } + AttributeValue::Bool(b) => { + let array = arrow::array::BooleanArray::from(vec![*b]); + NdArrowArray::new( + Arc::new(array) as arrow::array::ArrayRef, + nd_arrow_array::dimensions::Dimensions::Scalar, + ) + .unwrap() + } + } + } +} diff --git a/beacon-arrow-zarr/src/data_types.rs b/beacon-arrow-zarr/src/data_types.rs new file mode 100644 index 00000000..069735cc --- /dev/null +++ b/beacon-arrow-zarr/src/data_types.rs @@ -0,0 +1,24 @@ +pub fn try_zarrs_dtype_to_arrow( + data_type: zarrs::array::DataType, +) -> Result { + match data_type { + zarrs::array::DataType::Bool => Ok(arrow::datatypes::DataType::Boolean), + zarrs::array::DataType::Int2 => Ok(arrow::datatypes::DataType::Int16), + zarrs::array::DataType::Int4 => Ok(arrow::datatypes::DataType::Int32), + zarrs::array::DataType::Int8 => Ok(arrow::datatypes::DataType::Int8), + zarrs::array::DataType::Int16 => Ok(arrow::datatypes::DataType::Int16), + zarrs::array::DataType::Int32 => Ok(arrow::datatypes::DataType::Int32), + zarrs::array::DataType::Int64 => Ok(arrow::datatypes::DataType::Int64), + zarrs::array::DataType::UInt2 => Ok(arrow::datatypes::DataType::UInt16), + zarrs::array::DataType::UInt4 => Ok(arrow::datatypes::DataType::UInt32), + zarrs::array::DataType::UInt8 => Ok(arrow::datatypes::DataType::UInt8), + zarrs::array::DataType::UInt16 => Ok(arrow::datatypes::DataType::UInt16), + zarrs::array::DataType::UInt32 => Ok(arrow::datatypes::DataType::UInt32), + zarrs::array::DataType::UInt64 => Ok(arrow::datatypes::DataType::UInt64), + zarrs::array::DataType::Float32 => Ok(arrow::datatypes::DataType::Float32), + zarrs::array::DataType::Float64 => Ok(arrow::datatypes::DataType::Float64), + zarrs::array::DataType::String => Ok(arrow::datatypes::DataType::Utf8), + zarrs::array::DataType::Bytes => Ok(arrow::datatypes::DataType::Binary), + _ => Err(format!("Unsupported Zarrs data type: {:?}", data_type)), + } +} diff --git a/beacon-arrow-zarr/src/lib.rs b/beacon-arrow-zarr/src/lib.rs new file mode 100644 index 00000000..c910167c --- /dev/null +++ b/beacon-arrow-zarr/src/lib.rs @@ -0,0 +1,3 @@ +pub mod attributes; +pub mod data_types; +pub mod reader; diff --git a/beacon-arrow-zarr/src/reader.rs b/beacon-arrow-zarr/src/reader.rs new file mode 100644 index 00000000..1a278483 --- /dev/null +++ b/beacon-arrow-zarr/src/reader.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; + +use zarrs::group::{self, Group}; +use zarrs_storage::AsyncReadableListableStorageTraits; + +use crate::attributes::AttributeValue; + +pub struct ArrowGroupReader { + group: Group, + global_attributes: HashMap, + array_attributes: HashMap>, +} + +impl ArrowGroupReader { + pub async fn new(group: Group) -> Self { + group.attributes(); + + Self { group } + } +} + +#[cfg(test)] +mod tests { + + use std::sync::Arc; + + use object_store::local::LocalFileSystem; + use zarrs::{array::Array, array_subset::ArraySubset, group::Group}; + use zarrs_object_store::AsyncObjectStore; + use zarrs_storage::{AsyncListableStorageTraits, AsyncReadableListableStorage}; + + use super::*; + + #[tokio::test] + async fn test_name() { + let local_fs = LocalFileSystem::new_with_prefix("./test_files").unwrap(); + + let object_store = AsyncObjectStore::new(local_fs); + + let zarr_store: AsyncReadableListableStorage = Arc::new(object_store); + + let group = Group::async_open(zarr_store.clone(), "/gridded-example.zarr") + .await + .unwrap(); + + println!("{:?}", group.attributes()); + + println!("{:?}", group.async_child_array_paths().await.unwrap()); + println!("{:?}", group.async_child_group_paths().await.unwrap()); + + let sst_arr = Array::async_open(zarr_store.clone(), "/gridded-example.zarr/analysed_sst") + .await + .unwrap(); + println!("SST: {:?}", sst_arr.chunk_grid()); + + let lat_arr = Array::async_open(zarr_store.clone(), "/gridded-example.zarr/lat") + .await + .unwrap(); + println!("Lat: {:?}", lat_arr.chunk_grid()); + println!("Lat data type: {:?}", lat_arr.data_type()); + + let arr = lat_arr + .async_retrieve_array_subset_ndarray::(&ArraySubset::new_with_ranges(&[(0..10)])) + .await + .unwrap(); + + println!("Lat arr: {:?}", arr); + + let time_arr = Array::async_open(zarr_store.clone(), "/gridded-example.zarr/time") + .await + .unwrap(); + println!("Time: {:?}", time_arr.chunk_grid()); + println!("Time attrs: {:?}", time_arr.attributes()); + println!("Time data type: {:?}", time_arr.data_type()); + } +} diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/0/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/0/0 new file mode 100644 index 0000000000000000000000000000000000000000..fb949fc56c527a009a34a66cb1914da56180b3aa GIT binary patch literal 14245 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 HjaCQ(X8b#Y literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/0/1 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/0/1 new file mode 100644 index 0000000000000000000000000000000000000000..b954f9093d0869b3026251ea80e2117100b49136 GIT binary patch literal 7519 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 literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/1/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/1/0 new file mode 100644 index 0000000000000000000000000000000000000000..965a478b17041a3ace36de1e1e386b6c5c862a2a GIT binary patch literal 88789 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 I<$Db>0hbV(sIy^)Ams}?c1pClgi-| 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 z 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 literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/2/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/2/0 new file mode 100644 index 0000000000000000000000000000000000000000..fbe7f5cc44a61f6d9b62074e6dc44a0548dba533 GIT binary patch literal 88648 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%0Aw}q|Z7IAtY(}-Uz2C&N{A>q- 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 literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/2/1 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/2/1 new file mode 100644 index 0000000000000000000000000000000000000000..84b039c682ceab57d0018ff13f5a8c7668e813d5 GIT binary patch literal 76724 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 C$Z9+Q literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/3/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/3/0 new file mode 100644 index 0000000000000000000000000000000000000000..4a2f1ad4a79b4567634f07bf0ecf6fe18355e0b8 GIT binary patch literal 3820 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 literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/3/1 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/c/0/3/1 new file mode 100644 index 0000000000000000000000000000000000000000..5e829e45e3d5dddb0704dc74b80cc060c0c670b2 GIT binary patch literal 17283 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 literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/zarr.json b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/zarr.json new file mode 100644 index 00000000..69af4005 --- /dev/null +++ b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysed_sst/zarr.json @@ -0,0 +1,61 @@ +{ + "shape": [ + 1, + 1208, + 1920 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 302, + 960 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "analysed sea surface temperature", + "standard_name": "sea_surface_foundation_temperature", + "type": "foundation", + "units": "kelvin", + "valid_min": -300, + "valid_max": 4500, + "source": "EUR-L2P-ATS_NR_2P,UPA-L2P-ATS_NR_2P,JPL-L2P-MODIS_A,JPL-L2P-MODIS_T,EUR-L2P-NAR17_SST,EUR-L2P-NAR18_SST,EUR-L2P-SEVIRI_SST,EUR-L2P-AVHRR_METOP_A,EUR-L2P-AVHRR_METOP_B,NAVO-L2P-VIIRS_NPP,EUR-L2P-SEVIRI_SST_Meteosat11,MAR-L2P-SLSTR_S3A,MAR-L2P-SLSTR_S3B,STAR-L2P-VIIRS_N20-ACSPO_V2.80", + "comment": "Optimal interpolation (OI) at Ultrahigh resolution of sst measurements from infrared sensors", + "add_offset": 273.1499938964844, + "scale_factor": 0.009999999776482582, + "_FillValue": -32768 + }, + "dimension_names": [ + "time", + "lat", + "lon" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/0/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/0/0 new file mode 100644 index 0000000000000000000000000000000000000000..32e07e94842ad2f1ff815bf38ce18d160bb82f94 GIT binary patch literal 7677 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 literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/0/1 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/0/1 new file mode 100644 index 0000000000000000000000000000000000000000..a9a32956addd09cf89d9241d5ed0eccdcb1b89f0 GIT binary patch literal 1236 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

KzEO)pU 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∈xdGcDxd-PNc^_$ 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 l)I>auwA4#3d|m&ncB-n76^e#VWL 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 literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/3/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/c/0/3/0 new file mode 100644 index 0000000000000000000000000000000000000000..61d3ec276ac181a7b129a32d70586c476666377b GIT binary patch literal 1775 zcmVzidTFaQh;!LqbahKfYuaD)#Sm!92-o!#rRqam`-&PHU87tn6pD9OW>H(YXu z;IkS5m)ecGB{y0V8l@0UK#2P6XyWzQS;GxGEBQ3I5iTPE0mo>h>h&-xnzE|`QeL~F zq7st2-OKMr{kNxIrU8S1sN(e`8OGuZNQPm?5P=J!c@hADIEbfsjAS@Q5kw3j#1KLV zA%uvCh=|OHNKo(r&(m@9V{QlXcHi9p;Q{V0XzysoAN2F=(vhI9w&*$Wv7Yb+7IFZFy*BNB&)f)hW6J zzX&JJan0~u5lV8*CIwQ*860Q&8Ps9oto>If`9a8hFHx{izvyDrcECx0_E%q(XS=U*( zOZaownOi3nZFlTS+XAUz;JrHFc5c-Pv5&%Q4qW~3|1Hoeke|#|229)y+z_wYq?xcV zl&cqBWK9oR^U+MuGF>NCoa46s{+r-w&%n=K{?ydzS#Y>{w`GnUo(KQD&9tgkn$)Rr z>yGx>YZR76)hu7MM0Bdkuifkh9}Z3WMJtyfVg`4D+7ex&5Z|U77nmMwTBN*N^YhXe z^5(e0`#X?Yp#4ZBupWgZ9dF1fA(2JW`twE-Mn>HQA*zB}7q(F_abjZMP~P_}#n~2g-m0c;LMej(B?EOhqR)3+?tI zie%iB_qCKXX5{$P)+hG1;Bb?HzjqV2yoed#%LV+qJ;M@_))$F&r%nGnthfu#h0~X< zNle{roM)1#iT8)}@dh8C5^1$b1D`!z!H;GSw~hHoo}g&~hbypPNwyDhN-vx@xD0)7 z%%b}QD!h6gmkJy7NUt;tK6;8VTIIDG2lwTIr$IM=X7F2Fy$BGA3m>}~wA=8`_;bTK6Jz0osZ?~p z#j=ruqTsRfE#Q{P3p4jObUDkyZH5Zd=`v_S+0P0K8fst1&17hk{H7*e27h-i+oFuN zci_E?5f6T0(A#r@i(_R^In_)*bfbTCo+)dM0KS7vSuI*f_xH!vf#pTi$i&s1qw+Rc z+Sjo`0yq8A+oe;bJ62FVG_&+NX(p7FFBj$owZIHc3w-~v(+xUgHUhT_r$!zH?|2V5 z$YBZzo!UsAm8Je@V6VZYjE0Lr9ys~)h5d%2>^JGT(ohTE`0MR*o6L^ieJ<|6n+>?2 z1@}?rmOnb^Id`#Z`*&tnRnv3hZ;BWQ7AN^)!TYA3S-86@rw%D`l z1@~-qaQJf)oq35n?|-0{$FXo8T4Ts1(+3p(FS((%tRZMC~>rE zEjiSNHgGNElMo$3Nk;)y7>-N;00;m90Q~w7(wJ-gk0NDrt1hxQ1E>jbrB}oVW|NsAgf^QqyQYI-kApuPQNdQv-Lr=tj zAaXNG-AbFYEN7_NoQrOD+Bb_JL|d<=Xm3({YoE9v5hXe1DMh0oW#Y`aXlg}A^ol*g z*rP;TXc?u7WTp>u!Uzm(VBlc^Aq!QO?{YTJm?Y7Wis@97x=w=-4DCszawA|lWlwu^ zXug%NN!XdVJdp|^kkS&C^P;Moi{PkW+?*-x+EX1w6{jxOKEn z5Gq=`Dkc@=;{1n&7Z2XFmTCDnS(C4g8&S<103Q99Xnf_|_TdDaUU={@xOU@#vsh=J z(;Of!8vVMoo8!X3O|`lDZUX5zsw)K$Ns=tKO+ zo{K~r;FTbAgnsZUa4P&LSYhC)n8nbFZF0HryWvgc5%f9rzjppY$L-c#p>UtU-^;qo znD~9T&;hqA*ob+4?krTv7(7fg2Uxt&tZUqHM|2JgkmZz)nEc>OC&U89 z;QlI2x`$;>t!#hEgTlK9?Avp2He*6=uy=@ZeQ>_;*L`kqt)TeaPh;WegL&|CkoCI1 zHBEZ@(geI0o?EoZF}M?R7b~!s^yqEyn;tVC8Pb7>S@%6>gsFV30Y?$wi0sQ#jEJl94OzeUBlY3r2Fx;68S z*sQm@eDEA&MWK@d_@edj!)0yDOfqu(+j`H%t=%c??c?*H+uyJ@qN-+}^V8LP@_b)$ zn;Y+n!sX_h5%Qu^z<$6|8N!>_lgn zkj{p|{`|F9->dI%NeGt6qZ|}t zMaz+I3|0Ded@~fR2lKyIoaEN#Shpv|-5Wbu8!zkMy)YB774;eyf7yIH7WRW7>?@;2GNEsB^Zk1+pqJ-q*I7Wn)$Y*gdR6uU}2<7Hf(Eo84O@aCW^Jhk)-+S|h&r#SnX z7aYf{;Yw#)?{}M~}7$7#{W~C1mc*n#E7EDQ}A>`r#0eVsvw0 z7(|ceF$88;R`9A>?ObF%+(sxZhj$pq9H~r{_+0vz2|#(+>Txcao*5|4b9NZ_Xb2Ev z&Rgkw?QZ?k+CXdXv7I|&hyP3GR}6~ZQebnTE=AHq;#&OYHRlJ=J?vNt(u~4hL~zdT z!@K1Iq)7F}i5jjGSJ6qQmcH9N*7~T$?%K|_mx2WDqA3!8R6xAkBR%v-x0;!K>quih z@w?SNt`0B}(xu17dS=%(vqLxO zi5Hh>aj4@3Yraq14G$6)RI7?^b@!K>=^s|K= zFxuiHYvBJB$KmvfWODGkJuV0LP{DuDXT;C%ZXJO8bJgWG9}W!%Ww+88jqUz!s=+-e z50H9A2Dj{%F`t$9z4&kDW%Z)&#z>-tD+INDvDanU^MB--dNYQc?AjbG%&PEEu#sw^|h(QT0yaSm4CeAmVLexeZUfs{_*gOyT1t z;He*cJhiD;1FxTe1&_YfS`H>#j{yact#tAeCDQ><^<2tPe&_YzFWfdDU*E2^ER*5I zzwUT~n}cPZ`&AI&ps?ED+70vVG1W=Cx|99=#Gys}_RLGtAbJB>w}(Ad9`Aov9f3F7 z8*mdG*w$G7fCq>|JWcq@0#y1(%Ht@b0|)Grls5c>rseO@ixC2jf0lI~JGuCeg6{L^ zelX)vQrGc}bIy#ReL3X~@bOpcuKlI@tQqST56=B0+(5?b-%auWZC3-nYUrU`W=*t0 z4ZQd&$3yf)Y3xARw0aduAUt({Ux_UO;vbE(>!L;HK}R_l@x2- zN4+euRksDCzhs|WVh}C``v8r$0sib^N%&z|*1<%Sm62CYo zH=sC|#R_JnGq19mINOUYf?bmhqW=~S9=s71)L@Fc{I1=r9xNWjm@n(A6%_6gwrH;4 zrB!!jLM#!JF-0iSf}9Q zbi(h>!Eb*z)SA`4+5VxraOu`_;cmE{@fUhx{MPN2d`;o9mY1!Bw&{b{Qu}m?{v?CR zi$C@f4I}Y=Yi2ZXw9?k<>6^+~qx*%B(7n`Y^>(}+2Vtk|boxtU(fnUFdt!U5WmUD# z@FBcRmPMr+-jo`h&sMm0Qn?hPc^W)jVuAO+*L5<`79OFX?0I|pCh`EiTQ1KTNi*6D>`>#qFc0d7%&zC<1+GLAzW(+KzMo@&TH!i>uSK{u>G}Kiu(Ofmbo!`x5K$eO=ZY`MaOZNO#aE`S!8ps(j7-JsfV$ z-T4f-B(iQX91Y(AD!w({X4OIu+W!UK?Da zL%R=$`IaS-c*E%!+u^*L{Z}gHWIMTZ+$4;MdY_XJ2^PMTq5s0!ZN|+4-m)1DC)N}E zs*bv)PtWe?o}^R7dGtTlwHQL-3qo#i*&bqBe@7naE0Bwv11sx{23*R+mb(0w)SglX=#aCzQ9Yu*b#6TUvTgQ54QJ+ljfS`>C%u*uZE`e@Ah|> zev$45p4On$SARq^=*4XsJ?riJ9Be%SU+wz=f-%ULBQExfzOBA=QTB-xGw{xeFqa9q zQ?PDza~#jhYyOsRNj-S7qr#=im*4+2S_S^iA|8MVELCvPvDxan zjQd&ckF5ly2Lqy%?7`{sgBaDF12y0qJWo740nxy3Lc*)Ax!hPh^bEt>*Nj1ZgH>Kw@=q$|r>0tNfkT?SRX# z7|(pU>tlRbTigdj5Q9RNJ9yqUGYX-lCb+K~KrHGPJ<5XpcZGb=+86PE2+#2IM@dLm zWvvmvZ+SozVp~5EKixf!o_Z!n_Hb~6mH&boupogBt{`J8F8rj*cx9azSj!{qw;BQy zg2sOxe2)4wUNocjZ@o%Dt?zP>g+R93ZO|ZWco5X!&;9 zV|-0K|NJlH%LZVYyOkH;d~(~*`oV5*x97Uw;I&)D?+FcswO%~Lrcx)INhNgR0f|or z-jCk=)WVZm60b`u^%%HQrXh(R_0X_F!;V^#{*Hk&2GD^S`2vgjO-E5cOwU^D2Y6m4EpXm zCm&q8paCu@@TbfD0ZT_gOaG+ns$DqT#qT!{c~j-$XZ&T{dsp~D`CWAUf_Y8Z9C#!4 z8PDEq3yU6P?WG=_wl)+;qfWv{^RN~I1Upx$I0{QsdG*zct8ZsKj zy=6s@Txhz^`o&my%016ndv@f^;5|VWN6OwFe42vi({xVN3#eSWS^}q-5hx1Im3p2h z>2=$9XUlTmiwYZy%nUQwD|#P94aZm!s~RX^&r^eIPxD#1k7p^tyhjOqHf^W$5NGAW z&V=<&QshJgr)`tlbm8*2MerSVY}(FLG++z|&NOkmPS;bAgz*ipzDI9d>4E@bf>;o~ z8~Q)i8k+mR9n`MoR{jqzo4S3MoNw!QSkD3%?J|jj_$o{cPz0+X>$l*Mrf*dh$_~o4 zrOf>1hDqjnA$&Zu712hV;B-MpQdgPk`Z0cXaq{QDL$y!^Yx7-*c@ zS^(!8cXle49v%lC-2}DGE^HZY(-)CtR;wJlqJrK3`H7F1eG_}(e|OU%chV$!jRiLS ziY^jntnElHCYg{-O(g}@wk>P;@5C#`y(d1J!JRHhjdGun)6r|YMQyQrA|v znNgKV)&+?`z>ON~iFhtB5anS@RP>Lk@Z~P44;5+zin77I98KoRSgPA>sIYU_%*Q^& zRWHI#xiDmBR_eM=Pn!Ud?iH~f+at%CPICy2y)esFm;Zdtf#!0SkjkNv42O&=WjET-Giv|v3 z04Z?iJ+2_q^T!#Ci$&Yuk=kU@>O2(#e&5gOJ?P&8LTDvsXoP_rxS}Dwk&M=j1(3{7 zpBc8OpsEcDICTb8j-|mEVdVduyxiwBI2U2h8DX){gr4{=aeR=~ReHuMUuhFwK}Yh) z>%LE;(c&RSXZWu04@BVL$McBr`|YnT_ea{O=^);<>U!x}N)I1H$M6hZZibetY|Vqw z$z41xpY(n=Wj4~nb~WjCwJym2RC^3gURGcJm-zg}7N7ljqh_dhuNq~~TiDFLZ$N*dO> zX)E0mp7%GodfW1OkOqduA;`TaK)sAG8`ilHJd<`C1y0$t_2v9vk`G_$`YgH|dpLgJ zB&Sk^g(xX3rmM9o;Ni_wTMDUiE_8pPGZze_n?p`gt}J>q9x?iD`$a@5eW@K`+LFxG z3-dEpB8iH@Gu%*AtS~vATEa9VOuUz6s$Y(2>zvG+5N`mSWkPmagcm4#e~EDyvIRNU z+3wDKYX0ZRu#%9W;ejvpHw99&q|PnfUmDIS2A|DYkJRrk-y)3?Jr;DciJ!vKitzsc zPplBfYJlVqb;MvBxlZE()*d!|vWIQ|HiKcn@4j_sYH*PO*R?i&%(yg1;OktdnmIRh zWjBQ53i}Mz7bD(9A0CI$1Io4A&4ai5eoBd}rQk|va1~3rPT=3Nt1Udm{z_wo>SS;` zWxI@?D?66Z*^CXrmPL)ic`qQhUpJ0fgnzyqu81Q|{t@--r8nw9?DK?q`{6*4Zsc(! z6Oxyt=oEBc>r>nAYxyI-=blZ2OF5evoTaKCQr$zUBKch} z#k?-eXZ*|^7$YD85&l1>(?r)iL>QMF@P;FM+L}eL=IPv}O@~{CFpH<%W=J@UxhS4> zu8t++bHP5C*SiNGhq^*zP7xhHZf^*y}L=7!nm_OZLPvx9LT4Hezi@|OD@)bi8a zS?&^gIskT3D-*D_4GhCD3{`}fb(6@poid5t0U!V)03!fq zR}1*<)_>RRo7vqG-)_}5knj7h-K?$M?M1p}`!@okdu3O^p zT0q&^T6Pol&1U`UWdY)tDGoEwd;&AkO<&%wp*>=falY#Vbat5IuJ0CbZn<^W55|~t zjv+J9K!Op>2?TS3L&p?DCMW>~LO_KI8HA9`QpgyYg-GJaVI%Rxzzc)@sKVW)10oDS z#4v`8z%|J4-3N(;JXFI`sl@xa3M*Sip4MyK(1-bRz2b~vDU zThOLMb{;$yaeEzkv_V%r_%JyjB0_b*R_DN<$>+x5{qo=)cTLeEQ{8*i^Z&8C+rL1y zj=X=ovuom}y#qDq$>xir6L`y6^W(?f$wH4pN(++qE8AMoXV2ZY{b#1X}+VdVD5Zl|^3%QqbpJ zN`Jq04;&{eZoR;wpY1y)S%!D8(dH}Mw-k9a_0SEG5O3Soec((WkU7i~C8R;%=&5Yn zsJyps9afwNl$i=Q^Z7;)E{DTcuiRY4M6Zx=&W1e8xrV>Wxes`qfd&F+J`1;Jxp+-v znJV40#EAvoaDA5pj;4En(>sm~*UfO>^9BYW5l zhAy9r)AD1@z-g(mu;n*2;&_f2Y2hV$J-C<@b4*g_l6#IYHt0A=zTsS_jun}GeV{Z> zZI=()crCF;{js(AP@)Vu(7g&t+cRH1YTA)Oo;Mr3bW}4XMM*2*78-euK#Ye%0Rs`v z>b$$9aAo^Xxtk&$1j`x)E@P7;Wwd?$q6Kh|`|2NEvvS;+C9Z*1a_*+X41O4u{Z^=I(BB|$~1mZ1jEd}u8+Jh*g-Jxy?R z{Xu=Z%WtwBA=M2Q?hxj|OPI5lC6VplKb&UpyuHuQtv?)GMN)%+%FEhjT)u;PvkWCI zSa`hrHxh!vyZSh0qWesBIN#zxuTf-I`1to}zgh95oj+LkeP|uL z;D0)F(%{hXZS8%o@z{*XrfV;<>%L)Qc&tUVcWyLW(BI(cfiG3~1G8hEZI|}Jj~m-> z;d1YvT#Km{Au5ba2AsiLzFSkVzqt)6*ffQ)3-&2!Qa+pNLzV8`{xWl9y|K)tb%Wos zp3zQp>&yEUz6VdM^%v3o&^&T5ATw&rt6+?6x2-R!&^D_flgTx{>JeRrDW)!X+CUG% z3G`A-#svph;RfQz5A}s69+<@THNs0n--09&+AhL{`tZ<76Wn6);;Cb{A#Q;9UUQkX zNkfRZcrOW;G+J99l^L(gcFra+bo@aG8;xWN(~D(WugXUby?KA!uV??mS#{FV3~oT- z!J>t?Lwt93jR=K%@exu!iF0tbzV)ESwciZ79awo~#OBSRxsN!!?5GKy*I3IO9s2>H zeBkCfOvK=hY?ao|r`$Kt1cCBZMEXx}Ox;@Hv!C-$u=fc!8j#DpJ@~=3?F8qZJkleG zKCX2;9$RfK?Qq3^FkH4!I$5qow9&@Y+Yk^6H82jKq#PXl1){Wq7Ka(N5i`4qBflMd z-HMj>%{B(FTw-Q1g=yIF$oQKa-<$K+lHN_=UF~1N6J%j4o<=^a-_w&O8~rxInA^a^ zWmJvpQofmmsQw$DZY>Myi+9}q`@oR#-+!k7d zkykuHIVm}cg>{Zot0oa}H2v&J?PO_@to`!}W^ffc;`e{c9K16>zEi^nQKUq*I{MX%?8gcNztLLy(qCzM}ES)$B^9O0n>wp zuhaFFvjZ`^(yzq=Y(zANgSVo_{{hgSN7d-N3QlYpTLY)tqb2L)SPLC{n9{Qo%+0!#n^2mk>9{Qo%+0!;t_2mk>9{8$|b0ZrA4 A!~g&Q literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/zarr.json b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/zarr.json new file mode 100644 index 00000000..f21e202d --- /dev/null +++ b/beacon-arrow-zarr/test_files/gridded-example.zarr/analysis_error/zarr.json @@ -0,0 +1,59 @@ +{ + "shape": [ + 1, + 1208, + 1920 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 302, + 960 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "estimated error standard deviation of analysed_sst", + "units": "kelvin", + "valid_min": 0, + "valid_max": 10000, + "source": "EUR-L2P-ATS_NR_2P,UPA-L2P-ATS_NR_2P,JPL-L2P-MODIS_A,JPL-L2P-MODIS_T,EUR-L2P-NAR17_SST,EUR-L2P-NAR18_SST,EUR-L2P-SEVIRI_SST,EUR-L2P-AVHRR_METOP_A,EUR-L2P-AVHRR_METOP_B,NAVO-L2P-VIIRS_NPP,EUR-L2P-SEVIRI_SST_Meteosat11,MAR-L2P-SLSTR_S3A,MAR-L2P-SLSTR_S3B,STAR-L2P-VIIRS_N20-ACSPO_V2.80", + "comment": "Error obtained by Optimal interpolation (OI) at Ultrahigh resolution of sst measurements from infrared sensors", + "add_offset": 0.0, + "scale_factor": 0.009999999776482582, + "_FillValue": -32768 + }, + "dimension_names": [ + "time", + "lat", + "lon" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/lat/c/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/lat/c/0 new file mode 100644 index 0000000000000000000000000000000000000000..fb6d5039fdd8d9622e6849a8981f295bc72e4f09 GIT binary patch literal 3351 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 literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/lon/zarr.json b/beacon-arrow-zarr/test_files/gridded-example.zarr/lon/zarr.json new file mode 100644 index 00000000..ffd46435 --- /dev/null +++ b/beacon-arrow-zarr/test_files/gridded-example.zarr/lon/zarr.json @@ -0,0 +1,52 @@ +{ + "shape": [ + 1920 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1920 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "longitude", + "standard_name": "longitude", + "axis": "X", + "units": "degrees_east", + "comment": "geographical coordinates, WGS84 projection", + "valid_min": 25.0, + "valid_max": 43.0, + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "lon" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/0/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/0/0 new file mode 100644 index 0000000000000000000000000000000000000000..17a39e139a501e1866f31a080e96263041904cdd GIT binary patch literal 1458 zcmV;j1x@-WwJ-gkfNTT+Q~&@F0s;X5`~Nw>0`wyQv;qPF0RaI50RaI40RaI40RjR6 z0RjO50RjR60RjR60s#U70s;X60s;a70)kbjf^Taw3{XZv$cRbXHWGj^AP7cr48|}J z!!QWKAP|Ef2!bF8!ypXA7-o<{j8XHt0R#i{ka%iQTDeO4gU}{3*gK-Go*R0kkFPBT^=-s&yQ2*dA6Jo=6A8h=aVk*-_;tGB{V%W%DI2> zG3-z(VZ$6i>VRcA4Pu3s;gbX$Y}Vx=iR$qGeT3X|Xp#1TIMBTiZ|F=$2K!nDl>>43 z1s6Pc)=SX141wOOB{&XE^1S-Km+BfX;_V8A&%x%-zPKo(E@u=3E{A9K-_l7w(S?5F zZjX+~^C2H}DQ;aSNN1goc%;Xxa7{RAB#Pg9uQD7F?#gia1y-(Ej<>W4B2_O*0*C?0 zD*uaJDEq7$%_Dr0Ozi8DTlDX;hgiZs-|R?5!N;B;u?qEgvnkbplKW4a+xDDW+M~yi zL_=t>=%1d&Ev{OZfKrzInu7amC$O~V`dR=r_I`4oa$B!jRg*(#7dO}QyNa$6Py&tB z%ju`dzE@WQC^CBgiAH)LZk01tFFTq*-`j(_HB$A#2bBomf-r9iQ^$c=tcMfifGqm4 zt9+|}!TsTCO%~$oL$**=}QP}7?wM6dOtv?^RGEg)U}3BQ>)DQNoNgN!>6e-GRr6K z8S8#aW;WYH4?^*F!s;06-E^ucDt_Fp0HF zgqS)TBSSq4G#Rmf@tPTS3ukUPMbx}duR;}zBSPXYgtZlzFmDJ~OR^D)5ReQ&{=%n@Nv@zGW9LgAZY*I!b+`J6D}2lL%be!-MRH}CA-S$xJv=|s zMi#}DWe}>q`)5BT`w9FEg7&d<;6!x$2P5J)gE7zPnQ3`39*Fa!tz!!QiO5Ks_W*&9~O6+g&4r9y09bSGHTPluhN1#ktF|- zmzOpaZ^kJn0*W&g*;vwfYRRXqjYEw*z|77@N;sOeWTYBLh${^{#o?Niio1!h(D4*6 zvZCuL>eQ~c!v~pku`rv6H))9^5j>J86o{V9yhP-PO*iO24q+r{D}uois2^?oYI>Tg zBRweVDq%RrYl2vHVDExU&(?GRb}eku^j(IVlO{nOG`y0wJ2R^w8Yi+M6x4K?MnI_) M%^*szcseVO6k{NoxBvhE literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/0/1 b/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/0/1 new file mode 100644 index 0000000000000000000000000000000000000000..76fd11e5054880f32f2eb73b49e5501583b05464 GIT binary patch literal 568 zcmdPcs{eOELkOCn0;iUW=0wsoBpTI`qdz-!Iwz%@I2j}+vG@8F$l zZ|2I)pa61n!@(5~_!*i7C4Eh0gd7wkIQR}Z2)G-Hup|l`QQ&ZFG2m!Y;Aj$1HfU2& znfOO2eMj(+klw+px0q$7A1Bn}h0fcm4mAcGiCWYJLCt_j>NCT))3sy07ZP z)~~mk8`f^Ks|aQc_4-jCKgrGh!$+M52X-*bx91nCsGHLAqWH|aGR}!-A8ak^IxAV$ z?l|4x@+=DtaXGI5E2hG$LA@I{yx+t9wle<65zjP*Q?5aZTPynOF9g@C9GRegS>UN= zrq;KO2}Tcg$u-|7HWClqHgR8uYx0Tv^$*MioV_F`X~=rq?pYW7oGER=%2n(OuRaa9 z@=&^L^1TdprtGX7{?*FU>=KcpTXm z_;aD)o6u7JNk^hZD@q>8Y|xy)V2&P-VBszutm>5nZp$a~U$lQJqvSC4 zu&~0}Muw7d0fw8|9ZXvCO22{?l)eSK?)mFsvGI-ZGKCk$>l8jozD(Mg-s89-eU9T5 z_cc^;zv z-cl%7>}gLZN0FP>lsQdPm4e-F=Y_qt+wQ0*Dpb_jR&`NhQRC9Q-`?jhkwm zuQ4{PX1G_eP__OmYdw3+pIQ2U3M76eRH};aW8VBQalV}}``5)UT^7${pFYFv)}1{j zvJ3XN>swx%c4tAKL{0YX5A(`X*uRy1*!<|S(^0X$0!ss>g2eY;=My=N3)WPB^f0@q zpWChYrJ1?>t$f`5AO6!#8#3jhA7*f8EIqdVX+r33me%CcYcjQs8=9B4KClh3l$d?F zVd~SmLq9^*)|s9WRC#(rIHM~gT1G`@!$TdDS5`IEJT{+hOY?Q5SIexp!&kYb_w~s~ z8!Ok;Jf75ZP4ioIh(Ypqu@6O($<`_PRbeN!|9b7(f5Dm>evI}*v2zok3@d!B>2yn1CO%O0(;c)q&%mRcwj%|VlOXl_7V$?nI zwIxt>O)1xUtu>{s>4Mw7wl{0(l(nU6>=t@V9);M4S5Ys8mEMxLB!Ya6`RGe7veQ~ru{=lmu6iqn-l`9&-J zm=AutF8}uN{U2`g?tVSweP-jzYj>8Z7b8AeY#wZb5H)<(=SWHQ!nxUmR9t*ziHvl7KaHQ#(UPA zzl{z&o~gL+=tEXs?~asn#S4%11pcp4|M!0~Qz#1q2UE$62mA~|5{?i4|9Ahu`Rm`I zTfCEy2mQ&bca>a=@!3W%Wi3@mOa99t^0(Tu(g&&$+o$hlAw(J!bYuP6*)w)+$sAV6&7Du7I3P-KJ4M(MT3|FbT4_7HWi(*gh T2Bng{7qoKbP5{yYZj71$6#PUX literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/1/1 b/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/1/1 new file mode 100644 index 0000000000000000000000000000000000000000..efe5b4671fa4e73e2374866570479d51846ee4f8 GIT binary patch literal 1699 zcmV;U23+|lwJ-gkfNTT+1RelH0|Ej80s#U60s#U60s#U60s#U60s#U60s#U60s#U6 z0s#U60s#U60s#U60s#R50RjR60fKX=a&HHs4oF6*#GsH45Et3_^eq zBm@Y8AP9mW2!bF8!XONUBFIBO{fW$Ks6vY6sEQ0LO;vt8sZIpTQwv|3Dn*xERoMEf z{9Uig-_lkEvu-t?rLPiR{Z;j0hnOK%E4Jv^`Ax^W#*ag|y?4@$>VV%WX&zr3n~n zHhR2cMWY}1U+dB18!sCA|No&=kL#cwmAquSxP7e_s>~?14|7~O`x>q4nk@W5O#6M* zy7xJEqva``qxPLf%Wp>=8oqNFkq^`r=6D#~E5_s}=p28f3-`^*?lVx@K8A=Wc&2?e z%_A%74l`WM@O8ap z3kFq>#a(<@Ix2h};_02pF*rIN2-6H1_!f^QuH8lQ{zDjwdLc4whY-lPq;Co35Nq9Y zk>E)gVK4ZBm3MjVFH?GgbJRrX&i*+%jOhinRIQq@!2gu7zgPWYB6-fM^qjKoBvQrU z$k6`M*DQT)WolV0>pYsW2IAglC+}@ymC|*)hb`UNW)TuAb6G$?0 zN2l{F_tsh&q%PK(m-1I21(4&PXCr2PxJZchC`$C{88D%vr&+>pkN=X-Jx3&IKH-vt z_E3~)+0$5}PtU-lj-Hk#dU*y;^za;@=)t3bqVLWiMK?S93JK+~T^I+BGzRO&KXb25 zi-u^8U~@3dkMnk>)lt$+W9LZbnU850El}B)!$X;9C0_a=JLJi#1*yU_+5UhNwYcFT zU&OBxcC=9X_9`zng5KR}VZVTWE*Ff$9?1Q+sxg z1}fL4KVb(cNkNjra1u};2p||BEP@~mf*=S&kT3)c!6O6-f*=S2LuAB{ z0U}^hbh(^SpjLc~xe2LWDL`vVNGnNHX*eW{3uZo5w&X{(NR+q9+p#20n!HQikvFxS z>MyO88O!AJzP9G!30kPdReyb1z3a=> zS6^4p`l7sV-u;E#mhbdk660#7q@w_X8qMOrRv%yMdDvv(C^^#m~yQ zBSXhKyx$>w;-Z0_H?(8`LdiS2M1_<0Je0hl;T;P^$|(3F{0kvg_W)8A>uN*EgP{jf z^1(v`mHhCrLzO7 zIgaS>o+zEqi&7dX+BK?Cky}PDh9AA#a};OKQQVFrr4)g*Qyl4DJyIT>C#4dSv`bvl zKlMsc2w(bhlPg8ht`tYCrQ%hYn%d+`QRgqk55Dw2Zz=EQOi_j~?HJDVH=QZZ=9*H4 zG|d&(sfemPr;{)R9RvU{0RjR70s=G;097iKsenoaGZjdwfTn^d71C7TqymdSG^x<0 z0wxvQRG_2+oC=auh*QCl3N!v-qyn7^id3jm!H^1fDiBfuPX#|J?5V&<1wIw@sL-c^ t9Tk53K}Q8Z738Q8r~(`n1XXaO!k`LlR3KDAjS7V-m{H+S1u-fNsjf)w4u}8% literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/2/0 b/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/2/0 new file mode 100644 index 0000000000000000000000000000000000000000..e2f6eaac5b20da5d7fc05ad8d70f02296bcfcf61 GIT binary patch literal 2407 zcmV-t37GaMwJ-gkfNTT+>>2>FhXjEEYE6Ds{r~5ws;a80s;a6EfySuub_c-*BO)SX zl5`SaFc=5~F${ws5P~2O!XOC45FiLbfFJ}20fHb1LLmr(GL+pI0+@V|et60ER@b4o zrR<1RrwZ3HPB1f$ls)L&gT(Wc<&dfz$c+n4#iwPoP-8oIP-tP-^vXCGx7;4_!@jFDSxGZLe_{slyM6ZzE8B|eH#xs&RB|c2Z)ca0YlOv%|IhxS9CcHq-#T+wPd5y zgQzu4o;fuezo|vCiRw%e3Yi4N8@B?SY)|fz9R>yh(>T$F)~O8$pG@*9`;9ZKKOpWW zN%(^lekKU6bjgpJ2jRRVpR~}p2NrjyDmH4iiK~)EM&6F5ARd5UWFaKj4N#E=haFIg ze4dOWs4kS9)u@5uD%m^90$zK?S-{VSO$)y4Ko7kk->DrXA*pk#sM-(64lk$8;9v-p z4D7mzsSRO1tI0cvB>%;WF@{e}6`53rAgzfdh|7d3(q``B3=f4PTN06#d<=FknxYmb zDI#SPE4;X@l0@LO1^*aN1r z4?5L9cvK#uPwj|5wIlGU9O0*WM5Fo;KUIHdRomfJb%(s_H#Doxz^p!lX7vo7)o18d zd*NMe1^g-}_^Y1KuYSUR)e{=lM)+9$AjkR#9jhO3tbd?oy@8kY4La6-gI}QroFV{3 z0s#U70s#R60s#U60RaI50s#U60RaI50RaI40s#R60s;X60fKI*qJOdt5J8HFQKL!P z5@0YG2t+XuLNO2mAqc`y41*vDf*=TjAP|Be2tqLkA(rnAjRFg8yTEvVR8Q9EZT%)efklwJ5BiECd|*O2+`xop&Qi&D78gdGn@<(loF%=p|UGnq%&#EtOI>^({&3 zi7YlMrCi(WLDvFN5e+vXWkn6P!d zSH!4TE-To?xL6LsG9p+3c373M>C=7qDgX!>rw!{jfhooeyEr#ccVa&V%WCn!!D*^} z>5#@=p*a``^Iu--S?oTd2EAb~15Qd4locsMPOgdz$f@lnPVl zyxp!dnKo7F1@mj3b_6XA08jz~0s;X70s;a7fZM3>4KW7Dn3@@rCXE3SU|<-AL{SjK zFbsuI2*V%*2!k*Tf*=GE!yp35Aj2F+A2vY6O1U9Y^CL08TWO>}}z_P=r z3L%Bk~PM3J5NDd8kl{KK%!*&?=@ z$)!~3r`@=j7J!Xtv=x_Y!j>s&6mG8qfiBsKJ18fE)A)cIN;f>qb^x2)P0d#ul56S{ zQxZWLZxf$oiT=pGQ(YaSfz2o5j~ii6cN9o`|ex=Qz(s|v76TfRsg;jGC3Nc4Y_y2=23d}13vOeg}x!{ ZmY=w#=vpwL{8eJC^Gwi-R3de%f@R0_O}qdA literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/2/1 b/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/2/1 new file mode 100644 index 0000000000000000000000000000000000000000..4a6e47e402d089ae6af632568992e6360d0f342c GIT binary patch literal 2342 zcmV+>3EB22wJ-gkfNTT+j1&MO%m#r0t*WZ3s;a80s;a80s;a80s;a80s;a801c7g; z@%aNm2O?6CC>;_&FdzuRFbIMWAP50MkPsvYK|*jsfDkYUfk>A%y zX?x}Q8cCYC++HJ<9+BJE#?o*y=ii7=Mq>OM@tu);e`7rQSobx;r;%m9N4)yD?0dwk zxq6+fvA{2%Z$xD++_Hze+{Jjs_n!qTo4Jkgw-t+0uyCCl8E;>~e)lreD=Ol|P{XDC zAiPRgj>+GolRG^zf5LKF(qW8F$f4xv$JxoGw7+iz_Xyc+e!804s>DkMuh>HRm<9K} zm9)mQ4_6C*nIFsplF8|iO@8(+YIM7+_4r95FW#I2~2we zPWu7YssL^I&s_W6H9vXZcW?FeZGL~x_m}zp9iPCxH(2^6mVRQ-v)K3^*L`KnbNcbC z2~KRk&2DeCpYC#(Z7(tD5B5HN*S|OS&GlbeCeT+WcqikT+1e_-HB%!g=L&lvQlv@t2m5T26HTe6-$0Ki(R>8u5*z~xp0e!izKcB zye9xO0s;X70RaI40s;X70RaI40RjO60s;X60RaI40RaI40RaI4g7&DwKcWqoF$kJq zCrJkqU@#aAhGHNHf*6Kj7=~dO24N5cK@bE%7zRNwhACu>(>~}#F4x39586<06-XL8 z_>*O_-x|(e89ZPOo^x5;Vo{y-9Ya(swx@2%n0TpxXj66WAMsSzfd&jId7-+Q@S?&F z#V(`rL5t5&ebD7JR6gh;8xaD+?gjyY;=X}kO_q0MF<$W^i+tHEFpbJX9&qa#4>ob&cQIuFmXEn7XI@gfCZ%bjFy4{ zeME6!T>nXR(Z~W_{aF`5YDXjaprRfSQE0D&TRjxogRCL~@los&x%{cziqd|7X^W2k zG-r|eQCg@-^ar#wQa|c&N5wy2SEL5OL6r0cEFLMjgJoeQhp=F2X*dU8PhYx-68aQ# z`cM{d2SFWT%Ar$-hBHw#38&2=jA_C)>?7T~TM%QA>F-oBz%w1|{YLnPo@@!AG`{=_ zW;hbt3rz-%e`5j1I249({lUW9$6cF|%E#sDvJyCC^eI>f``%VEp&2bEpN6v&OH7k; z(`KQjFD*L_h7N<_x>j0;ann(eqUc0xfOrFMIU>tTQ|6>!QLQrFB_%AL*Vh9W<=Dua zvo0&GW5jg@(wx7#=4CK0m}cA+lO4Vy%tee`*LfKs~5@QbrrFj&K6lInRf(Km&Qxf*m_lkfWWC|#Z?KZOuma6Y3*em+Uvb;sqXX(DDg_P5NycIPrD(b3?A3}fsqGwh zDzJU^%11s`DpGV&JMmOVzbC64BONTNb(u`k9)}J+Xkf4-6x@PGy!%nJNkTOO-5EpX z3_D&4Z1T$?u81TpuUsmcU~*NV`n--{jvYO>Hx61JHlbSQUZUN?gKK=rYO*M{l+xDK zBNdal7;)<}UC$s7>LSDWmL(BQ#fUz<^o|wdSwM8E+qY-rPCHz0^rc(9kK{xf#vg{c z)?@%+IQf%0^$7poD8R?zAI3U4(&EZ&!U1Ir05Aao0s;X7fW)Za55WbF7)An=l4J}L zfIuJ!1YsD$APmNG7{@>mLI@#*5MhJ}5D^e0B)SLg0$uZ&1p*^Z{YDid7U=4je`t&m z9?IY&j80J@AeR#E@D@nGeOHy@$<~#uNIHlQ!s$GGx^w#!M9tz4V9>5W}tC>)+)HsG>JOOM~%V5`sMj z{tj4x?LG%EWN6+~I`CSDz>;Pz>z}N`CLK)#@C|jHE1ROYj0kCR8xXiVkaU13w}sPl M`^tV=Sc=^;y=c5%_y>Dg z)xJZik{c8+9%mO@@wUpT@I(KH*-e$#3S3(m?_Joa^X2ZJ_0Q%P`q!MiaC+MQ%(G8t z^G`WuY!~$3v&Zm_pODYdThZmg>9*Co*J@WET{zG6#J4!f`oooF{WPEWev*#pxLkH?4z5UfTK) zV|Vjir-0Xw-Wyt!{Z*RvN-XwW`S<>Bf^+@uBzd#UkT{lV(xF?HbUr}-)UuB~b(-gL ze{w8m-4$7@;QAroYG>ZfIOXLvKMKy8lyA1G5)uA#bASH#4{@(QKTGC1((!)lz8t$% zNlOa5o~B414qV_Sxl2fQanYhRyDu84GwlgtIxDYWxY;AD^x8C?&YyQ8{fbYPoe>>Obh}{%qh%F0H3B2X8-^I literal 0 HcmV?d00001 diff --git a/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/3/1 b/beacon-arrow-zarr/test_files/gridded-example.zarr/mask/c/0/3/1 new file mode 100644 index 0000000000000000000000000000000000000000..ddcbbcaeea12e132ae9025487aa18dd479a1b020 GIT binary patch literal 1160 zcmV;31b6!=wJ-gkfNTT+q$2=?0s;a70s#U60s#U60s#U60s;a80RaI50s#U60RjR6 z0RjO50s#U60)lI(g7*PJ4H!WL8RZqF4HBSG7>0r<3PKPDK^O#K5ClOG1VIo4VGsmC z5Qw2L#85B!9H+lO+v{tVcgSb4eP5>iS0Sv2n@{79Fe=bQO*wz?V?i4=|Z zPc^unk`h!3Ff+LQY{654NO#7e81-ZnK;M{40|1>dgA4)si`hf0W(zeHXctja4El?9 zM%7e7jn($*D8o zLyk^<7PpQ-50^k`)5G zyv;u{3gsI7G~gp^^r-_-YRaRR52ZPe*?&L-J%REV8hFf+XK3Q&DH4-S@0zMSckqyp zYLty)%7y^F@elAsblO5qRo^MZI4@1;GHvmS2qO74nNn_Iam8hYE^T^;-!L|tNn-G| z#Ftfv)N|BecM3s|qcSt-q)j3J+2IU1enG6VredZ|pzmWjS^qI${^Y1HPOJjx{tR>J z^S_f<8}pzikMYDY#!wqzF^)ArxrKKQ&~ID`v{x*>5xDsT?n7Vo|A^%Jh1#eT66Z<1 zmAf<%9A7I==;C3;d@l!FjV{Fb5R&#oSVM@Cx~2C+QGreS92f0N@|$fJi=D%`NN}55 zgE@JvL5Y(giyRz#9F-ZWEB=)*W6yDovi<+S9Pd|UQ>`zAcgz5-mWW))!9`v0{9WwM zl!a1*zP$=elQQ2ZI04H{Pjt5K@0)}<9$RHX3IITl378AqYD-445LqvVS-MIUkypSL zPhg5qC=o!E0mksBt`kU_!a9s#e=gzIAd~127 are encounterd, please subtract 256", + "license": "free registration at Copernicus Marine Service (http://marine.copernicus.eu/web/56-user-registration-form.php)", + "id": "OISST_UHR_NRT-GOS-L4-BLK-v3.0", + "naming_authority": "org.ghrsst", + "product_version": "1.1", + "uuid": "f3079d2c-7d0f-11ee-8b9f-005056be6f27", + "gds_version_id": "v2.0.5", + "netcdf_version_id": "4.7.1, Build Date: OCT 6 2022", + "date_created": "20231107T014955Z", + "file_quality_level": 0, + "spatial_resolution": "0.01 degree", + "start_time": "20130228T190000Z", + "time_coverage_start": "20130228T190000Z", + "stop_time": "20130301T060000Z", + "time_coverage_end": "20130301T060000Z", + "software_version": "Copernicus Marine Service UHR L4 Processor V.1.0", + "westernmost_longitude": 26.379167556762695, + "easternmost_longitude": 42.37083435058594, + "southernmost_latitude": 38.75416564941406, + "northernmost_latitude": 48.8125, + "Scaling_Equation": "(scale_factor*data) + add_offset", + "source": "EUR-L2P-ATS_NR_2P, UPA-L2P-ATS_NR_2P, JPL-L2P-MODIS_A, JPL-L2P-MODIS_T, EUR-L2P-NAR17_SST, EUR-L2P-NAR18_SST, EUR-L2P-SEVIRI_SST, EUR-L2P-AVHRR_METOP_A, EUR-L2P-AVHRR_METOP_B, NAVO-L2P-VIIRS_NPP, EUR-L2P-SEVIRI_SST_Meteosat11, MAR-L2P-SLSTR_S3A, MAR-L2P-SLSTR_S3B, STAR-L2P-VIIRS_N20-ACSPO_V2.80", + "platform": "Envisat, NOAA-18, MetOpA, Aqua, Terra, MSG1, MetOpB, SUOMI-NPP, Meteosat_11, Sentinel 3A, Sentinel 3B, NOAA-20", + "sensor": "AATSR, SEVIRI, AVHRR, MODIS, VIIRS, SLSTR", + "Metadata_Conventions": "Unidata Dataset Discovery v1.0", + "metadata_link": "Link to collection metadata record at archive", + "keywords": "Oceans > Ocean Temperature > Sea Surface Temperature", + "keywords_vocabulary": "NASA Global Change Master Directory (GCMD) Science Keywords", + "standard_name_vocabulary": "NetCDF Climate and Forecast (CF) Metadata Convention", + "geospatial_lat_units": "degrees_north", + "geospatial_lat_resolution": 0.009999999776482582, + "geospatial_lon_units": "degrees_east", + "geospatial_lon_resolution": 0.009999999776482582, + "acknowledgment": "Please acknowledge the use of these data with the following statement: Generated/provided by Copernicus Marine Service and CNR - ISMAR ROME. We would also appreciate being informed of any publications.", + "creator_name": "ISMAR - Institute of Marine Sciences (CNR - Rome)", + "creator_email": "gsdk@artov.ismar.cnr.it", + "creator_url": "http://gosweb.artov.isac.cnr.it/", + "project": "Copernicus Marine Environment Monitoring Service (CMEMS)", + "publisher_name": "CNR ISMAR GOS - CMEMS SST-TAC", + "publisher_url": "http://marine.copernicus.eu/", + "publisher_email": "servicedesk.cmems@mercator-ocean.eu, gsdk@artov.ismar.cnr.it", + "processing_level": "L4", + "cdm_data_type": "grid" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": { + "time": { + "shape": [ + 1 + ], + "data_type": "int32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "reference time of sst field", + "standard_name": "time", + "axis": "T", + "comment": "Nominal time of Level 4 analysis", + "units": "seconds since 1981-01-01", + "calendar": "Gregorian" + }, + "dimension_names": [ + "time" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "mask": { + "shape": [ + 1, + 1208, + 1920 + ], + "data_type": "int8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 302, + 960 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "sea/land/lake/ice field composite mask", + "flag_values": [ + 1, + 2, + 3, + 4 + ], + "flag_meanings": "sea land lake ice", + "valid_min": 1, + "valid_max": 4, + "source_mask": "----", + "comment": "b0:1= sea; b1:1= land; b2:1= lake; b3:1= sea ice; b4-7: reserved for grid mask", + "_FillValue": -128 + }, + "dimension_names": [ + "time", + "lat", + "lon" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "sea_ice_fraction": { + "shape": [ + 1, + 1208, + 1920 + ], + "data_type": "int8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 302, + 960 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "standard_name": "sea_ice_area_fraction", + "long_name": "sea ice area fraction", + "units": "1", + "valid_min": 0, + "valid_max": 100, + "source_ice": "Not applicabile for Black sea", + "comment_ice": "Not applicabile for Black sea", + "source": "Not applicabile for Black sea", + "comment": "Not applicabile for Black sea", + "add_offset": 0.0, + "scale_factor": 0.009999999776482582, + "_FillValue": -128 + }, + "dimension_names": [ + "time", + "lat", + "lon" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "lon": { + "shape": [ + 1920 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1920 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "longitude", + "standard_name": "longitude", + "axis": "X", + "units": "degrees_east", + "comment": "geographical coordinates, WGS84 projection", + "valid_min": 25.0, + "valid_max": 43.0, + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "lon" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "analysed_sst": { + "shape": [ + 1, + 1208, + 1920 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 302, + 960 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "analysed sea surface temperature", + "standard_name": "sea_surface_foundation_temperature", + "type": "foundation", + "units": "kelvin", + "valid_min": -300, + "valid_max": 4500, + "source": "EUR-L2P-ATS_NR_2P,UPA-L2P-ATS_NR_2P,JPL-L2P-MODIS_A,JPL-L2P-MODIS_T,EUR-L2P-NAR17_SST,EUR-L2P-NAR18_SST,EUR-L2P-SEVIRI_SST,EUR-L2P-AVHRR_METOP_A,EUR-L2P-AVHRR_METOP_B,NAVO-L2P-VIIRS_NPP,EUR-L2P-SEVIRI_SST_Meteosat11,MAR-L2P-SLSTR_S3A,MAR-L2P-SLSTR_S3B,STAR-L2P-VIIRS_N20-ACSPO_V2.80", + "comment": "Optimal interpolation (OI) at Ultrahigh resolution of sst measurements from infrared sensors", + "add_offset": 273.1499938964844, + "scale_factor": 0.009999999776482582, + "_FillValue": -32768 + }, + "dimension_names": [ + "time", + "lat", + "lon" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "lat": { + "shape": [ + 1208 + ], + "data_type": "float32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1208 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "latitude", + "standard_name": "latitude", + "axis": "Y", + "units": "degrees_north", + "comment": "geographical coordinates, WGS84 projection", + "valid_min": 37.0, + "valid_max": 49.0, + "_FillValue": "AAAAAAAA+H8=" + }, + "dimension_names": [ + "lat" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "analysis_error": { + "shape": [ + 1, + 1208, + 1920 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 302, + 960 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "long_name": "estimated error standard deviation of analysed_sst", + "units": "kelvin", + "valid_min": 0, + "valid_max": 10000, + "source": "EUR-L2P-ATS_NR_2P,UPA-L2P-ATS_NR_2P,JPL-L2P-MODIS_A,JPL-L2P-MODIS_T,EUR-L2P-NAR17_SST,EUR-L2P-NAR18_SST,EUR-L2P-SEVIRI_SST,EUR-L2P-AVHRR_METOP_A,EUR-L2P-AVHRR_METOP_B,NAVO-L2P-VIIRS_NPP,EUR-L2P-SEVIRI_SST_Meteosat11,MAR-L2P-SLSTR_S3A,MAR-L2P-SLSTR_S3B,STAR-L2P-VIIRS_N20-ACSPO_V2.80", + "comment": "Error obtained by Optimal interpolation (OI) at Ultrahigh resolution of sst measurements from infrared sensors", + "add_offset": 0.0, + "scale_factor": 0.009999999776482582, + "_FillValue": -32768 + }, + "dimension_names": [ + "time", + "lat", + "lon" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + } + } + }, + "node_type": "group" +} \ No newline at end of file From 6fd189e3e755050dfea37cc78a150c16526086e7 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 8 Oct 2025 16:57:11 +0200 Subject: [PATCH 02/19] wip --- Cargo.lock | 2 + beacon-arrow-zarr/Cargo.toml | 4 +- beacon-arrow-zarr/src/attributes.rs | 8 ++ beacon-arrow-zarr/src/data_types.rs | 4 - beacon-arrow-zarr/src/lib.rs | 1 + beacon-arrow-zarr/src/reader.rs | 116 ++++++++++++++++-- beacon-arrow-zarr/src/stream.rs | 181 ++++++++++++++++++++++++++++ 7 files changed, 302 insertions(+), 14 deletions(-) create mode 100644 beacon-arrow-zarr/src/stream.rs diff --git a/Cargo.lock b/Cargo.lock index b5aa67be..34900be4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,10 +620,12 @@ name = "beacon-arrow-zarr" version = "0.1.0" dependencies = [ "arrow", + "indexmap 2.10.0", "nd-arrow-array", "object_store 0.12.3", "serde_json", "tokio", + "tracing", "zarrs", "zarrs_object_store", "zarrs_storage", diff --git a/beacon-arrow-zarr/Cargo.toml b/beacon-arrow-zarr/Cargo.toml index 1677c03e..c1876554 100644 --- a/beacon-arrow-zarr/Cargo.toml +++ b/beacon-arrow-zarr/Cargo.toml @@ -11,4 +11,6 @@ object_store = { workspace = true } tokio = { workspace = true } arrow = { workspace = true } nd-arrow-array = { git = "https://github.com/maris-development/nd-arrow-array.git", branch = "main", version = "1.2.2" } -serde_json = { workspace = true } \ No newline at end of file +serde_json = { workspace = true } +indexmap = { workspace = true } +tracing = { workspace = true } \ No newline at end of file diff --git a/beacon-arrow-zarr/src/attributes.rs b/beacon-arrow-zarr/src/attributes.rs index 72bf8e77..29c954c5 100644 --- a/beacon-arrow-zarr/src/attributes.rs +++ b/beacon-arrow-zarr/src/attributes.rs @@ -18,6 +18,14 @@ impl AttributeValue { } } + pub fn arrow_data_type(&self) -> arrow::datatypes::DataType { + match self { + AttributeValue::String(_) => arrow::datatypes::DataType::Utf8, + AttributeValue::Float64(_) => arrow::datatypes::DataType::Float64, + AttributeValue::Bool(_) => arrow::datatypes::DataType::Boolean, + } + } + pub fn as_nd_arrow_array(&self) -> NdArrowArray { match self { AttributeValue::String(s) => { diff --git a/beacon-arrow-zarr/src/data_types.rs b/beacon-arrow-zarr/src/data_types.rs index 069735cc..ee5911b4 100644 --- a/beacon-arrow-zarr/src/data_types.rs +++ b/beacon-arrow-zarr/src/data_types.rs @@ -3,14 +3,10 @@ pub fn try_zarrs_dtype_to_arrow( ) -> Result { match data_type { zarrs::array::DataType::Bool => Ok(arrow::datatypes::DataType::Boolean), - zarrs::array::DataType::Int2 => Ok(arrow::datatypes::DataType::Int16), - zarrs::array::DataType::Int4 => Ok(arrow::datatypes::DataType::Int32), zarrs::array::DataType::Int8 => Ok(arrow::datatypes::DataType::Int8), zarrs::array::DataType::Int16 => Ok(arrow::datatypes::DataType::Int16), zarrs::array::DataType::Int32 => Ok(arrow::datatypes::DataType::Int32), zarrs::array::DataType::Int64 => Ok(arrow::datatypes::DataType::Int64), - zarrs::array::DataType::UInt2 => Ok(arrow::datatypes::DataType::UInt16), - zarrs::array::DataType::UInt4 => Ok(arrow::datatypes::DataType::UInt32), zarrs::array::DataType::UInt8 => Ok(arrow::datatypes::DataType::UInt8), zarrs::array::DataType::UInt16 => Ok(arrow::datatypes::DataType::UInt16), zarrs::array::DataType::UInt32 => Ok(arrow::datatypes::DataType::UInt32), diff --git a/beacon-arrow-zarr/src/lib.rs b/beacon-arrow-zarr/src/lib.rs index c910167c..6318fed2 100644 --- a/beacon-arrow-zarr/src/lib.rs +++ b/beacon-arrow-zarr/src/lib.rs @@ -1,3 +1,4 @@ pub mod attributes; pub mod data_types; pub mod reader; +pub mod stream; diff --git a/beacon-arrow-zarr/src/reader.rs b/beacon-arrow-zarr/src/reader.rs index 1a278483..1ddb95e6 100644 --- a/beacon-arrow-zarr/src/reader.rs +++ b/beacon-arrow-zarr/src/reader.rs @@ -1,21 +1,99 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use zarrs::group::{self, Group}; +use indexmap::IndexMap; +use zarrs::{array::Array, group::Group}; use zarrs_storage::AsyncReadableListableStorageTraits; use crate::attributes::AttributeValue; pub struct ArrowGroupReader { group: Group, - global_attributes: HashMap, - array_attributes: HashMap>, + schema: arrow::datatypes::SchemaRef, + arrays: IndexMap>, + attributes: IndexMap, } impl ArrowGroupReader { pub async fn new(group: Group) -> Self { - group.attributes(); + let mut fields = Vec::new(); + + let mut attributes = IndexMap::new(); + let mut arrays = IndexMap::new(); + for (attr_name, attr_value) in group.attributes() { + if let Some(attr_value) = AttributeValue::from_json_value(attr_value) { + let arrow_data_type = attr_value.arrow_data_type(); + fields.push(arrow::datatypes::Field::new( + format!(".{}", attr_name), + arrow_data_type, + true, + )); + attributes.insert(format!(".{}", attr_name), attr_value); + } + } + + let group_path = group.path(); + + for array in group.async_child_arrays().await.unwrap() { + let array_json_attributes = array.attributes(); + let array_node_path = array.path().clone(); + let array_name = array_node_path + .as_str() + .strip_prefix(group_path.as_str()) + .unwrap() + .to_string(); + + // Strip the / from the start of the array name if present + let array_name = array_name.strip_prefix('/').unwrap_or(&array_name); + + for (attr_name, attr_value) in array_json_attributes { + if let Some(attr_value) = AttributeValue::from_json_value(attr_value) { + fields.push(arrow::datatypes::Field::new( + format!("{}.{}", array_name, attr_name), + attr_value.arrow_data_type(), + true, + )); + attributes.insert(format!("{}.{}", array_name, attr_name), attr_value); + } + } + + let zarr_data_type = array.data_type(); + if let Ok(arrow_data_type) = + crate::data_types::try_zarrs_dtype_to_arrow(zarr_data_type.clone()) + { + fields.push(arrow::datatypes::Field::new( + array_name, + arrow_data_type, + true, + )); + arrays.insert(array_name.to_string(), array); + } else { + // Unsupported data type, skip adding field + tracing::warn!( + "Skipping array {} with unsupported data type {:?}", + array_name, + zarr_data_type + ); + } + } + + Self { + schema: std::sync::Arc::new(arrow::datatypes::Schema::new(fields)), + group, + arrays, + attributes, + } + } + + pub fn arrow_schema(&self) -> arrow::datatypes::SchemaRef { + self.schema.clone() + } - Self { group } + pub fn arrays(&self) -> &IndexMap> { + &self.arrays + } + + pub fn attributes(&self) -> &IndexMap { + &self.attributes } } @@ -27,9 +105,7 @@ mod tests { use object_store::local::LocalFileSystem; use zarrs::{array::Array, array_subset::ArraySubset, group::Group}; use zarrs_object_store::AsyncObjectStore; - use zarrs_storage::{AsyncListableStorageTraits, AsyncReadableListableStorage}; - - use super::*; + use zarrs_storage::AsyncReadableListableStorage; #[tokio::test] async fn test_name() { @@ -69,8 +145,30 @@ mod tests { let time_arr = Array::async_open(zarr_store.clone(), "/gridded-example.zarr/time") .await .unwrap(); + println!("Time: {:?}", time_arr.chunk_grid()); + println!("Time shape: {:?}", time_arr.chunk_grid_shape()); println!("Time attrs: {:?}", time_arr.attributes()); println!("Time data type: {:?}", time_arr.data_type()); } + + #[tokio::test] + async fn test_arrow_schema() { + let local_fs = LocalFileSystem::new_with_prefix("./test_files").unwrap(); + + let object_store = AsyncObjectStore::new(local_fs); + + let zarr_store: AsyncReadableListableStorage = Arc::new(object_store); + + let group = Group::async_open(zarr_store.clone(), "/gridded-example.zarr") + .await + .unwrap(); + + let reader = super::ArrowGroupReader::new(group).await; + + let schema = reader.arrow_schema(); + for field in schema.fields() { + println!("Field: {:?} : {:?}", field.name(), field.data_type()); + } + } } diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs new file mode 100644 index 00000000..57590903 --- /dev/null +++ b/beacon-arrow-zarr/src/stream.rs @@ -0,0 +1,181 @@ +use std::sync::Arc; + +use arrow::array::BooleanArray; +use indexmap::IndexMap; +use nd_arrow_array::{ + NdArrowArray, + dimensions::{Dimension, Dimensions}, +}; +use zarrs::array_subset::ArraySubset; + +use crate::reader::ArrowGroupReader; + +pub struct ArrowZarrStream { + group_reader: Arc, + projected_schema: arrow::datatypes::SchemaRef, + + // Streaming State + is_done: bool, + dimension_lengths: IndexMap, + chunk_sizes: IndexMap, + chunk_indices: IndexMap, +} + +impl ArrowZarrStream { + pub fn new( + group_reader: Arc, + projected_schema: arrow::datatypes::SchemaRef, + ) -> Result { + // Get all the variables + let variables = projected_schema + .fields() + .iter() + .filter_map(|f| { + if !f.name().contains('.') { + Some(f.name().clone()) + } else { + None + } + }) + .collect::>(); + + // Find the variable with the most dimensions + let max_dims_var = variables + .iter() + .max_by_key(|var_name| { + group_reader + .arrays() + .get(var_name.as_str()) + .map(|array| array.shape().len()) + .unwrap_or(0) + }) + .cloned(); + + let mut chunk_sizes = IndexMap::new(); + let mut dimension_lengths = IndexMap::new(); + if let Some(var_name) = max_dims_var { + let var = group_reader.arrays().get(var_name.as_str()).unwrap(); + let chunk_shape = var.chunk_grid_shape(); + if let Some(dimensions) = var.dimension_names() { + for (i, maybe_dimension) in dimensions.iter().enumerate() { + if let Some(dimension) = maybe_dimension { + chunk_sizes.insert(dimension.clone(), chunk_shape[i]); + dimension_lengths.insert(dimension.clone(), var.shape()[i]); + } else { + return Err(format!( + "Array {} has unnamed dimensions, cannot stream.", + var_name + )); + } + } + } + } + + let mut chunk_indices = IndexMap::new(); + for (dim_name, _) in chunk_sizes.iter() { + chunk_indices.insert(dim_name.clone(), 0); + } + + Ok(Self { + group_reader, + projected_schema, + is_done: false, + dimension_lengths, + chunk_sizes, + chunk_indices, + }) + } + + fn advance_chunk_state(&mut self) { + // Advance like a multi-dimensional counter + for (dim, step) in self.chunk_indices.iter_mut() { + if let Some(d) = self.dimension_lengths.get(dim) { + let size = self.chunk_sizes[dim]; + *step += size; + if *step < *d { + return; // still within bounds + } else { + *step = 0; // reset and carry to next dimension + } + } + } + self.is_done = true; // All dimensions have been processed + } + + fn generate_array_subset(&self, dimensions: &[String]) -> ArraySubset { + let mut ranges = vec![]; + for dim in dimensions { + let chunk_count = self.chunk_sizes.get(dim).cloned().unwrap_or_else(|| { + self.dimension_lengths.get(dim).cloned().unwrap() // Default to full length + }); + let step = self.chunk_indices.get(dim).cloned().unwrap_or(0); + let min_chunk_count = self.dimension_lengths[dim] + .saturating_sub(step) + .min(chunk_count); + + ranges.push(step..step + min_chunk_count); + } + + ArraySubset::new_with_ranges(&ranges) + } + + fn read_attribute(&self, attribute_name: &str) -> Option { + self.group_reader + .attributes() + .get(attribute_name) + .map(|attr| attr.as_nd_arrow_array()) + } + + async fn read_array( + &self, + array_name: &str, + subset: &ArraySubset, + ) -> Result, String> { + let array_reader = if let Some(array) = self.group_reader.arrays().get(array_name) { + array + } else { + return Ok(None); + }; + + let dimensions: Vec = array_reader + .dimension_names() + .as_ref() + .unwrap() + .iter() + .zip(subset.shape().iter()) + .map(|(dim, &len)| Dimension { + name: dim.clone().unwrap(), + size: len as usize, + }) + .collect::>(); + let dimensions = Dimensions::new(dimensions); + + match array_reader.data_type() { + zarrs::array::DataType::Bool => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_boolean_array = BooleanArray::from(array.into_raw_vec_and_offset().0); + + NdArrowArray::new(Arc::new(arrow_boolean_array), dimensions); + + todo!() + } + zarrs::array::DataType::Int8 => todo!(), + zarrs::array::DataType::Int16 => todo!(), + zarrs::array::DataType::Int32 => todo!(), + zarrs::array::DataType::Int64 => todo!(), + zarrs::array::DataType::UInt8 => todo!(), + zarrs::array::DataType::UInt16 => todo!(), + zarrs::array::DataType::UInt32 => todo!(), + zarrs::array::DataType::UInt64 => todo!(), + zarrs::array::DataType::Float32 => todo!(), + zarrs::array::DataType::Float64 => todo!(), + zarrs::array::DataType::String => todo!(), + zarrs::array::DataType::Bytes => todo!(), + _ => todo!(), + } + } +} From d2bb6f4b58596845e7763c38e5d97265ad939139 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Fri, 10 Oct 2025 11:05:29 +0200 Subject: [PATCH 03/19] wip --- Cargo.lock | 2 + beacon-arrow-zarr/Cargo.toml | 4 +- beacon-arrow-zarr/src/decoder.rs | 286 +++++++++++++++++++++++++++++++ beacon-arrow-zarr/src/lib.rs | 1 + beacon-arrow-zarr/src/stream.rs | 179 +++++++++++++++++-- 5 files changed, 455 insertions(+), 17 deletions(-) create mode 100644 beacon-arrow-zarr/src/decoder.rs diff --git a/Cargo.lock b/Cargo.lock index 34900be4..648f834b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,9 +620,11 @@ name = "beacon-arrow-zarr" version = "0.1.0" dependencies = [ "arrow", + "hifitime", "indexmap 2.10.0", "nd-arrow-array", "object_store 0.12.3", + "regex", "serde_json", "tokio", "tracing", diff --git a/beacon-arrow-zarr/Cargo.toml b/beacon-arrow-zarr/Cargo.toml index c1876554..763a5aa9 100644 --- a/beacon-arrow-zarr/Cargo.toml +++ b/beacon-arrow-zarr/Cargo.toml @@ -13,4 +13,6 @@ arrow = { workspace = true } nd-arrow-array = { git = "https://github.com/maris-development/nd-arrow-array.git", branch = "main", version = "1.2.2" } serde_json = { workspace = true } indexmap = { workspace = true } -tracing = { workspace = true } \ No newline at end of file +tracing = { workspace = true } +hifitime = "4.0.2" +regex = "1.11.1" \ No newline at end of file diff --git a/beacon-arrow-zarr/src/decoder.rs b/beacon-arrow-zarr/src/decoder.rs new file mode 100644 index 00000000..7db6490e --- /dev/null +++ b/beacon-arrow-zarr/src/decoder.rs @@ -0,0 +1,286 @@ +use std::{str::FromStr, sync::Arc}; + +use arrow::{ + array::{AsArray, PrimitiveArray, TimestampMillisecondArray}, + datatypes::{Float64Type, Int64Type, SchemaRef, TimestampMillisecondType}, +}; +use hifitime::Epoch; +use indexmap::IndexMap; +use nd_arrow_array::NdArrowArray; +use regex::Regex; + +pub trait Decoder { + fn create( + group_reader: &crate::reader::ArrowGroupReader, + input_schema: SchemaRef, + ) -> Result + where + Self: Sized; + fn decoded_schema(&self) -> SchemaRef; + fn decoding_pipeline(&self, array_name: &str) -> Option>>; +} + +pub trait DecodingPipelineStep { + fn output_data_type(&self) -> arrow::datatypes::DataType; + fn decode(&self, array: NdArrowArray) -> Result; +} + +pub struct CFDecoder { + // Array decoders to apply per array name + array_decoders: std::collections::HashMap>>, + decoded_schema: SchemaRef, +} + +impl Decoder for CFDecoder { + fn create( + group_reader: &crate::reader::ArrowGroupReader, + input_schema: SchemaRef, + ) -> Result { + let mut array_decoders: std::collections::HashMap> = + std::collections::HashMap::new(); + let mut fields = IndexMap::new(); + for field in input_schema.fields() { + fields.insert(field.name().clone(), field.as_ref().clone()); + } + + for (array_name, _) in group_reader.arrays() { + // Get all the attributes that start with array_name + "." + let prefix = format!("{}.", array_name); + let array_attribute: Vec<_> = group_reader + .arrow_schema() + .fields() + .iter() + .filter(|field| field.name().starts_with(&prefix)) + .cloned() + .collect(); + + // Check for scale_factor and add_offset attributes (SCALE_FACTOR and ADD_OFFSET in CF conventions) + + let mut offset = None; + let mut scale_factor = None; + + for field in &array_attribute { + if *field.name() == format!("{}.scale_factor", array_name) { + match group_reader.attributes()[field.name()] { + crate::attributes::AttributeValue::Float64(value) => { + scale_factor = Some(value) + } + _ => { + return Err(format!( + "scale_factor attribute for array {} is not Float64", + array_name + )); + } + } + } else if *field.name() == format!("{}.add_offset", array_name) { + match group_reader.attributes()[field.name()] { + crate::attributes::AttributeValue::Float64(value) => offset = Some(value), + _ => { + return Err(format!( + "add_offset attribute for array {} is not Float64", + array_name + )); + } + } + } + } + + let decoding_pipeline = match (scale_factor, offset) { + (Some(scale), Some(offset)) => { + Some(Arc::new(ScaleFactorOffsetDecoder::new(scale, offset)) + as Arc) + } + (Some(scale), None) => Some(Arc::new(ScaleFactorOffsetDecoder::new(scale, 0.0)) + as Arc), + (None, Some(offset)) => Some(Arc::new(ScaleFactorOffsetDecoder::new(1.0, offset)) + as Arc), + _ => None, + }; + + if let Some(decoding_pipeline) = decoding_pipeline { + array_decoders.insert(array_name.to_string(), decoding_pipeline.clone()); + // Update the field data type to Float64 + let array_field = fields.get_mut(array_name).unwrap(); + *array_field = array_field + .clone() + .with_data_type(decoding_pipeline.output_data_type()); + } + + // Check for cf time attributes (units) + for field in &array_attribute { + if *field.name() == format!("{}.units", array_name) + && let crate::attributes::AttributeValue::String(ref value) = + group_reader.attributes()[field.name()] + { + // Check if units is a valid CF time unit + if value.starts_with("seconds since") + || value.starts_with("minutes since") + || value.starts_with("hours since") + || value.starts_with("days since") + || value.starts_with("weeks since") + || value.starts_with("months since") + || value.starts_with("years since") + { + match TimeUnitsDecoder::new(value.clone()) { + Ok(decoder) => { + let decoder = Arc::new(decoder) as Arc; + array_decoders.insert(array_name.to_string(), decoder.clone()); + // Update the field data type to Int64 (Arrow timestamp in milliseconds) + let array_field = fields.get_mut(array_name).unwrap(); + *array_field = array_field + .clone() + .with_data_type(decoder.output_data_type()); + } + Err(e) => { + tracing::warn!( + "Could not create TimeUnitsDecoder for array {}: {}", + array_name, + e + ); + } + } + } + } + } + } + + Ok(Self { + array_decoders: array_decoders + .into_iter() + .map(|(k, v)| (k, vec![v])) + .collect(), + decoded_schema: Arc::new(arrow::datatypes::Schema::new( + fields.into_values().collect::>(), + )), + }) + } + + fn decoded_schema(&self) -> SchemaRef { + self.decoded_schema.clone() + } + + fn decoding_pipeline(&self, array_name: &str) -> Option>> { + self.array_decoders.get(array_name).cloned() + } +} + +pub struct ScaleFactorOffsetDecoder { + scale: f64, + offset: f64, +} + +impl ScaleFactorOffsetDecoder { + pub fn new(scale: f64, offset: f64) -> Self { + ScaleFactorOffsetDecoder { scale, offset } + } +} + +impl DecodingPipelineStep for ScaleFactorOffsetDecoder { + fn decode(&self, array: NdArrowArray) -> Result { + let arrow_array = array.as_arrow_array(); + + // Cast to float64 for processing + let float_array = + arrow::compute::cast(&arrow_array, &arrow::datatypes::DataType::Float64).unwrap(); + + let float_array = float_array.as_primitive::(); + + let applied_array: PrimitiveArray = + float_array.unary(|value| value * self.scale + self.offset); + + Ok(NdArrowArray::new( + Arc::new(applied_array) as arrow::array::ArrayRef, + array.dimensions().clone(), + ) + .unwrap()) + } + + fn output_data_type(&self) -> arrow::datatypes::DataType { + arrow::datatypes::DataType::Float64 + } +} + +pub struct TimeUnitsDecoder { + unit: hifitime::Unit, + epoch: Epoch, +} + +impl TimeUnitsDecoder { + pub fn new(units_str: String) -> Result { + let unit = Self::extract_units(&units_str) + .ok_or(format!("Unsupported CF time units: {}", units_str))?; + let epoch = Self::extract_epoch(&units_str).ok_or(format!( + "Could not extract epoch from CF time units: {}", + units_str + ))?; + Ok(TimeUnitsDecoder { unit, epoch }) + } + + fn convert_array( + &self, + array: &PrimitiveArray, + unit: hifitime::Unit, + epoch: Epoch, + ) -> TimestampMillisecondArray { + let data: PrimitiveArray = + array.unary(|v| (epoch + (v * unit)).to_unix_milliseconds() as i64); + + // Reinterpret the Int64 array as TimestampMillisecond + data.reinterpret_cast::() + } + + fn extract_units(input: &str) -> Option { + let re = Regex::new(r"^(?P\w+) since").unwrap(); + re.captures(input) + .and_then(|caps| match caps["units"].to_string().as_str() { + "seconds" => Some(hifitime::Unit::Second), + "milliseconds" => Some(hifitime::Unit::Millisecond), + "microseconds" => Some(hifitime::Unit::Microsecond), + "nanoseconds" => Some(hifitime::Unit::Nanosecond), + "days" => Some(hifitime::Unit::Day), + "weeks" => Some(hifitime::Unit::Week), + _ => None, + }) + } + + /// Extracts the epoch date from a string like "days since -4713-11-24" + fn extract_epoch(input: &str) -> Option { + let re = Regex::new(r"since (?P-?\d{1,4}-\d{1,2}-\d{1,2})").unwrap(); + let result = re.captures(input).and_then(|caps| { + let epoch_str = caps["epoch"].to_string(); + let mut epoch = Epoch::from_str(&epoch_str).ok(); + + if epoch.is_none() && epoch_str == "-4713-01-01" { + epoch = Some(Epoch::from_jde_utc(0.0)); + } + + epoch + }); + + result + } +} + +impl DecodingPipelineStep for TimeUnitsDecoder { + fn output_data_type(&self) -> arrow::datatypes::DataType { + arrow::datatypes::DataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None) + } + + fn decode(&self, array: NdArrowArray) -> Result { + let arrow_array = array.as_arrow_array(); + + // Cast to Int64 for processing + let int_array = + arrow::compute::cast(&arrow_array, &arrow::datatypes::DataType::Int64).unwrap(); + + let int_array = int_array.as_primitive::(); + + let applied_array = self.convert_array(int_array, self.unit, self.epoch); + + Ok(NdArrowArray::new( + Arc::new(applied_array) as arrow::array::ArrayRef, + array.dimensions().clone(), + ) + .unwrap()) + } +} diff --git a/beacon-arrow-zarr/src/lib.rs b/beacon-arrow-zarr/src/lib.rs index 6318fed2..c2587ce3 100644 --- a/beacon-arrow-zarr/src/lib.rs +++ b/beacon-arrow-zarr/src/lib.rs @@ -1,4 +1,5 @@ pub mod attributes; pub mod data_types; +pub mod decoder; pub mod reader; pub mod stream; diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs index 57590903..f8ea21cb 100644 --- a/beacon-arrow-zarr/src/stream.rs +++ b/beacon-arrow-zarr/src/stream.rs @@ -8,11 +8,12 @@ use nd_arrow_array::{ }; use zarrs::array_subset::ArraySubset; -use crate::reader::ArrowGroupReader; +use crate::{decoder::Decoder, reader::ArrowGroupReader}; pub struct ArrowZarrStream { group_reader: Arc, projected_schema: arrow::datatypes::SchemaRef, + decoders: Vec>, // Streaming State is_done: bool, @@ -25,6 +26,7 @@ impl ArrowZarrStream { pub fn new( group_reader: Arc, projected_schema: arrow::datatypes::SchemaRef, + decoders: Vec>, ) -> Result { // Get all the variables let variables = projected_schema @@ -77,6 +79,7 @@ impl ArrowZarrStream { } Ok(Self { + decoders, group_reader, projected_schema, is_done: false, @@ -159,23 +162,167 @@ impl ArrowZarrStream { let arrow_boolean_array = BooleanArray::from(array.into_raw_vec_and_offset().0); - NdArrowArray::new(Arc::new(arrow_boolean_array), dimensions); + let nd_array = NdArrowArray::new(Arc::new(arrow_boolean_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Int8 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_int8_array = + arrow::array::Int8Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_int8_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Int16 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_int16_array = + arrow::array::Int16Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_int16_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Int32 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_int32_array = + arrow::array::Int32Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_int32_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Int64 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_int64_array = + arrow::array::Int64Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_int64_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::UInt8 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_uint8_array = + arrow::array::UInt8Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_uint8_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::UInt16 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_uint16_array = + arrow::array::UInt16Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_uint16_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::UInt32 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_uint32_array = + arrow::array::UInt32Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_uint32_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::UInt64 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_uint64_array = + arrow::array::UInt64Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_uint64_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Float32 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_float32_array = + arrow::array::Float32Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_float32_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Float64 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_float64_array = + arrow::array::Float64Array::from(array.into_raw_vec_and_offset().0); - todo!() + let nd_array = NdArrowArray::new(Arc::new(arrow_float64_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::String => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_string_array = + arrow::array::StringArray::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_string_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Bytes => { + let array = array_reader + .async_retrieve_array_subset_ndarray::>(&subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_binary_array = + arrow::array::BinaryArray::from_iter_values(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_binary_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) } - zarrs::array::DataType::Int8 => todo!(), - zarrs::array::DataType::Int16 => todo!(), - zarrs::array::DataType::Int32 => todo!(), - zarrs::array::DataType::Int64 => todo!(), - zarrs::array::DataType::UInt8 => todo!(), - zarrs::array::DataType::UInt16 => todo!(), - zarrs::array::DataType::UInt32 => todo!(), - zarrs::array::DataType::UInt64 => todo!(), - zarrs::array::DataType::Float32 => todo!(), - zarrs::array::DataType::Float64 => todo!(), - zarrs::array::DataType::String => todo!(), - zarrs::array::DataType::Bytes => todo!(), - _ => todo!(), + zarr_data_type => Err(format!("Unsupported Zarrs data type: {:?}", zarr_data_type)), } } } From 3a112ebfdf8eb656d3970a9d1306f0c19adb3a30 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Fri, 10 Oct 2025 17:34:16 +0200 Subject: [PATCH 04/19] wip --- Cargo.lock | 3 + beacon-arrow-zarr/Cargo.toml | 5 +- beacon-arrow-zarr/src/decoder.rs | 80 +++++++++--- beacon-arrow-zarr/src/stream.rs | 212 ++++++++++++++++++++++--------- 4 files changed, 221 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 648f834b..4893cebd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,10 +620,13 @@ name = "beacon-arrow-zarr" version = "0.1.0" dependencies = [ "arrow", + "futures", "hifitime", "indexmap 2.10.0", "nd-arrow-array", "object_store 0.12.3", + "parking_lot", + "pin-project", "regex", "serde_json", "tokio", diff --git a/beacon-arrow-zarr/Cargo.toml b/beacon-arrow-zarr/Cargo.toml index 763a5aa9..fe7c73dd 100644 --- a/beacon-arrow-zarr/Cargo.toml +++ b/beacon-arrow-zarr/Cargo.toml @@ -15,4 +15,7 @@ serde_json = { workspace = true } indexmap = { workspace = true } tracing = { workspace = true } hifitime = "4.0.2" -regex = "1.11.1" \ No newline at end of file +regex = "1.11.1" +futures = { workspace = true } +pin-project = "1.1.10" +parking_lot = { workspace = true } \ No newline at end of file diff --git a/beacon-arrow-zarr/src/decoder.rs b/beacon-arrow-zarr/src/decoder.rs index 7db6490e..791eee7c 100644 --- a/beacon-arrow-zarr/src/decoder.rs +++ b/beacon-arrow-zarr/src/decoder.rs @@ -6,10 +6,10 @@ use arrow::{ }; use hifitime::Epoch; use indexmap::IndexMap; -use nd_arrow_array::NdArrowArray; +use nd_arrow_array::{NdArrowArray, batch::NdRecordBatch}; use regex::Regex; -pub trait Decoder { +pub trait Decoder: Send + Sync { fn create( group_reader: &crate::reader::ArrowGroupReader, input_schema: SchemaRef, @@ -17,10 +17,45 @@ pub trait Decoder { where Self: Sized; fn decoded_schema(&self) -> SchemaRef; - fn decoding_pipeline(&self, array_name: &str) -> Option>>; + fn decoding_array_pipeline( + &self, + array_name: &str, + ) -> Option>>; + fn decode_batch(&self, batch: NdRecordBatch) -> Result { + let mut arrays = Vec::new(); + let mut fields = Vec::new(); + for (field, array) in batch.schema().fields().iter().zip(batch.arrays()) { + if let Some(pipeline) = self.decoding_array_pipeline(field.name()) { + let mut decoded_array = array.clone(); + for step in &pipeline { + decoded_array = step.decode(decoded_array)?; + } + arrays.push(decoded_array); + fields.push( + field + .as_ref() + .clone() + .with_data_type(pipeline.last().unwrap().output_data_type()), + ); + } else { + arrays.push(array.clone()); + fields.push(field.as_ref().clone()); + } + } + + let decoded_batch = NdRecordBatch::new(fields, arrays).unwrap(); + // Ensure the schema is the same as decoded_schema + assert_eq!( + decoded_batch.schema(), + self.decoded_schema(), + "Decoded batch schema does not match decoder's decoded schema" + ); + + Ok(decoded_batch) + } } -pub trait DecodingPipelineStep { +pub trait DecodingPipelineStep: Send + Sync { fn output_data_type(&self) -> arrow::datatypes::DataType; fn decode(&self, array: NdArrowArray) -> Result; } @@ -87,13 +122,17 @@ impl Decoder for CFDecoder { let decoding_pipeline = match (scale_factor, offset) { (Some(scale), Some(offset)) => { - Some(Arc::new(ScaleFactorOffsetDecoder::new(scale, offset)) + Some(Arc::new(ScaleFactorOffsetDecodingStep::new(scale, offset)) + as Arc) + } + (Some(scale), None) => { + Some(Arc::new(ScaleFactorOffsetDecodingStep::new(scale, 0.0)) + as Arc) + } + (None, Some(offset)) => { + Some(Arc::new(ScaleFactorOffsetDecodingStep::new(1.0, offset)) as Arc) } - (Some(scale), None) => Some(Arc::new(ScaleFactorOffsetDecoder::new(scale, 0.0)) - as Arc), - (None, Some(offset)) => Some(Arc::new(ScaleFactorOffsetDecoder::new(1.0, offset)) - as Arc), _ => None, }; @@ -121,7 +160,7 @@ impl Decoder for CFDecoder { || value.starts_with("months since") || value.starts_with("years since") { - match TimeUnitsDecoder::new(value.clone()) { + match TimeUnitsDecodingStep::new(value.clone()) { Ok(decoder) => { let decoder = Arc::new(decoder) as Arc; array_decoders.insert(array_name.to_string(), decoder.clone()); @@ -159,23 +198,26 @@ impl Decoder for CFDecoder { self.decoded_schema.clone() } - fn decoding_pipeline(&self, array_name: &str) -> Option>> { + fn decoding_array_pipeline( + &self, + array_name: &str, + ) -> Option>> { self.array_decoders.get(array_name).cloned() } } -pub struct ScaleFactorOffsetDecoder { +pub struct ScaleFactorOffsetDecodingStep { scale: f64, offset: f64, } -impl ScaleFactorOffsetDecoder { +impl ScaleFactorOffsetDecodingStep { pub fn new(scale: f64, offset: f64) -> Self { - ScaleFactorOffsetDecoder { scale, offset } + ScaleFactorOffsetDecodingStep { scale, offset } } } -impl DecodingPipelineStep for ScaleFactorOffsetDecoder { +impl DecodingPipelineStep for ScaleFactorOffsetDecodingStep { fn decode(&self, array: NdArrowArray) -> Result { let arrow_array = array.as_arrow_array(); @@ -200,12 +242,12 @@ impl DecodingPipelineStep for ScaleFactorOffsetDecoder { } } -pub struct TimeUnitsDecoder { +pub struct TimeUnitsDecodingStep { unit: hifitime::Unit, epoch: Epoch, } -impl TimeUnitsDecoder { +impl TimeUnitsDecodingStep { pub fn new(units_str: String) -> Result { let unit = Self::extract_units(&units_str) .ok_or(format!("Unsupported CF time units: {}", units_str))?; @@ -213,7 +255,7 @@ impl TimeUnitsDecoder { "Could not extract epoch from CF time units: {}", units_str ))?; - Ok(TimeUnitsDecoder { unit, epoch }) + Ok(TimeUnitsDecodingStep { unit, epoch }) } fn convert_array( @@ -261,7 +303,7 @@ impl TimeUnitsDecoder { } } -impl DecodingPipelineStep for TimeUnitsDecoder { +impl DecodingPipelineStep for TimeUnitsDecodingStep { fn output_data_type(&self) -> arrow::datatypes::DataType { arrow::datatypes::DataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None) } diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs index f8ea21cb..d9f6e20f 100644 --- a/beacon-arrow-zarr/src/stream.rs +++ b/beacon-arrow-zarr/src/stream.rs @@ -1,28 +1,34 @@ -use std::sync::Arc; +use std::{ + pin::Pin, + sync::{ + Arc, + atomic::{AtomicBool, AtomicU64, Ordering}, + }, + task::Poll, +}; use arrow::array::BooleanArray; +use futures::{Future, Stream}; use indexmap::IndexMap; use nd_arrow_array::{ NdArrowArray, + batch::NdRecordBatch, dimensions::{Dimension, Dimensions}, }; use zarrs::array_subset::ArraySubset; use crate::{decoder::Decoder, reader::ArrowGroupReader}; -pub struct ArrowZarrStream { +#[derive(Clone)] +pub struct ArrowZarrStreamComposer { group_reader: Arc, projected_schema: arrow::datatypes::SchemaRef, decoders: Vec>, - // Streaming State - is_done: bool, - dimension_lengths: IndexMap, - chunk_sizes: IndexMap, - chunk_indices: IndexMap, + state: Arc>, } -impl ArrowZarrStream { +impl ArrowZarrStreamComposer { pub fn new( group_reader: Arc, projected_schema: arrow::datatypes::SchemaRef, @@ -75,53 +81,17 @@ impl ArrowZarrStream { let mut chunk_indices = IndexMap::new(); for (dim_name, _) in chunk_sizes.iter() { - chunk_indices.insert(dim_name.clone(), 0); + chunk_indices.insert(dim_name.clone(), Arc::new(AtomicU64::new(0))); } Ok(Self { decoders, group_reader, projected_schema, - is_done: false, - dimension_lengths, - chunk_sizes, - chunk_indices, + state: todo!(), }) } - fn advance_chunk_state(&mut self) { - // Advance like a multi-dimensional counter - for (dim, step) in self.chunk_indices.iter_mut() { - if let Some(d) = self.dimension_lengths.get(dim) { - let size = self.chunk_sizes[dim]; - *step += size; - if *step < *d { - return; // still within bounds - } else { - *step = 0; // reset and carry to next dimension - } - } - } - self.is_done = true; // All dimensions have been processed - } - - fn generate_array_subset(&self, dimensions: &[String]) -> ArraySubset { - let mut ranges = vec![]; - for dim in dimensions { - let chunk_count = self.chunk_sizes.get(dim).cloned().unwrap_or_else(|| { - self.dimension_lengths.get(dim).cloned().unwrap() // Default to full length - }); - let step = self.chunk_indices.get(dim).cloned().unwrap_or(0); - let min_chunk_count = self.dimension_lengths[dim] - .saturating_sub(step) - .min(chunk_count); - - ranges.push(step..step + min_chunk_count); - } - - ArraySubset::new_with_ranges(&ranges) - } - fn read_attribute(&self, attribute_name: &str) -> Option { self.group_reader .attributes() @@ -156,7 +126,7 @@ impl ArrowZarrStream { match array_reader.data_type() { zarrs::array::DataType::Bool => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -168,7 +138,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::Int8 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -181,7 +151,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::Int16 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -194,7 +164,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::Int32 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -207,7 +177,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::Int64 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -220,7 +190,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::UInt8 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -233,7 +203,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::UInt16 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -246,7 +216,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::UInt32 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -259,7 +229,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::UInt64 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -272,7 +242,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::Float32 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -285,7 +255,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::Float64 => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -298,7 +268,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::String => { let array = array_reader - .async_retrieve_array_subset_ndarray::(&subset) + .async_retrieve_array_subset_ndarray::(subset) .await .map_err(|e| e.to_string())?; @@ -311,7 +281,7 @@ impl ArrowZarrStream { } zarrs::array::DataType::Bytes => { let array = array_reader - .async_retrieve_array_subset_ndarray::>(&subset) + .async_retrieve_array_subset_ndarray::>(subset) .await .map_err(|e| e.to_string())?; @@ -325,4 +295,128 @@ impl ArrowZarrStream { zarr_data_type => Err(format!("Unsupported Zarrs data type: {:?}", zarr_data_type)), } } + + pub fn next_state(&self) -> ArrowZarrStreamState { + todo!() + } +} + +#[derive(Debug, Clone)] +pub struct ArrowZarrStreamState { + is_done: bool, + dimension_lengths: Arc>, + chunk_sizes: Arc>, + chunk_indices: IndexMap, +} + +impl ArrowZarrStreamState { + pub fn new( + dimension_lengths: IndexMap, + chunk_sizes: IndexMap, + ) -> Self { + let mut chunk_indices = IndexMap::new(); + for (dim_name, _) in chunk_sizes.iter() { + chunk_indices.insert(dim_name.clone(), 0); + } + + Self { + is_done: false, + dimension_lengths: Arc::new(dimension_lengths), + chunk_sizes: Arc::new(chunk_sizes), + chunk_indices, + } + } + + fn is_done(&self) -> bool { + self.is_done + } + + fn advance_chunk_state(&mut self) { + // Advance like a multi-dimensional counter + for (dim, step) in self.chunk_indices.iter_mut() { + if let Some(d) = self.dimension_lengths.get(dim) { + let size = self.chunk_sizes[dim]; + *step += size; + if *step < *d { + return; // still within bounds + } else { + *step = 0; // reset and carry to next dimension + } + } + } + self.is_done = true; // All dimensions exhausted + } + + fn generate_array_subset(&self, dimensions: &[String]) -> ArraySubset { + let mut ranges = vec![]; + for dim in dimensions { + let chunk_count = self.chunk_sizes.get(dim).cloned().unwrap_or_else(|| { + self.dimension_lengths.get(dim).cloned().unwrap() // Default to full length + }); + let step = self.chunk_indices.get(dim).map(|s| *s).unwrap_or(0); + let min_chunk_count = self.dimension_lengths[dim] + .saturating_sub(step) + .min(chunk_count); + + ranges.push(step..step + min_chunk_count); + } + + ArraySubset::new_with_ranges(&ranges) + } +} + +#[pin_project::pin_project] +pub struct ArrowZarrStream { + composer: Arc, + // Pinned state + #[pin] + ongoing: Option>> + Send>>>, +} + +impl ArrowZarrStream { + pub async fn next_chunk( + composer: Arc, + ) -> Option> { + let state = composer.next_state(); + if state.is_done() { + return None; + } + + let mut arrays = vec![]; + let mut fields = vec![]; + for column in composer.projected_schema.fields().iter() { + let array_name = column.name(); + fields.push(column.as_ref().clone()); + + composer.read_array(array_name, subset) + } + + todo!() + } +} + +impl Stream for ArrowZarrStream { + type Item = Result; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let mut pinned_stream = self.project(); + let composer = pinned_stream.composer.clone(); + + if pinned_stream.ongoing.is_none() { + *pinned_stream.ongoing = Some(Box::pin(Self::next_chunk(composer)) as _); + } + + let fut = pinned_stream.ongoing.as_mut().as_pin_mut().unwrap(); + match fut.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Some(value)) => { + *pinned_stream.ongoing = None; + Poll::Ready(Some(value)) + } + Poll::Ready(None) => Poll::Ready(None), + } + } } From eb984f409eb5e2036f10d1bf9c8f8917e69b80fc Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Sun, 12 Oct 2025 15:38:26 +0200 Subject: [PATCH 05/19] wip --- Cargo.lock | 23 ++ Cargo.toml | 2 +- beacon-arrow-zarr/Cargo.toml | 3 +- beacon-arrow-zarr/src/decoder.rs | 445 +++++++++++++++++++++++++++---- beacon-arrow-zarr/src/reader.rs | 354 +++++++++++++++++++++++- beacon-arrow-zarr/src/stream.rs | 427 ++++++++--------------------- 6 files changed, 873 insertions(+), 381 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4893cebd..b7a09ff5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -620,6 +620,7 @@ name = "beacon-arrow-zarr" version = "0.1.0" dependencies = [ "arrow", + "crossbeam", "futures", "hifitime", "indexmap 2.10.0", @@ -1134,6 +1135,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1162,6 +1176,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" diff --git a/Cargo.toml b/Cargo.toml index e80d232b..5888abb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,12 +23,12 @@ thiserror = "2.0.12" tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } - glob = "0.3.2" tempfile = "3.15.0" typetag = "0.2.19" indexmap = { version = "2.7.1", features = ["serde"]} chrono = { version = "0.4.41", features = ["serde"] } +crossbeam = "0.8.4" datafusion = "49.0.0" object_store = { version = "0.12.3", features = ["aws"] } diff --git a/beacon-arrow-zarr/Cargo.toml b/beacon-arrow-zarr/Cargo.toml index fe7c73dd..3e14c58c 100644 --- a/beacon-arrow-zarr/Cargo.toml +++ b/beacon-arrow-zarr/Cargo.toml @@ -18,4 +18,5 @@ hifitime = "4.0.2" regex = "1.11.1" futures = { workspace = true } pin-project = "1.1.10" -parking_lot = { workspace = true } \ No newline at end of file +parking_lot = { workspace = true } +crossbeam = { workspace = true } \ No newline at end of file diff --git a/beacon-arrow-zarr/src/decoder.rs b/beacon-arrow-zarr/src/decoder.rs index 791eee7c..c434f69e 100644 --- a/beacon-arrow-zarr/src/decoder.rs +++ b/beacon-arrow-zarr/src/decoder.rs @@ -1,8 +1,14 @@ use std::{str::FromStr, sync::Arc}; use arrow::{ - array::{AsArray, PrimitiveArray, TimestampMillisecondArray}, - datatypes::{Float64Type, Int64Type, SchemaRef, TimestampMillisecondType}, + array::{ + ArrayRef, AsArray, BinaryArray, BooleanArray, PrimitiveArray, StringArray, + TimestampMillisecondArray, + }, + datatypes::{ + Float32Type, Float64Type, Int8Type, Int16Type, Int32Type, Int64Type, SchemaRef, + TimestampMillisecondType, UInt8Type, UInt16Type, UInt32Type, UInt64Type, + }, }; use hifitime::Epoch; use indexmap::IndexMap; @@ -11,7 +17,7 @@ use regex::Regex; pub trait Decoder: Send + Sync { fn create( - group_reader: &crate::reader::ArrowGroupReader, + group_reader: &crate::reader::AsyncArrowZarrGroupReader, input_schema: SchemaRef, ) -> Result where @@ -21,43 +27,356 @@ pub trait Decoder: Send + Sync { &self, array_name: &str, ) -> Option>>; - fn decode_batch(&self, batch: NdRecordBatch) -> Result { - let mut arrays = Vec::new(); - let mut fields = Vec::new(); - for (field, array) in batch.schema().fields().iter().zip(batch.arrays()) { - if let Some(pipeline) = self.decoding_array_pipeline(field.name()) { - let mut decoded_array = array.clone(); - for step in &pipeline { - decoded_array = step.decode(decoded_array)?; - } - arrays.push(decoded_array); - fields.push( - field - .as_ref() - .clone() - .with_data_type(pipeline.last().unwrap().output_data_type()), + fn decode_array(&self, array_name: &str, array: NdArrowArray) -> Result { + if let Some(pipeline) = self.decoding_array_pipeline(array_name) { + pipeline.iter().fold(Ok(array), |maybe_array, step| { + maybe_array.and_then(|array| step.decode(array)) + }) + } else { + Ok(array) + } + } +} + +pub trait DecodingPipelineStep: Send + Sync { + fn output_data_type( + &self, + input_data_type: arrow::datatypes::DataType, + ) -> arrow::datatypes::DataType { + input_data_type + } + fn decode(&self, array: NdArrowArray) -> Result; +} + +pub struct FillValueDecoder { + // Array decoders to apply per array name + array_fill_decoders: std::collections::HashMap>, + decoded_schema: SchemaRef, +} + +impl Decoder for FillValueDecoder { + fn create( + group_reader: &crate::reader::AsyncArrowZarrGroupReader, + input_schema: SchemaRef, + ) -> Result + where + Self: Sized, + { + let mut array_fill_decoders = std::collections::HashMap::new(); + for (array_name, array_reader) in group_reader.arrays() { + // Add a FillValueDecodingStep if the array has a fill value + if let Some(arrow_fill_value) = ArrowFillValue::from_zarrs_fill_value( + array_reader.data_type(), + array_reader.fill_value(), + ) { + let fill_value_decoder = FillValueDecodingStep { + fill_value: arrow_fill_value, + }; + array_fill_decoders.insert( + array_name.to_string(), + Arc::new(fill_value_decoder) as Arc, ); - } else { - arrays.push(array.clone()); - fields.push(field.as_ref().clone()); } } + Ok(Self { + array_fill_decoders, + decoded_schema: input_schema, + }) + } - let decoded_batch = NdRecordBatch::new(fields, arrays).unwrap(); - // Ensure the schema is the same as decoded_schema - assert_eq!( - decoded_batch.schema(), - self.decoded_schema(), - "Decoded batch schema does not match decoder's decoded schema" - ); + fn decoded_schema(&self) -> SchemaRef { + self.decoded_schema.clone() + } - Ok(decoded_batch) + fn decoding_array_pipeline( + &self, + array_name: &str, + ) -> Option>> { + self.array_fill_decoders + .get(array_name) + .map(|decoder| vec![decoder.clone()]) } } -pub trait DecodingPipelineStep: Send + Sync { - fn output_data_type(&self) -> arrow::datatypes::DataType; - fn decode(&self, array: NdArrowArray) -> Result; +pub struct FillValueDecodingStep { + fill_value: ArrowFillValue, +} + +pub enum ArrowFillValue { + Bool(bool), + Int8(i8), + Int16(i16), + Int32(i32), + Int64(i64), + UInt8(u8), + UInt16(u16), + UInt32(u32), + UInt64(u64), + Float32(f32), + Float64(f64), + String(String), + Bytes(Vec), +} + +impl ArrowFillValue { + pub fn from_zarrs_fill_value( + array_data_type: &zarrs::array::DataType, + fill_bytes: &zarrs::array::FillValue, + ) -> Option { + match (array_data_type) { + zarrs::array::DataType::Bool => { + if fill_bytes.as_ne_bytes().len() == 1 { + Some(ArrowFillValue::Bool(fill_bytes.as_ne_bytes()[0] != 0)) + } else { + None + } + } + zarrs::array::DataType::Int8 => { + if fill_bytes.as_ne_bytes().len() == 1 { + Some(ArrowFillValue::Int8(fill_bytes.as_ne_bytes()[0] as i8)) + } else { + None + } + } + zarrs::array::DataType::Int16 => { + if fill_bytes.as_ne_bytes().len() == 2 { + let bytes = fill_bytes.as_ne_bytes()[0..2] + .try_into() + .expect("slice with incorrect length"); + Some(ArrowFillValue::Int16(i16::from_ne_bytes(bytes))) + } else { + None + } + } + zarrs::array::DataType::Int32 => { + if fill_bytes.as_ne_bytes().len() == 4 { + let bytes = fill_bytes.as_ne_bytes()[0..4] + .try_into() + .expect("slice with incorrect length"); + Some(ArrowFillValue::Int32(i32::from_ne_bytes(bytes))) + } else { + None + } + } + zarrs::array::DataType::Int64 => { + if fill_bytes.as_ne_bytes().len() == 8 { + let bytes = fill_bytes.as_ne_bytes()[0..8] + .try_into() + .expect("slice with incorrect length"); + Some(ArrowFillValue::Int64(i64::from_ne_bytes(bytes))) + } else { + None + } + } + zarrs::array::DataType::UInt8 => { + if fill_bytes.as_ne_bytes().len() == 1 { + Some(ArrowFillValue::UInt8(fill_bytes.as_ne_bytes()[0])) + } else { + None + } + } + zarrs::array::DataType::UInt16 => { + if fill_bytes.as_ne_bytes().len() == 2 { + let bytes = fill_bytes.as_ne_bytes()[0..2] + .try_into() + .expect("slice with incorrect length"); + Some(ArrowFillValue::UInt16(u16::from_ne_bytes(bytes))) + } else { + None + } + } + zarrs::array::DataType::UInt32 => { + if fill_bytes.as_ne_bytes().len() == 4 { + let bytes = fill_bytes.as_ne_bytes()[0..4] + .try_into() + .expect("slice with incorrect length"); + Some(ArrowFillValue::UInt32(u32::from_ne_bytes(bytes))) + } else { + None + } + } + + zarrs::array::DataType::UInt64 => { + if fill_bytes.as_ne_bytes().len() == 8 { + let bytes = fill_bytes.as_ne_bytes()[0..8] + .try_into() + .expect("slice with incorrect length"); + Some(ArrowFillValue::UInt64(u64::from_ne_bytes(bytes))) + } else { + None + } + } + + zarrs::array::DataType::Float32 => { + if fill_bytes.as_ne_bytes().len() == 4 { + let bytes = fill_bytes.as_ne_bytes()[0..4] + .try_into() + .expect("slice with incorrect length"); + Some(ArrowFillValue::Float32(f32::from_ne_bytes(bytes))) + } else { + None + } + } + + zarrs::array::DataType::Float64 => { + if fill_bytes.as_ne_bytes().len() == 8 { + let bytes = fill_bytes.as_ne_bytes()[0..8] + .try_into() + .expect("slice with incorrect length"); + Some(ArrowFillValue::Float64(f64::from_ne_bytes(bytes))) + } else { + None + } + } + + zarrs::array::DataType::String => { + // Interpret fill_bytes as UTF-8 string + match std::str::from_utf8(fill_bytes.as_ne_bytes()) { + Ok(s) => Some(ArrowFillValue::String(s.to_string())), + Err(_) => None, + } + } + + zarrs::array::DataType::Bytes => { + // Interpret fill_bytes as raw bytes + Some(ArrowFillValue::Bytes(fill_bytes.as_ne_bytes().to_vec())) + } + + _ => None, + } + } +} + +impl DecodingPipelineStep for FillValueDecodingStep { + fn output_data_type( + &self, + input_data_type: arrow::datatypes::DataType, + ) -> arrow::datatypes::DataType { + input_data_type + } + + fn decode(&self, array: NdArrowArray) -> Result { + let arrow_array = array.as_arrow_array(); + let nullable_array: ArrayRef = match (arrow_array.data_type(), &self.fill_value) { + (arrow::datatypes::DataType::Null, _) => return Ok(array.clone()), + (arrow::datatypes::DataType::Boolean, ArrowFillValue::Bool(fill_value)) => { + let bool_array = array.as_boolean(); + let filled_array: BooleanArray = bool_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::Int8, ArrowFillValue::Int8(fill_value)) => { + let int_array = array.as_primitive::(); + let filled_array: PrimitiveArray = int_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::Int16, ArrowFillValue::Int16(fill_value)) => { + let int_array = array.as_primitive::(); + let filled_array: PrimitiveArray = int_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::Int32, ArrowFillValue::Int32(fill_value)) => { + let int_array = array.as_primitive::(); + let filled_array: PrimitiveArray = int_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::Int64, ArrowFillValue::Int64(fill_value)) => { + let int_array = array.as_primitive::(); + let filled_array: PrimitiveArray = int_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::UInt8, ArrowFillValue::UInt8(fill_value)) => { + let int_array = array.as_primitive::(); + let filled_array: PrimitiveArray = int_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::UInt16, ArrowFillValue::UInt16(fill_value)) => { + let int_array = array.as_primitive::(); + let filled_array: PrimitiveArray = int_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + + (arrow::datatypes::DataType::UInt32, ArrowFillValue::UInt32(fill_value)) => { + let int_array = array.as_primitive::(); + let filled_array: PrimitiveArray = int_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + + (arrow::datatypes::DataType::UInt64, ArrowFillValue::UInt64(fill_value)) => { + let int_array = array.as_primitive::(); + let filled_array: PrimitiveArray = int_array + .iter() + .map(|v| v.filter(|&value| value != *fill_value)) + .collect(); + Arc::new(filled_array) + } + + (arrow::datatypes::DataType::Float32, ArrowFillValue::Float32(value)) => { + let float_array = array.as_primitive::(); + let filled_array: PrimitiveArray = float_array + .iter() + .map(|v| v.filter(|&v| v != *value)) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::Float64, ArrowFillValue::Float64(value)) => { + let float_array = array.as_primitive::(); + let filled_array: PrimitiveArray = float_array + .iter() + .map(|v| v.filter(|&v| v != *value)) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::Binary, ArrowFillValue::Bytes(fill_value)) => { + let binary_array = array.as_binary::(); + let filled_array: BinaryArray = binary_array + .iter() + .map(|v| v.filter(|&value| value != fill_value.as_slice())) + .collect(); + Arc::new(filled_array) + } + (arrow::datatypes::DataType::Utf8, ArrowFillValue::String(fill_value)) => { + let string_array = array.as_string::(); + let filled_array: StringArray = string_array + .iter() + .map(|v| v.filter(|&value| value != fill_value)) + .collect(); + + Arc::new(filled_array) + } + _ => { + return Err(format!( + "FillValueDecodingStep does not support data type: {:?}", + array.data_type() + )); + } + }; + + Ok(NdArrowArray::new(nullable_array, array.dimensions().clone()).unwrap()) + } } pub struct CFDecoder { @@ -68,17 +387,34 @@ pub struct CFDecoder { impl Decoder for CFDecoder { fn create( - group_reader: &crate::reader::ArrowGroupReader, + group_reader: &crate::reader::AsyncArrowZarrGroupReader, input_schema: SchemaRef, ) -> Result { - let mut array_decoders: std::collections::HashMap> = - std::collections::HashMap::new(); + let mut array_decoders: std::collections::HashMap< + String, + Vec>, + > = std::collections::HashMap::new(); let mut fields = IndexMap::new(); for field in input_schema.fields() { fields.insert(field.name().clone(), field.as_ref().clone()); } - for (array_name, _) in group_reader.arrays() { + for (array_name, array_reader) in group_reader.arrays() { + // Add a FillValueDecodingStep if the array has a fill value + if let Some(arrow_fill_value) = ArrowFillValue::from_zarrs_fill_value( + array_reader.data_type(), + array_reader.fill_value(), + ) { + let fill_value_decoder = FillValueDecodingStep { + fill_value: arrow_fill_value, + }; + + array_decoders + .entry(array_name.to_string()) + .or_default() + .push(Arc::new(fill_value_decoder)); + } + // Get all the attributes that start with array_name + "." let prefix = format!("{}.", array_name); let array_attribute: Vec<_> = group_reader @@ -137,12 +473,15 @@ impl Decoder for CFDecoder { }; if let Some(decoding_pipeline) = decoding_pipeline { - array_decoders.insert(array_name.to_string(), decoding_pipeline.clone()); + array_decoders + .entry(array_name.to_string()) + .or_default() + .push(decoding_pipeline.clone()); // Update the field data type to Float64 let array_field = fields.get_mut(array_name).unwrap(); - *array_field = array_field - .clone() - .with_data_type(decoding_pipeline.output_data_type()); + *array_field = array_field.clone().with_data_type( + decoding_pipeline.output_data_type(array_field.data_type().clone()), + ); } // Check for cf time attributes (units) @@ -163,12 +502,15 @@ impl Decoder for CFDecoder { match TimeUnitsDecodingStep::new(value.clone()) { Ok(decoder) => { let decoder = Arc::new(decoder) as Arc; - array_decoders.insert(array_name.to_string(), decoder.clone()); + array_decoders + .entry(array_name.to_string()) + .or_default() + .push(decoder.clone()); // Update the field data type to Int64 (Arrow timestamp in milliseconds) let array_field = fields.get_mut(array_name).unwrap(); - *array_field = array_field - .clone() - .with_data_type(decoder.output_data_type()); + *array_field = array_field.clone().with_data_type( + decoder.output_data_type(array_field.data_type().clone()), + ); } Err(e) => { tracing::warn!( @@ -184,10 +526,7 @@ impl Decoder for CFDecoder { } Ok(Self { - array_decoders: array_decoders - .into_iter() - .map(|(k, v)| (k, vec![v])) - .collect(), + array_decoders, decoded_schema: Arc::new(arrow::datatypes::Schema::new( fields.into_values().collect::>(), )), @@ -237,7 +576,10 @@ impl DecodingPipelineStep for ScaleFactorOffsetDecodingStep { .unwrap()) } - fn output_data_type(&self) -> arrow::datatypes::DataType { + fn output_data_type( + &self, + _input_data_type: arrow::datatypes::DataType, + ) -> arrow::datatypes::DataType { arrow::datatypes::DataType::Float64 } } @@ -304,7 +646,10 @@ impl TimeUnitsDecodingStep { } impl DecodingPipelineStep for TimeUnitsDecodingStep { - fn output_data_type(&self) -> arrow::datatypes::DataType { + fn output_data_type( + &self, + _input_data_type: arrow::datatypes::DataType, + ) -> arrow::datatypes::DataType { arrow::datatypes::DataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None) } diff --git a/beacon-arrow-zarr/src/reader.rs b/beacon-arrow-zarr/src/reader.rs index 1ddb95e6..ce936b6a 100644 --- a/beacon-arrow-zarr/src/reader.rs +++ b/beacon-arrow-zarr/src/reader.rs @@ -1,20 +1,33 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; +use arrow::array::BooleanArray; use indexmap::IndexMap; -use zarrs::{array::Array, group::Group}; +use nd_arrow_array::{ + NdArrowArray, + dimensions::{Dimension, Dimensions}, +}; +use zarrs::{array::Array, array_subset::ArraySubset, group::Group}; use zarrs_storage::AsyncReadableListableStorageTraits; -use crate::attributes::AttributeValue; +use crate::{ + attributes::AttributeValue, + decoder::{self, Decoder}, + stream::{ArrowZarrStreamComposer, ArrowZarrStreamComposerRef}, +}; -pub struct ArrowGroupReader { +pub struct AsyncArrowZarrGroupReader { group: Group, + decoders: Vec>, schema: arrow::datatypes::SchemaRef, arrays: IndexMap>, attributes: IndexMap, } -impl ArrowGroupReader { - pub async fn new(group: Group) -> Self { +impl AsyncArrowZarrGroupReader { + pub async fn new(group: Group) -> Result { let mut fields = Vec::new(); let mut attributes = IndexMap::new(); @@ -76,12 +89,26 @@ impl ArrowGroupReader { } } - Self { - schema: std::sync::Arc::new(arrow::datatypes::Schema::new(fields)), + let input_schema = Arc::new(arrow::datatypes::Schema::new(fields.clone())); + let mut self_reader = Self { + schema: input_schema.clone(), group, arrays, attributes, - } + decoders: Vec::new(), + }; + + let mut decoders = Vec::new(); + // Create decoder for fill values + let fill_value_decoder = decoder::FillValueDecoder::create(&self_reader, input_schema)?; + self_reader.schema = fill_value_decoder.decoded_schema(); + decoders.push(Arc::new(fill_value_decoder) as Arc); + // Create decoder for CF conventions + let cf_decoder = decoder::CFDecoder::create(&self_reader, self_reader.schema.clone())?; + self_reader.schema = cf_decoder.decoded_schema(); + decoders.push(Arc::new(cf_decoder) as Arc); + self_reader.decoders = decoders; + Ok(self_reader) } pub fn arrow_schema(&self) -> arrow::datatypes::SchemaRef { @@ -95,6 +122,246 @@ impl ArrowGroupReader { pub fn attributes(&self) -> &IndexMap { &self.attributes } + + pub fn into_parallel_stream_composer>( + self, + projection: Option

, + ) -> Result { + let projected_schema = match projection { + Some(p) => Arc::new(self.schema.project(p.as_ref()).map_err(|e| e.to_string())?), + None => self.schema.clone(), + }; + + ArrowZarrStreamComposer::new(self, projected_schema) + } + + pub fn read_attribute(&self, attribute_name: &str) -> Option { + self.attributes() + .get(attribute_name) + .map(|attr| attr.as_nd_arrow_array()) + } + + pub async fn read_array( + &self, + array_name: &str, + subset: &ArraySubset, + ) -> Result, String> { + let array_reader = if let Some(array) = self.arrays().get(array_name) { + array + } else { + return Ok(None); + }; + + let dimensions: Vec = array_reader + .dimension_names() + .as_ref() + .unwrap() + .iter() + .zip(subset.shape().iter()) + .map(|(dim, &len)| Dimension { + name: dim.clone().unwrap(), + size: len as usize, + }) + .collect::>(); + let dimensions = Dimensions::new(dimensions); + + let mut maybe_nd_array = match array_reader.data_type() { + zarrs::array::DataType::Bool => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_boolean_array = BooleanArray::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_boolean_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Int8 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_int8_array = + arrow::array::Int8Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_int8_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Int16 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_int16_array = + arrow::array::Int16Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_int16_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Int32 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_int32_array = + arrow::array::Int32Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_int32_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Int64 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_int64_array = + arrow::array::Int64Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_int64_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::UInt8 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_uint8_array = + arrow::array::UInt8Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_uint8_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::UInt16 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_uint16_array = + arrow::array::UInt16Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_uint16_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::UInt32 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_uint32_array = + arrow::array::UInt32Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_uint32_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::UInt64 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_uint64_array = + arrow::array::UInt64Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_uint64_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Float32 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_float32_array = + arrow::array::Float32Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_float32_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Float64 => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_float64_array = + arrow::array::Float64Array::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_float64_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::String => { + let array = array_reader + .async_retrieve_array_subset_ndarray::(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_string_array = + arrow::array::StringArray::from(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_string_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarrs::array::DataType::Bytes => { + let array = array_reader + .async_retrieve_array_subset_ndarray::>(subset) + .await + .map_err(|e| e.to_string())?; + + let arrow_binary_array = + arrow::array::BinaryArray::from_iter_values(array.into_raw_vec_and_offset().0); + + let nd_array = NdArrowArray::new(Arc::new(arrow_binary_array), dimensions) + .map_err(|e| e.to_string())?; + Ok(Some(nd_array)) + } + zarr_data_type => Err(format!("Unsupported Zarrs data type: {:?}", zarr_data_type)), + }; + + // let res = maybe_nd_array.and_then(|nd_array_opt| { + // nd_array_opt.map(|nd_array| { + // // Apply decoders + // self.decoders + // .iter() + // .fold(Ok(nd_array), |maybe_array, decoder| { + // maybe_array.and_then(|array| decoder.decode(array)) + // }) + // .unwrap() + // }) + // }); + + maybe_nd_array = if let Ok(Some(nd_array)) = maybe_nd_array { + // Apply decoders + for decoder in &self.decoders { + maybe_nd_array = decoder.decode_array(array_name, &nd_array); + } + + Ok(Some(nd_array)) + } else { + maybe_nd_array + }; + + maybe_nd_array + } } #[cfg(test)] @@ -102,8 +369,13 @@ mod tests { use std::sync::Arc; + use futures::StreamExt; use object_store::local::LocalFileSystem; - use zarrs::{array::Array, array_subset::ArraySubset, group::Group}; + use zarrs::{ + array::{Array, ChunkGrid}, + array_subset::ArraySubset, + group::Group, + }; use zarrs_object_store::AsyncObjectStore; use zarrs_storage::AsyncReadableListableStorage; @@ -127,7 +399,22 @@ mod tests { let sst_arr = Array::async_open(zarr_store.clone(), "/gridded-example.zarr/analysed_sst") .await .unwrap(); - println!("SST: {:?}", sst_arr.chunk_grid()); + println!( + "SST: {:?}", + sst_arr + .chunk_grid() + .iter_chunk_indices() + .collect::>() + ); + + let y = sst_arr + .chunk_grid() + .iter_chunk_indices() + .collect::>(); + let chunk_grid = sst_arr.chunk_grid().clone(); + + println!("SST chunk subset: {:?}", sst_arr.chunk_subset(&[0, 3, 1])); + println!("SST chunk subset: {:?}", chunk_grid.subset(&[0, 3, 1])); let lat_arr = Array::async_open(zarr_store.clone(), "/gridded-example.zarr/lat") .await @@ -164,11 +451,54 @@ mod tests { .await .unwrap(); - let reader = super::ArrowGroupReader::new(group).await; + let reader = super::AsyncArrowZarrGroupReader::new(group).await.unwrap(); let schema = reader.arrow_schema(); for field in schema.fields() { println!("Field: {:?} : {:?}", field.name(), field.data_type()); } } + + #[tokio::test] + async fn test_read_stream() { + let local_fs = LocalFileSystem::new_with_prefix("./test_files").unwrap(); + + let object_store = AsyncObjectStore::new(local_fs); + + let zarr_store: AsyncReadableListableStorage = Arc::new(object_store); + + let group = Group::async_open(zarr_store.clone(), "/gridded-example.zarr") + .await + .unwrap(); + + let reader = super::AsyncArrowZarrGroupReader::new(group).await.unwrap(); + + let longitude_idx = reader + .schema + .index_of("lon") + .expect("longitude field not found in schema"); + let latitude_idx = reader + .schema + .index_of("lat") + .expect("latitude field not found in schema"); + + let analysed_sst_idx = reader + .schema + .index_of("analysed_sst") + .expect("analysed_sst field not found in schema"); + + let mut stream = reader + .into_parallel_stream_composer(Some(&[longitude_idx, latitude_idx, analysed_sst_idx])) + .unwrap() + .stream(); + + let next_batch = stream.next().await; + + arrow::util::pretty::print_batches(&[next_batch + .unwrap() + .unwrap() + .to_arrow_record_batch() + .unwrap()]) + .unwrap(); + } } diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs index d9f6e20f..51113e75 100644 --- a/beacon-arrow-zarr/src/stream.rs +++ b/beacon-arrow-zarr/src/stream.rs @@ -1,39 +1,31 @@ -use std::{ - pin::Pin, - sync::{ - Arc, - atomic::{AtomicBool, AtomicU64, Ordering}, - }, - task::Poll, -}; +use std::{pin::Pin, sync::Arc, task::Poll}; -use arrow::array::BooleanArray; use futures::{Future, Stream}; use indexmap::IndexMap; -use nd_arrow_array::{ - NdArrowArray, - batch::NdRecordBatch, - dimensions::{Dimension, Dimensions}, +use nd_arrow_array::batch::NdRecordBatch; +use zarrs::{ + array::{ArrayShardedExt, ChunkGrid}, + array_subset::ArraySubset, }; -use zarrs::array_subset::ArraySubset; -use crate::{decoder::Decoder, reader::ArrowGroupReader}; +use crate::reader::AsyncArrowZarrGroupReader; + +pub type ArrowZarrStreamComposerRef = Arc; -#[derive(Clone)] pub struct ArrowZarrStreamComposer { - group_reader: Arc, + group_reader: crate::reader::AsyncArrowZarrGroupReader, projected_schema: arrow::datatypes::SchemaRef, - decoders: Vec>, // Streaming State - state: Arc>, + chunk_grid: Option, + dimension_names: Vec, + readable_chunks: crossbeam::queue::SegQueue, } impl ArrowZarrStreamComposer { pub fn new( - group_reader: Arc, + group_reader: AsyncArrowZarrGroupReader, projected_schema: arrow::datatypes::SchemaRef, - decoders: Vec>, - ) -> Result { + ) -> Result { // Get all the variables let variables = projected_schema .fields() @@ -59,328 +51,88 @@ impl ArrowZarrStreamComposer { }) .cloned(); - let mut chunk_sizes = IndexMap::new(); - let mut dimension_lengths = IndexMap::new(); + let mut chunk_grid = None; + let mut dimension_names = vec![]; + let queue = crossbeam::queue::SegQueue::new(); + if let Some(var_name) = max_dims_var { let var = group_reader.arrays().get(var_name.as_str()).unwrap(); - let chunk_shape = var.chunk_grid_shape(); - if let Some(dimensions) = var.dimension_names() { - for (i, maybe_dimension) in dimensions.iter().enumerate() { - if let Some(dimension) = maybe_dimension { - chunk_sizes.insert(dimension.clone(), chunk_shape[i]); - dimension_lengths.insert(dimension.clone(), var.shape()[i]); - } else { - return Err(format!( - "Array {} has unnamed dimensions, cannot stream.", - var_name - )); - } - } - } + let chunk_shape = var.chunk_grid(); + let var_dimension_names = var + .dimension_names() + .as_ref() + .unwrap() + .iter() + .map(|d| d.clone().unwrap()) + .collect::>(); + + chunk_grid = Some(chunk_shape.clone()); + dimension_names = var_dimension_names; } - let mut chunk_indices = IndexMap::new(); - for (dim_name, _) in chunk_sizes.iter() { - chunk_indices.insert(dim_name.clone(), Arc::new(AtomicU64::new(0))); + if let Some(chunk_grid) = &chunk_grid { + // Generate all chunk indices + for chunk_indices in chunk_grid.iter_chunk_indices() { + queue.push(chunk_indices); + } + } else { + // We should at least have one chunk to read, we can add an empty as it should only contain scalar attributes and arrays + queue.push(Vec::new()); } - Ok(Self { - decoders, + Ok(Arc::new(Self { group_reader, projected_schema, - state: todo!(), - }) - } - - fn read_attribute(&self, attribute_name: &str) -> Option { - self.group_reader - .attributes() - .get(attribute_name) - .map(|attr| attr.as_nd_arrow_array()) - } - - async fn read_array( - &self, - array_name: &str, - subset: &ArraySubset, - ) -> Result, String> { - let array_reader = if let Some(array) = self.group_reader.arrays().get(array_name) { - array - } else { - return Ok(None); - }; - - let dimensions: Vec = array_reader - .dimension_names() - .as_ref() - .unwrap() - .iter() - .zip(subset.shape().iter()) - .map(|(dim, &len)| Dimension { - name: dim.clone().unwrap(), - size: len as usize, - }) - .collect::>(); - let dimensions = Dimensions::new(dimensions); - - match array_reader.data_type() { - zarrs::array::DataType::Bool => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_boolean_array = BooleanArray::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_boolean_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::Int8 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_int8_array = - arrow::array::Int8Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_int8_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::Int16 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_int16_array = - arrow::array::Int16Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_int16_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::Int32 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_int32_array = - arrow::array::Int32Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_int32_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::Int64 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_int64_array = - arrow::array::Int64Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_int64_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::UInt8 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_uint8_array = - arrow::array::UInt8Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_uint8_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::UInt16 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_uint16_array = - arrow::array::UInt16Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_uint16_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::UInt32 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_uint32_array = - arrow::array::UInt32Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_uint32_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::UInt64 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_uint64_array = - arrow::array::UInt64Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_uint64_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::Float32 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_float32_array = - arrow::array::Float32Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_float32_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::Float64 => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_float64_array = - arrow::array::Float64Array::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_float64_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::String => { - let array = array_reader - .async_retrieve_array_subset_ndarray::(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_string_array = - arrow::array::StringArray::from(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_string_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarrs::array::DataType::Bytes => { - let array = array_reader - .async_retrieve_array_subset_ndarray::>(subset) - .await - .map_err(|e| e.to_string())?; - - let arrow_binary_array = - arrow::array::BinaryArray::from_iter_values(array.into_raw_vec_and_offset().0); - - let nd_array = NdArrowArray::new(Arc::new(arrow_binary_array), dimensions) - .map_err(|e| e.to_string())?; - Ok(Some(nd_array)) - } - zarr_data_type => Err(format!("Unsupported Zarrs data type: {:?}", zarr_data_type)), - } - } - - pub fn next_state(&self) -> ArrowZarrStreamState { - todo!() + chunk_grid, + dimension_names, + readable_chunks: queue, + })) } -} - -#[derive(Debug, Clone)] -pub struct ArrowZarrStreamState { - is_done: bool, - dimension_lengths: Arc>, - chunk_sizes: Arc>, - chunk_indices: IndexMap, -} - -impl ArrowZarrStreamState { - pub fn new( - dimension_lengths: IndexMap, - chunk_sizes: IndexMap, - ) -> Self { - let mut chunk_indices = IndexMap::new(); - for (dim_name, _) in chunk_sizes.iter() { - chunk_indices.insert(dim_name.clone(), 0); - } - Self { - is_done: false, - dimension_lengths: Arc::new(dimension_lengths), - chunk_sizes: Arc::new(chunk_sizes), - chunk_indices, + pub fn generate_array_subset(&self, chunk_indices: &ChunkIndices) -> ArraySubset { + match self.chunk_grid.as_ref() { + Some(chunk_grid) => chunk_grid.subset(chunk_indices).unwrap().unwrap(), + None => ArraySubset::new_empty(0), // Scalar array } } - fn is_done(&self) -> bool { - self.is_done - } - - fn advance_chunk_state(&mut self) { - // Advance like a multi-dimensional counter - for (dim, step) in self.chunk_indices.iter_mut() { - if let Some(d) = self.dimension_lengths.get(dim) { - let size = self.chunk_sizes[dim]; - *step += size; - if *step < *d { - return; // still within bounds - } else { - *step = 0; // reset and carry to next dimension - } - } + pub fn stream(self: &Arc) -> ArrowZarrStream { + ArrowZarrStream { + composer: self.clone(), + ongoing: None, } - self.is_done = true; // All dimensions exhausted } +} - fn generate_array_subset(&self, dimensions: &[String]) -> ArraySubset { - let mut ranges = vec![]; - for dim in dimensions { - let chunk_count = self.chunk_sizes.get(dim).cloned().unwrap_or_else(|| { - self.dimension_lengths.get(dim).cloned().unwrap() // Default to full length - }); - let step = self.chunk_indices.get(dim).map(|s| *s).unwrap_or(0); - let min_chunk_count = self.dimension_lengths[dim] - .saturating_sub(step) - .min(chunk_count); +pub type ChunkIndices = Vec; - ranges.push(step..step + min_chunk_count); - } - - ArraySubset::new_with_ranges(&ranges) - } -} +type ArrowZarrStreamFuture = + Pin>> + Send>>; #[pin_project::pin_project] pub struct ArrowZarrStream { composer: Arc, // Pinned state #[pin] - ongoing: Option>> + Send>>>, + ongoing: Option, } impl ArrowZarrStream { pub async fn next_chunk( composer: Arc, ) -> Option> { - let state = composer.next_state(); - if state.is_done() { - return None; - } + let chunk_indices = match composer.readable_chunks.pop() { + Some(chunk_indices) => chunk_indices, + None => return None, // Queue is empty + }; + let array_subset = composer.generate_array_subset(&chunk_indices); + // Blend dimension_names and array_subset ranges into a map + let dimension_subset_ranges: IndexMap> = composer + .dimension_names + .iter() + .zip(array_subset.to_ranges()) + .map(|(name, range)| (name.clone(), range)) + .collect(); let mut arrays = vec![]; let mut fields = vec![]; @@ -388,10 +140,51 @@ impl ArrowZarrStream { let array_name = column.name(); fields.push(column.as_ref().clone()); - composer.read_array(array_name, subset) + if array_name.contains('.') { + // Reading attribute + if let Some(attribute) = composer.group_reader.read_attribute(array_name) { + arrays.push(attribute); + } else { + return Some(Err(format!( + "Attribute {} not found in Zarr group.", + array_name + ))); + } + } else { + // Reading array + let subset = if let Some(dimension_names) = + composer.group_reader.arrays()[array_name.as_str()].dimension_names() + { + let ranges = dimension_names + .iter() + .map(|dim_name| { + dimension_subset_ranges + .get(dim_name.as_ref().unwrap().as_str()) + .unwrap() + .clone() + }) + .collect::>(); + + ArraySubset::new_with_ranges(&ranges) + } else { + // Scalar array + ArraySubset::new_empty(0) + }; + + let array = composer + .group_reader + .read_array(array_name, &subset) + .await + .unwrap() + .unwrap(); + + arrays.push(array); + } } - todo!() + let record_batch = NdRecordBatch::new(fields, arrays).map_err(|e| e.to_string()); + + Some(record_batch) } } From de7bf6b8ada6620c93579a5a039e7de5338667d0 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Mon, 13 Oct 2025 09:41:10 +0200 Subject: [PATCH 06/19] wip --- Cargo.lock | 4 + beacon-arrow-zarr/src/attributes.rs | 21 ++++ beacon-arrow-zarr/src/decoder.rs | 178 ++++++++-------------------- beacon-arrow-zarr/src/reader.rs | 26 +--- beacon-arrow-zarr/src/stream.rs | 7 +- beacon-formats/Cargo.toml | 6 +- beacon-formats/src/lib.rs | 1 + beacon-formats/src/zarr/mod.rs | 115 ++++++++++++++++++ 8 files changed, 201 insertions(+), 157 deletions(-) create mode 100644 beacon-formats/src/zarr/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b7a09ff5..66c73f82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -738,12 +738,14 @@ dependencies = [ "async_zip", "beacon-arrow-netcdf", "beacon-arrow-odv", + "beacon-arrow-zarr", "beacon-common", "datafusion", "futures", "geoarrow", "geoarrow-array", "geoparquet", + "glob", "object_store 0.12.3", "parking_lot", "parquet", @@ -754,6 +756,8 @@ dependencies = [ "tokio-util", "tracing", "typetag", + "url", + "zarrs_object_store", ] [[package]] diff --git a/beacon-arrow-zarr/src/attributes.rs b/beacon-arrow-zarr/src/attributes.rs index 29c954c5..67db2aa1 100644 --- a/beacon-arrow-zarr/src/attributes.rs +++ b/beacon-arrow-zarr/src/attributes.rs @@ -18,6 +18,27 @@ impl AttributeValue { } } + pub fn as_str(&self) -> Option<&str> { + match self { + AttributeValue::String(s) => Some(s.as_str()), + _ => None, + } + } + + pub fn as_f64(&self) -> Option { + match self { + AttributeValue::Float64(f) => Some(*f), + _ => None, + } + } + + pub fn as_bool(&self) -> Option { + match self { + AttributeValue::Bool(b) => Some(*b), + _ => None, + } + } + pub fn arrow_data_type(&self) -> arrow::datatypes::DataType { match self { AttributeValue::String(_) => arrow::datatypes::DataType::Utf8, diff --git a/beacon-arrow-zarr/src/decoder.rs b/beacon-arrow-zarr/src/decoder.rs index c434f69e..b870a836 100644 --- a/beacon-arrow-zarr/src/decoder.rs +++ b/beacon-arrow-zarr/src/decoder.rs @@ -15,6 +15,8 @@ use indexmap::IndexMap; use nd_arrow_array::{NdArrowArray, batch::NdRecordBatch}; use regex::Regex; +use crate::attributes::AttributeValue; + pub trait Decoder: Send + Sync { fn create( group_reader: &crate::reader::AsyncArrowZarrGroupReader, @@ -65,9 +67,12 @@ impl Decoder for FillValueDecoder { let mut array_fill_decoders = std::collections::HashMap::new(); for (array_name, array_reader) in group_reader.arrays() { // Add a FillValueDecodingStep if the array has a fill value + let fill_value_attr = array_reader.attributes().get("_FillValue"); if let Some(arrow_fill_value) = ArrowFillValue::from_zarrs_fill_value( array_reader.data_type(), - array_reader.fill_value(), + fill_value_attr + .and_then(AttributeValue::from_json_value) + .as_ref(), ) { let fill_value_decoder = FillValueDecodingStep { fill_value: arrow_fill_value, @@ -121,125 +126,51 @@ pub enum ArrowFillValue { impl ArrowFillValue { pub fn from_zarrs_fill_value( array_data_type: &zarrs::array::DataType, - fill_bytes: &zarrs::array::FillValue, + fill_value_attr: Option<&AttributeValue>, ) -> Option { - match (array_data_type) { - zarrs::array::DataType::Bool => { - if fill_bytes.as_ne_bytes().len() == 1 { - Some(ArrowFillValue::Bool(fill_bytes.as_ne_bytes()[0] != 0)) - } else { - None - } - } - zarrs::array::DataType::Int8 => { - if fill_bytes.as_ne_bytes().len() == 1 { - Some(ArrowFillValue::Int8(fill_bytes.as_ne_bytes()[0] as i8)) - } else { - None - } - } - zarrs::array::DataType::Int16 => { - if fill_bytes.as_ne_bytes().len() == 2 { - let bytes = fill_bytes.as_ne_bytes()[0..2] - .try_into() - .expect("slice with incorrect length"); - Some(ArrowFillValue::Int16(i16::from_ne_bytes(bytes))) - } else { - None - } - } - zarrs::array::DataType::Int32 => { - if fill_bytes.as_ne_bytes().len() == 4 { - let bytes = fill_bytes.as_ne_bytes()[0..4] - .try_into() - .expect("slice with incorrect length"); - Some(ArrowFillValue::Int32(i32::from_ne_bytes(bytes))) - } else { - None - } - } - zarrs::array::DataType::Int64 => { - if fill_bytes.as_ne_bytes().len() == 8 { - let bytes = fill_bytes.as_ne_bytes()[0..8] - .try_into() - .expect("slice with incorrect length"); - Some(ArrowFillValue::Int64(i64::from_ne_bytes(bytes))) - } else { - None - } - } - zarrs::array::DataType::UInt8 => { - if fill_bytes.as_ne_bytes().len() == 1 { - Some(ArrowFillValue::UInt8(fill_bytes.as_ne_bytes()[0])) - } else { - None - } - } - zarrs::array::DataType::UInt16 => { - if fill_bytes.as_ne_bytes().len() == 2 { - let bytes = fill_bytes.as_ne_bytes()[0..2] - .try_into() - .expect("slice with incorrect length"); - Some(ArrowFillValue::UInt16(u16::from_ne_bytes(bytes))) - } else { - None - } - } - zarrs::array::DataType::UInt32 => { - if fill_bytes.as_ne_bytes().len() == 4 { - let bytes = fill_bytes.as_ne_bytes()[0..4] - .try_into() - .expect("slice with incorrect length"); - Some(ArrowFillValue::UInt32(u32::from_ne_bytes(bytes))) - } else { - None - } - } - - zarrs::array::DataType::UInt64 => { - if fill_bytes.as_ne_bytes().len() == 8 { - let bytes = fill_bytes.as_ne_bytes()[0..8] - .try_into() - .expect("slice with incorrect length"); - Some(ArrowFillValue::UInt64(u64::from_ne_bytes(bytes))) - } else { - None - } - } - - zarrs::array::DataType::Float32 => { - if fill_bytes.as_ne_bytes().len() == 4 { - let bytes = fill_bytes.as_ne_bytes()[0..4] - .try_into() - .expect("slice with incorrect length"); - Some(ArrowFillValue::Float32(f32::from_ne_bytes(bytes))) - } else { - None - } - } - - zarrs::array::DataType::Float64 => { - if fill_bytes.as_ne_bytes().len() == 8 { - let bytes = fill_bytes.as_ne_bytes()[0..8] - .try_into() - .expect("slice with incorrect length"); - Some(ArrowFillValue::Float64(f64::from_ne_bytes(bytes))) - } else { - None - } - } + match array_data_type { + zarrs::array::DataType::Bool => fill_value_attr + .and_then(|attr| attr.as_bool()) + .map(ArrowFillValue::Bool), + zarrs::array::DataType::Int8 => fill_value_attr + .and_then(|attr| attr.as_f64().and_then(|v| i8::try_from(v as i64).ok())) + .map(ArrowFillValue::Int8), + zarrs::array::DataType::Int16 => fill_value_attr + .and_then(|attr| attr.as_f64().and_then(|v| i16::try_from(v as i64).ok())) + .map(ArrowFillValue::Int16), + zarrs::array::DataType::Int32 => fill_value_attr + .and_then(|attr| attr.as_f64().and_then(|v| i32::try_from(v as i64).ok())) + .map(ArrowFillValue::Int32), + zarrs::array::DataType::Int64 => fill_value_attr + .and_then(|attr| attr.as_f64().map(|v| v as i64).or(None)) + .map(ArrowFillValue::Int64), + zarrs::array::DataType::UInt8 => fill_value_attr + .and_then(|attr| attr.as_f64().and_then(|v| u8::try_from(v as u64).ok())) + .map(ArrowFillValue::UInt8), + zarrs::array::DataType::UInt16 => fill_value_attr + .and_then(|attr| attr.as_f64().and_then(|v| u16::try_from(v as u64).ok())) + .map(ArrowFillValue::UInt16), + zarrs::array::DataType::UInt32 => fill_value_attr + .and_then(|attr| attr.as_f64().and_then(|v| u32::try_from(v as u64).ok())) + .map(ArrowFillValue::UInt32), + + zarrs::array::DataType::UInt64 => fill_value_attr + .and_then(|attr| attr.as_f64().map(|v| v as u64).or(None)) + .map(ArrowFillValue::UInt64), + + zarrs::array::DataType::Float32 => fill_value_attr + .and_then(|attr| attr.as_f64().map(|v| v as f32).or(None)) + .map(ArrowFillValue::Float32), + + zarrs::array::DataType::Float64 => fill_value_attr + .and_then(|attr| attr.as_f64()) + .map(ArrowFillValue::Float64), zarrs::array::DataType::String => { // Interpret fill_bytes as UTF-8 string - match std::str::from_utf8(fill_bytes.as_ne_bytes()) { - Ok(s) => Some(ArrowFillValue::String(s.to_string())), - Err(_) => None, - } - } - - zarrs::array::DataType::Bytes => { - // Interpret fill_bytes as raw bytes - Some(ArrowFillValue::Bytes(fill_bytes.as_ne_bytes().to_vec())) + fill_value_attr + .and_then(|attr| attr.as_str().map(|s| s.to_string())) + .map(ArrowFillValue::String) } _ => None, @@ -400,21 +331,6 @@ impl Decoder for CFDecoder { } for (array_name, array_reader) in group_reader.arrays() { - // Add a FillValueDecodingStep if the array has a fill value - if let Some(arrow_fill_value) = ArrowFillValue::from_zarrs_fill_value( - array_reader.data_type(), - array_reader.fill_value(), - ) { - let fill_value_decoder = FillValueDecodingStep { - fill_value: arrow_fill_value, - }; - - array_decoders - .entry(array_name.to_string()) - .or_default() - .push(Arc::new(fill_value_decoder)); - } - // Get all the attributes that start with array_name + "." let prefix = format!("{}.", array_name); let array_attribute: Vec<_> = group_reader diff --git a/beacon-arrow-zarr/src/reader.rs b/beacon-arrow-zarr/src/reader.rs index ce936b6a..5fb0fc7b 100644 --- a/beacon-arrow-zarr/src/reader.rs +++ b/beacon-arrow-zarr/src/reader.rs @@ -337,22 +337,10 @@ impl AsyncArrowZarrGroupReader { zarr_data_type => Err(format!("Unsupported Zarrs data type: {:?}", zarr_data_type)), }; - // let res = maybe_nd_array.and_then(|nd_array_opt| { - // nd_array_opt.map(|nd_array| { - // // Apply decoders - // self.decoders - // .iter() - // .fold(Ok(nd_array), |maybe_array, decoder| { - // maybe_array.and_then(|array| decoder.decode(array)) - // }) - // .unwrap() - // }) - // }); - - maybe_nd_array = if let Ok(Some(nd_array)) = maybe_nd_array { + maybe_nd_array = if let Ok(Some(mut nd_array)) = maybe_nd_array { // Apply decoders for decoder in &self.decoders { - maybe_nd_array = decoder.decode_array(array_name, &nd_array); + nd_array = decoder.decode_array(array_name, nd_array)?; } Ok(Some(nd_array)) @@ -371,11 +359,7 @@ mod tests { use futures::StreamExt; use object_store::local::LocalFileSystem; - use zarrs::{ - array::{Array, ChunkGrid}, - array_subset::ArraySubset, - group::Group, - }; + use zarrs::{array::Array, array_subset::ArraySubset, group::Group}; use zarrs_object_store::AsyncObjectStore; use zarrs_storage::AsyncReadableListableStorage; @@ -407,6 +391,8 @@ mod tests { .collect::>() ); + println!("SST attrs: {:?}", sst_arr.attributes()); + let y = sst_arr .chunk_grid() .iter_chunk_indices() @@ -490,7 +476,7 @@ mod tests { let mut stream = reader .into_parallel_stream_composer(Some(&[longitude_idx, latitude_idx, analysed_sst_idx])) .unwrap() - .stream(); + .pollable_shared_stream(); let next_batch = stream.next().await; diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs index 51113e75..6a3c0aec 100644 --- a/beacon-arrow-zarr/src/stream.rs +++ b/beacon-arrow-zarr/src/stream.rs @@ -3,10 +3,7 @@ use std::{pin::Pin, sync::Arc, task::Poll}; use futures::{Future, Stream}; use indexmap::IndexMap; use nd_arrow_array::batch::NdRecordBatch; -use zarrs::{ - array::{ArrayShardedExt, ChunkGrid}, - array_subset::ArraySubset, -}; +use zarrs::{array::ChunkGrid, array_subset::ArraySubset}; use crate::reader::AsyncArrowZarrGroupReader; @@ -96,7 +93,7 @@ impl ArrowZarrStreamComposer { } } - pub fn stream(self: &Arc) -> ArrowZarrStream { + pub fn pollable_shared_stream(self: &Arc) -> ArrowZarrStream { ArrowZarrStream { composer: self.clone(), ongoing: None, diff --git a/beacon-formats/Cargo.toml b/beacon-formats/Cargo.toml index 3874d469..b08d0762 100644 --- a/beacon-formats/Cargo.toml +++ b/beacon-formats/Cargo.toml @@ -25,7 +25,11 @@ tokio = { workspace = true } async_zip = { version = "0.0.18", features = ["tokio", "zstd", "deflate"] } tokio-util = "0.7.16" +zarrs_object_store = "0.5.0" +url = "2.5.4" +glob = "0.3.3" beacon-common = { path = "../beacon-common" } beacon-arrow-netcdf = { path = "../beacon-arrow-netcdf" } -beacon-arrow-odv = { path = "../beacon-arrow-odv" } \ No newline at end of file +beacon-arrow-odv = { path = "../beacon-arrow-odv" } +beacon-arrow-zarr = { path = "../beacon-arrow-zarr" } \ No newline at end of file diff --git a/beacon-formats/src/lib.rs b/beacon-formats/src/lib.rs index f8f8fea2..faed294a 100644 --- a/beacon-formats/src/lib.rs +++ b/beacon-formats/src/lib.rs @@ -19,6 +19,7 @@ pub mod geo_parquet; pub mod netcdf; pub mod odv_ascii; pub mod parquet; +pub mod zarr; /// Register file formats with the session state that can be used for reading pub fn register_file_formats( diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs new file mode 100644 index 00000000..a8fb417c --- /dev/null +++ b/beacon-formats/src/zarr/mod.rs @@ -0,0 +1,115 @@ +use std::{fmt::Debug, sync::Arc}; + +use datafusion::{ + common::GetExt, + datasource::file_format::{FileFormat, FileFormatFactory}, +}; +use object_store::ObjectStore; +use zarrs_object_store::AsyncObjectStore; + +pub struct ZarrFormatFactory { + zarr_object_store: AsyncObjectStore>, +} + +impl Debug for ZarrFormatFactory { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ZarrFormatFactory").finish() + } +} + +impl ZarrFormatFactory { + pub fn new(object_store: Arc) -> Self { + Self { + zarr_object_store: AsyncObjectStore::new(object_store), + } + } +} + +impl GetExt for ZarrFormatFactory { + fn get_ext(&self) -> String { + "zarr".to_string() + } +} + +impl FileFormatFactory for ZarrFormatFactory { + fn create( + &self, + state: &dyn datafusion::catalog::Session, + format_options: &std::collections::HashMap, + ) -> datafusion::error::Result> { + todo!() + } + + fn default(&self) -> std::sync::Arc { + todo!() + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +pub struct ZarrFormat { + zarr_object_store: AsyncObjectStore>, +} + +// impl FileFormat for ZarrFormat { +// fn as_any(&self) -> &dyn std::any::Any { +// self +// } +// } + +#[cfg(test)] +mod tests { + use datafusion::datasource::listing::ListingTableUrl; + use futures::StreamExt; + + use super::*; + + #[test] + fn test_name() { + let test_path = object_store::path::Path::parse("bucket/key.zarr").unwrap(); + + println!("Path: {:?}", test_path); + } + + #[tokio::test] + async fn test_listing_table() { + let object_store = + object_store::local::LocalFileSystem::new_with_prefix("./test_files").unwrap(); + + let url = ListingTableUrl::try_new( + "file:///".try_into().unwrap(), + Some(glob::Pattern::new("gridded-example.zarr").unwrap()), + ) + .unwrap(); + println!("URL: {:?}", url); + + let session = datafusion::execution::context::SessionContext::new(); + let state = session.state(); + let mut files = url + .list_all_files(&state, &object_store, "zarr.json") + .await + .unwrap(); + + while let Some(file) = files.next().await { + println!("File: {:?}", file); + } + + let url = + ListingTableUrl::try_new("file:///gridded-example.zarr/".try_into().unwrap(), None) + .unwrap(); + println!("URL: {:?}", url); + + let session = datafusion::execution::context::SessionContext::new(); + let state = session.state(); + let mut files = url + .list_all_files(&state, &object_store, "zarr.json") + .await + .unwrap(); + + while let Some(file) = files.next().await { + println!("File: {:?}", file); + } + } +} From 89ece2dc3b864326a7b199592c104ad71a0a565c Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Tue, 14 Oct 2025 03:23:45 +0200 Subject: [PATCH 07/19] added minimum zarr example --- Cargo.lock | 3 + beacon-data-lake/src/table/table_formats.rs | 10 + beacon-formats/Cargo.toml | 6 +- beacon-formats/src/lib.rs | 2 + beacon-formats/src/zarr/mod.rs | 302 +++++++++++++++++-- beacon-formats/src/zarr/source.rs | 317 ++++++++++++++++++++ beacon-formats/src/zarr/stream_share.rs | 44 +++ zarr.ipynb | 0 8 files changed, 663 insertions(+), 21 deletions(-) create mode 100644 beacon-formats/src/zarr/source.rs create mode 100644 beacon-formats/src/zarr/stream_share.rs create mode 100644 zarr.ipynb diff --git a/Cargo.lock b/Cargo.lock index 66c73f82..cfeb13f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -746,6 +746,7 @@ dependencies = [ "geoarrow-array", "geoparquet", "glob", + "nd-arrow-array", "object_store 0.12.3", "parking_lot", "parquet", @@ -757,7 +758,9 @@ dependencies = [ "tracing", "typetag", "url", + "zarrs", "zarrs_object_store", + "zarrs_storage", ] [[package]] diff --git a/beacon-data-lake/src/table/table_formats.rs b/beacon-data-lake/src/table/table_formats.rs index a7a5a0b6..a3b13f6a 100644 --- a/beacon-data-lake/src/table/table_formats.rs +++ b/beacon-data-lake/src/table/table_formats.rs @@ -48,3 +48,13 @@ impl TableFileFormat for NetCDFFileFormat { "nc".to_string() } } + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct ZarrFileFormat; + +#[typetag::serde(name = "zarr")] +impl TableFileFormat for ZarrFileFormat { + fn file_ext(&self) -> String { + "zarr".to_string() + } +} diff --git a/beacon-formats/Cargo.toml b/beacon-formats/Cargo.toml index b08d0762..4e06bed9 100644 --- a/beacon-formats/Cargo.toml +++ b/beacon-formats/Cargo.toml @@ -26,10 +26,14 @@ tokio = { workspace = true } async_zip = { version = "0.0.18", features = ["tokio", "zstd", "deflate"] } tokio-util = "0.7.16" zarrs_object_store = "0.5.0" +zarrs_storage = "0.4.0" +zarrs = {"version" = "0.22.2", features = ["async"]} + url = "2.5.4" glob = "0.3.3" beacon-common = { path = "../beacon-common" } beacon-arrow-netcdf = { path = "../beacon-arrow-netcdf" } beacon-arrow-odv = { path = "../beacon-arrow-odv" } -beacon-arrow-zarr = { path = "../beacon-arrow-zarr" } \ No newline at end of file +beacon-arrow-zarr = { path = "../beacon-arrow-zarr" } +nd-arrow-array = { git = "https://github.com/maris-development/nd-arrow-array.git", branch = "main" } \ No newline at end of file diff --git a/beacon-formats/src/lib.rs b/beacon-formats/src/lib.rs index faed294a..51a916fe 100644 --- a/beacon-formats/src/lib.rs +++ b/beacon-formats/src/lib.rs @@ -11,6 +11,7 @@ use crate::{ object_resolver::{NetCDFObjectResolver, NetCDFSinkResolver}, }, parquet::ParquetFormatFactory, + zarr::ZarrFormatFactory, }; pub mod arrow; @@ -39,5 +40,6 @@ pub fn register_file_formats( )), true, )?; + session_state.register_file_format(Arc::new(ZarrFormatFactory), true)?; Ok(()) } diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs index a8fb417c..863b591e 100644 --- a/beacon-formats/src/zarr/mod.rs +++ b/beacon-formats/src/zarr/mod.rs @@ -1,15 +1,31 @@ -use std::{fmt::Debug, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Debug, + sync::Arc, +}; +use arrow::datatypes::SchemaRef; use datafusion::{ - common::GetExt, - datasource::file_format::{FileFormat, FileFormatFactory}, + catalog::{Session, memory::DataSourceExec}, + common::{GetExt, Statistics}, + datasource::{ + file_format::{FileFormat, FileFormatFactory, file_compression_type::FileCompressionType}, + listing::PartitionedFile, + physical_plan::{ + FileGroup, FileGroupPartitioner, FileScanConfig, FileScanConfigBuilder, FileSource, + }, + }, + physical_plan::ExecutionPlan, }; -use object_store::ObjectStore; +use object_store::{ObjectMeta, ObjectStore}; use zarrs_object_store::AsyncObjectStore; -pub struct ZarrFormatFactory { - zarr_object_store: AsyncObjectStore>, -} +use crate::zarr::source::{ZarrSource, fetch_schema}; + +mod source; +mod stream_share; + +pub struct ZarrFormatFactory; impl Debug for ZarrFormatFactory { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -18,10 +34,14 @@ impl Debug for ZarrFormatFactory { } impl ZarrFormatFactory { - pub fn new(object_store: Arc) -> Self { - Self { - zarr_object_store: AsyncObjectStore::new(object_store), - } + pub fn new() -> Self { + Self + } +} + +impl Default for ZarrFormatFactory { + fn default() -> Self { + Self::new() } } @@ -37,11 +57,12 @@ impl FileFormatFactory for ZarrFormatFactory { state: &dyn datafusion::catalog::Session, format_options: &std::collections::HashMap, ) -> datafusion::error::Result> { - todo!() + // Here you can use `state` and `format_options` to customize the creation of the FileFormat + Ok(Arc::new(ZarrFormat)) } fn default(&self) -> std::sync::Arc { - todo!() + Arc::new(ZarrFormat) } fn as_any(&self) -> &dyn std::any::Any { @@ -49,15 +70,161 @@ impl FileFormatFactory for ZarrFormatFactory { } } -pub struct ZarrFormat { - zarr_object_store: AsyncObjectStore>, +#[derive(Debug, Clone)] +pub struct ZarrFormat; + +#[async_trait::async_trait] +impl FileFormat for ZarrFormat { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn compression_type(&self) -> Option { + None + } + + fn get_ext(&self) -> String { + "zarr.json".to_string() + } + + fn get_ext_with_compression( + &self, + _file_compression_type: &FileCompressionType, + ) -> datafusion::error::Result { + Ok("zarr.json".to_string()) + } + + async fn infer_schema( + &self, + state: &dyn Session, + store: &Arc, + objects: &[ObjectMeta], + ) -> datafusion::error::Result { + let objects = top_level_zarr_meta_v3(objects); + tracing::debug!("Top-level Zarr v3 groups: {:?}", objects); + let mut schema = None; + for object in objects { + let object_schema = fetch_schema(store.clone(), &object.clone()).await?; + schema = Some(object_schema); + } + Ok(schema.unwrap()) + } + + async fn infer_stats( + &self, + _state: &dyn Session, + _store: &Arc, + table_schema: SchemaRef, + _object: &ObjectMeta, + ) -> datafusion::error::Result { + Ok(Statistics::new_unknown(&table_schema)) + } + + async fn create_physical_plan( + &self, + _state: &dyn Session, + conf: FileScanConfig, + ) -> datafusion::error::Result> { + // Recreate file groups here. Only maintain top-level groups. + let mut object_metas: Vec = Vec::new(); + for group in &conf.file_groups { + for file in group.files() { + object_metas.push(file.object_meta.clone()); + } + } + + // Recreate file groups with only top-level Zarr groups. + let top_level_metas = top_level_zarr_meta_v3(&object_metas); + let file_groups: Vec = top_level_metas + .into_iter() + .map(|meta| { + FileGroup::new(vec![ + PartitionedFile::from(meta.clone()).with_range(0, meta.size as i64), + ]) + }) + .collect(); + + let source = ZarrSource::default(); + let conf = FileScanConfigBuilder::from(conf) + .with_file_groups(file_groups) + .with_source(Arc::new(source)) + .build(); + Ok(DataSourceExec::from_data_source(conf)) + } + + fn file_source(&self) -> Arc { + Arc::new(ZarrSource::default()) + } +} + +/// Get parent directory of a Path (S3-style). +/// Example: "a/b/c" -> Some("a/b") +/// "a" -> None +fn path_parent(p: &object_store::path::Path) -> Option { + let s = p.to_string(); + if let Some(pos) = s.rfind('/') { + // Parent is everything before the last '/' + let parent_str = &s[..pos]; + Some(object_store::path::Path::from(parent_str)) + } else { + // No '/' in path => parent is root (optional: return None instead) + None + } } -// impl FileFormat for ZarrFormat { -// fn as_any(&self) -> &dyn std::any::Any { -// self -// } -// } +/// Check if this ObjectMeta represents a Zarr v3 metadata file ("zarr.json") +fn is_zarr_v3_metadata(meta: &object_store::ObjectMeta) -> bool { + // Normalize for safety, S3 paths are UTF-8 so this is fine + let loc = meta.location.to_string().to_lowercase(); + loc.ends_with("/zarr.json") +} + +/// Return only the ObjectMeta entries corresponding to **top-level Zarr groups**. +pub fn top_level_zarr_meta_v3(metas: &[object_store::ObjectMeta]) -> Vec { + // 1. Collect all metas that are zarr.json + record their directories. + let mut dir_to_meta: HashMap = + HashMap::new(); + + for meta in metas { + if is_zarr_v3_metadata(meta) + && let Some(parent) = path_parent(&meta.location) + { + dir_to_meta.insert(parent, meta); + } + } + + // 2. Extract all candidate directories and sort. + let mut candidates: Vec = dir_to_meta.keys().cloned().collect(); + candidates.sort(); + candidates.dedup(); + + // 3. Build a set for quick ancestor checks. + let all_dirs: HashSet = candidates.iter().cloned().collect(); + + // 4. Keep only directories that have no ancestor also in all_dirs. + let mut top_level_dirs: Vec = Vec::new(); + + 'outer: for dir in &candidates { + let mut current = path_parent(dir); + while let Some(ancestor) = current { + if ancestor == *dir { + break; + } + if all_dirs.contains(&ancestor) { + // dir is nested under another zarr.json directory + continue 'outer; + } + current = path_parent(&ancestor); + } + top_level_dirs.push(dir.clone()); + } + + // 5. Return the corresponding ObjectMetas (cloned) + top_level_dirs + .into_iter() + .filter_map(|d| dir_to_meta.get(&d).cloned().cloned()) + .collect() +} #[cfg(test)] mod tests { @@ -112,4 +279,99 @@ mod tests { println!("File: {:?}", file); } } + + use object_store::{ObjectMeta, path::Path}; + + fn meta(path: &str) -> ObjectMeta { + ObjectMeta { + location: Path::from(path), + last_modified: Default::default(), + size: 0, + e_tag: None, + version: None, + } + } + + fn extract_paths(metas: &[ObjectMeta]) -> Vec { + metas.iter().map(|m| m.location.to_string()).collect() + } + + #[test] + fn test_single_top_level() { + let metas = vec![meta("a/zarr.json")]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_nested_group_dropped() { + let metas = vec![ + meta("a/zarr.json"), // top-level + meta("a/b/zarr.json"), // nested -> drop + ]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_multiple_top_level() { + let metas = vec![meta("a/zarr.json"), meta("b/zarr.json")]; + let result = top_level_zarr_meta_v3(&metas); + let mut paths = extract_paths(&result); + paths.sort(); + assert_eq!(paths, vec!["a/zarr.json", "b/zarr.json"]); + } + + #[test] + fn test_deep_nesting_dropped() { + let metas = vec![ + meta("a/zarr.json"), + meta("a/b/zarr.json"), + meta("a/b/c/zarr.json"), + ]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_sibling_groups() { + let metas = vec![ + meta("root1/zarr.json"), + meta("root2/zarr.json"), + meta("root2/child/zarr.json"), // nested -> drop + ]; + let result = top_level_zarr_meta_v3(&metas); + let mut paths = extract_paths(&result); + paths.sort(); + assert_eq!(paths, vec!["root1/zarr.json", "root2/zarr.json"]); + } + + #[test] + fn test_root_level_key() { + let metas = vec![ + meta("zarr.json"), // top-level at root + meta("a/zarr.json"), // nested under root -> drop + ]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_ignore_non_zarr_json() { + let metas = vec![ + meta("a/zarr.json"), // valid + meta("b/not_zarr.json"), + meta("c/zarr.txt"), + meta("d/data.bin"), + ]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_empty_input() { + let metas: Vec = vec![]; + let result = top_level_zarr_meta_v3(&metas); + assert!(result.is_empty()); + } } diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs new file mode 100644 index 00000000..23a9d2fc --- /dev/null +++ b/beacon-formats/src/zarr/source.rs @@ -0,0 +1,317 @@ +use std::{collections::HashMap, sync::Arc}; + +use arrow::{ + array::RecordBatch, + datatypes::{Schema, SchemaRef}, + error::ArrowError, +}; +use beacon_arrow_zarr::reader::AsyncArrowZarrGroupReader; +use datafusion::{ + common::Statistics, + datasource::{ + listing::PartitionedFile, + physical_plan::{ + FileGroup, FileMeta, FileOpenFuture, FileOpener, FileScanConfig, FileSource, + }, + schema_adapter::{DefaultSchemaAdapterFactory, SchemaAdapter, SchemaAdapterFactory}, + }, + physical_plan::metrics::ExecutionPlanMetricsSet, +}; +use futures::{FutureExt, StreamExt}; +use object_store::{ObjectMeta, ObjectStore}; +use parking_lot::Mutex; +use zarrs::group::Group; +use zarrs_object_store::AsyncObjectStore; +use zarrs_storage::AsyncReadableListableStorageTraits; + +use crate::zarr::{path_parent, stream_share::ZarrStreamShare}; + +pub async fn fetch_schema( + object_store: Arc, + object_meta: &ObjectMeta, +) -> datafusion::error::Result { + let zarr_store = Arc::new(AsyncObjectStore::new(object_store)) + as Arc; + + // The object meta reprensents the zarr.json file in the root of the zarr group. We need to open the group at the parent directory of this file. + let parent_path = path_parent(&object_meta.location).ok_or_else(|| { + datafusion::error::DataFusionError::Execution(format!( + "Could not determine parent path of object: {}", + object_meta.location + )) + })?; + + let group = Group::async_open(zarr_store.clone(), &format!("/{}", parent_path)) + .await + .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; + + let reader = AsyncArrowZarrGroupReader::new(group) + .await + .map_err(datafusion::error::DataFusionError::Execution)?; + + let schema = reader.arrow_schema(); + + Ok(schema) +} + +#[derive(Default, Clone)] +pub struct ZarrSource { + /// Optional schema adapter factory. + schema_adapter_factory: Option>, + /// Optional schema override. + override_schema: Option, + /// Optional column projection. + projection: Option>, + /// Execution plan metrics. + execution_plan_metrics: ExecutionPlanMetricsSet, + /// Projected statistics. + projected_statistics: Option, + /// Stream Partition Share + stream_partition_shares: Arc>>>, +} + +impl FileSource for ZarrSource { + /// Creates a file opener for the given object store and scan config. + fn create_file_opener( + &self, + object_store: Arc, + base_config: &FileScanConfig, + _partition: usize, + ) -> Arc { + let table_schema = self + .override_schema + .clone() + .unwrap_or_else(|| base_config.file_schema.clone()); + let projected_schema = base_config.projected_schema(); + let schema_adapter_factory = self + .schema_adapter_factory + .clone() + .unwrap_or_else(|| Arc::new(DefaultSchemaAdapterFactory)); + let schema_adapter = schema_adapter_factory.create(projected_schema, table_schema); + let arc_schema_adapter: Arc = Arc::from(schema_adapter); + + Arc::new(ZarrFileOpener { + zarr_object_store: Arc::new(AsyncObjectStore::new(object_store)), + schema_adapter: arc_schema_adapter, + stream_partition_shares: self.stream_partition_shares.clone(), + }) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn with_batch_size(&self, _batch_size: usize) -> Arc { + Arc::new(self.clone()) + } + + fn with_schema(&self, schema: SchemaRef) -> Arc { + Arc::new(Self { + override_schema: Some(schema), + projection: self.projection.clone(), + execution_plan_metrics: self.execution_plan_metrics.clone(), + projected_statistics: self.projected_statistics.clone(), + schema_adapter_factory: self.schema_adapter_factory.clone(), + stream_partition_shares: self.stream_partition_shares.clone(), + }) + } + + fn with_projection(&self, config: &FileScanConfig) -> Arc { + Arc::new(Self { + override_schema: self.override_schema.clone(), + projection: config.projection.clone(), + execution_plan_metrics: self.execution_plan_metrics.clone(), + projected_statistics: self.projected_statistics.clone(), + schema_adapter_factory: self.schema_adapter_factory.clone(), + stream_partition_shares: self.stream_partition_shares.clone(), + }) + } + + fn with_statistics(&self, statistics: Statistics) -> Arc { + Arc::new(Self { + override_schema: self.override_schema.clone(), + projection: self.projection.clone(), + execution_plan_metrics: self.execution_plan_metrics.clone(), + projected_statistics: Some(statistics), + schema_adapter_factory: self.schema_adapter_factory.clone(), + stream_partition_shares: self.stream_partition_shares.clone(), + }) + } + + fn metrics(&self) -> &ExecutionPlanMetricsSet { + &self.execution_plan_metrics + } + + fn statistics(&self) -> datafusion::error::Result { + if let Some(statistics) = &self.projected_statistics { + Ok(statistics.clone()) + } else if let Some(schema) = self.override_schema.as_ref() { + Ok(Statistics::new_unknown(schema)) + } else { + Err(datafusion::error::DataFusionError::Execution( + "Schema must be set to compute statistics".to_string(), + )) + } + } + + fn file_type(&self) -> &str { + "zarr" + } + + fn with_schema_adapter_factory( + &self, + factory: Arc, + ) -> datafusion::error::Result> { + Ok(Arc::new(Self { + override_schema: self.override_schema.clone(), + projection: self.projection.clone(), + execution_plan_metrics: self.execution_plan_metrics.clone(), + projected_statistics: self.projected_statistics.clone(), + schema_adapter_factory: Some(factory), + stream_partition_shares: self.stream_partition_shares.clone(), + })) + } + + fn schema_adapter_factory(&self) -> Option> { + self.schema_adapter_factory.clone() + } + + fn repartitioned( + &self, + target_partitions: usize, + repartition_file_min_size: usize, + output_ordering: Option, + config: &FileScanConfig, + ) -> datafusion::error::Result> { + // Repartion by duplicating the file groups to reach the target number of partitions. + let file_groups = config.file_groups.clone(); + + if file_groups.len() >= target_partitions { + // No need to repartition + return Ok(None); + } + + let repartitioned: Vec = file_groups + .iter() + .cycle() + .take(target_partitions) + .cloned() + .collect(); + + Ok(config.clone().with_file_groups(repartitioned).into()) + } +} + +pub struct ZarrFileOpener { + zarr_object_store: Arc>>, + /// Schema adapter for mapping NetCDF schema to Arrow schema. + schema_adapter: Arc, + /// Stream partition shares for the Zarr file. + stream_partition_shares: Arc>>>, +} + +impl FileOpener for ZarrFileOpener { + fn open( + &self, + file_meta: FileMeta, + _file: PartitionedFile, + ) -> datafusion::error::Result { + let zarr_object_store = self.zarr_object_store.clone(); + let adapter = self.schema_adapter.clone(); + let stream_partition_shares = self.stream_partition_shares.clone(); + let stream_partition_share = { + let mut stream_partition_share_map = stream_partition_shares.lock(); + let object_path = file_meta.object_meta.location.clone(); + println!( + "Getting or creating ZarrStreamShare for path: {}", + object_path + ); + stream_partition_share_map + .entry(object_path) + .or_insert_with(|| Arc::new(ZarrStreamShare::new())) + .clone() + }; + + let fut = async move { + let (stream, schema_mapper, file_schema) = stream_partition_share + .get_or_try_init(|| async move { + tracing::debug!("Opening file: {:?}", file_meta.object_meta.location); + + let parent_path = + path_parent(&file_meta.object_meta.location).ok_or_else(|| { + datafusion::error::DataFusionError::Execution(format!( + "Could not determine parent path of object: {}", + file_meta.object_meta.location + )) + })?; + + // Open the zarr group + let group = Group::async_open( + zarr_object_store.clone() as Arc, + &format!("/{}", parent_path), + ) + .await + .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; + + let reader = AsyncArrowZarrGroupReader::new(group).await.map_err(|e| { + datafusion::error::DataFusionError::Execution(e.to_string()) + })?; + + let file_schema = reader.arrow_schema(); + + let (schema_mapper, projection) = adapter + .map_schema(&file_schema) + .map_err(|e| datafusion::error::DataFusionError::External(Box::new(e)))?; + + let stream_producer = reader + .into_parallel_stream_composer(Some(projection)) + .map_err(|e| { + datafusion::error::DataFusionError::Execution(e.to_string()) + })?; + + Ok::<_, datafusion::error::DataFusionError>(( + stream_producer, + schema_mapper, + file_schema, + )) + }) + .await? + .clone(); + let producer = stream.pollable_shared_stream(); + + let stream_proxy = producer + .map( + move |nd_batch| -> Result { + let schema_mapper = schema_mapper.clone(); + let arrow_batch = nd_batch + .map_err(|e| { + tracing::error!( + "Error reading NdRecordBatch from Zarr stream: {:?}", + e + ); + ArrowError::IoError( + "Error reading NdRecordBatch from Zarr stream".to_string(), + std::io::Error::other(e), + ) + })? + .to_arrow_record_batch() + .unwrap_or_else(|e| { + tracing::error!( + "Error converting NdRecordBatch to Arrow RecordBatch: {:?}", + e + ); + RecordBatch::new_empty(file_schema.clone()) + }); + let mapped_batch = schema_mapper + .map_batch(arrow_batch) + .map_err(|e| ArrowError::ExternalError(Box::new(e)))?; + Ok(mapped_batch) + }, + ) + .boxed(); + Ok(stream_proxy) + }; + + Ok(fut.boxed()) + } +} diff --git a/beacon-formats/src/zarr/stream_share.rs b/beacon-formats/src/zarr/stream_share.rs new file mode 100644 index 00000000..ddea018d --- /dev/null +++ b/beacon-formats/src/zarr/stream_share.rs @@ -0,0 +1,44 @@ +use std::{future::Future, sync::Arc}; + +use arrow::datatypes::SchemaRef; +use beacon_arrow_zarr::stream::ArrowZarrStreamComposerRef; +use datafusion::datasource::schema_adapter::SchemaMapper; + +pub struct ZarrStreamShare { + inner: tokio::sync::OnceCell<(ArrowZarrStreamComposerRef, Arc, SchemaRef)>, +} + +impl ZarrStreamShare { + pub fn new() -> Self { + Self { + inner: tokio::sync::OnceCell::new(), + } + } + + pub fn get_or_try_init( + &self, + f: F, + ) -> impl Future< + Output = Result< + &(ArrowZarrStreamComposerRef, Arc, SchemaRef), + datafusion::error::DataFusionError, + >, + > + where + F: FnOnce() -> Fut, + Fut: Future< + Output = Result< + (ArrowZarrStreamComposerRef, Arc, SchemaRef), + datafusion::error::DataFusionError, + >, + >, + { + self.inner.get_or_try_init(f) + } +} + +impl Default for ZarrStreamShare { + fn default() -> Self { + Self::new() + } +} diff --git a/zarr.ipynb b/zarr.ipynb new file mode 100644 index 00000000..e69de29b From fa6711a8ebf3c4eea2fc9655e081a902639c3a75 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Tue, 14 Oct 2025 04:31:13 +0200 Subject: [PATCH 08/19] Fix streams --- beacon-arrow-zarr/src/stream.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs index 6a3c0aec..4ee5bcc3 100644 --- a/beacon-arrow-zarr/src/stream.rs +++ b/beacon-arrow-zarr/src/stream.rs @@ -15,6 +15,7 @@ pub struct ArrowZarrStreamComposer { // Streaming State chunk_grid: Option, dimension_names: Vec, + dimension_sizes: Vec, readable_chunks: crossbeam::queue::SegQueue, } @@ -50,6 +51,7 @@ impl ArrowZarrStreamComposer { let mut chunk_grid = None; let mut dimension_names = vec![]; + let mut dimension_sizes = vec![]; let queue = crossbeam::queue::SegQueue::new(); if let Some(var_name) = max_dims_var { @@ -65,6 +67,7 @@ impl ArrowZarrStreamComposer { chunk_grid = Some(chunk_shape.clone()); dimension_names = var_dimension_names; + dimension_sizes = var.shape().to_vec(); } if let Some(chunk_grid) = &chunk_grid { @@ -82,6 +85,7 @@ impl ArrowZarrStreamComposer { projected_schema, chunk_grid, dimension_names, + dimension_sizes, readable_chunks: queue, })) } @@ -123,12 +127,20 @@ impl ArrowZarrStream { None => return None, // Queue is empty }; let array_subset = composer.generate_array_subset(&chunk_indices); - // Blend dimension_names and array_subset ranges into a map + + // Blend dimension_names and array_subset ranges into a map (this will wrap ranges that exceed dimension sizes) let dimension_subset_ranges: IndexMap> = composer .dimension_names .iter() .zip(array_subset.to_ranges()) - .map(|(name, range)| (name.clone(), range)) + .zip(composer.dimension_sizes.iter()) + .map(|((name, range), dim_size)| { + if range.end > *dim_size { + (name.clone(), range.start..*dim_size) + } else { + (name.clone(), range) + } + }) .collect(); let mut arrays = vec![]; @@ -167,7 +179,6 @@ impl ArrowZarrStream { // Scalar array ArraySubset::new_empty(0) }; - let array = composer .group_reader .read_array(array_name, &subset) From 74ecdc50cd81108cafa289fb4a663028d3939ffc Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 15 Oct 2025 00:47:53 +0200 Subject: [PATCH 09/19] Implement zarr pushdown --- Cargo.lock | 2 + beacon-arrow-zarr/src/array_slice_pushdown.rs | 60 +++++ beacon-arrow-zarr/src/lib.rs | 1 + beacon-arrow-zarr/src/reader.rs | 10 +- beacon-arrow-zarr/src/stream.rs | 50 +++- beacon-data-lake/Cargo.toml | 3 +- beacon-data-lake/src/table/logical/mod.rs | 16 +- beacon-data-lake/src/table/table_formats.rs | 19 +- beacon-formats/Cargo.toml | 1 + beacon-formats/src/zarr/array_step_span.rs | 8 + beacon-formats/src/zarr/expr_util.rs | 232 ++++++++++++++++++ beacon-formats/src/zarr/mod.rs | 28 ++- beacon-formats/src/zarr/source.rs | 147 ++++++++++- 13 files changed, 550 insertions(+), 27 deletions(-) create mode 100644 beacon-arrow-zarr/src/array_slice_pushdown.rs create mode 100644 beacon-formats/src/zarr/array_step_span.rs create mode 100644 beacon-formats/src/zarr/expr_util.rs diff --git a/Cargo.lock b/Cargo.lock index 35a09fc2..09260234 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -705,6 +705,7 @@ version = "1.3.0" dependencies = [ "arrow", "async-trait", + "beacon-arrow-zarr", "beacon-config", "beacon-formats", "chrono", @@ -740,6 +741,7 @@ dependencies = [ "beacon-arrow-odv", "beacon-arrow-zarr", "beacon-common", + "chrono", "datafusion", "futures", "geoarrow", diff --git a/beacon-arrow-zarr/src/array_slice_pushdown.rs b/beacon-arrow-zarr/src/array_slice_pushdown.rs new file mode 100644 index 00000000..48c92476 --- /dev/null +++ b/beacon-arrow-zarr/src/array_slice_pushdown.rs @@ -0,0 +1,60 @@ +#[derive(Clone, Debug)] +pub struct ArraySlicePushDown { + pub dimension: String, + pub start: Option, + pub end: Option, +} + +impl ArraySlicePushDown { + pub fn new(dimension: String, start: Option, end: Option) -> Self { + Self { + dimension, + start, + end, + } + } + + pub fn dimension(&self) -> &str { + &self.dimension + } + + pub fn start(&self) -> Option { + self.start + } + + pub fn end(&self) -> Option { + self.end + } + + pub fn overlapping_range( + &self, + array_range: std::ops::Range, + ) -> Option> { + match (self.start, self.end) { + (Some(push_start), Some(push_end)) => { + let push_end = push_end + 1; // Make end inclusive + let start = array_range.start.max(push_start); + let end = array_range.end.min(push_end); + if start < end { Some(start..end) } else { None } + } + (Some(push_start), None) => { + let start = array_range.start.max(push_start); + if start < array_range.end { + Some(start..array_range.end) + } else { + None + } + } + (None, Some(push_end)) => { + let push_end = push_end + 1; // Make end inclusive + let end = array_range.end.min(push_end); + if array_range.start < end { + Some(array_range.start..end) + } else { + None + } + } + (None, None) => Some(array_range), + } + } +} diff --git a/beacon-arrow-zarr/src/lib.rs b/beacon-arrow-zarr/src/lib.rs index c2587ce3..12a62301 100644 --- a/beacon-arrow-zarr/src/lib.rs +++ b/beacon-arrow-zarr/src/lib.rs @@ -1,3 +1,4 @@ +pub mod array_slice_pushdown; pub mod attributes; pub mod data_types; pub mod decoder; diff --git a/beacon-arrow-zarr/src/reader.rs b/beacon-arrow-zarr/src/reader.rs index 5fb0fc7b..66068bac 100644 --- a/beacon-arrow-zarr/src/reader.rs +++ b/beacon-arrow-zarr/src/reader.rs @@ -13,6 +13,7 @@ use zarrs::{array::Array, array_subset::ArraySubset, group::Group}; use zarrs_storage::AsyncReadableListableStorageTraits; use crate::{ + array_slice_pushdown::ArraySlicePushDown, attributes::AttributeValue, decoder::{self, Decoder}, stream::{ArrowZarrStreamComposer, ArrowZarrStreamComposerRef}, @@ -126,13 +127,14 @@ impl AsyncArrowZarrGroupReader { pub fn into_parallel_stream_composer>( self, projection: Option

, + array_slice_pushdowns: Option>, ) -> Result { let projected_schema = match projection { Some(p) => Arc::new(self.schema.project(p.as_ref()).map_err(|e| e.to_string())?), None => self.schema.clone(), }; - ArrowZarrStreamComposer::new(self, projected_schema) + ArrowZarrStreamComposer::new(self, projected_schema, array_slice_pushdowns) } pub fn read_attribute(&self, attribute_name: &str) -> Option { @@ -392,6 +394,7 @@ mod tests { ); println!("SST attrs: {:?}", sst_arr.attributes()); + println!("SST dims: {:?}", sst_arr.dimension_names()); let y = sst_arr .chunk_grid() @@ -474,7 +477,10 @@ mod tests { .expect("analysed_sst field not found in schema"); let mut stream = reader - .into_parallel_stream_composer(Some(&[longitude_idx, latitude_idx, analysed_sst_idx])) + .into_parallel_stream_composer( + Some(&[longitude_idx, latitude_idx, analysed_sst_idx]), + None, + ) .unwrap() .pollable_shared_stream(); diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs index 4ee5bcc3..793d6e63 100644 --- a/beacon-arrow-zarr/src/stream.rs +++ b/beacon-arrow-zarr/src/stream.rs @@ -1,11 +1,11 @@ -use std::{pin::Pin, sync::Arc, task::Poll}; +use std::{collections::HashMap, pin::Pin, sync::Arc, task::Poll}; use futures::{Future, Stream}; use indexmap::IndexMap; -use nd_arrow_array::batch::NdRecordBatch; +use nd_arrow_array::{NdArrowArray, batch::NdRecordBatch}; use zarrs::{array::ChunkGrid, array_subset::ArraySubset}; -use crate::reader::AsyncArrowZarrGroupReader; +use crate::{array_slice_pushdown::ArraySlicePushDown, reader::AsyncArrowZarrGroupReader}; pub type ArrowZarrStreamComposerRef = Arc; @@ -17,12 +17,14 @@ pub struct ArrowZarrStreamComposer { dimension_names: Vec, dimension_sizes: Vec, readable_chunks: crossbeam::queue::SegQueue, + array_slice_pushdowns: Option>, } impl ArrowZarrStreamComposer { pub fn new( group_reader: AsyncArrowZarrGroupReader, projected_schema: arrow::datatypes::SchemaRef, + array_slice_pushdowns: Option>, ) -> Result { // Get all the variables let variables = projected_schema @@ -87,6 +89,7 @@ impl ArrowZarrStreamComposer { dimension_names, dimension_sizes, readable_chunks: queue, + array_slice_pushdowns, })) } @@ -129,7 +132,7 @@ impl ArrowZarrStream { let array_subset = composer.generate_array_subset(&chunk_indices); // Blend dimension_names and array_subset ranges into a map (this will wrap ranges that exceed dimension sizes) - let dimension_subset_ranges: IndexMap> = composer + let mut dimension_subset_ranges: IndexMap> = composer .dimension_names .iter() .zip(array_subset.to_ranges()) @@ -142,7 +145,46 @@ impl ArrowZarrStream { } }) .collect(); + // println!( + // "Array slice pushdowns: {:?}", + // composer.array_slice_pushdowns + // ); + if let Some(pushdowns) = &composer.array_slice_pushdowns { + // Apply array slice pushdowns + for (dim_name, pushdown) in pushdowns.iter() { + if let Some(range) = dimension_subset_ranges.get_mut(dim_name) { + if let Some(overlap) = + pushdown.overlapping_range(range.start as usize..range.end as usize) + { + *range = overlap.start as u64..overlap.end as u64; + // println!("Applying pushdown on dimension {}: {:?}", dim_name, range); + } else { + // No overlap, return empty batch + // println!( + // "Skipping chunk {:?} due to no overlap with pushdown on dimension {}", + // chunk_indices, dim_name + // ); + let mut arrays = vec![]; + let mut fields = vec![]; + for field in composer.projected_schema.fields() { + fields.push(field.as_ref().clone()); + arrays.push(NdArrowArray::new_null_scalar(Some( + field.data_type().clone(), + ))); + } + let empty_batch = + NdRecordBatch::new(fields, arrays).map_err(|e| e.to_string()); + return Some(empty_batch); + } + } + } + } + tracing::debug!( + "Reading chunk {:?} with subset ranges: {:?}", + chunk_indices, + dimension_subset_ranges + ); let mut arrays = vec![]; let mut fields = vec![]; for column in composer.projected_schema.fields().iter() { diff --git a/beacon-data-lake/Cargo.toml b/beacon-data-lake/Cargo.toml index 6ab912c2..6aeea5de 100644 --- a/beacon-data-lake/Cargo.toml +++ b/beacon-data-lake/Cargo.toml @@ -35,4 +35,5 @@ utoipa = { workspace=true} beacon-config = { path = "../beacon-config" } -beacon-formats = { path = "../beacon-formats" } \ No newline at end of file +beacon-formats = { path = "../beacon-formats" } +beacon-arrow-zarr = { path = "../beacon-arrow-zarr" } \ No newline at end of file diff --git a/beacon-data-lake/src/table/logical/mod.rs b/beacon-data-lake/src/table/logical/mod.rs index 4208858c..8e338b1a 100644 --- a/beacon-data-lake/src/table/logical/mod.rs +++ b/beacon-data-lake/src/table/logical/mod.rs @@ -117,11 +117,17 @@ impl LogicalTable { )?; // Create the data source with the given file format and table URLs. - let source = FileCollection::new(&session_state, file_format_factory.default(), table_urls) - .await - .map_err(|e| { - TableError::GenericTableError(format!("Failed to create file collection: {}", e)) - })?; + let source = FileCollection::new( + &session_state, + self.file_format + .file_format() + .unwrap_or(file_format_factory.default()), + table_urls, + ) + .await + .map_err(|e| { + TableError::GenericTableError(format!("Failed to create file collection: {}", e)) + })?; Ok(Arc::new(source)) } diff --git a/beacon-data-lake/src/table/table_formats.rs b/beacon-data-lake/src/table/table_formats.rs index a3b13f6a..527ac969 100644 --- a/beacon-data-lake/src/table/table_formats.rs +++ b/beacon-data-lake/src/table/table_formats.rs @@ -1,10 +1,14 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; -use beacon_formats::{arrow::ArrowFormat, csv::CsvFormat, parquet::ParquetFormat}; +use beacon_formats::zarr::{ZarrFormat, array_step_span::NumericArrayStepSpan}; +use datafusion::datasource::file_format::FileFormat; #[typetag::serde(tag = "file_format")] pub trait TableFileFormat: std::fmt::Debug + Send + Sync { fn file_ext(&self) -> String; + fn file_format(&self) -> Option> { + None + } } #[derive(Debug, serde::Deserialize, serde::Serialize)] @@ -50,11 +54,20 @@ impl TableFileFormat for NetCDFFileFormat { } #[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct ZarrFileFormat; +pub struct ZarrFileFormat { + #[serde(default)] + pub global_array_steps: HashMap, +} #[typetag::serde(name = "zarr")] impl TableFileFormat for ZarrFileFormat { fn file_ext(&self) -> String { "zarr".to_string() } + + fn file_format(&self) -> Option> { + Some(Arc::new( + ZarrFormat::default().with_array_steps(self.global_array_steps.clone()), + )) + } } diff --git a/beacon-formats/Cargo.toml b/beacon-formats/Cargo.toml index 15917aa6..54ddbfde 100644 --- a/beacon-formats/Cargo.toml +++ b/beacon-formats/Cargo.toml @@ -31,6 +31,7 @@ zarrs = {"version" = "0.22.2", features = ["async"]} url = "2.5.4" glob = "0.3.3" +chrono = { workspace = true } beacon-common = { path = "../beacon-common" } beacon-arrow-netcdf = { path = "../beacon-arrow-netcdf" } diff --git a/beacon-formats/src/zarr/array_step_span.rs b/beacon-formats/src/zarr/array_step_span.rs new file mode 100644 index 00000000..5a993c44 --- /dev/null +++ b/beacon-formats/src/zarr/array_step_span.rs @@ -0,0 +1,8 @@ +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct NumericArrayStepSpan { + pub dimension: String, + pub column: String, + pub start: f64, + pub end: f64, + pub step: f64, +} diff --git a/beacon-formats/src/zarr/expr_util.rs b/beacon-formats/src/zarr/expr_util.rs new file mode 100644 index 00000000..ce665b2a --- /dev/null +++ b/beacon-formats/src/zarr/expr_util.rs @@ -0,0 +1,232 @@ +use datafusion::logical_expr::Operator; +use datafusion::physical_expr::PhysicalExpr; +use datafusion::physical_expr::expressions::{BinaryExpr, CastExpr, Column, Literal}; +use datafusion::scalar::ScalarValue; +use std::sync::Arc; + +/// A single interval: (value, inclusive?) +#[derive(Debug, Clone, Default)] +pub struct Range { + pub min: Option<(ScalarValue, bool)>, + pub max: Option<(ScalarValue, bool)>, +} + +impl Range { + pub fn as_f64_max(&self) -> Option { + self.max.as_ref().and_then(|(v, _)| { + if let ScalarValue::Float64(Some(f)) = v { + Some(*f) + } else { + None + } + }) + } + + pub fn as_f64_min(&self) -> Option { + self.min.as_ref().and_then(|(v, _)| { + if let ScalarValue::Float64(Some(f)) = v { + Some(*f) + } else { + None + } + }) + } + + pub fn as_timestamp_max(&self) -> Option { + self.max.as_ref().and_then(|(v, _)| match v { + ScalarValue::TimestampNanosecond(Some(ts), None) => { + chrono::NaiveDateTime::from_timestamp_nanos(*ts) + } + ScalarValue::TimestampMicrosecond(Some(ts), None) => { + chrono::NaiveDateTime::from_timestamp_micros(*ts) + } + ScalarValue::TimestampMillisecond(Some(ts), None) => { + chrono::NaiveDateTime::from_timestamp_millis(*ts) + } + ScalarValue::TimestampSecond(Some(ts), None) => { + Some(chrono::NaiveDateTime::from_timestamp(*ts, 0)) + } + _ => None, + }) + } + + pub fn as_timestamp_min(&self) -> Option { + self.min.as_ref().and_then(|(v, _)| match v { + ScalarValue::TimestampNanosecond(Some(ts), None) => { + chrono::NaiveDateTime::from_timestamp_nanos(*ts) + } + ScalarValue::TimestampMicrosecond(Some(ts), None) => { + chrono::NaiveDateTime::from_timestamp_micros(*ts) + } + ScalarValue::TimestampMillisecond(Some(ts), None) => { + chrono::NaiveDateTime::from_timestamp_millis(*ts) + } + ScalarValue::TimestampSecond(Some(ts), None) => { + Some(chrono::NaiveDateTime::from_timestamp(*ts, 0)) + } + _ => None, + }) + } + + pub fn is_max_inclusive(&self) -> Option { + self.max.as_ref().map(|(_, inc)| *inc) + } + pub fn is_min_inclusive(&self) -> Option { + self.min.as_ref().map(|(_, inc)| *inc) + } + + fn tighten_lower(&mut self, val: ScalarValue, inclusive: bool) { + match &self.min { + None => self.min = Some((val, inclusive)), + Some((cur, cur_inc)) => { + if let Some(ord) = cur.partial_cmp(&val) { + use std::cmp::Ordering::*; + match ord { + Less => self.min = Some((val, inclusive)), + Equal => self.min = Some((val, *cur_inc && inclusive)), + Greater => {} // keep tighter existing lower + } + } + } + } + } + fn tighten_upper(&mut self, val: ScalarValue, inclusive: bool) { + match &self.max { + None => self.max = Some((val, inclusive)), + Some((cur, cur_inc)) => { + if let Some(ord) = cur.partial_cmp(&val) { + use std::cmp::Ordering::*; + match ord { + Greater => self.max = Some((val, inclusive)), + Equal => self.max = Some((val, *cur_inc && inclusive)), + Less => {} // keep tighter existing upper + } + } + } + } + } +} + +/// Entry point: extract tightest range for `target_col` from a set of +/// physical filter expressions (possibly complex AND trees). +pub fn extract_range_from_physical_filters( + filters: &[Arc], + target_col: &str, +) -> Option { + let mut out = Range::default(); + for f in filters { + walk_physical_expr(f, target_col, &mut out); + } + + if out.min.is_some() || out.max.is_some() { + Some(out) + } else { + None + } +} + +fn walk_physical_expr(node: &Arc, target_col: &str, out: &mut Range) { + if let Some(bin) = downcast::(node) { + let op = bin.op(); + + // AND = BinaryExpr with Operator::And: visit both sides + if *op == Operator::And { + walk_physical_expr(bin.left(), target_col, out); + walk_physical_expr(bin.right(), target_col, out); + return; + } + + // Comparison: try (col vs lit), then (lit vs col) with flipped op + if let (Some(_c), Some(lv)) = ( + as_target_col(bin.left(), target_col), + as_literal(bin.right()), + ) { + apply_cmp(out, *op, lv); + return; + } + if let (Some(lv), Some(_c)) = ( + as_literal(bin.left()), + as_target_col(bin.right(), target_col), + ) { + apply_cmp(out, flip_op(*op), lv); + return; + } + + // Not a direct pattern: still walk children (there might be nested pieces) + walk_physical_expr(bin.left(), target_col, out); + walk_physical_expr(bin.right(), target_col, out); + return; + } + + // Generic: peel through any children (e.g., some wrappers/functions you may want to inspect) + for child in node.children() { + walk_physical_expr(child, target_col, out); + } +} + +fn apply_cmp(out: &mut Range, op: Operator, lit: &ScalarValue) { + match op { + Operator::Gt => out.tighten_lower(lit.clone(), false), + Operator::GtEq => out.tighten_lower(lit.clone(), true), + Operator::Lt => out.tighten_upper(lit.clone(), false), + Operator::LtEq => out.tighten_upper(lit.clone(), true), + Operator::Eq => { + out.tighten_lower(lit.clone(), true); + out.tighten_upper(lit.clone(), true); + } + _ => {} // ignore OR, NotEq, etc. (not a single-interval constraint) + } +} + +fn flip_op(op: Operator) -> Operator { + match op { + Operator::Lt => Operator::Gt, + Operator::LtEq => Operator::GtEq, + Operator::Gt => Operator::Lt, + Operator::GtEq => Operator::LtEq, + other => other, + } +} + +/// ---- helpers ---- + +fn as_literal(e: &Arc) -> Option<&ScalarValue> { + match peel_casts(e) { + Some(l) if downcast::(l).is_some() => { + downcast::(l).map(|lit| lit.value()) + } + _ => None, + } +} + +fn as_target_col<'l>(e: &'l Arc, target: &str) -> Option<&'l Column> { + match peel_casts(e) { + Some(c) if downcast::(c).is_some() => { + let c = downcast::(c).unwrap(); + // Column::name() is the leaf name at physical layer; qualify if you pass "table.col" + if c.name() == target || format!("{}", c).ends_with(&format!(".{}", target)) { + Some(c) + } else { + None + } + } + _ => None, + } +} + +/// Remove any number of CAST(...) wrappers around an expression. +fn peel_casts(mut e: &Arc) -> Option<&Arc> { + let mut cur = e; + loop { + if let Some(c) = downcast::(cur) { + cur = c.expr(); + continue; + } + return Some(cur); + } +} + +/// Convenience downcast +fn downcast(expr: &Arc) -> Option<&T> { + expr.as_any().downcast_ref::() +} diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs index 863b591e..92f8ff4e 100644 --- a/beacon-formats/src/zarr/mod.rs +++ b/beacon-formats/src/zarr/mod.rs @@ -20,8 +20,13 @@ use datafusion::{ use object_store::{ObjectMeta, ObjectStore}; use zarrs_object_store::AsyncObjectStore; -use crate::zarr::source::{ZarrSource, fetch_schema}; +use crate::zarr::{ + array_step_span::NumericArrayStepSpan, + source::{ZarrSource, fetch_schema}, +}; +pub mod array_step_span; +pub mod expr_util; mod source; mod stream_share; @@ -58,11 +63,11 @@ impl FileFormatFactory for ZarrFormatFactory { format_options: &std::collections::HashMap, ) -> datafusion::error::Result> { // Here you can use `state` and `format_options` to customize the creation of the FileFormat - Ok(Arc::new(ZarrFormat)) + Ok(Arc::new(ZarrFormat::default())) } fn default(&self) -> std::sync::Arc { - Arc::new(ZarrFormat) + Arc::new(ZarrFormat::default()) } fn as_any(&self) -> &dyn std::any::Any { @@ -70,8 +75,17 @@ impl FileFormatFactory for ZarrFormatFactory { } } -#[derive(Debug, Clone)] -pub struct ZarrFormat; +#[derive(Debug, Clone, Default)] +pub struct ZarrFormat { + array_steps: HashMap, +} + +impl ZarrFormat { + pub fn with_array_steps(mut self, array_steps: HashMap) -> Self { + self.array_steps = array_steps; + self + } +} #[async_trait::async_trait] impl FileFormat for ZarrFormat { @@ -144,7 +158,7 @@ impl FileFormat for ZarrFormat { }) .collect(); - let source = ZarrSource::default(); + let source = ZarrSource::default().with_array_steps(self.array_steps.clone()); let conf = FileScanConfigBuilder::from(conf) .with_file_groups(file_groups) .with_source(Arc::new(source)) @@ -153,7 +167,7 @@ impl FileFormat for ZarrFormat { } fn file_source(&self) -> Arc { - Arc::new(ZarrSource::default()) + Arc::new(ZarrSource::default().with_array_steps(self.array_steps.clone())) } } diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs index 23a9d2fc..fb28e95f 100644 --- a/beacon-formats/src/zarr/source.rs +++ b/beacon-formats/src/zarr/source.rs @@ -5,9 +5,12 @@ use arrow::{ datatypes::{Schema, SchemaRef}, error::ArrowError, }; -use beacon_arrow_zarr::reader::AsyncArrowZarrGroupReader; +use beacon_arrow_zarr::{ + array_slice_pushdown::ArraySlicePushDown, reader::AsyncArrowZarrGroupReader, +}; use datafusion::{ common::Statistics, + config::ConfigOptions, datasource::{ listing::PartitionedFile, physical_plan::{ @@ -15,7 +18,12 @@ use datafusion::{ }, schema_adapter::{DefaultSchemaAdapterFactory, SchemaAdapter, SchemaAdapterFactory}, }, - physical_plan::metrics::ExecutionPlanMetricsSet, + physical_expr::conjunction, + physical_plan::{ + PhysicalExpr, + filter_pushdown::{FilterPushdownPropagation, PushedDown}, + metrics::ExecutionPlanMetricsSet, + }, }; use futures::{FutureExt, StreamExt}; use object_store::{ObjectMeta, ObjectStore}; @@ -24,7 +32,10 @@ use zarrs::group::Group; use zarrs_object_store::AsyncObjectStore; use zarrs_storage::AsyncReadableListableStorageTraits; -use crate::zarr::{path_parent, stream_share::ZarrStreamShare}; +use crate::zarr::{ + array_step_span::NumericArrayStepSpan, expr_util::extract_range_from_physical_filters, + path_parent, stream_share::ZarrStreamShare, +}; pub async fn fetch_schema( object_store: Arc, @@ -68,6 +79,17 @@ pub struct ZarrSource { projected_statistics: Option, /// Stream Partition Share stream_partition_shares: Arc>>>, + /// Array Steps for slicing arrays based on step spans. This is utilized by the pruning predicate pushdown. + array_steps: Arc>, + /// Pruning Predicate + predicate: Option>, +} + +impl ZarrSource { + pub fn with_array_steps(mut self, array_steps: HashMap) -> Self { + self.array_steps = Arc::new(array_steps); + self + } } impl FileSource for ZarrSource { @@ -94,6 +116,8 @@ impl FileSource for ZarrSource { zarr_object_store: Arc::new(AsyncObjectStore::new(object_store)), schema_adapter: arc_schema_adapter, stream_partition_shares: self.stream_partition_shares.clone(), + array_steps: self.array_steps.clone(), + predicate: self.predicate.clone(), }) } @@ -113,6 +137,8 @@ impl FileSource for ZarrSource { projected_statistics: self.projected_statistics.clone(), schema_adapter_factory: self.schema_adapter_factory.clone(), stream_partition_shares: self.stream_partition_shares.clone(), + array_steps: self.array_steps.clone(), + predicate: self.predicate.clone(), }) } @@ -124,6 +150,8 @@ impl FileSource for ZarrSource { projected_statistics: self.projected_statistics.clone(), schema_adapter_factory: self.schema_adapter_factory.clone(), stream_partition_shares: self.stream_partition_shares.clone(), + array_steps: self.array_steps.clone(), + predicate: self.predicate.clone(), }) } @@ -135,6 +163,8 @@ impl FileSource for ZarrSource { projected_statistics: Some(statistics), schema_adapter_factory: self.schema_adapter_factory.clone(), stream_partition_shares: self.stream_partition_shares.clone(), + array_steps: self.array_steps.clone(), + predicate: self.predicate.clone(), }) } @@ -169,6 +199,8 @@ impl FileSource for ZarrSource { projected_statistics: self.projected_statistics.clone(), schema_adapter_factory: Some(factory), stream_partition_shares: self.stream_partition_shares.clone(), + array_steps: self.array_steps.clone(), + predicate: self.predicate.clone(), })) } @@ -200,6 +232,34 @@ impl FileSource for ZarrSource { Ok(config.clone().with_file_groups(repartitioned).into()) } + + fn try_pushdown_filters( + &self, + filters: Vec>, + _config: &ConfigOptions, + ) -> datafusion::error::Result>> { + let Some(file_schema) = self.override_schema.clone() else { + return Ok(FilterPushdownPropagation::with_parent_pushdown_result( + vec![PushedDown::No; filters.len()], + )); + }; + + let predicate = match self.predicate.clone() { + Some(predicate) => conjunction(std::iter::once(predicate).chain(filters.clone())), + None => conjunction(filters.clone()), + }; + + let source = Self { + predicate: Some(predicate), + ..self.clone() + }; + + Ok(FilterPushdownPropagation::with_parent_pushdown_result(vec![ + PushedDown::No; + filters.len() + ]) + .with_updated_node(Arc::new(source))) + } } pub struct ZarrFileOpener { @@ -208,6 +268,10 @@ pub struct ZarrFileOpener { schema_adapter: Arc, /// Stream partition shares for the Zarr file. stream_partition_shares: Arc>>>, + /// Array Steps for slicing arrays based on step spans. This is utilized by the pruning predicate pushdown. + array_steps: Arc>, + /// Pruning Predicate + predicate: Option>, } impl FileOpener for ZarrFileOpener { @@ -222,7 +286,7 @@ impl FileOpener for ZarrFileOpener { let stream_partition_share = { let mut stream_partition_share_map = stream_partition_shares.lock(); let object_path = file_meta.object_meta.location.clone(); - println!( + tracing::debug!( "Getting or creating ZarrStreamShare for path: {}", object_path ); @@ -231,6 +295,8 @@ impl FileOpener for ZarrFileOpener { .or_insert_with(|| Arc::new(ZarrStreamShare::new())) .clone() }; + let pruning_predicate = self.predicate.clone(); + let array_steps = self.array_steps.clone(); let fut = async move { let (stream, schema_mapper, file_schema) = stream_partition_share @@ -263,8 +329,27 @@ impl FileOpener for ZarrFileOpener { .map_schema(&file_schema) .map_err(|e| datafusion::error::DataFusionError::External(Box::new(e)))?; + // Generate ArraySlicePushDown based on the predicate and array steps + let array_slice_pushdowns = if let Some(pruning_predicate) = &pruning_predicate + { + array_steps + .values() + .filter_map(|span| { + generate_numeric_span_slice_pushdown(pruning_predicate, span) + }) + .map(|pd| (pd.dimension().to_string(), pd)) + .collect() + } else { + HashMap::new() + }; + + tracing::debug!("Array Slice Pushdowns: {:?}", array_slice_pushdowns); + let stream_producer = reader - .into_parallel_stream_composer(Some(projection)) + .into_parallel_stream_composer( + Some(projection), + Some(array_slice_pushdowns), + ) .map_err(|e| { datafusion::error::DataFusionError::Execution(e.to_string()) })?; @@ -315,3 +400,55 @@ impl FileOpener for ZarrFileOpener { Ok(fut.boxed()) } } + +fn generate_numeric_span_slice_pushdown( + pruning_predicate: &Arc, + span: &NumericArrayStepSpan, +) -> Option { + let r = + extract_range_from_physical_filters(std::slice::from_ref(pruning_predicate), &span.column); + let mut min_step = None; + let mut max_step = None; + if let Some(min) = r.as_ref().and_then(|r| r.as_f64_min()) { + // Calculate start index based on step + let start_index = lower_index(span.start, span.step, min); + min_step = Some(if start_index < 0 { 0 } else { start_index }); + } + if let Some(max) = r.as_ref().and_then(|r| r.as_f64_max()) { + // Calculate end index based on step + let end_index = upper_index(span.start, span.step, max); + max_step = Some(if end_index < 0 { 0 } else { end_index }); + } + + // If min_step > max_step, then swap them + if let (Some(min), Some(max)) = (min_step, max_step) + && min > max + { + min_step = Some(max); + max_step = Some(min); + } + + Some(ArraySlicePushDown::new( + span.dimension.clone(), + min_step.map(|v| v as usize), + max_step.map(|v| v as usize), + )) +} + +fn lower_index(start: f64, step: f64, value: f64) -> i64 { + let raw = (value - start) / step; + if step >= 0.0 { + raw.floor() as i64 // ascending: lower = floor + } else { + raw.ceil() as i64 // descending: lower = ceil + } +} + +fn upper_index(start: f64, step: f64, value: f64) -> i64 { + let raw = (value - start) / step; + if step >= 0.0 { + raw.ceil() as i64 // ascending: upper = ceil + } else { + raw.floor() as i64 // descending: upper = floor + } +} From 3768a0bfa516521a2014c384a715abc3b28e8830 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 15 Oct 2025 17:40:41 +0200 Subject: [PATCH 10/19] wip --- beacon-arrow-zarr/src/reader.rs | 24 ++++- beacon-arrow-zarr/src/stream.rs | 4 - beacon-formats/src/zarr/mod.rs | 95 ++++++++++++++++++- .../src/zarr/pushdown_statistics.rs | 10 ++ beacon-formats/src/zarr/source.rs | 29 ++++-- 5 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 beacon-formats/src/zarr/pushdown_statistics.rs diff --git a/beacon-arrow-zarr/src/reader.rs b/beacon-arrow-zarr/src/reader.rs index 66068bac..465a2cc4 100644 --- a/beacon-arrow-zarr/src/reader.rs +++ b/beacon-arrow-zarr/src/reader.rs @@ -20,7 +20,7 @@ use crate::{ }; pub struct AsyncArrowZarrGroupReader { - group: Group, + group: Arc>, decoders: Vec>, schema: arrow::datatypes::SchemaRef, arrays: IndexMap>, @@ -28,7 +28,9 @@ pub struct AsyncArrowZarrGroupReader { } impl AsyncArrowZarrGroupReader { - pub async fn new(group: Group) -> Result { + pub async fn new( + group: Arc>, + ) -> Result { let mut fields = Vec::new(); let mut attributes = IndexMap::new(); @@ -143,6 +145,16 @@ impl AsyncArrowZarrGroupReader { .map(|attr| attr.as_nd_arrow_array()) } + pub async fn read_array_full(&self, array_name: &str) -> Result, String> { + match self.arrays.get(array_name) { + Some(v) => { + let subset = v.subset_all(); + self.read_array(array_name, &subset).await + } + None => Ok(None), + } + } + pub async fn read_array( &self, array_name: &str, @@ -440,7 +452,9 @@ mod tests { .await .unwrap(); - let reader = super::AsyncArrowZarrGroupReader::new(group).await.unwrap(); + let reader = super::AsyncArrowZarrGroupReader::new(Arc::new(group)) + .await + .unwrap(); let schema = reader.arrow_schema(); for field in schema.fields() { @@ -460,7 +474,9 @@ mod tests { .await .unwrap(); - let reader = super::AsyncArrowZarrGroupReader::new(group).await.unwrap(); + let reader = super::AsyncArrowZarrGroupReader::new(Arc::new(group)) + .await + .unwrap(); let longitude_idx = reader .schema diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs index 793d6e63..0c3bfe7a 100644 --- a/beacon-arrow-zarr/src/stream.rs +++ b/beacon-arrow-zarr/src/stream.rs @@ -145,10 +145,6 @@ impl ArrowZarrStream { } }) .collect(); - // println!( - // "Array slice pushdowns: {:?}", - // composer.array_slice_pushdowns - // ); if let Some(pushdowns) = &composer.array_slice_pushdowns { // Apply array slice pushdowns for (dim_name, pushdown) in pushdowns.iter() { diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs index 92f8ff4e..d9280ec2 100644 --- a/beacon-formats/src/zarr/mod.rs +++ b/beacon-formats/src/zarr/mod.rs @@ -5,9 +5,10 @@ use std::{ }; use arrow::datatypes::SchemaRef; +use beacon_arrow_zarr::reader::AsyncArrowZarrGroupReader; use datafusion::{ catalog::{Session, memory::DataSourceExec}, - common::{GetExt, Statistics}, + common::{ColumnStatistics, GetExt, Statistics}, datasource::{ file_format::{FileFormat, FileFormatFactory, file_compression_type::FileCompressionType}, listing::PartitionedFile, @@ -18,15 +19,19 @@ use datafusion::{ physical_plan::ExecutionPlan, }; use object_store::{ObjectMeta, ObjectStore}; +use zarrs::group::Group; use zarrs_object_store::AsyncObjectStore; +use zarrs_storage::AsyncReadableListableStorageTraits; use crate::zarr::{ array_step_span::NumericArrayStepSpan, + pushdown_statistics::PushDownZarrStatistics, source::{ZarrSource, fetch_schema}, }; pub mod array_step_span; pub mod expr_util; +pub mod pushdown_statistics; mod source; mod stream_share; @@ -78,6 +83,7 @@ impl FileFormatFactory for ZarrFormatFactory { #[derive(Debug, Clone, Default)] pub struct ZarrFormat { array_steps: HashMap, + zarr_pushdown_statistics: PushDownZarrStatistics, } impl ZarrFormat { @@ -171,6 +177,93 @@ impl FileFormat for ZarrFormat { } } +impl ZarrFormat { + async fn partition_zarr_group( + &self, + object: &ObjectMeta, + object_store: Arc, + ) -> datafusion::error::Result { + // The object can be assumed to be a top-level zarr.json file. + let zarr_store = Arc::new(AsyncObjectStore::new(object_store)); + let group = Group::async_open(Arc::new(zarr_store), object.location.to_string().as_str()) + .await + .map_err(|e| { + datafusion::error::DataFusionError::Execution(format!( + "Failed to open Zarr group at {}: {}", + object.location, e + )) + })?; + + match Self::find_partitioned_files(&group).await { + Some(groups) => { + // Means it is a partitioned zarr group with sub-groups + // Each sub-group is a partition + // Create a FileGroup for each sub-group + // Each FileGroup contains a single PartitionedFile pointing to the zarr.json of the sub-group + + for group in groups { + let path = group.path(); + + // let partioned_file = PartitionedFile::from() + } + } + None => { + // Means it is a single zarr group with no sub-groups + } + } + + todo!() + } + + async fn generate_statistics_for_partitioned_file( + &self, + table_schema: &SchemaRef, + group: Arc>, + ) -> Option { + let reader = AsyncArrowZarrGroupReader::new(group.clone()).await.ok()?; + + let mut statistics = Statistics::default(); + for field in table_schema.fields() { + if self.zarr_pushdown_statistics.has_array(field.name()) { + // Read the full array + if let Ok(Some(array)) = reader.read_array_full(field.name()).await { + statistics = statistics.add_column_statistics(array_stats); + } else { + statistics = statistics.add_column_statistics(ColumnStatistics::new_unknown()); + } + } else { + statistics = statistics.add_column_statistics(ColumnStatistics::new_unknown()); + } + } + + todo!() + } + + async fn find_partitioned_files< + S: AsyncReadableListableStorageTraits + Sized + Clone + 'static, + >( + group: &Group, + ) -> Option>> { + match group.async_child_groups().await { + Ok(children) => { + // Find recursively all child groups that contain groups aswell + let mut result = Vec::new(); + for child in children { + if let Some(mut sub_children) = + Box::pin(Self::find_partitioned_files(&child)).await + { + result.append(&mut sub_children); + } else { + result.push(child); + } + } + Some(result) + } + Err(_) => None, + } + } +} + /// Get parent directory of a Path (S3-style). /// Example: "a/b/c" -> Some("a/b") /// "a" -> None diff --git a/beacon-formats/src/zarr/pushdown_statistics.rs b/beacon-formats/src/zarr/pushdown_statistics.rs new file mode 100644 index 00000000..7bee6068 --- /dev/null +++ b/beacon-formats/src/zarr/pushdown_statistics.rs @@ -0,0 +1,10 @@ +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct PushDownZarrStatistics { + pub arrays: Vec, +} + +impl PushDownZarrStatistics { + pub fn has_array(&self, array_name: &str) -> bool { + self.arrays.contains(&array_name.to_string()) + } +} diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs index fb28e95f..1879a9aa 100644 --- a/beacon-formats/src/zarr/source.rs +++ b/beacon-formats/src/zarr/source.rs @@ -19,6 +19,7 @@ use datafusion::{ schema_adapter::{DefaultSchemaAdapterFactory, SchemaAdapter, SchemaAdapterFactory}, }, physical_expr::conjunction, + physical_optimizer::pruning::PruningPredicate, physical_plan::{ PhysicalExpr, filter_pushdown::{FilterPushdownPropagation, PushedDown}, @@ -34,7 +35,7 @@ use zarrs_storage::AsyncReadableListableStorageTraits; use crate::zarr::{ array_step_span::NumericArrayStepSpan, expr_util::extract_range_from_physical_filters, - path_parent, stream_share::ZarrStreamShare, + path_parent, pushdown_statistics::PushDownZarrStatistics, stream_share::ZarrStreamShare, }; pub async fn fetch_schema( @@ -77,10 +78,13 @@ pub struct ZarrSource { execution_plan_metrics: ExecutionPlanMetricsSet, /// Projected statistics. projected_statistics: Option, + /// Stream Partition Share stream_partition_shares: Arc>>>, /// Array Steps for slicing arrays based on step spans. This is utilized by the pruning predicate pushdown. array_steps: Arc>, + /// Zarr Statistics + pushdown_zarr_statistics: PushDownZarrStatistics, /// Pruning Predicate predicate: Option>, } @@ -90,6 +94,14 @@ impl ZarrSource { self.array_steps = Arc::new(array_steps); self } + + pub fn with_pushdown_zarr_statistics( + mut self, + pushdown_zarr_statistics: PushDownZarrStatistics, + ) -> Self { + self.pushdown_zarr_statistics = pushdown_zarr_statistics; + self + } } impl FileSource for ZarrSource { @@ -118,6 +130,7 @@ impl FileSource for ZarrSource { stream_partition_shares: self.stream_partition_shares.clone(), array_steps: self.array_steps.clone(), predicate: self.predicate.clone(), + pushdown_zarr_statistics: self.pushdown_zarr_statistics.clone(), }) } @@ -139,6 +152,7 @@ impl FileSource for ZarrSource { stream_partition_shares: self.stream_partition_shares.clone(), array_steps: self.array_steps.clone(), predicate: self.predicate.clone(), + pushdown_zarr_statistics: self.pushdown_zarr_statistics.clone(), }) } @@ -152,6 +166,7 @@ impl FileSource for ZarrSource { stream_partition_shares: self.stream_partition_shares.clone(), array_steps: self.array_steps.clone(), predicate: self.predicate.clone(), + pushdown_zarr_statistics: self.pushdown_zarr_statistics.clone(), }) } @@ -165,6 +180,7 @@ impl FileSource for ZarrSource { stream_partition_shares: self.stream_partition_shares.clone(), array_steps: self.array_steps.clone(), predicate: self.predicate.clone(), + pushdown_zarr_statistics: self.pushdown_zarr_statistics.clone(), }) } @@ -201,6 +217,7 @@ impl FileSource for ZarrSource { stream_partition_shares: self.stream_partition_shares.clone(), array_steps: self.array_steps.clone(), predicate: self.predicate.clone(), + pushdown_zarr_statistics: self.pushdown_zarr_statistics.clone(), })) } @@ -218,11 +235,6 @@ impl FileSource for ZarrSource { // Repartion by duplicating the file groups to reach the target number of partitions. let file_groups = config.file_groups.clone(); - if file_groups.len() >= target_partitions { - // No need to repartition - return Ok(None); - } - let repartitioned: Vec = file_groups .iter() .cycle() @@ -270,6 +282,8 @@ pub struct ZarrFileOpener { stream_partition_shares: Arc>>>, /// Array Steps for slicing arrays based on step spans. This is utilized by the pruning predicate pushdown. array_steps: Arc>, + /// Pushdown Zarr Statistics + pushdown_zarr_statistics: PushDownZarrStatistics, /// Pruning Predicate predicate: Option>, } @@ -297,6 +311,9 @@ impl FileOpener for ZarrFileOpener { }; let pruning_predicate = self.predicate.clone(); let array_steps = self.array_steps.clone(); + let pushdown_zarr_statistics = self.pushdown_zarr_statistics.clone(); + + // Check if the pruning predicate references any arrays in the pushdown statistics let fut = async move { let (stream, schema_mapper, file_schema) = stream_partition_share From 40f88f187af17f63ccce7e2dbcba307165c01b63 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Thu, 16 Oct 2025 01:52:20 +0200 Subject: [PATCH 11/19] wip --- beacon-data-lake/src/table/table_formats.rs | 10 +- beacon-formats/src/zarr/mod.rs | 140 +++++++----- .../src/zarr/pushdown_statistics.rs | 209 ++++++++++++++++++ beacon-formats/src/zarr/source.rs | 13 +- 4 files changed, 309 insertions(+), 63 deletions(-) diff --git a/beacon-data-lake/src/table/table_formats.rs b/beacon-data-lake/src/table/table_formats.rs index 527ac969..ab31c98d 100644 --- a/beacon-data-lake/src/table/table_formats.rs +++ b/beacon-data-lake/src/table/table_formats.rs @@ -1,6 +1,8 @@ use std::{collections::HashMap, sync::Arc}; -use beacon_formats::zarr::{ZarrFormat, array_step_span::NumericArrayStepSpan}; +use beacon_formats::zarr::{ + ZarrFormat, array_step_span::NumericArrayStepSpan, pushdown_statistics::PushDownZarrStatistics, +}; use datafusion::datasource::file_format::FileFormat; #[typetag::serde(tag = "file_format")] @@ -57,6 +59,8 @@ impl TableFileFormat for NetCDFFileFormat { pub struct ZarrFileFormat { #[serde(default)] pub global_array_steps: HashMap, + #[serde(default)] + pub pushdown_statistics: PushDownZarrStatistics, } #[typetag::serde(name = "zarr")] @@ -67,7 +71,9 @@ impl TableFileFormat for ZarrFileFormat { fn file_format(&self) -> Option> { Some(Arc::new( - ZarrFormat::default().with_array_steps(self.global_array_steps.clone()), + ZarrFormat::default() + .with_array_steps(self.global_array_steps.clone()) + .with_pushdown_statistics(self.pushdown_statistics.clone()), )) } } diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs index d9280ec2..5a9b29c9 100644 --- a/beacon-formats/src/zarr/mod.rs +++ b/beacon-formats/src/zarr/mod.rs @@ -4,7 +4,7 @@ use std::{ sync::Arc, }; -use arrow::datatypes::SchemaRef; +use arrow::{compute::kernels::partition, datatypes::SchemaRef}; use beacon_arrow_zarr::reader::AsyncArrowZarrGroupReader; use datafusion::{ catalog::{Session, memory::DataSourceExec}, @@ -91,6 +91,14 @@ impl ZarrFormat { self.array_steps = array_steps; self } + + pub fn with_pushdown_statistics( + mut self, + zarr_pushdown_statistics: PushDownZarrStatistics, + ) -> Self { + self.zarr_pushdown_statistics = zarr_pushdown_statistics; + self + } } #[async_trait::async_trait] @@ -142,7 +150,7 @@ impl FileFormat for ZarrFormat { async fn create_physical_plan( &self, - _state: &dyn Session, + state: &dyn Session, conf: FileScanConfig, ) -> datafusion::error::Result> { // Recreate file groups here. Only maintain top-level groups. @@ -152,19 +160,23 @@ impl FileFormat for ZarrFormat { object_metas.push(file.object_meta.clone()); } } + let object_store = state + .runtime_env() + .object_store(conf.object_store_url.clone())?; // Recreate file groups with only top-level Zarr groups. let top_level_metas = top_level_zarr_meta_v3(&object_metas); - let file_groups: Vec = top_level_metas - .into_iter() - .map(|meta| { - FileGroup::new(vec![ - PartitionedFile::from(meta.clone()).with_range(0, meta.size as i64), - ]) - }) - .collect(); - - let source = ZarrSource::default().with_array_steps(self.array_steps.clone()); + let mut file_groups: Vec = vec![]; + for meta in top_level_metas { + let file = self + .partition_zarr_group(&conf.file_schema, &meta, object_store.clone()) + .await?; + file_groups.push(file); + } + + let source = ZarrSource::default() + .with_array_steps(self.array_steps.clone()) + .with_pushdown_zarr_statistics(self.zarr_pushdown_statistics.clone()); let conf = FileScanConfigBuilder::from(conf) .with_file_groups(file_groups) .with_source(Arc::new(source)) @@ -173,19 +185,31 @@ impl FileFormat for ZarrFormat { } fn file_source(&self) -> Arc { - Arc::new(ZarrSource::default().with_array_steps(self.array_steps.clone())) + Arc::new( + ZarrSource::default() + .with_array_steps(self.array_steps.clone()) + .with_pushdown_zarr_statistics(self.zarr_pushdown_statistics.clone()), + ) } } impl ZarrFormat { async fn partition_zarr_group( &self, + table_schema: &SchemaRef, object: &ObjectMeta, object_store: Arc, ) -> datafusion::error::Result { // The object can be assumed to be a top-level zarr.json file. - let zarr_store = Arc::new(AsyncObjectStore::new(object_store)); - let group = Group::async_open(Arc::new(zarr_store), object.location.to_string().as_str()) + let zarr_store = Arc::new(AsyncObjectStore::new(object_store)) + as Arc; + let group_path = path_parent(&object.location).ok_or_else(|| { + datafusion::error::DataFusionError::Execution(format!( + "Could not determine parent path of object: {}", + object.location + )) + })?; + let group = Group::async_open(zarr_store, &format!("/{}", group_path)) .await .map_err(|e| { datafusion::error::DataFusionError::Execution(format!( @@ -195,55 +219,59 @@ impl ZarrFormat { })?; match Self::find_partitioned_files(&group).await { - Some(groups) => { - // Means it is a partitioned zarr group with sub-groups - // Each sub-group is a partition - // Create a FileGroup for each sub-group - // Each FileGroup contains a single PartitionedFile pointing to the zarr.json of the sub-group - - for group in groups { - let path = group.path(); - - // let partioned_file = PartitionedFile::from() + Some(partition_groups) => { + if partition_groups.is_empty() { + // No sub-groups found, treat as single group + let statistics = pushdown_statistics::generate_statistics_from_zarr_group( + table_schema, + &self.zarr_pushdown_statistics, + Arc::new(group), + ) + .await + .unwrap_or(Statistics::new_unknown(table_schema)); + + Ok(FileGroup::new(vec![ + PartitionedFile::new(format!("{}/zarr.json", group_path), 0) + .with_statistics(Arc::new(statistics)), + ])) + } else { + // Multiple sub-groups found, treat each as a partition + let mut files = Vec::new(); + for group in partition_groups { + let group = Arc::new(group); + let partition_path = group.path().to_string(); + let statistics = pushdown_statistics::generate_statistics_from_zarr_group( + table_schema, + &self.zarr_pushdown_statistics, + group.clone(), + ) + .await + .unwrap_or(Statistics::new_unknown(table_schema)); + println!("SUB Group Path: {:?}", group.path()); + + // Create a PartitionedFile for the sub-group + let partitioned_file = + PartitionedFile::new(format!("{}/zarr.json", partition_path), 0) + .with_statistics(Arc::new(statistics)); + files.push(partitioned_file); + } + Ok(FileGroup::new(files)) } } None => { // Means it is a single zarr group with no sub-groups + println!("Group Path: {:?}", group.path()); + panic!( + "Failed to list child groups of Zarr group at {}", + group_path + ); } } - - todo!() - } - - async fn generate_statistics_for_partitioned_file( - &self, - table_schema: &SchemaRef, - group: Arc>, - ) -> Option { - let reader = AsyncArrowZarrGroupReader::new(group.clone()).await.ok()?; - - let mut statistics = Statistics::default(); - for field in table_schema.fields() { - if self.zarr_pushdown_statistics.has_array(field.name()) { - // Read the full array - if let Ok(Some(array)) = reader.read_array_full(field.name()).await { - statistics = statistics.add_column_statistics(array_stats); - } else { - statistics = statistics.add_column_statistics(ColumnStatistics::new_unknown()); - } - } else { - statistics = statistics.add_column_statistics(ColumnStatistics::new_unknown()); - } - } - - todo!() } - async fn find_partitioned_files< - S: AsyncReadableListableStorageTraits + Sized + Clone + 'static, - >( - group: &Group, - ) -> Option>> { + async fn find_partitioned_files( + group: &Group, + ) -> Option>> { match group.async_child_groups().await { Ok(children) => { // Find recursively all child groups that contain groups aswell diff --git a/beacon-formats/src/zarr/pushdown_statistics.rs b/beacon-formats/src/zarr/pushdown_statistics.rs index 7bee6068..41d7f54e 100644 --- a/beacon-formats/src/zarr/pushdown_statistics.rs +++ b/beacon-formats/src/zarr/pushdown_statistics.rs @@ -1,3 +1,17 @@ +use std::sync::Arc; + +use arrow::{ + array::AsArray, + datatypes::{Int8Type, SchemaRef}, +}; +use beacon_arrow_zarr::reader::AsyncArrowZarrGroupReader; +use datafusion::{ + common::{ColumnStatistics, Statistics, stats::Precision}, + scalar::ScalarValue, +}; +use zarrs::group::Group; +use zarrs_storage::AsyncReadableListableStorageTraits; + #[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct PushDownZarrStatistics { pub arrays: Vec, @@ -8,3 +22,198 @@ impl PushDownZarrStatistics { self.arrays.contains(&array_name.to_string()) } } + +pub async fn generate_statistics_from_zarr_group( + schema: &SchemaRef, + pushdown: &PushDownZarrStatistics, + zarr_group: Arc>, +) -> Option { + let zarr_reader = AsyncArrowZarrGroupReader::new(zarr_group.clone()) + .await + .ok()?; + let mut statistics = Statistics::default(); + for field in schema.fields() { + if pushdown.has_array(field.name()) { + // Read the full array + if let Ok(Some(array)) = zarr_reader.read_array_full(field.name()).await { + // Compute statistics for the array + if let Some(array_stats) = compute_statistics_for_array(&array) { + statistics = statistics.add_column_statistics(array_stats); + } else { + statistics = statistics.add_column_statistics(ColumnStatistics::new_unknown()); + } + } else { + statistics = statistics.add_column_statistics(ColumnStatistics::new_unknown()); + } + } else { + statistics = statistics.add_column_statistics(ColumnStatistics::new_unknown()); + } + } + + Some(statistics) +} + +fn compute_statistics_for_array(array: &arrow::array::ArrayRef) -> Option { + let data_type = array.data_type().clone(); + let null_count = array.null_count(); + let mut statistics = ColumnStatistics { + null_count: Precision::Exact(null_count), + distinct_count: Precision::Absent, + max_value: Precision::Absent, + min_value: Precision::Absent, + sum_value: Precision::Absent, + }; + + match data_type { + arrow::datatypes::DataType::Boolean => { + let boolean_array = array.as_boolean(); + arrow::compute::min_boolean(boolean_array).map(|min| { + statistics.min_value = Precision::Exact(ScalarValue::Boolean(Some(min))) + }); + arrow::compute::max_boolean(boolean_array).map(|max| { + statistics.max_value = Precision::Exact(ScalarValue::Boolean(Some(max))) + }); + } + arrow::datatypes::DataType::Int8 => { + let int_array = array.as_primitive::(); + arrow::compute::min(int_array) + .map(|min| statistics.min_value = Precision::Exact(ScalarValue::Int8(Some(min)))); + arrow::compute::max(int_array) + .map(|max| statistics.max_value = Precision::Exact(ScalarValue::Int8(Some(max)))); + } + arrow::datatypes::DataType::Int16 => { + let int_array = array.as_primitive::(); + arrow::compute::min(int_array) + .map(|min| statistics.min_value = Precision::Exact(ScalarValue::Int16(Some(min)))); + arrow::compute::max(int_array) + .map(|max| statistics.max_value = Precision::Exact(ScalarValue::Int16(Some(max)))); + } + arrow::datatypes::DataType::Int32 => { + let int_array = array.as_primitive::(); + arrow::compute::min(int_array) + .map(|min| statistics.min_value = Precision::Exact(ScalarValue::Int32(Some(min)))); + arrow::compute::max(int_array) + .map(|max| statistics.max_value = Precision::Exact(ScalarValue::Int32(Some(max)))); + } + arrow::datatypes::DataType::Int64 => { + let int_array = array.as_primitive::(); + arrow::compute::min(int_array) + .map(|min| statistics.min_value = Precision::Exact(ScalarValue::Int64(Some(min)))); + arrow::compute::max(int_array) + .map(|max| statistics.max_value = Precision::Exact(ScalarValue::Int64(Some(max)))); + } + arrow::datatypes::DataType::UInt8 => { + let int_array = array.as_primitive::(); + arrow::compute::min(int_array) + .map(|min| statistics.min_value = Precision::Exact(ScalarValue::UInt8(Some(min)))); + arrow::compute::max(int_array) + .map(|max| statistics.max_value = Precision::Exact(ScalarValue::UInt8(Some(max)))); + } + arrow::datatypes::DataType::UInt16 => { + let int_array = array.as_primitive::(); + arrow::compute::min(int_array) + .map(|min| statistics.min_value = Precision::Exact(ScalarValue::UInt16(Some(min)))); + arrow::compute::max(int_array) + .map(|max| statistics.max_value = Precision::Exact(ScalarValue::UInt16(Some(max)))); + } + arrow::datatypes::DataType::UInt32 => { + let int_array = array.as_primitive::(); + arrow::compute::min(int_array) + .map(|min| statistics.min_value = Precision::Exact(ScalarValue::UInt32(Some(min)))); + arrow::compute::max(int_array) + .map(|max| statistics.max_value = Precision::Exact(ScalarValue::UInt32(Some(max)))); + } + arrow::datatypes::DataType::UInt64 => { + let int_array = array.as_primitive::(); + arrow::compute::min(int_array) + .map(|min| statistics.min_value = Precision::Exact(ScalarValue::UInt64(Some(min)))); + arrow::compute::max(int_array) + .map(|max| statistics.max_value = Precision::Exact(ScalarValue::UInt64(Some(max)))); + } + arrow::datatypes::DataType::Float32 => { + let float_array = array.as_primitive::(); + arrow::compute::min(float_array).map(|min| { + statistics.min_value = Precision::Exact(ScalarValue::Float32(Some(min))) + }); + arrow::compute::max(float_array).map(|max| { + statistics.max_value = Precision::Exact(ScalarValue::Float32(Some(max))) + }); + } + arrow::datatypes::DataType::Float64 => { + let float_array = array.as_primitive::(); + arrow::compute::min(float_array).map(|min| { + statistics.min_value = Precision::Exact(ScalarValue::Float64(Some(min))) + }); + arrow::compute::max(float_array).map(|max| { + statistics.max_value = Precision::Exact(ScalarValue::Float64(Some(max))) + }); + } + arrow::datatypes::DataType::Timestamp(time_unit, _) => match time_unit { + arrow::datatypes::TimeUnit::Second => { + let ts_array = array.as_primitive::(); + arrow::compute::min(ts_array).map(|min| { + statistics.min_value = + Precision::Exact(ScalarValue::TimestampSecond(Some(min), None)) + }); + arrow::compute::max(ts_array).map(|max| { + statistics.max_value = + Precision::Exact(ScalarValue::TimestampSecond(Some(max), None)) + }); + } + arrow::datatypes::TimeUnit::Millisecond => { + let ts_array = array.as_primitive::(); + arrow::compute::min(ts_array).map(|min| { + statistics.min_value = + Precision::Exact(ScalarValue::TimestampMillisecond(Some(min), None)) + }); + arrow::compute::max(ts_array).map(|max| { + statistics.max_value = + Precision::Exact(ScalarValue::TimestampMillisecond(Some(max), None)) + }); + } + arrow::datatypes::TimeUnit::Microsecond => { + let ts_array = array.as_primitive::(); + arrow::compute::min(ts_array).map(|min| { + statistics.min_value = + Precision::Exact(ScalarValue::TimestampMicrosecond(Some(min), None)) + }); + arrow::compute::max(ts_array).map(|max| { + statistics.max_value = + Precision::Exact(ScalarValue::TimestampMicrosecond(Some(max), None)) + }); + } + arrow::datatypes::TimeUnit::Nanosecond => { + let ts_array = array.as_primitive::(); + arrow::compute::min(ts_array).map(|min| { + statistics.min_value = + Precision::Exact(ScalarValue::TimestampNanosecond(Some(min), None)) + }); + arrow::compute::max(ts_array).map(|max| { + statistics.max_value = + Precision::Exact(ScalarValue::TimestampNanosecond(Some(max), None)) + }); + } + }, + arrow::datatypes::DataType::Binary => { + let binary_array = array.as_binary::(); + arrow::compute::min_binary(binary_array).map(|min| { + statistics.min_value = Precision::Exact(ScalarValue::Binary(Some(min.to_vec()))) + }); + arrow::compute::max_binary(binary_array).map(|max| { + statistics.max_value = Precision::Exact(ScalarValue::Binary(Some(max.to_vec()))) + }); + } + arrow::datatypes::DataType::Utf8 => { + let string_array = array.as_string::(); + arrow::compute::min_string(string_array).map(|min| { + statistics.min_value = Precision::Exact(ScalarValue::Utf8(Some(min.to_string()))) + }); + arrow::compute::max_string(string_array).map(|max| { + statistics.max_value = Precision::Exact(ScalarValue::Utf8(Some(max.to_string()))) + }); + } + _ => {} // Unsupported data type for statistics + } + + Some(statistics) +} diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs index 1879a9aa..f623d835 100644 --- a/beacon-formats/src/zarr/source.rs +++ b/beacon-formats/src/zarr/source.rs @@ -57,7 +57,7 @@ pub async fn fetch_schema( .await .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; - let reader = AsyncArrowZarrGroupReader::new(group) + let reader = AsyncArrowZarrGroupReader::new(Arc::new(group)) .await .map_err(datafusion::error::DataFusionError::Execution)?; @@ -112,6 +112,8 @@ impl FileSource for ZarrSource { base_config: &FileScanConfig, _partition: usize, ) -> Arc { + println!("Partition: {}", _partition); + let table_schema = self .override_schema .clone() @@ -314,7 +316,6 @@ impl FileOpener for ZarrFileOpener { let pushdown_zarr_statistics = self.pushdown_zarr_statistics.clone(); // Check if the pruning predicate references any arrays in the pushdown statistics - let fut = async move { let (stream, schema_mapper, file_schema) = stream_partition_share .get_or_try_init(|| async move { @@ -336,9 +337,11 @@ impl FileOpener for ZarrFileOpener { .await .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; - let reader = AsyncArrowZarrGroupReader::new(group).await.map_err(|e| { - datafusion::error::DataFusionError::Execution(e.to_string()) - })?; + let reader = AsyncArrowZarrGroupReader::new(Arc::new(group)) + .await + .map_err(|e| { + datafusion::error::DataFusionError::Execution(e.to_string()) + })?; let file_schema = reader.arrow_schema(); From 27d656fd7166f6415fa6749152dfc62799f54d4b Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Tue, 21 Oct 2025 17:20:13 +0200 Subject: [PATCH 12/19] wip --- beacon-formats/src/zarr/mod.rs | 180 ++---------------------- beacon-formats/src/zarr/source.rs | 3 +- beacon-formats/src/zarr/util.rs | 222 ++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 174 deletions(-) create mode 100644 beacon-formats/src/zarr/util.rs diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs index 5a9b29c9..f04def70 100644 --- a/beacon-formats/src/zarr/mod.rs +++ b/beacon-formats/src/zarr/mod.rs @@ -27,6 +27,7 @@ use crate::zarr::{ array_step_span::NumericArrayStepSpan, pushdown_statistics::PushDownZarrStatistics, source::{ZarrSource, fetch_schema}, + util::{ZarrPath, path_parent, top_level_zarr_meta_v3}, }; pub mod array_step_span; @@ -34,6 +35,7 @@ pub mod expr_util; pub mod pushdown_statistics; mod source; mod stream_share; +pub mod util; pub struct ZarrFormatFactory; @@ -203,13 +205,13 @@ impl ZarrFormat { // The object can be assumed to be a top-level zarr.json file. let zarr_store = Arc::new(AsyncObjectStore::new(object_store)) as Arc; - let group_path = path_parent(&object.location).ok_or_else(|| { + let group_path = ZarrPath::new_from_object_meta(object.clone()).map_err(|e| { datafusion::error::DataFusionError::Execution(format!( - "Could not determine parent path of object: {}", - object.location + "Failed to create ZarrPath from ObjectMeta at {}: {}", + object.location, e )) })?; - let group = Group::async_open(zarr_store, &format!("/{}", group_path)) + let group = Group::async_open(zarr_store, &group_path.as_zarr_path()) .await .map_err(|e| { datafusion::error::DataFusionError::Execution(format!( @@ -231,7 +233,7 @@ impl ZarrFormat { .unwrap_or(Statistics::new_unknown(table_schema)); Ok(FileGroup::new(vec![ - PartitionedFile::new(format!("{}/zarr.json", group_path), 0) + PartitionedFile::new(group_path.as_zarr_path(), 0) .with_statistics(Arc::new(statistics)), ])) } else { @@ -262,7 +264,7 @@ impl ZarrFormat { // Means it is a single zarr group with no sub-groups println!("Group Path: {:?}", group.path()); panic!( - "Failed to list child groups of Zarr group at {}", + "Failed to list child groups of Zarr group at {:?}", group_path ); } @@ -292,82 +294,11 @@ impl ZarrFormat { } } -/// Get parent directory of a Path (S3-style). -/// Example: "a/b/c" -> Some("a/b") -/// "a" -> None -fn path_parent(p: &object_store::path::Path) -> Option { - let s = p.to_string(); - if let Some(pos) = s.rfind('/') { - // Parent is everything before the last '/' - let parent_str = &s[..pos]; - Some(object_store::path::Path::from(parent_str)) - } else { - // No '/' in path => parent is root (optional: return None instead) - None - } -} - -/// Check if this ObjectMeta represents a Zarr v3 metadata file ("zarr.json") -fn is_zarr_v3_metadata(meta: &object_store::ObjectMeta) -> bool { - // Normalize for safety, S3 paths are UTF-8 so this is fine - let loc = meta.location.to_string().to_lowercase(); - loc.ends_with("/zarr.json") -} - -/// Return only the ObjectMeta entries corresponding to **top-level Zarr groups**. -pub fn top_level_zarr_meta_v3(metas: &[object_store::ObjectMeta]) -> Vec { - // 1. Collect all metas that are zarr.json + record their directories. - let mut dir_to_meta: HashMap = - HashMap::new(); - - for meta in metas { - if is_zarr_v3_metadata(meta) - && let Some(parent) = path_parent(&meta.location) - { - dir_to_meta.insert(parent, meta); - } - } - - // 2. Extract all candidate directories and sort. - let mut candidates: Vec = dir_to_meta.keys().cloned().collect(); - candidates.sort(); - candidates.dedup(); - - // 3. Build a set for quick ancestor checks. - let all_dirs: HashSet = candidates.iter().cloned().collect(); - - // 4. Keep only directories that have no ancestor also in all_dirs. - let mut top_level_dirs: Vec = Vec::new(); - - 'outer: for dir in &candidates { - let mut current = path_parent(dir); - while let Some(ancestor) = current { - if ancestor == *dir { - break; - } - if all_dirs.contains(&ancestor) { - // dir is nested under another zarr.json directory - continue 'outer; - } - current = path_parent(&ancestor); - } - top_level_dirs.push(dir.clone()); - } - - // 5. Return the corresponding ObjectMetas (cloned) - top_level_dirs - .into_iter() - .filter_map(|d| dir_to_meta.get(&d).cloned().cloned()) - .collect() -} - #[cfg(test)] mod tests { use datafusion::datasource::listing::ListingTableUrl; use futures::StreamExt; - use super::*; - #[test] fn test_name() { let test_path = object_store::path::Path::parse("bucket/key.zarr").unwrap(); @@ -414,99 +345,4 @@ mod tests { println!("File: {:?}", file); } } - - use object_store::{ObjectMeta, path::Path}; - - fn meta(path: &str) -> ObjectMeta { - ObjectMeta { - location: Path::from(path), - last_modified: Default::default(), - size: 0, - e_tag: None, - version: None, - } - } - - fn extract_paths(metas: &[ObjectMeta]) -> Vec { - metas.iter().map(|m| m.location.to_string()).collect() - } - - #[test] - fn test_single_top_level() { - let metas = vec![meta("a/zarr.json")]; - let result = top_level_zarr_meta_v3(&metas); - assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); - } - - #[test] - fn test_nested_group_dropped() { - let metas = vec![ - meta("a/zarr.json"), // top-level - meta("a/b/zarr.json"), // nested -> drop - ]; - let result = top_level_zarr_meta_v3(&metas); - assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); - } - - #[test] - fn test_multiple_top_level() { - let metas = vec![meta("a/zarr.json"), meta("b/zarr.json")]; - let result = top_level_zarr_meta_v3(&metas); - let mut paths = extract_paths(&result); - paths.sort(); - assert_eq!(paths, vec!["a/zarr.json", "b/zarr.json"]); - } - - #[test] - fn test_deep_nesting_dropped() { - let metas = vec![ - meta("a/zarr.json"), - meta("a/b/zarr.json"), - meta("a/b/c/zarr.json"), - ]; - let result = top_level_zarr_meta_v3(&metas); - assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); - } - - #[test] - fn test_sibling_groups() { - let metas = vec![ - meta("root1/zarr.json"), - meta("root2/zarr.json"), - meta("root2/child/zarr.json"), // nested -> drop - ]; - let result = top_level_zarr_meta_v3(&metas); - let mut paths = extract_paths(&result); - paths.sort(); - assert_eq!(paths, vec!["root1/zarr.json", "root2/zarr.json"]); - } - - #[test] - fn test_root_level_key() { - let metas = vec![ - meta("zarr.json"), // top-level at root - meta("a/zarr.json"), // nested under root -> drop - ]; - let result = top_level_zarr_meta_v3(&metas); - assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); - } - - #[test] - fn test_ignore_non_zarr_json() { - let metas = vec![ - meta("a/zarr.json"), // valid - meta("b/not_zarr.json"), - meta("c/zarr.txt"), - meta("d/data.bin"), - ]; - let result = top_level_zarr_meta_v3(&metas); - assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); - } - - #[test] - fn test_empty_input() { - let metas: Vec = vec![]; - let result = top_level_zarr_meta_v3(&metas); - assert!(result.is_empty()); - } } diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs index f623d835..cae0e712 100644 --- a/beacon-formats/src/zarr/source.rs +++ b/beacon-formats/src/zarr/source.rs @@ -19,7 +19,6 @@ use datafusion::{ schema_adapter::{DefaultSchemaAdapterFactory, SchemaAdapter, SchemaAdapterFactory}, }, physical_expr::conjunction, - physical_optimizer::pruning::PruningPredicate, physical_plan::{ PhysicalExpr, filter_pushdown::{FilterPushdownPropagation, PushedDown}, @@ -35,7 +34,7 @@ use zarrs_storage::AsyncReadableListableStorageTraits; use crate::zarr::{ array_step_span::NumericArrayStepSpan, expr_util::extract_range_from_physical_filters, - path_parent, pushdown_statistics::PushDownZarrStatistics, stream_share::ZarrStreamShare, + pushdown_statistics::PushDownZarrStatistics, stream_share::ZarrStreamShare, util::path_parent, }; pub async fn fetch_schema( diff --git a/beacon-formats/src/zarr/util.rs b/beacon-formats/src/zarr/util.rs new file mode 100644 index 00000000..2ba98369 --- /dev/null +++ b/beacon-formats/src/zarr/util.rs @@ -0,0 +1,222 @@ +use std::collections::{HashMap, HashSet}; + +use object_store::ObjectStore; + +#[derive(Clone, Debug)] +pub enum ZarrPath { + ObjectMeta(object_store::ObjectMeta), + // Directory path representing a Zarr group. Inside there should always be a "zarr.json" file. + DirPath(object_store::path::Path), +} + +impl ZarrPath { + pub fn as_zarr_path(&self) -> String { + match self { + ZarrPath::ObjectMeta(meta) => meta.location.as_ref().to_string(), + ZarrPath::DirPath(path) => format!("{}/zarr.json", path.as_ref()), + } + } + + pub fn new_from_object_meta(meta: object_store::ObjectMeta) -> Result { + // Ensure this is a zarr.json file? + if !is_zarr_v3_metadata(&meta) { + return Err(format!( + "ObjectMeta at location '{}' is not a Zarr v3 metadata file (zarr.json)", + meta.location.as_ref() + )); + } + Ok(ZarrPath::ObjectMeta(meta)) + } + + pub async fn new_from_dir_path( + object_store: &dyn ObjectStore, + path: object_store::path::Path, + ) -> Result { + // Ensure that path does not end with "zarr.json" and that in the folder underneath there is a zarr.json file. + if path.as_ref().ends_with("zarr.json") { + return Err(format!( + "Provided path '{}' should be a directory path, not a zarr.json file", + path.as_ref() + )); + } + + let zarr_json_path = path.child("zarr.json"); + let exists = object_store.head(&zarr_json_path).await.is_ok(); + if !exists { + return Err(format!( + "No zarr.json file found under directory path '{}'", + path.as_ref() + )); + } + Ok(ZarrPath::DirPath(path)) + } +} + +/// Get parent directory of a Path (S3-style). +/// Example: "a/b/c" -> Some("a/b") +/// "a" -> None +pub fn path_parent(p: &object_store::path::Path) -> Option { + let s = p.to_string(); + if let Some(pos) = s.rfind('/') { + // Parent is everything before the last '/' + let parent_str = &s[..pos]; + Some(object_store::path::Path::from(parent_str)) + } else { + // No '/' in path => parent is root (optional: return None instead) + None + } +} + +/// Check if this ObjectMeta represents a Zarr v3 metadata file ("zarr.json") +pub fn is_zarr_v3_metadata(meta: &object_store::ObjectMeta) -> bool { + // Normalize for safety, S3 paths are UTF-8 so this is fine + let loc = meta.location.to_string().to_lowercase(); + loc.ends_with("/zarr.json") +} + +/// Return only the ObjectMeta entries corresponding to **top-level Zarr groups**. +pub fn top_level_zarr_meta_v3(metas: &[object_store::ObjectMeta]) -> Vec { + // 1. Collect all metas that are zarr.json + record their directories. + let mut dir_to_meta: HashMap = + HashMap::new(); + + for meta in metas { + if is_zarr_v3_metadata(meta) + && let Some(parent) = path_parent(&meta.location) + { + dir_to_meta.insert(parent, meta); + } + } + + // 2. Extract all candidate directories and sort. + let mut candidates: Vec = dir_to_meta.keys().cloned().collect(); + candidates.sort(); + candidates.dedup(); + + // 3. Build a set for quick ancestor checks. + let all_dirs: HashSet = candidates.iter().cloned().collect(); + + // 4. Keep only directories that have no ancestor also in all_dirs. + let mut top_level_dirs: Vec = Vec::new(); + + 'outer: for dir in &candidates { + let mut current = path_parent(dir); + while let Some(ancestor) = current { + if ancestor == *dir { + break; + } + if all_dirs.contains(&ancestor) { + // dir is nested under another zarr.json directory + continue 'outer; + } + current = path_parent(&ancestor); + } + top_level_dirs.push(dir.clone()); + } + + // 5. Return the corresponding ObjectMetas (cloned) + top_level_dirs + .into_iter() + .filter_map(|d| dir_to_meta.get(&d).cloned().cloned()) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + use object_store::{ObjectMeta, path::Path}; + + fn meta(path: &str) -> ObjectMeta { + ObjectMeta { + location: Path::from(path), + last_modified: Default::default(), + size: 0, + e_tag: None, + version: None, + } + } + + fn extract_paths(metas: &[ObjectMeta]) -> Vec { + metas.iter().map(|m| m.location.to_string()).collect() + } + + #[test] + fn test_single_top_level() { + let metas = vec![meta("a/zarr.json")]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_nested_group_dropped() { + let metas = vec![ + meta("a/zarr.json"), // top-level + meta("a/b/zarr.json"), // nested -> drop + ]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_multiple_top_level() { + let metas = vec![meta("a/zarr.json"), meta("b/zarr.json")]; + let result = top_level_zarr_meta_v3(&metas); + let mut paths = extract_paths(&result); + paths.sort(); + assert_eq!(paths, vec!["a/zarr.json", "b/zarr.json"]); + } + + #[test] + fn test_deep_nesting_dropped() { + let metas = vec![ + meta("a/zarr.json"), + meta("a/b/zarr.json"), + meta("a/b/c/zarr.json"), + ]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_sibling_groups() { + let metas = vec![ + meta("root1/zarr.json"), + meta("root2/zarr.json"), + meta("root2/child/zarr.json"), // nested -> drop + ]; + let result = top_level_zarr_meta_v3(&metas); + let mut paths = extract_paths(&result); + paths.sort(); + assert_eq!(paths, vec!["root1/zarr.json", "root2/zarr.json"]); + } + + #[test] + fn test_root_level_key() { + let metas = vec![ + meta("zarr.json"), // top-level at root + meta("a/zarr.json"), // nested under root -> drop + ]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_ignore_non_zarr_json() { + let metas = vec![ + meta("a/zarr.json"), // valid + meta("b/not_zarr.json"), + meta("c/zarr.txt"), + meta("d/data.bin"), + ]; + let result = top_level_zarr_meta_v3(&metas); + assert_eq!(extract_paths(&result), vec!["a/zarr.json"]); + } + + #[test] + fn test_empty_input() { + let metas: Vec = vec![]; + let result = top_level_zarr_meta_v3(&metas); + assert!(result.is_empty()); + } +} From adaf2f813f4271616431da4536db9afbfcbe9bbb Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Tue, 28 Oct 2025 11:53:01 +0100 Subject: [PATCH 13/19] wip --- beacon-data-lake/src/table/table_formats.rs | 4 +- beacon-formats/src/zarr/mod.rs | 9 +-- beacon-formats/src/zarr/partition.rs | 61 +++++++++++++++++++ .../src/zarr/pushdown_statistics.rs | 6 +- beacon-formats/src/zarr/source.rs | 8 +-- 5 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 beacon-formats/src/zarr/partition.rs diff --git a/beacon-data-lake/src/table/table_formats.rs b/beacon-data-lake/src/table/table_formats.rs index ab31c98d..549dcb32 100644 --- a/beacon-data-lake/src/table/table_formats.rs +++ b/beacon-data-lake/src/table/table_formats.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, sync::Arc}; use beacon_formats::zarr::{ - ZarrFormat, array_step_span::NumericArrayStepSpan, pushdown_statistics::PushDownZarrStatistics, + ZarrFormat, array_step_span::NumericArrayStepSpan, pushdown_statistics::ZarrPushDownStatistics, }; use datafusion::datasource::file_format::FileFormat; @@ -60,7 +60,7 @@ pub struct ZarrFileFormat { #[serde(default)] pub global_array_steps: HashMap, #[serde(default)] - pub pushdown_statistics: PushDownZarrStatistics, + pub pushdown_statistics: ZarrPushDownStatistics, } #[typetag::serde(name = "zarr")] diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs index f04def70..501e70b9 100644 --- a/beacon-formats/src/zarr/mod.rs +++ b/beacon-formats/src/zarr/mod.rs @@ -4,7 +4,7 @@ use std::{ sync::Arc, }; -use arrow::{compute::kernels::partition, datatypes::SchemaRef}; +use arrow::datatypes::SchemaRef; use beacon_arrow_zarr::reader::AsyncArrowZarrGroupReader; use datafusion::{ catalog::{Session, memory::DataSourceExec}, @@ -25,13 +25,14 @@ use zarrs_storage::AsyncReadableListableStorageTraits; use crate::zarr::{ array_step_span::NumericArrayStepSpan, - pushdown_statistics::PushDownZarrStatistics, + pushdown_statistics::ZarrPushDownStatistics, source::{ZarrSource, fetch_schema}, util::{ZarrPath, path_parent, top_level_zarr_meta_v3}, }; pub mod array_step_span; pub mod expr_util; +mod partition; pub mod pushdown_statistics; mod source; mod stream_share; @@ -85,7 +86,7 @@ impl FileFormatFactory for ZarrFormatFactory { #[derive(Debug, Clone, Default)] pub struct ZarrFormat { array_steps: HashMap, - zarr_pushdown_statistics: PushDownZarrStatistics, + zarr_pushdown_statistics: ZarrPushDownStatistics, } impl ZarrFormat { @@ -96,7 +97,7 @@ impl ZarrFormat { pub fn with_pushdown_statistics( mut self, - zarr_pushdown_statistics: PushDownZarrStatistics, + zarr_pushdown_statistics: ZarrPushDownStatistics, ) -> Self { self.zarr_pushdown_statistics = zarr_pushdown_statistics; self diff --git a/beacon-formats/src/zarr/partition.rs b/beacon-formats/src/zarr/partition.rs new file mode 100644 index 00000000..c5e149e5 --- /dev/null +++ b/beacon-formats/src/zarr/partition.rs @@ -0,0 +1,61 @@ +use std::sync::Arc; + +use arrow::datatypes::SchemaRef; +use datafusion::datasource::physical_plan::FileGroup; +use object_store::{ObjectMeta, ObjectStore}; +use zarrs::group::Group; +use zarrs_object_store::AsyncObjectStore; +use zarrs_storage::AsyncReadableListableStorageTraits; + +use crate::zarr::{pushdown_statistics::ZarrPushDownStatistics, util::ZarrPath}; + +pub(crate) async fn find_partitioned_files( + group: &Group, +) -> Option>> { + match group.async_child_groups().await { + Ok(children) => { + // Find recursively all child groups that contain groups aswell + let mut result = Vec::new(); + for child in children { + if let Some(mut sub_children) = Box::pin(find_partitioned_files(&child)).await { + result.append(&mut sub_children); + } else { + result.push(child); + } + } + Some(result) + } + Err(_) => None, + } +} + +pub(crate) async fn partition_zarr_group( + table_schema: SchemaRef, + object: &ObjectMeta, + object_store: Arc, + pushdown_statistics: &ZarrPushDownStatistics, +) -> datafusion::error::Result { + // Create an Zarr AsyncObjectStore wrapper around the ObjectStore + let zarr_store = Arc::new(AsyncObjectStore::new(object_store.clone())); + + let zarr_group_path = ZarrPath::new_from_object_meta(object.clone()).map_err(|e| { + datafusion::error::DataFusionError::Execution(format!( + "Failed to create ZarrPath from object metadata: {}", + e + )) + })?; + + let group = Group::async_open(zarr_store, &zarr_group_path.as_zarr_path()) + .await + .map_err(|e| { + datafusion::error::DataFusionError::Execution(format!( + "Failed to open Zarr group at path '{}': {}", + zarr_group_path.as_zarr_path(), + e + )) + })?; + + + + todo!() +} diff --git a/beacon-formats/src/zarr/pushdown_statistics.rs b/beacon-formats/src/zarr/pushdown_statistics.rs index 41d7f54e..7f0e2baf 100644 --- a/beacon-formats/src/zarr/pushdown_statistics.rs +++ b/beacon-formats/src/zarr/pushdown_statistics.rs @@ -13,11 +13,11 @@ use zarrs::group::Group; use zarrs_storage::AsyncReadableListableStorageTraits; #[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct PushDownZarrStatistics { +pub struct ZarrPushDownStatistics { pub arrays: Vec, } -impl PushDownZarrStatistics { +impl ZarrPushDownStatistics { pub fn has_array(&self, array_name: &str) -> bool { self.arrays.contains(&array_name.to_string()) } @@ -25,7 +25,7 @@ impl PushDownZarrStatistics { pub async fn generate_statistics_from_zarr_group( schema: &SchemaRef, - pushdown: &PushDownZarrStatistics, + pushdown: &ZarrPushDownStatistics, zarr_group: Arc>, ) -> Option { let zarr_reader = AsyncArrowZarrGroupReader::new(zarr_group.clone()) diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs index cae0e712..ea1996a0 100644 --- a/beacon-formats/src/zarr/source.rs +++ b/beacon-formats/src/zarr/source.rs @@ -34,7 +34,7 @@ use zarrs_storage::AsyncReadableListableStorageTraits; use crate::zarr::{ array_step_span::NumericArrayStepSpan, expr_util::extract_range_from_physical_filters, - pushdown_statistics::PushDownZarrStatistics, stream_share::ZarrStreamShare, util::path_parent, + pushdown_statistics::ZarrPushDownStatistics, stream_share::ZarrStreamShare, util::path_parent, }; pub async fn fetch_schema( @@ -83,7 +83,7 @@ pub struct ZarrSource { /// Array Steps for slicing arrays based on step spans. This is utilized by the pruning predicate pushdown. array_steps: Arc>, /// Zarr Statistics - pushdown_zarr_statistics: PushDownZarrStatistics, + pushdown_zarr_statistics: ZarrPushDownStatistics, /// Pruning Predicate predicate: Option>, } @@ -96,7 +96,7 @@ impl ZarrSource { pub fn with_pushdown_zarr_statistics( mut self, - pushdown_zarr_statistics: PushDownZarrStatistics, + pushdown_zarr_statistics: ZarrPushDownStatistics, ) -> Self { self.pushdown_zarr_statistics = pushdown_zarr_statistics; self @@ -284,7 +284,7 @@ pub struct ZarrFileOpener { /// Array Steps for slicing arrays based on step spans. This is utilized by the pruning predicate pushdown. array_steps: Arc>, /// Pushdown Zarr Statistics - pushdown_zarr_statistics: PushDownZarrStatistics, + pushdown_zarr_statistics: ZarrPushDownStatistics, /// Pruning Predicate predicate: Option>, } From a5475fc399c1812c7efaa1c6eeea88b0369c95af Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 29 Oct 2025 09:09:09 +0100 Subject: [PATCH 14/19] wip --- beacon-formats/src/zarr/mod.rs | 29 ++- beacon-formats/src/zarr/opener.rs | 241 ++++++++++++++++++++++ beacon-formats/src/zarr/source.rs | 258 +++++------------------- beacon-formats/src/zarr/stream_share.rs | 30 ++- beacon-formats/src/zarr/util.rs | 16 +- 5 files changed, 336 insertions(+), 238 deletions(-) create mode 100644 beacon-formats/src/zarr/opener.rs diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs index 501e70b9..8331661d 100644 --- a/beacon-formats/src/zarr/mod.rs +++ b/beacon-formats/src/zarr/mod.rs @@ -27,11 +27,12 @@ use crate::zarr::{ array_step_span::NumericArrayStepSpan, pushdown_statistics::ZarrPushDownStatistics, source::{ZarrSource, fetch_schema}, - util::{ZarrPath, path_parent, top_level_zarr_meta_v3}, + util::{ZarrPath, is_zarr_v3_metadata, path_parent, top_level_zarr_meta_v3}, }; pub mod array_step_span; pub mod expr_util; +pub mod opener; mod partition; pub mod pushdown_statistics; mod source; @@ -127,15 +128,31 @@ impl FileFormat for ZarrFormat { async fn infer_schema( &self, - state: &dyn Session, + _state: &dyn Session, store: &Arc, objects: &[ObjectMeta], ) -> datafusion::error::Result { - let objects = top_level_zarr_meta_v3(objects); - tracing::debug!("Top-level Zarr v3 groups: {:?}", objects); - let mut schema = None; + // Ensure every group is a zarr.json file. for object in objects { - let object_schema = fetch_schema(store.clone(), &object.clone()).await?; + if !is_zarr_v3_metadata(object) { + return Err(datafusion::error::DataFusionError::Execution(format!( + "Object at location '{}' is not a Zarr v3 metadata file (zarr.json)", + object.location.as_ref() + ))); + } + } + // Collect only top-level zarr.json files. + let verified_objects = top_level_zarr_meta_v3(objects); + tracing::debug!("Top-level Zarr v3 groups: {:?}", verified_objects); + let mut schema = None; + for object in verified_objects { + let zarr_path = ZarrPath::new_from_object_meta(object.clone()).map_err(|e| { + datafusion::error::DataFusionError::Execution(format!( + "Failed to create ZarrPath from ObjectMeta at {}: {}", + object.location, e + )) + })?; + let object_schema = fetch_schema(store.clone(), &zarr_path).await?; schema = Some(object_schema); } Ok(schema.unwrap()) diff --git a/beacon-formats/src/zarr/opener.rs b/beacon-formats/src/zarr/opener.rs new file mode 100644 index 00000000..fc2b18cc --- /dev/null +++ b/beacon-formats/src/zarr/opener.rs @@ -0,0 +1,241 @@ +use std::{collections::HashMap, sync::Arc}; + +use arrow::{array::RecordBatch, error::ArrowError}; +use beacon_arrow_zarr::{ + array_slice_pushdown::ArraySlicePushDown, reader::AsyncArrowZarrGroupReader, +}; +use datafusion::{ + datasource::{ + listing::PartitionedFile, + physical_plan::{FileMeta, FileOpenFuture, FileOpener}, + schema_adapter::SchemaAdapter, + }, + physical_plan::PhysicalExpr, +}; +use futures::{FutureExt, StreamExt}; +use object_store::ObjectStore; +use parking_lot::Mutex; +use zarrs::group::Group; +use zarrs_object_store::AsyncObjectStore; +use zarrs_storage::AsyncReadableListableStorageTraits; + +use crate::zarr::{ + array_step_span::NumericArrayStepSpan, + expr_util::extract_range_from_physical_filters, + pushdown_statistics::ZarrPushDownStatistics, + stream_share::{PartitionedZarrStreamShare, ZarrStreamShare}, + util::{ZarrPath, path_parent}, +}; + +pub struct ZarrFileOpener { + pub zarr_object_store: Arc>>, + /// Schema adapter for mapping NetCDF schema to Arrow schema. + pub schema_adapter: Arc, + /// Stream partition shares for the Zarr file. + pub stream_partition_shares: + Arc>>>, + /// Array Steps for slicing arrays based on step spans. This is utilized by the pruning predicate pushdown. + pub array_steps: Arc>, + /// Pushdown Zarr Statistics + pub pushdown_zarr_statistics: ZarrPushDownStatistics, + /// Pruning Predicate + pub predicate: Option>, +} + +async fn fetch_partitioned_streams( + zarr_path: ZarrPath, +) -> datafusion::error::Result> { + todo!() +} + +impl FileOpener for ZarrFileOpener { + fn open( + &self, + file_meta: FileMeta, + file: PartitionedFile, + ) -> datafusion::error::Result { + // Parse file meta as ZarrFile + + let zarr_path = ZarrPath::new_from_object_meta(file_meta.object_meta).map_err(|e| { + datafusion::error::DataFusionError::Execution(format!( + "Failed to create ZarrPath from ObjectMeta: {}", + e + )) + })?; + + let zarr_object_store = self.zarr_object_store.clone(); + let adapter = self.schema_adapter.clone(); + let stream_partition_shares = self.stream_partition_shares.clone(); + let stream_partition_share = { + let mut stream_partition_share_map = stream_partition_shares.lock(); + let object_path = file_meta.object_meta.location.clone(); + tracing::debug!( + "Getting or creating ZarrStreamShare for path: {}", + object_path + ); + stream_partition_share_map + .entry(object_path) + .or_insert_with(|| Arc::new(ZarrStreamShare::new())) + .clone() + }; + let pruning_predicate = self.predicate.clone(); + let array_steps = self.array_steps.clone(); + let pushdown_zarr_statistics = self.pushdown_zarr_statistics.clone(); + + // Check if the pruning predicate references any arrays in the pushdown statistics + let fut = async move { + let (stream, schema_mapper, file_schema) = stream_partition_share + .get_or_try_init(|| async move { + tracing::debug!("Opening file: {:?}", file_meta.object_meta.location); + + let parent_path = + path_parent(&file_meta.object_meta.location).ok_or_else(|| { + datafusion::error::DataFusionError::Execution(format!( + "Could not determine parent path of object: {}", + file_meta.object_meta.location + )) + })?; + + // Open the zarr group + let group = Group::async_open( + zarr_object_store.clone() as Arc, + &format!("/{}", parent_path), + ) + .await + .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; + + let reader = AsyncArrowZarrGroupReader::new(Arc::new(group)) + .await + .map_err(|e| { + datafusion::error::DataFusionError::Execution(e.to_string()) + })?; + + let file_schema = reader.arrow_schema(); + + let (schema_mapper, projection) = adapter + .map_schema(&file_schema) + .map_err(|e| datafusion::error::DataFusionError::External(Box::new(e)))?; + + // Generate ArraySlicePushDown based on the predicate and array steps + let array_slice_pushdowns = if let Some(pruning_predicate) = &pruning_predicate + { + array_steps + .values() + .filter_map(|span| { + generate_numeric_span_slice_pushdown(pruning_predicate, span) + }) + .map(|pd| (pd.dimension().to_string(), pd)) + .collect() + } else { + HashMap::new() + }; + + tracing::debug!("Array Slice Pushdowns: {:?}", array_slice_pushdowns); + + let stream_producer = reader + .into_parallel_stream_composer( + Some(projection), + Some(array_slice_pushdowns), + ) + .map_err(|e| { + datafusion::error::DataFusionError::Execution(e.to_string()) + })?; + + Ok::<_, datafusion::error::DataFusionError>(( + stream_producer, + schema_mapper, + file_schema, + )) + }) + .await? + .clone(); + let producer = stream.pollable_shared_stream(); + + let stream_proxy = producer + .map( + move |nd_batch| -> Result { + let schema_mapper = schema_mapper.clone(); + let arrow_batch = nd_batch + .map_err(|e| { + tracing::error!( + "Error reading NdRecordBatch from Zarr stream: {:?}", + e + ); + ArrowError::IoError( + "Error reading NdRecordBatch from Zarr stream".to_string(), + std::io::Error::other(e), + ) + })? + .to_arrow_record_batch() + .unwrap_or_else(|e| { + tracing::error!( + "Error converting NdRecordBatch to Arrow RecordBatch: {:?}", + e + ); + RecordBatch::new_empty(file_schema.clone()) + }); + let mapped_batch = schema_mapper + .map_batch(arrow_batch) + .map_err(|e| ArrowError::ExternalError(Box::new(e)))?; + Ok(mapped_batch) + }, + ) + .boxed(); + Ok(stream_proxy) + }; + + Ok(fut.boxed()) + } +} + +fn generate_numeric_span_slice_pushdown( + pruning_predicate: &Arc, + span: &NumericArrayStepSpan, +) -> Option { + let r = + extract_range_from_physical_filters(std::slice::from_ref(pruning_predicate), &span.column); + let mut min_step = None; + let mut max_step = None; + if let Some(min) = r.as_ref().and_then(|r| r.as_f64_min()) { + // Calculate start index based on step + let start_index = lower_index(span.start, span.step, min); + min_step = Some(if start_index < 0 { 0 } else { start_index }); + } + if let Some(max) = r.as_ref().and_then(|r| r.as_f64_max()) { + // Calculate end index based on step + let end_index = upper_index(span.start, span.step, max); + max_step = Some(if end_index < 0 { 0 } else { end_index }); + } + + // If min_step > max_step, then swap them + if let (Some(min), Some(max)) = (min_step, max_step) + && min > max + { + min_step = Some(max); + max_step = Some(min); + } + + Some(ArraySlicePushDown::new( + span.dimension.clone(), + min_step.map(|v| v as usize), + max_step.map(|v| v as usize), + )) +} + +fn lower_index(start: f64, step: f64, value: f64) -> i64 { + let raw = (value - start) / step; + if step >= 0.0 { + raw.floor() as i64 // ascending: lower = floor + } else { + raw.ceil() as i64 // descending: lower = ceil + } +} + +fn upper_index(start: f64, step: f64, value: f64) -> i64 { + let raw = (value - start) / step; + if step >= 0.0 { + raw.ceil() as i64 // ascending: upper = ceil + } else { + raw.floor() as i64 // descending: upper = floor + } +} diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs index ea1996a0..10087e12 100644 --- a/beacon-formats/src/zarr/source.rs +++ b/beacon-formats/src/zarr/source.rs @@ -33,36 +33,65 @@ use zarrs_object_store::AsyncObjectStore; use zarrs_storage::AsyncReadableListableStorageTraits; use crate::zarr::{ - array_step_span::NumericArrayStepSpan, expr_util::extract_range_from_physical_filters, - pushdown_statistics::ZarrPushDownStatistics, stream_share::ZarrStreamShare, util::path_parent, + array_step_span::NumericArrayStepSpan, + expr_util::extract_range_from_physical_filters, + opener::ZarrFileOpener, + pushdown_statistics::ZarrPushDownStatistics, + stream_share::ZarrStreamShare, + util::{ZarrPath, path_parent}, }; pub async fn fetch_schema( object_store: Arc, - object_meta: &ObjectMeta, + zarr_path: &ZarrPath, ) -> datafusion::error::Result { let zarr_store = Arc::new(AsyncObjectStore::new(object_store)) as Arc; - // The object meta reprensents the zarr.json file in the root of the zarr group. We need to open the group at the parent directory of this file. - let parent_path = path_parent(&object_meta.location).ok_or_else(|| { + let group = Group::async_open(zarr_store.clone(), &zarr_path.as_zarr_path()) + .await + .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; + let mut zarr_groups = Vec::new(); + recursive_groups(Arc::new(group), &mut zarr_groups).await?; + + // For each zarr group, fetch its schema and merge them. + let mut schemas = Vec::new(); + for zarr_group in zarr_groups { + let reader = AsyncArrowZarrGroupReader::new(zarr_group.clone()) + .await + .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; + let schema = reader.arrow_schema(); + schemas.push(schema); + } + + let super_schema = beacon_common::super_typing::super_type_schema(&schemas).map_err(|e| { datafusion::error::DataFusionError::Execution(format!( - "Could not determine parent path of object: {}", - object_meta.location + "Failed to compute super schema for Zarr groups at {}: {}", + zarr_path.as_zarr_path(), + e )) })?; - let group = Group::async_open(zarr_store.clone(), &format!("/{}", parent_path)) - .await - .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; - - let reader = AsyncArrowZarrGroupReader::new(Arc::new(group)) - .await - .map_err(datafusion::error::DataFusionError::Execution)?; + Ok(Arc::new(super_schema)) +} - let schema = reader.arrow_schema(); +pub async fn recursive_groups( + top_level_group: Arc>, + zarr_groups: &mut Vec>>, +) -> datafusion::error::Result<()> { + let child_groups = top_level_group.async_child_groups().await.map_err(|e| { + datafusion::error::DataFusionError::Execution(format!("Failed to list child groups: {}", e)) + })?; - Ok(schema) + if child_groups.is_empty() { + // Add the top-level group itself if it has no child groups. + zarr_groups.push(top_level_group.clone()); + } else { + for child_group in child_groups { + Box::pin(recursive_groups(Arc::new(child_group), zarr_groups)).await?; + } + } + return Ok(()); } #[derive(Default, Clone)] @@ -274,200 +303,3 @@ impl FileSource for ZarrSource { .with_updated_node(Arc::new(source))) } } - -pub struct ZarrFileOpener { - zarr_object_store: Arc>>, - /// Schema adapter for mapping NetCDF schema to Arrow schema. - schema_adapter: Arc, - /// Stream partition shares for the Zarr file. - stream_partition_shares: Arc>>>, - /// Array Steps for slicing arrays based on step spans. This is utilized by the pruning predicate pushdown. - array_steps: Arc>, - /// Pushdown Zarr Statistics - pushdown_zarr_statistics: ZarrPushDownStatistics, - /// Pruning Predicate - predicate: Option>, -} - -impl FileOpener for ZarrFileOpener { - fn open( - &self, - file_meta: FileMeta, - _file: PartitionedFile, - ) -> datafusion::error::Result { - let zarr_object_store = self.zarr_object_store.clone(); - let adapter = self.schema_adapter.clone(); - let stream_partition_shares = self.stream_partition_shares.clone(); - let stream_partition_share = { - let mut stream_partition_share_map = stream_partition_shares.lock(); - let object_path = file_meta.object_meta.location.clone(); - tracing::debug!( - "Getting or creating ZarrStreamShare for path: {}", - object_path - ); - stream_partition_share_map - .entry(object_path) - .or_insert_with(|| Arc::new(ZarrStreamShare::new())) - .clone() - }; - let pruning_predicate = self.predicate.clone(); - let array_steps = self.array_steps.clone(); - let pushdown_zarr_statistics = self.pushdown_zarr_statistics.clone(); - - // Check if the pruning predicate references any arrays in the pushdown statistics - let fut = async move { - let (stream, schema_mapper, file_schema) = stream_partition_share - .get_or_try_init(|| async move { - tracing::debug!("Opening file: {:?}", file_meta.object_meta.location); - - let parent_path = - path_parent(&file_meta.object_meta.location).ok_or_else(|| { - datafusion::error::DataFusionError::Execution(format!( - "Could not determine parent path of object: {}", - file_meta.object_meta.location - )) - })?; - - // Open the zarr group - let group = Group::async_open( - zarr_object_store.clone() as Arc, - &format!("/{}", parent_path), - ) - .await - .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; - - let reader = AsyncArrowZarrGroupReader::new(Arc::new(group)) - .await - .map_err(|e| { - datafusion::error::DataFusionError::Execution(e.to_string()) - })?; - - let file_schema = reader.arrow_schema(); - - let (schema_mapper, projection) = adapter - .map_schema(&file_schema) - .map_err(|e| datafusion::error::DataFusionError::External(Box::new(e)))?; - - // Generate ArraySlicePushDown based on the predicate and array steps - let array_slice_pushdowns = if let Some(pruning_predicate) = &pruning_predicate - { - array_steps - .values() - .filter_map(|span| { - generate_numeric_span_slice_pushdown(pruning_predicate, span) - }) - .map(|pd| (pd.dimension().to_string(), pd)) - .collect() - } else { - HashMap::new() - }; - - tracing::debug!("Array Slice Pushdowns: {:?}", array_slice_pushdowns); - - let stream_producer = reader - .into_parallel_stream_composer( - Some(projection), - Some(array_slice_pushdowns), - ) - .map_err(|e| { - datafusion::error::DataFusionError::Execution(e.to_string()) - })?; - - Ok::<_, datafusion::error::DataFusionError>(( - stream_producer, - schema_mapper, - file_schema, - )) - }) - .await? - .clone(); - let producer = stream.pollable_shared_stream(); - - let stream_proxy = producer - .map( - move |nd_batch| -> Result { - let schema_mapper = schema_mapper.clone(); - let arrow_batch = nd_batch - .map_err(|e| { - tracing::error!( - "Error reading NdRecordBatch from Zarr stream: {:?}", - e - ); - ArrowError::IoError( - "Error reading NdRecordBatch from Zarr stream".to_string(), - std::io::Error::other(e), - ) - })? - .to_arrow_record_batch() - .unwrap_or_else(|e| { - tracing::error!( - "Error converting NdRecordBatch to Arrow RecordBatch: {:?}", - e - ); - RecordBatch::new_empty(file_schema.clone()) - }); - let mapped_batch = schema_mapper - .map_batch(arrow_batch) - .map_err(|e| ArrowError::ExternalError(Box::new(e)))?; - Ok(mapped_batch) - }, - ) - .boxed(); - Ok(stream_proxy) - }; - - Ok(fut.boxed()) - } -} - -fn generate_numeric_span_slice_pushdown( - pruning_predicate: &Arc, - span: &NumericArrayStepSpan, -) -> Option { - let r = - extract_range_from_physical_filters(std::slice::from_ref(pruning_predicate), &span.column); - let mut min_step = None; - let mut max_step = None; - if let Some(min) = r.as_ref().and_then(|r| r.as_f64_min()) { - // Calculate start index based on step - let start_index = lower_index(span.start, span.step, min); - min_step = Some(if start_index < 0 { 0 } else { start_index }); - } - if let Some(max) = r.as_ref().and_then(|r| r.as_f64_max()) { - // Calculate end index based on step - let end_index = upper_index(span.start, span.step, max); - max_step = Some(if end_index < 0 { 0 } else { end_index }); - } - - // If min_step > max_step, then swap them - if let (Some(min), Some(max)) = (min_step, max_step) - && min > max - { - min_step = Some(max); - max_step = Some(min); - } - - Some(ArraySlicePushDown::new( - span.dimension.clone(), - min_step.map(|v| v as usize), - max_step.map(|v| v as usize), - )) -} - -fn lower_index(start: f64, step: f64, value: f64) -> i64 { - let raw = (value - start) / step; - if step >= 0.0 { - raw.floor() as i64 // ascending: lower = floor - } else { - raw.ceil() as i64 // descending: lower = ceil - } -} - -fn upper_index(start: f64, step: f64, value: f64) -> i64 { - let raw = (value - start) / step; - if step >= 0.0 { - raw.ceil() as i64 // ascending: upper = ceil - } else { - raw.floor() as i64 // descending: upper = floor - } -} diff --git a/beacon-formats/src/zarr/stream_share.rs b/beacon-formats/src/zarr/stream_share.rs index ddea018d..ebaec4b8 100644 --- a/beacon-formats/src/zarr/stream_share.rs +++ b/beacon-formats/src/zarr/stream_share.rs @@ -1,39 +1,35 @@ use std::{future::Future, sync::Arc}; use arrow::datatypes::SchemaRef; -use beacon_arrow_zarr::stream::ArrowZarrStreamComposerRef; use datafusion::datasource::schema_adapter::SchemaMapper; +use futures::FutureExt; + +pub struct PartitionedZarrStreamShare { + pub stream_share: Arc, + pub schema_mapper: Arc, + pub schema: SchemaRef, +} pub struct ZarrStreamShare { - inner: tokio::sync::OnceCell<(ArrowZarrStreamComposerRef, Arc, SchemaRef)>, + partitions: tokio::sync::OnceCell>, } impl ZarrStreamShare { pub fn new() -> Self { Self { - inner: tokio::sync::OnceCell::new(), + partitions: tokio::sync::OnceCell::new(), } } - pub fn get_or_try_init( - &self, - f: F, - ) -> impl Future< - Output = Result< - &(ArrowZarrStreamComposerRef, Arc, SchemaRef), - datafusion::error::DataFusionError, - >, - > + pub fn get_or_try_init(&self, f: F) -> impl Future where F: FnOnce() -> Fut, Fut: Future< - Output = Result< - (ArrowZarrStreamComposerRef, Arc, SchemaRef), - datafusion::error::DataFusionError, - >, + Output = Result, datafusion::error::DataFusionError>, >, { - self.inner.get_or_try_init(f) + // Clone the Arc inside the OnceCell to return it. + self.partitions.get_or_try_init(f).map(|f| f.cloned()) } } diff --git a/beacon-formats/src/zarr/util.rs b/beacon-formats/src/zarr/util.rs index 2ba98369..d4174931 100644 --- a/beacon-formats/src/zarr/util.rs +++ b/beacon-formats/src/zarr/util.rs @@ -12,8 +12,20 @@ pub enum ZarrPath { impl ZarrPath { pub fn as_zarr_path(&self) -> String { match self { - ZarrPath::ObjectMeta(meta) => meta.location.as_ref().to_string(), - ZarrPath::DirPath(path) => format!("{}/zarr.json", path.as_ref()), + ZarrPath::ObjectMeta(meta) => { + // Strip the trailing "zarr.json" to get the group path. + let loc = meta.location.as_ref(); + loc.strip_suffix("zarr.json").unwrap_or(loc).to_string() + } + ZarrPath::DirPath(path) => { + // Check if ends with '/' and return as is or add it. + let p_str = path.as_ref(); + if p_str.ends_with('/') { + p_str.to_string() + } else { + format!("{}/", p_str) + } + } } } From 4e9fb5d672c0222bac2287e45c85713e892a3c5f Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 29 Oct 2025 11:34:06 +0100 Subject: [PATCH 15/19] zarr groups as partitioned streams --- beacon-formats/src/zarr/opener.rs | 215 +++++++++++++----------- beacon-formats/src/zarr/stream_share.rs | 21 ++- 2 files changed, 132 insertions(+), 104 deletions(-) diff --git a/beacon-formats/src/zarr/opener.rs b/beacon-formats/src/zarr/opener.rs index fc2b18cc..f7cb89ef 100644 --- a/beacon-formats/src/zarr/opener.rs +++ b/beacon-formats/src/zarr/opener.rs @@ -1,8 +1,9 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, pin::Pin, sync::Arc}; -use arrow::{array::RecordBatch, error::ArrowError}; +use arrow::error::ArrowError; use beacon_arrow_zarr::{ array_slice_pushdown::ArraySlicePushDown, reader::AsyncArrowZarrGroupReader, + stream::ArrowZarrStream, }; use datafusion::{ datasource::{ @@ -23,8 +24,9 @@ use crate::zarr::{ array_step_span::NumericArrayStepSpan, expr_util::extract_range_from_physical_filters, pushdown_statistics::ZarrPushDownStatistics, + source::recursive_groups, stream_share::{PartitionedZarrStreamShare, ZarrStreamShare}, - util::{ZarrPath, path_parent}, + util::ZarrPath, }; pub struct ZarrFileOpener { @@ -44,24 +46,116 @@ pub struct ZarrFileOpener { async fn fetch_partitioned_streams( zarr_path: ZarrPath, + zarr_object_store: Arc>>, + array_steps: Arc>, + pruning_predicate: Option>, + table_schema_adapter: Arc, ) -> datafusion::error::Result> { - todo!() + let base_group = Group::async_open( + zarr_object_store.clone() as Arc, + &zarr_path.as_zarr_path(), + ) + .await + .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; + + let mut partitioned_groups = Vec::new(); + recursive_groups(Arc::new(base_group), &mut partitioned_groups).await?; + // Iterate over arrays in the group and open them as partitioned groups + + let mut partitioned_streams = Vec::new(); + for group_partition in partitioned_groups { + let reader = AsyncArrowZarrGroupReader::new(group_partition) + .await + .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; + + let file_schema = reader.arrow_schema(); + + let (table_mapper, projection) = table_schema_adapter.map_schema(&file_schema)?; + let partition_file_schema = file_schema + .project(&projection) + .map_err(|e| datafusion::error::DataFusionError::ArrowError(Box::new(e), None))?; + + // Generate ArraySlicePushDown based on the predicate and array steps + let array_slice_pushdowns = if let Some(pruning_predicate) = &pruning_predicate { + array_steps + .values() + .filter_map(|span| generate_numeric_span_slice_pushdown(pruning_predicate, span)) + .map(|pd| (pd.dimension().to_string(), pd)) + .collect() + } else { + HashMap::new() + }; + + let stream = reader + .into_parallel_stream_composer(Some(projection), Some(array_slice_pushdowns)) + .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; + + let partition_stream = + PartitionedZarrStreamShare::new(stream, table_mapper, Arc::new(partition_file_schema)); + partitioned_streams.push(partition_stream); + } + + Ok(Arc::from(partitioned_streams)) +} + +async fn flatten_partitioned_streams( + partitioned_streams: Arc<[PartitionedZarrStreamShare]>, +) -> Pin< + Box< + dyn futures::Stream> + + Send, + >, +> { + let mut all_streams = Vec::new(); + for partition in partitioned_streams.iter() { + let stream = flatten_batch_stream(partition.stream_composer.pollable_shared_stream()); + all_streams.push(stream); + } + let combined_stream = futures::stream::iter(all_streams).flatten(); + combined_stream.boxed() +} + +fn flatten_batch_stream( + stream: ArrowZarrStream, +) -> Pin< + Box< + dyn futures::Stream> + + Send, + >, +> { + (stream + .map(|res_nd_batch| { + let nd_batch = res_nd_batch.map_err(|e| { + ArrowError::IoError( + "Error reading NdRecordBatch from Zarr stream".to_string(), + std::io::Error::other(e), + ) + })?; + + let arrow_batch = nd_batch + .to_arrow_record_batch() + .map_err(|e| ArrowError::ExternalError(Box::new(e)))?; + + Ok(arrow_batch) + }) + .boxed()) as _ } impl FileOpener for ZarrFileOpener { fn open( &self, file_meta: FileMeta, - file: PartitionedFile, + _file: PartitionedFile, ) -> datafusion::error::Result { // Parse file meta as ZarrFile - let zarr_path = ZarrPath::new_from_object_meta(file_meta.object_meta).map_err(|e| { - datafusion::error::DataFusionError::Execution(format!( - "Failed to create ZarrPath from ObjectMeta: {}", - e - )) - })?; + let zarr_path = + ZarrPath::new_from_object_meta(file_meta.object_meta.clone()).map_err(|e| { + datafusion::error::DataFusionError::Execution(format!( + "Failed to create ZarrPath from ObjectMeta: {}", + e + )) + })?; let zarr_object_store = self.zarr_object_store.clone(); let adapter = self.schema_adapter.clone(); @@ -80,107 +174,26 @@ impl FileOpener for ZarrFileOpener { }; let pruning_predicate = self.predicate.clone(); let array_steps = self.array_steps.clone(); - let pushdown_zarr_statistics = self.pushdown_zarr_statistics.clone(); // Check if the pruning predicate references any arrays in the pushdown statistics let fut = async move { - let (stream, schema_mapper, file_schema) = stream_partition_share + let partitioned_streams = stream_partition_share .get_or_try_init(|| async move { tracing::debug!("Opening file: {:?}", file_meta.object_meta.location); - let parent_path = - path_parent(&file_meta.object_meta.location).ok_or_else(|| { - datafusion::error::DataFusionError::Execution(format!( - "Could not determine parent path of object: {}", - file_meta.object_meta.location - )) - })?; - - // Open the zarr group - let group = Group::async_open( - zarr_object_store.clone() as Arc, - &format!("/{}", parent_path), + fetch_partitioned_streams( + zarr_path, + zarr_object_store, + array_steps, + pruning_predicate, + adapter, ) .await - .map_err(|e| datafusion::error::DataFusionError::Execution(e.to_string()))?; - - let reader = AsyncArrowZarrGroupReader::new(Arc::new(group)) - .await - .map_err(|e| { - datafusion::error::DataFusionError::Execution(e.to_string()) - })?; - - let file_schema = reader.arrow_schema(); - - let (schema_mapper, projection) = adapter - .map_schema(&file_schema) - .map_err(|e| datafusion::error::DataFusionError::External(Box::new(e)))?; - - // Generate ArraySlicePushDown based on the predicate and array steps - let array_slice_pushdowns = if let Some(pruning_predicate) = &pruning_predicate - { - array_steps - .values() - .filter_map(|span| { - generate_numeric_span_slice_pushdown(pruning_predicate, span) - }) - .map(|pd| (pd.dimension().to_string(), pd)) - .collect() - } else { - HashMap::new() - }; - - tracing::debug!("Array Slice Pushdowns: {:?}", array_slice_pushdowns); - - let stream_producer = reader - .into_parallel_stream_composer( - Some(projection), - Some(array_slice_pushdowns), - ) - .map_err(|e| { - datafusion::error::DataFusionError::Execution(e.to_string()) - })?; - - Ok::<_, datafusion::error::DataFusionError>(( - stream_producer, - schema_mapper, - file_schema, - )) }) .await? .clone(); - let producer = stream.pollable_shared_stream(); - - let stream_proxy = producer - .map( - move |nd_batch| -> Result { - let schema_mapper = schema_mapper.clone(); - let arrow_batch = nd_batch - .map_err(|e| { - tracing::error!( - "Error reading NdRecordBatch from Zarr stream: {:?}", - e - ); - ArrowError::IoError( - "Error reading NdRecordBatch from Zarr stream".to_string(), - std::io::Error::other(e), - ) - })? - .to_arrow_record_batch() - .unwrap_or_else(|e| { - tracing::error!( - "Error converting NdRecordBatch to Arrow RecordBatch: {:?}", - e - ); - RecordBatch::new_empty(file_schema.clone()) - }); - let mapped_batch = schema_mapper - .map_batch(arrow_batch) - .map_err(|e| ArrowError::ExternalError(Box::new(e)))?; - Ok(mapped_batch) - }, - ) - .boxed(); + + let stream_proxy = flatten_partitioned_streams(partitioned_streams).await; Ok(stream_proxy) }; diff --git a/beacon-formats/src/zarr/stream_share.rs b/beacon-formats/src/zarr/stream_share.rs index ebaec4b8..4bc7b725 100644 --- a/beacon-formats/src/zarr/stream_share.rs +++ b/beacon-formats/src/zarr/stream_share.rs @@ -1,13 +1,28 @@ use std::{future::Future, sync::Arc}; use arrow::datatypes::SchemaRef; +use beacon_arrow_zarr::stream::ArrowZarrStreamComposerRef; use datafusion::datasource::schema_adapter::SchemaMapper; use futures::FutureExt; pub struct PartitionedZarrStreamShare { - pub stream_share: Arc, - pub schema_mapper: Arc, - pub schema: SchemaRef, + pub stream_composer: ArrowZarrStreamComposerRef, + pub table_schema_mapper: Arc, + pub partition_file_schema: SchemaRef, +} + +impl PartitionedZarrStreamShare { + pub fn new( + stream_share: ArrowZarrStreamComposerRef, + table_schema_mapper: Arc, + partition_file_schema: SchemaRef, + ) -> Self { + Self { + stream_composer: stream_share, + table_schema_mapper, + partition_file_schema, + } + } } pub struct ZarrStreamShare { From f1928e6e7a11e5f46a3e7b330f5be4e45ed2e0a4 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 29 Oct 2025 11:38:06 +0100 Subject: [PATCH 16/19] Add some comments --- beacon-formats/src/zarr/opener.rs | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/beacon-formats/src/zarr/opener.rs b/beacon-formats/src/zarr/opener.rs index f7cb89ef..0b6aff12 100644 --- a/beacon-formats/src/zarr/opener.rs +++ b/beacon-formats/src/zarr/opener.rs @@ -29,6 +29,41 @@ use crate::zarr::{ util::ZarrPath, }; +/// Open a Zarr object and produce a future that yields a stream proxy for its partitions. +/// +/// This implements FileOpener for ZarrFileOpener and performs the following steps: +/// - Converts the provided FileMeta's ObjectMeta into a ZarrPath, returning a DataFusion +/// Execution error if parsing fails. +/// - Uses an internal, shared map of stream shares keyed by object path to obtain an +/// Arc. The map access is protected by a lock; if a share for the +/// path does not exist it is created lazily. This ensures concurrent open requests +/// for the same path share initialization work and the resulting streams. +/// - Builds and returns a boxed asynchronous future. When polled, that future: +/// - Calls get_or_try_init on the ZarrStreamShare to initialize or reuse the +/// partitioned streams for the object. Initialization is performed by +/// fetch_partitioned_streams which may access the object store, apply pruning +/// using pushdown statistics/predicates, and consider array step configuration. +/// - Flattens the resulting partitioned streams into a single stream proxy suitable +/// for DataFusion consumption and returns it. +/// +/// Parameters: +/// - file_meta: Metadata for the file to open; its ObjectMeta is used to construct +/// the ZarrPath and to key the stream-share cache. +/// - _file: A PartitionedFile value that is unused by this opener (present to match +/// the trait signature). +/// +/// Return: +/// - On success: Ok(BoxFuture) that resolves to a stream proxy of the opened file's +/// partitions. +/// - On error: DataFusionError::Execution with a descriptive message (e.g., failed +/// ZarrPath creation or stream initialization). +/// +/// Additional notes: +/// - The method clones necessary handles (object store, schema adapter, predicate, +/// array steps) so the returned future is self-contained and safe to run later. +/// - Debug tracing is emitted for path creation and file open operations to aid +/// diagnosis. +/// - IO and heavy work are deferred until the returned future is awaited/polled. pub struct ZarrFileOpener { pub zarr_object_store: Arc>>, /// Schema adapter for mapping NetCDF schema to Arrow schema. @@ -201,6 +236,31 @@ impl FileOpener for ZarrFileOpener { } } +/// Result of translating the current physical pruning predicate(s) into a +/// concrete range for the provided column span. +/// +/// This value is produced by `extract_range_from_physical_filters` and +/// captures the contiguous interval of physical values (or row positions) +/// that are guaranteed to satisfy the pruning predicate applied to +/// `span.column`. The computed range is intended for chunk/block-level +/// pruning so that downstream I/O or scanning can skip data that falls +/// outside the returned interval. +/// +/// Behavior notes: +/// - When the predicate can be mapped to a simple interval, `r` holds that +/// interval (e.g., an inclusive/exclusive min..max). Consumers can use it +/// to restrict reads to the overlap with a chunk's bounds. +/// - If the predicate cannot be expressed as a single contiguous range, or +/// if it is contradictory, the function will indicate that no safe range +/// can be derived (typically via `None` or an empty/invalid range), and +/// no pruning should be applied. +/// - The pruning predicate is provided as a slice here to allow the same +/// translation logic to accept zero, one, or multiple physical filters; +/// callers intentionally pass a one-element slice when converting a single +/// predicate. +/// +/// Callers should always check the returned value before using it to avoid +/// incorrect data exclusion when translation fails or is inexact. fn generate_numeric_span_slice_pushdown( pruning_predicate: &Arc, span: &NumericArrayStepSpan, From 4b41a44f2f805b047278053de01292adfba6baa5 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 29 Oct 2025 11:42:58 +0100 Subject: [PATCH 17/19] Cleanup some comments --- beacon-arrow-zarr/src/stream.rs | 5 ----- beacon-formats/src/zarr/opener.rs | 2 +- beacon-formats/src/zarr/source.rs | 4 +--- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/beacon-arrow-zarr/src/stream.rs b/beacon-arrow-zarr/src/stream.rs index 0c3bfe7a..4010be3a 100644 --- a/beacon-arrow-zarr/src/stream.rs +++ b/beacon-arrow-zarr/src/stream.rs @@ -153,13 +153,8 @@ impl ArrowZarrStream { pushdown.overlapping_range(range.start as usize..range.end as usize) { *range = overlap.start as u64..overlap.end as u64; - // println!("Applying pushdown on dimension {}: {:?}", dim_name, range); } else { // No overlap, return empty batch - // println!( - // "Skipping chunk {:?} due to no overlap with pushdown on dimension {}", - // chunk_indices, dim_name - // ); let mut arrays = vec![]; let mut fields = vec![]; for field in composer.projected_schema.fields() { diff --git a/beacon-formats/src/zarr/opener.rs b/beacon-formats/src/zarr/opener.rs index 0b6aff12..4f56e92f 100644 --- a/beacon-formats/src/zarr/opener.rs +++ b/beacon-formats/src/zarr/opener.rs @@ -66,7 +66,7 @@ use crate::zarr::{ /// - IO and heavy work are deferred until the returned future is awaited/polled. pub struct ZarrFileOpener { pub zarr_object_store: Arc>>, - /// Schema adapter for mapping NetCDF schema to Arrow schema. + /// Schema adapter for mapping Zarr schema to Arrow schema. pub schema_adapter: Arc, /// Stream partition shares for the Zarr file. pub stream_partition_shares: diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs index 10087e12..b43e8311 100644 --- a/beacon-formats/src/zarr/source.rs +++ b/beacon-formats/src/zarr/source.rs @@ -140,8 +140,6 @@ impl FileSource for ZarrSource { base_config: &FileScanConfig, _partition: usize, ) -> Arc { - println!("Partition: {}", _partition); - let table_schema = self .override_schema .clone() @@ -262,7 +260,7 @@ impl FileSource for ZarrSource { output_ordering: Option, config: &FileScanConfig, ) -> datafusion::error::Result> { - // Repartion by duplicating the file groups to reach the target number of partitions. + // Repartition by duplicating the file groups to reach the target number of partitions. let file_groups = config.file_groups.clone(); let repartitioned: Vec = file_groups From 9073bba4c078d2c932b7eacb4199e5d99f5d6082 Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 29 Oct 2025 12:33:04 +0100 Subject: [PATCH 18/19] Fix parsing zarr path --- beacon-formats/src/zarr/util.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/beacon-formats/src/zarr/util.rs b/beacon-formats/src/zarr/util.rs index d4174931..3053451d 100644 --- a/beacon-formats/src/zarr/util.rs +++ b/beacon-formats/src/zarr/util.rs @@ -11,7 +11,7 @@ pub enum ZarrPath { impl ZarrPath { pub fn as_zarr_path(&self) -> String { - match self { + let directory_path = match self { ZarrPath::ObjectMeta(meta) => { // Strip the trailing "zarr.json" to get the group path. let loc = meta.location.as_ref(); @@ -19,14 +19,11 @@ impl ZarrPath { } ZarrPath::DirPath(path) => { // Check if ends with '/' and return as is or add it. - let p_str = path.as_ref(); - if p_str.ends_with('/') { - p_str.to_string() - } else { - format!("{}/", p_str) - } + path.as_ref().to_string() } - } + }; + let p_str = directory_path.trim_end_matches('/'); + format!("/{}", p_str) } pub fn new_from_object_meta(meta: object_store::ObjectMeta) -> Result { From 338aae896b2d6e0d559d317522420818be319b7c Mon Sep 17 00:00:00 2001 From: Robin Kooyman Date: Wed, 29 Oct 2025 16:22:51 +0100 Subject: [PATCH 19/19] Implement zarr statistics filtering --- Cargo.toml | 1 - beacon-api/Cargo.toml | 2 +- beacon-data-lake/src/lib.rs | 49 +---- beacon-data-lake/src/table/error.rs | 2 + beacon-data-lake/src/table/logical/mod.rs | 68 +------ beacon-data-lake/src/util.rs | 175 ++++++++++++++++-- beacon-formats/src/zarr/mod.rs | 12 +- beacon-formats/src/zarr/opener.rs | 40 ++-- .../src/zarr/pushdown_statistics.rs | 2 +- beacon-formats/src/zarr/source.rs | 3 +- beacon-formats/src/zarr/util.rs | 10 + 11 files changed, 221 insertions(+), 143 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d78d4ef1..5888abb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,4 +39,3 @@ parquet = { version = "^55.2.0", features = ["async"] } geoarrow = { version = "=0.4.0" } geoarrow-array = "=0.4.0" geoparquet = "0.4.0" -pprof = { version = "0.15", features = ["flamegraph"] } diff --git a/beacon-api/Cargo.toml b/beacon-api/Cargo.toml index fb3aa485..ae914052 100644 --- a/beacon-api/Cargo.toml +++ b/beacon-api/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [target.'cfg(not(windows))'.dependencies] tikv-jemallocator = "0.6.0" +pprof = { version = "0.15", features = ["flamegraph"] } [dependencies] axum = { version = "0.8.1", features = ["tracing"] } @@ -27,7 +28,6 @@ arrow-schema = { workspace = true} uuid = { version = "1.16.0" } tracing = { workspace = true} tracing-subscriber = { workspace = true} -pprof = { workspace = true } # Local dependencies beacon-config = { path = "../beacon-config" } diff --git a/beacon-data-lake/src/lib.rs b/beacon-data-lake/src/lib.rs index f175adf1..98ddedf4 100644 --- a/beacon-data-lake/src/lib.rs +++ b/beacon-data-lake/src/lib.rs @@ -17,12 +17,11 @@ use datafusion::{ }; use futures::StreamExt; use object_store::{ObjectStore, aws::AmazonS3Builder, local::LocalFileSystem, path::PathPart}; -use url::Url; use crate::{ files::{collection::FileCollection, temp_output_file::TempOutputFile}, table::{Table, empty::EmptyTable, error::TableError}, - util::split_glob, + util::parse_listing_table_url, }; pub mod files; @@ -72,50 +71,16 @@ impl Debug for DataLake { } impl DataLake { + #[inline(always)] pub fn try_create_listing_url( &self, path: String, ) -> datafusion::error::Result { - let parts = self.data_directory_prefix.parts().collect::>(); - if let Some((base, pattern)) = split_glob(&path) { - let pattern = glob::Pattern::new(&pattern).unwrap(); - let mut full_path = format!( - "{}{}", - self.data_directory_store_url, - object_store::path::Path::from_iter(parts.clone().into_iter()), - ); - if base.components().next().is_some() { - full_path.push_str(format!("/{}", base.as_os_str().to_string_lossy()).as_str()); - } - if !full_path.ends_with('/') { - full_path.push('/'); - } - - let url = Url::parse(&full_path).unwrap(); - - let table_url = ListingTableUrl::try_new(url, Some(pattern))?; - - Ok(table_url) - } else { - let mut full_path = format!( - "{}{}", - self.data_directory_store_url, - object_store::path::Path::from_iter(parts.clone().into_iter()), - ); - - // If full path ends with '/' then just append the path, otherwise append without a '/' - if full_path.ends_with('/') { - full_path.push_str(&path); - } else { - full_path.push_str(format!("/{}", path).as_str()); - } - - let url = Url::parse(&full_path).unwrap(); - - let table_url = ListingTableUrl::try_new(url, None)?; - - Ok(table_url) - } + parse_listing_table_url( + &self.data_directory_store_url, + &self.data_directory_prefix, + &path, + ) } pub fn netcdf_object_resolver() -> Arc { diff --git a/beacon-data-lake/src/table/error.rs b/beacon-data-lake/src/table/error.rs index 9a4d3185..109989cd 100644 --- a/beacon-data-lake/src/table/error.rs +++ b/beacon-data-lake/src/table/error.rs @@ -10,4 +10,6 @@ pub enum TableError { TableAlreadyExists(String), #[error("Table not found: {0}")] TableNotFound(String), + #[error("Datafusion Error: {0}")] + DatafusionError(#[from] datafusion::error::DataFusionError), } diff --git a/beacon-data-lake/src/table/logical/mod.rs b/beacon-data-lake/src/table/logical/mod.rs index 8e338b1a..b04be813 100644 --- a/beacon-data-lake/src/table/logical/mod.rs +++ b/beacon-data-lake/src/table/logical/mod.rs @@ -1,16 +1,13 @@ use std::sync::Arc; use datafusion::{ - catalog::TableProvider, datasource::listing::ListingTableUrl, - execution::object_store::ObjectStoreUrl, prelude::SessionContext, + catalog::TableProvider, execution::object_store::ObjectStoreUrl, prelude::SessionContext, }; -use object_store::path::PathPart; -use url::Url; use crate::{ files::collection::FileCollection, table::{error::TableError, table_formats::TableFileFormat}, - util::split_glob, + util::parse_listing_table_url, }; /// A representation of a logical table configuration. @@ -44,63 +41,14 @@ impl LogicalTable { let session_state = session_context.state(); let mut table_urls = Vec::new(); - let parts: Vec> = data_directory_prefix - .parts() - .map(|part| part.as_ref().to_string().into()) - .collect::>(); // Construct table URLs based on the paths provided in the logical table. - for path in &self.glob_paths { - // Check if path contains glob expression - if let Some((base, pattern)) = split_glob(path) { - let pattern = glob::Pattern::new(&pattern).map_err(|e| { - TableError::GenericTableError(format!("Failed to parse glob pattern: {}", e)) - })?; - let mut full_path = format!( - "{}{}", - data_directory_store_url, - object_store::path::Path::from_iter(parts.clone().into_iter()), - ); - if base.components().next().is_some() { - full_path.push_str(format!("/{}", base.as_os_str().to_string_lossy()).as_str()); - } - if !full_path.ends_with('/') { - full_path.push('/'); - } - - let url = Url::parse(&full_path).map_err(|e| { - TableError::GenericTableError(format!("Failed to parse URL: {}", e)) - })?; - - let table_url = ListingTableUrl::try_new(url, Some(pattern)).map_err(|e| { - TableError::GenericTableError(format!("Failed to create table URL: {}", e)) - })?; - - table_urls.push(table_url); - } else { - let mut full_path = format!( - "{}{}", - data_directory_store_url, - object_store::path::Path::from_iter(parts.clone().into_iter()), - ); - - // If full path ends with '/' then just append the path, otherwise append without a '/' - if full_path.ends_with('/') { - full_path.push_str(path); - } else { - full_path.push_str(format!("/{}", path).as_str()); - } - - let url = Url::parse(&full_path).map_err(|e| { - TableError::GenericTableError(format!("Failed to parse URL: {}", e)) - })?; - - let table_url = ListingTableUrl::try_new(url, None).map_err(|e| { - TableError::GenericTableError(format!("Failed to create table URL: {}", e)) - })?; - - table_urls.push(table_url); - } + for glob_path in &self.glob_paths { + table_urls.push(parse_listing_table_url( + data_directory_store_url, + data_directory_prefix, + glob_path, + )?); } tracing::debug!( diff --git a/beacon-data-lake/src/util.rs b/beacon-data-lake/src/util.rs index 21a0b715..28181b69 100644 --- a/beacon-data-lake/src/util.rs +++ b/beacon-data-lake/src/util.rs @@ -1,16 +1,16 @@ -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; +use std::{collections::HashMap, path::PathBuf}; use arrow::datatypes::{DataType, Fields, Schema, TimeUnit}; use datafusion::{ common::{ - Column, + Column, exec_datafusion_err, tree_node::{Transformed, TreeNode}, }, + datasource::listing::ListingTableUrl, + execution::object_store::ObjectStoreUrl, prelude::Expr, }; +use url::Url; #[derive(Debug, Clone, thiserror::Error)] pub enum SuperTypeError { @@ -293,17 +293,160 @@ pub fn remap_filter( Ok(transformed.data) } -pub fn split_glob(path: &str) -> Option<(PathBuf, String)> { - let p = Path::new(path); +pub fn parse_listing_table_url( + data_directory_store_url: &ObjectStoreUrl, + data_directory_prefix: &object_store::path::Path, + glob_path: &str, +) -> datafusion::error::Result { + // Check if path contains glob expression + let (path, glob_pattern) = split_path_and_glob(glob_path); + let mut full_path = format!("{}{}", data_directory_store_url, data_directory_prefix); + if path.components().next().is_some() { + full_path.push_str(format!("/{}", path.as_os_str().to_string_lossy()).as_str()); + } + if !full_path.ends_with('/') { + full_path.push('/'); + } - // Get the last component (file name or pattern) - if let Some(last) = p.file_name().and_then(|os| os.to_str()) { - // Does it look like a glob? - if last.contains('*') || last.contains('?') || last.contains('[') { - // Base path = everything before the last component - let base = p.parent().unwrap_or(Path::new("")).to_path_buf(); - return Some((base, last.to_string())); - } + let glob_pattern_parsed = if let Some(pattern) = glob_pattern { + Some( + glob::Pattern::new(&pattern) + .map_err(|e| exec_datafusion_err!("Failed to parse glob pattern: {}", e))?, + ) + } else { + None + }; + + let url = + Url::parse(&full_path).map_err(|e| exec_datafusion_err!("Failed to parse URL: {}", e))?; + + let table_url = ListingTableUrl::try_new(url, glob_pattern_parsed) + .map_err(|e| exec_datafusion_err!("Failed to create table URL: {}", e))?; + + Ok(table_url) +} + +/// Splits a path containing an optional glob pattern into +/// (base_path, optional_glob_pattern). +/// +/// Examples: +/// - "src/**/*.rs" -> ("src", Some("**/*.rs")) +/// - "src/*.rs" -> ("src", Some("*.rs")) +/// - "*.rs" -> (".", Some("*.rs")) +/// - "src/main.rs" -> ("src/main.rs", None) +pub fn split_path_and_glob(input: &str) -> (PathBuf, Option) { + let glob_chars = ['*', '?', '[', ']']; + + // Split the path into components manually (cross-platform) + let parts: Vec<&str> = input.split(['/', '\\']).collect(); + + // Find the index of the first segment that contains any glob char + if let Some(i) = parts + .iter() + .position(|p| p.chars().any(|c| glob_chars.contains(&c))) + { + let base = if i == 0 { + PathBuf::from(".") + } else { + PathBuf::from(parts[..i].join(std::path::MAIN_SEPARATOR_STR)) + }; + let glob = parts[i..].join(std::path::MAIN_SEPARATOR_STR); + (base, Some(glob)) + } else { + // No glob pattern at all + (PathBuf::from(input), None) + } +} + +#[cfg(test)] +mod tests { + use std::path::MAIN_SEPARATOR; + + use super::*; + + #[test] + fn test_no_glob() { + let (base, glob) = split_path_and_glob("src/main.rs"); + assert_eq!(base, PathBuf::from("src/main.rs")); + assert_eq!(glob, None); + } + + #[test] + fn test_simple_glob() { + let (base, glob) = split_path_and_glob("src/*.rs"); + assert_eq!(base, PathBuf::from("src")); + assert_eq!(glob, Some("*.rs".to_string())); + } + + #[test] + fn test_recursive_glob() { + let (base, glob) = split_path_and_glob("src/**/*.rs"); + assert_eq!(base, PathBuf::from("src")); + assert_eq!(glob, Some(format!("**{}*.rs", std::path::MAIN_SEPARATOR))); + } + + #[test] + fn test_glob_at_start() { + let (base, glob) = split_path_and_glob("*.txt"); + assert_eq!(base, PathBuf::from(".")); + assert_eq!(glob, Some("*.txt".to_string())); + } + + #[test] + fn test_glob_in_middle_segment() { + let (base, glob) = split_path_and_glob("foo[ab]/bar?.txt"); + assert_eq!(base, PathBuf::from("foo[ab]")); + assert_eq!(glob, Some("bar?.txt".to_string())); + } + + #[test] + fn test_no_separator_but_glob_present() { + let (base, glob) = split_path_and_glob("file[0-9].log"); + assert_eq!(base, PathBuf::from(".")); + assert_eq!(glob, Some("file[0-9].log".to_string())); + } + + #[test] + fn test_windows_style_path() { + // Works on both Unix and Windows separators + let input = format!("dir{}**{}*.txt", MAIN_SEPARATOR, MAIN_SEPARATOR); + let (base, glob) = split_path_and_glob(&input); + assert_eq!(base, PathBuf::from("dir")); + assert_eq!(glob, Some(format!("**{}*.txt", MAIN_SEPARATOR))); + } + + #[test] + fn test_parse_listing_table_url_no_glob() { + use datafusion::execution::object_store::ObjectStoreUrl; + + let store_url: ObjectStoreUrl = ObjectStoreUrl::parse("file:///").unwrap(); + let prefix = object_store::path::Path::from("datasets"); + let url = parse_listing_table_url(&store_url, &prefix, "foo/zarr.json").unwrap(); + // Debug representation should contain the base path we constructed + let dbg = format!("{:?}", url); + assert!(dbg.contains("/datasets/foo/"), "dbg={}", dbg); + } + + #[test] + fn test_parse_listing_table_url_with_glob() { + use datafusion::execution::object_store::ObjectStoreUrl; + + let store_url: ObjectStoreUrl = ObjectStoreUrl::parse("file:///").unwrap(); + let prefix = object_store::path::Path::from("datasets"); + let url = parse_listing_table_url(&store_url, &prefix, "subdir/*.json").unwrap(); + let dbg = format!("{:?}", url); + assert!(dbg.contains("/datasets/subdir/"), "dbg={}", dbg); + // pattern should be present in debug output + assert!(dbg.contains("*.json"), "dbg={}", dbg); + } + + #[test] + fn test_parse_listing_table_url_invalid_glob() { + use datafusion::execution::object_store::ObjectStoreUrl; + + let store_url: ObjectStoreUrl = ObjectStoreUrl::parse("file:///").unwrap(); + let prefix = object_store::path::Path::from("datasets"); + let res = parse_listing_table_url(&store_url, &prefix, "subdir/[invalid"); + assert!(res.is_err(), "expected error for invalid glob pattern"); } - None } diff --git a/beacon-formats/src/zarr/mod.rs b/beacon-formats/src/zarr/mod.rs index 8331661d..48fea513 100644 --- a/beacon-formats/src/zarr/mod.rs +++ b/beacon-formats/src/zarr/mod.rs @@ -251,7 +251,7 @@ impl ZarrFormat { .unwrap_or(Statistics::new_unknown(table_schema)); Ok(FileGroup::new(vec![ - PartitionedFile::new(group_path.as_zarr_path(), 0) + PartitionedFile::new(group_path.as_zarr_json_path(), 0) .with_statistics(Arc::new(statistics)), ])) } else { @@ -267,7 +267,6 @@ impl ZarrFormat { ) .await .unwrap_or(Statistics::new_unknown(table_schema)); - println!("SUB Group Path: {:?}", group.path()); // Create a PartitionedFile for the sub-group let partitioned_file = @@ -278,14 +277,7 @@ impl ZarrFormat { Ok(FileGroup::new(files)) } } - None => { - // Means it is a single zarr group with no sub-groups - println!("Group Path: {:?}", group.path()); - panic!( - "Failed to list child groups of Zarr group at {:?}", - group_path - ); - } + None => Ok(FileGroup::new(vec![])), } } diff --git a/beacon-formats/src/zarr/opener.rs b/beacon-formats/src/zarr/opener.rs index 4f56e92f..22261ac6 100644 --- a/beacon-formats/src/zarr/opener.rs +++ b/beacon-formats/src/zarr/opener.rs @@ -1,16 +1,18 @@ use std::{collections::HashMap, pin::Pin, sync::Arc}; -use arrow::error::ArrowError; +use arrow::{datatypes::SchemaRef, error::ArrowError}; use beacon_arrow_zarr::{ array_slice_pushdown::ArraySlicePushDown, reader::AsyncArrowZarrGroupReader, stream::ArrowZarrStream, }; use datafusion::{ + common::pruning::PrunableStatistics, datasource::{ listing::PartitionedFile, - physical_plan::{FileMeta, FileOpenFuture, FileOpener}, + physical_plan::{FileMeta, FileOpenFuture, FileOpener, ParquetSource}, schema_adapter::SchemaAdapter, }, + physical_optimizer::pruning::{FilePruner, PruningPredicate}, physical_plan::PhysicalExpr, }; use futures::{FutureExt, StreamExt}; @@ -65,6 +67,8 @@ use crate::zarr::{ /// diagnosis. /// - IO and heavy work are deferred until the returned future is awaited/polled. pub struct ZarrFileOpener { + pub table_schema: SchemaRef, + /// Underlying object store for accessing Zarr data. pub zarr_object_store: Arc>>, /// Schema adapter for mapping Zarr schema to Arrow schema. pub schema_adapter: Arc, @@ -180,17 +184,29 @@ impl FileOpener for ZarrFileOpener { fn open( &self, file_meta: FileMeta, - _file: PartitionedFile, + file: PartitionedFile, ) -> datafusion::error::Result { - // Parse file meta as ZarrFile + if let (Some(statistics), Some(predicate)) = (file.statistics, self.predicate.as_ref()) { + let prunable_stats = + PrunableStatistics::new(vec![statistics.clone()], self.table_schema.clone()); + let pruning_predicate = + PruningPredicate::try_new(predicate.clone(), self.table_schema.clone())?; - let zarr_path = - ZarrPath::new_from_object_meta(file_meta.object_meta.clone()).map_err(|e| { - datafusion::error::DataFusionError::Execution(format!( - "Failed to create ZarrPath from ObjectMeta: {}", - e - )) - })?; + let pruning_result = pruning_predicate.prune(&prunable_stats)?; + + // If the file can be pruned, return an empty stream + if pruning_result.iter().all(|&v| !v) { + let empty_stream = futures::stream::empty().boxed(); + return Ok(Box::pin(async move { Ok(empty_stream) })); + } + } + + let zarr_path = ZarrPath::new_from_object_meta(file.object_meta.clone()).map_err(|e| { + datafusion::error::DataFusionError::Execution(format!( + "Failed to create ZarrPath from ObjectMeta: {}", + e + )) + })?; let zarr_object_store = self.zarr_object_store.clone(); let adapter = self.schema_adapter.clone(); @@ -212,6 +228,8 @@ impl FileOpener for ZarrFileOpener { // Check if the pruning predicate references any arrays in the pushdown statistics let fut = async move { + // Determine based on statistics and predicate which arrays to pushdown + let partitioned_streams = stream_partition_share .get_or_try_init(|| async move { tracing::debug!("Opening file: {:?}", file_meta.object_meta.location); diff --git a/beacon-formats/src/zarr/pushdown_statistics.rs b/beacon-formats/src/zarr/pushdown_statistics.rs index 7f0e2baf..9d932e32 100644 --- a/beacon-formats/src/zarr/pushdown_statistics.rs +++ b/beacon-formats/src/zarr/pushdown_statistics.rs @@ -38,7 +38,7 @@ pub async fn generate_statistics_from_zarr_group( if let Ok(Some(array)) = zarr_reader.read_array_full(field.name()).await { // Compute statistics for the array if let Some(array_stats) = compute_statistics_for_array(&array) { - statistics = statistics.add_column_statistics(array_stats); + statistics = statistics.add_column_statistics(array_stats.clone()); } else { statistics = statistics.add_column_statistics(ColumnStatistics::new_unknown()); } diff --git a/beacon-formats/src/zarr/source.rs b/beacon-formats/src/zarr/source.rs index b43e8311..e77ce454 100644 --- a/beacon-formats/src/zarr/source.rs +++ b/beacon-formats/src/zarr/source.rs @@ -149,10 +149,11 @@ impl FileSource for ZarrSource { .schema_adapter_factory .clone() .unwrap_or_else(|| Arc::new(DefaultSchemaAdapterFactory)); - let schema_adapter = schema_adapter_factory.create(projected_schema, table_schema); + let schema_adapter = schema_adapter_factory.create(projected_schema, table_schema.clone()); let arc_schema_adapter: Arc = Arc::from(schema_adapter); Arc::new(ZarrFileOpener { + table_schema, zarr_object_store: Arc::new(AsyncObjectStore::new(object_store)), schema_adapter: arc_schema_adapter, stream_partition_shares: self.stream_partition_shares.clone(), diff --git a/beacon-formats/src/zarr/util.rs b/beacon-formats/src/zarr/util.rs index 3053451d..d7f4f922 100644 --- a/beacon-formats/src/zarr/util.rs +++ b/beacon-formats/src/zarr/util.rs @@ -26,6 +26,16 @@ impl ZarrPath { format!("/{}", p_str) } + pub fn as_zarr_json_path(&self) -> String { + match self { + ZarrPath::ObjectMeta(meta) => meta.location.as_ref().to_string(), + ZarrPath::DirPath(path) => { + let zarr_json_path = path.child("zarr.json"); + zarr_json_path.as_ref().to_string() + } + } + } + pub fn new_from_object_meta(meta: object_store::ObjectMeta) -> Result { // Ensure this is a zarr.json file? if !is_zarr_v3_metadata(&meta) {