Files
rocksdb/utilities/trie_index/trie_index_factory.h
T
zaidoon 1cec28d82d Finish C API code generation (continues #14572) (#14868)
Summary:
This continues and finishes **https://github.com/facebook/rocksdb/issues/14572** ("Add semi-automated code generation for RocksDB C API bindings") by xingbowang. The original author is unavailable to finish it, so I've taken it over. **All 13 of the original commits are preserved** (this branch was created from the PR head and builds on top of it — `git log` shows the original `Xingbo Wang` authorship intact); my follow-up work is in the commits prefixed `C API codegen:`.

The underlying design is unchanged and is the original author's: hand-written source templates (`tools/c_api_gen/c_base.h` / `c_base.cc`) plus two generators (auto-discovery from the C++ headers + a spec-driven generator) are inlined into a single, self-contained, `generated` `include/rocksdb/c.h` and `db/c.cc`. This grows the public C API by **668 functions** while keeping `c.h` a single includable header with no `-I` requirement (so `bindgen` and other FFI tools keep working unchanged).

This branch reconciles the PR with ~4 months of `main` and addresses the outstanding review feedback (clang-tidy bot, the automated code review, the `c.h` self-containedness discussion, and pdillinger's points about `include/rocksdb` hygiene and `generated` marking).

## What changed on top of the original PR

### Reconciled with current `main`
- Merged current `main` (conflicts were confined to the generated/test files) and regenerated. Reconciled the 14 C API functions `main` added since the merge-base (e.g. `rocksdb_set_db_options`, the backup-engine rate limiters, `memtable_batch_lookup_optimization`, `optimize_multiget_for_io`, …) and restored 5 enum constants that upstream had added by hand (`rocksdb_txndb_write_policy_*`, `..._index_block_search_type_auto`, `rocksdb_blob_cache_read_byte`).

### Maintainer feedback (pdillinger)
- **No non-user-includable files in `include/rocksdb`.** Moved the hand-written templates out of `include/rocksdb/` and `db/` to `tools/c_api_gen/c_base.{h,cc}`. They were `#include`-ing generated fragments, which broke `make check-headers` and was shipped by `make install`. `include/rocksdb/` now contains only the user-facing, self-contained, `generated` `c.h`.
- `c.h` / `c.cc` carry the `// generated` marker.

### Backward compatibility (zero ABI break)
- The generator derived each wrapper's C type purely from the C++ field, which had silently changed **5 already-shipped signatures** (e.g. `rocksdb_writeoptions_disable_WAL` `int` → `unsigned char`). Added an ABI type-pinning layer (`tools/c_api_gen/abi_type_overrides.json`) so already-shipped functions keep their historical C signature (the body still casts to the real field type). A repo-wide diff against the merge-base now reports **0 ABI drift**.
- New `check_api_compatibility.py` gate (wired into CI + `make`) fails on any removed/changed public function **or** removed enum/typedef symbol, vs a reference revision. Intentional changes go in an allowlist with a reason.

### Correctness (from the automated review)
- Restored 5 option setters that were declared in `c.h` but **defined nowhere** (link failure for downstream bindings such as `rust-rocksdb`). Added `check_api_completeness.py` (dependency-free; runs in CI + `make`) asserting every declared function has exactly one definition — this is the gate that would have caught it.
- `CopyStringVector` now null-checks `malloc`; the WAL filter `std::move`s the `WriteBatch`; the backup exclude-files callback captures by value instead of the wrapper pointer.

### Build / CI robustness
- Removed the dead `C_API_CODEGEN_STAMP` Makefile prerequisite (it was a silent no-op).
- The `make check` staleness check is now opt-out-able (behind `SKIP_FORMAT_BUCK_CHECKS`) and skips gracefully when `clang++` is unavailable, so `make check` works without the codegen toolchain. CI remains the authoritative gate.
- Pinned `clang-format` consistently through `regen_all.py` / `verify_generated_up_to_date.py` (CI uses clang-format-21) so regeneration is byte-reproducible across environments.
- Cleared all 20 `clang-tidy` warnings the bot reported on `db/c.cc` changed lines (fixed in the `c_base.cc` template, not the generated output).
- Updated the internal Buck `c_test_bin` wrapper to expose generated `c_api_gen/*.inc` fragments as headers, so sandboxed Buck builds can compile `db/c_test.c` after the generated round-trip tests are included.

### Test coverage
- Added `gen_roundtrip_tests.py`, which derives **462 set→get→assert round-trip checks across 25 option objects** from the same generated fragments and wires them into `db/c_test.c`. Coverage now tracks the generated surface automatically.

### Docs
- Added the `unreleased_history/public_api_changes` note and fixed `claude_md/add_public_api.md`, which still told contributors to hand-edit the now-`generated` `c.h`/`c.cc`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14868

Test Plan:
- `make c_test && ./c_test` — **passes**, including the 462 generated round-trip assertions (a successful link also confirms the API is complete).
- `python3 tools/c_api_gen/check_api_completeness.py` — all 1737 declared functions defined exactly once.
- `python3 tools/c_api_gen/check_api_compatibility.py --ref <release>` — 1070 reference functions + 229 enum/typedef symbols preserved, 0 removed/changed.
- `python3 tools/c_api_gen/verify_generated_up_to_date.py` — generated output is stable.
- `include/rocksdb/c.h` confirmed self-contained (only `<stdbool.h>`, `<stddef.h>`, `<stdint.h>`).

cc xingbowang

- `buck2 build --flagfile fbcode//mode/dev fbcode//internal_repo_rocksdb/repo:c_test_bin` — passes.
- `buck2 build --flagfile fbcode//mode/dev --config fbcode.arch=aarch64 fbcode//internal_repo_rocksdb/repo:c_test_bin` — passes.

Reviewed By: pdillinger

Differential Revision: D109149150

Pulled By: xingbowang

fbshipit-source-id: 3417375345f360a4c78bdfe27e9850b89d0a226a
2026-06-24 10:45:42 -07:00

321 lines
13 KiB
C++

// Copyright (c) Meta Platforms, Inc. and affiliates.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
//
// *****************************************************************
// EXPERIMENTAL - subject to change while under development
// *****************************************************************
//
// Trie-based User Defined Index (UDI) for RocksDB's block-based tables.
//
// This provides a TrieIndexFactory that implements the UserDefinedIndexFactory
// interface, building a Fast Succinct Trie (FST) index from the separator keys
// generated during SST file construction. Based on the SuRF paper results, the
// trie is expected to achieve significant space reduction compared to the
// default binary search index while providing comparable Seek() performance.
//
// Usage:
// auto trie_factory = std::make_shared<TrieIndexFactory>();
// BlockBasedTableOptions table_options;
// table_options.user_defined_index_factory = trie_factory;
//
// At read time, set ReadOptions::table_index_factory to the same factory
// to use the trie for iteration:
// ReadOptions ro;
// ro.table_index_factory = trie_factory.get();
// auto iter = db->NewIterator(ro);
#pragma once
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
#include "rocksdb/comparator.h"
#include "rocksdb/types.h"
#include "rocksdb/user_defined_index.h"
#include "utilities/trie_index/louds_trie.h"
namespace ROCKSDB_NAMESPACE {
class ObjectLibrary;
namespace trie_index {
// Registers the built-in trie UDI in the supplied object library.
int RegisterBuiltinTrieIndexFactory(ObjectLibrary& library,
const std::string& arg);
// ============================================================================
// TrieIndexBuilder: Implements UserDefinedIndexBuilder using LoudsTrieBuilder.
//
// During SST file construction, RocksDB calls:
// 1. OnKeyAdded() for each key-value pair.
// 2. AddIndexEntry() at each data block boundary.
// 3. Finish() to serialize the index.
//
// The trie builder collects the separator keys from AddIndexEntry() and
// builds a LOUDS-encoded trie during Finish().
// ============================================================================
class TrieIndexBuilder final : public UserDefinedIndexBuilder {
public:
explicit TrieIndexBuilder(const Comparator* comparator);
~TrieIndexBuilder() override = default;
// Called at each data block boundary. We compute a short separator
// between last_key_in_current_block and first_key_in_next_block, buffer
// it for deferred trie construction in Finish(), and return it.
//
// The sequence numbers from context are used to detect same-user-key
// block boundaries. When such a boundary is detected, the builder
// switches to encoding ALL separators with seqno side-table metadata,
// mirroring the internal index's must_use_separator_with_seq_ strategy.
Slice AddIndexEntry(const Slice& last_key_in_current_block,
const Slice* first_key_in_next_block,
const BlockHandle& block_handle,
std::string* separator_scratch,
const IndexEntryContext& context) override;
// Called for each key added to the SST. Currently a no-op -- the trie is
// built entirely from separator keys provided via AddIndexEntry().
void OnKeyAdded(const Slice& key, ValueType type,
const Slice& value) override;
// Finalize the trie and return the serialized index data.
Status Finish(Slice* index_contents) override;
// Returns an estimate of the current serialized index size.
uint64_t EstimatedSize() const override;
private:
const Comparator* comparator_;
LoudsTrieBuilder trie_builder_;
bool finished_;
// --- Sequence number handling ---
//
// Seqno encoding is always enabled: AddIndexEntry() unconditionally sets
// must_use_separator_with_seq_ to true (the unconditional set at the end of
// AddIndexEntry()). This means the 8-byte-per-leaf seqno side-table overhead
// is always incurred. The flag exists so that Finish() can check it to decide
// whether to serialize the seqno side-table (true path) or emit a plain
// trie without seqno data (false/else path, only reachable for an empty
// trie with zero entries).
//
// We buffer all separator entries during building, then at Finish() feed
// them to the trie with seqno side-table metadata.
//
// Always set to true in AddIndexEntry() -- seqno encoding is
// unconditionally enabled. The 8-byte per-leaf overhead is always incurred.
bool must_use_separator_with_seq_;
// Buffered separator entries: (separator_key, tag, handle).
// The separator_key is the user-key-only separator computed by
// FindShortestSeparator. The tag field stores:
// - For same-user-key boundaries: the real tag of last_key
// - For the last block: the real tag of last_key
// - For intermediate non-boundary entries: 0 (sentinel)
struct BufferedEntry {
std::string separator_key;
uint64_t tag{};
TrieBlockHandle handle;
};
std::vector<BufferedEntry> buffered_entries_;
// Running total of separator key bytes for O(1) EstimatedSize().
uint64_t total_separator_bytes_ = 0;
};
// ============================================================================
// TrieIndexIterator: Implements UserDefinedIndexIterator using
// LoudsTrieIterator.
//
// Wraps LoudsTrieIterator and adapts it to the UDI iterator interface,
// handling bounds checking against ScanOptions.
// ============================================================================
class TrieIndexIterator final : public UserDefinedIndexIterator {
public:
// @param has_seqno_encoding: true if the trie was built with a seqno
// side-table (enabling post-seek correction for same-user-key boundaries).
// This flag is read from the trie's serialized header.
TrieIndexIterator(const LoudsTrie* trie, const Comparator* comparator,
bool has_seqno_encoding);
~TrieIndexIterator() override = default;
// Prepare for a batch of scans. Stores scan bounds for later use.
void Prepare(const ScanOptions scan_opts[], size_t num_opts) override;
// Position at the very first index entry. Descends directly to the
// leftmost leaf without a full seek traversal.
Status SeekToFirstAndGetResult(IterateResult* result) override;
// Position at the very last index entry. Descends directly to the
// rightmost leaf without a full seek traversal.
Status SeekToLastAndGetResult(IterateResult* result) override;
// Move to the previous index entry. When in an overflow run, decrements
// within the run before moving to the previous trie leaf.
Status PrevAndGetResult(IterateResult* result) override;
// Seek to the first index entry >= target. When has_seqno_encoding_ is
// true, the trie is searched with user_key only, then post-seek correction
// uses target_tag from context to advance through overflow blocks.
Status SeekAndGetResult(const Slice& target, IterateResult* result,
const SeekContext& context) override;
// Advance to the next index entry. When in an overflow run, advances
// within the run before moving to the next trie leaf.
Status NextAndGetResult(IterateResult* result) override;
// Return the BlockHandle of the current block. When positioned on an
// overflow block, returns the overflow block's handle instead of the
// trie leaf's handle.
UserDefinedIndexBuilder::BlockHandle value() override;
private:
// Check if the current block is within the active scan bounds.
// reference_key is the key to compare against the limit: for Seek this
// is the seek target, for Next this is the previous separator key.
// The trie stores separator keys (upper bounds on block contents), not
// first-in-block keys, so we cannot compare the current separator against
// the limit directly -- see the UDI API contract in user_defined_index.h.
IterBoundCheck CheckBounds(const Slice& reference_key) const;
const Comparator* comparator_;
LoudsTrieIterator iter_;
// Pointer to the trie for side-table access during overflow iteration.
const LoudsTrie* trie_;
// Scratch space for the current separator key (reconstructed from trie).
std::string current_key_scratch_;
// Previous separator key, used as reference for Next() bounds checking.
// Always valid when NextAndGetResult is called, since Next requires a
// preceding Seek or Next which sets current_key_scratch_ (copied here).
std::string prev_key_scratch_;
// Active scan options (from Prepare()).
std::vector<ScanOptions> scan_opts_;
size_t current_scan_idx_;
bool prepared_;
// True if the trie was built with a seqno side-table. When true:
// - SeekAndGetResult seeks with user key only, then does post-seek
// correction using seqno comparison
// - value() may return overflow block handles
// - NextAndGetResult advances within overflow runs before trie leaves
bool has_seqno_encoding_;
// Reset overflow state to the default single-block position.
void ResetOverflowState() {
overflow_run_index_ = 0;
overflow_run_size_ = 1;
overflow_base_idx_ = 0;
}
// Set up overflow state for the current trie leaf. When position_at_last
// is true (reverse iteration), positions at the last block in the run.
void SetupOverflowForCurrentLeaf(bool position_at_last) {
if (has_seqno_encoding_ && iter_.Valid()) {
uint64_t leaf_idx = iter_.LeafIndex();
uint32_t block_count = trie_->GetLeafBlockCount(leaf_idx);
overflow_run_size_ = block_count;
overflow_base_idx_ = trie_->GetOverflowBase(leaf_idx);
if (position_at_last) {
overflow_run_index_ = block_count - 1;
}
}
}
// Copy the current trie key into current_key_scratch_ and set the result.
void CopyTrieKeyToResult(IterateResult* result) {
Slice trie_key = iter_.Key();
current_key_scratch_.assign(trie_key.data(), trie_key.size());
result->key = Slice(current_key_scratch_);
}
// ---- Overflow run state ----
//
// When the trie iterator lands on a leaf that has block_count > 1 (a
// same-user-key run), these track the current position within the run:
//
// overflow_run_index_: 0 = primary block (trie leaf), 1+ = overflow block.
// Used by value() to select the right handle.
// overflow_run_size_: total blocks for current leaf (from leaf_block_counts).
// 1 = no overflow (the common case).
// overflow_base_idx_: starting index into overflow arrays for this leaf
// (from overflow_base_ prefix sum).
uint32_t overflow_run_index_;
uint32_t overflow_run_size_;
uint32_t overflow_base_idx_;
};
// ============================================================================
// TrieIndexReader: Implements UserDefinedIndexReader.
//
// Owns (or references) the deserialized LoudsTrie and creates iterators
// for read operations.
// ============================================================================
class TrieIndexReader : public UserDefinedIndexReader {
public:
explicit TrieIndexReader(const Comparator* comparator);
~TrieIndexReader() override = default;
// Initialize from serialized index data. The data must remain valid for
// the lifetime of this reader (it's typically a block cache entry).
Status InitFromSlice(const Slice& data);
// Create a new iterator for scanning.
std::unique_ptr<UserDefinedIndexIterator> NewIterator(
const ReadOptions& read_options) override;
// Approximate memory usage of the deserialized trie.
size_t ApproximateMemoryUsage() const override;
private:
const Comparator* comparator_;
LoudsTrie trie_;
size_t data_size_; // Size of the raw serialized data.
};
// ============================================================================
// TrieIndexFactory: Implements UserDefinedIndexFactory.
//
// Factory for creating TrieIndexBuilder (during SST file writes) and
// TrieIndexReader (during SST file reads). Registered as a Customizable
// with name "trie_index".
// ============================================================================
class TrieIndexFactory : public UserDefinedIndexFactory {
public:
TrieIndexFactory() = default;
~TrieIndexFactory() override = default;
static const char* kClassName() { return "trie_index"; }
const char* Name() const override { return kClassName(); }
// Deprecated API (required by base class). Use the overloads that accept
// UserDefinedIndexOption instead. These must never be called; the new
// overloads with UserDefinedIndexOption are always used by the block-based
// table builder/reader. Abort unconditionally (in both debug and release
// builds) to surface programming errors immediately.
UserDefinedIndexBuilder* NewBuilder() const override {
abort();
return nullptr;
}
std::unique_ptr<UserDefinedIndexReader> NewReader(
Slice& /*index_block*/) const override {
abort();
return nullptr;
}
// New API with comparator.
Status NewBuilder(
const UserDefinedIndexOption& option,
std::unique_ptr<UserDefinedIndexBuilder>& builder) const override;
Status NewReader(
const UserDefinedIndexOption& option, Slice& index_block,
std::unique_ptr<UserDefinedIndexReader>& reader) const override;
};
} // namespace trie_index
} // namespace ROCKSDB_NAMESPACE