covalence 0.1.0.dev2__tar.gz → 0.1.0.dev3__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 (160) hide show
  1. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/Cargo.lock +19 -19
  2. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/Cargo.toml +17 -17
  3. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/PKG-INFO +21 -9
  4. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/README.md +20 -8
  5. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/kv/src/lib.rs +28 -10
  6. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/kv/tests/kv.rs +59 -6
  7. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/trait/src/lib.rs +8 -4
  8. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/memory/src/lib.rs +35 -2
  9. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/memory/tests/memory.rs +30 -0
  10. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/src/lib.rs +36 -2
  11. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/src/write.rs +83 -34
  12. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/tests/kv.rs +79 -0
  13. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/trait/src/lib.rs +40 -0
  14. covalence-0.1.0.dev3/crates/kv/trait/tests/atomic.rs +52 -0
  15. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/README.md +20 -8
  16. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/src/lib.rs +145 -2
  17. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/tests/test_api.py +172 -4
  18. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/python/covalence/THIRD-PARTY-LICENSES.md +17 -17
  19. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/python/covalence/_native.pyi +27 -2
  20. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/LICENSE-APACHE +0 -0
  21. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/LICENSE-CC0 +0 -0
  22. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/LICENSE-MIT +0 -0
  23. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/Cargo.toml +0 -0
  24. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/LICENSE-APACHE +0 -0
  25. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/LICENSE-CC0 +0 -0
  26. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/LICENSE-MIT +0 -0
  27. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/README.md +0 -0
  28. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/src/lib.rs +0 -0
  29. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/tests/blob.rs +0 -0
  30. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/tests/common/mod.rs +0 -0
  31. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/blob/trait/tests/fact.rs +0 -0
  32. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/kv/Cargo.toml +0 -0
  33. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/kv/LICENSE-APACHE +0 -0
  34. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/kv/LICENSE-CC0 +0 -0
  35. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/kv/LICENSE-MIT +0 -0
  36. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/memory/Cargo.toml +0 -0
  37. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/memory/LICENSE-APACHE +0 -0
  38. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/memory/LICENSE-CC0 +0 -0
  39. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/memory/LICENSE-MIT +0 -0
  40. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/memory/src/lib.rs +0 -0
  41. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/memory/tests/memory.rs +0 -0
  42. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/sqlite/Cargo.toml +0 -0
  43. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/sqlite/LICENSE-APACHE +0 -0
  44. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/sqlite/LICENSE-CC0 +0 -0
  45. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/sqlite/LICENSE-MIT +0 -0
  46. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/sqlite/src/lib.rs +0 -0
  47. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/sqlite/tests/sqlite.rs +0 -0
  48. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/trait/Cargo.toml +0 -0
  49. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/trait/LICENSE-APACHE +0 -0
  50. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/trait/LICENSE-CC0 +0 -0
  51. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/trait/LICENSE-MIT +0 -0
  52. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/cas/trait/tests/trait.rs +0 -0
  53. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/memory/Cargo.toml +0 -0
  54. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/memory/LICENSE-APACHE +0 -0
  55. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/memory/LICENSE-CC0 +0 -0
  56. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/memory/LICENSE-MIT +0 -0
  57. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/Cargo.toml +0 -0
  58. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/LICENSE-APACHE +0 -0
  59. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/LICENSE-CC0 +0 -0
  60. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/LICENSE-MIT +0 -0
  61. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/src/read.rs +0 -0
  62. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/s3/tests/aws.rs +0 -0
  63. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/trait/Cargo.toml +0 -0
  64. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/trait/LICENSE-APACHE +0 -0
  65. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/trait/LICENSE-CC0 +0 -0
  66. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/kv/trait/LICENSE-MIT +0 -0
  67. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/Cargo.toml +0 -0
  68. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/LICENSE-APACHE +0 -0
  69. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/LICENSE-CC0 +0 -0
  70. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/LICENSE-MIT +0 -0
  71. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/README.md +0 -0
  72. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/src/dasl.rs +0 -0
  73. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/src/fact.rs +0 -0
  74. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/src/git.rs +0 -0
  75. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/src/lib.rs +0 -0
  76. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/src/multicodec.rs +0 -0
  77. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/src/o256.rs +0 -0
  78. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/src/storage.rs +0 -0
  79. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/tests/cid.rs +0 -0
  80. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/tests/fact.rs +0 -0
  81. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/tests/git.rs +0 -0
  82. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/tests/o256.rs +0 -0
  83. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/tests/shattered/1.bin +0 -0
  84. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/tests/shattered/2.bin +0 -0
  85. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/cid/tests/shattered/README.md +0 -0
  86. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/Cargo.toml +0 -0
  87. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/LICENSE-APACHE +0 -0
  88. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/LICENSE-CC0 +0 -0
  89. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/LICENSE-MIT +0 -0
  90. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/README.md +0 -0
  91. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/src/drisl.rs +0 -0
  92. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/src/event.rs +0 -0
  93. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/src/json.rs +0 -0
  94. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/src/lib.rs +0 -0
  95. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/codec.rs +0 -0
  96. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/events.rs +0 -0
  97. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fact.rs +0 -0
  98. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/LICENSE +0 -0
  99. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/README.md +0 -0
  100. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/cid.json +0 -0
  101. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/concat.json +0 -0
  102. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/floats.json +0 -0
  103. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/indefinite.json +0 -0
  104. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/integer_range.json +0 -0
  105. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/map_keys.json +0 -0
  106. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/numeric_reduction.json +0 -0
  107. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/short_form.json +0 -0
  108. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/simple.json +0 -0
  109. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/tags.json +0 -0
  110. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures/utf8.json +0 -0
  111. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/fixtures.rs +0 -0
  112. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/json.rs +0 -0
  113. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/vectors/README.md +0 -0
  114. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/vectors/cid-vectors.jsonl +0 -0
  115. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/vectors/dasl-vectors.jsonl +0 -0
  116. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/dasl/tests/vectors.rs +0 -0
  117. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/data/Cargo.toml +0 -0
  118. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/data/LICENSE-APACHE +0 -0
  119. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/data/LICENSE-CC0 +0 -0
  120. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/data/LICENSE-MIT +0 -0
  121. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/data/src/lib.rs +0 -0
  122. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/error/Cargo.toml +0 -0
  123. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/error/LICENSE-APACHE +0 -0
  124. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/error/LICENSE-CC0 +0 -0
  125. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/error/LICENSE-MIT +0 -0
  126. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/error/src/lib.rs +0 -0
  127. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/python/Cargo.toml +0 -0
  128. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/python/LICENSE-APACHE +0 -0
  129. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/python/LICENSE-CC0 +0 -0
  130. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/python/LICENSE-MIT +0 -0
  131. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/python/src/lib.rs +0 -0
  132. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/rand/Cargo.toml +0 -0
  133. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/rand/LICENSE-APACHE +0 -0
  134. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/rand/LICENSE-CC0 +0 -0
  135. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/rand/LICENSE-MIT +0 -0
  136. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/rand/src/lib.rs +0 -0
  137. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/serde/Cargo.toml +0 -0
  138. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/serde/LICENSE-APACHE +0 -0
  139. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/serde/LICENSE-CC0 +0 -0
  140. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/serde/LICENSE-MIT +0 -0
  141. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/serde/src/lib.rs +0 -0
  142. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/sqlite/Cargo.toml +0 -0
  143. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/sqlite/LICENSE-APACHE +0 -0
  144. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/sqlite/LICENSE-CC0 +0 -0
  145. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/sqlite/LICENSE-MIT +0 -0
  146. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/lib/sqlite/src/lib.rs +0 -0
  147. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/Cargo.toml +0 -0
  148. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/LICENSE-APACHE +0 -0
  149. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/LICENSE-CC0 +0 -0
  150. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/LICENSE-MIT +0 -0
  151. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/about.md.hbs +0 -0
  152. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/about.toml +0 -0
  153. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/src/errors.rs +0 -0
  154. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/tests/test_docs.py +0 -0
  155. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/tests/test_package.py +0 -0
  156. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/tests/test_stubs.py +0 -0
  157. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/crates/python/tests/typed_usage.py +0 -0
  158. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/pyproject.toml +0 -0
  159. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/python/covalence/__init__.py +0 -0
  160. {covalence-0.1.0.dev2 → covalence-0.1.0.dev3}/python/covalence/py.typed +0 -0
@@ -779,7 +779,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
779
779
 
780
780
  [[package]]
781
781
  name = "covalence-blob-trait"
782
- version = "0.1.0-dev.2"
782
+ version = "0.1.0-dev.3"
783
783
  dependencies = [
784
784
  "covalence-lib-cid",
785
785
  "covalence-lib-data",
@@ -788,7 +788,7 @@ dependencies = [
788
788
 
789
789
  [[package]]
790
790
  name = "covalence-cas-kv"
791
- version = "0.1.0-dev.2"
791
+ version = "0.1.0-dev.3"
792
792
  dependencies = [
793
793
  "blake3",
794
794
  "covalence-cas-trait",
@@ -810,7 +810,7 @@ dependencies = [
810
810
 
811
811
  [[package]]
812
812
  name = "covalence-cas-memory"
813
- version = "0.1.0-dev.2"
813
+ version = "0.1.0-dev.3"
814
814
  dependencies = [
815
815
  "covalence-cas-trait",
816
816
  "covalence-lib-cid",
@@ -821,7 +821,7 @@ dependencies = [
821
821
 
822
822
  [[package]]
823
823
  name = "covalence-cas-sqlite"
824
- version = "0.1.0-dev.2"
824
+ version = "0.1.0-dev.3"
825
825
  dependencies = [
826
826
  "covalence-cas-trait",
827
827
  "covalence-lib-cid",
@@ -836,7 +836,7 @@ dependencies = [
836
836
 
837
837
  [[package]]
838
838
  name = "covalence-cas-trait"
839
- version = "0.1.0-dev.2"
839
+ version = "0.1.0-dev.3"
840
840
  dependencies = [
841
841
  "covalence-lib-cid",
842
842
  "covalence-lib-dasl",
@@ -847,7 +847,7 @@ dependencies = [
847
847
 
848
848
  [[package]]
849
849
  name = "covalence-kv-memory"
850
- version = "0.1.0-dev.2"
850
+ version = "0.1.0-dev.3"
851
851
  dependencies = [
852
852
  "covalence-kv-trait",
853
853
  "covalence-lib-dasl",
@@ -857,7 +857,7 @@ dependencies = [
857
857
 
858
858
  [[package]]
859
859
  name = "covalence-kv-s3"
860
- version = "0.1.0-dev.2"
860
+ version = "0.1.0-dev.3"
861
861
  dependencies = [
862
862
  "aws-config",
863
863
  "aws-credential-types",
@@ -881,7 +881,7 @@ dependencies = [
881
881
 
882
882
  [[package]]
883
883
  name = "covalence-kv-trait"
884
- version = "0.1.0-dev.2"
884
+ version = "0.1.0-dev.3"
885
885
  dependencies = [
886
886
  "covalence-lib-dasl",
887
887
  "covalence-lib-data",
@@ -890,7 +890,7 @@ dependencies = [
890
890
 
891
891
  [[package]]
892
892
  name = "covalence-lib-cid"
893
- version = "0.1.0-dev.2"
893
+ version = "0.1.0-dev.3"
894
894
  dependencies = [
895
895
  "blake3",
896
896
  "covalence-lib-data",
@@ -905,14 +905,14 @@ dependencies = [
905
905
 
906
906
  [[package]]
907
907
  name = "covalence-lib-cli"
908
- version = "0.1.0-dev.2"
908
+ version = "0.1.0-dev.3"
909
909
  dependencies = [
910
910
  "clap",
911
911
  ]
912
912
 
913
913
  [[package]]
914
914
  name = "covalence-lib-dasl"
915
- version = "0.1.0-dev.2"
915
+ version = "0.1.0-dev.3"
916
916
  dependencies = [
917
917
  "blake3",
918
918
  "covalence-lib-cid",
@@ -927,7 +927,7 @@ dependencies = [
927
927
 
928
928
  [[package]]
929
929
  name = "covalence-lib-data"
930
- version = "0.1.0-dev.2"
930
+ version = "0.1.0-dev.3"
931
931
  dependencies = [
932
932
  "arrayvec",
933
933
  "bytes",
@@ -939,7 +939,7 @@ dependencies = [
939
939
 
940
940
  [[package]]
941
941
  name = "covalence-lib-error"
942
- version = "0.1.0-dev.2"
942
+ version = "0.1.0-dev.3"
943
943
  dependencies = [
944
944
  "miette",
945
945
  "snafu",
@@ -947,14 +947,14 @@ dependencies = [
947
947
 
948
948
  [[package]]
949
949
  name = "covalence-lib-python"
950
- version = "0.1.0-dev.2"
950
+ version = "0.1.0-dev.3"
951
951
  dependencies = [
952
952
  "pyo3",
953
953
  ]
954
954
 
955
955
  [[package]]
956
956
  name = "covalence-lib-rand"
957
- version = "0.1.0-dev.2"
957
+ version = "0.1.0-dev.3"
958
958
  dependencies = [
959
959
  "getrandom 0.3.4",
960
960
  "rand",
@@ -962,21 +962,21 @@ dependencies = [
962
962
 
963
963
  [[package]]
964
964
  name = "covalence-lib-serde"
965
- version = "0.1.0-dev.2"
965
+ version = "0.1.0-dev.3"
966
966
  dependencies = [
967
967
  "serde",
968
968
  ]
969
969
 
970
970
  [[package]]
971
971
  name = "covalence-lib-sqlite"
972
- version = "0.1.0-dev.2"
972
+ version = "0.1.0-dev.3"
973
973
  dependencies = [
974
974
  "rusqlite",
975
975
  ]
976
976
 
977
977
  [[package]]
978
978
  name = "covalence-python"
979
- version = "0.1.0-dev.2"
979
+ version = "0.1.0-dev.3"
980
980
  dependencies = [
981
981
  "blake3",
982
982
  "covalence-blob-trait",
@@ -997,7 +997,7 @@ dependencies = [
997
997
 
998
998
  [[package]]
999
999
  name = "covalence-tools-glu"
1000
- version = "0.1.0-dev.2"
1000
+ version = "0.1.0-dev.3"
1001
1001
  dependencies = [
1002
1002
  "covalence-lib-cli",
1003
1003
  "covalence-lib-error",
@@ -3,7 +3,7 @@ members = ["crates/lib/error", "crates/lib/serde", "crates/lib/data", "crates/li
3
3
  resolver = "3"
4
4
 
5
5
  [workspace.package]
6
- version = "0.1.0-dev.2"
6
+ version = "0.1.0-dev.3"
7
7
  edition = "2024"
8
8
  rust-version = "1.98"
9
9
  license = "CC0-1.0 OR MIT OR Apache-2.0"
@@ -17,22 +17,22 @@ miette = "7.6.0"
17
17
  sha2 = "0.11.0"
18
18
  snafu = "0.9.2"
19
19
  # Published crates, at the workspace version so the set moves together.
20
- covalence-blob-trait = { path = "crates/blob/trait", version = "=0.1.0-dev.2" }
21
- covalence-cas-kv = { path = "crates/cas/kv", version = "=0.1.0-dev.2" }
22
- covalence-cas-memory = { path = "crates/cas/memory", version = "=0.1.0-dev.2" }
23
- covalence-cas-sqlite = { path = "crates/cas/sqlite", version = "=0.1.0-dev.2" }
24
- covalence-cas-trait = { path = "crates/cas/trait", version = "=0.1.0-dev.2" }
25
- covalence-kv-memory = { path = "crates/kv/memory", version = "=0.1.0-dev.2" }
26
- covalence-kv-s3 = { path = "crates/kv/s3", version = "=0.1.0-dev.2" }
27
- covalence-kv-trait = { path = "crates/kv/trait", version = "=0.1.0-dev.2" }
28
- covalence-lib-cid = { path = "crates/lib/cid", version = "=0.1.0-dev.2" }
29
- covalence-lib-cli = { path = "crates/lib/cli", version = "=0.1.0-dev.2" }
30
- covalence-lib-dasl = { path = "crates/lib/dasl", version = "=0.1.0-dev.2" }
31
- covalence-lib-data = { path = "crates/lib/data", version = "=0.1.0-dev.2" }
32
- covalence-lib-error = { path = "crates/lib/error", version = "=0.1.0-dev.2" }
33
- covalence-lib-rand = { path = "crates/lib/rand", version = "=0.1.0-dev.2" }
34
- covalence-lib-serde = { path = "crates/lib/serde", version = "=0.1.0-dev.2" }
35
- covalence-lib-sqlite = { path = "crates/lib/sqlite", version = "=0.1.0-dev.2" }
20
+ covalence-blob-trait = { path = "crates/blob/trait", version = "=0.1.0-dev.3" }
21
+ covalence-cas-kv = { path = "crates/cas/kv", version = "=0.1.0-dev.3" }
22
+ covalence-cas-memory = { path = "crates/cas/memory", version = "=0.1.0-dev.3" }
23
+ covalence-cas-sqlite = { path = "crates/cas/sqlite", version = "=0.1.0-dev.3" }
24
+ covalence-cas-trait = { path = "crates/cas/trait", version = "=0.1.0-dev.3" }
25
+ covalence-kv-memory = { path = "crates/kv/memory", version = "=0.1.0-dev.3" }
26
+ covalence-kv-s3 = { path = "crates/kv/s3", version = "=0.1.0-dev.3" }
27
+ covalence-kv-trait = { path = "crates/kv/trait", version = "=0.1.0-dev.3" }
28
+ covalence-lib-cid = { path = "crates/lib/cid", version = "=0.1.0-dev.3" }
29
+ covalence-lib-cli = { path = "crates/lib/cli", version = "=0.1.0-dev.3" }
30
+ covalence-lib-dasl = { path = "crates/lib/dasl", version = "=0.1.0-dev.3" }
31
+ covalence-lib-data = { path = "crates/lib/data", version = "=0.1.0-dev.3" }
32
+ covalence-lib-error = { path = "crates/lib/error", version = "=0.1.0-dev.3" }
33
+ covalence-lib-rand = { path = "crates/lib/rand", version = "=0.1.0-dev.3" }
34
+ covalence-lib-serde = { path = "crates/lib/serde", version = "=0.1.0-dev.3" }
35
+ covalence-lib-sqlite = { path = "crates/lib/sqlite", version = "=0.1.0-dev.3" }
36
36
 
37
37
  [workspace.lints.rust]
38
38
  unsafe_code = "forbid"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: covalence
3
- Version: 0.1.0.dev2
3
+ Version: 0.1.0.dev3
4
4
  License-File: LICENSE-APACHE
5
5
  License-File: LICENSE-CC0
6
6
  License-File: LICENSE-MIT
@@ -35,7 +35,7 @@ True
35
35
  ## Install
36
36
 
37
37
  ```sh
38
- pip install covalence==0.1.0.dev2
38
+ pip install covalence==0.1.0.dev3
39
39
  ```
40
40
 
41
41
  - Pre-releases need an exact pin like this one, or `pip install --pre`.
@@ -138,6 +138,9 @@ pip install covalence==0.1.0.dev2
138
138
  `prefix/blake3/<digest>/bytes`.
139
139
  - `put` returns the raw BLAKE3 `Cid`, after checking that the store kept the
140
140
  data under it.
141
+ - `Cas.kv` never replaces a block its `Kv` already holds: on `Kv.s3` each
142
+ write is conditional (`If-None-Match: *`), so concurrent writers of one
143
+ block race safely and the first one's bytes stay.
141
144
  - `put_value(value)` stores the DRISL encoding of a `Value` (or what
142
145
  `Value(...)` takes) and returns its DRISL CID, which is `value.cid()`.
143
146
  - `get` returns the bytes and `get_fact` a `BlobFact`, both checked against
@@ -156,16 +159,25 @@ pip install covalence==0.1.0.dev2
156
159
  - Ranges are clipped at the end.
157
160
  - Stores without native ranges fetch the whole value and slice it.
158
161
 
159
- ### Files
162
+ ### Files and streams
160
163
 
161
164
  - `Cas.put_file(path)` stores a file and returns its raw BLAKE3 `Cid`, as
162
165
  `put` does.
166
+ - `Cas.put_from(cid, chunks, length)` stores a block from an iterable of
167
+ bytes, such as a request body, and returns its raw BLAKE3 `Cid`. Before a
168
+ new block is stored, its bytes must match `length` and the BLAKE3 `cid`,
169
+ and `chunks` must end there. A store may return an existing block without
170
+ consuming all the input; unread bytes and source errors are not checked.
171
+ While consuming input, a hash mismatch (`IntegrityError`), too few or too
172
+ many bytes (`ValueError`), or a source exception publish no new block.
173
+ Source exceptions propagate unchanged unless cleanup also fails, in which
174
+ case `StoreError` reports that failure with the source as its `__cause__`.
163
175
  - `Cas.get_file(cid, path)` writes the block to a file and returns its
164
176
  length, or `None` if missing.
165
- - `put_file` and `get_file` stream on `Cas.kv` over `Kv.s3`, holding up to
166
- 16 parts of 8 MiB (128 MiB) at a time; files over 78 GiB go up in larger
167
- parts. Other stores, including `Cas.kv(Kv.memory())`, hold the block in
168
- memory.
177
+ - `put_file`, `put_from` and `get_file` stream on `Cas.kv` over `Kv.s3`,
178
+ holding up to 16 parts of 8 MiB (128 MiB) at a time, or as many as
179
+ `Kv.s3(..., concurrency=n)` says; blocks over 78 GiB go up in larger parts. Other stores, including `Cas.kv(Kv.memory())`, hold
180
+ the block in memory.
169
181
  - `put_file` reads the file twice, to hash it and to store it, and stores the
170
182
  bytes it hashed. If they changed in between it raises `IntegrityError`,
171
183
  and if the file got shorter `OSError`, storing nothing either way. Bytes
@@ -254,9 +266,9 @@ workflow artifacts. A release tag must match the workspace version, such as
254
266
  `.github/workflows/crates.yml` publishes the crates to crates.io.
255
267
  - `python-v<version>` only attaches them to the GitHub release.
256
268
 
257
- To publish `0.1.0-dev.2`:
269
+ To publish `0.1.0-dev.3`:
258
270
 
259
271
  ```sh
260
- git tag v0.1.0-dev.2 && git push origin v0.1.0-dev.2
272
+ git tag v0.1.0-dev.3 && git push origin v0.1.0-dev.3
261
273
  ```
262
274
 
@@ -21,7 +21,7 @@ True
21
21
  ## Install
22
22
 
23
23
  ```sh
24
- pip install covalence==0.1.0.dev2
24
+ pip install covalence==0.1.0.dev3
25
25
  ```
26
26
 
27
27
  - Pre-releases need an exact pin like this one, or `pip install --pre`.
@@ -124,6 +124,9 @@ pip install covalence==0.1.0.dev2
124
124
  `prefix/blake3/<digest>/bytes`.
125
125
  - `put` returns the raw BLAKE3 `Cid`, after checking that the store kept the
126
126
  data under it.
127
+ - `Cas.kv` never replaces a block its `Kv` already holds: on `Kv.s3` each
128
+ write is conditional (`If-None-Match: *`), so concurrent writers of one
129
+ block race safely and the first one's bytes stay.
127
130
  - `put_value(value)` stores the DRISL encoding of a `Value` (or what
128
131
  `Value(...)` takes) and returns its DRISL CID, which is `value.cid()`.
129
132
  - `get` returns the bytes and `get_fact` a `BlobFact`, both checked against
@@ -142,16 +145,25 @@ pip install covalence==0.1.0.dev2
142
145
  - Ranges are clipped at the end.
143
146
  - Stores without native ranges fetch the whole value and slice it.
144
147
 
145
- ### Files
148
+ ### Files and streams
146
149
 
147
150
  - `Cas.put_file(path)` stores a file and returns its raw BLAKE3 `Cid`, as
148
151
  `put` does.
152
+ - `Cas.put_from(cid, chunks, length)` stores a block from an iterable of
153
+ bytes, such as a request body, and returns its raw BLAKE3 `Cid`. Before a
154
+ new block is stored, its bytes must match `length` and the BLAKE3 `cid`,
155
+ and `chunks` must end there. A store may return an existing block without
156
+ consuming all the input; unread bytes and source errors are not checked.
157
+ While consuming input, a hash mismatch (`IntegrityError`), too few or too
158
+ many bytes (`ValueError`), or a source exception publish no new block.
159
+ Source exceptions propagate unchanged unless cleanup also fails, in which
160
+ case `StoreError` reports that failure with the source as its `__cause__`.
149
161
  - `Cas.get_file(cid, path)` writes the block to a file and returns its
150
162
  length, or `None` if missing.
151
- - `put_file` and `get_file` stream on `Cas.kv` over `Kv.s3`, holding up to
152
- 16 parts of 8 MiB (128 MiB) at a time; files over 78 GiB go up in larger
153
- parts. Other stores, including `Cas.kv(Kv.memory())`, hold the block in
154
- memory.
163
+ - `put_file`, `put_from` and `get_file` stream on `Cas.kv` over `Kv.s3`,
164
+ holding up to 16 parts of 8 MiB (128 MiB) at a time, or as many as
165
+ `Kv.s3(..., concurrency=n)` says; blocks over 78 GiB go up in larger parts. Other stores, including `Cas.kv(Kv.memory())`, hold
166
+ the block in memory.
155
167
  - `put_file` reads the file twice, to hash it and to store it, and stores the
156
168
  bytes it hashed. If they changed in between it raises `IntegrityError`,
157
169
  and if the file got shorter `OSError`, storing nothing either way. Bytes
@@ -240,8 +252,8 @@ workflow artifacts. A release tag must match the workspace version, such as
240
252
  `.github/workflows/crates.yml` publishes the crates to crates.io.
241
253
  - `python-v<version>` only attaches them to the GitHub release.
242
254
 
243
- To publish `0.1.0-dev.2`:
255
+ To publish `0.1.0-dev.3`:
244
256
 
245
257
  ```sh
246
- git tag v0.1.0-dev.2 && git push origin v0.1.0-dev.2
258
+ git tag v0.1.0-dev.3 && git push origin v0.1.0-dev.3
247
259
  ```
@@ -46,13 +46,16 @@ use data_encoding::BASE32_NOPAD;
46
46
  /// `get_fact` and `aget_fact` check the bytes against the CID and fail with
47
47
  /// [`Error::Check`] if they do not match. `get_unchecked`, sizes and ranges
48
48
  /// are not checked.
49
- /// - `has` asks the key-value store whether the key exists (a HEAD request on
50
- /// S3), without reading the bytes.
51
- /// - `put` writes without asking first, so it needs only write access. (S3
52
- /// answers a HEAD for a missing key with 403 to a client that may not list
53
- /// the bucket.)
49
+ /// - `has` asks the key-value store whether the key exists, without reading
50
+ /// the bytes.
51
+ /// - `put`, `aput` and `put_from` never replace a block: they store through
52
+ /// the key-value store's [`Kv::put_new`], [`Kv::aput_new`] and
53
+ /// [`Kv::put_new_from`], so concurrent writers of one block race safely.
54
+ /// Writes fail if the backend does not implement atomic create-only writes.
54
55
  /// - `put_from` and `get_to` stream through the key-value store's own
55
- /// `put_from` and `get_to`, hashing the bytes as they pass.
56
+ /// `put_new_from` and `get_to`, hashing the bytes as they pass.
57
+ /// If the backend skips an existing block, `put_from` returns without
58
+ /// consuming or checking any remaining input.
56
59
  /// - If the bytes `put_from` reads do not hash to the CID, the read that would
57
60
  /// hand over the last byte fails instead. The key-value store's `put_from`
58
61
  /// then fails as [`Kv::put_from`] says, storing nothing, and `put_from`
@@ -60,6 +63,18 @@ use data_encoding::BASE32_NOPAD;
60
63
  /// store reports a further failure, such as failing to clean up.
61
64
  /// - [`Cas::info`] is `{"name": "kv", "prefix": <prefix>, "kv": <the
62
65
  /// key-value store's info>}`; [`Cas::stats`] is the key-value store's.
66
+ ///
67
+ /// # S3
68
+ ///
69
+ /// Writes use conditional requests to preserve existing blocks. They do not
70
+ /// make a preliminary HEAD request, so they need only write access.
71
+ ///
72
+ /// `has` uses a HEAD request. S3 answers a HEAD for a missing key with 403 to
73
+ /// a client that may not list the bucket.
74
+ ///
75
+ /// S3 may consume the source before discovering that a block already exists.
76
+ /// Once the backend skips insertion, `put_from` leaves any remaining input
77
+ /// unread.
63
78
  pub struct KvCas {
64
79
  kv: Arc<dyn Kv>,
65
80
  prefix: String,
@@ -277,7 +292,7 @@ impl Put<Blake3> for KvCas {
277
292
  fn put(&self, bytes: Bytes) -> Result<Blake3, Error> {
278
293
  let cid = Blake3::of(&bytes);
279
294
  self.kv
280
- .put(self.key(&cid).as_bytes(), bytes)
295
+ .put_new(self.key(&cid).as_bytes(), bytes)
281
296
  .map_err(backend)?;
282
297
  Ok(cid)
283
298
  }
@@ -294,7 +309,10 @@ impl AsyncPut for KvCas {
294
309
  Box::pin(async move {
295
310
  let cid = Blake3::of(&bytes);
296
311
  let key = self.key(&cid);
297
- self.kv.aput(key.as_bytes(), bytes).await.map_err(backend)?;
312
+ self.kv
313
+ .aput_new(key.as_bytes(), bytes)
314
+ .await
315
+ .map_err(backend)?;
298
316
  Ok(cid.into())
299
317
  })
300
318
  }
@@ -334,8 +352,8 @@ impl Cas for KvCas {
334
352
  mismatch: false,
335
353
  };
336
354
  let key = self.key(&cid);
337
- match self.kv.put_from(key.as_bytes(), &mut checked, len) {
338
- Ok(()) => Ok(cid.into()),
355
+ match self.kv.put_new_from(key.as_bytes(), &mut checked, len) {
356
+ Ok(_) => Ok(cid.into()),
339
357
  // The store failed as asked; a store error beyond that, such as a
340
358
  // failed cleanup, is the one to report.
341
359
  Err(covalence_kv_trait::Error::Io { .. }) if checked.mismatch => {
@@ -25,7 +25,7 @@ use tokio::sync::oneshot;
25
25
  const HELLO: &str = "blake3/5khrmpntq2bjexseshc6ldklwnig56gbj23yvbxjbdcwestheahq/bytes";
26
26
 
27
27
  /// Store "hello" and check reads, then `corrupt` it and check that checked
28
- /// reads notice, and that `put` writes the block again.
28
+ /// reads notice, and that `put` leaves the block it finds: it never replaces.
29
29
  fn check(store: &KvCas, corrupt: impl FnOnce()) {
30
30
  let cid: Cid = store.put("hello".into()).unwrap();
31
31
  assert_eq!(cid, Blake3::of(b"hello").into());
@@ -59,7 +59,12 @@ fn check(store: &KvCas, corrupt: impl FnOnce()) {
59
59
  ));
60
60
  assert!(matches!(store.get_fact(&cid), Err(Error::Check { .. })));
61
61
  let _: Cid = store.put("hello".into()).unwrap();
62
- assert_eq!(store.get(&cid).unwrap().unwrap(), "hello");
62
+ assert_eq!(store.get_unchecked(&cid).unwrap().unwrap(), "wrong");
63
+ let _ = block_on(store.aput("hello".into())).unwrap();
64
+ assert_eq!(store.get_unchecked(&cid).unwrap().unwrap(), "wrong");
65
+ let put_from = store.put_from(Blake3::of(b"hello"), &mut &b"hello"[..], 5);
66
+ assert_eq!(put_from.unwrap(), cid);
67
+ assert_eq!(store.get_unchecked(&cid).unwrap().unwrap(), "wrong");
63
68
  }
64
69
 
65
70
  #[test]
@@ -72,7 +77,7 @@ fn memory() {
72
77
  }
73
78
 
74
79
  /// A key-value store that cannot read values, only whether they exist.
75
- struct Blind(MemoryKv);
80
+ struct Blind(MemoryKv, usize);
76
81
 
77
82
  impl Kv for Blind {
78
83
  fn get(&self, _: &[u8]) -> Result<Option<Bytes>, covalence_kv_trait::Error> {
@@ -90,13 +95,61 @@ impl Kv for Blind {
90
95
  fn exists(&self, key: &[u8]) -> Result<bool, covalence_kv_trait::Error> {
91
96
  self.0.exists(key)
92
97
  }
98
+ fn put_new(&self, key: &[u8], value: Bytes) -> Result<bool, covalence_kv_trait::Error> {
99
+ self.0.put_new(key, value)
100
+ }
101
+ fn aput_new<'a>(&'a self, key: &'a [u8], value: Bytes) -> KvFuture<'a, bool> {
102
+ self.0.aput_new(key, value)
103
+ }
104
+ fn put_new_from(
105
+ &self,
106
+ key: &[u8],
107
+ reader: &mut dyn std::io::Read,
108
+ len: u64,
109
+ ) -> Result<bool, covalence_kv_trait::Error> {
110
+ if self.0.exists(key)? {
111
+ reader
112
+ .read_exact(&mut vec![0; self.1])
113
+ .map_err(|source| covalence_kv_trait::Error::Io { source })?;
114
+ return Ok(false);
115
+ }
116
+ self.0.put_new_from(key, reader, len)
117
+ }
118
+ }
119
+
120
+ /// An existing block may succeed with invalid or failing input left unread.
121
+ #[test]
122
+ fn existing_blocks_leave_unused_stream_bytes_unchecked() {
123
+ for consumed in [0, 2] {
124
+ // A backend may skip immediately or after reading part of the source.
125
+ let store = KvCas::new(Blind(MemoryKv::default(), consumed).into_dyn());
126
+ let cid: Cid = store.put("hello".into()).unwrap();
127
+ let claim = Blake3::of(b"hello");
128
+ for data in [&b"wrong"[..], &b"bad"[..]] {
129
+ let mut source = std::io::Cursor::new(data);
130
+ assert_eq!(store.put_from(claim, &mut source, 5).unwrap(), cid);
131
+ assert_eq!(source.position(), consumed as u64);
132
+ }
133
+ // An error beyond the consumed prefix must not be observed either.
134
+ let mut source = std::io::Read::chain(std::io::Cursor::new(b"he"), FailingReader);
135
+ assert_eq!(store.put_from(claim, &mut source, 5).unwrap(), cid);
136
+ assert_eq!(source.get_ref().0.position(), consumed as u64);
137
+ }
138
+ }
139
+
140
+ struct FailingReader;
141
+
142
+ impl std::io::Read for FailingReader {
143
+ fn read(&mut self, _: &mut [u8]) -> std::io::Result<usize> {
144
+ Err(std::io::Error::other("source failed"))
145
+ }
93
146
  }
94
147
 
95
148
  /// `has` and `missing` ask whether keys exist rather than reading values,
96
149
  /// through `Arc<dyn Cas>` too.
97
150
  #[test]
98
151
  fn existence_without_reads() {
99
- let store = KvCas::new(Blind(MemoryKv::default()).into_dyn()).into_dyn();
152
+ let store = KvCas::new(Blind(MemoryKv::default(), 0).into_dyn()).into_dyn();
100
153
  let cid = store.put("hello".into()).unwrap();
101
154
  assert_eq!(store.put("hello".into()).unwrap(), cid);
102
155
  let missing: Cid = Blake3::of(b"missing").into();
@@ -352,12 +405,12 @@ impl Kv for Leaky {
352
405
  fn put(&self, _: &[u8], _: Bytes) -> Result<(), covalence_kv_trait::Error> {
353
406
  Ok(())
354
407
  }
355
- fn put_from(
408
+ fn put_new_from(
356
409
  &self,
357
410
  _: &[u8],
358
411
  reader: &mut dyn std::io::Read,
359
412
  _: u64,
360
- ) -> Result<(), covalence_kv_trait::Error> {
413
+ ) -> Result<bool, covalence_kv_trait::Error> {
361
414
  let _ = reader.read_to_end(&mut Vec::new());
362
415
  Err(covalence_kv_trait::Error::Backend {
363
416
  source: "abort failed".into(),
@@ -350,10 +350,14 @@ pub trait Cas: Get + Put + AsyncGet + AsyncPut {
350
350
  }
351
351
 
352
352
  /// Store the next `len` bytes of `reader`, whose raw BLAKE3 CID is `cid`,
353
- /// and return the key [`Put::put_fact`] would. Reads no further; stores
354
- /// nothing and fails with [`Error::Check`] if the bytes have another CID,
355
- /// or with [`Error::Io`] if `reader` fails or ends early. Reads them into
356
- /// memory by default; stores that can stream override it.
353
+ /// and return the key [`Put::put_fact`] would.
354
+ ///
355
+ /// - A store may return an existing block without consuming all of
356
+ /// `reader`; unread bytes are not checked.
357
+ /// - Otherwise, reads exactly `len` bytes and no further. Stores nothing
358
+ /// and fails with [`Error::Check`] if the bytes have another CID, or
359
+ /// with [`Error::Io`] if `reader` fails or ends early.
360
+ /// - Reads into memory by default; stores that can stream override it.
357
361
  fn put_from(&self, cid: Blake3, reader: &mut dyn Read, len: u64) -> Result<Cid, Error> {
358
362
  let mut bytes = Vec::new();
359
363
  let read = reader
@@ -17,13 +17,17 @@
17
17
  //! ```
18
18
  #![deny(missing_docs)]
19
19
 
20
- use std::{collections::BTreeMap, sync::RwLock};
20
+ use std::{
21
+ collections::BTreeMap,
22
+ io::{self, Read},
23
+ sync::RwLock,
24
+ };
21
25
 
22
26
  use covalence_kv_trait::{Error, Kv, KvFuture, ready};
23
27
  use covalence_lib_dasl::Value;
24
28
  use covalence_lib_data::Bytes;
25
29
 
26
- /// An in-memory store. It accepts every key.
30
+ /// An in-memory store. It accepts every key, and [`Kv::put_new`] is atomic.
27
31
  ///
28
32
  /// [`Kv::info`] is `{"name": "memory"}`, and [`Kv::stats`] counts `keys` and
29
33
  /// the `bytes` of their values.
@@ -48,6 +52,35 @@ impl Kv for MemoryKv {
48
52
  ready(self.put(key, value))
49
53
  }
50
54
 
55
+ fn put_new(&self, key: &[u8], value: Bytes) -> Result<bool, Error> {
56
+ let mut map = self.0.write().unwrap();
57
+ if map.contains_key(key) {
58
+ return Ok(false);
59
+ }
60
+ map.insert(key.into(), value);
61
+ Ok(true)
62
+ }
63
+
64
+ /// Reads the value into memory first, so the store is locked only to
65
+ /// insert it.
66
+ fn put_new_from(&self, key: &[u8], reader: &mut dyn Read, len: u64) -> Result<bool, Error> {
67
+ let mut value = Vec::new();
68
+ let read = reader
69
+ .take(len)
70
+ .read_to_end(&mut value)
71
+ .map_err(|source| Error::Io { source })?;
72
+ if (read as u64) < len {
73
+ return Err(Error::Io {
74
+ source: io::ErrorKind::UnexpectedEof.into(),
75
+ });
76
+ }
77
+ self.put_new(key, value.into())
78
+ }
79
+
80
+ fn aput_new<'a>(&'a self, key: &'a [u8], value: Bytes) -> KvFuture<'a, bool> {
81
+ ready(self.put_new(key, value))
82
+ }
83
+
51
84
  fn exists(&self, key: &[u8]) -> Result<bool, Error> {
52
85
  Ok(self.0.read().unwrap().contains_key(key))
53
86
  }
@@ -27,6 +27,36 @@ fn put_and_get() -> Result<(), Error> {
27
27
  Ok(())
28
28
  }
29
29
 
30
+ /// A new value goes only under a free key, also through `Arc<dyn Kv>`, and
31
+ /// racing writers of one key store once.
32
+ #[test]
33
+ fn new_values_never_replace() -> Result<(), Error> {
34
+ let store = store();
35
+ assert!(store.put_new(b"a", "one".into())?);
36
+ assert!(!store.put_new(b"a", "two".into())?);
37
+ assert!(!block_on(store.aput_new(b"a", "two".into()))?);
38
+ assert!(!store.put_new_from(b"a", &mut &b"two"[..], 3)?);
39
+ assert_eq!(store.get(b"a")?.unwrap(), "one");
40
+ assert!(store.put_new_from(b"b", &mut &b"two!"[..], 3)?);
41
+ assert_eq!(store.get(b"b")?.unwrap(), "two");
42
+ let error = store.put_new_from(b"c", &mut &b"ab"[..], 3).unwrap_err();
43
+ assert!(matches!(error, Error::Io { .. }), "{error}");
44
+ assert!(!store.exists(b"c")?);
45
+ let stored = std::thread::scope(|scope| {
46
+ let store = &store;
47
+ let racers: Vec<_> = (0..8u8)
48
+ .map(|i| scope.spawn(move || store.put_new(b"race", vec![i].into()).unwrap()))
49
+ .collect();
50
+ racers
51
+ .into_iter()
52
+ .map(|racer| racer.join().unwrap())
53
+ .filter(|&stored| stored)
54
+ .count()
55
+ });
56
+ assert_eq!(stored, 1);
57
+ Ok(())
58
+ }
59
+
30
60
  #[test]
31
61
  fn async_matches_sync() -> Result<(), Error> {
32
62
  let store = store();