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,630 @@
1
+ //! An LSM tree.
2
+ const std = @import("std");
3
+ const assert = std.debug.assert;
4
+ const math = std.math;
5
+ const mem = std.mem;
6
+ const maybe = stdx.maybe;
7
+
8
+ const stdx = @import("stdx");
9
+ const constants = @import("../constants.zig");
10
+ const schema = @import("schema.zig");
11
+
12
+ const NodePool = @import("node_pool.zig").NodePoolType(constants.lsm_manifest_node_size, 16);
13
+ const GridType = @import("../vsr/grid.zig").GridType;
14
+ const BlockPtrConst = @import("../vsr/grid.zig").BlockPtrConst;
15
+ const ScratchMemory = @import("scratch_memory.zig").ScratchMemory;
16
+
17
+ pub const ScopeCloseMode = enum { persist, discard };
18
+
19
+ /// We reserve maxInt(u64) to indicate that a table has not been deleted.
20
+ /// Tables that have not been deleted have snapshot_max of maxInt(u64).
21
+ /// Since we ensure and assert that a query snapshot never exactly matches
22
+ /// the snapshot_min/snapshot_max of a table, we must use maxInt(u64) - 1
23
+ /// to query all non-deleted tables.
24
+ pub const snapshot_latest: u64 = math.maxInt(u64) - 1;
25
+
26
+ /// The maximum number of tables for a single tree.
27
+ pub const table_count_max = table_count_max_for_tree(
28
+ constants.lsm_growth_factor,
29
+ constants.lsm_levels,
30
+ );
31
+
32
+ pub const TreeConfig = struct {
33
+ /// Unique (stable) identifier, across all trees in the forest.
34
+ id: u16,
35
+ /// Human-readable tree name for logging.
36
+ name: []const u8,
37
+ };
38
+
39
+ pub fn TreeType(comptime TreeTable: type, comptime Storage: type) type {
40
+ const Key = TreeTable.Key;
41
+ const tombstone = TreeTable.tombstone;
42
+
43
+ return struct {
44
+ const Tree = @This();
45
+
46
+ pub const Table = TreeTable;
47
+ pub const Value = Table.Value;
48
+
49
+ pub const TableMemory = @import("table_memory.zig").TableMemoryType(Table);
50
+ pub const Manifest = @import("manifest.zig").ManifestType(Table, Storage);
51
+
52
+ const Grid = GridType(Storage);
53
+ const ManifestLog = @import("manifest_log.zig").ManifestLogType(Storage);
54
+ const KeyRange = Manifest.KeyRange;
55
+
56
+ const CompactionType = @import("compaction.zig").CompactionType;
57
+ pub const Compaction = CompactionType(Table, Tree, Storage);
58
+
59
+ pub const LookupMemoryResult = union(enum) {
60
+ negative,
61
+ positive: *const Value,
62
+ possible: u8,
63
+ };
64
+
65
+ grid: *Grid,
66
+ config: Config,
67
+ options: Options,
68
+
69
+ table_mutable: TableMemory,
70
+ table_immutable: TableMemory,
71
+
72
+ manifest: Manifest,
73
+
74
+ /// The forest can run compactions in any order, potentially even concurrently across all
75
+ /// levels and trees simultaneously. There's a + 1 here for the immmutable table to level
76
+ /// 0, but it's cancelled by a - 1 since the last level doesn't compact to anything.
77
+ /// Each Compaction object is only around ~2KB of control plane state.
78
+ compactions: [constants.lsm_levels]Compaction,
79
+
80
+ /// While a compaction is running, this is the op of the last compact().
81
+ /// While no compaction is running, this is the op of the last compact() to complete.
82
+ /// (When recovering from a checkpoint, compaction_op starts at op_checkpoint).
83
+ compaction_op: ?u64 = null,
84
+
85
+ active_scope: ?struct {
86
+ value_context: TableMemory.ValueContext,
87
+ key_range: ?KeyRange,
88
+ } = null,
89
+
90
+ /// The range of keys in this tree at snapshot_latest.
91
+ key_range: ?KeyRange = null,
92
+
93
+ /// (Constructed by the Forest.)
94
+ pub const Config = TreeConfig;
95
+
96
+ /// (Constructed by the StateMachine.)
97
+ pub const Options = struct {
98
+ /// The (runtime) upper-limit of values created by a single batch.
99
+ batch_value_count_limit: u32,
100
+ };
101
+
102
+ pub fn init(
103
+ tree: *Tree,
104
+ allocator: mem.Allocator,
105
+ node_pool: *NodePool,
106
+ grid: *Grid,
107
+ radix_buffer: *ScratchMemory,
108
+ config: Config,
109
+ options: Options,
110
+ ) !void {
111
+ assert(grid.superblock.opened);
112
+ assert(config.id != 0); // id=0 is reserved.
113
+ assert(config.name.len > 0);
114
+ assert(radix_buffer.state == .free);
115
+
116
+ const value_count_limit =
117
+ options.batch_value_count_limit * constants.lsm_compaction_ops;
118
+ assert(value_count_limit > 0);
119
+ assert(value_count_limit <= TreeTable.value_count_max);
120
+
121
+ tree.* = .{
122
+ .grid = grid,
123
+ .config = config,
124
+ .options = options,
125
+
126
+ .table_mutable = undefined,
127
+ .table_immutable = undefined,
128
+ .manifest = undefined,
129
+ .compactions = undefined,
130
+ };
131
+
132
+ try tree.table_mutable.init(allocator, radix_buffer, .mutable, config.name, .{
133
+ .value_count_limit = value_count_limit,
134
+ });
135
+ errdefer tree.table_mutable.deinit(allocator);
136
+
137
+ try tree.table_immutable.init(allocator, radix_buffer, .immutable, config.name, .{
138
+ .value_count_limit = value_count_limit,
139
+ });
140
+ errdefer tree.table_immutable.deinit(allocator);
141
+
142
+ try tree.manifest.init(allocator, node_pool, config, grid.trace);
143
+ errdefer tree.manifest.deinit(allocator);
144
+
145
+ for (0..tree.compactions.len) |i| {
146
+ errdefer for (tree.compactions[0..i]) |*c| c.deinit();
147
+ tree.compactions[i] = Compaction.init(tree, grid, @intCast(i));
148
+ }
149
+ errdefer for (tree.compactions) |*c| c.deinit();
150
+ }
151
+
152
+ pub fn deinit(tree: *Tree, allocator: mem.Allocator) void {
153
+ tree.manifest.deinit(allocator);
154
+ tree.table_immutable.deinit(allocator);
155
+ tree.table_mutable.deinit(allocator);
156
+ }
157
+
158
+ pub fn reset(tree: *Tree) void {
159
+ tree.table_mutable.reset();
160
+ tree.table_immutable.reset();
161
+ tree.manifest.reset();
162
+
163
+ for (&tree.compactions) |*compaction| compaction.reset();
164
+
165
+ tree.* = .{
166
+ .grid = tree.grid,
167
+ .config = tree.config,
168
+ .options = tree.options,
169
+ .table_mutable = tree.table_mutable,
170
+ .table_immutable = tree.table_immutable,
171
+ .manifest = tree.manifest,
172
+ .compactions = tree.compactions,
173
+ };
174
+ }
175
+
176
+ /// Open a new scope. Within a scope, changes can be persisted
177
+ /// or discarded. Only one scope can be active at a time.
178
+ pub fn scope_open(tree: *Tree) void {
179
+ assert(tree.active_scope == null);
180
+ tree.active_scope = .{
181
+ .value_context = tree.table_mutable.value_context,
182
+ .key_range = tree.key_range,
183
+ };
184
+ }
185
+
186
+ pub fn scope_close(tree: *Tree, mode: ScopeCloseMode) void {
187
+ assert(tree.active_scope != null);
188
+ assert(tree.active_scope.?.value_context.count <=
189
+ tree.table_mutable.value_context.count);
190
+
191
+ if (mode == .discard) {
192
+ tree.table_mutable.value_context = tree.active_scope.?.value_context;
193
+ tree.key_range = tree.active_scope.?.key_range;
194
+ }
195
+
196
+ tree.active_scope = null;
197
+ }
198
+
199
+ pub fn put(tree: *Tree, value: *const Value) void {
200
+ tree.table_mutable.put(value);
201
+ }
202
+
203
+ pub fn remove(tree: *Tree, value: *const Value) void {
204
+ tree.table_mutable.put(&Table.tombstone_from_key(Table.key_from_value(value)));
205
+ }
206
+
207
+ pub fn key_range_update(tree: *Tree, key: Key) void {
208
+ if (tree.key_range) |*key_range| {
209
+ if (key < key_range.key_min) key_range.key_min = key;
210
+ if (key > key_range.key_max) key_range.key_max = key;
211
+ } else {
212
+ tree.key_range = KeyRange{ .key_min = key, .key_max = key };
213
+ }
214
+ }
215
+
216
+ /// Returns True if the given key may be present in the Tree, False if the key is
217
+ /// guaranteed to not be present.
218
+ ///
219
+ /// Specifically, it checks whether the key exists within the Tree's key range.
220
+ pub fn key_range_contains(tree: *const Tree, snapshot: u64, key: Key) bool {
221
+ if (snapshot == snapshot_latest) {
222
+ return tree.key_range != null and
223
+ tree.key_range.?.key_min <= key and
224
+ key <= tree.key_range.?.key_max;
225
+ } else {
226
+ return true;
227
+ }
228
+ }
229
+
230
+ /// This function is intended to never be called by regular code. It only
231
+ /// exists for fuzzing, due to the performance overhead it carries. Real
232
+ /// code must rely on the Groove cache for lookups.
233
+ /// The returned Value pointer is only valid synchronously.
234
+ pub fn lookup_from_memory(tree: *Tree, key: Key) ?*const Value {
235
+ comptime assert(constants.verify);
236
+
237
+ tree.table_mutable.sort();
238
+ return tree.table_mutable.get(key) orelse tree.table_immutable.get(key);
239
+ }
240
+
241
+ /// Returns:
242
+ /// - .negative if the key does not exist in the Manifest.
243
+ /// - .positive if the key exists in the Manifest, along with the associated value.
244
+ /// - .possible if the key may exist in the Manifest but its existence cannot be
245
+ /// ascertained without IO, along with the level number at which IO must be performed.
246
+ ///
247
+ /// This function attempts to fetch the index & value blocks for the tables that
248
+ /// could contain the key synchronously from the Grid cache. It then attempts to ascertain
249
+ /// the existence of the key in the value block. If any of the blocks needed to
250
+ /// ascertain the existence of the key are not in the Grid cache, it bails out.
251
+ /// The returned `.positive` Value pointer is only valid synchronously.
252
+ pub fn lookup_from_levels_cache(tree: *Tree, snapshot: u64, key: Key) LookupMemoryResult {
253
+ if (tree.table_immutable.get(key)) |value| {
254
+ return .{ .positive = value };
255
+ }
256
+
257
+ var iterator = tree.manifest.lookup(snapshot, key, 0);
258
+ while (iterator.next()) |table| {
259
+ const index_block = tree.grid.read_block_from_cache(
260
+ table.address,
261
+ table.checksum,
262
+ .{ .coherent = true },
263
+ ) orelse {
264
+ // Index block not in cache. We cannot rule out existence without I/O,
265
+ // and therefore bail out.
266
+ return .{ .possible = iterator.level - 1 };
267
+ };
268
+
269
+ const key_blocks = Table.index_blocks_for_key(index_block, key) orelse continue;
270
+ switch (tree.cached_value_block_search(
271
+ key_blocks.value_block_address,
272
+ key_blocks.value_block_checksum,
273
+ key,
274
+ )) {
275
+ .negative => {},
276
+ // Key present in the value block.
277
+ .positive => |value| return .{ .positive = value },
278
+ // Value block was not found in the grid cache. We cannot rule out
279
+ // the existence of the key without I/O, and therefore bail out.
280
+ .block_not_in_cache => return .{ .possible = iterator.level - 1 },
281
+ }
282
+ }
283
+ // Key not present in the Manifest.
284
+ return .negative;
285
+ }
286
+
287
+ pub fn block_value_count_max(_: *const Tree) u32 {
288
+ return Table.layout.block_value_count_max;
289
+ }
290
+
291
+ fn cached_value_block_search(
292
+ tree: *Tree,
293
+ address: u64,
294
+ checksum: u128,
295
+ key: Key,
296
+ ) union(enum) {
297
+ positive: *const Value,
298
+ negative,
299
+ block_not_in_cache,
300
+ } {
301
+ if (tree.grid.read_block_from_cache(
302
+ address,
303
+ checksum,
304
+ .{ .coherent = true },
305
+ )) |value_block| {
306
+ if (Table.value_block_search(value_block, key)) |value| {
307
+ return .{ .positive = value };
308
+ } else {
309
+ return .negative;
310
+ }
311
+ } else {
312
+ return .block_not_in_cache;
313
+ }
314
+ }
315
+
316
+ /// Call this function only after checking `lookup_from_levels_cache()`.
317
+ /// The callback's Value pointer is only valid synchronously within the callback.
318
+ pub fn lookup_from_levels_storage(tree: *Tree, parameters: struct {
319
+ callback: *const fn (*LookupContext, ?*const Value) void,
320
+ context: *LookupContext,
321
+ snapshot: u64,
322
+ key: Key,
323
+ level_min: u8,
324
+ }) void {
325
+ var index_block_count: u8 = 0;
326
+ var index_block_addresses: [constants.lsm_levels]u64 = undefined;
327
+ var index_block_checksums: [constants.lsm_levels]u128 = undefined;
328
+ {
329
+ var it = tree.manifest.lookup(
330
+ parameters.snapshot,
331
+ parameters.key,
332
+ parameters.level_min,
333
+ );
334
+ while (it.next()) |table| : (index_block_count += 1) {
335
+ assert(table.visible(parameters.snapshot));
336
+ assert(table.key_min <= parameters.key);
337
+ assert(parameters.key <= table.key_max);
338
+
339
+ index_block_addresses[index_block_count] = table.address;
340
+ index_block_checksums[index_block_count] = table.checksum;
341
+ }
342
+ }
343
+
344
+ if (index_block_count == 0) {
345
+ parameters.callback(parameters.context, null);
346
+ return;
347
+ }
348
+
349
+ parameters.context.* = .{
350
+ .tree = tree,
351
+ .completion = undefined,
352
+ .key = parameters.key,
353
+ .index_block_count = index_block_count,
354
+ .index_block_addresses = index_block_addresses,
355
+ .index_block_checksums = index_block_checksums,
356
+ .callback = parameters.callback,
357
+ };
358
+
359
+ parameters.context.read_index_block();
360
+ }
361
+
362
+ pub const LookupContext = struct {
363
+ const Read = Grid.Read;
364
+
365
+ tree: *Tree,
366
+ completion: Read,
367
+
368
+ key: Key,
369
+
370
+ /// This value is an index into the index_block_addresses/checksums arrays.
371
+ index_block: u8 = 0,
372
+ index_block_count: u8,
373
+ index_block_addresses: [constants.lsm_levels]u64,
374
+ index_block_checksums: [constants.lsm_levels]u128,
375
+
376
+ value_block: ?struct {
377
+ address: u64,
378
+ checksum: u128,
379
+ } = null,
380
+
381
+ callback: *const fn (*Tree.LookupContext, ?*const Value) void,
382
+
383
+ fn read_index_block(context: *LookupContext) void {
384
+ assert(context.value_block == null);
385
+ assert(context.index_block < context.index_block_count);
386
+ assert(context.index_block_count > 0);
387
+ assert(context.index_block_count <= constants.lsm_levels);
388
+
389
+ context.tree.grid.read_block(
390
+ .{ .from_local_or_global_storage = read_index_block_callback },
391
+ &context.completion,
392
+ context.index_block_addresses[context.index_block],
393
+ context.index_block_checksums[context.index_block],
394
+ .{ .cache_read = true, .cache_write = true },
395
+ );
396
+ }
397
+
398
+ fn read_index_block_callback(completion: *Read, index_block: BlockPtrConst) void {
399
+ const context: *LookupContext = @fieldParentPtr("completion", completion);
400
+ assert(context.value_block == null);
401
+ assert(context.index_block < context.index_block_count);
402
+ assert(context.index_block_count > 0);
403
+ assert(context.index_block_count <= constants.lsm_levels);
404
+ assert(Table.index.block_metadata(index_block).tree_id == context.tree.config.id);
405
+
406
+ const blocks = Table.index_blocks_for_key(index_block, context.key) orelse {
407
+ // The key is not present in this table, check the next level.
408
+ context.advance_to_next_level();
409
+ return;
410
+ };
411
+
412
+ context.value_block = .{
413
+ .address = blocks.value_block_address,
414
+ .checksum = blocks.value_block_checksum,
415
+ };
416
+
417
+ context.tree.grid.read_block(
418
+ .{ .from_local_or_global_storage = read_value_block_callback },
419
+ completion,
420
+ context.value_block.?.address,
421
+ context.value_block.?.checksum,
422
+ .{ .cache_read = true, .cache_write = true },
423
+ );
424
+ }
425
+
426
+ fn read_value_block_callback(completion: *Read, value_block: BlockPtrConst) void {
427
+ const context: *LookupContext = @fieldParentPtr("completion", completion);
428
+ assert(context.value_block != null);
429
+ assert(context.index_block < context.index_block_count);
430
+ assert(context.index_block_count > 0);
431
+ assert(context.index_block_count <= constants.lsm_levels);
432
+ assert(Table.data.block_metadata(value_block).tree_id == context.tree.config.id);
433
+
434
+ if (Table.value_block_search(value_block, context.key)) |value| {
435
+ context.callback(context, unwrap_tombstone(value));
436
+ } else {
437
+ // The key is not present in this table, check the next level.
438
+ context.advance_to_next_level();
439
+ }
440
+ }
441
+
442
+ fn advance_to_next_level(context: *LookupContext) void {
443
+ assert(context.index_block < context.index_block_count);
444
+ assert(context.index_block_count > 0);
445
+ assert(context.index_block_count <= constants.lsm_levels);
446
+
447
+ // Value block may be null if the key is not contained in the
448
+ // index block's key range.
449
+ maybe(context.value_block == null);
450
+
451
+ context.index_block += 1;
452
+ if (context.index_block == context.index_block_count) {
453
+ context.callback(context, null);
454
+ return;
455
+ }
456
+ assert(context.index_block < context.index_block_count);
457
+
458
+ context.value_block = null;
459
+ context.read_index_block();
460
+ }
461
+ };
462
+
463
+ /// Returns null if the value is null or a tombstone, otherwise returns the value.
464
+ /// We use tombstone values internally, but expose them as null to the user.
465
+ /// This distinction enables us to cache a null result as a tombstone in our hash maps.
466
+ pub inline fn unwrap_tombstone(value: ?*const Value) ?*const Value {
467
+ return if (value == null or tombstone(value.?)) null else value.?;
468
+ }
469
+
470
+ pub fn open_commence(tree: *Tree, manifest_log: *ManifestLog) void {
471
+ assert(tree.compaction_op == null);
472
+ assert(tree.key_range == null);
473
+
474
+ tree.manifest.open_commence(manifest_log);
475
+ }
476
+
477
+ pub fn open_table(
478
+ tree: *Tree,
479
+ table: *const schema.ManifestNode.TableInfo,
480
+ ) void {
481
+ assert(tree.compaction_op == null);
482
+ assert(tree.key_range == null);
483
+
484
+ const tree_table = Manifest.TreeTableInfo.decode(table);
485
+ tree.manifest.levels[table.label.level].insert_table(
486
+ tree.manifest.node_pool,
487
+ &tree_table,
488
+ );
489
+ }
490
+
491
+ pub fn open_complete(tree: *Tree) void {
492
+ assert(tree.compaction_op == null);
493
+ assert(tree.key_range == null);
494
+
495
+ tree.compaction_op = tree.grid.superblock.working.vsr_state.checkpoint.header.op;
496
+ tree.key_range = tree.manifest.key_range();
497
+
498
+ tree.manifest.verify(snapshot_latest);
499
+ assert(tree.compaction_op.? == 0 or
500
+ (tree.compaction_op.? + 1) % constants.lsm_compaction_ops == 0);
501
+ maybe(tree.key_range == null);
502
+ }
503
+
504
+ pub fn compact(tree: *Tree) void {
505
+ assert(tree.table_mutable.mutability == .mutable);
506
+
507
+ tree.grid.trace.start(.{ .compact_mutable_suffix = .{
508
+ .tree = @enumFromInt(tree.config.id),
509
+ } });
510
+ defer tree.grid.trace.stop(.{ .compact_mutable_suffix = .{
511
+ .tree = @enumFromInt(tree.config.id),
512
+ } });
513
+
514
+ // Spreads sort+deduplication work between beats, to avoid a latency spike at the end of
515
+ // each bar (or immediately prior to scans).
516
+ tree.table_mutable.sort_suffix();
517
+ }
518
+
519
+ /// Called after the last beat of a full compaction bar, by the compaction instance.
520
+ pub fn swap_mutable_and_immutable(tree: *Tree, snapshot_min: u64) void {
521
+ assert(tree.table_mutable.mutability == .mutable);
522
+ assert(tree.table_immutable.mutability == .immutable);
523
+ assert(snapshot_min > 0);
524
+ assert(snapshot_min < snapshot_latest);
525
+
526
+ tree.grid.trace.start(.{ .compact_mutable = .{
527
+ .tree = @enumFromInt(tree.config.id),
528
+ } });
529
+ defer tree.grid.trace.stop(.{ .compact_mutable = .{
530
+ .tree = @enumFromInt(tree.config.id),
531
+ } });
532
+
533
+ if (tree.table_immutable.mutability.immutable.flushed) {
534
+ // The immutable table must be visible to the next bar.
535
+ // In addition, the immutable table is conceptually an output table of this
536
+ // compaction bar, and now its snapshot_min matches the snapshot_min of the
537
+ // Compactions' output tables.
538
+ tree.table_immutable.compact(&tree.table_mutable, snapshot_min);
539
+ } else {
540
+ assert(tree.table_immutable.value_context.count +
541
+ tree.table_mutable.value_context.count <= tree.table_immutable.values.len);
542
+
543
+ // The immutable table wasn't flushed because there is enough room left over for the
544
+ // mutable table's values, allowing us to skip some compaction work.
545
+ tree.table_immutable.absorb(&tree.table_mutable, snapshot_min);
546
+ assert(tree.table_mutable.value_context.count == 0);
547
+ }
548
+
549
+ // TODO
550
+ // assert((tree.compaction_op.? + 1) % constants.lsm_compaction_ops == 0);
551
+
552
+ assert(tree.table_mutable.count() == 0);
553
+ assert(tree.table_mutable.mutability == .mutable);
554
+ assert(tree.table_immutable.mutability == .immutable);
555
+ }
556
+
557
+ pub fn assert_between_bars(tree: *const Tree) void {
558
+ // Assert that this is the last beat in the compaction bar.
559
+ // const compaction_beat = tree.compaction_op.? % constants.lsm_compaction_ops;
560
+ // const last_beat_in_bar = constants.lsm_compaction_ops - 1;
561
+ // assert(last_beat_in_bar == compaction_beat);
562
+
563
+ // Assert no outstanding compactions.
564
+ for (&tree.compactions) |*compaction| {
565
+ compaction.assert_between_bars();
566
+ }
567
+
568
+ // Assert all manifest levels haven't overflowed their table counts.
569
+ tree.manifest.assert_level_table_counts();
570
+
571
+ if (constants.verify) {
572
+ tree.manifest.assert_no_invisible_tables(&.{});
573
+ }
574
+ }
575
+
576
+ // Returns the last segment of the tree's fully qualified value type name.
577
+ // Inline function, so it can be fully resolved at comptime.
578
+ pub inline fn tree_name() []const u8 {
579
+ const name_full = @typeName(Value);
580
+ if (comptime std.mem.lastIndexOfScalar(u8, name_full, '.')) |offset| {
581
+ return name_full[offset + 1 ..];
582
+ } else {
583
+ return name_full;
584
+ }
585
+ }
586
+ };
587
+ }
588
+
589
+ /// The total number of tables that can be supported by the tree across so many levels.
590
+ pub fn table_count_max_for_tree(growth_factor: u32, levels_count: u32) u32 {
591
+ assert(growth_factor >= 4);
592
+ assert(growth_factor <= 16); // Limit excessive write amplification.
593
+ assert(levels_count >= 2);
594
+ assert(levels_count <= 10); // Limit excessive read amplification.
595
+ assert(levels_count <= constants.lsm_levels);
596
+
597
+ var count: u32 = 0;
598
+ var level: u32 = 0;
599
+ while (level < levels_count) : (level += 1) {
600
+ count += table_count_max_for_level(growth_factor, level);
601
+ }
602
+ return count;
603
+ }
604
+
605
+ /// The total number of tables that can be supported by the level alone.
606
+ pub fn table_count_max_for_level(growth_factor: u32, level: u32) u32 {
607
+ assert(level >= 0);
608
+ assert(level < constants.lsm_levels);
609
+
610
+ return math.pow(u32, growth_factor, level + 1);
611
+ }
612
+
613
+ test "table_count_max_for_level/tree" {
614
+ const expectEqual = std.testing.expectEqual;
615
+
616
+ try expectEqual(@as(u32, 8), table_count_max_for_level(8, 0));
617
+ try expectEqual(@as(u32, 64), table_count_max_for_level(8, 1));
618
+ try expectEqual(@as(u32, 512), table_count_max_for_level(8, 2));
619
+ try expectEqual(@as(u32, 4096), table_count_max_for_level(8, 3));
620
+ try expectEqual(@as(u32, 32768), table_count_max_for_level(8, 4));
621
+ try expectEqual(@as(u32, 262144), table_count_max_for_level(8, 5));
622
+ try expectEqual(@as(u32, 2097152), table_count_max_for_level(8, 6));
623
+
624
+ try expectEqual(@as(u32, 8 + 64), table_count_max_for_tree(8, 2));
625
+ try expectEqual(@as(u32, 72 + 512), table_count_max_for_tree(8, 3));
626
+ try expectEqual(@as(u32, 584 + 4096), table_count_max_for_tree(8, 4));
627
+ try expectEqual(@as(u32, 4680 + 32768), table_count_max_for_tree(8, 5));
628
+ try expectEqual(@as(u32, 37448 + 262144), table_count_max_for_tree(8, 6));
629
+ try expectEqual(@as(u32, 299592 + 2097152), table_count_max_for_tree(8, 7));
630
+ }