icechunk 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. {icechunk-0.2.0 → icechunk-0.2.2}/PKG-INFO +1 -1
  2. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/Cargo.toml +1 -1
  3. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/examples/multithreaded_store.rs +1 -0
  4. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/repository.rs +28 -7
  5. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/storage/mod.rs +47 -1
  6. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/storage/object_store.rs +55 -59
  7. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/storage/s3.rs +64 -31
  8. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/tests/test_storage.rs +53 -1
  9. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/tests/test_virtual_refs.rs +1 -0
  10. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/Cargo.toml +2 -2
  11. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/demo-dummy-data.ipynb +2 -2
  12. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/version-control.ipynb +2 -2
  13. {icechunk-0.2.0 → icechunk-0.2.2/icechunk-python}/python/icechunk/__init__.py +2 -0
  14. {icechunk-0.2.0 → icechunk-0.2.2/icechunk-python}/python/icechunk/_icechunk_python.pyi +40 -6
  15. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/repository.py +25 -15
  16. {icechunk-0.2.0 → icechunk-0.2.2/icechunk-python}/python/icechunk/storage.py +5 -0
  17. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/store.py +1 -1
  18. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/src/config.rs +36 -15
  19. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/src/repository.rs +27 -15
  20. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_can_read_old.py +2 -2
  21. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_config.py +1 -1
  22. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_credentials.py +1 -1
  23. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_stateful_repo_ops.py +1 -1
  24. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_timetravel.py +42 -7
  25. {icechunk-0.2.0/icechunk-python → icechunk-0.2.2}/python/icechunk/__init__.py +2 -0
  26. {icechunk-0.2.0/icechunk-python → icechunk-0.2.2}/python/icechunk/_icechunk_python.pyi +40 -6
  27. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/repository.py +25 -15
  28. {icechunk-0.2.0/icechunk-python → icechunk-0.2.2}/python/icechunk/storage.py +5 -0
  29. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/store.py +1 -1
  30. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/examples/low_level_dataset.rs +0 -0
  31. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/examples/multithreaded_get_chunk_refs.rs +0 -0
  32. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/flatbuffers/all.fbs +0 -0
  33. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/flatbuffers/manifest.fbs +0 -0
  34. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/flatbuffers/object_ids.fbs +0 -0
  35. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/flatbuffers/snapshot.fbs +0 -0
  36. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/flatbuffers/transaction_log.fbs +0 -0
  37. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/proptest-regressions/dataset.txt +0 -0
  38. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/proptest-regressions/format/manifest.txt +0 -0
  39. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/proptest-regressions/manifest.txt +0 -0
  40. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/proptest-regressions/session.txt +0 -0
  41. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/proptest-regressions/storage/mod.txt +0 -0
  42. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/proptest-regressions/storage/s3.txt +0 -0
  43. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/proptest-regressions/storage/virtual_ref.txt +0 -0
  44. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/asset_manager.rs +0 -0
  45. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/change_set.rs +0 -0
  46. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/config.rs +0 -0
  47. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/conflicts/basic_solver.rs +0 -0
  48. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/conflicts/detector.rs +0 -0
  49. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/conflicts/mod.rs +0 -0
  50. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/error.rs +0 -0
  51. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/format/attributes.rs +0 -0
  52. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/format/flatbuffers/all_generated.rs +0 -0
  53. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/format/manifest.rs +0 -0
  54. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/format/mod.rs +0 -0
  55. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/format/serializers/mod.rs +0 -0
  56. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/format/snapshot.rs +0 -0
  57. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/format/transaction_log.rs +0 -0
  58. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/lib.rs +0 -0
  59. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/ops/gc.rs +0 -0
  60. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/ops/mod.rs +0 -0
  61. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/refs.rs +0 -0
  62. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/session.rs +0 -0
  63. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/storage/logging.rs +0 -0
  64. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/store.rs +0 -0
  65. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/strategies.rs +0 -0
  66. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/src/virtual_chunks.rs +0 -0
  67. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/tests/test_concurrency.rs +0 -0
  68. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/tests/test_distributed_writes.rs +0 -0
  69. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk/tests/test_gc.rs +0 -0
  70. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/.gitignore +0 -0
  71. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/Cargo.lock +0 -0
  72. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/__init__.py +0 -0
  73. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/coiled_runner.py +0 -0
  74. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/conftest.py +0 -0
  75. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/create_era5.py +0 -0
  76. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/datasets.py +0 -0
  77. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/envs/icechunk-alpha-12.txt +0 -0
  78. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/envs/icechunk-alpha-release.txt +0 -0
  79. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/helpers.py +0 -0
  80. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/lib.py +0 -0
  81. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/most_recent.sh +0 -0
  82. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/runner.py +0 -0
  83. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/tasks.py +0 -0
  84. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/test_benchmark_reads.py +0 -0
  85. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/benchmarks/test_benchmark_writes.py +0 -0
  86. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/examples/dask_write.py +0 -0
  87. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/conflict.ipynb +0 -0
  88. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/demo-azure-blob.ipynb +0 -0
  89. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/demo-gcs.ipynb +0 -0
  90. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/demo-s3.ipynb +0 -0
  91. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/environment.yml +0 -0
  92. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/memorystore.ipynb +0 -0
  93. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/performance/era5_xarray-Icechunk.ipynb +0 -0
  94. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/performance/era5_xarray-zarr2.ipynb +0 -0
  95. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/notebooks/performance/era5_xarray-zarr3.ipynb +0 -0
  96. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/credentials.py +0 -0
  97. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/dask.py +0 -0
  98. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/distributed.py +0 -0
  99. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/py.typed +0 -0
  100. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/session.py +0 -0
  101. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/vendor/__init__.py +0 -0
  102. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/vendor/xarray.py +0 -0
  103. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/python/icechunk/xarray.py +0 -0
  104. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/src/conflicts.rs +0 -0
  105. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/src/errors.rs +0 -0
  106. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/src/lib.rs +0 -0
  107. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/src/session.rs +0 -0
  108. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/src/store.rs +0 -0
  109. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/src/streams.rs +0 -0
  110. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/__init__.py +0 -0
  111. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/conftest.py +0 -0
  112. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/chunks/09HEW2P03CSMHFAZY7DG +0 -0
  113. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/chunks/52H0E4NSPN8SVRK9EVGG +0 -0
  114. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/chunks/DWQ75SDC624XF9H326RG +0 -0
  115. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/chunks/RW938N1KP2R4BHMW62QG +0 -0
  116. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/config.yaml +0 -0
  117. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/manifests/CMYVHDWMSTG9R25780YG +0 -0
  118. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/manifests/G3W2W8V6ZG09J6C21WE0 +0 -0
  119. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/manifests/Q04J7QW5RQ8D17TPA10G +0 -0
  120. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/manifests/SHTEAP8C784YMZSJKBM0 +0 -0
  121. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/refs/branch.main/ref.json +0 -0
  122. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/refs/branch.my-branch/ref.json +0 -0
  123. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/refs/tag.deleted/ref.json +0 -0
  124. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/refs/tag.deleted/ref.json.deleted +0 -0
  125. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/refs/tag.it also works!/ref.json +0 -0
  126. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/refs/tag.it works!/ref.json +0 -0
  127. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/snapshots/4QF8JA0YPDN51MHSSYVG +0 -0
  128. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/snapshots/7XAF0Q905SH4938DN9CG +0 -0
  129. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/snapshots/GC4YVH5SKBPEZCENYQE0 +0 -0
  130. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/snapshots/NXH3M0HJ7EEJ0699DPP0 +0 -0
  131. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/snapshots/P874YS3J196959RDHX7G +0 -0
  132. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/snapshots/XDZ162T1TYBEJMK99NPG +0 -0
  133. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/transactions/4QF8JA0YPDN51MHSSYVG +0 -0
  134. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/transactions/7XAF0Q905SH4938DN9CG +0 -0
  135. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/transactions/GC4YVH5SKBPEZCENYQE0 +0 -0
  136. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/transactions/NXH3M0HJ7EEJ0699DPP0 +0 -0
  137. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/data/test-repo/transactions/XDZ162T1TYBEJMK99NPG +0 -0
  138. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/run_xarray_backends_tests.py +0 -0
  139. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_commit_properties.py +0 -0
  140. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_concurrency.py +0 -0
  141. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_conflicts.py +0 -0
  142. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_dask.py +0 -0
  143. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_distributed_writers.py +0 -0
  144. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_error.py +0 -0
  145. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_gc.py +0 -0
  146. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_pickle.py +0 -0
  147. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_regressions.py +0 -0
  148. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_store.py +0 -0
  149. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_virtual_ref.py +0 -0
  150. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_xarray.py +0 -0
  151. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_zarr/__init__.py +0 -0
  152. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_zarr/test_array.py +0 -0
  153. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_zarr/test_group.py +0 -0
  154. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_zarr/test_properties.py +0 -0
  155. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_zarr/test_stateful.py +0 -0
  156. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_zarr/test_store/__init__.py +0 -0
  157. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_zarr/test_store/test_core.py +0 -0
  158. {icechunk-0.2.0 → icechunk-0.2.2}/icechunk-python/tests/test_zarr/test_store/test_icechunk_store.py +0 -0
  159. {icechunk-0.2.0 → icechunk-0.2.2}/pyproject.toml +0 -0
  160. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/credentials.py +0 -0
  161. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/dask.py +0 -0
  162. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/distributed.py +0 -0
  163. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/py.typed +0 -0
  164. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/session.py +0 -0
  165. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/vendor/__init__.py +0 -0
  166. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/vendor/xarray.py +0 -0
  167. {icechunk-0.2.0 → icechunk-0.2.2}/python/icechunk/xarray.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: icechunk
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "icechunk"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "Transactional storage engine for Zarr designed for use on cloud object storage"
5
5
  readme = "README.md"
6
6
  repository = "https://github.com/earth-mover/icechunk"
@@ -54,6 +54,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
54
54
  async fn writer(name: &str, range: Range<u64>, store: &Store) {
55
55
  println!("Starting writer {name}.");
56
56
  for i in range {
57
+ #[allow(clippy::dbg_macro)]
57
58
  if let Err(err) = store
58
59
  .set(
59
60
  format!("array/c/{i}").as_str(),
@@ -5,7 +5,9 @@ use std::{
5
5
  sync::Arc,
6
6
  };
7
7
 
8
+ use async_recursion::async_recursion;
8
9
  use bytes::Bytes;
10
+ use chrono::{DateTime, Utc};
9
11
  use err_into::ErrorInto as _;
10
12
  use futures::{
11
13
  stream::{FuturesOrdered, FuturesUnordered},
@@ -37,15 +39,13 @@ use crate::{
37
39
  Storage, StorageError,
38
40
  };
39
41
 
40
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42
+ #[derive(Debug, Clone, PartialEq, Eq)]
41
43
  #[non_exhaustive]
42
44
  pub enum VersionInfo {
43
- #[serde(rename = "snapshot_id")]
44
45
  SnapshotId(SnapshotId),
45
- #[serde(rename = "tag")]
46
46
  TagRef(String),
47
- #[serde(rename = "branch")]
48
47
  BranchTipRef(String),
48
+ AsOf { branch: String, at: DateTime<Utc> },
49
49
  }
50
50
 
51
51
  #[derive(Debug, Error)]
@@ -60,6 +60,8 @@ pub enum RepositoryErrorKind {
60
60
 
61
61
  #[error("snapshot not found: `{id}`")]
62
62
  SnapshotNotFound { id: SnapshotId },
63
+ #[error("branch {branch} does not have a snapshots before or at {at}")]
64
+ InvalidAsOfSpec { branch: String, at: DateTime<Utc> },
63
65
  #[error("invalid snapshot id: `{0}`")]
64
66
  InvalidSnapshotId(String),
65
67
  #[error("tag error: `{0}`")]
@@ -404,11 +406,12 @@ impl Repository {
404
406
  }
405
407
 
406
408
  /// Returns the sequence of parents of the snapshot pointed by the given version
409
+ #[async_recursion(?Send)]
407
410
  #[instrument(skip(self))]
408
- pub async fn ancestry(
409
- &self,
411
+ pub async fn ancestry<'a>(
412
+ &'a self,
410
413
  version: &VersionInfo,
411
- ) -> RepositoryResult<impl Stream<Item = RepositoryResult<SnapshotInfo>> + '_> {
414
+ ) -> RepositoryResult<impl Stream<Item = RepositoryResult<SnapshotInfo>> + 'a> {
412
415
  let snapshot_id = self.resolve_version(version).await?;
413
416
  self.snapshot_ancestry(&snapshot_id).await
414
417
  }
@@ -572,6 +575,24 @@ impl Repository {
572
575
  .await?;
573
576
  Ok(ref_data.snapshot)
574
577
  }
578
+ VersionInfo::AsOf { branch, at } => {
579
+ let tip = VersionInfo::BranchTipRef(branch.clone());
580
+ let snap = self
581
+ .ancestry(&tip)
582
+ .await?
583
+ .try_skip_while(|parent| ready(Ok(&parent.flushed_at > at)))
584
+ .take(1)
585
+ .try_collect::<Vec<_>>()
586
+ .await?;
587
+ match snap.into_iter().next() {
588
+ Some(snap) => Ok(snap.id),
589
+ None => Err(RepositoryErrorKind::InvalidAsOfSpec {
590
+ branch: branch.clone(),
591
+ at: *at,
592
+ }
593
+ .into()),
594
+ }
595
+ }
575
596
  }
576
597
  }
577
598
 
@@ -174,6 +174,9 @@ impl ConcurrencySettings {
174
174
  #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default)]
175
175
  pub struct Settings {
176
176
  pub concurrency: Option<ConcurrencySettings>,
177
+ pub unsafe_use_conditional_update: Option<bool>,
178
+ pub unsafe_use_conditional_create: Option<bool>,
179
+ pub unsafe_use_metadata: Option<bool>,
177
180
  }
178
181
 
179
182
  static DEFAULT_CONCURRENCY: OnceLock<ConcurrencySettings> = OnceLock::new();
@@ -185,6 +188,18 @@ impl Settings {
185
188
  .unwrap_or_else(|| DEFAULT_CONCURRENCY.get_or_init(Default::default))
186
189
  }
187
190
 
191
+ pub fn unsafe_use_conditional_create(&self) -> bool {
192
+ self.unsafe_use_conditional_create.unwrap_or(true)
193
+ }
194
+
195
+ pub fn unsafe_use_conditional_update(&self) -> bool {
196
+ self.unsafe_use_conditional_update.unwrap_or(true)
197
+ }
198
+
199
+ pub fn unsafe_use_metadata(&self) -> bool {
200
+ self.unsafe_use_metadata.unwrap_or(true)
201
+ }
202
+
188
203
  pub fn merge(&self, other: Self) -> Self {
189
204
  Self {
190
205
  concurrency: match (&self.concurrency, other.concurrency) {
@@ -193,6 +208,33 @@ impl Settings {
193
208
  (Some(c), None) => Some(c.clone()),
194
209
  (Some(mine), Some(theirs)) => Some(mine.merge(theirs)),
195
210
  },
211
+ unsafe_use_conditional_create: match (
212
+ &self.unsafe_use_conditional_create,
213
+ other.unsafe_use_conditional_create,
214
+ ) {
215
+ (None, None) => None,
216
+ (None, Some(c)) => Some(c),
217
+ (Some(c), None) => Some(*c),
218
+ (Some(_), Some(theirs)) => Some(theirs),
219
+ },
220
+ unsafe_use_conditional_update: match (
221
+ &self.unsafe_use_conditional_update,
222
+ other.unsafe_use_conditional_update,
223
+ ) {
224
+ (None, None) => None,
225
+ (None, Some(c)) => Some(c),
226
+ (Some(c), None) => Some(*c),
227
+ (Some(_), Some(theirs)) => Some(theirs),
228
+ },
229
+ unsafe_use_metadata: match (
230
+ &self.unsafe_use_metadata,
231
+ other.unsafe_use_metadata,
232
+ ) {
233
+ (None, None) => None,
234
+ (None, Some(c)) => Some(c),
235
+ (Some(c), None) => Some(*c),
236
+ (Some(_), Some(theirs)) => Some(theirs),
237
+ },
196
238
  }
197
239
  }
198
240
  }
@@ -364,7 +406,11 @@ pub trait Storage: fmt::Debug + private::Sealed + Sync + Send {
364
406
  ) -> StorageResult<DateTime<Utc>>;
365
407
 
366
408
  async fn root_is_clean(&self) -> StorageResult<bool> {
367
- Ok(self.list_objects(&Settings::default(), "").await?.next().await.is_none())
409
+ match self.list_objects(&Settings::default(), "").await?.next().await {
410
+ None => Ok(true),
411
+ Some(Ok(_)) => Ok(false),
412
+ Some(Err(err)) => Err(err),
413
+ }
368
414
  }
369
415
 
370
416
  async fn list_chunks(
@@ -158,16 +158,6 @@ impl ObjectStorage {
158
158
  self.backend.artificially_sort_refs_in_mem()
159
159
  }
160
160
 
161
- /// We need this because object_store's local file implementation doesn't support metadata.
162
- pub fn supports_metadata(&self) -> bool {
163
- self.backend.supports_metadata()
164
- }
165
-
166
- /// We need this because object_store's local file implementation doesn't support it
167
- pub fn supports_conditional_put_updates(&self) -> bool {
168
- self.backend.supports_conditional_put_updates()
169
- }
170
-
171
161
  /// Return all keys in the store
172
162
  ///
173
163
  /// Intended for testing and debugging purposes only.
@@ -251,8 +241,12 @@ impl ObjectStorage {
251
241
  .compat())
252
242
  }
253
243
 
254
- fn metadata_to_attributes(&self, metadata: Vec<(String, String)>) -> Attributes {
255
- if self.supports_metadata() {
244
+ fn metadata_to_attributes(
245
+ &self,
246
+ settings: &Settings,
247
+ metadata: Vec<(String, String)>,
248
+ ) -> Attributes {
249
+ if settings.unsafe_use_metadata() {
256
250
  Attributes::from_iter(metadata.into_iter().map(|(key, val)| {
257
251
  (
258
252
  Attribute::Metadata(std::borrow::Cow::Owned(key)),
@@ -270,18 +264,24 @@ impl ObjectStorage {
270
264
  Some(parent.as_ref().to_string())
271
265
  }
272
266
 
273
- fn get_put_mode(&self, previous_version: &VersionInfo) -> PutMode {
274
- let degrade_to_overwrite =
275
- !previous_version.is_create() && !self.supports_conditional_put_updates();
276
- if degrade_to_overwrite {
277
- PutMode::Overwrite
278
- } else if previous_version.is_create() {
279
- PutMode::Create
280
- } else {
281
- PutMode::Update(UpdateVersion {
267
+ fn get_put_mode(
268
+ &self,
269
+ settings: &Settings,
270
+ previous_version: &VersionInfo,
271
+ ) -> PutMode {
272
+ match (
273
+ previous_version.is_create(),
274
+ settings.unsafe_use_conditional_create(),
275
+ settings.unsafe_use_conditional_update(),
276
+ ) {
277
+ (true, true, _) => PutMode::Create,
278
+ (true, false, _) => PutMode::Overwrite,
279
+
280
+ (false, _, true) => PutMode::Update(UpdateVersion {
282
281
  e_tag: previous_version.etag().cloned(),
283
282
  version: previous_version.generation().cloned(),
284
- })
283
+ }),
284
+ (false, _, false) => PutMode::Overwrite,
285
285
  }
286
286
  }
287
287
  }
@@ -317,15 +317,15 @@ impl Storage for ObjectStorage {
317
317
  Err(err) => Err(err.into()),
318
318
  }
319
319
  }
320
- #[instrument(skip(self, _settings, config))]
320
+ #[instrument(skip(self, settings, config))]
321
321
  async fn update_config(
322
322
  &self,
323
- _settings: &Settings,
323
+ settings: &Settings,
324
324
  config: Bytes,
325
325
  previous_version: &VersionInfo,
326
326
  ) -> StorageResult<UpdateConfigResult> {
327
327
  let path = self.get_config_path();
328
- let attributes = if self.supports_metadata() {
328
+ let attributes = if settings.unsafe_use_metadata() {
329
329
  Attributes::from_iter(vec![(
330
330
  Attribute::ContentType,
331
331
  AttributeValue::from("application/yaml"),
@@ -334,7 +334,7 @@ impl Storage for ObjectStorage {
334
334
  Attributes::new()
335
335
  };
336
336
 
337
- let mode = self.get_put_mode(previous_version);
337
+ let mode = self.get_put_mode(settings, previous_version);
338
338
 
339
339
  let options = PutOptions { mode, attributes, ..PutOptions::default() };
340
340
  let res = self.get_client().await.put_opts(&path, config.into(), options).await;
@@ -394,48 +394,48 @@ impl Storage for ObjectStorage {
394
394
  Ok(Box::new(self.get_object_reader(settings, &path).await?))
395
395
  }
396
396
 
397
- #[instrument(skip(self, _settings, metadata, bytes))]
397
+ #[instrument(skip(self, settings, metadata, bytes))]
398
398
  async fn write_snapshot(
399
399
  &self,
400
- _settings: &Settings,
400
+ settings: &Settings,
401
401
  id: SnapshotId,
402
402
  metadata: Vec<(String, String)>,
403
403
  bytes: Bytes,
404
404
  ) -> StorageResult<()> {
405
405
  let path = self.get_snapshot_path(&id);
406
- let attributes = self.metadata_to_attributes(metadata);
406
+ let attributes = self.metadata_to_attributes(settings, metadata);
407
407
  let options = PutOptions { attributes, ..PutOptions::default() };
408
408
  // FIXME: use multipart
409
409
  self.get_client().await.put_opts(&path, bytes.into(), options).await?;
410
410
  Ok(())
411
411
  }
412
412
 
413
- #[instrument(skip(self, _settings, metadata, bytes))]
413
+ #[instrument(skip(self, settings, metadata, bytes))]
414
414
  async fn write_manifest(
415
415
  &self,
416
- _settings: &Settings,
416
+ settings: &Settings,
417
417
  id: ManifestId,
418
418
  metadata: Vec<(String, String)>,
419
419
  bytes: Bytes,
420
420
  ) -> StorageResult<()> {
421
421
  let path = self.get_manifest_path(&id);
422
- let attributes = self.metadata_to_attributes(metadata);
422
+ let attributes = self.metadata_to_attributes(settings, metadata);
423
423
  let options = PutOptions { attributes, ..PutOptions::default() };
424
424
  // FIXME: use multipart
425
425
  self.get_client().await.put_opts(&path, bytes.into(), options).await?;
426
426
  Ok(())
427
427
  }
428
428
 
429
- #[instrument(skip(self, _settings, metadata, bytes))]
429
+ #[instrument(skip(self, settings, metadata, bytes))]
430
430
  async fn write_transaction_log(
431
431
  &self,
432
- _settings: &Settings,
432
+ settings: &Settings,
433
433
  id: SnapshotId,
434
434
  metadata: Vec<(String, String)>,
435
435
  bytes: Bytes,
436
436
  ) -> StorageResult<()> {
437
437
  let path = self.get_transaction_path(&id);
438
- let attributes = self.metadata_to_attributes(metadata);
438
+ let attributes = self.metadata_to_attributes(settings, metadata);
439
439
  let options = PutOptions { attributes, ..PutOptions::default() };
440
440
  // FIXME: use multipart
441
441
  self.get_client().await.put_opts(&path, bytes.into(), options).await?;
@@ -506,16 +506,16 @@ impl Storage for ObjectStorage {
506
506
  .await?)
507
507
  }
508
508
 
509
- #[instrument(skip(self, _settings, bytes))]
509
+ #[instrument(skip(self, settings, bytes))]
510
510
  async fn write_ref(
511
511
  &self,
512
- _settings: &Settings,
512
+ settings: &Settings,
513
513
  ref_key: &str,
514
514
  bytes: Bytes,
515
515
  previous_version: &VersionInfo,
516
516
  ) -> StorageResult<WriteRefResult> {
517
517
  let key = self.ref_key(ref_key);
518
- let mode = self.get_put_mode(previous_version);
518
+ let mode = self.get_put_mode(settings, previous_version);
519
519
  let opts = PutOptions { mode, ..PutOptions::default() };
520
520
 
521
521
  match self
@@ -634,19 +634,7 @@ pub trait ObjectStoreBackend: Debug + Sync + Send {
634
634
  false
635
635
  }
636
636
 
637
- /// We need this because object_store's local file implementation doesn't support metadata.
638
- fn supports_metadata(&self) -> bool {
639
- true
640
- }
641
-
642
- /// We need this because object_store's local file implementation doesn't support it
643
- fn supports_conditional_put_updates(&self) -> bool {
644
- true
645
- }
646
-
647
- fn default_settings(&self) -> Settings {
648
- Settings::default()
649
- }
637
+ fn default_settings(&self) -> Settings;
650
638
  }
651
639
 
652
640
  #[derive(Debug, Serialize, Deserialize)]
@@ -674,6 +662,7 @@ impl ObjectStoreBackend for InMemoryObjectStoreBackend {
674
662
  NonZeroU64::new(1).unwrap_or(NonZeroU64::MIN),
675
663
  ),
676
664
  }),
665
+ ..Default::default()
677
666
  }
678
667
  }
679
668
  }
@@ -706,14 +695,6 @@ impl ObjectStoreBackend for LocalFileSystemObjectStoreBackend {
706
695
  true
707
696
  }
708
697
 
709
- fn supports_metadata(&self) -> bool {
710
- false
711
- }
712
-
713
- fn supports_conditional_put_updates(&self) -> bool {
714
- false
715
- }
716
-
717
698
  fn default_settings(&self) -> Settings {
718
699
  Settings {
719
700
  concurrency: Some(ConcurrencySettings {
@@ -724,6 +705,9 @@ impl ObjectStoreBackend for LocalFileSystemObjectStoreBackend {
724
705
  NonZeroU64::new(4 * 1024).unwrap_or(NonZeroU64::MIN),
725
706
  ),
726
707
  }),
708
+ unsafe_use_conditional_update: Some(false),
709
+ unsafe_use_metadata: Some(false),
710
+ ..Default::default()
727
711
  }
728
712
  }
729
713
  }
@@ -795,6 +779,10 @@ impl ObjectStoreBackend for S3ObjectStoreBackend {
795
779
  fn prefix(&self) -> String {
796
780
  self.prefix.clone().unwrap_or("".to_string())
797
781
  }
782
+
783
+ fn default_settings(&self) -> Settings {
784
+ Default::default()
785
+ }
798
786
  }
799
787
 
800
788
  #[derive(Debug, Serialize, Deserialize)]
@@ -844,6 +832,10 @@ impl ObjectStoreBackend for AzureObjectStoreBackend {
844
832
  fn prefix(&self) -> String {
845
833
  self.prefix.clone().unwrap_or("".to_string())
846
834
  }
835
+
836
+ fn default_settings(&self) -> Settings {
837
+ Default::default()
838
+ }
847
839
  }
848
840
 
849
841
  #[derive(Debug, Serialize, Deserialize)]
@@ -910,6 +902,10 @@ impl ObjectStoreBackend for GcsObjectStoreBackend {
910
902
  fn prefix(&self) -> String {
911
903
  self.prefix.clone().unwrap_or("".to_string())
912
904
  }
905
+
906
+ fn default_settings(&self) -> Settings {
907
+ Default::default()
908
+ }
913
909
  }
914
910
 
915
911
  #[derive(Debug)]
@@ -196,6 +196,7 @@ impl S3Storage {
196
196
  I: IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
197
197
  >(
198
198
  &self,
199
+ settings: &Settings,
199
200
  key: &str,
200
201
  content_type: Option<impl Into<String>>,
201
202
  metadata: I,
@@ -204,14 +205,17 @@ impl S3Storage {
204
205
  let mut b =
205
206
  self.get_client().await.put_object().bucket(self.bucket.clone()).key(key);
206
207
 
207
- if let Some(ct) = content_type {
208
- b = b.content_type(ct)
209
- };
210
-
211
- for (k, v) in metadata {
212
- b = b.metadata(k, v);
208
+ if settings.unsafe_use_metadata() {
209
+ if let Some(ct) = content_type {
210
+ b = b.content_type(ct)
211
+ };
213
212
  }
214
213
 
214
+ if settings.unsafe_use_metadata() {
215
+ for (k, v) in metadata {
216
+ b = b.metadata(k, v);
217
+ }
218
+ }
215
219
  b.body(bytes.into()).send().await?;
216
220
  Ok(())
217
221
  }
@@ -296,10 +300,10 @@ impl Storage for S3Storage {
296
300
  }
297
301
  }
298
302
 
299
- #[instrument(skip(self, _settings, config))]
303
+ #[instrument(skip(self, settings, config))]
300
304
  async fn update_config(
301
305
  &self,
302
- _settings: &Settings,
306
+ settings: &Settings,
303
307
  config: Bytes,
304
308
  previous_version: &VersionInfo,
305
309
  ) -> StorageResult<UpdateConfigResult> {
@@ -310,13 +314,20 @@ impl Storage for S3Storage {
310
314
  .put_object()
311
315
  .bucket(self.bucket.clone())
312
316
  .key(key)
313
- .content_type("application/yaml")
314
317
  .body(config.into());
315
318
 
316
- if let Some(etag) = previous_version.etag() {
317
- req = req.if_match(etag)
318
- } else {
319
- req = req.if_none_match("*")
319
+ if settings.unsafe_use_metadata() {
320
+ req = req.content_type("application/yaml")
321
+ }
322
+
323
+ match (
324
+ previous_version.etag(),
325
+ settings.unsafe_use_conditional_create(),
326
+ settings.unsafe_use_conditional_update(),
327
+ ) {
328
+ (None, true, _) => req = req.if_none_match("*"),
329
+ (Some(etag), _, true) => req = req.if_match(etag),
330
+ (_, _, _) => {}
320
331
  }
321
332
 
322
333
  let res = req.send().await;
@@ -411,53 +422,67 @@ impl Storage for S3Storage {
411
422
  .await
412
423
  }
413
424
 
414
- #[instrument(skip(self, _settings, metadata, bytes))]
425
+ #[instrument(skip(self, settings, metadata, bytes))]
415
426
  async fn write_snapshot(
416
427
  &self,
417
- _settings: &Settings,
428
+ settings: &Settings,
418
429
  id: SnapshotId,
419
430
  metadata: Vec<(String, String)>,
420
431
  bytes: Bytes,
421
432
  ) -> StorageResult<()> {
422
433
  let key = self.get_snapshot_path(&id)?;
423
- self.put_object(key.as_str(), None::<String>, metadata, bytes).await
434
+ self.put_object(settings, key.as_str(), None::<String>, metadata, bytes).await
424
435
  }
425
436
 
426
- #[instrument(skip(self, _settings, metadata, bytes))]
437
+ #[instrument(skip(self, settings, metadata, bytes))]
427
438
  async fn write_manifest(
428
439
  &self,
429
- _settings: &Settings,
440
+ settings: &Settings,
430
441
  id: ManifestId,
431
442
  metadata: Vec<(String, String)>,
432
443
  bytes: Bytes,
433
444
  ) -> StorageResult<()> {
434
445
  let key = self.get_manifest_path(&id)?;
435
- self.put_object(key.as_str(), None::<String>, metadata.into_iter(), bytes).await
446
+ self.put_object(
447
+ settings,
448
+ key.as_str(),
449
+ None::<String>,
450
+ metadata.into_iter(),
451
+ bytes,
452
+ )
453
+ .await
436
454
  }
437
455
 
438
- #[instrument(skip(self, _settings, metadata, bytes))]
456
+ #[instrument(skip(self, settings, metadata, bytes))]
439
457
  async fn write_transaction_log(
440
458
  &self,
441
- _settings: &Settings,
459
+ settings: &Settings,
442
460
  id: SnapshotId,
443
461
  metadata: Vec<(String, String)>,
444
462
  bytes: Bytes,
445
463
  ) -> StorageResult<()> {
446
464
  let key = self.get_transaction_path(&id)?;
447
- self.put_object(key.as_str(), None::<String>, metadata.into_iter(), bytes).await
465
+ self.put_object(
466
+ settings,
467
+ key.as_str(),
468
+ None::<String>,
469
+ metadata.into_iter(),
470
+ bytes,
471
+ )
472
+ .await
448
473
  }
449
474
 
450
- #[instrument(skip(self, _settings, bytes))]
475
+ #[instrument(skip(self, settings, bytes))]
451
476
  async fn write_chunk(
452
477
  &self,
453
- _settings: &Settings,
478
+ settings: &Settings,
454
479
  id: ChunkId,
455
480
  bytes: bytes::Bytes,
456
481
  ) -> Result<(), StorageError> {
457
482
  let key = self.get_chunk_path(&id)?;
458
483
  //FIXME: use multipart upload
459
484
  let metadata: [(String, String); 0] = [];
460
- self.put_object(key.as_str(), None::<String>, metadata, bytes).await
485
+ self.put_object(settings, key.as_str(), None::<String>, metadata, bytes).await
461
486
  }
462
487
 
463
488
  #[instrument(skip(self, _settings))]
@@ -523,10 +548,10 @@ impl Storage for S3Storage {
523
548
  Ok(res)
524
549
  }
525
550
 
526
- #[instrument(skip(self, _settings, bytes))]
551
+ #[instrument(skip(self, settings, bytes))]
527
552
  async fn write_ref(
528
553
  &self,
529
- _settings: &Settings,
554
+ settings: &Settings,
530
555
  ref_key: &str,
531
556
  bytes: Bytes,
532
557
  previous_version: &VersionInfo,
@@ -539,10 +564,18 @@ impl Storage for S3Storage {
539
564
  .bucket(self.bucket.clone())
540
565
  .key(key.clone());
541
566
 
542
- if let Some(etag) = previous_version.etag() {
543
- builder = builder.if_match(etag);
544
- } else {
545
- builder = builder.if_none_match("*");
567
+ match (
568
+ previous_version.etag(),
569
+ settings.unsafe_use_conditional_create(),
570
+ settings.unsafe_use_conditional_update(),
571
+ ) {
572
+ (None, true, _) => {
573
+ builder = builder.if_none_match("*");
574
+ }
575
+ (Some(etag), _, true) => {
576
+ builder = builder.if_match(etag);
577
+ }
578
+ (_, _, _) => {}
546
579
  }
547
580
 
548
581
  let res = builder.body(bytes.into()).send().await;
@@ -14,7 +14,7 @@ use icechunk::{
14
14
  RefErrorKind,
15
15
  },
16
16
  storage::{
17
- new_in_memory_storage, new_s3_storage, ETag, FetchConfigResult, Generation,
17
+ self, new_in_memory_storage, new_s3_storage, ETag, FetchConfigResult, Generation,
18
18
  StorageResult, UpdateConfigResult, VersionInfo,
19
19
  },
20
20
  ObjectStorage, Storage,
@@ -488,3 +488,55 @@ pub async fn test_write_config_fails_on_bad_version_when_existing(
488
488
  }).await?;
489
489
  Ok(())
490
490
  }
491
+
492
+ #[tokio::test]
493
+ #[allow(clippy::panic)]
494
+ pub async fn test_write_config_can_overwrite_with_unsafe_config(
495
+ ) -> Result<(), Box<dyn std::error::Error>> {
496
+ with_storage(|_, storage| async move {
497
+ let storage_settings = storage::Settings {
498
+ unsafe_use_conditional_update: Some(false),
499
+ unsafe_use_conditional_create: Some(false),
500
+ ..storage.default_settings()
501
+ };
502
+
503
+ // create the initial version
504
+ let config = Bytes::copy_from_slice(b"hello");
505
+ match storage
506
+ .update_config(
507
+ &storage_settings,
508
+ config.clone(),
509
+ &VersionInfo {
510
+ etag: Some(ETag("some-bad-etag".to_string())),
511
+ generation: Some(Generation("42".to_string())),
512
+ }
513
+ )
514
+ .await?
515
+ {
516
+ UpdateConfigResult::Updated { new_version } => new_version,
517
+ _ => panic!(),
518
+ };
519
+
520
+ // attempt a bad change that should succeed in this config
521
+ let update_res = storage
522
+ .update_config(
523
+ &storage_settings,
524
+ Bytes::copy_from_slice(b"bye"),
525
+ &VersionInfo {
526
+ etag: Some(ETag("other-bad-etag".to_string())),
527
+ generation: Some(Generation("55".to_string())),
528
+ },
529
+ )
530
+ .await?;
531
+
532
+ assert!(matches!(update_res, UpdateConfigResult::Updated { .. }));
533
+
534
+ let fetch_res = storage.fetch_config(&storage_settings).await?;
535
+ assert!(
536
+ matches!(fetch_res, FetchConfigResult::Found{bytes, ..} if bytes.as_ref() == b"bye")
537
+ );
538
+ Ok(())
539
+ })
540
+ .await?;
541
+ Ok(())
542
+ }