Packages
rocksdb
3.1.2
3.1.2
3.1.1
3.1.0
3.0.0
2.6.2
2.6.1
retired
2.6.0
retired
2.5.0
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
1.9.0
1.8.0
1.7.0
1.6.0
1.5.1
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.1
1.1.0
1.0.0
0.26.2
0.26.1
0.26.0
0.25.0
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.0
0.21.0
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
RocksDB for Erlang
Current section
Files
Jump to
Current section
Files
c_src/posting_list_nif.cc
// Copyright (c) 2018-2026 Benoit Chesneau
//
// This file is provided to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <string>
#include <map>
#include <set>
#include <vector>
#include <cstdint>
#include <cstring>
#include "erl_nif.h"
#include "atoms.h"
#include "erocksdb.h"
#include "posting_list_merge_operator.h"
#ifdef WITH_CROARING
#include <roaring/roaring64.h>
#endif
namespace erocksdb {
// Forward declaration
struct PostingListResource;
// Resource type for parsed posting lists
static ErlNifResourceType* posting_list_resource_type = nullptr;
struct PostingListResource {
std::set<std::string> keys; // All keys (for exact lookup)
std::vector<std::string> sorted_keys; // For indexed access
#ifdef WITH_CROARING
roaring64_bitmap_t* bitmap; // For fast hash lookup
#endif
PostingListResource() {
#ifdef WITH_CROARING
bitmap = nullptr;
#endif
}
~PostingListResource() {
#ifdef WITH_CROARING
if (bitmap) {
roaring64_bitmap_free(bitmap);
}
#endif
}
};
static void posting_list_resource_dtor(ErlNifEnv* env, void* obj) {
auto* res = static_cast<PostingListResource*>(obj);
res->~PostingListResource();
}
// Initialize resource type (call from nif_load)
bool init_posting_list_resource(ErlNifEnv* env) {
posting_list_resource_type = enif_open_resource_type(
env, nullptr, "posting_list",
posting_list_resource_dtor,
ERL_NIF_RT_CREATE, nullptr);
return posting_list_resource_type != nullptr;
}
// MurmurHash3 64-bit finalizer for mixing (same as in merge operator)
static inline uint64_t fmix64(uint64_t k) {
k ^= k >> 33;
k *= 0xff51afd7ed558ccdULL;
k ^= k >> 33;
k *= 0xc4ceb9fe1a85ec53ULL;
k ^= k >> 33;
return k;
}
static uint64_t hash_key(const std::string& key) {
uint64_t h = 0x9e3779b97f4a7c15ULL; // seed
const char* data = key.data();
size_t len = key.size();
// Process 8-byte blocks
size_t nblocks = len / 8;
for (size_t i = 0; i < nblocks; i++) {
uint64_t k;
memcpy(&k, data + i * 8, sizeof(k));
h ^= fmix64(k);
h = (h << 27) | (h >> 37);
h = h * 5 + 0x52dce729;
}
// Handle remaining bytes
const uint8_t* tail = (const uint8_t*)(data + nblocks * 8);
uint64_t k = 0;
switch (len & 7) {
case 7: k ^= ((uint64_t)tail[6]) << 48; [[fallthrough]];
case 6: k ^= ((uint64_t)tail[5]) << 40; [[fallthrough]];
case 5: k ^= ((uint64_t)tail[4]) << 32; [[fallthrough]];
case 4: k ^= ((uint64_t)tail[3]) << 24; [[fallthrough]];
case 3: k ^= ((uint64_t)tail[2]) << 16; [[fallthrough]];
case 2: k ^= ((uint64_t)tail[1]) << 8; [[fallthrough]];
case 1: k ^= ((uint64_t)tail[0]);
h ^= fmix64(k);
}
return fmix64(h ^ len);
}
// Detect format version from data
static uint8_t detect_version(const unsigned char* data, size_t size) {
if (size == 0) {
return POSTING_LIST_V1;
}
if (data[0] == POSTING_LIST_V2) {
return POSTING_LIST_V2;
}
return POSTING_LIST_V1;
}
// Parse V1 posting list binary: <Len:32><Flag:8><Key:Len>...
static std::set<std::string> parse_posting_list_v1(
const unsigned char* data, size_t size)
{
std::set<std::string> result;
std::map<std::string, bool> states;
const unsigned char* ptr = data;
const unsigned char* end = data + size;
while (ptr + 5 <= end) {
uint32_t len = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4;
bool is_tombstone = (*ptr != 0);
ptr += 1;
if (ptr + len > end) break;
std::string key(reinterpret_cast<const char*>(ptr), len);
ptr += len;
states[key] = is_tombstone; // Last occurrence wins
}
for (const auto& [key, is_tombstone] : states) {
if (!is_tombstone) {
result.insert(key);
}
}
return result;
}
// Parse V2 posting list binary
// Returns sorted set of active keys
static std::set<std::string> parse_posting_list_v2(
const unsigned char* data, size_t size)
{
std::set<std::string> result;
const unsigned char* ptr = data;
const unsigned char* end = data + size;
// Skip version byte
if (ptr >= end) return result;
ptr += 1;
// Read bitmap size
if (ptr + 4 > end) return result;
uint32_t bitmap_size = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4;
// Skip bitmap data
if (ptr + bitmap_size > end) return result;
ptr += bitmap_size;
// Read key count
if (ptr + 4 > end) return result;
uint32_t key_count = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4;
// Read sorted keys
for (uint32_t i = 0; i < key_count && ptr + 4 <= end; i++) {
uint32_t len = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4;
if (ptr + len > end) break;
std::string key(reinterpret_cast<const char*>(ptr), len);
ptr += len;
result.insert(key);
}
return result;
}
// Parse any format (auto-detect)
static std::set<std::string> parse_posting_list(
const unsigned char* data, size_t size)
{
uint8_t version = detect_version(data, size);
if (version == POSTING_LIST_V2) {
return parse_posting_list_v2(data, size);
}
return parse_posting_list_v1(data, size);
}
#ifdef WITH_CROARING
// Extract bitmap from V2 posting list (returns nullptr for V1)
static roaring64_bitmap_t* extract_bitmap_v2(
const unsigned char* data, size_t size)
{
if (detect_version(data, size) != POSTING_LIST_V2) {
return nullptr;
}
const unsigned char* ptr = data;
const unsigned char* end = data + size;
// Skip version byte
ptr += 1;
// Read bitmap size
if (ptr + 4 > end) return nullptr;
uint32_t bitmap_size = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4;
if (bitmap_size == 0) return nullptr;
if (ptr + bitmap_size > end) return nullptr;
return roaring64_bitmap_portable_deserialize_safe(
reinterpret_cast<const char*>(ptr), bitmap_size);
}
// Build bitmap from keys (for V1 or building new)
static roaring64_bitmap_t* build_bitmap_from_keys(const std::set<std::string>& keys) {
roaring64_bitmap_t* bitmap = roaring64_bitmap_create();
for (const auto& key : keys) {
roaring64_bitmap_add(bitmap, hash_key(key));
}
return bitmap;
}
#endif
// Serialize set of keys to V2 format
static void serialize_v2(const std::set<std::string>& keys,
std::string& output) {
output.clear();
#ifdef WITH_CROARING
// Build roaring bitmap
roaring64_bitmap_t* bitmap = roaring64_bitmap_create();
for (const auto& key : keys) {
roaring64_bitmap_add(bitmap, hash_key(key));
}
// Serialize bitmap
size_t bitmap_size = roaring64_bitmap_portable_size_in_bytes(bitmap);
std::vector<char> bitmap_data(bitmap_size);
roaring64_bitmap_portable_serialize(bitmap, bitmap_data.data());
roaring64_bitmap_free(bitmap);
// Version byte
output.push_back(POSTING_LIST_V2);
// Bitmap size (big-endian)
uint32_t bs = static_cast<uint32_t>(bitmap_size);
output.push_back((bs >> 24) & 0xFF);
output.push_back((bs >> 16) & 0xFF);
output.push_back((bs >> 8) & 0xFF);
output.push_back(bs & 0xFF);
// Bitmap data
output.append(bitmap_data.data(), bitmap_size);
#else
// Without CRoaring, write empty bitmap
output.push_back(POSTING_LIST_V2);
output.push_back(0);
output.push_back(0);
output.push_back(0);
output.push_back(0);
#endif
// Key count (big-endian)
uint32_t kc = static_cast<uint32_t>(keys.size());
output.push_back((kc >> 24) & 0xFF);
output.push_back((kc >> 16) & 0xFF);
output.push_back((kc >> 8) & 0xFF);
output.push_back(kc & 0xFF);
// Sorted keys (std::set is already sorted)
for (const auto& key : keys) {
uint32_t len = static_cast<uint32_t>(key.size());
output.push_back((len >> 24) & 0xFF);
output.push_back((len >> 16) & 0xFF);
output.push_back((len >> 8) & 0xFF);
output.push_back(len & 0xFF);
output.append(key);
}
}
// posting_list_keys(Binary) -> [binary()]
ERL_NIF_TERM PostingListKeys(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin;
if (argc != 1 || !enif_inspect_binary(env, argv[0], &bin)) {
return enif_make_badarg(env);
}
auto keys = parse_posting_list(bin.data, bin.size);
// Build list of keys (sorted)
ERL_NIF_TERM list = enif_make_list(env, 0);
// Iterate in reverse to build list in correct order
for (auto it = keys.rbegin(); it != keys.rend(); ++it) {
ERL_NIF_TERM key_bin;
unsigned char* buf = enif_make_new_binary(env, it->size(), &key_bin);
if (buf == nullptr) {
return enif_make_badarg(env);
}
memcpy(buf, it->data(), it->size());
list = enif_make_list_cell(env, key_bin, list);
}
return list;
}
// Fast binary search in V2 sorted keys section (no allocation)
static bool binary_search_v2_keys(const unsigned char* data, size_t size,
const unsigned char* search_key, size_t search_len) {
const unsigned char* ptr = data;
const unsigned char* end = data + size;
// Skip version byte
if (ptr >= end || *ptr != POSTING_LIST_V2) return false;
ptr += 1;
// Skip bitmap
if (ptr + 4 > end) return false;
uint32_t bitmap_size = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4 + bitmap_size;
// Read key count
if (ptr + 4 > end) return false;
uint32_t key_count = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4;
if (key_count == 0) return false;
// Build index of key offsets for binary search
std::vector<std::pair<const unsigned char*, uint32_t>> key_offsets;
key_offsets.reserve(key_count);
const unsigned char* scan = ptr;
for (uint32_t i = 0; i < key_count && scan + 4 <= end; i++) {
uint32_t len = (static_cast<uint32_t>(scan[0]) << 24) |
(static_cast<uint32_t>(scan[1]) << 16) |
(static_cast<uint32_t>(scan[2]) << 8) |
static_cast<uint32_t>(scan[3]);
scan += 4;
if (scan + len > end) break;
key_offsets.push_back({scan, len});
scan += len;
}
// Binary search
size_t lo = 0, hi = key_offsets.size();
while (lo < hi) {
size_t mid = lo + (hi - lo) / 2;
const unsigned char* key_ptr = key_offsets[mid].first;
uint32_t key_len = key_offsets[mid].second;
// Compare
size_t cmp_len = (key_len < search_len) ? key_len : search_len;
int cmp = memcmp(key_ptr, search_key, cmp_len);
if (cmp == 0) {
if (key_len < search_len) cmp = -1;
else if (key_len > search_len) cmp = 1;
}
if (cmp == 0) return true;
if (cmp < 0) lo = mid + 1;
else hi = mid;
}
return false;
}
// posting_list_contains(Binary, Key) -> boolean()
ERL_NIF_TERM PostingListContains(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin, search_key;
if (argc != 2 ||
!enif_inspect_binary(env, argv[0], &bin) ||
!enif_inspect_binary(env, argv[1], &search_key)) {
return enif_make_badarg(env);
}
// Fast path for V2: binary search without allocation
if (detect_version(bin.data, bin.size) == POSTING_LIST_V2) {
if (binary_search_v2_keys(bin.data, bin.size, search_key.data, search_key.size)) {
return ATOM_TRUE;
}
return ATOM_FALSE;
}
// V1 fallback: need to parse (has tombstones)
std::string key_str(reinterpret_cast<const char*>(search_key.data), search_key.size);
auto keys = parse_posting_list_v1(bin.data, bin.size);
if (keys.count(key_str) > 0) {
return ATOM_TRUE;
}
return ATOM_FALSE;
}
// posting_list_find(Binary, Key) -> {ok, boolean()} | not_found
ERL_NIF_TERM PostingListFind(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin, search_key;
if (argc != 2 ||
!enif_inspect_binary(env, argv[0], &bin) ||
!enif_inspect_binary(env, argv[1], &search_key)) {
return enif_make_badarg(env);
}
std::string key_str(reinterpret_cast<const char*>(search_key.data), search_key.size);
// For V2, all keys present are active (no tombstones stored)
uint8_t version = detect_version(bin.data, bin.size);
if (version == POSTING_LIST_V2) {
auto keys = parse_posting_list_v2(bin.data, bin.size);
if (keys.count(key_str) > 0) {
return enif_make_tuple2(env, ATOM_OK, ATOM_FALSE); // active (not tombstone)
}
return ATOM_NOT_FOUND;
}
// For V1, we need to check tombstone status
std::map<std::string, bool> states;
const unsigned char* ptr = bin.data;
const unsigned char* end = bin.data + bin.size;
while (ptr + 5 <= end) {
uint32_t len = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4;
bool is_tombstone = (*ptr != 0);
ptr += 1;
if (ptr + len > end) break;
std::string key(reinterpret_cast<const char*>(ptr), len);
ptr += len;
states[key] = is_tombstone;
}
auto it = states.find(key_str);
if (it == states.end()) {
return ATOM_NOT_FOUND;
}
return enif_make_tuple2(env, ATOM_OK,
it->second ? ATOM_TRUE : ATOM_FALSE);
}
// posting_list_count(Binary) -> non_neg_integer()
ERL_NIF_TERM PostingListCount(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin;
if (argc != 1 || !enif_inspect_binary(env, argv[0], &bin)) {
return enif_make_badarg(env);
}
auto keys = parse_posting_list(bin.data, bin.size);
return enif_make_uint64(env, keys.size());
}
// posting_list_to_map(Binary) -> #{binary() => active | tombstone}
ERL_NIF_TERM PostingListToMap(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin;
if (argc != 1 || !enif_inspect_binary(env, argv[0], &bin)) {
return enif_make_badarg(env);
}
ERL_NIF_TERM map = enif_make_new_map(env);
uint8_t version = detect_version(bin.data, bin.size);
if (version == POSTING_LIST_V2) {
// V2: all keys are active
auto keys = parse_posting_list_v2(bin.data, bin.size);
for (const auto& key : keys) {
ERL_NIF_TERM key_bin;
unsigned char* buf = enif_make_new_binary(env, key.size(), &key_bin);
if (buf == nullptr) {
return enif_make_badarg(env);
}
memcpy(buf, key.data(), key.size());
enif_make_map_put(env, map, key_bin, ATOM_ACTIVE, &map);
}
} else {
// V1: parse with tombstone status
std::map<std::string, bool> states;
const unsigned char* ptr = bin.data;
const unsigned char* end = bin.data + bin.size;
while (ptr + 5 <= end) {
uint32_t len = (static_cast<uint32_t>(ptr[0]) << 24) |
(static_cast<uint32_t>(ptr[1]) << 16) |
(static_cast<uint32_t>(ptr[2]) << 8) |
static_cast<uint32_t>(ptr[3]);
ptr += 4;
bool is_tombstone = (*ptr != 0);
ptr += 1;
if (ptr + len > end) break;
std::string key(reinterpret_cast<const char*>(ptr), len);
ptr += len;
states[key] = is_tombstone;
}
for (const auto& [key, is_tombstone] : states) {
ERL_NIF_TERM key_bin;
unsigned char* buf = enif_make_new_binary(env, key.size(), &key_bin);
if (buf == nullptr) {
return enif_make_badarg(env);
}
memcpy(buf, key.data(), key.size());
ERL_NIF_TERM value = is_tombstone ? ATOM_TOMBSTONE : ATOM_ACTIVE;
enif_make_map_put(env, map, key_bin, value, &map);
}
}
return map;
}
// posting_list_version(Binary) -> 1 | 2
ERL_NIF_TERM PostingListVersion(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin;
if (argc != 1 || !enif_inspect_binary(env, argv[0], &bin)) {
return enif_make_badarg(env);
}
uint8_t version = detect_version(bin.data, bin.size);
return enif_make_uint(env, version);
}
// posting_list_intersection(Binary1, Binary2) -> Binary
ERL_NIF_TERM PostingListIntersection(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin1, bin2;
if (argc != 2 ||
!enif_inspect_binary(env, argv[0], &bin1) ||
!enif_inspect_binary(env, argv[1], &bin2)) {
return enif_make_badarg(env);
}
auto keys1 = parse_posting_list(bin1.data, bin1.size);
auto keys2 = parse_posting_list(bin2.data, bin2.size);
// Compute intersection
std::set<std::string> result;
for (const auto& key : keys1) {
if (keys2.count(key) > 0) {
result.insert(key);
}
}
// Serialize to V2 format
std::string output;
serialize_v2(result, output);
// Create Erlang binary
ERL_NIF_TERM result_bin;
unsigned char* buf = enif_make_new_binary(env, output.size(), &result_bin);
if (buf == nullptr) {
return enif_make_badarg(env);
}
memcpy(buf, output.data(), output.size());
return result_bin;
}
// posting_list_union(Binary1, Binary2) -> Binary
ERL_NIF_TERM PostingListUnion(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin1, bin2;
if (argc != 2 ||
!enif_inspect_binary(env, argv[0], &bin1) ||
!enif_inspect_binary(env, argv[1], &bin2)) {
return enif_make_badarg(env);
}
auto keys1 = parse_posting_list(bin1.data, bin1.size);
auto keys2 = parse_posting_list(bin2.data, bin2.size);
// Compute union (std::set automatically handles duplicates)
std::set<std::string> result = keys1;
result.insert(keys2.begin(), keys2.end());
// Serialize to V2 format
std::string output;
serialize_v2(result, output);
// Create Erlang binary
ERL_NIF_TERM result_bin;
unsigned char* buf = enif_make_new_binary(env, output.size(), &result_bin);
if (buf == nullptr) {
return enif_make_badarg(env);
}
memcpy(buf, output.data(), output.size());
return result_bin;
}
// posting_list_difference(Binary1, Binary2) -> Binary
// Returns keys in Binary1 that are not in Binary2
ERL_NIF_TERM PostingListDifference(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin1, bin2;
if (argc != 2 ||
!enif_inspect_binary(env, argv[0], &bin1) ||
!enif_inspect_binary(env, argv[1], &bin2)) {
return enif_make_badarg(env);
}
auto keys1 = parse_posting_list(bin1.data, bin1.size);
auto keys2 = parse_posting_list(bin2.data, bin2.size);
// Compute difference (keys1 - keys2)
std::set<std::string> result;
for (const auto& key : keys1) {
if (keys2.count(key) == 0) {
result.insert(key);
}
}
// Serialize to V2 format
std::string output;
serialize_v2(result, output);
// Create Erlang binary
ERL_NIF_TERM result_bin;
unsigned char* buf = enif_make_new_binary(env, output.size(), &result_bin);
if (buf == nullptr) {
return enif_make_badarg(env);
}
memcpy(buf, output.data(), output.size());
return result_bin;
}
// posting_list_intersection_count(Binary1, Binary2) -> non_neg_integer()
// Fast cardinality using bitmap when available
ERL_NIF_TERM PostingListIntersectionCount(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin1, bin2;
if (argc != 2 ||
!enif_inspect_binary(env, argv[0], &bin1) ||
!enif_inspect_binary(env, argv[1], &bin2)) {
return enif_make_badarg(env);
}
#ifdef WITH_CROARING
// Try fast path with bitmaps if both are V2
uint8_t v1 = detect_version(bin1.data, bin1.size);
uint8_t v2 = detect_version(bin2.data, bin2.size);
if (v1 == POSTING_LIST_V2 && v2 == POSTING_LIST_V2) {
roaring64_bitmap_t* bitmap1 = extract_bitmap_v2(bin1.data, bin1.size);
roaring64_bitmap_t* bitmap2 = extract_bitmap_v2(bin2.data, bin2.size);
if (bitmap1 && bitmap2) {
uint64_t count = roaring64_bitmap_and_cardinality(bitmap1, bitmap2);
roaring64_bitmap_free(bitmap1);
roaring64_bitmap_free(bitmap2);
return enif_make_uint64(env, count);
}
if (bitmap1) roaring64_bitmap_free(bitmap1);
if (bitmap2) roaring64_bitmap_free(bitmap2);
}
#endif
// Fallback: compute exact intersection
auto keys1 = parse_posting_list(bin1.data, bin1.size);
auto keys2 = parse_posting_list(bin2.data, bin2.size);
size_t count = 0;
for (const auto& key : keys1) {
if (keys2.count(key) > 0) {
count++;
}
}
return enif_make_uint64(env, count);
}
// posting_list_bitmap_contains(Binary, Key) -> boolean()
// Fast bitmap-based lookup (may have false positives in rare hash collision cases)
ERL_NIF_TERM PostingListBitmapContains(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin, search_key;
if (argc != 2 ||
!enif_inspect_binary(env, argv[0], &bin) ||
!enif_inspect_binary(env, argv[1], &search_key)) {
return enif_make_badarg(env);
}
#ifdef WITH_CROARING
if (detect_version(bin.data, bin.size) == POSTING_LIST_V2) {
roaring64_bitmap_t* bitmap = extract_bitmap_v2(bin.data, bin.size);
if (bitmap) {
std::string key_str(reinterpret_cast<const char*>(search_key.data),
search_key.size);
uint64_t h = hash_key(key_str);
bool contains = roaring64_bitmap_contains(bitmap, h);
roaring64_bitmap_free(bitmap);
return contains ? ATOM_TRUE : ATOM_FALSE;
}
}
#endif
// Fallback to exact lookup
std::string key_str(reinterpret_cast<const char*>(search_key.data), search_key.size);
auto keys = parse_posting_list(bin.data, bin.size);
if (keys.count(key_str) > 0) {
return ATOM_TRUE;
}
return ATOM_FALSE;
}
// postings_open(Binary) -> {ok, Resource} | {error, Reason}
// Parse posting list into a resource for fast repeated lookups
ERL_NIF_TERM PostingsOpen(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary bin;
if (argc != 1 || !enif_inspect_binary(env, argv[0], &bin)) {
return enif_make_badarg(env);
}
// Allocate resource
void* mem = enif_alloc_resource(posting_list_resource_type, sizeof(PostingListResource));
if (!mem) {
return enif_make_tuple2(env, ATOM_ERROR,
enif_make_atom(env, "alloc_failed"));
}
// Construct in place
auto* res = new (mem) PostingListResource();
// Parse keys
res->keys = parse_posting_list(bin.data, bin.size);
res->sorted_keys.reserve(res->keys.size());
for (const auto& key : res->keys) {
res->sorted_keys.push_back(key);
}
#ifdef WITH_CROARING
// Extract or build bitmap
if (detect_version(bin.data, bin.size) == POSTING_LIST_V2) {
res->bitmap = extract_bitmap_v2(bin.data, bin.size);
}
if (!res->bitmap) {
res->bitmap = build_bitmap_from_keys(res->keys);
}
#endif
ERL_NIF_TERM result = enif_make_resource(env, res);
enif_release_resource(res);
return enif_make_tuple2(env, ATOM_OK, result);
}
// postings_contains(Resource, Key) -> boolean()
// Fast lookup using parsed resource
ERL_NIF_TERM PostingsContains(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
PostingListResource* res;
ErlNifBinary search_key;
if (argc != 2 ||
!enif_get_resource(env, argv[0], posting_list_resource_type, (void**)&res) ||
!enif_inspect_binary(env, argv[1], &search_key)) {
return enif_make_badarg(env);
}
std::string key_str(reinterpret_cast<const char*>(search_key.data), search_key.size);
if (res->keys.count(key_str) > 0) {
return ATOM_TRUE;
}
return ATOM_FALSE;
}
// postings_bitmap_contains(Resource, Key) -> boolean()
// Fast hash-based lookup using bitmap
ERL_NIF_TERM PostingsBitmapContains(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
PostingListResource* res;
ErlNifBinary search_key;
if (argc != 2 ||
!enif_get_resource(env, argv[0], posting_list_resource_type, (void**)&res) ||
!enif_inspect_binary(env, argv[1], &search_key)) {
return enif_make_badarg(env);
}
#ifdef WITH_CROARING
if (res->bitmap) {
std::string key_str(reinterpret_cast<const char*>(search_key.data), search_key.size);
uint64_t h = hash_key(key_str);
if (roaring64_bitmap_contains(res->bitmap, h)) {
return ATOM_TRUE;
}
return ATOM_FALSE;
}
#endif
// Fallback to exact lookup
std::string key_str(reinterpret_cast<const char*>(search_key.data), search_key.size);
if (res->keys.count(key_str) > 0) {
return ATOM_TRUE;
}
return ATOM_FALSE;
}
// postings_count(Resource) -> non_neg_integer()
ERL_NIF_TERM PostingsCount(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
PostingListResource* res;
if (argc != 1 ||
!enif_get_resource(env, argv[0], posting_list_resource_type, (void**)&res)) {
return enif_make_badarg(env);
}
return enif_make_uint64(env, res->keys.size());
}
// postings_keys(Resource) -> [binary()]
ERL_NIF_TERM PostingsKeys(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
PostingListResource* res;
if (argc != 1 ||
!enif_get_resource(env, argv[0], posting_list_resource_type, (void**)&res)) {
return enif_make_badarg(env);
}
// Build list of keys (already sorted in std::set)
ERL_NIF_TERM list = enif_make_list(env, 0);
for (auto it = res->keys.rbegin(); it != res->keys.rend(); ++it) {
ERL_NIF_TERM key_bin;
unsigned char* buf = enif_make_new_binary(env, it->size(), &key_bin);
if (buf == nullptr) {
return enif_make_badarg(env);
}
memcpy(buf, it->data(), it->size());
list = enif_make_list_cell(env, key_bin, list);
}
return list;
}
// postings_to_binary(Resource) -> binary()
// Convert postings resource back to V2 binary format
ERL_NIF_TERM PostingsToBinary(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
PostingListResource* res;
if (argc != 1 ||
!enif_get_resource(env, argv[0], posting_list_resource_type, (void**)&res)) {
return enif_make_badarg(env);
}
// Serialize to V2 format
std::string output;
serialize_v2(res->keys, output);
// Create Erlang binary
ERL_NIF_TERM result_bin;
unsigned char* buf = enif_make_new_binary(env, output.size(), &result_bin);
if (buf == nullptr) {
return enif_make_badarg(env);
}
memcpy(buf, output.data(), output.size());
return result_bin;
}
} // namespace erocksdb