apache-datasketches-sys 0.2.1

Raw cxx bridge to Apache DataSketches C++ (do not use directly; see apache-datasketches)
Documentation
#include "array_of_doubles_sketch_shim.h"
#include "array_of_doubles_compact_shim.h"
#include "array_of_doubles_sketch.rs.h" // generated by cxx from src/array_of_doubles_sketch.rs; provides the full TupleResizeFactor enum definition

namespace apache_datasketches_rs {

datasketches::resize_factor to_cpp_tuple_resize_factor(TupleResizeFactor rf) {
  switch (rf) {
    case TupleResizeFactor::X1: return datasketches::resize_factor::X1;
    case TupleResizeFactor::X2: return datasketches::resize_factor::X2;
    case TupleResizeFactor::X4: return datasketches::resize_factor::X4;
    case TupleResizeFactor::X8: return datasketches::resize_factor::X8;
    default: throw std::invalid_argument("unknown TupleResizeFactor");
  }
}

namespace {

// The builder's single constructor argument is the update policy, which is
// what carries num_values. lg_k/resize_factor/p validation is inherited from
// theta_base_builder and throws std::invalid_argument, which cxx turns into
// Result::Err on the Rust side.
datasketches::update_array_of_doubles_sketch build_sketch(uint8_t lg_k, TupleResizeFactor rf, float p, uint8_t num_values) {
  datasketches::update_array_of_doubles_sketch::builder builder{
      datasketches::default_array_of_doubles_update_policy(num_values)};
  builder.set_lg_k(lg_k);
  builder.set_resize_factor(to_cpp_tuple_resize_factor(rf));
  builder.set_p(p);
  return builder.build();
}

// Upstream's default_array_tuple_update_policy indexes the supplied value
// blindly for i in [0, num_values), with no bounds check of its own.
//
// This is load-bearing, not defence-in-depth. The update_* methods below hand
// upstream a bare `const double*` (see the note there), which carries no
// length, so this check is the *only* thing standing between a short slice and
// an out-of-bounds read. It must stay, and it must run before the update call.
//
// Note what the throw does here: the update_* methods are declared in
// src/array_of_doubles_sketch.rs *without* `Result`, so cxx's generated
// trampoline is noexcept and this exception terminates the process rather than
// surfacing as an Err. That is deliberate -- trading a wild read for a loud
// abort -- but it means this is not a graceful error path for direct sys-crate
// callers. The safe wrapper in `apache-datasketches` checks the length itself
// and returns InvalidConfig, so ordinary users never reach this.
void check_values_len(const datasketches::update_array_of_doubles_sketch& sketch, rust::Slice<const double> values) {
  if (values.size() != sketch.get_num_values()) {
    throw std::invalid_argument("values length does not match num_values");
  }
}

} // namespace

ArrayOfDoublesSketchShim::ArrayOfDoublesSketchShim(uint8_t lg_k, TupleResizeFactor rf, float p, uint8_t num_values)
  : sketch_(build_sketch(lg_k, rf, p, num_values)) {}

// Every update_* below passes `values.data()` -- a bare `const double*` --
// rather than copying the slice into a std::vector.
//
// Upstream's update policy is `template<typename InputArray> void
// update(Array&, const InputArray&)`, which only ever does `array[i] +=
// update[i]` over [0, num_values); its own comment blesses `double*`
// explicitly. `update_tuple_sketch::update` takes the value as a forwarding
// reference and perfect-forwards it to the policy, and update_array_tuple_sketch
// does not narrow that signature, so nothing in the chain requires an actual
// container.
//
// This matters because the copy was not free: it heap-allocated on *every*
// call, while upstream's update screens the key first
// (`if (hash == 0) return;`) and never reads the values for a key that theta
// rejects. Native C++ allocates nothing per update; so do we now.
//
// The cost is that length information no longer reaches upstream -- hence
// check_values_len above, which is why it is mandatory rather than advisory.
void ArrayOfDoublesSketchShim::update_u64(uint64_t key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_i64(int64_t key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_u32(uint32_t key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_i32(int32_t key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_u16(uint16_t key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_i16(int16_t key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_u8(uint8_t key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_i8(int8_t key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_f64(double key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key, values.data());
}
void ArrayOfDoublesSketchShim::update_str(rust::Str key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(std::string(key), values.data());
}
void ArrayOfDoublesSketchShim::update_bytes(rust::Slice<const uint8_t> key, rust::Slice<const double> values) {
  check_values_len(sketch_, values);
  sketch_.update(key.data(), key.size(), values.data());
}

void ArrayOfDoublesSketchShim::trim() { sketch_.trim(); }
void ArrayOfDoublesSketchShim::reset() { sketch_.reset(); }

double ArrayOfDoublesSketchShim::get_estimate() const { return sketch_.get_estimate(); }
double ArrayOfDoublesSketchShim::get_lower_bound(uint8_t num_std_dev) const {
  return sketch_.get_lower_bound(num_std_dev);
}
double ArrayOfDoublesSketchShim::get_upper_bound(uint8_t num_std_dev) const {
  return sketch_.get_upper_bound(num_std_dev);
}
bool ArrayOfDoublesSketchShim::is_empty() const { return sketch_.is_empty(); }
bool ArrayOfDoublesSketchShim::is_estimation_mode() const { return sketch_.is_estimation_mode(); }
bool ArrayOfDoublesSketchShim::is_ordered() const { return sketch_.is_ordered(); }
double ArrayOfDoublesSketchShim::get_theta() const { return sketch_.get_theta(); }
uint32_t ArrayOfDoublesSketchShim::get_num_retained() const { return sketch_.get_num_retained(); }
uint8_t ArrayOfDoublesSketchShim::get_num_values() const { return sketch_.get_num_values(); }

rust::Vec<uint64_t> ArrayOfDoublesSketchShim::entry_hashes() const {
  rust::Vec<uint64_t> out;
  for (const auto& entry : sketch_) out.push_back(entry.first);
  return out;
}

rust::Vec<double> ArrayOfDoublesSketchShim::entry_values() const {
  rust::Vec<double> out;
  for (const auto& entry : sketch_) {
    for (uint8_t i = 0; i < entry.second.size(); ++i) out.push_back(entry.second[i]);
  }
  return out;
}

std::unique_ptr<ArrayOfDoublesSketchShim> new_array_of_doubles_sketch(uint8_t lg_k, TupleResizeFactor rf, float p, uint8_t num_values) {
  return std::make_unique<ArrayOfDoublesSketchShim>(lg_k, rf, p, num_values);
}

std::unique_ptr<CompactArrayOfDoublesSketchShim> ArrayOfDoublesSketchShim::compact(bool ordered) const {
  return array_of_doubles_sketch_compact(*this, ordered);
}

} // namespace apache_datasketches_rs