Skip to content

Commit 26e86ac

Browse files
authored
fix: AsyncIcechunkStore::list_dir() prefix appending (#9)
1 parent 10f4085 commit 26e86ac

File tree

2 files changed

+195
-3
lines changed

2 files changed

+195
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Changed
1111
- Bump MSRV to 1.89 in alignment with `icechunk` 0.3.10
1212

13+
### Fixed
14+
- Fix `AsyncIcechunkStore::list_dir()` which returned keys/prefixes without the parent prefix
15+
1316
## [0.4.0] - 2025-09-19
1417

1518
### Changed

src/lib.rs

Lines changed: 192 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,14 @@ impl AsyncListableStorageTraits for AsyncIcechunkStore {
309309
.map(|item| {
310310
match item? {
311311
icechunk::store::ListDirItem::Key(key) => {
312-
keys.push(StoreKey::new(&key)?);
312+
keys.push(StoreKey::new(format!("{}{}", prefix.as_str(), &key))?);
313313
}
314-
icechunk::store::ListDirItem::Prefix(prefix) => {
315-
prefixes.push(StorePrefix::new(prefix + "/")?);
314+
icechunk::store::ListDirItem::Prefix(prefix_inner) => {
315+
prefixes.push(StorePrefix::new(format!(
316+
"{}{}/",
317+
prefix.as_str(),
318+
&prefix_inner
319+
))?);
316320
}
317321
}
318322
Ok::<_, StorageError>(())
@@ -426,4 +430,189 @@ mod tests {
426430

427431
Ok(())
428432
}
433+
434+
#[tokio::test]
435+
async fn list_dir_and_list_prefix_nested() -> Result<(), Box<dyn Error>> {
436+
// Create an icechunk repository with a deeply nested zarr hierarchy
437+
let storage = icechunk::new_in_memory_storage().await?;
438+
let config = RepositoryConfig::default();
439+
let repo = Repository::create(Some(config), storage, HashMap::new()).await?;
440+
let store = AsyncIcechunkStore::new(repo.writable_session("main").await?);
441+
442+
let group_json = r#"{"zarr_format":3,"node_type":"group"}"#;
443+
let array_json = r#"{"zarr_format":3,"node_type":"array","shape":[10, 10],"data_type":"int32","chunk_grid":{"name":"regular","configuration":{"chunk_shape":[5, 5]}},"chunk_key_encoding":{"name":"default","configuration":{"separator":"/"}},"fill_value":0,"codecs":[{"name":"bytes","configuration":{"endian":"little"}}]}"#;
444+
445+
// Create a deeply nested hierarchy:
446+
// root/
447+
// zarr.json
448+
// group0/
449+
// zarr.json
450+
// group1/
451+
// zarr.json
452+
// array0/
453+
// zarr.json
454+
// c/0/0
455+
// c/0/1
456+
// array1/
457+
// zarr.json
458+
// c/0/0
459+
460+
// Create groups
461+
store
462+
.set(&StoreKey::new("zarr.json")?, group_json.into())
463+
.await?;
464+
store
465+
.set(&StoreKey::new("group0/zarr.json")?, group_json.into())
466+
.await?;
467+
store
468+
.set(
469+
&StoreKey::new("group0/group1/zarr.json")?,
470+
group_json.into(),
471+
)
472+
.await?;
473+
474+
// Create arrays
475+
store
476+
.set(
477+
&StoreKey::new("group0/group1/array0/zarr.json")?,
478+
array_json.into(),
479+
)
480+
.await?;
481+
store
482+
.set(
483+
&StoreKey::new("group0/group1/array0/c/0/0")?,
484+
vec![0u8; 20].into(),
485+
)
486+
.await?;
487+
store
488+
.set(
489+
&StoreKey::new("group0/group1/array0/c/0/1")?,
490+
vec![1u8; 20].into(),
491+
)
492+
.await?;
493+
494+
store
495+
.set(
496+
&StoreKey::new("group0/group1/array1/zarr.json")?,
497+
array_json.into(),
498+
)
499+
.await?;
500+
store
501+
.set(
502+
&StoreKey::new("group0/group1/array1/c/0/0")?,
503+
vec![2u8; 20].into(),
504+
)
505+
.await?;
506+
507+
// Commit the data
508+
store
509+
.session()
510+
.write()
511+
.await
512+
.commit("Create nested hierarchy", None)
513+
.await?;
514+
515+
// Test list_dir at root
516+
let root_items = store.list_dir(&StorePrefix::root()).await?;
517+
assert_eq!(root_items.keys().len(), 1); // zarr.json
518+
assert_eq!(root_items.prefixes().len(), 1); // group0/
519+
assert!(root_items.keys().contains(&StoreKey::new("zarr.json")?));
520+
assert!(root_items
521+
.prefixes()
522+
.contains(&StorePrefix::new("group0/")?));
523+
524+
// Test list_dir at group0
525+
let group0_items = store.list_dir(&StorePrefix::new("group0/")?).await?;
526+
assert_eq!(group0_items.keys().len(), 1); // zarr.json
527+
assert_eq!(group0_items.prefixes().len(), 1); // group1/
528+
assert!(group0_items
529+
.keys()
530+
.contains(&StoreKey::new("group0/zarr.json")?));
531+
assert!(group0_items
532+
.prefixes()
533+
.contains(&StorePrefix::new("group0/group1/")?));
534+
535+
// Test list_dir at group1
536+
let group1_items = store.list_dir(&StorePrefix::new("group0/group1/")?).await?;
537+
assert_eq!(group1_items.keys().len(), 1); // zarr.json
538+
assert_eq!(group1_items.prefixes().len(), 2); // array0/, array1/
539+
assert!(group1_items
540+
.keys()
541+
.contains(&StoreKey::new("group0/group1/zarr.json")?));
542+
assert!(group1_items
543+
.prefixes()
544+
.contains(&StorePrefix::new("group0/group1/array0/")?));
545+
assert!(group1_items
546+
.prefixes()
547+
.contains(&StorePrefix::new("group0/group1/array1/")?));
548+
549+
// Test list_dir inside array0
550+
let array0_items = store
551+
.list_dir(&StorePrefix::new("group0/group1/array0/")?)
552+
.await?;
553+
assert_eq!(array0_items.keys().len(), 1); // zarr.json
554+
assert_eq!(array0_items.prefixes().len(), 1); // c/
555+
assert!(array0_items
556+
.keys()
557+
.contains(&StoreKey::new("group0/group1/array0/zarr.json")?));
558+
assert!(array0_items
559+
.prefixes()
560+
.contains(&StorePrefix::new("group0/group1/array0/c/")?));
561+
562+
// Test list_dir inside array0/c/ directory
563+
let array0_c_items = store
564+
.list_dir(&StorePrefix::new("group0/group1/array0/c/")?)
565+
.await?;
566+
assert_eq!(array0_c_items.keys().len(), 0); // no direct keys
567+
assert_eq!(array0_c_items.prefixes().len(), 1); // 0/
568+
assert!(array0_c_items
569+
.prefixes()
570+
.contains(&StorePrefix::new("group0/group1/array0/c/0/")?));
571+
572+
// Test list_dir inside array0/c/0/ directory
573+
let array0_c0_items = store
574+
.list_dir(&StorePrefix::new("group0/group1/array0/c/0/")?)
575+
.await?;
576+
assert_eq!(array0_c0_items.keys().len(), 2); // 0, 1
577+
assert_eq!(array0_c0_items.prefixes().len(), 0); // no subdirectories
578+
assert!(array0_c0_items
579+
.keys()
580+
.contains(&StoreKey::new("group0/group1/array0/c/0/0")?));
581+
assert!(array0_c0_items
582+
.keys()
583+
.contains(&StoreKey::new("group0/group1/array0/c/0/1")?));
584+
585+
// Test list_prefix at root (should get all keys recursively)
586+
let all_keys = store.list_prefix(&StorePrefix::root()).await?;
587+
assert_eq!(all_keys.len(), 8); // Total of 8 keys in the hierarchy
588+
589+
// Test list_prefix at group0
590+
let group0_keys = store.list_prefix(&StorePrefix::new("group0/")?).await?;
591+
assert_eq!(group0_keys.len(), 7); // All keys under group0/
592+
593+
// Test list_prefix at group1
594+
let group1_keys = store
595+
.list_prefix(&StorePrefix::new("group0/group1/")?)
596+
.await?;
597+
assert_eq!(group1_keys.len(), 6); // zarr.json + 2 arrays with their chunks
598+
599+
// Test list_prefix for array0 (should get all chunks recursively)
600+
let array0_keys = store
601+
.list_prefix(&StorePrefix::new("group0/group1/array0/")?)
602+
.await?;
603+
assert_eq!(array0_keys.len(), 3); // zarr.json + 2 chunks
604+
assert!(array0_keys.contains(&StoreKey::new("group0/group1/array0/zarr.json")?));
605+
assert!(array0_keys.contains(&StoreKey::new("group0/group1/array0/c/0/0")?));
606+
assert!(array0_keys.contains(&StoreKey::new("group0/group1/array0/c/0/1")?));
607+
608+
// Test list_prefix for array1 (should get all chunks recursively)
609+
let array1_keys = store
610+
.list_prefix(&StorePrefix::new("group0/group1/array1/")?)
611+
.await?;
612+
assert_eq!(array1_keys.len(), 2); // zarr.json + 1 chunk
613+
assert!(array1_keys.contains(&StoreKey::new("group0/group1/array1/zarr.json")?));
614+
assert!(array1_keys.contains(&StoreKey::new("group0/group1/array1/c/0/0")?));
615+
616+
Ok(())
617+
}
429618
}

0 commit comments

Comments
 (0)