#include "semq_internal.h"
#include <stdlib.h>
static const char RESERVED_ENCODER[] = "encoder";
static const char RESERVED_REVISION[] = "encoder_revision";
int semqi_mul_le(uint64_t a, uint64_t b, uint64_t c, uint64_t d) {
const uint64_t a0 = a & 0xFFFFFFFFu, a1 = a >> 32, b0 = b & 0xFFFFFFFFu, b1 = b >> 32;
const uint64_t c0 = c & 0xFFFFFFFFu, c1 = c >> 32, d0 = d & 0xFFFFFFFFu, d1 = d >> 32;
uint64_t lo, hi, lo2, hi2;
{
const uint64_t p00 = a0 * b0, p01 = a0 * b1, p10 = a1 * b0, p11 = a1 * b1;
const uint64_t mid = (p00 >> 32) + (p01 & 0xFFFFFFFFu) + (p10 & 0xFFFFFFFFu);
lo = (p00 & 0xFFFFFFFFu) | (mid << 32);
hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32);
}
{
const uint64_t p00 = c0 * d0, p01 = c0 * d1, p10 = c1 * d0, p11 = c1 * d1;
const uint64_t mid = (p00 >> 32) + (p01 & 0xFFFFFFFFu) + (p10 & 0xFFFFFFFFu);
lo2 = (p00 & 0xFFFFFFFFu) | (mid << 32);
hi2 = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32);
}
if (hi != hi2) return hi < hi2;
return lo <= lo2;
}
static int cmp_u64(const void* a, const void* b) {
const uint64_t x = *(const uint64_t*)a, y = *(const uint64_t*)b;
return (x < y) ? -1 : (x > y) ? 1 : 0;
}
static semq_status_t p99(const uint64_t* values, uint64_t m, uint64_t* out, semq_error_t* err) {
if (m == 0u) { *out = 0u; return SEMQ_OK; }
uint64_t* tmp = (uint64_t*)semqi_alloc(m * sizeof(uint64_t));
if (tmp == NULL) return SEMQI_NOMEM(err);
memcpy(tmp, values, (size_t)m * sizeof(uint64_t));
qsort(tmp, (size_t)m, sizeof(uint64_t), cmp_u64);
const uint64_t k = m - m / 100u;
*out = tmp[k - 1u];
semqi_free(tmp);
return SEMQ_OK;
}
static int is_reserved(const semq_manifest_change_t* m) {
return (m->key_len == sizeof(RESERVED_ENCODER) - 1u && memcmp(m->key, RESERVED_ENCODER, m->key_len) == 0)
|| (m->key_len == sizeof(RESERVED_REVISION) - 1u && memcmp(m->key, RESERVED_REVISION, m->key_len) == 0);
}
static int has_reserved_change(const semq_diff_t* d) {
for (uint32_t m = 0u; m < d->n_mchanges; m++) {
if (is_reserved(&d->mchanges[m])) return 1;
}
return 0;
}
static const uint8_t* reference_state_id(const semq_diff_t* d) {
return d->ref->footer + SEMQ_DIGEST_BYTES;
}
semq_status_t semqi_floor_create(const semq_config_t* config, uint32_t id_kind,
const uint8_t reference_id[32], uint64_t nulls,
uint64_t changed_rows, uint64_t total_rows,
uint64_t hamming, uint64_t max_hamming, uint64_t distinct_nulls,
semq_floor_t** out, semq_error_t* err) {
if (out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "output is NULL");
*out = NULL;
if (config == NULL || reference_id == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "NULL argument");
semq_status_t s = semq_config_validate(config, err);
if (s != SEMQ_OK) return s;
if (id_kind != (uint32_t)SEMQ_ID_U64 && id_kind != (uint32_t)SEMQ_ID_UTF8) {
return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.id_kind must be u64 or utf8");
}
if (nulls == 0u) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.nulls must be > 0");
if (total_rows == 0u) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.total_rows must be > 0");
if (changed_rows > total_rows) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.changed_rows exceeds total_rows");
if (hamming > (uint64_t)semq_config_units_per_row(config)) {
return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.hamming exceeds units_per_row");
}
if (max_hamming != SEMQ_NONE) {
if (max_hamming < hamming) {
return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.max_hamming is below floor.hamming");
}
if (max_hamming > (uint64_t)semq_config_units_per_row(config)) {
return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.max_hamming exceeds units_per_row");
}
}
if (distinct_nulls != SEMQ_NONE) {
if (distinct_nulls == 0u) {
return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.distinct_nulls must be at least 1");
}
if (distinct_nulls > nulls) {
return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "floor.distinct_nulls exceeds floor.nulls");
}
}
semq_floor_t* f = (semq_floor_t*)semqi_alloc(sizeof(*f));
if (f == NULL) return SEMQI_NOMEM(err);
f->config = *config;
f->id_kind = id_kind;
memcpy(f->reference_id, reference_id, SEMQ_DIGEST_BYTES);
f->nulls = nulls;
f->changed_rows = changed_rows;
f->total_rows = total_rows;
f->hamming = hamming;
f->max_hamming = max_hamming;
f->distinct_nulls = distinct_nulls;
*out = f;
semqi_ok(err);
return SEMQ_OK;
}
SEMQ_API semq_status_t semq_floor_create(const semq_config_t* config, uint32_t id_kind,
const uint8_t reference_id[32], uint64_t nulls,
uint64_t changed_rows, uint64_t total_rows,
uint64_t hamming, semq_floor_t** out,
semq_error_t* err) {
return semqi_floor_create(config, id_kind, reference_id, nulls, changed_rows, total_rows, hamming,
SEMQ_NONE, SEMQ_NONE, out, err);
}
SEMQ_API void semq_floor_free(semq_floor_t* f) { semqi_free(f); }
SEMQ_API const semq_config_t* semq_floor_config(const semq_floor_t* f) { return &f->config; }
SEMQ_API uint32_t semq_floor_id_kind(const semq_floor_t* f) { return f->id_kind; }
SEMQ_API void semq_floor_reference_id(const semq_floor_t* f, uint8_t out[32]) {
memcpy(out, f->reference_id, SEMQ_DIGEST_BYTES);
}
SEMQ_API uint64_t semq_floor_nulls(const semq_floor_t* f) { return f->nulls; }
SEMQ_API uint64_t semq_floor_changed_rows(const semq_floor_t* f) { return f->changed_rows; }
SEMQ_API uint64_t semq_floor_total_rows(const semq_floor_t* f) { return f->total_rows; }
SEMQ_API uint64_t semq_floor_hamming(const semq_floor_t* f) { return f->hamming; }
SEMQ_API uint64_t semq_floor_max_hamming(const semq_floor_t* f) { return f->max_hamming; }
SEMQ_API uint64_t semq_floor_distinct_nulls(const semq_floor_t* f) { return f->distinct_nulls; }
#define KNOWN_CHECKS ((uint32_t)SEMQ_CHECK_PER_ROW)
static semq_status_t gate(const semq_diff_t* d, const semq_floor_t* f, int per_row, uint32_t* reasons,
uint64_t** rows, uint64_t* n_rows, semq_error_t* err) {
if (!semq_config_equal(&f->config, &d->ref->config)) {
return semqi_fail(err, SEMQ_ERR_INCOMPATIBLE, SEMQ_NONE, SEMQ_NONE, "floor was measured with a different config");
}
if (f->id_kind != (uint32_t)d->ref->id_kind) {
return semqi_fail(err, SEMQ_ERR_INCOMPATIBLE, SEMQ_NONE, SEMQ_NONE, "floor was measured with a different id kind");
}
if (memcmp(f->reference_id, reference_state_id(d), SEMQ_DIGEST_BYTES) != 0) {
return semqi_fail(err, SEMQ_ERR_INCOMPATIBLE, SEMQ_NONE, SEMQ_NONE, "floor was measured against a different reference");
}
if (per_row && f->max_hamming == SEMQ_NONE) {
return semqi_fail(err, SEMQ_ERR_INCOMPATIBLE, SEMQ_NONE, SEMQ_NONE,
"floor has no per-row data (max_hamming); measure it again with the per-row check");
}
const uint64_t n_common = d->n_unchanged + d->n_changed;
uint64_t p = 0u;
const semq_status_t s = p99(d->hamming, d->n_changed, &p, err);
if (s != SEMQ_OK) return s;
uint32_t r = 0u;
if (n_common == 0u) r |= (uint32_t)SEMQ_REASON_NO_COMMON_ROWS;
if (d->n_removed != 0u) r |= (uint32_t)SEMQ_REASON_REMOVED_ROWS;
if (!semqi_mul_le(d->n_changed, f->total_rows, f->changed_rows, n_common)) r |= (uint32_t)SEMQ_REASON_CHANGED_RATIO;
if (p > f->hamming) r |= (uint32_t)SEMQ_REASON_HAMMING;
if (has_reserved_change(d)) r |= (uint32_t)SEMQ_REASON_ENCODER;
uint64_t count = 0u;
if (per_row) {
for (uint64_t i = 0u; i < d->n_changed; i++) {
if (d->hamming[i] > f->max_hamming) count++;
}
}
if (count > 0u) {
uint64_t bytes = 0u;
if (semqi_mul_ovf(count, sizeof(uint64_t), &bytes)) return SEMQI_NOMEM(err);
uint64_t* list = (uint64_t*)semqi_alloc(bytes);
if (list == NULL) return SEMQI_NOMEM(err);
uint64_t j = 0u;
for (uint64_t i = 0u; i < d->n_changed; i++) {
if (d->hamming[i] > f->max_hamming) list[j++] = i;
}
r |= (uint32_t)SEMQ_REASON_ROW_ABOVE_MAX;
*rows = list;
}
*n_rows = count;
*reasons = r;
return SEMQ_OK;
}
SEMQ_API semq_status_t semq_diff_within(const semq_diff_t* d, const semq_floor_t* f, int* out,
semq_error_t* err) {
if (d == NULL || f == NULL || out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "NULL argument");
uint32_t reasons = 0u;
uint64_t* rows = NULL;
uint64_t n_rows = 0u;
const semq_status_t s = gate(d, f, 0, &reasons, &rows, &n_rows, err);
if (s != SEMQ_OK) return s;
*out = reasons == 0u;
semqi_ok(err);
return SEMQ_OK;
}
SEMQ_API semq_status_t semq_diff_evaluate(const semq_diff_t* d, const semq_floor_t* f, uint32_t checks,
semq_verdict_t** out, semq_error_t* err) {
if (out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "output is NULL");
*out = NULL;
if (d == NULL || f == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "NULL argument");
if ((checks & ~KNOWN_CHECKS) != 0u) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "checks has an unknown bit");
semq_verdict_t* v = (semq_verdict_t*)semqi_calloc(1u, sizeof(*v));
if (v == NULL) return SEMQI_NOMEM(err);
const semq_status_t s = gate(d, f, (checks & (uint32_t)SEMQ_CHECK_PER_ROW) != 0u, &v->reasons, &v->rows,
&v->n_rows, err);
if (s != SEMQ_OK) {
semq_verdict_free(v);
return s;
}
*out = v;
semqi_ok(err);
return SEMQ_OK;
}
SEMQ_API void semq_verdict_free(semq_verdict_t* v) {
if (v == NULL) return;
semqi_free(v->rows);
semqi_free(v);
}
SEMQ_API int semq_verdict_passed(const semq_verdict_t* v) { return v->reasons == 0u; }
SEMQ_API uint32_t semq_verdict_reasons(const semq_verdict_t* v) { return v->reasons; }
SEMQ_API uint64_t semq_verdict_row_count(const semq_verdict_t* v) { return v->n_rows; }
SEMQ_API uint64_t semq_verdict_row(const semq_verdict_t* v, uint64_t i) {
return i < v->n_rows ? v->rows[i] : SEMQ_NONE;
}
static int cmp_digest(const void* a, const void* b) {
return memcmp(*(const uint8_t* const*)a, *(const uint8_t* const*)b, SEMQ_DIGEST_BYTES);
}
static semq_status_t count_distinct(const semq_diff_t* const* diffs, uint32_t k, uint64_t* out, semq_error_t* err) {
uint64_t bytes = 0u;
if (semqi_mul_ovf(k, sizeof(const uint8_t*), &bytes)) return SEMQI_NOMEM(err);
const uint8_t** digests = (const uint8_t**)semqi_alloc(bytes);
if (digests == NULL) return SEMQI_NOMEM(err);
for (uint32_t i = 0u; i < k; i++) digests[i] = diffs[i]->cand->footer;
qsort((void*)digests, (size_t)k, sizeof(digests[0]), cmp_digest);
uint64_t n = 1u;
for (uint32_t i = 1u; i < k; i++) {
if (memcmp(digests[i - 1u], digests[i], SEMQ_DIGEST_BYTES) != 0) n++;
}
semqi_free((void*)digests);
*out = n;
return SEMQ_OK;
}
SEMQ_API semq_status_t semq_floor_measure_for(const semq_diff_t* const* diffs, uint32_t k, uint32_t checks,
semq_floor_t** out, semq_error_t* err) {
if (out == NULL) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "output is NULL");
*out = NULL;
if ((checks & ~KNOWN_CHECKS) != 0u) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "checks has an unknown bit");
if (diffs == NULL || k == 0u) return SEMQI_INVALID(err, SEMQ_NONE, SEMQ_NONE, "measure needs at least one null diff");
const int per_row = (checks & (uint32_t)SEMQ_CHECK_PER_ROW) != 0u;
uint64_t best_changed = 0u, best_common = 1u, max_p = 0u, max_row = 0u;
for (uint32_t i = 0u; i < k; i++) {
const semq_diff_t* d = diffs[i];
if (d == NULL) return SEMQI_INVALID(err, SEMQ_NONE, i, "null diff is NULL");
if (!semq_config_equal(&d->ref->config, &diffs[0]->ref->config)) {
return semqi_fail(err, SEMQ_ERR_INCOMPATIBLE, SEMQ_NONE, i, "null diffs have different configs");
}
if (d->ref->id_kind != diffs[0]->ref->id_kind) {
return semqi_fail(err, SEMQ_ERR_INCOMPATIBLE, SEMQ_NONE, i, "null diffs have different id kinds");
}
if (memcmp(reference_state_id(d), reference_state_id(diffs[0]), SEMQ_DIGEST_BYTES) != 0) {
return semqi_fail(err, SEMQ_ERR_INCOMPATIBLE, SEMQ_NONE, i, "null diffs have different references");
}
const uint64_t n_common = d->n_unchanged + d->n_changed;
if (n_common == 0u) return SEMQI_INVALID(err, SEMQ_NONE, i, "null diff has no rows in common");
if (d->n_added != 0u || d->n_removed != 0u) {
return SEMQI_INVALID(err, SEMQ_NONE, i, "null diff adds or removes rows");
}
if (has_reserved_change(d)) {
return SEMQI_INVALID(err, SEMQ_NONE, i, "null diff changes encoder or encoder_revision");
}
if (i == 0u || !semqi_mul_le(d->n_changed, best_common, best_changed, n_common)) {
best_changed = d->n_changed;
best_common = n_common;
}
uint64_t p = 0u;
const semq_status_t s = p99(d->hamming, d->n_changed, &p, err);
if (s != SEMQ_OK) return s;
if (p > max_p) max_p = p;
if (per_row) {
for (uint64_t r = 0u; r < d->n_changed; r++) {
if (d->hamming[r] > max_row) max_row = d->hamming[r];
}
}
}
uint64_t distinct = SEMQ_NONE;
if (per_row) {
const semq_status_t s = count_distinct(diffs, k, &distinct, err);
if (s != SEMQ_OK) return s;
}
return semqi_floor_create(&diffs[0]->ref->config, (uint32_t)diffs[0]->ref->id_kind,
reference_state_id(diffs[0]), k, best_changed, best_common, max_p,
per_row ? max_row : SEMQ_NONE, distinct, out, err);
}
SEMQ_API semq_status_t semq_floor_measure(const semq_diff_t* const* diffs, uint32_t k,
semq_floor_t** out, semq_error_t* err) {
return semq_floor_measure_for(diffs, k, 0u, out, err);
}