%% @doc Postings Resource API for barrel_docdb %% %% This module wraps the rocksdb postings_* resource API for efficient %% posting list operations. The resource API parses posting list binaries %% once and provides O(1) or O(log n) lookups for repeated access. %% %% Key benefits: %% - Parse once, lookup many times (~0.1 us per lookup) %% - Native set operations (intersection, union, difference) %% - Pre-sorted keys in V2 format %% - Roaring64 bitmap for fast existence checks %% %% @end -module(barrel_postings). -export([ %% Resource lifecycle open/1, to_binary/1, %% Lookups contains/2, bitmap_contains/2, %% Metadata count/1, keys/1, %% Set operations (return {ok, Resource} or error) intersection/2, union/2, difference/2, intersect_all/1, intersection_count/2 ]). -export_type([postings/0]). -opaque postings() :: reference(). %% @doc Parse a posting list binary into a resource for fast repeated lookups. %% The binary must be a valid V1 or V2 posting list format. -spec open(binary()) -> {ok, postings()} | {error, term()}. open(Binary) when is_binary(Binary) -> rocksdb:postings_open(Binary). %% @doc Convert a postings resource back to binary format. -spec to_binary(postings()) -> binary(). to_binary(Postings) -> rocksdb:postings_to_binary(Postings). %% @doc Check if a key exists in the posting list (exact match). %% Uses O(log n) binary search on sorted keys. -spec contains(postings(), binary()) -> boolean(). contains(Postings, Key) when is_binary(Key) -> rocksdb:postings_contains(Postings, Key). %% @doc Check if a key exists using the embedded roaring bitmap. %% O(1) hash-based lookup, may have rare false positives. %% Use contains/2 for exact checks when false positives are unacceptable. -spec bitmap_contains(postings(), binary()) -> boolean(). bitmap_contains(Postings, Key) when is_binary(Key) -> rocksdb:postings_bitmap_contains(Postings, Key). %% @doc Get the number of active keys in the posting list. -spec count(postings()) -> non_neg_integer(). count(Postings) -> rocksdb:postings_count(Postings). %% @doc Get all active keys in lexicographic (sorted) order. %% Keys are pre-sorted in V2 format, so this is O(n). -spec keys(postings()) -> [binary()]. keys(Postings) -> rocksdb:postings_keys(Postings). %% @doc Compute intersection (AND) of two posting lists. %% Both arguments can be postings resources or binaries. -spec intersection(postings() | binary(), postings() | binary()) -> {ok, postings()} | {error, term()}. intersection(A, B) -> rocksdb:postings_intersection(A, B). %% @doc Compute union (OR) of two posting lists. %% Both arguments can be postings resources or binaries. -spec union(postings() | binary(), postings() | binary()) -> {ok, postings()} | {error, term()}. union(A, B) -> rocksdb:postings_union(A, B). %% @doc Compute difference (A - B) of two posting lists. %% Returns keys in A that are not in B. -spec difference(postings() | binary(), postings() | binary()) -> {ok, postings()} | {error, term()}. difference(A, B) -> rocksdb:postings_difference(A, B). %% @doc Compute intersection of multiple posting lists. %% Efficiently processes smallest lists first. %% All list elements can be postings resources or binaries. -spec intersect_all([postings() | binary()]) -> {ok, postings()} | {error, term()}. intersect_all(Postings) when is_list(Postings), length(Postings) > 0 -> rocksdb:postings_intersect_all(Postings); intersect_all([]) -> {error, empty_list}. %% @doc Get the cardinality of intersection without materializing the result. %% Uses roaring bitmap for fast computation. -spec intersection_count(postings() | binary(), postings() | binary()) -> non_neg_integer(). intersection_count(A, B) -> rocksdb:postings_intersection_count(A, B).