OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / rocksdb-native / binding.cc

2205 lines · cc ref:
#include <set>

#include <assert.h>
#include <bare.h>
#include <js.h>
#include <jstl.h>
#include <rocksdb.h>
#include <stdlib.h>
#include <string.h>
#include <utf.h>
#include <uv.h>

using rocksdb_native_on_open_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_close_t = js_function_t<void, js_receiver_t>;
using rocksdb_native_on_suspend_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_resume_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_flush_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_write_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_read_t = js_function_t<void, js_receiver_t, js_array_t, js_array_t>;
using rocksdb_native_on_iterator_open_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_iterator_close_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_iterator_read_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>, js_array_t, js_array_t>;
using rocksdb_native_on_compact_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_compact_range_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>>;
using rocksdb_native_on_approximate_size_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>, uint64_t>;
using rocksdb_native_on_current_wal_file_t = js_function_t<void, js_receiver_t, std::optional<js_object_t>, char *, uint64_t, uint32_t, uint64_t, uint64_t>;

struct rocksdb_native_t;

struct rocksdb_native_column_family_t {
  rocksdb_column_family_t *handle;
  rocksdb_column_family_descriptor_t descriptor;

  std::string name;

  rocksdb_native_t *db;

  js_persistent_t<js_arraybuffer_t> ctx;
};

struct rocksdb_native_snapshot_t {
  rocksdb_snapshot_t handle;

  rocksdb_native_t *db;

  js_persistent_t<js_arraybuffer_t> ctx;
};

struct rocksdb_native_t {
  rocksdb_t handle;
  rocksdb_options_t options;

  rocksdb_slice_t *wal_filter_prefixes;
  size_t wal_filter_prefixes_len;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;

  bool closing;
  bool exiting;

  std::set<rocksdb_native_column_family_t *> column_families;
  std::set<rocksdb_native_snapshot_t *> snapshots;

  js_deferred_teardown_t *teardown;
};

struct rocksdb_native_open_t {
  rocksdb_open_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_open_t> on_open;

  js_persistent_t<js_array_t> column_families;
};

struct rocksdb_native_close_t {
  rocksdb_close_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_close_t> on_close;
};

struct rocksdb_native_suspend_t {
  rocksdb_suspend_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_suspend_t> on_suspend;
};

struct rocksdb_native_resume_t {
  rocksdb_resume_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_resume_t> on_resume;
};

struct rocksdb_native_iterator_t {
  rocksdb_iterator_t handle;

  rocksdb_slice_t *keys;
  rocksdb_slice_t *values;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_iterator_open_t> on_open;
  js_persistent_t<rocksdb_native_on_iterator_close_t> on_close;
  js_persistent_t<rocksdb_native_on_iterator_read_t> on_read;

  bool closing;
  bool exiting;

  js_deferred_teardown_t *teardown;
};

struct rocksdb_native_read_batch_t {
  rocksdb_read_batch_t handle;

  rocksdb_read_t *reads;

  size_t capacity;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_read_t> on_read;
};

struct rocksdb_native_write_batch_t {
  rocksdb_write_batch_t handle;

  rocksdb_write_t *writes;

  size_t capacity;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_write_t> on_write;
};

struct rocksdb_native_flush_t {
  rocksdb_flush_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_flush_t> on_flush;

  js_persistent_t<rocksdb_native_column_family_t> column_family;
};

struct rocksdb_native_compact_t {
  rocksdb_compact_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_compact_t> on_compact;
};

struct rocksdb_native_compact_range_t {
  rocksdb_compact_range_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_compact_range_t> on_compact_range;
};

struct rocksdb_native_current_wal_file_t {
  rocksdb_current_wal_file_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_current_wal_file_t> on_current_wal_file;
};

struct rocksdb_native_approximate_size_t {
  rocksdb_approximate_size_t handle;

  js_env_t *env;
  js_persistent_t<js_receiver_t> ctx;
  js_persistent_t<rocksdb_native_on_approximate_size_t> on_approximate_size;
};

static void
rocksdb_native__on_free(js_env_t *env, char *data) {
  free(data);
}

static int
rocksdb_native__try_create_external_arraybuffer(js_env_t *env, char *data, size_t len, js_arraybuffer_t &result) {
  int err;

  err = js_create_external_arraybuffer<rocksdb_native__on_free>(env, data, len, result);
  if (err == 0) return 0;

  err = js_create_arraybuffer(env, data, len, result);
  assert(err == 0);

  free(data);

  return 0;
}

static void
rocksdb_native__on_open(rocksdb_open_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_open_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  auto descriptors = handle->column_families;
  auto handles = handle->handles;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_open_t cb;
  err = js_get_reference_value(env, req->on_open, cb);
  assert(err == 0);

  js_array_t column_families;
  err = js_get_reference_value(env, req->column_families, column_families);
  assert(err == 0);

  req->on_open.reset();
  req->column_families.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  } else {
    std::vector<js_arraybuffer_t> elements;
    err = js_get_array_elements(env, column_families, elements);
    assert(err == 0);

    const auto len = elements.size();

    for (uint32_t i = 0; i < len; i++) {
      if (db->exiting) rocksdb_column_family_destroy(&db->handle, handles[i]);
      else {
        js_arraybuffer_t handle = elements[i];

        rocksdb_native_column_family_t *column_family;
        err = js_get_arraybuffer_info(env, handle, column_family);
        assert(err == 0);

        column_family->handle = handles[i];

        err = js_create_reference(env, handle, column_family->ctx);
        assert(err == 0);

        db->column_families.insert(column_family);
      }
    }
  }

  rocksdb_open_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);

  delete[] descriptors;
  delete[] handles;
}

static void
rocksdb_native__on_idle(rocksdb_t *handle) {
  int err;

  auto db = reinterpret_cast<rocksdb_native_t *>(handle);

  for (auto &column_family : db->column_families) {
    err = rocksdb_column_family_destroy(&column_family->db->handle, column_family->handle);
    assert(err == 0);

    column_family->handle = nullptr;

    column_family->name.~basic_string();

    column_family->ctx.reset();
  }

  for (auto &snapshot : db->snapshots) {
    rocksdb_snapshot_destroy(&snapshot->handle);

    snapshot->ctx.reset();
  }

  db->column_families.~set();
  db->snapshots.~set();

  if (db->wal_filter_prefixes) {
    for (size_t i = 0; i < db->wal_filter_prefixes_len; i++) {
      free(const_cast<char *>(db->wal_filter_prefixes[i].data));
    }

    free(db->wal_filter_prefixes);

    db->wal_filter_prefixes = nullptr;
    db->wal_filter_prefixes_len = 0;
  }
}

static void
rocksdb_native__on_close(rocksdb_close_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_close_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  auto teardown = db->teardown;

  rocksdb_close_cleanup(&req->handle);

  if (db->closing) {
    js_handle_scope_t *scope;
    err = js_open_handle_scope(env, &scope);
    assert(err == 0);

    js_receiver_t ctx;
    err = js_get_reference_value(env, req->ctx, ctx);
    assert(err == 0);

    rocksdb_native_on_close_t cb;
    err = js_get_reference_value(env, req->on_close, cb);
    assert(err == 0);

    req->on_close.reset();
    req->ctx.reset();

    if (!db->exiting) {
      err = js_call_function_with_checkpoint(env, cb, ctx);
      (void) err;
    }

    err = js_close_handle_scope(env, scope);
    assert(err == 0);
  } else {
    delete req;
  }

  db->ctx.reset();

  err = js_finish_deferred_teardown_callback(teardown);
  assert(err == 0);
}

static void
rocksdb_native__on_teardown(js_deferred_teardown_t *teardown, void *data) {
  int err;

  auto db = reinterpret_cast<rocksdb_native_t *>(data);

  db->exiting = true;

  if (db->closing) return;

  auto env = db->env;

  auto req = new rocksdb_native_close_t();

  req->env = env;
  req->handle.data = req;

  err = rocksdb_close(&db->handle, &req->handle, rocksdb_native__on_idle, rocksdb_native__on_close);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_init(
  js_env_t *env,
  bool read_only,
  bool create_if_missing,
  bool create_missing_column_families,
  int32_t max_background_jobs,
  uint64_t bytes_per_sync,
  int32_t max_open_files,
  bool use_direct_reads,
  bool avoid_unnecessary_blocking_io,
  bool skip_stats_update_on_db_open,
  bool use_direct_io_for_flush_and_compaction,
  int32_t max_file_opening_threads,
  uint32_t wal_recovery_mode,
  bool best_efforts_recovery,
  bool enable_statistics,
  uint32_t stats_level,
  uint64_t wal_ttl_seconds,
  uint64_t wal_size_limit_mb,
  bool avoid_flush_during_shutdown,
  js_array_t wal_filter_prefixes_array
) {
  int err;

  std::vector<js_typedarray_t<>> prefix_elements;
  err = js_get_array_elements(env, wal_filter_prefixes_array, prefix_elements);
  assert(err == 0);

  const auto wal_filter_prefixes_len = prefix_elements.size();

  rocksdb_slice_t *wal_filter_prefixes = nullptr;

  if (wal_filter_prefixes_len > 0) {
    wal_filter_prefixes = reinterpret_cast<rocksdb_slice_t *>(malloc(wal_filter_prefixes_len * sizeof(rocksdb_slice_t)));

    for (size_t i = 0; i < wal_filter_prefixes_len; i++) {
      rocksdb_slice_t prefix;
      err = js_get_typedarray_info(env, prefix_elements[i], prefix.data, prefix.len);
      assert(err == 0);

      auto data = reinterpret_cast<char *>(malloc(prefix.len));
      memcpy(data, prefix.data, prefix.len);

      wal_filter_prefixes[i].data = data;
      wal_filter_prefixes[i].len = prefix.len;
    }
  }

  js_arraybuffer_t handle;

  rocksdb_native_t *db;
  err = js_create_arraybuffer(env, db, handle);
  assert(err == 0);

  db->env = env;
  db->closing = false;
  db->exiting = false;

  db->wal_filter_prefixes = wal_filter_prefixes;
  db->wal_filter_prefixes_len = wal_filter_prefixes_len;

  new (&db->column_families) std::set<rocksdb_native_column_family_t *>();
  new (&db->snapshots) std::set<rocksdb_native_snapshot_t *>();

  rocksdb_options_init(&db->options, 8);

  db->options.read_only = read_only;
  db->options.create_if_missing = create_if_missing;
  db->options.create_missing_column_families = create_missing_column_families;
  db->options.max_background_jobs = max_background_jobs;
  db->options.bytes_per_sync = bytes_per_sync;
  db->options.max_open_files = max_open_files;
  db->options.use_direct_reads = use_direct_reads;
  db->options.avoid_unnecessary_blocking_io = avoid_unnecessary_blocking_io;
  db->options.skip_stats_update_on_db_open = skip_stats_update_on_db_open;
  db->options.use_direct_io_for_flush_and_compaction = use_direct_io_for_flush_and_compaction;
  db->options.max_file_opening_threads = max_file_opening_threads;
  db->options.lock = -1;
  db->options.wal_recovery_mode = rocksdb_wal_recovery_mode_t(wal_recovery_mode);
  db->options.best_efforts_recovery = best_efforts_recovery;
  db->options.enable_statistics = enable_statistics;
  db->options.stats_level = rocksdb_stats_level_t(stats_level);
  db->options.wal_ttl_seconds = wal_ttl_seconds;
  db->options.wal_size_limit_mb = wal_size_limit_mb;
  db->options.avoid_flush_during_shutdown = avoid_flush_during_shutdown;
  db->options.wal_filter_prefixes = db->wal_filter_prefixes;
  db->options.wal_filter_prefixes_len = db->wal_filter_prefixes_len;

  return handle;
}

static js_arraybuffer_t
rocksdb_native_open(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_receiver_t self,
  std::string path,
  js_array_t column_families_array,
  int lock,
  js_receiver_t ctx,
  rocksdb_native_on_open_t on_open
) {
  int err;

  std::vector<js_arraybuffer_t> elements;
  err = js_get_array_elements(env, column_families_array, elements);
  assert(err == 0);

  const auto len = elements.size();

  auto column_families = new rocksdb_column_family_descriptor_t[len];

  for (uint32_t i = 0; i < len; i++) {
    js_arraybuffer_t handle = elements[i];

    rocksdb_native_column_family_t *column_family;
    err = js_get_arraybuffer_info(env, handle, column_family);
    assert(err == 0);

    memcpy(&column_families[i], &column_family->descriptor, sizeof(rocksdb_column_family_descriptor_t));

    column_family->db = db;
  }

  auto handles = new rocksdb_column_family_t *[len];

  uv_loop_t *loop;
  err = js_get_env_loop(env, &loop);
  assert(err == 0);

  js_arraybuffer_t handle;

  rocksdb_native_open_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  db->options.lock = lock;

  err = rocksdb_open(loop, &db->handle, &req->handle, path.c_str(), &db->options, column_families, handles, len, nullptr, rocksdb_native__on_open);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    if (lock >= 0) {
      uv_fs_t fs;
      err = uv_fs_close(NULL, &fs, lock, NULL);
      assert(err == 0);
    }

    throw js_pending_exception;
  }

  err = js_create_reference(env, self, db->ctx);
  assert(err == 0);

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_open, req->on_open);
  assert(err == 0);

  err = js_create_reference(env, column_families_array, req->column_families);
  assert(err == 0);

  err = js_add_deferred_teardown_callback(env, rocksdb_native__on_teardown, db, &db->teardown);
  assert(err == 0);

  return handle;
}

static js_arraybuffer_t
rocksdb_native_close(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_receiver_t ctx,
  rocksdb_native_on_close_t on_close
) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_close_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  err = rocksdb_close(&db->handle, &req->handle, rocksdb_native__on_idle, rocksdb_native__on_close);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_close, req->on_close);
  assert(err == 0);

  db->closing = true;

  return handle;
}

static void
rocksdb_native__on_suspend(rocksdb_suspend_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_suspend_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_suspend_t cb;
  err = js_get_reference_value(env, req->on_suspend, cb);
  assert(err == 0);

  req->on_suspend.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_suspend_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_suspend(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_receiver_t ctx,
  rocksdb_native_on_suspend_t on_suspend
) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_suspend_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  err = rocksdb_suspend(&db->handle, &req->handle, rocksdb_native__on_suspend);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_suspend, req->on_suspend);
  assert(err == 0);

  return handle;
}

static void
rocksdb_native__on_resume(rocksdb_resume_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_resume_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_resume_t cb;
  err = js_get_reference_value(env, req->on_resume, cb);
  assert(err == 0);

  req->on_resume.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_resume_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_resume(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_receiver_t ctx,
  rocksdb_native_on_resume_t on_resume
) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_resume_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  err = rocksdb_resume(&db->handle, &req->handle, rocksdb_native__on_resume);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_resume, req->on_resume);
  assert(err == 0);

  return handle;
}

static js_arraybuffer_t
rocksdb_native_column_family_init(
  js_env_t *env,
  std::string name,
  bool enable_blob_files,
  uint64_t min_blob_size,
  uint64_t blob_file_size,
  bool enable_blob_garbage_collection,
  uint64_t block_size,
  bool cache_index_and_filter_blocks,
  uint32_t format_version,
  bool optimize_filters_for_memory,
  bool no_block_cache,
  uint32_t filter_policy_type,
  double bits_per_key,
  int32_t bloom_before_level,
  uint32_t top_level_index_pinning_tier,
  uint32_t partition_pinning_tier,
  uint32_t unpartitioned_pinning_tier,
  bool optimize_filters_for_hits,
  int32_t num_levels,
  int32_t max_write_buffer_number,
  double blob_garbage_collection_age_cutoff,
  double blob_garbage_collection_force_threshold
) {
  int err;

  rocksdb_filter_policy_t filter_policy = {rocksdb_filter_policy_type_t(filter_policy_type)};

  switch (filter_policy_type) {
  case rocksdb_bloom_filter_policy:
    filter_policy.bloom = (rocksdb_bloom_filter_options_t) {
      0,
      bits_per_key
    };
    break;

  case rocksdb_ribbon_filter_policy:
    filter_policy.ribbon = (rocksdb_ribbon_filter_options_t) {
      0,
      bits_per_key,
      bloom_before_level,
    };
    break;
  }

  uv_loop_t *loop;
  err = js_get_env_loop(env, &loop);
  assert(err == 0);

  js_arraybuffer_t handle;

  rocksdb_native_column_family_t *column_family;
  err = js_create_arraybuffer(env, column_family, handle);
  assert(err == 0);

  column_family->db = nullptr;
  column_family->handle = nullptr;

  new (&column_family->name) std::string(std::move(name));

  column_family->descriptor = (rocksdb_column_family_descriptor_t) {
    column_family->name.c_str(),
    {
      5,
      rocksdb_level_compaction,
      enable_blob_files,
      min_blob_size,
      blob_file_size,
      enable_blob_garbage_collection,
      block_size,
      cache_index_and_filter_blocks,
      format_version,
      optimize_filters_for_memory,
      no_block_cache,
      filter_policy,
      rocksdb_pinning_tier_t(top_level_index_pinning_tier),
      rocksdb_pinning_tier_t(partition_pinning_tier),
      rocksdb_pinning_tier_t(unpartitioned_pinning_tier),
      optimize_filters_for_hits,
      num_levels,
      max_write_buffer_number,
      blob_garbage_collection_age_cutoff,
      blob_garbage_collection_force_threshold,
    }
  };

  return handle;
}

static void
rocksdb_native_column_family_destroy(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_column_family_t, 1> column_family
) {
  int err;

  if (column_family->handle == nullptr) return;

  err = rocksdb_column_family_destroy(&column_family->db->handle, column_family->handle);
  assert(err == 0);

  column_family->db->column_families.erase(column_family);

  column_family->handle = nullptr;

  column_family->name.~basic_string();

  column_family->ctx.reset();
}

static js_arraybuffer_t
rocksdb_native_iterator_init(js_env_t *env) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_iterator_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->closing = false;
  req->exiting = false;
  req->handle.data = req;

  return handle;
}

static js_arraybuffer_t
rocksdb_native_iterator_buffer(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_iterator_t, 1> req,
  uint32_t capacity
) {
  int err;

  js_arraybuffer_t handle;

  uint8_t *data;
  err = js_create_arraybuffer(env, 2 * capacity * sizeof(rocksdb_slice_t), data, handle);
  assert(err == 0);

  size_t offset = 0;

  req->keys = reinterpret_cast<rocksdb_slice_t *>(&data[offset]);

  offset += capacity * sizeof(rocksdb_slice_t);

  req->values = reinterpret_cast<rocksdb_slice_t *>(&data[offset]);

  return handle;
}

static void
rocksdb_native__on_iterator_close(rocksdb_iterator_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_iterator_t *>(handle->data);

  auto env = req->env;

  auto teardown = req->teardown;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_iterator_close_t cb;
  err = js_get_reference_value(env, req->on_close, cb);
  assert(err == 0);

  req->on_open.reset();
  req->on_close.reset();
  req->on_read.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_iterator_cleanup(&req->handle);

  if (!req->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);

  err = js_finish_deferred_teardown_callback(teardown);
  assert(err == 0);
}

static void
rocksdb_native__on_iterator_open(rocksdb_iterator_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_iterator_t *>(handle->data);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_iterator_open_t cb;
  err = js_get_reference_value(env, req->on_open, cb);
  assert(err == 0);

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_iterator_cleanup(&req->handle);

  if (!req->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static void
rocksdb_native__on_iterator_teardown(js_deferred_teardown_t *teardown, void *data) {
  int err;

  auto req = reinterpret_cast<rocksdb_native_iterator_t *>(data);

  req->exiting = true;

  if (req->closing) return;

  err = rocksdb_iterator_close(&req->handle, rocksdb_native__on_iterator_close);
  assert(err == 0);
}

static void
rocksdb_native_iterator_open(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_arraybuffer_span_of_t<rocksdb_native_iterator_t, 1> req,
  js_arraybuffer_span_of_t<rocksdb_native_column_family_t, 1> column_family,
  js_typedarray_t<> gt,
  js_typedarray_t<> gte,
  js_typedarray_t<> lt,
  js_typedarray_t<> lte,
  bool reverse,
  bool keys_only,
  std::optional<js_arraybuffer_span_of_t<rocksdb_native_snapshot_t, 1>> snapshot,
  js_receiver_t ctx,
  rocksdb_native_on_iterator_open_t on_open,
  rocksdb_native_on_iterator_close_t on_close,
  rocksdb_native_on_iterator_read_t on_read
) {
  int err;

  rocksdb_range_t range;

  err = js_get_typedarray_info(env, gt, range.gt.data, range.gt.len);
  assert(err == 0);

  err = js_get_typedarray_info(env, gte, range.gte.data, range.gte.len);
  assert(err == 0);

  err = js_get_typedarray_info(env, lt, range.lt.data, range.lt.len);
  assert(err == 0);

  err = js_get_typedarray_info(env, lte, range.lte.data, range.lte.len);
  assert(err == 0);

  rocksdb_iterator_options_t options = {
    .version = 0,
    .reverse = reverse,
    .keys_only = keys_only
  };

  if (snapshot) options.snapshot = &snapshot.value()->handle;

  err = rocksdb_iterator_open(&db->handle, &req->handle, column_family->handle, range, &options, rocksdb_native__on_iterator_open);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_open, req->on_open);
  assert(err == 0);

  err = js_create_reference(env, on_close, req->on_close);
  assert(err == 0);

  err = js_create_reference(env, on_read, req->on_read);
  assert(err == 0);

  err = js_add_deferred_teardown_callback(env, rocksdb_native__on_iterator_teardown, req, &req->teardown);
  assert(err == 0);
}

static void
rocksdb_native_iterator_close(js_env_t *env, js_arraybuffer_span_of_t<rocksdb_native_iterator_t, 1> req) {
  int err;

  err = rocksdb_iterator_close(&req->handle, rocksdb_native__on_iterator_close);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  req->closing = true;
}

static void
rocksdb_native__on_iterator_read(rocksdb_iterator_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_iterator_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto len = req->handle.len;

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_iterator_read_t cb;
  err = js_get_reference_value(env, req->on_read, cb);
  assert(err == 0);

  std::optional<js_object_t> error;

  js_array_t keys;
  err = js_create_array(env, len, keys);
  assert(err == 0);

  js_array_t values;
  err = js_create_array(env, len, values);
  assert(err == 0);

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  } else {
    for (size_t i = 0; i < len; i++) {
      rocksdb_slice_t *key = &req->keys[i];
      rocksdb_slice_t *value = &req->values[i];

      if (req->exiting) {
        rocksdb_slice_destroy(key);
        rocksdb_slice_destroy(value);
      } else {
        js_arraybuffer_t result;

        err = rocksdb_native__try_create_external_arraybuffer(env, const_cast<char *>(key->data), key->len, result);
        assert(err == 0);

        err = js_set_element(env, keys, i, result);
        assert(err == 0);

        err = rocksdb_native__try_create_external_arraybuffer(env, const_cast<char *>(value->data), value->len, result);
        assert(err == 0);

        err = js_set_element(env, values, i, result);
        assert(err == 0);
      }
    }
  }

  rocksdb_iterator_cleanup(&req->handle);

  if (!req->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error, keys, values);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static void
rocksdb_native_iterator_read(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_iterator_t, 1> req,
  uint32_t capacity
) {
  int err;

  err = rocksdb_iterator_read(&req->handle, req->keys, req->values, capacity, rocksdb_native__on_iterator_read);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }
}

static js_arraybuffer_t
rocksdb_native_read_init(js_env_t *env) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_read_batch_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  return handle;
}

static js_arraybuffer_t
rocksdb_native_read_buffer(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_read_batch_t, 1> req,
  uint32_t capacity
) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_read_t *reads;
  err = js_create_arraybuffer(env, capacity, reads, handle);
  assert(err == 0);

  req->capacity = capacity;
  req->reads = reads;

  return handle;
}

static void
rocksdb_native__on_read(rocksdb_read_batch_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_read_batch_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto len = req->handle.len;

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_read_t cb;
  err = js_get_reference_value(env, req->on_read, cb);
  assert(err == 0);

  req->on_read.reset();
  req->ctx.reset();

  js_array_t errors;
  err = js_create_array(env, len, errors);
  assert(err == 0);

  js_array_t values;
  err = js_create_array(env, len, values);
  assert(err == 0);

  for (size_t i = 0; i < len; i++) {
    char *error = req->handle.errors[i];
    int status = req->handle.statuses[i];

    if (error) {
      js_object_t result;

      err = js_create_error(env, uv_err_name(status), error, result);
      assert(err == 0);

      err = js_set_element(env, errors, i, result);
      assert(err == 0);
    } else {
      rocksdb_slice_t *value = &req->reads[i].value;

      if (db->exiting) rocksdb_slice_destroy(value);
      else {
        js_arraybuffer_t result;

        if (value->data == nullptr && value->len == size_t(-1)) {
          err = js_get_null(env, static_cast<js_value_t **>(result));
          assert(err == 0);
        } else {
          err = rocksdb_native__try_create_external_arraybuffer(env, const_cast<char *>(value->data), value->len, result);
          assert(err == 0);
        }

        err = js_set_element(env, values, i, result);
        assert(err == 0);
      }
    }
  }

  rocksdb_read_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, errors, values);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static void
rocksdb_native_read(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_arraybuffer_span_of_t<rocksdb_native_read_batch_t, 1> req,
  js_array_t operations,
  std::optional<js_arraybuffer_span_of_t<rocksdb_native_snapshot_t, 1>> snapshot,
  bool async_io,
  bool fill_cache,
  js_receiver_t ctx,
  rocksdb_native_on_read_t on_read
) {
  int err;

  std::vector<js_object_t> elements;
  err = js_get_array_elements(env, operations, elements);
  assert(err == 0);

  const auto len = elements.size();

  for (uint32_t i = 0; i < len; i++) {
    js_object_t read = elements[i];

    rocksdb_read_type_t type;
    err = js_get_property(env, read, "type", reinterpret_cast<uint32_t &>(type));
    assert(err == 0);

    req->reads[i].type = type;

    js_arraybuffer_t column_family_property;
    err = js_get_property(env, read, "columnFamily", column_family_property);
    assert(err == 0);

    rocksdb_native_column_family_t *column_family;
    err = js_get_arraybuffer_info(env, column_family_property, column_family);
    assert(err == 0);

    req->reads[i].column_family = column_family->handle;

    switch (type) {
    case rocksdb_get: {
      js_typedarray_t property;

      rocksdb_slice_t *key = &req->reads[i].key;

      err = js_get_property(env, read, "key", property);
      assert(err == 0);

      err = js_get_typedarray_info(env, property, key->data, key->len);
      assert(err == 0);
      break;
    }
    }
  }

  rocksdb_read_options_t options = {
    .version = 1,
    .async_io = async_io,
    .fill_cache = fill_cache
  };

  if (snapshot) options.snapshot = &snapshot.value()->handle;

  err = rocksdb_read(&db->handle, &req->handle, req->reads, len, &options, rocksdb_native__on_read);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_read, req->on_read);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_write_init(js_env_t *env) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_write_batch_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  return handle;
}

static js_arraybuffer_t
rocksdb_native_write_buffer(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_write_batch_t, 1> req,
  uint32_t capacity
) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_write_t *writes;
  err = js_create_arraybuffer(env, capacity, writes, handle);
  assert(err == 0);

  req->capacity = capacity;
  req->writes = writes;

  return handle;
}

static void
rocksdb_native__on_write(rocksdb_write_batch_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_write_batch_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_write_t cb;
  err = js_get_reference_value(env, req->on_write, cb);
  assert(err == 0);

  req->on_write.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_write_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static void
rocksdb_native_write(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_arraybuffer_span_of_t<rocksdb_native_write_batch_t, 1> req,
  js_array_t operations,
  js_receiver_t ctx,
  rocksdb_native_on_write_t on_write
) {
  int err;

  std::vector<js_object_t> elements;
  err = js_get_array_elements(env, operations, elements);
  assert(err == 0);

  const auto len = elements.size();

  for (uint32_t i = 0; i < len; i++) {
    js_object_t write = elements[i];

    rocksdb_write_type_t type;
    err = js_get_property(env, write, "type", reinterpret_cast<uint32_t &>(type));
    assert(err == 0);

    req->writes[i].type = type;

    js_arraybuffer_t column_family_property;
    err = js_get_property(env, write, "columnFamily", column_family_property);
    assert(err == 0);

    rocksdb_native_column_family_t *column_family;
    err = js_get_arraybuffer_info(env, column_family_property, column_family);
    assert(err == 0);

    req->writes[i].column_family = column_family->handle;

    js_typedarray_t property;

    switch (type) {
    case rocksdb_put: {
      rocksdb_slice_t *key = &req->writes[i].key;

      err = js_get_property(env, write, "key", property);
      assert(err == 0);

      err = js_get_typedarray_info(env, property, key->data, key->len);
      assert(err == 0);

      rocksdb_slice_t *value = &req->writes[i].value;

      err = js_get_property(env, write, "value", property);
      assert(err == 0);

      err = js_get_typedarray_info(env, property, value->data, value->len);
      assert(err == 0);
      break;
    }

    case rocksdb_delete: {
      rocksdb_slice_t *key = &req->writes[i].key;

      err = js_get_property(env, write, "key", property);
      assert(err == 0);

      err = js_get_typedarray_info(env, property, key->data, key->len);
      assert(err == 0);
      break;
    }

    case rocksdb_delete_range: {
      rocksdb_slice_t *start = &req->writes[i].start;

      err = js_get_property(env, write, "start", property);
      assert(err == 0);

      err = js_get_typedarray_info(env, property, start->data, start->len);
      assert(err == 0);

      rocksdb_slice_t *end = &req->writes[i].end;

      err = js_get_property(env, write, "end", property);
      assert(err == 0);

      err = js_get_typedarray_info(env, property, end->data, end->len);
      assert(err == 0);
      break;
    }
    }
  }

  err = rocksdb_write(&db->handle, &req->handle, req->writes, len, nullptr, rocksdb_native__on_write);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_write, req->on_write);
  assert(err == 0);
}

static void
rocksdb_native__on_flush(rocksdb_flush_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_flush_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_flush_t cb;
  err = js_get_reference_value(env, req->on_flush, cb);
  assert(err == 0);

  req->on_flush.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_flush_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_flush(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_arraybuffer_span_of_t<rocksdb_native_column_family_t, 1> column_family,
  js_receiver_t ctx,
  rocksdb_native_on_flush_t on_flush
) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_flush_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  err = rocksdb_flush(&db->handle, &req->handle, column_family->handle, nullptr, rocksdb_native__on_flush);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_flush, req->on_flush);
  assert(err == 0);

  return handle;
}

static void
rocksdb_native__on_compact_range(rocksdb_compact_range_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_compact_range_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_compact_range_t cb;
  err = js_get_reference_value(env, req->on_compact_range, cb);
  assert(err == 0);

  req->on_compact_range.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_compact_range_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_compact_range(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_arraybuffer_span_of_t<rocksdb_native_column_family_t, 1> column_family,
  js_typedarray_t<> start,
  js_typedarray_t<> end,
  bool exclusive,
  uint32_t blob_garbage_collection_policy,
  double blob_garbage_collection_age_cutoff,
  uint32_t bottommost_level_compaction,
  js_receiver_t ctx,
  rocksdb_native_on_compact_range_t on_compact_range
) {
  int err;

  rocksdb_slice_t start_slice;
  err = js_get_typedarray_info(env, start, start_slice.data, start_slice.len);
  assert(err == 0);

  rocksdb_slice_t end_slice;
  err = js_get_typedarray_info(env, end, end_slice.data, end_slice.len);
  assert(err == 0);

  js_arraybuffer_t handle;

  rocksdb_native_compact_range_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  rocksdb_compact_range_options_t options;
  rocksdb_compact_range_options_init(&options, 1);

  options.exclusive_manual_compaction = exclusive;
  options.blob_garbage_collection_policy = rocksdb_blob_garbage_collection_policy_t(blob_garbage_collection_policy);
  options.blob_garbage_collection_age_cutoff = blob_garbage_collection_age_cutoff;
  options.bottommost_level_compaction = rocksdb_bottommost_level_compaction_t(bottommost_level_compaction);

  err = rocksdb_compact_range(&db->handle, &req->handle, column_family->handle, start_slice, end_slice, &options, rocksdb_native__on_compact_range);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_compact_range, req->on_compact_range);
  assert(err == 0);

  return handle;
}

static void
rocksdb_native__on_compact(rocksdb_compact_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_compact_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_compact_t cb;
  err = js_get_reference_value(env, req->on_compact, cb);
  assert(err == 0);

  req->on_compact.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_compact_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_compact(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_arraybuffer_span_of_t<rocksdb_native_column_family_t, 1> column_family,
  bool exclusive,
  uint32_t blob_garbage_collection_policy,
  double blob_garbage_collection_age_cutoff,
  uint32_t bottommost_level_compaction,
  js_receiver_t ctx,
  rocksdb_native_on_compact_t on_compact
) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_compact_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  rocksdb_compact_range_options_t options;
  rocksdb_compact_range_options_init(&options, 1);

  options.exclusive_manual_compaction = exclusive;
  options.blob_garbage_collection_policy = rocksdb_blob_garbage_collection_policy_t(blob_garbage_collection_policy);
  options.blob_garbage_collection_age_cutoff = blob_garbage_collection_age_cutoff;
  options.bottommost_level_compaction = rocksdb_bottommost_level_compaction_t(bottommost_level_compaction);

  err = rocksdb_compact(&db->handle, &req->handle, column_family->handle, &options, rocksdb_native__on_compact);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_compact, req->on_compact);
  assert(err == 0);

  return handle;
}

static void
rocksdb_native__on_approximate_size(rocksdb_approximate_size_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_approximate_size_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_approximate_size_t cb;
  err = js_get_reference_value(env, req->on_approximate_size, cb);
  assert(err == 0);

  req->on_approximate_size.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  rocksdb_approximate_size_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error, req->handle.result);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_approximate_size(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_arraybuffer_span_of_t<rocksdb_native_column_family_t, 1> column_family,
  js_typedarray_t<> start,
  js_typedarray_t<> end,
  bool include_memtables,
  bool include_files,
  double error_margin,
  js_receiver_t ctx,
  rocksdb_native_on_approximate_size_t on_approximate_size
) {
  int err;

  rocksdb_slice_t start_slice;
  err = js_get_typedarray_info(env, start, start_slice.data, start_slice.len);
  assert(err == 0);

  rocksdb_slice_t end_slice;
  err = js_get_typedarray_info(env, end, end_slice.data, end_slice.len);
  assert(err == 0);

  js_arraybuffer_t handle;

  rocksdb_native_approximate_size_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  rocksdb_approximate_size_options_t options;
  rocksdb_approximate_size_options_init(&options, 0);

  options.include_memtables = include_memtables;
  options.include_files = include_files;
  options.files_size_error_margin = error_margin;

  err = rocksdb_approximate_size(&db->handle, &req->handle, column_family->handle, start_slice, end_slice, &options, rocksdb_native__on_approximate_size);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_approximate_size, req->on_approximate_size);
  assert(err == 0);

  return handle;
}

static void
rocksdb_native__on_current_wal_file(rocksdb_current_wal_file_t *handle, int status) {
  int err;

  assert(status == 0);

  auto req = reinterpret_cast<rocksdb_native_current_wal_file_t *>(handle->data);

  auto db = reinterpret_cast<rocksdb_native_t *>(req->handle.req.db);

  auto env = req->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_receiver_t ctx;
  err = js_get_reference_value(env, req->ctx, ctx);
  assert(err == 0);

  rocksdb_native_on_current_wal_file_t cb;
  err = js_get_reference_value(env, req->on_current_wal_file, cb);
  assert(err == 0);

  req->on_current_wal_file.reset();
  req->ctx.reset();

  std::optional<js_object_t> error;

  if (req->handle.error) {
    err = js_create_error(env, uv_err_name(req->handle.status), req->handle.error, error.emplace());
    assert(err == 0);
  }

  auto &file = req->handle.result;

  rocksdb_current_wal_file_cleanup(&req->handle);

  if (!db->exiting) {
    err = js_call_function_with_checkpoint(env, cb, ctx, error, file.path, file.number, uint32_t(file.type), file.start_sequence, file.size);
    (void) err;
  }

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static js_arraybuffer_t
rocksdb_native_current_wal_file(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  js_receiver_t ctx,
  rocksdb_native_on_current_wal_file_t on_current_wal_file
) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_current_wal_file_t *req;
  err = js_create_arraybuffer(env, req, handle);
  assert(err == 0);

  req->env = env;
  req->handle.data = req;

  err = rocksdb_current_wal_file(&db->handle, &req->handle, rocksdb_native__on_current_wal_file);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, ctx, req->ctx);
  assert(err == 0);

  err = js_create_reference(env, on_current_wal_file, req->on_current_wal_file);
  assert(err == 0);

  return handle;
}

static uint32_t
rocksdb_native_stats_level_get(js_env_t *env, js_arraybuffer_span_of_t<rocksdb_native_t, 1> db) {
  int err;

  rocksdb_stats_level_t level;
  err = rocksdb_stats_level_get(&db->handle, &level);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  return uint32_t(level);
}

static void
rocksdb_native_stats_level_set(js_env_t *env, js_arraybuffer_span_of_t<rocksdb_native_t, 1> db, uint32_t level) {
  int err;

  err = rocksdb_stats_level_set(&db->handle, rocksdb_stats_level_t(level));

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }
}

static std::optional<std::string>
rocksdb_native_property_get(
  js_env_t *env,
  js_arraybuffer_span_of_t<rocksdb_native_t, 1> db,
  std::string name
) {
  rocksdb_slice_t value;
  int err = rocksdb_property_get(&db->handle, name.c_str(), &value);

  if (err == UV_ENOENT) return std::nullopt;
  assert(err == 0);

  std::string result(value.data, value.len);
  rocksdb_slice_destroy(&value);

  return result;
}

static js_arraybuffer_t
rocksdb_native_snapshot_create(js_env_t *env, js_arraybuffer_span_of_t<rocksdb_native_t, 1> db) {
  int err;

  js_arraybuffer_t handle;

  rocksdb_native_snapshot_t *snapshot;
  err = js_create_arraybuffer(env, snapshot, handle);
  assert(err == 0);

  snapshot->db = db;

  err = rocksdb_snapshot_create(&db->handle, &snapshot->handle);

  if (err < 0) {
    err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
    assert(err == 0);

    throw js_pending_exception;
  }

  err = js_create_reference(env, handle, snapshot->ctx);
  assert(err == 0);

  db->snapshots.insert(snapshot);

  return handle;
}

static void
rocksdb_native_snapshot_destroy(js_env_t *env, js_arraybuffer_span_of_t<rocksdb_native_snapshot_t, 1> snapshot) {
  int err;

  rocksdb_snapshot_destroy(&snapshot->handle);

  snapshot->db->snapshots.erase(snapshot);

  snapshot->ctx.reset();
}

static js_value_t *
rocksdb_native_exports(js_env_t *env, js_value_t *exports) {
  int err;

#define V(name, fn) \
  err = js_set_property<fn>(env, exports, name); \
  assert(err == 0);

  V("init", rocksdb_native_init)
  V("open", rocksdb_native_open)
  V("close", rocksdb_native_close)
  V("suspend", rocksdb_native_suspend)
  V("resume", rocksdb_native_resume)

  V("columnFamilyInit", rocksdb_native_column_family_init)
  V("columnFamilyDestroy", rocksdb_native_column_family_destroy)

  V("readInit", rocksdb_native_read_init)
  V("readBuffer", rocksdb_native_read_buffer)
  V("read", rocksdb_native_read)

  V("writeInit", rocksdb_native_write_init)
  V("writeBuffer", rocksdb_native_write_buffer)
  V("write", rocksdb_native_write)

  V("iteratorInit", rocksdb_native_iterator_init)
  V("iteratorBuffer", rocksdb_native_iterator_buffer)
  V("iteratorOpen", rocksdb_native_iterator_open)
  V("iteratorClose", rocksdb_native_iterator_close)
  V("iteratorRead", rocksdb_native_iterator_read)

  V("statsLevelGet", rocksdb_native_stats_level_get)
  V("statsLevelSet", rocksdb_native_stats_level_set)

  V("flush", rocksdb_native_flush)
  V("compact", rocksdb_native_compact)
  V("compactRange", rocksdb_native_compact_range)
  V("approximateSize", rocksdb_native_approximate_size)
  V("currentWalFile", rocksdb_native_current_wal_file)
  V("propertyGet", rocksdb_native_property_get)

  V("snapshotCreate", rocksdb_native_snapshot_create)
  V("snapshotDestroy", rocksdb_native_snapshot_destroy)
#undef V

#define V(name, n) \
  { \
    js_value_t *val; \
    err = js_create_uint32(env, n, &val); \
    assert(err == 0); \
    err = js_set_named_property(env, exports, name, val); \
    assert(err == 0); \
  }

  V("GET", rocksdb_get)
  V("PUT", rocksdb_put)
  V("DELETE", rocksdb_delete)
  V("DELETE_RANGE", rocksdb_delete_range)
#undef V

  return exports;
}

BARE_MODULE(rocksdb_native, rocksdb_native_exports)