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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/ext/tb_client/extconf.rb +13 -13
- data/ext/tb_client/tigerbeetle/LICENSE +177 -0
- data/ext/tb_client/tigerbeetle/build.zig +2327 -0
- data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
- data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
- data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
- data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
- data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
- data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
- data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
- data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
- data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
- data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
- data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
- data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
- data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
- data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
- data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
- data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
- data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
- data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
- data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
- data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
- data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
- data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
- data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
- data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
- data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
- data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
- data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
- data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
- data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
- data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
- data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
- data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
- data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
- data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
- data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
- data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
- data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
- data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
- data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
- data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
- data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
- data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
- data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
- data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
- data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
- data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
- data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
- data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
- data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
- data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
- data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
- data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
- data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
- data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
- data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
- data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
- data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
- data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
- data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
- data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
- data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
- data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
- data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
- data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
- data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
- data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
- data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
- data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
- data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
- data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
- data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
- data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
- data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
- data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
- data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
- data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
- data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
- data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
- data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
- data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
- data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
- data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
- data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
- data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
- data/lib/tb_client/shared_lib.rb +12 -5
- data/lib/tigerbeetle/client.rb +1 -1
- data/lib/tigerbeetle/platforms.rb +9 -0
- data/lib/tigerbeetle/version.rb +2 -2
- data/tigerbeetle.gemspec +22 -5
- metadata +242 -3
- data/ext/tb_client/pkg.tar.gz +0 -0
|
@@ -0,0 +1,1495 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const builtin = @import("builtin");
|
|
3
|
+
const assert = std.debug.assert;
|
|
4
|
+
const maybe = stdx.maybe;
|
|
5
|
+
const math = std.math;
|
|
6
|
+
const mem = std.mem;
|
|
7
|
+
|
|
8
|
+
const stdx = @import("stdx");
|
|
9
|
+
const constants = @import("../constants.zig");
|
|
10
|
+
|
|
11
|
+
const TableType = @import("table.zig").TableType;
|
|
12
|
+
const TimestampRange = @import("timestamp_range.zig").TimestampRange;
|
|
13
|
+
const TreeType = @import("tree.zig").TreeType;
|
|
14
|
+
const GridType = @import("../vsr/grid.zig").GridType;
|
|
15
|
+
const CompositeKeyType = @import("composite_key.zig").CompositeKeyType;
|
|
16
|
+
const NodePool = @import("node_pool.zig").NodePoolType(constants.lsm_manifest_node_size, 16);
|
|
17
|
+
const CacheMapType = @import("cache_map.zig").CacheMapType;
|
|
18
|
+
const ScopeCloseMode = @import("tree.zig").ScopeCloseMode;
|
|
19
|
+
const ManifestLogType = @import("manifest_log.zig").ManifestLogType;
|
|
20
|
+
const ScanBuilderType = @import("scan_builder.zig").ScanBuilderType;
|
|
21
|
+
|
|
22
|
+
const ScratchMemory = @import("scratch_memory.zig").ScratchMemory;
|
|
23
|
+
|
|
24
|
+
const snapshot_latest = @import("tree.zig").snapshot_latest;
|
|
25
|
+
|
|
26
|
+
fn ObjectTreeHelperType(comptime Object: type) type {
|
|
27
|
+
assert(@hasField(Object, "timestamp"));
|
|
28
|
+
assert(@FieldType(Object, "timestamp") == u64);
|
|
29
|
+
|
|
30
|
+
return struct {
|
|
31
|
+
inline fn key_from_value(value: *const Object) u64 {
|
|
32
|
+
return value.timestamp & ~@as(u64, tombstone_bit);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sentinel_key = std.math.maxInt(u64);
|
|
36
|
+
const tombstone_bit = 1 << (64 - 1);
|
|
37
|
+
|
|
38
|
+
inline fn tombstone(value: *const Object) bool {
|
|
39
|
+
return (value.timestamp & tombstone_bit) != 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
inline fn tombstone_from_key(timestamp: u64) Object {
|
|
43
|
+
assert(timestamp & tombstone_bit == 0);
|
|
44
|
+
|
|
45
|
+
var value = std.mem.zeroes(Object); // Full zero-initialized Value.
|
|
46
|
+
value.timestamp = timestamp | tombstone_bit;
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const IdTreeValue = extern struct {
|
|
53
|
+
id: u128,
|
|
54
|
+
timestamp: u64,
|
|
55
|
+
padding: u64 = 0,
|
|
56
|
+
|
|
57
|
+
comptime {
|
|
58
|
+
// Assert that there is no implicit padding.
|
|
59
|
+
assert(@sizeOf(IdTreeValue) == 32);
|
|
60
|
+
assert(stdx.no_padding(IdTreeValue));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
inline fn key_from_value(value: *const IdTreeValue) u128 {
|
|
64
|
+
return value.id;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const sentinel_key = std.math.maxInt(u128);
|
|
68
|
+
const tombstone_bit = 1 << (64 - 1);
|
|
69
|
+
|
|
70
|
+
inline fn tombstone(value: *const IdTreeValue) bool {
|
|
71
|
+
return (value.timestamp & tombstone_bit) != 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
inline fn tombstone_from_key(id: u128) IdTreeValue {
|
|
75
|
+
return .{
|
|
76
|
+
.id = id,
|
|
77
|
+
.timestamp = tombstone_bit,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/// Normalizes index tree field types into either u64 or u128 for CompositeKey
|
|
83
|
+
fn IndexCompositeKeyType(comptime Field: type) type {
|
|
84
|
+
switch (@typeInfo(Field)) {
|
|
85
|
+
.void => return void,
|
|
86
|
+
.@"enum" => |e| {
|
|
87
|
+
return switch (@bitSizeOf(e.tag_type)) {
|
|
88
|
+
0...@bitSizeOf(u64) => u64,
|
|
89
|
+
@bitSizeOf(u65)...@bitSizeOf(u128) => u128,
|
|
90
|
+
else => @compileError("Unsupported enum tag for index: " ++ @typeName(e.tag_type)),
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
.int => |i| {
|
|
94
|
+
if (i.signedness != .unsigned) {
|
|
95
|
+
@compileError("Index int type (" ++ @typeName(Field) ++ ") is not unsigned");
|
|
96
|
+
}
|
|
97
|
+
return switch (@bitSizeOf(Field)) {
|
|
98
|
+
0...@bitSizeOf(u64) => u64,
|
|
99
|
+
@bitSizeOf(u65)...@bitSizeOf(u128) => u128,
|
|
100
|
+
else => @compileError("Unsupported int type for index: " ++ @typeName(Field)),
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
else => @compileError("Index type " ++ @typeName(Field) ++ " is not supported"),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
comptime {
|
|
108
|
+
assert(IndexCompositeKeyType(void) == void);
|
|
109
|
+
assert(IndexCompositeKeyType(u0) == u64);
|
|
110
|
+
assert(IndexCompositeKeyType(enum(u0) { x }) == u64);
|
|
111
|
+
|
|
112
|
+
assert(IndexCompositeKeyType(u1) == u64);
|
|
113
|
+
assert(IndexCompositeKeyType(u16) == u64);
|
|
114
|
+
assert(IndexCompositeKeyType(enum(u16) { x }) == u64);
|
|
115
|
+
|
|
116
|
+
assert(IndexCompositeKeyType(u32) == u64);
|
|
117
|
+
assert(IndexCompositeKeyType(u63) == u64);
|
|
118
|
+
assert(IndexCompositeKeyType(u64) == u64);
|
|
119
|
+
|
|
120
|
+
assert(IndexCompositeKeyType(enum(u65) { x }) == u128);
|
|
121
|
+
assert(IndexCompositeKeyType(u65) == u128);
|
|
122
|
+
assert(IndexCompositeKeyType(u128) == u128);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fn IndexTreeType(
|
|
126
|
+
comptime Storage: type,
|
|
127
|
+
comptime Field: type,
|
|
128
|
+
comptime table_value_count_max: usize,
|
|
129
|
+
) type {
|
|
130
|
+
const CompositeKey = CompositeKeyType(IndexCompositeKeyType(Field));
|
|
131
|
+
const Table = TableType(
|
|
132
|
+
CompositeKey.Key,
|
|
133
|
+
CompositeKey,
|
|
134
|
+
CompositeKey.key_from_value,
|
|
135
|
+
CompositeKey.sentinel_key,
|
|
136
|
+
CompositeKey.tombstone,
|
|
137
|
+
CompositeKey.tombstone_from_key,
|
|
138
|
+
table_value_count_max,
|
|
139
|
+
.secondary_index,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return TreeType(Table, Storage);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// A Groove is a collection of LSM trees auto generated for fields on a struct type
|
|
146
|
+
/// as well as custom derived fields from said struct type.
|
|
147
|
+
pub fn GrooveType(
|
|
148
|
+
comptime Storage: type,
|
|
149
|
+
comptime Object: type,
|
|
150
|
+
/// An anonymous struct instance which contains the following:
|
|
151
|
+
///
|
|
152
|
+
/// - ids: { .tree = u16 }:
|
|
153
|
+
/// An anonymous struct which maps each of the groove's trees to a stable, forest-unique,
|
|
154
|
+
/// tree identifier.
|
|
155
|
+
///
|
|
156
|
+
/// - batch_value_count_max: { .field = usize }:
|
|
157
|
+
/// An anonymous struct which contains, for each field of `Object`,
|
|
158
|
+
/// the maximum number of values per table per batch for the corresponding index tree.
|
|
159
|
+
///
|
|
160
|
+
/// - ignored: [][]const u8:
|
|
161
|
+
/// An array of fields on the Object type that should not be given index trees
|
|
162
|
+
///
|
|
163
|
+
/// - optional: [][]const u8:
|
|
164
|
+
/// An array of fields that should *not* index zero values.
|
|
165
|
+
///
|
|
166
|
+
/// - derived: { .field = *const fn (*const Object) ?DerivedType }:
|
|
167
|
+
/// An anonymous struct which contain fields that don't exist on the Object
|
|
168
|
+
/// but can be derived from an Object instance using the field's corresponding function.
|
|
169
|
+
///
|
|
170
|
+
/// - orphaned_ids: bool:
|
|
171
|
+
/// Whether Groove should store objectless `id`s to prevent their reuse.
|
|
172
|
+
/// Should be `true` only if the object contains an `id` field.
|
|
173
|
+
///
|
|
174
|
+
/// - objects_cache: bool:
|
|
175
|
+
/// Whether Groove should have an ObjectCache.
|
|
176
|
+
/// Should be `false` only if both `prefetch_entries_for_update_max` and
|
|
177
|
+
/// `prefetch_entries_for_read_max` are set to 0.
|
|
178
|
+
comptime groove_options: anytype,
|
|
179
|
+
) type {
|
|
180
|
+
@setEvalBranchQuota(64_000);
|
|
181
|
+
|
|
182
|
+
const has_id = @hasField(Object, "id");
|
|
183
|
+
comptime if (has_id) assert(@FieldType(Object, "id") == u128);
|
|
184
|
+
comptime if (groove_options.orphaned_ids) assert(has_id);
|
|
185
|
+
|
|
186
|
+
assert(@hasField(Object, "timestamp"));
|
|
187
|
+
assert(@FieldType(Object, "timestamp") == u64);
|
|
188
|
+
|
|
189
|
+
comptime var index_fields: []const std.builtin.Type.StructField = &.{};
|
|
190
|
+
|
|
191
|
+
const primary_field = if (has_id) "id" else "timestamp";
|
|
192
|
+
const PrimaryKey = if (has_id) u128 else u64;
|
|
193
|
+
|
|
194
|
+
// Generate index LSM trees from the struct fields.
|
|
195
|
+
for (std.meta.fields(Object)) |field| {
|
|
196
|
+
// See if we should ignore this field from the options.
|
|
197
|
+
//
|
|
198
|
+
// By default, we ignore the "timestamp" field since it's a special identifier.
|
|
199
|
+
// Since the "timestamp" is ignored by default, it shouldn't be provided
|
|
200
|
+
// in groove_options.ignored.
|
|
201
|
+
comptime var ignored =
|
|
202
|
+
mem.eql(u8, field.name, "timestamp") or mem.eql(u8, field.name, "id");
|
|
203
|
+
for (groove_options.ignored) |ignored_field_name| {
|
|
204
|
+
comptime assert(!std.mem.eql(u8, ignored_field_name, "timestamp"));
|
|
205
|
+
comptime assert(!std.mem.eql(u8, ignored_field_name, "id"));
|
|
206
|
+
ignored = ignored or std.mem.eql(u8, field.name, ignored_field_name);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!ignored) {
|
|
210
|
+
const table_value_count_max = constants.lsm_compaction_ops *
|
|
211
|
+
@field(groove_options.batch_value_count_max, field.name);
|
|
212
|
+
const IndexTree = IndexTreeType(Storage, field.type, table_value_count_max);
|
|
213
|
+
index_fields = index_fields ++ [_]std.builtin.Type.StructField{
|
|
214
|
+
.{
|
|
215
|
+
.name = field.name,
|
|
216
|
+
.type = IndexTree,
|
|
217
|
+
.default_value_ptr = null,
|
|
218
|
+
.is_comptime = false,
|
|
219
|
+
.alignment = @alignOf(IndexTree),
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Generate IndexTrees for fields derived from the Value in groove_options.
|
|
226
|
+
const derived_fields = std.meta.fields(@TypeOf(groove_options.derived));
|
|
227
|
+
for (derived_fields) |field| {
|
|
228
|
+
// Get the function info for the derived field.
|
|
229
|
+
const derive_func = @field(groove_options.derived, field.name);
|
|
230
|
+
const derive_func_info = @typeInfo(@TypeOf(derive_func)).@"fn";
|
|
231
|
+
|
|
232
|
+
// Make sure it has only one argument.
|
|
233
|
+
if (derive_func_info.params.len != 1) {
|
|
234
|
+
@compileError("expected derive fn to take in *const " ++ @typeName(Object));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Make sure the function takes in a reference to the Value:
|
|
238
|
+
const derive_arg = derive_func_info.params[0];
|
|
239
|
+
if (derive_arg.is_generic) @compileError("expected derive fn arg to not be generic");
|
|
240
|
+
if (derive_arg.type != *const Object) {
|
|
241
|
+
@compileError("expected derive fn to take in *const " ++ @typeName(Object));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Get the return value from the derived field as the DerivedType.
|
|
245
|
+
if (derive_func_info.return_type == null) {
|
|
246
|
+
@compileError("expected derive fn to return valid tree index type");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const derive_return_type = @typeInfo(derive_func_info.return_type.?);
|
|
250
|
+
if (derive_return_type != .optional) {
|
|
251
|
+
@compileError("expected derive fn to return optional tree index type");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const DerivedType = derive_return_type.optional.child;
|
|
255
|
+
const table_value_count_max = constants.lsm_compaction_ops *
|
|
256
|
+
@field(groove_options.batch_value_count_max, field.name);
|
|
257
|
+
const IndexTree = IndexTreeType(Storage, DerivedType, table_value_count_max);
|
|
258
|
+
|
|
259
|
+
index_fields = index_fields ++ [_]std.builtin.Type.StructField{
|
|
260
|
+
.{
|
|
261
|
+
.name = field.name,
|
|
262
|
+
.type = IndexTree,
|
|
263
|
+
.default_value_ptr = null,
|
|
264
|
+
.is_comptime = false,
|
|
265
|
+
.alignment = @alignOf(IndexTree),
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
comptime var index_options_fields: [index_fields.len]std.builtin.Type.StructField = undefined;
|
|
271
|
+
for (index_fields, 0..) |index_field, i| {
|
|
272
|
+
const IndexTree = index_field.type;
|
|
273
|
+
index_options_fields[i] = .{
|
|
274
|
+
.name = index_field.name,
|
|
275
|
+
.type = IndexTree.Options,
|
|
276
|
+
.default_value_ptr = null,
|
|
277
|
+
.is_comptime = false,
|
|
278
|
+
.alignment = @alignOf(IndexTree.Options),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Verify that every tree referenced by "optional" corresponds to an actual field.
|
|
283
|
+
for (groove_options.optional) |field_name| {
|
|
284
|
+
if (!@hasField(Object, field_name)) {
|
|
285
|
+
std.debug.panic("optional: unrecognized field name: {s}", .{field_name});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const ObjectTreeHelper = ObjectTreeHelperType(Object);
|
|
290
|
+
|
|
291
|
+
const _ObjectTree = blk: {
|
|
292
|
+
const table_value_count_max = constants.lsm_compaction_ops *
|
|
293
|
+
groove_options.batch_value_count_max.timestamp;
|
|
294
|
+
const Table = TableType(
|
|
295
|
+
u64, // key = timestamp
|
|
296
|
+
Object,
|
|
297
|
+
ObjectTreeHelper.key_from_value,
|
|
298
|
+
ObjectTreeHelper.sentinel_key,
|
|
299
|
+
ObjectTreeHelper.tombstone,
|
|
300
|
+
ObjectTreeHelper.tombstone_from_key,
|
|
301
|
+
table_value_count_max,
|
|
302
|
+
.general,
|
|
303
|
+
);
|
|
304
|
+
break :blk TreeType(Table, Storage);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const _IdTree = if (!has_id) void else blk: {
|
|
308
|
+
const table_value_count_max = constants.lsm_compaction_ops *
|
|
309
|
+
groove_options.batch_value_count_max.id;
|
|
310
|
+
const Table = TableType(
|
|
311
|
+
u128,
|
|
312
|
+
IdTreeValue,
|
|
313
|
+
IdTreeValue.key_from_value,
|
|
314
|
+
IdTreeValue.sentinel_key,
|
|
315
|
+
IdTreeValue.tombstone,
|
|
316
|
+
IdTreeValue.tombstone_from_key,
|
|
317
|
+
table_value_count_max,
|
|
318
|
+
.general,
|
|
319
|
+
);
|
|
320
|
+
break :blk TreeType(Table, Storage);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const _IndexTrees = @Type(.{
|
|
324
|
+
.@"struct" = .{
|
|
325
|
+
.layout = .auto,
|
|
326
|
+
.fields = index_fields,
|
|
327
|
+
.decls = &.{},
|
|
328
|
+
.is_tuple = false,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
const _IndexTreeOptions = @Type(.{
|
|
332
|
+
.@"struct" = .{
|
|
333
|
+
.layout = .auto,
|
|
334
|
+
.fields = &index_options_fields,
|
|
335
|
+
.decls = &.{},
|
|
336
|
+
.is_tuple = false,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const has_scan = index_fields.len > 0;
|
|
341
|
+
|
|
342
|
+
// Verify groove index count:
|
|
343
|
+
const indexes_count_actual = std.meta.fields(_IndexTrees).len;
|
|
344
|
+
const indexes_count_expect = std.meta.fields(Object).len -
|
|
345
|
+
groove_options.ignored.len -
|
|
346
|
+
// The id/timestamp fields are implicitly ignored since it's the primary key for ObjectTree:
|
|
347
|
+
(@as(usize, 1) + @intFromBool(has_id)) +
|
|
348
|
+
std.meta.fields(@TypeOf(groove_options.derived)).len;
|
|
349
|
+
|
|
350
|
+
assert(indexes_count_actual == indexes_count_expect);
|
|
351
|
+
assert(indexes_count_actual == std.meta.fields(_IndexTreeOptions).len);
|
|
352
|
+
|
|
353
|
+
const _IndexTreeFieldHelperType = struct {
|
|
354
|
+
fn HelperType(comptime field_name: []const u8) type {
|
|
355
|
+
return struct {
|
|
356
|
+
pub const Index = type: {
|
|
357
|
+
if (is_derived) {
|
|
358
|
+
const derived_fn = @typeInfo(@TypeOf(@field(
|
|
359
|
+
groove_options.derived,
|
|
360
|
+
field_name,
|
|
361
|
+
)));
|
|
362
|
+
assert(derived_fn == .@"fn");
|
|
363
|
+
assert(derived_fn.@"fn".return_type != null);
|
|
364
|
+
|
|
365
|
+
const return_type = @typeInfo(derived_fn.@"fn".return_type.?);
|
|
366
|
+
assert(return_type == .optional);
|
|
367
|
+
break :type return_type.optional.child;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
break :type @TypeOf(@field(@as(Object, undefined), field_name));
|
|
371
|
+
};
|
|
372
|
+
pub const IndexPrefix = switch (@typeInfo(Index)) {
|
|
373
|
+
.void => void,
|
|
374
|
+
.int => Index,
|
|
375
|
+
.@"enum" => |info| info.tag_type,
|
|
376
|
+
else => @compileError("Unsupported index type for " ++ field_name),
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const is_derived: bool = is_derived: {
|
|
380
|
+
for (derived_fields) |derived_field| {
|
|
381
|
+
if (std.mem.eql(u8, derived_field.name, field_name)) break :is_derived true;
|
|
382
|
+
}
|
|
383
|
+
break :is_derived false;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const allow_zero: bool = allow_zero: {
|
|
387
|
+
for (groove_options.optional) |optional| {
|
|
388
|
+
if (std.mem.eql(u8, field_name, optional)) {
|
|
389
|
+
assert(!is_derived);
|
|
390
|
+
break :allow_zero false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
break :allow_zero true;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
inline fn as_prefix(index: Index) IndexPrefix {
|
|
397
|
+
return switch (@typeInfo(Index)) {
|
|
398
|
+
.void => {},
|
|
399
|
+
.int => index,
|
|
400
|
+
.@"enum" => @intFromEnum(index),
|
|
401
|
+
else => unreachable,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/// Try to extract an index from the object, deriving it when necessary.
|
|
406
|
+
/// Null means the value should not be indexed.
|
|
407
|
+
pub fn index_from_object(object: *const Object) ?IndexPrefix {
|
|
408
|
+
if (is_derived) {
|
|
409
|
+
return if (@field(groove_options.derived, field_name)(object)) |value|
|
|
410
|
+
as_prefix(value)
|
|
411
|
+
else
|
|
412
|
+
null;
|
|
413
|
+
} else {
|
|
414
|
+
const value = as_prefix(@field(object, field_name));
|
|
415
|
+
return if (allow_zero or value != 0)
|
|
416
|
+
value
|
|
417
|
+
else
|
|
418
|
+
null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}.HelperType;
|
|
424
|
+
|
|
425
|
+
const ObjectsCacheHelpers = struct {
|
|
426
|
+
const tombstone_bit = 1 << (64 - 1);
|
|
427
|
+
|
|
428
|
+
inline fn key_from_value(value: *const Object) PrimaryKey {
|
|
429
|
+
if (has_id) {
|
|
430
|
+
return value.id;
|
|
431
|
+
} else {
|
|
432
|
+
return value.timestamp & ~@as(u64, tombstone_bit);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
inline fn hash(key: PrimaryKey) u64 {
|
|
437
|
+
return stdx.hash_inline(key);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
inline fn tombstone_from_key(a: PrimaryKey) Object {
|
|
441
|
+
var obj: Object = undefined;
|
|
442
|
+
if (has_id) {
|
|
443
|
+
obj.id = a;
|
|
444
|
+
obj.timestamp = 0;
|
|
445
|
+
} else {
|
|
446
|
+
obj.timestamp = a;
|
|
447
|
+
}
|
|
448
|
+
obj.timestamp |= tombstone_bit;
|
|
449
|
+
return obj;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
inline fn tombstone(a: *const Object) bool {
|
|
453
|
+
return (a.timestamp & tombstone_bit) != 0;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const _ObjectsCache = if (groove_options.objects_cache) CacheMapType(
|
|
458
|
+
PrimaryKey,
|
|
459
|
+
Object,
|
|
460
|
+
ObjectsCacheHelpers.key_from_value,
|
|
461
|
+
ObjectsCacheHelpers.hash,
|
|
462
|
+
ObjectsCacheHelpers.tombstone_from_key,
|
|
463
|
+
ObjectsCacheHelpers.tombstone,
|
|
464
|
+
) else void;
|
|
465
|
+
|
|
466
|
+
const TimestampSet = struct {
|
|
467
|
+
const TimestampSet = @This();
|
|
468
|
+
const Found = union(enum) { found: u128, not_found };
|
|
469
|
+
const Map = std.AutoHashMapUnmanaged(u64, Found);
|
|
470
|
+
|
|
471
|
+
map: Map,
|
|
472
|
+
|
|
473
|
+
fn init(self: *TimestampSet, allocator: mem.Allocator, entries_max: u32) !void {
|
|
474
|
+
self.* = .{
|
|
475
|
+
.map = undefined,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
self.map = .{};
|
|
479
|
+
try self.map.ensureTotalCapacity(allocator, entries_max);
|
|
480
|
+
errdefer self.map.deinit(allocator);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
fn deinit(self: *TimestampSet, allocator: mem.Allocator) void {
|
|
484
|
+
self.map.deinit(allocator);
|
|
485
|
+
self.* = undefined;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
fn reset(self: *TimestampSet) void {
|
|
489
|
+
self.map.clearRetainingCapacity();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/// Marks the timestamp as "found" or "not found".
|
|
493
|
+
/// Can be called only once per timestamp.
|
|
494
|
+
fn set(self: *TimestampSet, timestamp: u64, value: Found) void {
|
|
495
|
+
self.map.putAssumeCapacityNoClobber(timestamp, value);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/// Whether the previously enqueued timestamp was found or not.
|
|
499
|
+
fn get(self: *const TimestampSet, timestamp: u64) Found {
|
|
500
|
+
const result = self.map.get(timestamp);
|
|
501
|
+
assert(result != null);
|
|
502
|
+
|
|
503
|
+
return result.?;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
fn has(self: *const TimestampSet, timestamp: u64) bool {
|
|
507
|
+
return self.map.contains(timestamp);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
return struct {
|
|
512
|
+
const Groove = @This();
|
|
513
|
+
|
|
514
|
+
pub const ObjectTree = _ObjectTree;
|
|
515
|
+
pub const IdTree = _IdTree;
|
|
516
|
+
pub const IndexTrees = _IndexTrees;
|
|
517
|
+
pub const ObjectsCache = _ObjectsCache;
|
|
518
|
+
pub const config = groove_options;
|
|
519
|
+
|
|
520
|
+
/// Helper function for interacting with an Index field type.
|
|
521
|
+
pub const IndexTreeFieldHelperType = _IndexTreeFieldHelperType;
|
|
522
|
+
|
|
523
|
+
const Grid = GridType(Storage);
|
|
524
|
+
const ManifestLog = ManifestLogType(Storage);
|
|
525
|
+
|
|
526
|
+
const LookupBy = enum {
|
|
527
|
+
/// Either `id` or `timestamp` for objects without the id field.
|
|
528
|
+
/// This is the same key used by the object cache map.
|
|
529
|
+
primary_key,
|
|
530
|
+
|
|
531
|
+
/// Lookup by `timestamp` in an object where the object cache map is indexed by `id`.
|
|
532
|
+
/// In this case, the `timestamp` is also indexed to support indirect lookups such as
|
|
533
|
+
/// `exists()` and `get_by_timestamp()`.
|
|
534
|
+
/// Invariant: `has_id` is true.
|
|
535
|
+
timestamp,
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const PrefetchKey = union(enum) {
|
|
539
|
+
id: if (has_id) u128 else void,
|
|
540
|
+
timestamp: u64,
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const PrefetchKeys = std.AutoHashMapUnmanaged(
|
|
544
|
+
PrefetchKey,
|
|
545
|
+
struct {
|
|
546
|
+
level: u8,
|
|
547
|
+
lookup_by: LookupBy,
|
|
548
|
+
},
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const LookupResult = union(enum) {
|
|
552
|
+
found_object: Object,
|
|
553
|
+
found_orphaned_id,
|
|
554
|
+
not_found,
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
pub const ScanBuilder = if (has_scan) ScanBuilderType(Groove, Storage) else void;
|
|
558
|
+
|
|
559
|
+
grid: *Grid,
|
|
560
|
+
objects: ObjectTree,
|
|
561
|
+
ids: IdTree,
|
|
562
|
+
indexes: IndexTrees,
|
|
563
|
+
|
|
564
|
+
/// Object IDs and timestamps enqueued to be prefetched.
|
|
565
|
+
/// Prefetching ensures that point lookups against the latest snapshot are synchronous.
|
|
566
|
+
/// This shields state machine implementations from the challenges of concurrency and I/O,
|
|
567
|
+
/// and enables simple state machine function signatures that commit writes atomically.
|
|
568
|
+
prefetch_keys: PrefetchKeys,
|
|
569
|
+
|
|
570
|
+
/// The snapshot to prefetch from.
|
|
571
|
+
prefetch_snapshot: ?u64 = null,
|
|
572
|
+
|
|
573
|
+
/// This is used to accelerate point lookups and is not used for range queries.
|
|
574
|
+
/// It's also where prefetched data is loaded into, so we don't have a different
|
|
575
|
+
/// prefetch cache to our object cache.
|
|
576
|
+
///
|
|
577
|
+
/// The values cache is only used for the latest snapshot for simplicity.
|
|
578
|
+
/// Earlier snapshots will still be able to utilize the block cache.
|
|
579
|
+
///
|
|
580
|
+
/// The values cache is updated on every `insert()`/`upsert()`/`remove()` and stores
|
|
581
|
+
/// a duplicate of data that's already in table_mutable. This is done because
|
|
582
|
+
/// keeping table_mutable as an array, and simplifying the compaction path
|
|
583
|
+
/// is faster than trying to amortize and save memory.
|
|
584
|
+
///
|
|
585
|
+
/// Invariant: if there is an objects_cache then if something is in the mutable
|
|
586
|
+
/// table, it _must_ exist in our object cache.
|
|
587
|
+
/// Otherwise, the ObjectsCache is of type void.
|
|
588
|
+
objects_cache: ObjectsCache,
|
|
589
|
+
|
|
590
|
+
timestamps: if (has_id) TimestampSet else void,
|
|
591
|
+
|
|
592
|
+
scan_builder: ScanBuilder,
|
|
593
|
+
|
|
594
|
+
pub const IndexTreeOptions = _IndexTreeOptions;
|
|
595
|
+
|
|
596
|
+
pub const Options = struct {
|
|
597
|
+
/// The maximum number of objects that might be prefetched and not modified by a batch.
|
|
598
|
+
prefetch_entries_for_read_max: u32,
|
|
599
|
+
/// The maximum number of objects that might be prefetched and then modified by a batch.
|
|
600
|
+
prefetch_entries_for_update_max: u32,
|
|
601
|
+
cache_entries_max: u32,
|
|
602
|
+
|
|
603
|
+
tree_options_object: ObjectTree.Options,
|
|
604
|
+
tree_options_id: if (has_id) IdTree.Options else void,
|
|
605
|
+
tree_options_index: IndexTreeOptions,
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
pub fn init(
|
|
609
|
+
groove: *Groove,
|
|
610
|
+
allocator: mem.Allocator,
|
|
611
|
+
node_pool: *NodePool,
|
|
612
|
+
grid: *Grid,
|
|
613
|
+
radix_buffer: *ScratchMemory,
|
|
614
|
+
options: Options,
|
|
615
|
+
) !void {
|
|
616
|
+
assert(options.tree_options_object.batch_value_count_limit *
|
|
617
|
+
constants.lsm_compaction_ops <= ObjectTree.Table.value_count_max);
|
|
618
|
+
assert(radix_buffer.state == .free);
|
|
619
|
+
|
|
620
|
+
groove.* = .{
|
|
621
|
+
.grid = grid,
|
|
622
|
+
|
|
623
|
+
.objects = undefined,
|
|
624
|
+
.ids = undefined,
|
|
625
|
+
.indexes = undefined,
|
|
626
|
+
.prefetch_keys = undefined,
|
|
627
|
+
.objects_cache = if (ObjectsCache != void) undefined else {},
|
|
628
|
+
.timestamps = undefined,
|
|
629
|
+
.scan_builder = undefined,
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
groove.objects_cache = if (ObjectsCache != void) try ObjectsCache.init(allocator, .{
|
|
633
|
+
.cache_value_count_max = options.cache_entries_max,
|
|
634
|
+
// In the worst case, each stash must be able to store
|
|
635
|
+
// batch_value_count_limit per beat (to contain either TableMutable or
|
|
636
|
+
// TableImmutable) as well as the maximum number of prefetches a bar may
|
|
637
|
+
// perform, excluding prefetches already accounted
|
|
638
|
+
// for by batch_value_count_limit.
|
|
639
|
+
.stash_value_count_max = constants.lsm_compaction_ops *
|
|
640
|
+
(options.tree_options_object.batch_value_count_limit +
|
|
641
|
+
options.prefetch_entries_for_read_max),
|
|
642
|
+
|
|
643
|
+
// Scopes are limited to a single beat, so the maximum number of entries in
|
|
644
|
+
// a single scope is batch_value_count_limit (total – not per beat).
|
|
645
|
+
.scope_value_count_max = options.tree_options_object.batch_value_count_limit,
|
|
646
|
+
|
|
647
|
+
.name = ObjectTree.tree_name(),
|
|
648
|
+
}) else {
|
|
649
|
+
// If there are no modifications or point lookups on the Groove then
|
|
650
|
+
// no `objects_cache` is needed.
|
|
651
|
+
assert(options.prefetch_entries_for_read_max == 0);
|
|
652
|
+
assert(options.prefetch_entries_for_update_max == 0);
|
|
653
|
+
{}
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
errdefer if (ObjectsCache != void) groove.objects_cache.deinit(allocator);
|
|
657
|
+
|
|
658
|
+
// Initialize the object LSM tree.
|
|
659
|
+
try groove.objects.init(
|
|
660
|
+
allocator,
|
|
661
|
+
node_pool,
|
|
662
|
+
grid,
|
|
663
|
+
radix_buffer,
|
|
664
|
+
.{
|
|
665
|
+
.id = @field(groove_options.ids, "timestamp"),
|
|
666
|
+
.name = ObjectTree.tree_name(),
|
|
667
|
+
},
|
|
668
|
+
options.tree_options_object,
|
|
669
|
+
);
|
|
670
|
+
errdefer groove.objects.deinit(allocator);
|
|
671
|
+
|
|
672
|
+
if (has_id) try groove.ids.init(
|
|
673
|
+
allocator,
|
|
674
|
+
node_pool,
|
|
675
|
+
grid,
|
|
676
|
+
radix_buffer,
|
|
677
|
+
.{
|
|
678
|
+
.id = @field(groove_options.ids, "id"),
|
|
679
|
+
.name = ObjectTree.tree_name() ++ ".id",
|
|
680
|
+
},
|
|
681
|
+
options.tree_options_id,
|
|
682
|
+
);
|
|
683
|
+
errdefer if (has_id) groove.ids.deinit(allocator);
|
|
684
|
+
|
|
685
|
+
var index_trees_initialized: usize = 0;
|
|
686
|
+
// Make sure to deinit initialized index LSM trees on error.
|
|
687
|
+
errdefer inline for (std.meta.fields(IndexTrees), 0..) |field, field_index| {
|
|
688
|
+
if (index_trees_initialized >= field_index + 1) {
|
|
689
|
+
const Tree = field.type;
|
|
690
|
+
const tree: *Tree = &@field(groove.indexes, field.name);
|
|
691
|
+
tree.deinit(allocator);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
// Initialize index LSM trees.
|
|
696
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
697
|
+
const Tree = field.type;
|
|
698
|
+
const tree: *Tree = &@field(groove.indexes, field.name);
|
|
699
|
+
try tree.init(
|
|
700
|
+
allocator,
|
|
701
|
+
node_pool,
|
|
702
|
+
grid,
|
|
703
|
+
radix_buffer,
|
|
704
|
+
.{
|
|
705
|
+
.id = @field(groove_options.ids, field.name),
|
|
706
|
+
.name = ObjectTree.tree_name() ++ "." ++ field.name,
|
|
707
|
+
},
|
|
708
|
+
@field(options.tree_options_index, field.name),
|
|
709
|
+
);
|
|
710
|
+
index_trees_initialized += 1;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
groove.prefetch_keys = .{};
|
|
714
|
+
try groove.prefetch_keys.ensureTotalCapacity(
|
|
715
|
+
allocator,
|
|
716
|
+
options.prefetch_entries_for_read_max + options.prefetch_entries_for_update_max,
|
|
717
|
+
);
|
|
718
|
+
errdefer groove.prefetch_keys.deinit(allocator);
|
|
719
|
+
|
|
720
|
+
if (has_id) try groove.timestamps.init(
|
|
721
|
+
allocator,
|
|
722
|
+
options.prefetch_entries_for_read_max,
|
|
723
|
+
);
|
|
724
|
+
errdefer if (has_id) groove.timestamps.deinit(allocator);
|
|
725
|
+
|
|
726
|
+
if (has_scan) try groove.scan_builder.init(allocator);
|
|
727
|
+
errdefer if (has_scan) groove.scan_builder.deinit(allocator);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
pub fn deinit(groove: *Groove, allocator: mem.Allocator) void {
|
|
731
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
732
|
+
@field(groove.indexes, field.name).deinit(allocator);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
groove.objects.deinit(allocator);
|
|
736
|
+
if (has_id) groove.ids.deinit(allocator);
|
|
737
|
+
|
|
738
|
+
groove.prefetch_keys.deinit(allocator);
|
|
739
|
+
|
|
740
|
+
if (ObjectsCache != void) groove.objects_cache.deinit(allocator);
|
|
741
|
+
if (has_id) groove.timestamps.deinit(allocator);
|
|
742
|
+
if (has_scan) groove.scan_builder.deinit(allocator);
|
|
743
|
+
|
|
744
|
+
groove.* = undefined;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
pub fn reset(groove: *Groove) void {
|
|
748
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
749
|
+
@field(groove.indexes, field.name).reset();
|
|
750
|
+
}
|
|
751
|
+
groove.objects.reset();
|
|
752
|
+
if (has_id) groove.ids.reset();
|
|
753
|
+
|
|
754
|
+
groove.prefetch_keys.clearRetainingCapacity();
|
|
755
|
+
|
|
756
|
+
if (ObjectsCache != void) groove.objects_cache.reset();
|
|
757
|
+
|
|
758
|
+
if (has_id) groove.timestamps.reset();
|
|
759
|
+
if (has_scan) groove.scan_builder.reset();
|
|
760
|
+
|
|
761
|
+
groove.* = .{
|
|
762
|
+
.grid = groove.grid,
|
|
763
|
+
.objects = groove.objects,
|
|
764
|
+
.ids = groove.ids,
|
|
765
|
+
.indexes = groove.indexes,
|
|
766
|
+
.prefetch_keys = groove.prefetch_keys,
|
|
767
|
+
.prefetch_snapshot = null,
|
|
768
|
+
.objects_cache = groove.objects_cache,
|
|
769
|
+
.timestamps = groove.timestamps,
|
|
770
|
+
.scan_builder = groove.scan_builder,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
pub fn get(groove: *const Groove, key: PrimaryKey) LookupResult {
|
|
775
|
+
if (groove.objects_cache.get(key)) |object| {
|
|
776
|
+
if (object.timestamp == 0) {
|
|
777
|
+
assert(has_id);
|
|
778
|
+
assert(groove_options.orphaned_ids);
|
|
779
|
+
return .found_orphaned_id;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return .{ .found_object = object.* };
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return .not_found;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/// Looks up an object by `timestamp`.
|
|
789
|
+
/// Use `get()` for objects that don't have the `id` field.
|
|
790
|
+
/// The timestamp must have been passed to `prefetch_enqueue_by_timestamp`.
|
|
791
|
+
pub fn get_by_timestamp(groove: *const Groove, timestamp: u64) LookupResult {
|
|
792
|
+
// Only applicable to objects with an `id` field.
|
|
793
|
+
// Use `get` if the object is already keyed by timestamp.
|
|
794
|
+
comptime assert(has_id);
|
|
795
|
+
assert(TimestampRange.valid(timestamp));
|
|
796
|
+
|
|
797
|
+
return switch (groove.timestamps.get(timestamp)) {
|
|
798
|
+
.found => |id| groove.get(id),
|
|
799
|
+
.not_found => .not_found,
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/// Returns whether an object with this timestamp exists or not.
|
|
804
|
+
/// The timestamp to be checked must have been passed to `prefetch_exists_enqueue`.
|
|
805
|
+
pub fn exists(groove: *const Groove, timestamp: u64) bool {
|
|
806
|
+
// Only applicable to objects with an `id` field.
|
|
807
|
+
// Use `get` if the object is already keyed by timestamp.
|
|
808
|
+
comptime assert(has_id);
|
|
809
|
+
assert(TimestampRange.valid(timestamp));
|
|
810
|
+
|
|
811
|
+
return groove.timestamps.get(timestamp) == .found;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/// Must be called directly before the state machine begins queuing ids for prefetch.
|
|
815
|
+
pub fn prefetch_setup(groove: *Groove, snapshot_target: u64) void {
|
|
816
|
+
assert(snapshot_target < snapshot_latest);
|
|
817
|
+
|
|
818
|
+
groove.prefetch_snapshot = snapshot_target;
|
|
819
|
+
assert(groove.prefetch_keys.count() == 0);
|
|
820
|
+
|
|
821
|
+
if (has_id) groove.timestamps.reset();
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/// This must be called by the state machine for every key to be prefetched.
|
|
825
|
+
/// We tolerate duplicate IDs enqueued by the state machine.
|
|
826
|
+
/// For example, if all unique operations require the same two dependencies.
|
|
827
|
+
pub fn prefetch_enqueue(groove: *Groove, key: PrimaryKey) void {
|
|
828
|
+
if (groove.objects_cache.has(key)) return;
|
|
829
|
+
|
|
830
|
+
if (has_id) {
|
|
831
|
+
// No need to check again if the key is already present.
|
|
832
|
+
if (groove.prefetch_keys.contains(.{ .id = key })) return;
|
|
833
|
+
if (!groove.ids.key_range_contains(groove.prefetch_snapshot.?, key)) return;
|
|
834
|
+
|
|
835
|
+
groove.prefetch_from_memory_by_id(key);
|
|
836
|
+
} else {
|
|
837
|
+
if (groove.prefetch_keys.contains(.{ .timestamp = key })) return;
|
|
838
|
+
if (!groove.objects.key_range_contains(groove.prefetch_snapshot.?, key)) return;
|
|
839
|
+
|
|
840
|
+
groove.prefetch_from_memory_by_timestamp(key, .primary_key);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/// This must be called by the state machine for every timestamp to be checked by `exists`.
|
|
845
|
+
/// The first call to this function may trigger the sorting of the mutable table, which is
|
|
846
|
+
/// likely a no-op since timestamps are strictly increasing and the table should already
|
|
847
|
+
/// be sorted, except for objects that are frequently updated (e.g., accounts).
|
|
848
|
+
/// We tolerate duplicate timestamps enqueued by the state machine.
|
|
849
|
+
pub fn prefetch_enqueue_by_timestamp(
|
|
850
|
+
groove: *Groove,
|
|
851
|
+
timestamp: u64,
|
|
852
|
+
) void {
|
|
853
|
+
// Only applicable to objects with an `id` field.
|
|
854
|
+
// Use `prefetch_enqueue` if the object is already keyed by timestamp.
|
|
855
|
+
comptime assert(has_id);
|
|
856
|
+
|
|
857
|
+
// Instead of asserting, we allow and ignore invalid timestamps (most likely zero),
|
|
858
|
+
// so the prefetch step does not need to verify the data's validity.
|
|
859
|
+
if (!TimestampRange.valid(timestamp)) return;
|
|
860
|
+
|
|
861
|
+
// No need to check again if the key is already present or enqueued for prefetching.
|
|
862
|
+
if (groove.timestamps.has(timestamp) or
|
|
863
|
+
groove.prefetch_keys.contains(.{ .timestamp = timestamp })) return;
|
|
864
|
+
|
|
865
|
+
// The mutable table needs to be sorted to enable searching by timestamp.
|
|
866
|
+
// The immutable table will be searched by `prefetch_from_memory_by_timestamp`.
|
|
867
|
+
groove.objects.table_mutable.sort();
|
|
868
|
+
if (groove.objects.table_mutable.get(timestamp)) |object| {
|
|
869
|
+
assert(object.timestamp == timestamp);
|
|
870
|
+
groove.timestamps.set(timestamp, .{ .found = object.id });
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
groove.prefetch_from_memory_by_timestamp(timestamp, .timestamp);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/// This function attempts to prefetch a value for the given id from the IdTree's
|
|
878
|
+
/// table blocks in the grid cache.
|
|
879
|
+
/// If found in the IdTree, we attempt to prefetch a value for the timestamp.
|
|
880
|
+
fn prefetch_from_memory_by_id(groove: *Groove, id: u128) void {
|
|
881
|
+
comptime assert(has_id);
|
|
882
|
+
switch (groove.ids.lookup_from_levels_cache(
|
|
883
|
+
groove.prefetch_snapshot.?,
|
|
884
|
+
id,
|
|
885
|
+
)) {
|
|
886
|
+
.negative => {},
|
|
887
|
+
.positive => |id_tree_value| {
|
|
888
|
+
if (IdTreeValue.tombstone(id_tree_value)) return;
|
|
889
|
+
|
|
890
|
+
if (id_tree_value.timestamp == 0) {
|
|
891
|
+
assert(groove_options.orphaned_ids);
|
|
892
|
+
|
|
893
|
+
// Zeroed timestamp indicates the object is not present,
|
|
894
|
+
// and this id cannot be used anymore.
|
|
895
|
+
groove.objects_cache.upsert(
|
|
896
|
+
&std.mem.zeroInit(Object, .{
|
|
897
|
+
.id = id_tree_value.id,
|
|
898
|
+
}),
|
|
899
|
+
);
|
|
900
|
+
} else {
|
|
901
|
+
if (groove.prefetch_keys.get(.{
|
|
902
|
+
.timestamp = id_tree_value.timestamp,
|
|
903
|
+
})) |prefetch_entry| {
|
|
904
|
+
// We don't want duplicate keys when prefetching the same object
|
|
905
|
+
// multiple times, but the `contains(.id)` check performed during
|
|
906
|
+
// `prefetch_enqueue()` may return false if:
|
|
907
|
+
|
|
908
|
+
// 1. The `IdTree` is already in memory (but not the `ObjectTree`),
|
|
909
|
+
// so we inserted the `.timestamp` rather than the `.id`.
|
|
910
|
+
maybe(prefetch_entry.lookup_by == .primary_key);
|
|
911
|
+
|
|
912
|
+
// 2. The same object was enqueued for prefetch by both `.id`
|
|
913
|
+
// and `.timestamp`.
|
|
914
|
+
maybe(prefetch_entry.lookup_by == .timestamp);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
groove.prefetch_from_memory_by_timestamp(
|
|
918
|
+
id_tree_value.timestamp,
|
|
919
|
+
.primary_key,
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
},
|
|
923
|
+
.possible => |level| {
|
|
924
|
+
groove.prefetch_keys.putAssumeCapacityNoClobber(
|
|
925
|
+
.{ .id = id },
|
|
926
|
+
.{
|
|
927
|
+
.level = level,
|
|
928
|
+
.lookup_by = .primary_key,
|
|
929
|
+
},
|
|
930
|
+
);
|
|
931
|
+
},
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/// This function attempts to prefetch a value for the timestamp from the ObjectTree's
|
|
936
|
+
/// table blocks in the grid cache.
|
|
937
|
+
fn prefetch_from_memory_by_timestamp(
|
|
938
|
+
groove: *Groove,
|
|
939
|
+
timestamp: u64,
|
|
940
|
+
lookup_by: LookupBy,
|
|
941
|
+
) void {
|
|
942
|
+
assert(TimestampRange.valid(timestamp));
|
|
943
|
+
assert(lookup_by == .primary_key or has_id);
|
|
944
|
+
|
|
945
|
+
switch (groove.objects.lookup_from_levels_cache(
|
|
946
|
+
groove.prefetch_snapshot.?,
|
|
947
|
+
timestamp,
|
|
948
|
+
)) {
|
|
949
|
+
.negative => switch (lookup_by) {
|
|
950
|
+
.primary_key => {},
|
|
951
|
+
.timestamp => if (has_id)
|
|
952
|
+
groove.timestamps.set(timestamp, .not_found)
|
|
953
|
+
else
|
|
954
|
+
unreachable,
|
|
955
|
+
},
|
|
956
|
+
.positive => |object| {
|
|
957
|
+
assert(!ObjectTreeHelper.tombstone(object));
|
|
958
|
+
switch (lookup_by) {
|
|
959
|
+
.primary_key => groove.objects_cache.upsert(object),
|
|
960
|
+
.timestamp => if (has_id) {
|
|
961
|
+
groove.objects_cache.upsert(object);
|
|
962
|
+
groove.timestamps.set(object.timestamp, .{ .found = object.id });
|
|
963
|
+
} else unreachable,
|
|
964
|
+
}
|
|
965
|
+
},
|
|
966
|
+
.possible => |level| {
|
|
967
|
+
groove.prefetch_keys.putAssumeCapacityNoClobber(
|
|
968
|
+
.{ .timestamp = timestamp },
|
|
969
|
+
.{
|
|
970
|
+
.level = level,
|
|
971
|
+
.lookup_by = lookup_by,
|
|
972
|
+
},
|
|
973
|
+
);
|
|
974
|
+
},
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
/// Ensure the objects corresponding to all ids enqueued with prefetch_enqueue() are
|
|
978
|
+
/// available in `objects_cache`.
|
|
979
|
+
pub fn prefetch(
|
|
980
|
+
groove: *Groove,
|
|
981
|
+
callback: *const fn (*PrefetchContext) void,
|
|
982
|
+
context: *PrefetchContext,
|
|
983
|
+
) void {
|
|
984
|
+
context.* = .{
|
|
985
|
+
.groove = groove,
|
|
986
|
+
.callback = callback,
|
|
987
|
+
.snapshot = groove.prefetch_snapshot.?,
|
|
988
|
+
.key_iterator = groove.prefetch_keys.iterator(),
|
|
989
|
+
};
|
|
990
|
+
groove.prefetch_snapshot = null;
|
|
991
|
+
context.start_workers();
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
pub const PrefetchContext = struct {
|
|
995
|
+
groove: *Groove,
|
|
996
|
+
callback: *const fn (*PrefetchContext) void,
|
|
997
|
+
snapshot: u64,
|
|
998
|
+
|
|
999
|
+
key_iterator: PrefetchKeys.Iterator,
|
|
1000
|
+
/// The goal is to fully utilize the disk I/O to ensure the prefetch completes as
|
|
1001
|
+
/// quickly as possible, so we run multiple lookups in parallel based on the max
|
|
1002
|
+
/// I/O depth of the Grid.
|
|
1003
|
+
workers: [Grid.read_iops_max]PrefetchWorker = undefined,
|
|
1004
|
+
/// The number of workers that are currently running in parallel.
|
|
1005
|
+
workers_pending: u32 = 0,
|
|
1006
|
+
|
|
1007
|
+
next_tick: Grid.NextTick = undefined,
|
|
1008
|
+
|
|
1009
|
+
fn start_workers(context: *PrefetchContext) void {
|
|
1010
|
+
assert(context.workers_pending == 0);
|
|
1011
|
+
|
|
1012
|
+
context.groove.grid.trace.start(.{
|
|
1013
|
+
.lookup = .{ .tree = @enumFromInt(context.groove.objects.config.id) },
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
// Track an extra "worker" that will finish after the loop.
|
|
1017
|
+
// This allows the callback to be called asynchronously on `next_tick`
|
|
1018
|
+
// if all workers are finished synchronously.
|
|
1019
|
+
context.workers_pending += 1;
|
|
1020
|
+
|
|
1021
|
+
for (&context.workers, 0..) |*worker, index| {
|
|
1022
|
+
assert(context.workers_pending == index + 1);
|
|
1023
|
+
|
|
1024
|
+
worker.* = .{
|
|
1025
|
+
.index = @intCast(index),
|
|
1026
|
+
.context = context,
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
context.groove.grid.trace.start(
|
|
1030
|
+
.{ .lookup_worker = .{
|
|
1031
|
+
.index = worker.index,
|
|
1032
|
+
.tree = @enumFromInt(context.groove.objects.config.id),
|
|
1033
|
+
} },
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
context.workers_pending += 1;
|
|
1037
|
+
worker.lookup_start_next();
|
|
1038
|
+
|
|
1039
|
+
// If the worker finished synchronously (e.g `workers_pending` decreased),
|
|
1040
|
+
// we don't need to start new ones.
|
|
1041
|
+
if (context.workers_pending == index + 1) break;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
assert(context.workers_pending > 0);
|
|
1045
|
+
context.workers_pending -= 1;
|
|
1046
|
+
|
|
1047
|
+
if (context.workers_pending == 0) {
|
|
1048
|
+
// All workers finished synchronously,
|
|
1049
|
+
// calling the callback on `next_tick`.
|
|
1050
|
+
context.groove.grid.on_next_tick(worker_next_tick, &context.next_tick);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
fn worker_next_tick(completion: *Grid.NextTick) void {
|
|
1055
|
+
const context: *PrefetchContext = @alignCast(
|
|
1056
|
+
@fieldParentPtr("next_tick", completion),
|
|
1057
|
+
);
|
|
1058
|
+
assert(context.workers_pending == 0);
|
|
1059
|
+
context.finish();
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
fn worker_finished(context: *PrefetchContext) void {
|
|
1063
|
+
context.workers_pending -= 1;
|
|
1064
|
+
if (context.workers_pending == 0) context.finish();
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
fn finish(context: *PrefetchContext) void {
|
|
1068
|
+
assert(context.workers_pending == 0);
|
|
1069
|
+
|
|
1070
|
+
assert(context.key_iterator.next() == null);
|
|
1071
|
+
context.groove.prefetch_keys.clearRetainingCapacity();
|
|
1072
|
+
assert(context.groove.prefetch_keys.count() == 0);
|
|
1073
|
+
|
|
1074
|
+
context.groove.grid.trace.stop(.{
|
|
1075
|
+
.lookup = .{ .tree = @enumFromInt(context.groove.objects.config.id) },
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
context.callback(context);
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
pub const PrefetchWorker = struct {
|
|
1083
|
+
// Since lookup contexts are used one at a time, it's safe to access
|
|
1084
|
+
// the union's fields and reuse the same memory for all context instances.
|
|
1085
|
+
// Can't use extern/packed union as the LookupContexts aren't ABI compliant.
|
|
1086
|
+
const LookupContext = union(enum) {
|
|
1087
|
+
id: if (has_id) IdTree.LookupContext else void,
|
|
1088
|
+
object: ObjectTree.LookupContext,
|
|
1089
|
+
|
|
1090
|
+
pub const Field = std.meta.FieldEnum(LookupContext);
|
|
1091
|
+
pub fn FieldType(comptime field: Field) type {
|
|
1092
|
+
return @FieldType(LookupContext, @tagName(field));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
pub inline fn parent(
|
|
1096
|
+
comptime field: Field,
|
|
1097
|
+
completion: *FieldType(field),
|
|
1098
|
+
) *PrefetchWorker {
|
|
1099
|
+
const lookup: *LookupContext = @fieldParentPtr(@tagName(field), completion);
|
|
1100
|
+
return @fieldParentPtr("lookup", lookup);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
pub inline fn get(self: *LookupContext, comptime field: Field) *FieldType(field) {
|
|
1104
|
+
self.* = @unionInit(LookupContext, @tagName(field), undefined);
|
|
1105
|
+
return &@field(self, @tagName(field));
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
index: u8,
|
|
1110
|
+
context: *PrefetchContext,
|
|
1111
|
+
lookup: LookupContext = undefined,
|
|
1112
|
+
current: ?struct {
|
|
1113
|
+
key: PrefetchKey,
|
|
1114
|
+
lookup_by: LookupBy,
|
|
1115
|
+
} = null,
|
|
1116
|
+
|
|
1117
|
+
fn lookup_start_next(worker: *PrefetchWorker) void {
|
|
1118
|
+
assert(worker.current == null);
|
|
1119
|
+
const prefetch_entry = worker.context.key_iterator.next() orelse {
|
|
1120
|
+
worker.context.groove.grid.trace.stop(
|
|
1121
|
+
.{ .lookup_worker = .{
|
|
1122
|
+
.index = worker.index,
|
|
1123
|
+
.tree = @enumFromInt(worker.context.groove.objects.config.id),
|
|
1124
|
+
} },
|
|
1125
|
+
);
|
|
1126
|
+
|
|
1127
|
+
worker.context.worker_finished();
|
|
1128
|
+
return;
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
worker.current = .{
|
|
1132
|
+
.key = prefetch_entry.key_ptr.*,
|
|
1133
|
+
.lookup_by = prefetch_entry.value_ptr.lookup_by,
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// prefetch_enqueue() ensures that the tree's cache is checked before queueing the
|
|
1137
|
+
// object for prefetching. If not in the LSM tree's cache, the object must be read
|
|
1138
|
+
// from disk and added to the auxiliary prefetch_objects hash map.
|
|
1139
|
+
switch (prefetch_entry.key_ptr.*) {
|
|
1140
|
+
.id => |id| if (has_id) {
|
|
1141
|
+
worker.context.groove.ids.lookup_from_levels_storage(.{
|
|
1142
|
+
.callback = lookup_id_callback,
|
|
1143
|
+
.context = worker.lookup.get(.id),
|
|
1144
|
+
.snapshot = worker.context.snapshot,
|
|
1145
|
+
.key = id,
|
|
1146
|
+
.level_min = prefetch_entry.value_ptr.level,
|
|
1147
|
+
});
|
|
1148
|
+
} else unreachable,
|
|
1149
|
+
.timestamp => |timestamp| {
|
|
1150
|
+
worker.context.groove.objects.lookup_from_levels_storage(.{
|
|
1151
|
+
.callback = lookup_object_callback,
|
|
1152
|
+
.context = worker.lookup.get(.object),
|
|
1153
|
+
.snapshot = worker.context.snapshot,
|
|
1154
|
+
.key = timestamp,
|
|
1155
|
+
.level_min = prefetch_entry.value_ptr.level,
|
|
1156
|
+
});
|
|
1157
|
+
},
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
fn lookup_id_callback(
|
|
1162
|
+
completion: *IdTree.LookupContext,
|
|
1163
|
+
result: ?*const IdTreeValue,
|
|
1164
|
+
) void {
|
|
1165
|
+
const worker = LookupContext.parent(.id, completion);
|
|
1166
|
+
worker.lookup = undefined;
|
|
1167
|
+
assert(worker.current != null);
|
|
1168
|
+
assert(worker.current.?.key == .id);
|
|
1169
|
+
assert(worker.current.?.lookup_by == .primary_key);
|
|
1170
|
+
|
|
1171
|
+
if (result) |id_tree_value| {
|
|
1172
|
+
if (groove_options.orphaned_ids and
|
|
1173
|
+
id_tree_value.timestamp == 0)
|
|
1174
|
+
{
|
|
1175
|
+
comptime assert(has_id);
|
|
1176
|
+
|
|
1177
|
+
// Zeroed timestamp indicates the object is not present,
|
|
1178
|
+
// and this id cannot be used anymore.
|
|
1179
|
+
worker.context.groove.objects_cache.upsert(
|
|
1180
|
+
&std.mem.zeroInit(Object, .{
|
|
1181
|
+
.id = id_tree_value.id,
|
|
1182
|
+
}),
|
|
1183
|
+
);
|
|
1184
|
+
} else if (!id_tree_value.tombstone()) {
|
|
1185
|
+
worker.lookup_by_timestamp(id_tree_value.timestamp);
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
worker.current = null;
|
|
1191
|
+
worker.lookup_start_next();
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
fn lookup_by_timestamp(worker: *PrefetchWorker, timestamp: u64) void {
|
|
1195
|
+
assert(TimestampRange.valid(timestamp));
|
|
1196
|
+
assert(worker.current != null);
|
|
1197
|
+
|
|
1198
|
+
switch (worker.context.groove.objects.lookup_from_levels_cache(
|
|
1199
|
+
worker.context.snapshot,
|
|
1200
|
+
timestamp,
|
|
1201
|
+
)) {
|
|
1202
|
+
.negative => {
|
|
1203
|
+
lookup_object_callback(worker.lookup.get(.object), null);
|
|
1204
|
+
},
|
|
1205
|
+
.positive => |value| {
|
|
1206
|
+
lookup_object_callback(worker.lookup.get(.object), value);
|
|
1207
|
+
},
|
|
1208
|
+
.possible => |level_min| {
|
|
1209
|
+
worker.context.groove.objects.lookup_from_levels_storage(.{
|
|
1210
|
+
.callback = lookup_object_callback,
|
|
1211
|
+
.context = worker.lookup.get(.object),
|
|
1212
|
+
.snapshot = worker.context.snapshot,
|
|
1213
|
+
.key = timestamp,
|
|
1214
|
+
.level_min = level_min,
|
|
1215
|
+
});
|
|
1216
|
+
},
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
fn lookup_object_callback(
|
|
1221
|
+
completion: *ObjectTree.LookupContext,
|
|
1222
|
+
result: ?*const Object,
|
|
1223
|
+
) void {
|
|
1224
|
+
const worker = LookupContext.parent(.object, completion);
|
|
1225
|
+
worker.lookup = undefined;
|
|
1226
|
+
|
|
1227
|
+
assert(worker.current != null);
|
|
1228
|
+
const entry = worker.current.?;
|
|
1229
|
+
worker.current = null;
|
|
1230
|
+
|
|
1231
|
+
if (result) |object| {
|
|
1232
|
+
assert(!ObjectTreeHelper.tombstone(object));
|
|
1233
|
+
switch (entry.key) {
|
|
1234
|
+
.id => |key| if (has_id) {
|
|
1235
|
+
assert(object.id == key);
|
|
1236
|
+
assert(entry.lookup_by == .primary_key);
|
|
1237
|
+
} else unreachable,
|
|
1238
|
+
.timestamp => |timestamp| {
|
|
1239
|
+
assert(object.timestamp == timestamp);
|
|
1240
|
+
assert(entry.lookup_by == .primary_key or
|
|
1241
|
+
entry.lookup_by == .timestamp);
|
|
1242
|
+
},
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
switch (entry.lookup_by) {
|
|
1246
|
+
.primary_key => worker.context.groove.objects_cache.upsert(object),
|
|
1247
|
+
.timestamp => if (has_id) {
|
|
1248
|
+
worker.context.groove.objects_cache.upsert(object);
|
|
1249
|
+
worker.context.groove.timestamps.set(
|
|
1250
|
+
object.timestamp,
|
|
1251
|
+
.{ .found = object.id },
|
|
1252
|
+
);
|
|
1253
|
+
} else unreachable,
|
|
1254
|
+
}
|
|
1255
|
+
} else switch (entry.lookup_by) {
|
|
1256
|
+
// If the object wasn't found, it should've been prefetched by timestamp,
|
|
1257
|
+
// or handled by `lookup_id_callback`.
|
|
1258
|
+
.primary_key => assert(!has_id),
|
|
1259
|
+
.timestamp => if (has_id) worker.context.groove.timestamps.set(
|
|
1260
|
+
entry.key.timestamp,
|
|
1261
|
+
.not_found,
|
|
1262
|
+
) else unreachable,
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
worker.lookup_start_next();
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
/// Insert the value into the objects tree and associated index trees. It's up to the
|
|
1270
|
+
/// caller to ensure it doesn't already exist.
|
|
1271
|
+
pub fn insert(groove: *Groove, object: *const Object) void {
|
|
1272
|
+
assert(TimestampRange.valid(object.timestamp));
|
|
1273
|
+
|
|
1274
|
+
if (ObjectsCache != void) {
|
|
1275
|
+
assert(!groove.objects_cache.has(@field(object, primary_field)));
|
|
1276
|
+
groove.objects_cache.upsert(object);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (has_id) {
|
|
1280
|
+
groove.ids.put(&IdTreeValue{ .id = object.id, .timestamp = object.timestamp });
|
|
1281
|
+
groove.ids.key_range_update(object.id);
|
|
1282
|
+
}
|
|
1283
|
+
groove.objects.put(object);
|
|
1284
|
+
groove.objects.key_range_update(object.timestamp);
|
|
1285
|
+
|
|
1286
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1287
|
+
const Helper = IndexTreeFieldHelperType(field.name);
|
|
1288
|
+
if (Helper.index_from_object(object)) |value| {
|
|
1289
|
+
@field(groove.indexes, field.name).put(&.{
|
|
1290
|
+
.timestamp = object.timestamp,
|
|
1291
|
+
.field = value,
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/// Update the value. Requires the old object to be provided.
|
|
1298
|
+
/// Update the object and index trees by diff'ing the old and new values.
|
|
1299
|
+
pub fn update(
|
|
1300
|
+
groove: *Groove,
|
|
1301
|
+
values: struct { old: *const Object, new: *const Object },
|
|
1302
|
+
) void {
|
|
1303
|
+
const old = values.old;
|
|
1304
|
+
const new = values.new;
|
|
1305
|
+
|
|
1306
|
+
if (ObjectsCache != void) {
|
|
1307
|
+
const old_from_cache = groove.objects_cache.get(@field(old, primary_field)).?;
|
|
1308
|
+
assert(stdx.equal_bytes(Object, old_from_cache, old));
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// Sanity check to ensure the caller didn't accidentally pass in an alias.
|
|
1312
|
+
assert(new != old);
|
|
1313
|
+
|
|
1314
|
+
if (has_id) assert(old.id == new.id);
|
|
1315
|
+
assert(old.timestamp == new.timestamp);
|
|
1316
|
+
assert(TimestampRange.valid(new.timestamp));
|
|
1317
|
+
|
|
1318
|
+
// The ID can't change, so no need to update the ID tree. Update the object tree entry
|
|
1319
|
+
// if any of the fields (even ignored) are different. We assume the caller will pass in
|
|
1320
|
+
// an object that has changes.
|
|
1321
|
+
// Unlike the index trees, the new and old values in the object tree share the same
|
|
1322
|
+
// key. Therefore put() is sufficient to overwrite the old value.
|
|
1323
|
+
{
|
|
1324
|
+
const tombstone = ObjectTreeHelper.tombstone;
|
|
1325
|
+
const key_from_value = ObjectTreeHelper.key_from_value;
|
|
1326
|
+
|
|
1327
|
+
assert(!stdx.equal_bytes(Object, old, new));
|
|
1328
|
+
assert(key_from_value(old) == key_from_value(new));
|
|
1329
|
+
assert(!tombstone(old) and !tombstone(new));
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1333
|
+
const Helper = IndexTreeFieldHelperType(field.name);
|
|
1334
|
+
const old_index = Helper.index_from_object(old);
|
|
1335
|
+
const new_index = Helper.index_from_object(new);
|
|
1336
|
+
|
|
1337
|
+
// Only update the indexes that change.
|
|
1338
|
+
if (old_index != new_index) {
|
|
1339
|
+
if (old_index) |value| {
|
|
1340
|
+
@field(groove.indexes, field.name).remove(&.{
|
|
1341
|
+
.timestamp = old.timestamp,
|
|
1342
|
+
.field = value,
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
if (new_index) |value| {
|
|
1346
|
+
@field(groove.indexes, field.name).put(&.{
|
|
1347
|
+
.timestamp = new.timestamp,
|
|
1348
|
+
.field = value,
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Putting the objects_cache upsert after the index tree updates is critical:
|
|
1355
|
+
// We diff the old and new objects, but the old object will be a pointer into the
|
|
1356
|
+
// objects_cache. If we upsert first, there's a high chance old.* == new.* (always,
|
|
1357
|
+
// unless old comes from the stash) and no secondary indexes will be updated!
|
|
1358
|
+
|
|
1359
|
+
if (ObjectsCache != void) {
|
|
1360
|
+
groove.objects_cache.upsert(new);
|
|
1361
|
+
}
|
|
1362
|
+
groove.objects.put(new);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/// Asserts that the object with the given PrimaryKey exists.
|
|
1366
|
+
pub fn remove(groove: *Groove, key: PrimaryKey) void {
|
|
1367
|
+
// TODO: Nothing currently calls or tests this method. The forest fuzzer should be
|
|
1368
|
+
// extended to cover it.
|
|
1369
|
+
assert(false);
|
|
1370
|
+
|
|
1371
|
+
const object = groove.objects_cache.get(key).?;
|
|
1372
|
+
assert(TimestampRange.valid(object.timestamp));
|
|
1373
|
+
|
|
1374
|
+
// TODO: should update the timestamp and id range, see `key_range_update`.
|
|
1375
|
+
groove.objects.remove(object);
|
|
1376
|
+
if (has_id) {
|
|
1377
|
+
groove.ids.remove(&IdTreeValue{ .id = object.id, .timestamp = object.timestamp });
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
groove.objects_cache.remove(key);
|
|
1381
|
+
|
|
1382
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1383
|
+
const Helper = IndexTreeFieldHelperType(field.name);
|
|
1384
|
+
if (Helper.index_from_object(object)) |value| {
|
|
1385
|
+
@field(groove.indexes, field.name).remove(&.{
|
|
1386
|
+
.timestamp = object.timestamp,
|
|
1387
|
+
.field = value,
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/// Insert an id associated with no object.
|
|
1394
|
+
/// It's up to the caller to ensure it doesn't already exist.
|
|
1395
|
+
pub fn insert_orphaned_id(groove: *Groove, id: u128) void {
|
|
1396
|
+
comptime assert(groove_options.orphaned_ids);
|
|
1397
|
+
comptime assert(has_id);
|
|
1398
|
+
|
|
1399
|
+
assert(id != 0);
|
|
1400
|
+
assert(id != std.math.maxInt(u128));
|
|
1401
|
+
|
|
1402
|
+
// We should not insert an orphaned `id` inside a scope.
|
|
1403
|
+
assert(!groove.objects_cache.scope_is_active);
|
|
1404
|
+
assert(groove.ids.active_scope == null);
|
|
1405
|
+
assert(!groove.objects_cache.has(id));
|
|
1406
|
+
|
|
1407
|
+
groove.objects_cache.upsert(&std.mem.zeroInit(Object, .{ .id = id }));
|
|
1408
|
+
groove.ids.put(&.{ .id = id, .timestamp = 0 });
|
|
1409
|
+
groove.ids.key_range_update(id);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
pub fn remove_orphaned_id(groove: *Groove, id: u128) void {
|
|
1413
|
+
comptime assert(groove_options.orphaned_ids);
|
|
1414
|
+
comptime assert(has_id);
|
|
1415
|
+
|
|
1416
|
+
// TODO: Nothing currently calls or tests this method. The forest fuzzer should be
|
|
1417
|
+
// extended to cover it.
|
|
1418
|
+
assert(false);
|
|
1419
|
+
|
|
1420
|
+
_ = groove;
|
|
1421
|
+
_ = id;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
pub fn scope_open(groove: *Groove) void {
|
|
1425
|
+
if (ObjectsCache != void) groove.objects_cache.scope_open();
|
|
1426
|
+
|
|
1427
|
+
if (has_id) {
|
|
1428
|
+
groove.ids.scope_open();
|
|
1429
|
+
}
|
|
1430
|
+
groove.objects.scope_open();
|
|
1431
|
+
|
|
1432
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1433
|
+
@field(groove.indexes, field.name).scope_open();
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
pub fn scope_close(groove: *Groove, mode: ScopeCloseMode) void {
|
|
1438
|
+
if (ObjectsCache != void) groove.objects_cache.scope_close(mode);
|
|
1439
|
+
|
|
1440
|
+
if (has_id) {
|
|
1441
|
+
groove.ids.scope_close(mode);
|
|
1442
|
+
}
|
|
1443
|
+
groove.objects.scope_close(mode);
|
|
1444
|
+
|
|
1445
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1446
|
+
@field(groove.indexes, field.name).scope_close(mode);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
pub fn compact(groove: *Groove, op: u64) void {
|
|
1451
|
+
if (has_id) groove.ids.compact();
|
|
1452
|
+
groove.objects.compact();
|
|
1453
|
+
|
|
1454
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1455
|
+
@field(groove.indexes, field.name).compact();
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// Compact the objects_cache on the last beat of the bar, just like the trees do to
|
|
1459
|
+
// their mutable tables.
|
|
1460
|
+
if (ObjectsCache != void) {
|
|
1461
|
+
const compaction_beat = op % constants.lsm_compaction_ops;
|
|
1462
|
+
if (compaction_beat == constants.lsm_compaction_ops - 1) {
|
|
1463
|
+
groove.objects_cache.compact();
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
pub fn open_commence(groove: *Groove, manifest_log: *ManifestLog) void {
|
|
1469
|
+
if (has_id) groove.ids.open_commence(manifest_log);
|
|
1470
|
+
groove.objects.open_commence(manifest_log);
|
|
1471
|
+
|
|
1472
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1473
|
+
@field(groove.indexes, field.name).open_commence(manifest_log);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
pub fn open_complete(groove: *Groove) void {
|
|
1478
|
+
if (has_id) groove.ids.open_complete();
|
|
1479
|
+
groove.objects.open_complete();
|
|
1480
|
+
|
|
1481
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1482
|
+
@field(groove.indexes, field.name).open_complete();
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
pub fn assert_between_bars(groove: *const Groove) void {
|
|
1487
|
+
if (has_id) groove.ids.assert_between_bars();
|
|
1488
|
+
groove.objects.assert_between_bars();
|
|
1489
|
+
|
|
1490
|
+
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
1491
|
+
@field(groove.indexes, field.name).assert_between_bars();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
}
|