tigerbeetle 0.0.36 → 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 (247) hide show
  1. checksums.yaml +4 -4
  2. data/ext/tb_client/extconf.rb +13 -13
  3. data/ext/tb_client/tigerbeetle/LICENSE +177 -0
  4. data/ext/tb_client/tigerbeetle/build.zig +2327 -0
  5. data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
  6. data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
  7. data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
  8. data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
  9. data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
  10. data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
  11. data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
  12. data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
  13. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
  14. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
  15. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
  16. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
  17. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
  18. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
  19. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
  20. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
  21. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
  22. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
  23. data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
  24. data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
  25. data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
  26. data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
  27. data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
  28. data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
  29. data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
  30. data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
  31. data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
  32. data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
  33. data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
  34. data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
  35. data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
  36. data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
  37. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
  38. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
  39. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
  40. data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
  41. data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
  42. data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
  43. data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
  44. data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
  45. data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
  46. data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
  47. data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
  48. data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
  49. data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
  50. data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
  51. data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
  52. data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
  53. data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
  54. data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
  55. data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
  56. data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
  57. data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
  58. data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
  59. data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
  60. data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
  61. data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
  62. data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
  63. data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
  64. data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
  65. data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
  66. data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
  67. data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
  68. data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
  69. data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
  70. data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
  71. data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
  72. data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
  73. data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
  74. data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
  75. data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
  76. data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
  77. data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
  78. data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
  79. data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
  80. data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
  81. data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
  82. data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
  83. data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
  84. data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
  85. data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
  86. data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
  87. data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
  88. data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
  89. data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
  90. data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
  91. data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
  92. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
  93. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
  94. data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
  95. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
  96. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
  97. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
  98. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
  99. data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
  100. data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
  101. data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
  102. data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
  103. data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
  104. data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
  105. data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
  106. data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
  107. data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
  108. data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
  109. data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
  110. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
  111. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
  112. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
  113. data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
  114. data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
  115. data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
  116. data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
  117. data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
  118. data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
  119. data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
  120. data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
  121. data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
  122. data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
  123. data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
  124. data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
  125. data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
  126. data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
  127. data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
  128. data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
  129. data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
  130. data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
  131. data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
  132. data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
  133. data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
  134. data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
  135. data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
  136. data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
  137. data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
  138. data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
  139. data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
  140. data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
  141. data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
  142. data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
  143. data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
  144. data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
  145. data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
  146. data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
  147. data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
  148. data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
  149. data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
  150. data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
  151. data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
  152. data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
  153. data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
  154. data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
  155. data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
  156. data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
  157. data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
  158. data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
  159. data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
  160. data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
  161. data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
  162. data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
  163. data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
  164. data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
  165. data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
  166. data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
  167. data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
  168. data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
  169. data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
  170. data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
  171. data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
  172. data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
  173. data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
  174. data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
  175. data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
  176. data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
  177. data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
  178. data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
  179. data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
  180. data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
  181. data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
  182. data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
  183. data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
  184. data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
  185. data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
  186. data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
  187. data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
  188. data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
  189. data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
  190. data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
  191. data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
  192. data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
  193. data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
  194. data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
  195. data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
  196. data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
  197. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
  198. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
  199. data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
  200. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
  201. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
  202. data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
  203. data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
  204. data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
  205. data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
  206. data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
  207. data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
  208. data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
  209. data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
  210. data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
  211. data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
  212. data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
  213. data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
  214. data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
  215. data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
  216. data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
  217. data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
  218. data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
  219. data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
  220. data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
  221. data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
  222. data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
  223. data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
  224. data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
  225. data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
  226. data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
  227. data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
  228. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
  229. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
  230. data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
  231. data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
  232. data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
  233. data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
  234. data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
  235. data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
  236. data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
  237. data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
  238. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
  239. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
  240. data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
  241. data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
  242. data/lib/tb_client/shared_lib.rb +12 -5
  243. data/lib/tigerbeetle/platforms.rb +9 -0
  244. data/lib/tigerbeetle/version.rb +1 -1
  245. data/tigerbeetle.gemspec +22 -5
  246. metadata +242 -3
  247. data/ext/tb_client/pkg.tar.gz +0 -0
@@ -0,0 +1,938 @@
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+ const maybe = stdx.maybe;
4
+
5
+ const constants = @import("../constants.zig");
6
+ const fixtures = @import("../testing/fixtures.zig");
7
+ const fuzz = @import("../testing/fuzz.zig");
8
+ const stdx = @import("stdx");
9
+ const vsr = @import("../vsr.zig");
10
+ const Ratio = stdx.PRNG.Ratio;
11
+ const ratio = stdx.PRNG.ratio;
12
+
13
+ const log = std.log.scoped(.lsm_scan_fuzz);
14
+
15
+ const TimeSim = @import("../testing/time.zig").TimeSim;
16
+ const Storage = @import("../testing/storage.zig").Storage;
17
+ const GridType = @import("../vsr/grid.zig").GridType;
18
+ const GrooveType = @import("groove.zig").GrooveType;
19
+ const ForestType = @import("forest.zig").ForestType;
20
+ const ScanLookupType = @import("scan_lookup.zig").ScanLookupType;
21
+ const TimestampRange = @import("timestamp_range.zig").TimestampRange;
22
+ const Direction = @import("../direction.zig").Direction;
23
+
24
+ const Grid = GridType(Storage);
25
+ const SuperBlock = vsr.SuperBlockType(Storage);
26
+
27
+ const batch_objects_max: u32 = @divFloor(
28
+ constants.message_body_size_max,
29
+ @sizeOf(Thing),
30
+ );
31
+
32
+ /// The max number of query specs generated per run.
33
+ const query_spec_max = 8;
34
+
35
+ /// The testing object.
36
+ const Thing = extern struct {
37
+ id: u128,
38
+ index_01: u64,
39
+ index_02: u64,
40
+ index_03: u64,
41
+ index_04: u64,
42
+ index_05: u64,
43
+ index_06: u64,
44
+ index_07: u64,
45
+ index_08: u64,
46
+ index_09: u64,
47
+ index_10: u64,
48
+ index_11: u64,
49
+ index_12: u64,
50
+ index_13: u64,
51
+ timestamp: u64,
52
+
53
+ fn get_index(thing: *const Thing, index: Index) u64 {
54
+ switch (index) {
55
+ inline else => |comptime_index| {
56
+ return @field(thing, @tagName(comptime_index));
57
+ },
58
+ }
59
+ }
60
+
61
+ comptime {
62
+ assert(stdx.no_padding(Thing));
63
+ assert(@sizeOf(Thing) == 128);
64
+ assert(@alignOf(Thing) == 16);
65
+ }
66
+ };
67
+
68
+ const ThingsGroove = GrooveType(
69
+ Storage,
70
+ Thing,
71
+ .{
72
+ .ids = .{
73
+ .id = 1,
74
+ .index_01 = 2,
75
+ .index_02 = 3,
76
+ .index_03 = 4,
77
+ .index_04 = 5,
78
+ .index_05 = 6,
79
+ .index_06 = 7,
80
+ .index_07 = 8,
81
+ .index_08 = 9,
82
+ .index_09 = 10,
83
+ .index_10 = 11,
84
+ .index_11 = 12,
85
+ .index_12 = 13,
86
+ .index_13 = 14,
87
+ .timestamp = 15,
88
+ },
89
+ .batch_value_count_max = .{
90
+ .id = batch_objects_max,
91
+ .index_01 = batch_objects_max,
92
+ .index_02 = batch_objects_max,
93
+ .index_03 = batch_objects_max,
94
+ .index_04 = batch_objects_max,
95
+ .index_05 = batch_objects_max,
96
+ .index_06 = batch_objects_max,
97
+ .index_07 = batch_objects_max,
98
+ .index_08 = batch_objects_max,
99
+ .index_09 = batch_objects_max,
100
+ .index_10 = batch_objects_max,
101
+ .index_11 = batch_objects_max,
102
+ .index_12 = batch_objects_max,
103
+ .index_13 = batch_objects_max,
104
+ .timestamp = batch_objects_max,
105
+ },
106
+ .ignored = &[_][]const u8{},
107
+ .optional = &[_][]const u8{},
108
+ .derived = .{},
109
+ .orphaned_ids = false,
110
+ .objects_cache = true,
111
+ },
112
+ );
113
+
114
+ const Forest = ForestType(Storage, .{
115
+ .things = ThingsGroove,
116
+ });
117
+
118
+ const Index = std.meta.FieldEnum(ThingsGroove.IndexTrees);
119
+
120
+ const ScanLookup = ScanLookupType(
121
+ ThingsGroove,
122
+ ThingsGroove.ScanBuilder.Scan,
123
+ Storage,
124
+ );
125
+
126
+ const Scan = ThingsGroove.ScanBuilder.Scan;
127
+
128
+ const thing_index_count = std.enums.values(Index).len;
129
+ /// The max number of indexes in a query.
130
+ const query_scans_max: comptime_int = @min(constants.lsm_scans_max, thing_index_count);
131
+ comptime {
132
+ assert(thing_index_count >= query_scans_max);
133
+ }
134
+
135
+ /// The max number of query parts.
136
+ /// If `query_scans_max == x`, then we can have at most x fields and x - 1 merge operations.
137
+ const query_part_max = (query_scans_max * 2) - 1;
138
+
139
+ const QueryPart = union(enum) {
140
+ const Field = struct { index: Index, value: u64 };
141
+ const Merge = struct { operator: QueryOperator, operand_count: u8 };
142
+
143
+ field: Field,
144
+ merge: Merge,
145
+ };
146
+
147
+ /// The query is represented non-recursively in reverse polish notation as an array of `QueryPart`.
148
+ /// Example: `(a OR b) AND (c OR d OR e)` == `[{AND;2}, {OR;2}, {a}, {b}, {OR;3}, {c}, {d}, {e}]`.
149
+ const Query = stdx.BoundedArrayType(QueryPart, query_part_max);
150
+
151
+ const QueryOperator = enum {
152
+ union_set,
153
+ intersection_set,
154
+
155
+ fn flip(self: QueryOperator) QueryOperator {
156
+ return switch (self) {
157
+ .union_set => .intersection_set,
158
+ .intersection_set => .union_set,
159
+ };
160
+ }
161
+ };
162
+
163
+ const QuerySpec = struct {
164
+ query: Query,
165
+ direction: Direction,
166
+
167
+ /// Formats the array of `QueryPart`, for debugging purposes.
168
+ /// E.g. "((a OR b) and c)".
169
+ pub fn format(
170
+ self: *const QuerySpec,
171
+ comptime _: []const u8,
172
+ _: std.fmt.FormatOptions,
173
+ writer: anytype,
174
+ ) !void {
175
+ var stack: stdx.BoundedArrayType(QueryPart.Merge, query_scans_max - 1) = .{};
176
+ var print_operator: bool = false;
177
+ for (0..self.query.count()) |index| {
178
+ // Reverse the RPN array in order to print in the natural order.
179
+ const query_part = self.query.get(self.query.count() - index - 1);
180
+
181
+ const merge_current: ?*QueryPart.Merge = if (stack.count() > 0) merge_current: {
182
+ const merge = &stack.slice()[stack.count() - 1];
183
+ assert(merge.operand_count > 0);
184
+ if (print_operator) switch (merge.operator) {
185
+ .union_set => try writer.print(" OR ", .{}),
186
+ .intersection_set => try writer.print(" AND ", .{}),
187
+ };
188
+ break :merge_current merge;
189
+ } else null;
190
+
191
+ switch (query_part) {
192
+ .field => |field| {
193
+ print_operator = true;
194
+ try writer.print("{s}", .{std.enums.tagName(Index, field.index).?});
195
+
196
+ if (merge_current) |merge| {
197
+ merge.operand_count -= 1;
198
+ }
199
+ },
200
+ .merge => |merge| {
201
+ print_operator = false;
202
+ try writer.print("(", .{});
203
+ stack.push(merge);
204
+ },
205
+ }
206
+
207
+ if (merge_current) |merge| {
208
+ if (merge.operand_count == 0) {
209
+ print_operator = true;
210
+ try writer.print(")", .{});
211
+ stack.truncate(stack.count() - 1);
212
+ }
213
+ }
214
+ }
215
+
216
+ // Closing the parenthesis from the tail of the stack:
217
+ stdx.maybe(stack.count() > 0);
218
+ while (stack.count() > 0) {
219
+ try writer.print(")", .{});
220
+ stack.truncate(stack.count() - 1);
221
+ }
222
+ assert(stack.count() == 0);
223
+ }
224
+
225
+ /// Returns whether the query results should include the specified object.
226
+ fn query_matches(query_spec: *const QuerySpec, thing: *const Thing) bool {
227
+ var matches = stdx.BoundedArrayType(bool, query_part_max){};
228
+ for (query_spec.query.const_slice()) |query_part| {
229
+ const match = switch (query_part) {
230
+ .field => |field| thing.get_index(field.index) == field.value,
231
+ .merge => |merge| switch (merge.operator) {
232
+ .union_set => match: {
233
+ var match: bool = false;
234
+ for (0..merge.operand_count) |_| match = matches.pop().? or match;
235
+ break :match match;
236
+ },
237
+ .intersection_set => match: {
238
+ var match: bool = true;
239
+ for (0..merge.operand_count) |_| match = matches.pop().? and match;
240
+ break :match match;
241
+ },
242
+ },
243
+ };
244
+ matches.push(match);
245
+ }
246
+ return matches.get(matches.count() - 1);
247
+ }
248
+ };
249
+
250
+ /// This fuzzer generates random arbitrary complex query conditions such as
251
+ /// `(a OR b) AND (c OR d OR (e AND f AND g))`.
252
+ ///
253
+ /// Some limitations in place:
254
+ ///
255
+ /// - Limited up to the max number of scans defined at `constants.lsm_scans_max`
256
+ /// or the number of indexed fields in `Thing`.
257
+ ///
258
+ /// - The next operator must be the opposite of the previous one,
259
+ /// avoiding unnecessary use of parenthesis, such as `(a AND b) AND c`.
260
+ /// This way, the query generated can be either `a AND b AND c` without
261
+ /// precedence or `(a AND b) OR c` flipping the operator.
262
+ ///
263
+ /// - Cannot repeat fields, while `(a=1 OR a=2)` is valid, this limitation avoids
264
+ /// always false conditions such as `(a=1 AND a=2)`.
265
+ const QuerySpecFuzzer = struct {
266
+ prng: *stdx.PRNG,
267
+ index_cardinality: [thing_index_count]u64,
268
+ indexes_used: std.EnumSet(Index) = std.EnumSet(Index).initEmpty(),
269
+
270
+ fn generate_fuzz_query_specs(
271
+ prng: *stdx.PRNG,
272
+ index_cardinality: [thing_index_count]u64,
273
+ ) [query_spec_max]QuerySpec {
274
+ var query_specs: [query_spec_max]QuerySpec = undefined;
275
+ for (&query_specs) |*query_spec| {
276
+ var fuzzer = QuerySpecFuzzer{
277
+ .prng = prng,
278
+ .index_cardinality = index_cardinality,
279
+ };
280
+
281
+ const query_field_max = prng.range_inclusive(u32, 1, query_scans_max);
282
+ const query = fuzzer.generate_query(query_field_max);
283
+
284
+ query_spec.* = .{
285
+ .query = query,
286
+ .direction = if (prng.boolean()) .ascending else .descending,
287
+ };
288
+ }
289
+ return query_specs;
290
+ }
291
+
292
+ fn generate_query(self: *QuerySpecFuzzer, field_max: u32) Query {
293
+ assert(field_max > 0);
294
+ assert(field_max <= query_scans_max);
295
+
296
+ const QueryPartTag = std.meta.Tag(QueryPart);
297
+ const MergeStack = struct {
298
+ index: usize,
299
+ operand_count: u8,
300
+ fields_remain: u32,
301
+
302
+ fn nested_merge_field_max(merge_stack: *const @This()) u32 {
303
+ // The query part must have at least two operands, if `operand_count == 0`
304
+ // it can start a nested query part, but at least one field must remain for
305
+ // the next operand.
306
+ return merge_stack.fields_remain - @intFromBool(merge_stack.operand_count == 0);
307
+ }
308
+ };
309
+
310
+ var query: Query = .{};
311
+ if (field_max == 1) {
312
+ // Single field queries must have just one part.
313
+ query.push(.{ .field = self.generate_query_field() });
314
+ return query;
315
+ }
316
+
317
+ // Multi field queries must start with a merge.
318
+ var stack: stdx.BoundedArrayType(MergeStack, query_scans_max - 1) = .{};
319
+ stack.push(.{
320
+ .index = 0,
321
+ .operand_count = 0,
322
+ .fields_remain = field_max,
323
+ });
324
+
325
+ query.push(.{
326
+ .merge = .{
327
+ .operator = self.prng.enum_uniform(QueryOperator),
328
+ .operand_count = 0,
329
+ },
330
+ });
331
+
332
+ // Limiting the maximum number of merges upfront produces both simple and complex
333
+ // queries with the same probability.
334
+ // Otherwise, simple queries would be rare or limited to have few fields.
335
+ const merge_max = self.prng.range_inclusive(u32, 1, field_max - 1);
336
+
337
+ var field_remain: u32 = field_max;
338
+ while (field_remain > 0) {
339
+ const stack_top: *MergeStack = &stack.slice()[stack.count() - 1];
340
+ const query_part_tag: QueryPartTag = if (stack.count() == merge_max) .field else tag: {
341
+ // Choose randomly between `.field` or `.merge` if there are enough
342
+ // available fields to start a new `.merge`.
343
+ assert(stack_top.fields_remain > 0);
344
+ const nested_merge_field_max = stack_top.nested_merge_field_max();
345
+ stdx.maybe(nested_merge_field_max == 0);
346
+ break :tag if (nested_merge_field_max > 1)
347
+ self.prng.enum_uniform(QueryPartTag)
348
+ else
349
+ .field;
350
+ };
351
+
352
+ const query_part = switch (query_part_tag) {
353
+ .field => field: {
354
+ assert(field_remain > 0);
355
+ field_remain -= 1;
356
+
357
+ assert(stack_top.fields_remain > 0);
358
+ stack_top.operand_count += 1;
359
+ stack_top.fields_remain -= 1;
360
+
361
+ if (stack_top.fields_remain == 0) {
362
+ assert(stack_top.operand_count > 1 or field_max == 1);
363
+
364
+ const parent = &query.slice()[stack_top.index];
365
+ parent.merge.operand_count = stack_top.operand_count;
366
+ stack.truncate(stack.count() - 1);
367
+ }
368
+
369
+ break :field QueryPart{
370
+ .field = self.generate_query_field(),
371
+ };
372
+ },
373
+ .merge => merge: {
374
+ assert(field_remain > 1);
375
+ const merge_field_remain = self.prng.range_inclusive(
376
+ u32,
377
+ // Merge must contain at least two fields, and at most the
378
+ // number of remaining field for the current merge.
379
+ 2,
380
+ stack_top.nested_merge_field_max(),
381
+ );
382
+
383
+ assert(merge_field_remain > 1);
384
+ assert(field_remain >= merge_field_remain);
385
+
386
+ stack_top.fields_remain -= merge_field_remain;
387
+ stack_top.operand_count += 1;
388
+
389
+ const parent: *QueryPart.Merge = &query.slice()[stack_top.index].merge;
390
+ if (stack_top.fields_remain == 0) {
391
+ assert(stack_top.operand_count > 1);
392
+ parent.operand_count = stack_top.operand_count;
393
+ stack.truncate(stack.count() - 1);
394
+ }
395
+
396
+ stack.push(.{
397
+ .index = query.count(),
398
+ .operand_count = 0,
399
+ .fields_remain = merge_field_remain,
400
+ });
401
+
402
+ break :merge QueryPart{
403
+ .merge = .{
404
+ .operator = parent.operator.flip(),
405
+ .operand_count = 0,
406
+ },
407
+ };
408
+ },
409
+ };
410
+
411
+ query.push(query_part);
412
+ }
413
+
414
+ assert(stack.count() == 0);
415
+
416
+ // Represented in reverse polish notation.
417
+ std.mem.reverse(QueryPart, query.slice());
418
+ return query;
419
+ }
420
+
421
+ fn generate_query_field(self: *QuerySpecFuzzer) QueryPart.Field {
422
+ assert(self.indexes_used.count() < thing_index_count);
423
+
424
+ const index = while (true) {
425
+ const index = self.prng.enum_uniform(Index);
426
+ if (!self.indexes_used.contains(index)) {
427
+ self.indexes_used.insert(index);
428
+ break index;
429
+ }
430
+ };
431
+
432
+ const index_cardinality = self.index_cardinality[@intFromEnum(index)];
433
+
434
+ return QueryPart.Field{
435
+ .index = index,
436
+ .value = self.prng.range_inclusive(u64, 1, index_cardinality),
437
+ };
438
+ }
439
+ };
440
+
441
+ const Environment = struct {
442
+ const node_count = 1024;
443
+
444
+ // This is the smallest size that set_associative_cache will allow us.
445
+ const cache_entries_max = 2048;
446
+ const forest_options = Forest.GroovesOptions{
447
+ .things = .{
448
+ .prefetch_entries_for_read_max = batch_objects_max,
449
+ .prefetch_entries_for_update_max = batch_objects_max,
450
+ .cache_entries_max = cache_entries_max,
451
+ .tree_options_object = .{ .batch_value_count_limit = batch_objects_max },
452
+ .tree_options_id = .{ .batch_value_count_limit = batch_objects_max },
453
+ .tree_options_index = .{
454
+ .index_01 = .{ .batch_value_count_limit = batch_objects_max },
455
+ .index_02 = .{ .batch_value_count_limit = batch_objects_max },
456
+ .index_03 = .{ .batch_value_count_limit = batch_objects_max },
457
+ .index_04 = .{ .batch_value_count_limit = batch_objects_max },
458
+ .index_05 = .{ .batch_value_count_limit = batch_objects_max },
459
+ .index_06 = .{ .batch_value_count_limit = batch_objects_max },
460
+ .index_07 = .{ .batch_value_count_limit = batch_objects_max },
461
+ .index_08 = .{ .batch_value_count_limit = batch_objects_max },
462
+ .index_09 = .{ .batch_value_count_limit = batch_objects_max },
463
+ .index_10 = .{ .batch_value_count_limit = batch_objects_max },
464
+ .index_11 = .{ .batch_value_count_limit = batch_objects_max },
465
+ .index_12 = .{ .batch_value_count_limit = batch_objects_max },
466
+ .index_13 = .{ .batch_value_count_limit = batch_objects_max },
467
+ },
468
+ },
469
+ };
470
+
471
+ const State = enum {
472
+ init,
473
+ forest_init,
474
+ forest_open,
475
+ fuzzing,
476
+ populating,
477
+ scanning,
478
+ forest_compact,
479
+ grid_checkpoint,
480
+ forest_checkpoint,
481
+ superblock_checkpoint,
482
+ grid_checkpoint_durable,
483
+ };
484
+
485
+ prng: *stdx.PRNG,
486
+ state: State,
487
+
488
+ storage: *Storage,
489
+ time_sim: TimeSim,
490
+ trace: Storage.Tracer,
491
+ superblock: SuperBlock,
492
+ superblock_context: SuperBlock.Context = undefined,
493
+ grid: Grid,
494
+ forest: Forest,
495
+ model: std.ArrayListUnmanaged(Thing), // Ordered by ascending timestamp.
496
+ model_matches: [query_spec_max]std.DynamicBitSetUnmanaged,
497
+ ticks_remaining: usize,
498
+
499
+ op: u64 = 1,
500
+ checkpoint_op: ?u64 = null,
501
+
502
+ scan_lookup: ScanLookup = undefined,
503
+ scan_lookup_buffer: []Thing,
504
+ scan_lookup_result: ?[]const Thing = null,
505
+
506
+ fn init(
507
+ env: *Environment,
508
+ gpa: std.mem.Allocator,
509
+ storage: *Storage,
510
+ prng: *stdx.PRNG,
511
+ ) !void {
512
+ env.time_sim = fixtures.init_time(.{});
513
+ env.trace = try fixtures.init_tracer(gpa, env.time_sim.time(), .{});
514
+ errdefer env.trace.deinit(gpa);
515
+
516
+ env.* = .{
517
+ .storage = storage,
518
+ .prng = prng,
519
+ .state = .init,
520
+ .time_sim = env.time_sim,
521
+ .trace = env.trace,
522
+
523
+ .superblock = try fixtures.init_superblock(gpa, env.storage, .{}),
524
+
525
+ .grid = try fixtures.init_grid(gpa, &env.trace, &env.superblock, .{
526
+ // Grid.mark_checkpoint_not_durable releases the FreeSet checkpoints blocks into
527
+ // FreeSet.blocks_released_prior_checkpoint_durability.
528
+ .blocks_released_prior_checkpoint_durability_max = 0,
529
+ }),
530
+ .forest = undefined,
531
+ .model = .{},
532
+ .model_matches = @splat(.{}),
533
+
534
+ .scan_lookup_buffer = try gpa.alloc(Thing, batch_objects_max),
535
+ .checkpoint_op = null,
536
+ .ticks_remaining = std.math.maxInt(usize),
537
+ };
538
+ }
539
+
540
+ fn deinit(env: *Environment, gpa: std.mem.Allocator) void {
541
+ for (&env.model_matches) |*matches| matches.deinit(gpa);
542
+ env.model.deinit(gpa);
543
+ env.superblock.deinit(gpa);
544
+ env.grid.deinit(gpa);
545
+ env.trace.deinit(gpa);
546
+ gpa.free(env.scan_lookup_buffer);
547
+ }
548
+
549
+ pub fn run(
550
+ gpa: std.mem.Allocator,
551
+ storage: *Storage,
552
+ prng: *stdx.PRNG,
553
+ commits_max: u32,
554
+ query_chance: Ratio,
555
+ ) !void {
556
+ assert(commits_max > 0);
557
+ log.info("commits = {}", .{commits_max});
558
+
559
+ var env: Environment = undefined;
560
+ try env.init(gpa, storage, prng);
561
+ defer env.deinit(gpa);
562
+
563
+ try env.open(gpa);
564
+ defer env.close(gpa);
565
+
566
+ var index_cardinality: [thing_index_count]u64 = undefined;
567
+ for (&index_cardinality) |*cardinality| {
568
+ cardinality.* = 1 +| fuzz.random_int_exponential(env.prng, u64, 32);
569
+ }
570
+
571
+ const query_specs = QuerySpecFuzzer.generate_fuzz_query_specs(env.prng, index_cardinality);
572
+ for (&query_specs, 0..) |*query_spec, i| {
573
+ log.info("query_specs[{}]: {} {s}", .{ i, query_spec, @tagName(query_spec.direction) });
574
+ }
575
+
576
+ for (0..commits_max) |_| {
577
+ assert(env.state == .fuzzing);
578
+
579
+ // Often insert full batches, to fill the database.
580
+ const batch_objects = if (prng.boolean())
581
+ batch_objects_max
582
+ else
583
+ prng.range_inclusive(u32, 1, batch_objects_max);
584
+ try env.model.ensureUnusedCapacity(gpa, batch_objects);
585
+ for (&env.model_matches) |*query_matches| {
586
+ try query_matches.resize(gpa, env.model.items.len + batch_objects, false);
587
+ }
588
+
589
+ for (0..batch_objects) |_| {
590
+ // TODO: sometimes update and delete things.
591
+ const thing_index = env.model.items.len;
592
+ const thing = Thing{
593
+ .id = env.prng.int(u128),
594
+ .index_01 = env.prng.range_inclusive(u64, 1, index_cardinality[0]),
595
+ .index_02 = env.prng.range_inclusive(u64, 1, index_cardinality[1]),
596
+ .index_03 = env.prng.range_inclusive(u64, 1, index_cardinality[2]),
597
+ .index_04 = env.prng.range_inclusive(u64, 1, index_cardinality[3]),
598
+ .index_05 = env.prng.range_inclusive(u64, 1, index_cardinality[4]),
599
+ .index_06 = env.prng.range_inclusive(u64, 1, index_cardinality[5]),
600
+ .index_07 = env.prng.range_inclusive(u64, 1, index_cardinality[6]),
601
+ .index_08 = env.prng.range_inclusive(u64, 1, index_cardinality[7]),
602
+ .index_09 = env.prng.range_inclusive(u64, 1, index_cardinality[8]),
603
+ .index_10 = env.prng.range_inclusive(u64, 1, index_cardinality[9]),
604
+ .index_11 = env.prng.range_inclusive(u64, 1, index_cardinality[10]),
605
+ .index_12 = env.prng.range_inclusive(u64, 1, index_cardinality[11]),
606
+ .index_13 = env.prng.range_inclusive(u64, 1, index_cardinality[12]),
607
+ .timestamp = thing_index + 1,
608
+ };
609
+
610
+ env.forest.grooves.things.insert(&thing);
611
+ env.model.appendAssumeCapacity(thing);
612
+
613
+ for (&query_specs, &env.model_matches) |*query_spec, *query_matches| {
614
+ query_matches.setValue(thing_index, query_spec.query_matches(&thing));
615
+ }
616
+ }
617
+ try env.commit();
618
+
619
+ if (env.prng.chance(query_chance)) {
620
+ for (&query_specs, &env.model_matches) |*query_spec, *query_matches| {
621
+ const query_results_count = try env.run_query(query_spec, query_matches);
622
+ assert(query_results_count == query_matches.count()); // Sanity-check.
623
+ }
624
+ }
625
+ }
626
+ for (&query_specs, &env.model_matches) |*query_spec, *query_matches| {
627
+ const query_results_count = try env.run_query(query_spec, query_matches);
628
+ assert(query_results_count == query_matches.count()); // Sanity-check.
629
+ }
630
+ }
631
+
632
+ fn run_query(
633
+ env: *Environment,
634
+ query_spec: *const QuerySpec,
635
+ model_matches: *const std.DynamicBitSetUnmanaged,
636
+ ) !u32 {
637
+ assert(model_matches.bit_length >= env.model.items.len);
638
+
639
+ var timestamp_previous: u64 = switch (query_spec.direction) {
640
+ .ascending => 0,
641
+ .descending => std.math.maxInt(u64),
642
+ };
643
+
644
+ // Execute the query repeatedly with different limits, paging until all objects are scanned.
645
+ var results_count: u32 = 0;
646
+ var model_offset: u32 = 0;
647
+ while (model_offset < env.model.items.len) {
648
+ assert(env.forest.scan_buffer_pool.scan_buffer_used == 0);
649
+ assert(env.scan_lookup_result == null);
650
+ defer {
651
+ env.forest.scan_buffer_pool.reset();
652
+ env.forest.grooves.things.scan_builder.reset();
653
+ }
654
+
655
+ const query_results_max: u32 = env.prng.range_inclusive(
656
+ u32,
657
+ 1,
658
+ @intCast(env.scan_lookup_buffer.len),
659
+ );
660
+ assert(query_results_max > 0);
661
+ const query_results = results: {
662
+ const scan = env.scan_from_condition(query_spec, timestamp_previous);
663
+ env.scan_lookup = ScanLookup.init(&env.forest.grooves.things, scan);
664
+ const scan_lookup_buffer = env.scan_lookup_buffer[0..query_results_max];
665
+
666
+ env.change_state(.fuzzing, .scanning);
667
+ env.scan_lookup.read(scan_lookup_buffer, &scan_lookup_callback);
668
+ try env.tick_until_state_change(.scanning, .fuzzing);
669
+
670
+ const query_results = env.scan_lookup_result.?;
671
+ env.scan_lookup_result = null;
672
+ break :results query_results;
673
+ };
674
+
675
+ var results_index: u32 = 0;
676
+ while (model_offset < env.model.items.len) {
677
+ defer model_offset += 1;
678
+
679
+ const model_index = switch (query_spec.direction) {
680
+ .ascending => model_offset,
681
+ .descending => env.model.items.len - model_offset - 1,
682
+ };
683
+
684
+ if (model_matches.isSet(model_index)) {
685
+ assert(results_index < query_results.len);
686
+ // Positive space:
687
+ // - Each result is a valid, matching object from the model.
688
+ // - The results are ordered correctly.
689
+ const query_result = &query_results[results_index];
690
+ const model_result = &env.model.items[model_index];
691
+ assert(stdx.equal_bytes(Thing, model_result, query_result));
692
+
693
+ timestamp_previous = query_result.timestamp;
694
+ results_index += 1;
695
+ if (results_index == query_results_max) break;
696
+ } else {
697
+ maybe(results_index == query_results.len);
698
+ }
699
+ }
700
+ // Negative space: The query didn't miss any matching objects.
701
+ assert(results_index == query_results.len);
702
+
703
+ results_count += @intCast(query_results.len);
704
+ assert(results_count <= model_matches.count());
705
+ assert(results_count == model_matches.count() or query_results.len > 0);
706
+ }
707
+ assert(model_matches.count() == results_count);
708
+
709
+ return results_count;
710
+ }
711
+
712
+ fn scan_from_condition(
713
+ env: *Environment,
714
+ query_spec: *const QuerySpec,
715
+ timestamp_last: u64, // exclusive
716
+ ) *Scan {
717
+ const scan_buffer_pool = &env.forest.scan_buffer_pool;
718
+ const things_groove = &env.forest.grooves.things;
719
+ const scan_builder: *ThingsGroove.ScanBuilder = &things_groove.scan_builder;
720
+ const snapshot = env.op;
721
+
722
+ var stack = stdx.BoundedArrayType(*Scan, query_scans_max){};
723
+ for (query_spec.query.const_slice()) |query_part| {
724
+ switch (query_part) {
725
+ .field => |field| {
726
+ const timestamp_range = if (timestamp_last == 0)
727
+ TimestampRange.all()
728
+ else if (query_spec.direction == .descending)
729
+ TimestampRange.lte(timestamp_last - 1)
730
+ else
731
+ TimestampRange.gte(timestamp_last + 1);
732
+ assert(timestamp_range.min <= timestamp_range.max);
733
+
734
+ const scan = switch (field.index) {
735
+ inline else => |comptime_index| scan_builder.scan_prefix(
736
+ comptime_index,
737
+ scan_buffer_pool.acquire_assume_capacity(),
738
+ snapshot,
739
+ field.value,
740
+ timestamp_range,
741
+ query_spec.direction,
742
+ ),
743
+ };
744
+ stack.push(scan);
745
+ },
746
+ .merge => |merge| {
747
+ assert(merge.operand_count > 1);
748
+
749
+ const scans_to_merge = stack.slice()[stack.count() - merge.operand_count ..];
750
+
751
+ const scan = switch (merge.operator) {
752
+ .union_set => scan_builder.merge_union(scans_to_merge),
753
+ .intersection_set => scan_builder.merge_intersection(scans_to_merge),
754
+ };
755
+
756
+ stack.truncate(stack.count() - merge.operand_count);
757
+ stack.push(scan);
758
+ },
759
+ }
760
+ }
761
+
762
+ assert(stack.count() == 1);
763
+ return stack.get(0);
764
+ }
765
+
766
+ fn change_state(env: *Environment, current_state: State, next_state: State) void {
767
+ assert(env.state == current_state);
768
+ env.state = next_state;
769
+ }
770
+
771
+ fn tick_until_state_change(env: *Environment, current_state: State, next_state: State) !void {
772
+ while (true) {
773
+ if (env.state != current_state) break;
774
+
775
+ if (env.ticks_remaining == 0) return error.OutOfTicks;
776
+ env.ticks_remaining -= 1;
777
+ env.storage.run();
778
+ }
779
+ assert(env.state == next_state);
780
+ }
781
+
782
+ fn open(env: *Environment, gpa: std.mem.Allocator) !void {
783
+ fixtures.open_superblock(&env.superblock);
784
+ fixtures.open_grid(&env.grid);
785
+
786
+ // The first checkpoint is trivially durable.
787
+ env.grid.free_set.mark_checkpoint_durable();
788
+
789
+ env.change_state(.init, .forest_init);
790
+ try env.forest.init(gpa, &env.grid, .{
791
+ .compaction_block_count = Forest.Options.compaction_block_count_min,
792
+ .node_count = node_count,
793
+ }, forest_options);
794
+ env.change_state(.forest_init, .forest_open);
795
+ env.forest.open(forest_open_callback);
796
+
797
+ try env.tick_until_state_change(.forest_open, .fuzzing);
798
+ }
799
+
800
+ fn close(env: *Environment, gpa: std.mem.Allocator) void {
801
+ env.forest.deinit(gpa);
802
+ }
803
+
804
+ fn commit(env: *Environment) !void {
805
+ defer env.op += 1;
806
+
807
+ // TODO Make LSM (and this fuzzer) unaware of VSR's checkpoint schedule.
808
+ const checkpoint = env.op == vsr.Checkpoint.trigger_for_checkpoint(
809
+ vsr.Checkpoint.checkpoint_after(env.superblock.working.vsr_state.checkpoint.header.op),
810
+ );
811
+
812
+ env.change_state(.fuzzing, .forest_compact);
813
+ env.forest.compact(forest_compact_callback, env.op);
814
+ try env.tick_until_state_change(.forest_compact, .fuzzing);
815
+
816
+ if (checkpoint) {
817
+ assert(env.checkpoint_op == null);
818
+ env.checkpoint_op = env.op - constants.lsm_compaction_ops;
819
+
820
+ env.change_state(.fuzzing, .forest_checkpoint);
821
+ env.forest.checkpoint(forest_checkpoint_callback);
822
+ try env.tick_until_state_change(.forest_checkpoint, .grid_checkpoint);
823
+
824
+ env.grid.checkpoint(grid_checkpoint_callback);
825
+ try env.tick_until_state_change(.grid_checkpoint, .superblock_checkpoint);
826
+
827
+ env.superblock.checkpoint(superblock_checkpoint_callback, &env.superblock_context, .{
828
+ .header = header: {
829
+ var header = vsr.Header.Prepare.root(fixtures.cluster);
830
+ header.op = env.checkpoint_op.?;
831
+ header.set_checksum();
832
+ break :header header;
833
+ },
834
+ .view_attributes = null,
835
+ .manifest_references = env.forest.manifest_log.checkpoint_references(),
836
+ .free_set_references = .{
837
+ .blocks_acquired = env.grid
838
+ .free_set_checkpoint_blocks_acquired.checkpoint_reference(),
839
+ .blocks_released = env.grid
840
+ .free_set_checkpoint_blocks_released.checkpoint_reference(),
841
+ },
842
+ .client_sessions_reference = .{
843
+ .last_block_checksum = 0,
844
+ .last_block_address = 0,
845
+ .trailer_size = 0,
846
+ .checksum = vsr.checksum(&.{}),
847
+ },
848
+ .commit_max = env.checkpoint_op.? + 1,
849
+ .sync_op_min = 0,
850
+ .sync_op_max = 0,
851
+ .storage_size = vsr.superblock.data_file_size_min +
852
+ (env.grid.free_set.highest_address_acquired() orelse 0) * constants.block_size,
853
+ .release = vsr.Release.minimum,
854
+ });
855
+ try env.tick_until_state_change(.superblock_checkpoint, .fuzzing);
856
+
857
+ // The fuzzer runs in a single process, all checkpoints are trivially durable. Use
858
+ // free_set.mark_checkpoint_durable() instead of grid.mark_checkpoint_durable(); the
859
+ // latter requires passing a callback, which is called synchronously in fuzzers anyway.
860
+ env.grid.mark_checkpoint_not_durable();
861
+ env.grid.free_set.mark_checkpoint_durable();
862
+
863
+ env.checkpoint_op = null;
864
+ }
865
+ }
866
+
867
+ fn forest_open_callback(forest: *Forest) void {
868
+ const env: *Environment = @fieldParentPtr("forest", forest);
869
+ env.change_state(.forest_open, .fuzzing);
870
+ }
871
+
872
+ fn grid_checkpoint_callback(grid: *Grid) void {
873
+ const env: *Environment = @fieldParentPtr("grid", grid);
874
+ assert(env.checkpoint_op != null);
875
+ env.change_state(.grid_checkpoint, .superblock_checkpoint);
876
+ }
877
+
878
+ fn forest_checkpoint_callback(forest: *Forest) void {
879
+ const env: *Environment = @fieldParentPtr("forest", forest);
880
+ assert(env.checkpoint_op != null);
881
+ env.change_state(.forest_checkpoint, .grid_checkpoint);
882
+ }
883
+
884
+ fn superblock_checkpoint_callback(superblock_context: *SuperBlock.Context) void {
885
+ const env: *Environment = @fieldParentPtr("superblock_context", superblock_context);
886
+ env.change_state(.superblock_checkpoint, .fuzzing);
887
+ }
888
+
889
+ fn forest_compact_callback(forest: *Forest) void {
890
+ const env: *Environment = @fieldParentPtr("forest", forest);
891
+ env.change_state(.forest_compact, .fuzzing);
892
+ }
893
+
894
+ fn scan_lookup_callback(scan_lookup: *ScanLookup, result: []const Thing) void {
895
+ const env: *Environment = @fieldParentPtr("scan_lookup", scan_lookup);
896
+ assert(env.scan_lookup_result == null);
897
+ env.scan_lookup_result = result;
898
+ env.change_state(.scanning, .fuzzing);
899
+ }
900
+ };
901
+
902
+ pub fn main(gpa: std.mem.Allocator, fuzz_args: fuzz.FuzzArgs) !void {
903
+ var prng = stdx.PRNG.from_seed(fuzz_args.seed);
904
+
905
+ var storage = try fixtures.init_storage(gpa, .{
906
+ .seed = prng.int(u64),
907
+ .size = constants.storage_size_limit_default,
908
+ });
909
+ defer storage.deinit(gpa);
910
+
911
+ try fixtures.storage_format(gpa, &storage, .{});
912
+
913
+ const commits_max: u32 = @intCast(
914
+ fuzz_args.events_max orelse prng.range_inclusive(u32, 1, 1024),
915
+ );
916
+
917
+ try Environment.run(gpa, &storage, &prng, commits_max, lerp_query_chance(commits_max));
918
+
919
+ log.info("Passed!", .{});
920
+ }
921
+
922
+ // Fuzzer is quadratic in commit_max, so make query probability decay linearly to 10% if
923
+ // commit_max ≥ 1_000
924
+ fn lerp_query_chance(commits_max: u64) Ratio {
925
+ return ratio(
926
+ // See the comptime test below.
927
+ @max(100 - @divFloor(100 * @as(u64, @min(commits_max, 1_000)), 1_000), 10),
928
+ 100,
929
+ );
930
+ }
931
+
932
+ comptime {
933
+ assert(lerp_query_chance(0).numerator == 100);
934
+ assert(lerp_query_chance(10).numerator == 99);
935
+ assert(lerp_query_chance(500).numerator == 50);
936
+ assert(lerp_query_chance(1_000).numerator == 10);
937
+ assert(lerp_query_chance(10_000).numerator == 10);
938
+ }