tigerbeetle 0.0.34 → 0.0.37

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/ext/tb_client/extconf.rb +13 -13
  4. data/ext/tb_client/tigerbeetle/LICENSE +177 -0
  5. data/ext/tb_client/tigerbeetle/build.zig +2327 -0
  6. data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
  7. data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
  8. data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
  9. data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
  10. data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
  11. data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
  12. data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
  13. data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
  14. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
  15. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
  16. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
  17. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
  18. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
  19. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
  20. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
  21. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
  22. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
  23. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
  24. data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
  25. data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
  26. data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
  27. data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
  28. data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
  29. data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
  30. data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
  31. data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
  32. data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
  33. data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
  34. data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
  35. data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
  36. data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
  37. data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
  38. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
  39. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
  40. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
  41. data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
  42. data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
  43. data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
  44. data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
  45. data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
  46. data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
  47. data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
  48. data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
  49. data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
  50. data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
  51. data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
  52. data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
  53. data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
  54. data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
  55. data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
  56. data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
  57. data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
  58. data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
  59. data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
  60. data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
  61. data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
  62. data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
  63. data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
  64. data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
  65. data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
  66. data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
  67. data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
  68. data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
  69. data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
  70. data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
  71. data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
  72. data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
  73. data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
  74. data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
  75. data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
  76. data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
  77. data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
  78. data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
  79. data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
  80. data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
  81. data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
  82. data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
  83. data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
  84. data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
  85. data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
  86. data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
  87. data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
  88. data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
  89. data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
  90. data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
  91. data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
  92. data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
  93. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
  94. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
  95. data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
  96. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
  97. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
  98. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
  99. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
  100. data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
  101. data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
  102. data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
  103. data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
  104. data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
  105. data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
  106. data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
  107. data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
  108. data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
  109. data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
  110. data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
  111. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
  112. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
  113. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
  114. data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
  115. data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
  116. data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
  117. data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
  118. data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
  119. data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
  120. data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
  121. data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
  122. data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
  123. data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
  124. data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
  125. data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
  126. data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
  127. data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
  128. data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
  129. data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
  130. data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
  131. data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
  132. data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
  133. data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
  134. data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
  135. data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
  136. data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
  137. data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
  138. data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
  139. data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
  140. data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
  141. data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
  142. data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
  143. data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
  144. data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
  145. data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
  146. data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
  147. data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
  148. data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
  149. data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
  150. data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
  151. data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
  152. data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
  153. data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
  154. data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
  155. data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
  156. data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
  157. data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
  158. data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
  159. data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
  160. data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
  161. data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
  162. data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
  163. data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
  164. data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
  165. data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
  166. data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
  167. data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
  168. data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
  169. data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
  170. data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
  171. data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
  172. data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
  173. data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
  174. data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
  175. data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
  176. data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
  177. data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
  178. data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
  179. data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
  180. data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
  181. data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
  182. data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
  183. data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
  184. data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
  185. data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
  186. data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
  187. data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
  188. data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
  189. data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
  190. data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
  191. data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
  192. data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
  193. data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
  194. data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
  195. data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
  196. data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
  197. data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
  198. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
  199. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
  200. data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
  201. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
  202. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
  203. data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
  204. data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
  205. data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
  206. data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
  207. data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
  208. data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
  209. data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
  210. data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
  211. data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
  212. data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
  213. data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
  214. data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
  215. data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
  216. data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
  217. data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
  218. data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
  219. data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
  220. data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
  221. data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
  222. data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
  223. data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
  224. data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
  225. data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
  226. data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
  227. data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
  228. data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
  229. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
  230. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
  231. data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
  232. data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
  233. data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
  234. data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
  235. data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
  236. data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
  237. data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
  238. data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
  239. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
  240. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
  241. data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
  242. data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
  243. data/lib/tb_client/shared_lib.rb +12 -5
  244. data/lib/tigerbeetle/client.rb +1 -1
  245. data/lib/tigerbeetle/platforms.rb +9 -0
  246. data/lib/tigerbeetle/version.rb +2 -2
  247. data/tigerbeetle.gemspec +22 -5
  248. metadata +242 -3
  249. data/ext/tb_client/pkg.tar.gz +0 -0
@@ -0,0 +1,843 @@
1
+ //! Memory tables for an LSM (log‑structured merge) stage.
2
+ //!
3
+ //! Each `tree.zig` maintains two in‑memory tables:
4
+ //! - Mutable table: accepts all updates (e.g., `Tree.put`).
5
+ //! - Immutable table: sorted read‑only staging area for the next flush to disk.
6
+ //!
7
+ //! New puts are appended to the mutable table. When the mutable table fills up
8
+ //! — or rather when we reach a bar boundary - the mutable table is compacted into
9
+ //! the immutable table. Compaction sorts and deduplicates entries and prepares them for the LSM
10
+ //! `compaction.zig` to be flushed to disk.
11
+ //!
12
+ //! Optimizations
13
+ //! 1) Sorted runs on every beat:
14
+ //! - At the end of each beat, the mutable table’s suffix is sorted and
15
+ //! deduplicated, and the suffix offset advances.
16
+ //! - By the end of a bar, the mutable table is a sequence of sorted arrays,
17
+ //! which are then merged and deduplicated.
18
+ //!
19
+ //! 2) Deferred disk flush:
20
+ //! - If the immutable table is not sufficiently full, `compaction.zig` may
21
+ //! choose not to flush to disk. Instead, we retain the current immutable
22
+ //! table and absorb the mutable table into it.
23
+
24
+ const std = @import("std");
25
+ const mem = std.mem;
26
+ const assert = std.debug.assert;
27
+
28
+ const constants = @import("../constants.zig");
29
+ const binary_search = @import("binary_search.zig");
30
+ const stdx = @import("stdx");
31
+ const maybe = stdx.maybe;
32
+
33
+ const KWayMergeIteratorType = @import("k_way_merge.zig").KWayMergeIteratorType;
34
+ const ScratchMemory = @import("scratch_memory.zig").ScratchMemory;
35
+ const Pending = error{Pending};
36
+
37
+ pub fn TableMemoryType(comptime Table: type) type {
38
+ const Key = Table.Key;
39
+ const Value = Table.Value;
40
+ const key_from_value = Table.key_from_value;
41
+
42
+ return struct {
43
+ const TableMemory = @This();
44
+
45
+ values: []Value,
46
+ value_context: ValueContext,
47
+ mutability: Mutability,
48
+ name: []const u8,
49
+
50
+ // Maintains per-table mutable state that must be snapshotted for “scopes”.
51
+ // When a scope is opened (e.g., in `tree.zig`), we copy `ValueContext` so we can
52
+ // roll back both the count and the sorted-run tracker if the scope is discarded.
53
+ pub const ValueContext = struct {
54
+ count: u32 = 0,
55
+ run_tracker: SortedRunTracker,
56
+ };
57
+
58
+ const Mutability = union(enum) {
59
+ mutable: struct {
60
+ // This buffer is shared between all `Tables` for the radix sort.
61
+ // It is passed down from the forest.
62
+ radix_buffer: *ScratchMemory,
63
+ },
64
+ immutable: struct {
65
+ // An empty table has nothing to flush.
66
+ flushed: bool = true,
67
+ // This field is only used for assertions, to verify that we don't absorb the
68
+ // mutable table immediately prior to checkpoint.
69
+ absorbed: bool = false,
70
+ snapshot_min: u64 = 0,
71
+ },
72
+ };
73
+
74
+ // Where the sorted run originated; affects merge precedence on equal keys.
75
+ // We prefer the immutable ones to keep ordering for deduplication (last key wins).
76
+ const RunOrigin = enum { mutable, immutable };
77
+
78
+ // A contiguous sorted range in `values[min..max)`.
79
+ const SortedRun = struct {
80
+ min: u32,
81
+ max: u32, // exclusive.
82
+ origin: RunOrigin, // Where the run originated; affects merge precedence on equal keys.
83
+ };
84
+
85
+ // At most: LSM compactions + one sort() call + one immutable run for `absorb`.
86
+ const sorted_runs_max = constants.lsm_compaction_ops + 2;
87
+
88
+ // Merge context for the k-way iterator across all runs in the tracker.
89
+ const MergeContext = struct {
90
+ streams: [sorted_runs_max][]const Value,
91
+ streams_count: u32,
92
+
93
+ fn stream_peek(
94
+ context: *const MergeContext,
95
+ stream_index: u32,
96
+ ) Pending!?Key {
97
+ // TODO: Enable the asserts once `constants.verify` is disabled on release.
98
+ //assert(stream_index < context.streams_count);
99
+ const stream = context.streams[stream_index];
100
+ if (stream.len == 0) return null;
101
+ return key_from_value(&stream[0]);
102
+ }
103
+
104
+ fn stream_pop(context: *MergeContext, stream_index: u32) Value {
105
+ // TODO: Enable the asserts once `constants.verify` is disabled on release.
106
+ //assert(stream_index < context.streams_count);
107
+ const stream = context.streams[stream_index];
108
+ context.streams[stream_index] = stream[1..];
109
+ return stream[0];
110
+ }
111
+ };
112
+
113
+ const KWayMergeIterator = KWayMergeIteratorType(
114
+ MergeContext,
115
+ Key,
116
+ Value,
117
+ .{
118
+ .streams_max = sorted_runs_max,
119
+ .deduplicate = false,
120
+ },
121
+ key_from_value,
122
+ MergeContext.stream_peek,
123
+ MergeContext.stream_pop,
124
+ );
125
+
126
+ const SortedRunTracker = struct {
127
+ /// Invariants:
128
+ /// - Runs are in ascending order.
129
+ /// - Runs have no gaps between them.
130
+ /// - There is at most one run with run.origin=.immutable
131
+ runs: [sorted_runs_max]SortedRun,
132
+ runs_count: u8,
133
+
134
+ fn init() SortedRunTracker {
135
+ return .{
136
+ .runs = undefined,
137
+ .runs_count = 0,
138
+ };
139
+ }
140
+
141
+ fn add(tracker: *SortedRunTracker, run: SortedRun) void {
142
+ if (run.min == run.max) return; // Ignore empty runs.
143
+
144
+ tracker.runs[tracker.runs_count] = run;
145
+ tracker.runs_count += 1;
146
+ }
147
+
148
+ // Adds a new run at the front and shifts the other runs by the offset to maintain
149
+ // the invariant that no run overlaps and they have no gaps.
150
+ fn add_front_and_propagate_offset(tracker: *SortedRunTracker, run: SortedRun) void {
151
+ if (run.min == run.max) return; // Ignore empty runs.
152
+ assert(tracker.runs_count + 1 <= tracker.runs.len);
153
+ stdx.copy_right(
154
+ .exact,
155
+ SortedRun,
156
+ tracker.runs[1 .. tracker.runs_count + 1],
157
+ tracker.runs[0..tracker.runs_count],
158
+ );
159
+
160
+ tracker.runs[0] = run;
161
+ tracker.runs_count += 1;
162
+
163
+ //Propagate the new offset to the remaining runs.
164
+ for (tracker.runs[1..tracker.count()]) |*run_old| {
165
+ run_old.min += run.max;
166
+ run_old.max += run.max;
167
+ }
168
+ }
169
+
170
+ fn reset(tracker: *SortedRunTracker) void {
171
+ tracker.* = .{
172
+ .runs = undefined,
173
+ .runs_count = 0,
174
+ };
175
+ }
176
+
177
+ fn count(tracker: *const SortedRunTracker) u32 {
178
+ return tracker.runs_count;
179
+ }
180
+
181
+ fn merge_context(tracker: *const SortedRunTracker, values: []const Value) MergeContext {
182
+ var context = MergeContext{
183
+ .streams = undefined,
184
+ .streams_count = undefined,
185
+ };
186
+
187
+ var stream_idx: u32 = 0;
188
+
189
+ // Place the immutable run first so smaller stream_id wins on ties.
190
+ for (tracker.runs[0..tracker.count()]) |run| {
191
+ if (run.origin != .immutable) continue;
192
+ context.streams[stream_idx] = values[run.min..run.max];
193
+ stream_idx += 1;
194
+ break;
195
+ }
196
+ // Now place all the mutable runs.
197
+ for (tracker.runs[0..tracker.count()]) |run| {
198
+ if (run.origin == .immutable) continue;
199
+ context.streams[stream_idx] = values[run.min..run.max];
200
+ stream_idx += 1;
201
+ }
202
+ context.streams_count = stream_idx;
203
+ return context;
204
+ }
205
+
206
+ fn last(tracker: *const SortedRunTracker) ?*const SortedRun {
207
+ if (tracker.count() == 0) return null;
208
+ return &tracker.runs[tracker.count() - 1];
209
+ }
210
+
211
+ fn assert_invariants(tracker: *const SortedRunTracker, table_count: u32) void {
212
+ const runs_count = tracker.count();
213
+
214
+ if (runs_count == 0) return;
215
+
216
+ assert(tracker.runs[0].min == 0);
217
+ assert(tracker.runs[runs_count - 1].max == table_count);
218
+
219
+ for (tracker.runs[0 .. runs_count - 1], tracker.runs[1..runs_count]) |a, b| {
220
+ assert(a.min < b.min); // Ordered and we ignore empty runs.
221
+ assert(a.max == b.min); // No gaps.
222
+ }
223
+
224
+ var immutable_runs: u1 = 0;
225
+ for (tracker.runs[0..runs_count]) |run| {
226
+ immutable_runs += @intFromBool(run.origin == .immutable);
227
+ }
228
+ assert(immutable_runs == 0 or immutable_runs == 1);
229
+ }
230
+ };
231
+
232
+ // Merge values with identical keys (last one wins) and collapse tombstones for
233
+ // secondary indexes in a streaming fashion.
234
+ const DedupSink = struct {
235
+ out: []Value,
236
+ target_index: u32 = 0,
237
+
238
+ // Holds the current candidate that may merge with the next item.
239
+ pending: ?Value = null,
240
+
241
+ pub fn init(out: []Value) DedupSink {
242
+ return .{ .out = out };
243
+ }
244
+
245
+ // Streamed equivalent of `deduplicate`.
246
+ pub fn push(self: *DedupSink, value: Value) void {
247
+ const pending = self.pending orelse {
248
+ // Starting a new run with a pending `value`.
249
+ self.pending = value;
250
+ return;
251
+ };
252
+
253
+ // If we're at the end of the source, there is no next value, so the next value
254
+ // can't be equal.
255
+ if (key_from_value(&pending) == key_from_value(&value)) {
256
+ if (Table.usage == .secondary_index) {
257
+ // Secondary index optimization --- cancel out put and remove.
258
+ // NB: while this prevents redundant tombstones from getting to disk, we
259
+ // still spend some extra CPU work to sort the entries in memory. Ideally,
260
+ // we annihilate tombstones immediately, before sorting, but that's tricky
261
+ // to do with scopes.
262
+ assert(Table.tombstone(&pending) != Table.tombstone(&value));
263
+ // Effect: consume both and produce nothing for this key.
264
+ self.pending = null; // drop both
265
+ } else {
266
+ // The last value in a run of duplicates needs to be the one that ends up in
267
+ // target.
268
+ // Effect: keep the slot, overwrite winner with the newer value.
269
+ self.pending = value; // last wins
270
+ }
271
+ } else {
272
+ // New key encountered: flush previous winner, start a new run.
273
+ self.out[self.target_index] = pending;
274
+ self.target_index += 1;
275
+ self.pending = value;
276
+ }
277
+ }
278
+
279
+ pub fn finish(self: *DedupSink) u32 {
280
+ // Flush the final pending value, if any.
281
+ if (self.pending) |p| {
282
+ self.out[self.target_index] = p;
283
+ self.target_index += 1;
284
+ self.pending = null;
285
+ }
286
+
287
+ // At this point, target_index is the number of deduplicated items written.
288
+ if (constants.verify) {
289
+ if (0 < self.target_index) {
290
+ for (
291
+ self.out[0 .. self.target_index - 1],
292
+ self.out[1..self.target_index],
293
+ ) |*value, *value_next| {
294
+ assert(key_from_value(value) < key_from_value(value_next));
295
+ }
296
+ }
297
+ }
298
+
299
+ return self.target_index;
300
+ }
301
+ };
302
+
303
+ pub fn init(
304
+ table: *TableMemory,
305
+ allocator: mem.Allocator,
306
+ radix_buffer: *ScratchMemory,
307
+ mutability: std.meta.Tag(Mutability),
308
+ name: []const u8,
309
+ options: struct {
310
+ value_count_limit: u32,
311
+ },
312
+ ) !void {
313
+ assert(options.value_count_limit <= Table.value_count_max);
314
+ assert(radix_buffer.state == .free);
315
+
316
+ table.* = .{
317
+ .value_context = .{
318
+ .run_tracker = SortedRunTracker.init(),
319
+ },
320
+ .mutability = switch (mutability) {
321
+ .mutable => .{ .mutable = .{
322
+ .radix_buffer = radix_buffer,
323
+ } },
324
+ .immutable => .{ .immutable = .{} },
325
+ },
326
+ .name = name,
327
+ .values = undefined,
328
+ };
329
+
330
+ // TODO This would ideally be value_count_limit, but needs to be value_count_max to
331
+ // ensure that memory table coalescing is deterministic even if the batch limit changes.
332
+ table.values = try allocator.alloc(Value, Table.value_count_max);
333
+ errdefer allocator.free(table.values);
334
+ }
335
+
336
+ pub fn deinit(table: *TableMemory, allocator: mem.Allocator) void {
337
+ allocator.free(table.values);
338
+ }
339
+
340
+ pub fn reset(table: *TableMemory) void {
341
+ const mutability: Mutability = switch (table.mutability) {
342
+ .immutable => .{ .immutable = .{} },
343
+ .mutable => .{ .mutable = .{
344
+ .radix_buffer = table.mutability.mutable.radix_buffer,
345
+ } },
346
+ };
347
+
348
+ table.value_context.run_tracker.reset();
349
+
350
+ table.* = .{
351
+ .values = table.values,
352
+ .value_context = .{
353
+ .run_tracker = table.value_context.run_tracker,
354
+ },
355
+ .mutability = mutability,
356
+ .name = table.name,
357
+ };
358
+ }
359
+
360
+ pub fn count(table: *const TableMemory) u32 {
361
+ return table.value_context.count;
362
+ }
363
+
364
+ pub fn values_used(table: *const TableMemory) []Value {
365
+ return table.values[0..table.count()];
366
+ }
367
+
368
+ // Appends a `value`. If it is strictly greater than the previous key,
369
+ // expand the last run by 1; otherwise the suffix will be sorted later.
370
+ pub fn put(table: *TableMemory, value: *const Value) void {
371
+ assert(table.mutability == .mutable);
372
+ assert(table.count() < table.values.len);
373
+
374
+ const run_count = table.value_context.run_tracker.count();
375
+ if (run_count > 0 and
376
+ table.value_context.run_tracker.runs[run_count - 1].max == table.count())
377
+ {
378
+ const expand: bool = table.count() == 0 or
379
+ key_from_value(&table.values[table.count() - 1]) <
380
+ key_from_value(value);
381
+ table.value_context.run_tracker.runs[run_count - 1].max += @intFromBool(expand);
382
+ }
383
+
384
+ table.values[table.count()] = value.*;
385
+ table.value_context.count += 1;
386
+ }
387
+
388
+ /// This must be called on sorted tables (single run from 0..count).
389
+ pub fn get(table: *TableMemory, key: Key) ?*const Value {
390
+ assert(table.count() <= table.values.len);
391
+ assert(table.sorted());
392
+
393
+ if (!table.key_range_contains(key)) {
394
+ return null;
395
+ }
396
+
397
+ return binary_search.binary_search_values(
398
+ Key,
399
+ Value,
400
+ key_from_value,
401
+ table.values_used(),
402
+ key,
403
+ .{ .mode = .upper_bound },
404
+ );
405
+ }
406
+
407
+ fn finalize(table_immutable: *TableMemory, snapshot_min: u64) void {
408
+ assert(table_immutable.mutability == .immutable);
409
+
410
+ table_immutable.mutability = .{ .immutable = .{
411
+ .flushed = table_immutable.count() == 0,
412
+ .snapshot_min = snapshot_min,
413
+ } };
414
+ table_immutable.value_context.run_tracker.reset();
415
+ table_immutable.value_context.run_tracker.add(.{
416
+ .min = 0,
417
+ .max = table_immutable.count(),
418
+ .origin = .immutable,
419
+ });
420
+ }
421
+
422
+ // Merge `table_mutable` runs into `table_immutable`.
423
+ pub fn compact(
424
+ table_immutable: *TableMemory,
425
+ table_mutable: *TableMemory,
426
+ snapshot_min: u64,
427
+ ) void {
428
+ assert(table_immutable.mutability == .immutable);
429
+ maybe(table_immutable.mutability.immutable.absorbed);
430
+ assert(table_mutable.mutability == .mutable);
431
+ maybe(table_mutable.sorted());
432
+ defer assert(table_immutable.sorted());
433
+ defer assert(table_mutable.count() == 0);
434
+
435
+ table_mutable.value_context.run_tracker.assert_invariants(table_mutable.count());
436
+
437
+ if (table_mutable.sorted()) {
438
+ // Fast-path: single contiguous sorted run: swap buffers.
439
+ assert(table_mutable.values.len == table_immutable.values.len);
440
+ std.mem.swap([]Value, &table_mutable.values, &table_immutable.values);
441
+
442
+ table_immutable.value_context.count = table_mutable.count();
443
+ } else {
444
+ var merge_context = table_mutable.value_context
445
+ .run_tracker.merge_context(table_mutable.values_used());
446
+ var merge_iterator = KWayMergeIterator.init(
447
+ &merge_context,
448
+ @intCast(merge_context.streams_count),
449
+ .ascending,
450
+ );
451
+
452
+ // Deduplicate values in streaming fashion.
453
+ var dedup_sink = DedupSink.init(table_immutable.values);
454
+ while (merge_iterator.pop() catch unreachable) |value| {
455
+ dedup_sink.push(value);
456
+ }
457
+ table_immutable.value_context.count = dedup_sink.finish();
458
+ }
459
+
460
+ table_mutable.reset();
461
+ table_immutable.finalize(snapshot_min);
462
+ assert(table_immutable.sorted());
463
+ }
464
+
465
+ // Absorb the current immutable table into the mutable one,
466
+ // then re-materialize a compact immutable table.
467
+ pub fn absorb(
468
+ table_immutable: *TableMemory,
469
+ table_mutable: *TableMemory,
470
+ snapshot_min: u64,
471
+ ) void {
472
+ assert(table_immutable.mutability == .immutable);
473
+ maybe(table_immutable.mutability.immutable.absorbed);
474
+ assert(table_immutable.sorted());
475
+ assert(table_mutable.mutability == .mutable);
476
+ maybe(table_mutable.sorted());
477
+ defer assert(table_immutable.sorted());
478
+
479
+ const values_count_limit = table_immutable.values.len;
480
+ assert(table_immutable.count() <= values_count_limit);
481
+ assert(table_mutable.count() <= values_count_limit);
482
+ assert(table_immutable.count() + table_mutable.count() <= values_count_limit);
483
+
484
+ if (table_mutable.count() == 0) return;
485
+
486
+ // Copy immutable after the current mutable tail and mark as an immutable run,
487
+ // so the merge prefers its entries on key ties.
488
+ const tables_combined_count = table_immutable.count() + table_mutable.count();
489
+
490
+ // Because `table_mutable` is likely to be smaller then `tabel_immutable` we:
491
+ // 1. Copy the values from `table_mutable` into `table_immutable`.
492
+ // 2. We swap the backing arrays so that `table_mutable` has all the values.
493
+ // 3. We add the new run from `table_immutable` at the beginning.
494
+ stdx.copy_disjoint(
495
+ .inexact,
496
+ Value,
497
+ table_immutable.values[table_immutable.count()..],
498
+ table_mutable.values[0..table_mutable.count()],
499
+ );
500
+ std.mem.swap([]Value, &table_mutable.values, &table_immutable.values);
501
+
502
+ table_mutable.value_context.run_tracker.add_front_and_propagate_offset(.{
503
+ .min = 0,
504
+ .max = table_immutable.count(),
505
+ .origin = .immutable,
506
+ });
507
+
508
+ table_mutable.value_context.count = tables_combined_count;
509
+ table_mutable.value_context.run_tracker.assert_invariants(table_mutable.count());
510
+
511
+ table_immutable.mutability.immutable.absorbed = true;
512
+ table_immutable.compact(table_mutable, snapshot_min);
513
+
514
+ // One fully sorted run or all keys are annihilated.
515
+ assert(table_immutable.value_context.run_tracker.count() <= 1);
516
+ assert(table_mutable.value_context.run_tracker.count() == 0);
517
+ }
518
+
519
+ // Fully sort the table if needed. Produces a single run [0..count).
520
+ pub fn sort(table: *TableMemory) void {
521
+ assert(table.mutability == .mutable);
522
+ defer table.value_context.run_tracker.assert_invariants(table.count());
523
+
524
+ if (!table.sorted()) {
525
+ _ = table.mutable_sort_suffix_from_offset(0);
526
+ table.value_context.run_tracker.reset();
527
+ table.value_context.run_tracker.add(.{
528
+ .min = 0,
529
+ .max = table.count(),
530
+ .origin = .mutable,
531
+ });
532
+ }
533
+ }
534
+
535
+ // When true, `values` is strictly ascending-ordered (no duplicates).
536
+ fn sorted(table: *const TableMemory) bool {
537
+ // Empty table is considered sorted.
538
+ if (table.count() == 0) return true;
539
+
540
+ // Only one sorted run can exist if it is sorted.
541
+ if (table.value_context.run_tracker.count() != 1) return false;
542
+
543
+ const last_run = table.value_context.run_tracker.last().?;
544
+ assert(last_run.min == 0);
545
+ assert(last_run.max <= table.count());
546
+
547
+ return table.count() == last_run.max;
548
+ }
549
+
550
+ pub fn sort_suffix(table: *TableMemory) void {
551
+ assert(table.mutability == .mutable);
552
+ defer table.value_context.run_tracker.assert_invariants(table.count());
553
+
554
+ if (table.sorted()) return;
555
+
556
+ const sort_suffix_offset = if (table.value_context.run_tracker.last()) |last_run|
557
+ last_run.max
558
+ else
559
+ 0;
560
+
561
+ assert(sort_suffix_offset <= table.count());
562
+
563
+ if (sort_suffix_offset == table.count()) return;
564
+
565
+ const run = table.mutable_sort_suffix_from_offset(sort_suffix_offset);
566
+ assert(run.min <= run.max);
567
+ assert(run.max == table.count());
568
+ assert(sort_suffix_offset <= run.max);
569
+ table.value_context.run_tracker.add(run);
570
+ }
571
+
572
+ fn mutable_sort_suffix_from_offset(table: *TableMemory, offset: u32) SortedRun {
573
+ assert(table.mutability == .mutable);
574
+ assert(offset == 0 or offset == table.value_context.run_tracker.last().?.max);
575
+ assert(offset <= table.count());
576
+
577
+ const radix_buffer_values = table.mutability.mutable.radix_buffer.acquire(
578
+ Value,
579
+ table.count(),
580
+ );
581
+ defer table.mutability.mutable.radix_buffer.release(Value, radix_buffer_values);
582
+
583
+ const target_count = sort_suffix_from_offset(
584
+ table.values_used(),
585
+ radix_buffer_values,
586
+ offset,
587
+ );
588
+ table.value_context.count = target_count;
589
+ return .{ .min = offset, .max = target_count, .origin = .mutable };
590
+ }
591
+
592
+ // Returns the new length of `values`. Values are deduplicated after sorting, so the
593
+ // returned count may be less than or equal to the original `values.len`.
594
+ fn sort_suffix_from_offset(values: []Value, values_scratch: []Value, offset: u32) u32 {
595
+ assert(values.len == values_scratch.len);
596
+ assert(offset <= values.len);
597
+
598
+ stdx.radix_sort(Key, Value, key_from_value, values[offset..], values_scratch[offset..]);
599
+
600
+ // Deduplicate values in streaming fashion.
601
+ var dedup_sink = DedupSink.init(values[offset..]);
602
+ for (values[offset..]) |value| {
603
+ dedup_sink.push(value);
604
+ }
605
+ const target_count = offset + dedup_sink.finish();
606
+
607
+ return target_count;
608
+ }
609
+
610
+ pub fn key_range_contains(table: *const TableMemory, key: Key) bool {
611
+ assert(table.sorted());
612
+
613
+ if (table.count() == 0) return false;
614
+ return table.key_min() <= key and key <= table.key_max();
615
+ }
616
+
617
+ pub fn key_min(table: *const TableMemory) Key {
618
+ const values = table.values_used();
619
+
620
+ assert(values.len > 0);
621
+ assert(table.sorted());
622
+
623
+ return key_from_value(&values[0]);
624
+ }
625
+
626
+ pub fn key_max(table: *const TableMemory) Key {
627
+ const values = table.values_used();
628
+
629
+ assert(values.len > 0);
630
+ assert(table.sorted());
631
+
632
+ return key_from_value(&values[values.len - 1]);
633
+ }
634
+ };
635
+ }
636
+
637
+ const TestHelper = struct {
638
+ pub const TableUsage = enum {
639
+ general,
640
+ secondary_index,
641
+ };
642
+
643
+ fn TestTableType(comptime mode: TableUsage) type {
644
+ return struct {
645
+ const Key = u32;
646
+ const Value = struct { key: Key, version: u32, tombstone: bool };
647
+ const value_count_max = 16;
648
+ const usage = mode;
649
+
650
+ pub inline fn key_from_value(v: *const Value) Key {
651
+ return v.key;
652
+ }
653
+ pub fn tombstone(v: *const Value) bool {
654
+ return v.tombstone;
655
+ }
656
+ };
657
+ }
658
+
659
+ fn create_table_immutable(
660
+ comptime TableType: type,
661
+ gpa: std.mem.Allocator,
662
+ value_count_limit: u32,
663
+ radix_buffer: *ScratchMemory,
664
+ ) !TableType {
665
+ var table_immutable: TableType = undefined;
666
+ try table_immutable.init(
667
+ gpa,
668
+ radix_buffer,
669
+ .immutable,
670
+ "immutable",
671
+ .{ .value_count_limit = value_count_limit },
672
+ );
673
+ return table_immutable;
674
+ }
675
+
676
+ fn create_table_mutable(
677
+ comptime TableType: type,
678
+ gpa: std.mem.Allocator,
679
+ value_count_limit: u32,
680
+ radix_buffer: *ScratchMemory,
681
+ ) !TableType {
682
+ var table_mutable: TableType = undefined;
683
+ try table_mutable.init(
684
+ gpa,
685
+ radix_buffer,
686
+ .mutable,
687
+ "mutable",
688
+ .{ .value_count_limit = value_count_limit },
689
+ );
690
+ return table_mutable;
691
+ }
692
+ };
693
+
694
+ test "table_memory: merge and absorb (last wins across streams)" {
695
+ const testing = std.testing;
696
+ const Snap = stdx.Snap;
697
+ const snap = Snap.snap_fn("src");
698
+
699
+ const Table = TestHelper.TestTableType(.general);
700
+ const Value = Table.Value;
701
+ const TableMemory = TableMemoryType(Table);
702
+
703
+ const alloc = testing.allocator;
704
+
705
+ var radix_buffer: ScratchMemory = try .init(alloc, Table.value_count_max * @sizeOf(Value));
706
+ defer radix_buffer.deinit(alloc);
707
+
708
+ var table_immutable: TableMemory = try TestHelper.create_table_immutable(
709
+ TableMemory,
710
+ alloc,
711
+ Table.value_count_max,
712
+ &radix_buffer,
713
+ );
714
+ defer table_immutable.deinit(alloc);
715
+
716
+ var table_mutable: TableMemory = try TestHelper.create_table_mutable(
717
+ TableMemory,
718
+ alloc,
719
+ Table.value_count_max,
720
+ &radix_buffer,
721
+ );
722
+ defer table_mutable.deinit(alloc);
723
+
724
+ table_mutable.put(&Value{ .key = 2, .version = 0, .tombstone = false });
725
+ table_mutable.put(&Value{ .key = 4, .version = 0, .tombstone = false });
726
+ table_mutable.sort();
727
+
728
+ table_immutable.compact(&table_mutable, 0);
729
+ assert(table_immutable.sorted());
730
+ assert(table_mutable.count() == 0);
731
+
732
+ table_mutable.put(&Value{ .key = 2, .version = 1, .tombstone = false });
733
+ table_mutable.put(&Value{ .key = 5, .version = 0, .tombstone = false });
734
+ table_mutable.sort();
735
+
736
+ table_immutable.absorb(&table_mutable, 0);
737
+
738
+ assert(table_immutable.sorted());
739
+ assert(table_mutable.count() == 0);
740
+ assert(table_immutable.count() == 3);
741
+
742
+ var keys: [3]struct { Table.Key, u32 } = undefined;
743
+
744
+ for (table_immutable.values_used(), 0..) |value, i| {
745
+ keys[i] = .{ value.key, value.version };
746
+ }
747
+
748
+ try snap(@src(),
749
+ \\{ { 2, 1 }, { 4, 0 }, { 5, 0 } }
750
+ ).diff_fmt("{any}", .{keys});
751
+ }
752
+
753
+ test "table_memory: compact and deduplicate across runs" {
754
+ const testing = std.testing;
755
+ const Snap = stdx.Snap;
756
+ const snap = Snap.snap_fn("src");
757
+
758
+ const Table = TestHelper.TestTableType(.general);
759
+ const Value = Table.Value;
760
+ const TableMemory = TableMemoryType(Table);
761
+
762
+ const alloc = testing.allocator;
763
+
764
+ var radix_buffer: ScratchMemory = try .init(alloc, Table.value_count_max * @sizeOf(Value));
765
+ defer radix_buffer.deinit(alloc);
766
+
767
+ var table_immutable: TableMemory = try TestHelper.create_table_immutable(
768
+ TableMemory,
769
+ alloc,
770
+ Table.value_count_max,
771
+ &radix_buffer,
772
+ );
773
+ defer table_immutable.deinit(alloc);
774
+
775
+ var table_mutable: TableMemory = try TestHelper.create_table_mutable(
776
+ TableMemory,
777
+ alloc,
778
+ Table.value_count_max,
779
+ &radix_buffer,
780
+ );
781
+ defer table_mutable.deinit(alloc);
782
+
783
+ table_mutable.put(&Value{ .key = 2, .version = 0, .tombstone = false });
784
+ table_mutable.put(&Value{ .key = 2, .version = 1, .tombstone = false });
785
+ table_mutable.sort_suffix();
786
+
787
+ table_mutable.put(&Value{ .key = 2, .version = 2, .tombstone = false });
788
+ table_mutable.put(&Value{ .key = 2, .version = 3, .tombstone = false });
789
+ table_mutable.sort_suffix();
790
+
791
+ table_immutable.compact(&table_mutable, 0);
792
+ assert(table_immutable.sorted());
793
+ assert(table_mutable.count() == 0);
794
+ assert(table_immutable.count() == 1);
795
+
796
+ var keys: [1]struct { Table.Key, u32 } = undefined;
797
+
798
+ for (table_immutable.values_used(), 0..) |value, i| {
799
+ keys[i] = .{ value.key, value.version };
800
+ }
801
+
802
+ try snap(@src(),
803
+ \\{ { 2, 3 } }
804
+ ).diff_fmt("{any}", .{keys});
805
+ }
806
+
807
+ test "table_memory (secondary): annhiliation yields zero after deduplicate" {
808
+ const testing = std.testing;
809
+
810
+ const Table = TestHelper.TestTableType(.secondary_index);
811
+ const Value = Table.Value;
812
+ const TableMemory = TableMemoryType(Table);
813
+
814
+ const alloc = testing.allocator;
815
+
816
+ var radix_buffer: ScratchMemory = try .init(alloc, Table.value_count_max * @sizeOf(Value));
817
+ defer radix_buffer.deinit(alloc);
818
+
819
+ var table_immutable: TableMemory = try TestHelper.create_table_immutable(
820
+ TableMemory,
821
+ alloc,
822
+ Table.value_count_max,
823
+ &radix_buffer,
824
+ );
825
+ defer table_immutable.deinit(alloc);
826
+
827
+ var table_mutable: TableMemory = try TestHelper.create_table_mutable(
828
+ TableMemory,
829
+ alloc,
830
+ Table.value_count_max,
831
+ &radix_buffer,
832
+ );
833
+ defer table_mutable.deinit(alloc);
834
+
835
+ table_mutable.put(&Value{ .key = 2, .version = 0, .tombstone = false });
836
+ table_mutable.put(&Value{ .key = 2, .version = 0, .tombstone = true });
837
+ table_mutable.sort_suffix();
838
+
839
+ table_immutable.compact(&table_mutable, 0);
840
+ assert(table_immutable.sorted());
841
+ assert(table_mutable.count() == 0);
842
+ assert(table_immutable.count() == 0);
843
+ }