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,739 @@
1
+ //! K-way merge via a loser tree algorithm (Knuth Volume 3 p. 253).
2
+ //! Merges k sorted streams using a tournament (loser) tree.
3
+ //! The current global winner lives in `win_key`/`win_id`. Internal nodes store
4
+ //! the losers of the last comparisons along the root-to-leaf paths in a
5
+ //! struct-of-arrays layout (`loser_keys`/`loser_ids`).
6
+ //!
7
+ //! 0 (winner)
8
+ //!
9
+ //! 1
10
+ //! / \
11
+ //! 2 3
12
+ //! / \ / \
13
+ //! 4 5 6 7
14
+ //! -------------
15
+ //! K input streams
16
+ //!
17
+ //! The internal nodes are organized in a flat Eytzinger layout.
18
+ //! That is the tree above is stored as [1][2][3][4][5][6][7].
19
+ //! Empty streams are represented with a sentinel node that always loses against real nodes.
20
+ //!
21
+ const std = @import("std");
22
+ const stdx = @import("stdx");
23
+ const assert = std.debug.assert;
24
+ const maybe = stdx.maybe;
25
+ const math = std.math;
26
+ const mem = std.mem;
27
+
28
+ const Direction = @import("../direction.zig").Direction;
29
+ const Pending = error{Pending};
30
+
31
+ pub fn TournamentTreeType(comptime Key: type, contestants_max: comptime_int) type {
32
+ return struct {
33
+ loser_keys: [node_count_max]Key align(64),
34
+ loser_ids: [node_count_max]u32 align(64),
35
+ win_key: Key,
36
+ win_id: u32,
37
+ contestants_left: u16,
38
+ height: u8,
39
+ direction: Direction,
40
+
41
+ pub const node_count_max: u32 = std.math.ceilPowerOfTwoAssert(u32, contestants_max);
42
+ const height_max = std.math.log2_int(u32, node_count_max);
43
+ const sentinel_key = std.math.maxInt(Key);
44
+
45
+ const Node = struct {
46
+ key: Key,
47
+ id: u32,
48
+
49
+ const id_sentinel = std.math.maxInt(u32);
50
+ const sentinel: Node = .{
51
+ .key = sentinel_key,
52
+ .id = id_sentinel,
53
+ };
54
+ };
55
+
56
+ const TournamentTree = @This();
57
+
58
+ pub fn init(
59
+ direction: Direction,
60
+ contestants: *[node_count_max]Node,
61
+ contestant_count: u16,
62
+ ) TournamentTree {
63
+ assert(contestant_count <= contestants_max);
64
+ maybe(contestant_count == 0);
65
+
66
+ var contestant_previous: ?*const Node = null;
67
+ var contestants_left: u16 = 0;
68
+ for (contestants[0..contestant_count]) |*contestant| {
69
+ if (contestant.id == Node.id_sentinel) {
70
+ // Stream is empty to begin with.
71
+ } else {
72
+ contestants_left += 1;
73
+ if (contestant_previous) |previous| assert(previous.id < contestant.id);
74
+ contestant_previous = contestant;
75
+ }
76
+ }
77
+ for (contestants[contestant_count..]) |*contestant| {
78
+ assert(contestant.id == Node.id_sentinel);
79
+ }
80
+
81
+ var tree: TournamentTree = .{
82
+ .win_key = sentinel_key,
83
+ .win_id = Node.id_sentinel,
84
+ .loser_keys = @splat(sentinel_key),
85
+ .loser_ids = @splat(Node.id_sentinel),
86
+ .direction = direction,
87
+ .contestants_left = contestants_left,
88
+ .height = 0,
89
+ };
90
+
91
+ if (contestants_left == 0) return tree;
92
+
93
+ // Compute effective tree size: only as large as needed for contestant_count.
94
+ const node_count: u32 = std.math.ceilPowerOfTwoAssert(u32, contestant_count);
95
+ tree.height = @intCast(std.math.log2_int(u32, node_count));
96
+
97
+ for (0..tree.height) |level| {
98
+ const shift_min: u5 = @intCast(level + 1);
99
+ const shift_max: u5 = @intCast(level);
100
+ const level_min: usize = (node_count >> shift_min) - 1;
101
+ const level_max: usize = (node_count >> shift_max) - 1;
102
+
103
+ for (level_min..level_max, 0..) |loser_index, competitor_index| {
104
+ const a = contestants[competitor_index * 2];
105
+ const b = contestants[competitor_index * 2 + 1];
106
+ const a_wins = beats(a.key, a.id, b.key, b.id, direction);
107
+
108
+ contestants[competitor_index] = .{
109
+ .key = stdx.branchless_select(Key, a_wins, a.key, b.key),
110
+ .id = stdx.branchless_select(u32, a_wins, a.id, b.id),
111
+ };
112
+ // We select the loser here thus a, b are swapped.
113
+ tree.loser_keys[loser_index] = stdx.branchless_select(
114
+ Key,
115
+ a_wins,
116
+ b.key,
117
+ a.key,
118
+ );
119
+ tree.loser_ids[loser_index] = stdx.branchless_select(u32, a_wins, b.id, a.id);
120
+ }
121
+ }
122
+
123
+ tree.win_key = contestants[0].key;
124
+ tree.win_id = contestants[0].id;
125
+
126
+ return tree;
127
+ }
128
+
129
+ pub fn pop_winner(tree: *TournamentTree, entrant: ?Key) void {
130
+ switch (tree.direction) {
131
+ inline else => |direction| {
132
+ switch (tree.height) {
133
+ inline 0...height_max => |height| pop_winner_impl(
134
+ tree,
135
+ entrant,
136
+ direction,
137
+ height,
138
+ ),
139
+ else => unreachable,
140
+ }
141
+ },
142
+ }
143
+ }
144
+
145
+ inline fn pop_winner_impl(
146
+ tree: *TournamentTree,
147
+ entrant: ?Key,
148
+ comptime direction: Direction,
149
+ comptime height: u32,
150
+ ) void {
151
+ const node_count = @as(u32, 1) << @as(u5, @intCast(height));
152
+ const winner_id = tree.win_id;
153
+
154
+ assert(tree.win_id < node_count);
155
+ if (entrant == null) tree.contestants_left -= 1;
156
+
157
+ var new_key: Key = if (entrant) |key| key else sentinel_key;
158
+ var new_id: u32 = if (entrant != null) winner_id else Node.id_sentinel;
159
+
160
+ var idx: usize = (node_count - 1) + winner_id;
161
+ inline for (0..height) |_| {
162
+ idx = (idx - 1) >> 1;
163
+
164
+ const opp_key = tree.loser_keys[idx];
165
+ const opp_id = tree.loser_ids[idx];
166
+ const new_wins = beats(new_key, new_id, opp_key, opp_id, direction);
167
+
168
+ tree.loser_keys[idx] = stdx.branchless_select(Key, new_wins, opp_key, new_key);
169
+ tree.loser_ids[idx] = stdx.branchless_select(u32, new_wins, opp_id, new_id);
170
+ new_key = stdx.branchless_select(Key, new_wins, new_key, opp_key);
171
+ new_id = stdx.branchless_select(u32, new_wins, new_id, opp_id);
172
+ }
173
+
174
+ tree.win_key = new_key;
175
+ tree.win_id = new_id;
176
+
177
+ if (tree.win_id == Node.id_sentinel) assert(tree.contestants_left == 0);
178
+ }
179
+
180
+ /// Returns true if (a_key, a_id) wins over (b_key, b_id).
181
+ /// Sentinels (id_sentinel) always lose. Equal keys broken by id for stability.
182
+ /// In ascending mode, sentinel_key (maxInt) naturally loses on `<` so no
183
+ /// explicit sentinel checks are needed. In descending mode, maxInt would
184
+ /// incorrectly "win" on `>`, so explicit sentinel checks are required.
185
+ inline fn beats(a_key: Key, a_id: u32, b_key: Key, b_id: u32, direction: Direction) bool {
186
+ const id_lt: u1 = @intFromBool(a_id < b_id);
187
+ const keys_eq: u1 = @intFromBool(a_key == b_key);
188
+ const eq_and_id_wins: u1 = keys_eq & id_lt;
189
+
190
+ if (direction == .ascending) {
191
+ const key_lt: u1 = @intFromBool(a_key < b_key);
192
+ return (key_lt | eq_and_id_wins) == 1;
193
+ } else {
194
+ const key_gt: u1 = @intFromBool(a_key > b_key);
195
+ const b_is_sentinel: u1 = @intFromBool(b_id == Node.id_sentinel);
196
+ const a_is_sentinel: u1 = @intFromBool(a_id == Node.id_sentinel);
197
+ const key_wins: u1 = key_gt | eq_and_id_wins;
198
+ return (b_is_sentinel | ((1 - a_is_sentinel) & key_wins)) == 1;
199
+ }
200
+ }
201
+ };
202
+ }
203
+
204
+ pub const KWayMergeOptions = struct {
205
+ streams_max: comptime_int,
206
+ deduplicate: bool = false,
207
+ };
208
+
209
+ pub fn KWayMergeIteratorType(
210
+ comptime Context: type,
211
+ comptime Key: type,
212
+ comptime Value: type,
213
+ comptime options: KWayMergeOptions,
214
+ comptime key_from_value: fn (*const Value) callconv(.@"inline") Key,
215
+ comptime stream_peek: fn (context: *Context, stream_index: u32) Pending!?Key,
216
+ comptime stream_pop: fn (context: *Context, stream_index: u32) Value,
217
+ ) type {
218
+ comptime assert(options.streams_max >= 1);
219
+ comptime assert(options.streams_max <= 1024); // Reasonable upper bound.
220
+
221
+ return struct {
222
+ context: *Context,
223
+ streams_count: u16,
224
+ direction: Direction,
225
+ key_popped: ?Key,
226
+ tree: ?Tree,
227
+
228
+ const Tree = TournamentTreeType(Key, options.streams_max);
229
+ const KWayMergeIterator = @This();
230
+
231
+ pub fn init(
232
+ context: *Context,
233
+ streams_count: u16,
234
+ direction: Direction,
235
+ ) KWayMergeIterator {
236
+ assert(streams_count <= options.streams_max);
237
+ maybe(streams_count == 0);
238
+
239
+ return .{
240
+ .context = context,
241
+ .key_popped = null,
242
+ .direction = direction,
243
+ .streams_count = streams_count,
244
+ .tree = null,
245
+ };
246
+ }
247
+
248
+ pub fn reset(self: *KWayMergeIterator) void {
249
+ self.* = .{
250
+ .context = self.context,
251
+ .direction = self.direction,
252
+ .streams_count = self.streams_count,
253
+ .key_popped = self.key_popped,
254
+ .tree = null,
255
+ };
256
+ }
257
+
258
+ fn load(self: *KWayMergeIterator) Pending!void {
259
+ assert(self.tree == null);
260
+ errdefer self.reset();
261
+
262
+ var contestants: [Tree.node_count_max]Tree.Node = @splat(.sentinel);
263
+ for (0..self.streams_count) |id_usize| {
264
+ const id: u32 = @intCast(id_usize);
265
+ const key = try stream_peek(self.context, id) orelse continue;
266
+ contestants[id_usize] = .{ .key = key, .id = id };
267
+ }
268
+
269
+ self.tree = Tree.init(self.direction, &contestants, self.streams_count);
270
+ }
271
+
272
+ pub fn pop(self: *KWayMergeIterator) Pending!?Value {
273
+ if (self.tree == null) try self.load();
274
+ const tree = &self.tree.?;
275
+
276
+ while (tree.contestants_left > 0) {
277
+ const key = try stream_peek(self.context, tree.win_id);
278
+ tree.pop_winner(key);
279
+ if (tree.contestants_left == 0) return null;
280
+ const value = stream_pop(self.context, tree.win_id);
281
+ if (options.deduplicate) {
282
+ const key_next = key_from_value(&value);
283
+ if (self.key_popped) |key_prev| if (key_next == key_prev) continue;
284
+ self.key_popped = key_next;
285
+ }
286
+ return value;
287
+ }
288
+ return null;
289
+ }
290
+ };
291
+ }
292
+
293
+ fn TestContextType(comptime streams_max: u32) type {
294
+ const testing = std.testing;
295
+
296
+ return struct {
297
+ const TestContext = @This();
298
+
299
+ const log = false;
300
+
301
+ const Value = struct {
302
+ key: u32,
303
+ version: u32,
304
+
305
+ inline fn to_key(v: *const Value) u32 {
306
+ return v.key;
307
+ }
308
+
309
+ fn less_than(direction: Direction, a: Value, b: Value) bool {
310
+ var order = math.order(a.key, b.key);
311
+ if (direction == .descending) order = order.invert();
312
+ return switch (order) {
313
+ .lt => true,
314
+ .eq => a.version < b.version,
315
+ .gt => false,
316
+ };
317
+ }
318
+ };
319
+
320
+ streams: [streams_max][]const Value,
321
+
322
+ fn stream_peek(
323
+ context: *const TestContext,
324
+ stream_index: u32,
325
+ ) Pending!?u32 {
326
+ const stream = context.streams[stream_index];
327
+ if (stream.len == 0) return null;
328
+ return stream[0].key;
329
+ }
330
+
331
+ fn stream_pop(context: *TestContext, stream_index: u32) Value {
332
+ const stream = context.streams[stream_index];
333
+ context.streams[stream_index] = stream[1..];
334
+ return stream[0];
335
+ }
336
+
337
+ fn merge(
338
+ direction: Direction,
339
+ streams_keys: []const []const u32,
340
+ expect: ?[]const Value,
341
+ ) !void {
342
+ const KWay = KWayMergeIteratorType(
343
+ TestContext,
344
+ u32,
345
+ Value,
346
+ .{
347
+ .streams_max = streams_max,
348
+ .deduplicate = true,
349
+ },
350
+ Value.to_key,
351
+ stream_peek,
352
+ stream_pop,
353
+ );
354
+
355
+ const gpa = std.testing.allocator;
356
+
357
+ var actual = std.ArrayList(Value).init(gpa);
358
+ defer actual.deinit();
359
+
360
+ var streams: [streams_max][]Value = undefined;
361
+
362
+ for (streams_keys, 0..) |stream_keys, i| {
363
+ errdefer for (streams[0..i]) |s| gpa.free(s);
364
+ streams[i] = try gpa.alloc(Value, stream_keys.len);
365
+ for (stream_keys, 0..) |key, j| {
366
+ streams[i][j] = .{
367
+ .key = key,
368
+ .version = @intCast(i),
369
+ };
370
+ }
371
+ }
372
+ defer for (streams[0..streams_keys.len]) |s| gpa.free(s);
373
+
374
+ const expect_naive_buffer = try gpa.alloc(Value, key_count: {
375
+ var total_count: u32 = 0;
376
+ for (streams_keys) |stream| total_count += @intCast(stream.len);
377
+ break :key_count total_count;
378
+ });
379
+ defer gpa.free(expect_naive_buffer);
380
+
381
+ const expect_naive = merge_naive(streams_keys, direction, expect_naive_buffer);
382
+
383
+ if (expect) |expect_explicit| {
384
+ try testing.expectEqualSlices(Value, expect_naive, expect_explicit);
385
+ }
386
+
387
+ var context: TestContext = .{ .streams = streams };
388
+ var kway = KWay.init(&context, @intCast(streams_keys.len), direction);
389
+
390
+ while (try kway.pop()) |value| {
391
+ try actual.append(value);
392
+ }
393
+
394
+ try testing.expectEqualSlices(Value, expect_naive, actual.items);
395
+ }
396
+
397
+ fn merge_naive(
398
+ streams: []const []const u32,
399
+ direction: Direction,
400
+ result: []Value,
401
+ ) []Value {
402
+ var count: u32 = 0;
403
+ for (streams, 0..) |stream, stream_index| {
404
+ for (stream) |key| {
405
+ result[count] = .{
406
+ .key = key,
407
+ .version = @intCast(stream_index),
408
+ };
409
+ count += 1;
410
+ }
411
+ }
412
+ assert(result.len >= count);
413
+
414
+ std.mem.sort(Value, result[0..count], direction, Value.less_than);
415
+
416
+ const count_duplicates = count;
417
+ count = 0;
418
+
419
+ var previous_key: ?u32 = null;
420
+ for (result[0..count_duplicates]) |value| {
421
+ if (previous_key) |p| {
422
+ if (value.key == p) continue;
423
+ }
424
+ previous_key = value.key;
425
+ result[count] = value;
426
+ count += 1;
427
+ }
428
+ return result[0..count];
429
+ }
430
+ };
431
+ }
432
+
433
+ test "k_way_merge: unit" {
434
+ // Empty stream.
435
+ try TestContextType(1).merge(
436
+ .ascending,
437
+ &.{},
438
+ &.{},
439
+ );
440
+
441
+ try TestContextType(1).merge(
442
+ .ascending,
443
+ &.{
444
+ &.{ 0, 3, 4, 8 },
445
+ },
446
+ &.{
447
+ .{ .key = 0, .version = 0 },
448
+ .{ .key = 3, .version = 0 },
449
+ .{ .key = 4, .version = 0 },
450
+ .{ .key = 8, .version = 0 },
451
+ },
452
+ );
453
+ try TestContextType(1).merge(
454
+ .descending,
455
+ &.{
456
+ &.{ 8, 4, 3, 0 },
457
+ },
458
+ &.{
459
+ .{ .key = 8, .version = 0 },
460
+ .{ .key = 4, .version = 0 },
461
+ .{ .key = 3, .version = 0 },
462
+ .{ .key = 0, .version = 0 },
463
+ },
464
+ );
465
+ try TestContextType(3).merge(
466
+ .ascending,
467
+ &.{
468
+ &.{ 0, 3, 4, 8, 11 },
469
+ &.{ 2, 11, 12, 13, 15 },
470
+ &.{ 1, 2, 11 },
471
+ },
472
+ &.{
473
+ .{ .key = 0, .version = 0 },
474
+ .{ .key = 1, .version = 2 },
475
+ .{ .key = 2, .version = 1 },
476
+ .{ .key = 3, .version = 0 },
477
+ .{ .key = 4, .version = 0 },
478
+ .{ .key = 8, .version = 0 },
479
+ .{ .key = 11, .version = 0 },
480
+ .{ .key = 12, .version = 1 },
481
+ .{ .key = 13, .version = 1 },
482
+ .{ .key = 15, .version = 1 },
483
+ },
484
+ );
485
+ try TestContextType(3).merge(
486
+ .descending,
487
+ &.{
488
+ &.{ 11, 8, 4, 3, 0 },
489
+ &.{ 15, 13, 12, 11, 2 },
490
+ &.{ 11, 2, 1 },
491
+ },
492
+ &.{
493
+ .{ .key = 15, .version = 1 },
494
+ .{ .key = 13, .version = 1 },
495
+ .{ .key = 12, .version = 1 },
496
+ .{ .key = 11, .version = 0 },
497
+ .{ .key = 8, .version = 0 },
498
+ .{ .key = 4, .version = 0 },
499
+ .{ .key = 3, .version = 0 },
500
+ .{ .key = 2, .version = 1 },
501
+ .{ .key = 1, .version = 2 },
502
+ .{ .key = 0, .version = 0 },
503
+ },
504
+ );
505
+
506
+ try TestContextType(32).merge(
507
+ .ascending,
508
+ &.{
509
+ &.{ 0, 3, 4, 8 },
510
+ },
511
+ &.{
512
+ .{ .key = 0, .version = 0 },
513
+ .{ .key = 3, .version = 0 },
514
+ .{ .key = 4, .version = 0 },
515
+ .{ .key = 8, .version = 0 },
516
+ },
517
+ );
518
+
519
+ try TestContextType(32).merge(
520
+ .descending,
521
+ &.{
522
+ &.{ 11, 8, 4, 3, 0 },
523
+ &.{ 15, 13, 12, 11, 2 },
524
+ &.{ 11, 2, 1 },
525
+ },
526
+ &.{
527
+ .{ .key = 15, .version = 1 },
528
+ .{ .key = 13, .version = 1 },
529
+ .{ .key = 12, .version = 1 },
530
+ .{ .key = 11, .version = 0 },
531
+ .{ .key = 8, .version = 0 },
532
+ .{ .key = 4, .version = 0 },
533
+ .{ .key = 3, .version = 0 },
534
+ .{ .key = 2, .version = 1 },
535
+ .{ .key = 1, .version = 2 },
536
+ .{ .key = 0, .version = 0 },
537
+ },
538
+ );
539
+ }
540
+
541
+ test "k_way_merge: exhaustigen" {
542
+ const Gen = @import("../testing/exhaustigen.zig");
543
+ const N = 3;
544
+ const M = 2;
545
+
546
+ var streams_buffer: [N][M]u32 = @splat(@splat(0));
547
+ var streams: [N][]u32 = @splat(&.{});
548
+ var g: Gen = .{};
549
+ while (!g.done()) {
550
+ const direction = g.enum_value(Direction);
551
+ for (0..N) |stream_index| {
552
+ const key_count = g.int_inclusive(u32, M);
553
+ for (0..key_count) |key_index| {
554
+ streams_buffer[stream_index][key_index] = g.int_inclusive(u32, M);
555
+ }
556
+ streams[stream_index] = streams_buffer[stream_index][0..key_count];
557
+ std.mem.sort(u32, streams[stream_index], {}, std.sort.asc(u32));
558
+ if (direction == .descending) {
559
+ std.mem.reverse(u32, streams[stream_index]);
560
+ }
561
+ }
562
+
563
+ try TestContextType(N).merge(
564
+ direction,
565
+ &streams,
566
+ null,
567
+ );
568
+ }
569
+ }
570
+
571
+ fn FuzzTestContextType(comptime streams_max: u32) type {
572
+ const testing = std.testing;
573
+ const ratio = stdx.PRNG.ratio;
574
+
575
+ return struct {
576
+ const FuzzTestContext = @This();
577
+
578
+ const TestContext = TestContextType(streams_max);
579
+ const Value = TestContext.Value;
580
+
581
+ const log = false;
582
+
583
+ prng: *stdx.PRNG,
584
+ inner: TestContext,
585
+
586
+ fn fuzz_stream_peek(
587
+ context: *const FuzzTestContext,
588
+ stream_index: u32,
589
+ ) Pending!?u32 {
590
+ if (context.prng.chance(ratio(5, 100))) {
591
+ return error.Pending;
592
+ }
593
+ return context.inner.stream_peek(stream_index);
594
+ }
595
+
596
+ fn stream_pop(context: *FuzzTestContext, stream_index: u32) Value {
597
+ return context.inner.stream_pop(stream_index);
598
+ }
599
+
600
+ fn merge(
601
+ gpa: std.mem.Allocator,
602
+ direction: Direction,
603
+ streams_keys: []const []const u32,
604
+ expect: []const Value,
605
+ prng: *stdx.PRNG,
606
+ ) !void {
607
+ const fuzz_helper = @import("../testing/fuzz.zig");
608
+ const KWay = KWayMergeIteratorType(
609
+ FuzzTestContext,
610
+ u32,
611
+ Value,
612
+ .{
613
+ .streams_max = streams_max,
614
+ .deduplicate = true,
615
+ },
616
+ Value.to_key,
617
+ fuzz_stream_peek,
618
+ stream_pop,
619
+ );
620
+ var actual = std.ArrayList(Value).init(gpa);
621
+ defer actual.deinit();
622
+
623
+ var streams: [streams_max][]Value = undefined;
624
+
625
+ for (streams_keys, 0..) |stream_keys, i| {
626
+ errdefer for (streams[0..i]) |s| gpa.free(s);
627
+ streams[i] = try gpa.alloc(Value, stream_keys.len);
628
+ for (stream_keys, 0..) |key, j| {
629
+ streams[i][j] = .{
630
+ .key = key,
631
+ .version = @intCast(i),
632
+ };
633
+ }
634
+ }
635
+ defer for (streams[0..streams_keys.len]) |s| gpa.free(s);
636
+
637
+ var context: FuzzTestContext = .{ .inner = .{ .streams = streams }, .prng = prng };
638
+ var kway = KWay.init(&context, @intCast(streams_keys.len), direction);
639
+
640
+ const Declarations = fuzz_helper.DeclEnumExcludingType(
641
+ KWay,
642
+ &.{.init},
643
+ );
644
+
645
+ var values_popped: u32 = 0;
646
+ while (values_popped < expect.len) {
647
+ switch (prng.enum_weighted(Declarations, .{ .pop = 98, .reset = 2 })) {
648
+ .pop => {
649
+ const maybe_value = kway.pop() catch continue;
650
+ const value = maybe_value orelse break;
651
+ try actual.append(value);
652
+ values_popped += 1;
653
+ },
654
+ .reset => {
655
+ kway.reset();
656
+ },
657
+ }
658
+ }
659
+
660
+ try testing.expectEqualSlices(Value, expect, actual.items);
661
+ }
662
+
663
+ fn fuzz(prng: *stdx.PRNG, stream_key_count_max: u32) !void {
664
+ if (log) std.debug.print("\n", .{});
665
+ const gpa = testing.allocator;
666
+
667
+ var streams: [streams_max][]u32 = undefined;
668
+
669
+ const streams_buffer = try gpa.alloc(u32, streams_max * stream_key_count_max);
670
+ defer gpa.free(streams_buffer);
671
+
672
+ const expect_buffer = try gpa.alloc(Value, streams_max * stream_key_count_max);
673
+ defer gpa.free(expect_buffer);
674
+
675
+ for (0..streams_max) |k| {
676
+ if (log) std.debug.print("k = {}\n", .{k});
677
+ {
678
+ for (0..k) |i| {
679
+ const len = fuzz_stream_len(prng, stream_key_count_max);
680
+ streams[i] = streams_buffer[i * stream_key_count_max ..][0..len];
681
+ fuzz_stream_keys(prng, streams[i]);
682
+
683
+ if (log) {
684
+ std.debug.print("stream {} = ", .{i});
685
+ for (streams[i]) |key| std.debug.print("{},", .{key});
686
+ std.debug.print("\n", .{});
687
+ }
688
+ }
689
+ }
690
+
691
+ const expect = TestContext.merge_naive(streams[0..k], .ascending, expect_buffer);
692
+
693
+ if (log) {
694
+ std.debug.print("expect = ", .{});
695
+ for (expect) |value| std.debug.print("({},{}),", .{ value.key, value.version });
696
+ std.debug.print("\n", .{});
697
+ }
698
+
699
+ try merge(gpa, .ascending, streams[0..k], expect, prng);
700
+
701
+ for (streams[0..k]) |stream| mem.reverse(u32, stream);
702
+ mem.reverse(Value, expect);
703
+
704
+ try merge(gpa, .descending, streams[0..k], expect, prng);
705
+
706
+ if (log) std.debug.print("\n", .{});
707
+ }
708
+ }
709
+
710
+ fn fuzz_stream_len(prng: *stdx.PRNG, stream_key_count_max: u32) u32 {
711
+ const Len = enum { zero, max, random };
712
+ return switch (prng.enum_weighted(Len, .{ .zero = 5, .max = 5, .random = 90 })) {
713
+ .zero => 0,
714
+ .max => stream_key_count_max,
715
+ .random => prng.int_inclusive(u32, stream_key_count_max),
716
+ };
717
+ }
718
+
719
+ fn fuzz_stream_keys(prng: *stdx.PRNG, stream: []u32) void {
720
+ const key_max = prng.range_inclusive(u32, 512, 1023);
721
+ const Key = enum { all_same, random };
722
+ switch (prng.enum_weighted(Key, .{ .all_same = 5, .random = 95 })) {
723
+ .all_same => {
724
+ @memset(stream, prng.int(u32));
725
+ },
726
+ .random => {
727
+ prng.fill(mem.sliceAsBytes(stream));
728
+ },
729
+ }
730
+ for (stream) |*key| key.* = key.* % key_max;
731
+ std.mem.sort(u32, stream, {}, std.sort.asc(u32));
732
+ }
733
+ };
734
+ }
735
+
736
+ test "k_way_merge: fuzz" {
737
+ var prng = stdx.PRNG.from_seed_testing();
738
+ try FuzzTestContextType(32).fuzz(&prng, 1024);
739
+ }