#include "ops/fused_topk.h"
#include "ops/fused_pred.h"
#include "ops/fused_group.h"
#include "ops/internal.h"
#include "lang/internal.h"
#include "core/pool.h"
#include <string.h>
int ray_fused_topk_supported(ray_t* where_expr, ray_t* tbl) {
return ray_fused_group_supported(where_expr, tbl);
}
#define FPK_MAX_KEYS 16
typedef struct {
int8_t type;
uint8_t attrs;
uint8_t esz;
uint8_t desc;
uint8_t has_nulls;
uint8_t nulls_first;
uint8_t dom_runtime;
uint8_t raw_ok;
ray_sym_domain_raw_t raw;
int64_t sym;
const void* base;
ray_t* col;
} fpk_keyspec_t;
static void fpk_unpin_keys(fpk_keyspec_t* keys, uint32_t n) {
for (uint32_t i = 0; i < n; i++)
if (keys[i].raw_ok) { ray_sym_domain_raw_unpin(ray_sym_vec_domain(keys[i].col)); keys[i].raw_ok = 0; }
}
typedef struct {
fp_pred_t pred;
fpk_keyspec_t keys[FPK_MAX_KEYS];
uint32_t n_keys;
int64_t k;
int64_t* heap_idx;
int32_t* heap_n;
ray_t* tbl;
ray_t** sym_strings;
uint32_t sym_count;
_Atomic(uint32_t) oom;
} fpk_par_ctx_t;
static inline int fpk_cmp(const fpk_par_ctx_t* c, int64_t row_a, int64_t row_b) {
if (RAY_UNLIKELY(row_a == row_b)) return 0;
for (uint32_t k = 0; k < c->n_keys; k++) {
const fpk_keyspec_t* ks = &c->keys[k];
int cmp = 0;
if (ks->has_nulls) {
bool a_null = ray_vec_is_null(ks->col, row_a);
bool b_null = ray_vec_is_null(ks->col, row_b);
if (a_null && b_null) continue;
if (a_null) return ks->nulls_first ? -1 : 1;
if (b_null) return ks->nulls_first ? 1 : -1;
}
if (ks->type == RAY_SYM) {
uint32_t ia = (uint32_t)read_by_esz(ks->base, row_a, ks->esz);
uint32_t ib = (uint32_t)read_by_esz(ks->base, row_b, ks->esz);
if (ia == ib) continue;
if (ks->raw_ok && (int64_t)ia < ks->raw.count && (int64_t)ib < ks->raw.count) {
size_t la, lb;
const char* pa = ray_sym_domain_raw_str(&ks->raw, (int64_t)ia, &la);
const char* pb = ray_sym_domain_raw_str(&ks->raw, (int64_t)ib, &lb);
size_t ml = la < lb ? la : lb;
cmp = ml ? memcmp(pa, pb, ml) : 0;
if (cmp == 0) cmp = la < lb ? -1 : (la > lb ? 1 : 0);
} else {
ray_t* sa;
ray_t* sb;
if (ks->dom_runtime) {
if (ia >= c->sym_count || ib >= c->sym_count) continue;
sa = c->sym_strings[ia];
sb = c->sym_strings[ib];
} else {
struct ray_sym_domain_s* dom = ray_sym_vec_domain(ks->col);
sa = ray_sym_domain_str(dom, (int64_t)ia);
sb = ray_sym_domain_str(dom, (int64_t)ib);
}
if (!sa || !sb) continue;
cmp = ray_str_cmp(sa, sb);
}
} else if (ks->type == RAY_STR) {
size_t la = 0, lb = 0;
const char* sa = ray_str_vec_get(ks->col, row_a, &la);
const char* sb = ray_str_vec_get(ks->col, row_b, &lb);
if (!sa) sa = "";
if (!sb) sb = "";
size_t ml = la < lb ? la : lb;
cmp = ml ? memcmp(sa, sb, ml) : 0;
if (cmp == 0) {
if (la < lb) cmp = -1;
else if (la > lb) cmp = 1;
}
if (cmp == 0) continue;
} else if (ks->type == RAY_GUID) {
cmp = memcmp((const char*)ks->base + (size_t)row_a * 16,
(const char*)ks->base + (size_t)row_b * 16, 16);
} else if (ks->type == RAY_F32 || ks->type == RAY_F64) {
double va = ks->type == RAY_F32 ? ((const float*)ks->base)[row_a] : ((const double*)ks->base)[row_a];
double vb = ks->type == RAY_F32 ? ((const float*)ks->base)[row_b] : ((const double*)ks->base)[row_b];
cmp = (va > vb) - (va < vb);
} else if (ks->esz == 8) {
int64_t va = ((const int64_t*)ks->base)[row_a];
int64_t vb = ((const int64_t*)ks->base)[row_b];
if (va < vb) cmp = -1; else if (va > vb) cmp = 1;
} else if (ks->esz == 4) {
int32_t va = ((const int32_t*)ks->base)[row_a];
int32_t vb = ((const int32_t*)ks->base)[row_b];
if (va < vb) cmp = -1; else if (va > vb) cmp = 1;
} else if (ks->esz == 2) {
int16_t va = ((const int16_t*)ks->base)[row_a];
int16_t vb = ((const int16_t*)ks->base)[row_b];
if (va < vb) cmp = -1; else if (va > vb) cmp = 1;
} else {
uint8_t va = ((const uint8_t*)ks->base)[row_a];
uint8_t vb = ((const uint8_t*)ks->base)[row_b];
if (va < vb) cmp = -1; else if (va > vb) cmp = 1;
}
if (cmp != 0) return ks->desc ? -cmp : cmp;
}
if (row_a > row_b) return 1;
if (row_a < row_b) return -1;
return 0;
}
static inline void fpk_sift_down(const fpk_par_ctx_t* c, int64_t* heap,
int32_t n, int32_t i) {
for (;;) {
int32_t worst = i;
int32_t l = 2 * i + 1, r = 2 * i + 2;
if (l < n && fpk_cmp(c, heap[l], heap[worst]) > 0) worst = l;
if (r < n && fpk_cmp(c, heap[r], heap[worst]) > 0) worst = r;
if (worst == i) return;
int64_t ti = heap[i]; heap[i] = heap[worst]; heap[worst] = ti;
i = worst;
}
}
static inline void fpk_heapify(const fpk_par_ctx_t* c, int64_t* heap, int32_t n) {
for (int32_t i = n / 2 - 1; i >= 0; i--)
fpk_sift_down(c, heap, n, i);
}
static void fpk_par_fn(void* raw, uint32_t worker_id, int64_t start, int64_t end) {
fpk_par_ctx_t* c = (fpk_par_ctx_t*)raw;
if (atomic_load_explicit(&c->oom, memory_order_relaxed)) return;
int32_t k = (int32_t)c->k;
int64_t* hidx = &c->heap_idx[(size_t)worker_id * (size_t)k];
int32_t hn = c->heap_n[worker_id];
int64_t row = start;
while (row < end) {
int64_t mend = row + RAY_MORSEL_ELEMS;
if (mend > end) mend = end;
int64_t mlen = mend - row;
uint8_t bits[RAY_MORSEL_ELEMS];
fp_eval_pred(&c->pred, row, mend, bits);
for (int64_t r = 0; r < mlen; r++) {
if (!bits[r]) continue;
int64_t src_row = row + r;
if (hn < k) {
hidx[hn++] = src_row;
if (hn == k) fpk_heapify(c, hidx, k);
} else {
if (fpk_cmp(c, src_row, hidx[0]) >= 0) continue;
hidx[0] = src_row;
fpk_sift_down(c, hidx, k, 0);
}
}
row = mend;
}
c->heap_n[worker_id] = hn;
}
static void fpk_sort_final(const fpk_par_ctx_t* c, int64_t* idx, int32_t n) {
for (int32_t i = 1; i < n; i++) {
int64_t v = idx[i];
int32_t j = i - 1;
while (j >= 0 && fpk_cmp(c, idx[j], v) > 0) {
idx[j + 1] = idx[j];
j--;
}
idx[j + 1] = v;
}
}
ray_t* ray_fused_topk_select(ray_t* tbl,
ray_t* where_expr,
const int64_t* sort_key_syms,
const uint8_t* sort_descs,
uint32_t n_sort_keys,
int64_t k,
const int64_t* out_col_syms,
const int64_t* out_alias_syms,
uint32_t n_out)
{
if (!tbl || tbl->type != RAY_TABLE || k <= 0 || n_out == 0) return NULL;
if (k > FPK_MAX_K) return NULL;
if (n_sort_keys == 0 || n_sort_keys > FPK_MAX_KEYS) return NULL;
int64_t nrows = ray_table_nrows(tbl);
if (nrows <= 0 || k >= nrows) return NULL;
for (uint32_t c = 0; c < n_out; c++) {
ray_t* col = ray_table_get_col(tbl, out_col_syms[c]);
if (!col) return NULL;
int8_t ot = col->type;
if (RAY_IS_PARTED(ot) || ot == RAY_MAPCOMMON) return NULL;
if (!ray_is_vec(col))
return NULL;
}
fpk_par_ctx_t ctx;
memset(&ctx, 0, sizeof(ctx));
int sym_needed = 0;
for (uint32_t i = 0; i < n_sort_keys; i++) {
ray_t* col = ray_table_get_col(tbl, sort_key_syms[i]);
if (!col) { fpk_unpin_keys(ctx.keys, i); return NULL; }
int8_t kt = col->type;
if (RAY_IS_PARTED(kt) || kt == RAY_MAPCOMMON) { fpk_unpin_keys(ctx.keys, i); return NULL; }
if (kt != RAY_SYM && kt != RAY_STR && kt != RAY_BOOL && kt != RAY_U8
&& kt != RAY_I16 && kt != RAY_I32 && kt != RAY_I64
&& kt != RAY_DATE && kt != RAY_TIME && kt != RAY_TIMESTAMP
&& kt != RAY_F32 && kt != RAY_F64 && kt != RAY_GUID)
{ fpk_unpin_keys(ctx.keys, i); return NULL; }
ctx.keys[i].type = kt;
ctx.keys[i].attrs = col->attrs;
ctx.keys[i].esz = ray_sym_elem_size(kt, col->attrs);
ctx.keys[i].desc = sort_descs[i];
ctx.keys[i].has_nulls = ray_vec_may_have_nulls(col) ? 1 : 0;
ctx.keys[i].nulls_first = sort_nulls_first(sort_descs[i]);
ctx.keys[i].dom_runtime =
(kt == RAY_SYM &&
ray_sym_vec_domain(col) == ray_sym_runtime_domain()) ? 1 : 0;
ctx.keys[i].sym = sort_key_syms[i];
ctx.keys[i].base = ray_data(col);
ctx.keys[i].col = col;
ctx.keys[i].raw_ok = 0;
if (ctx.keys[i].dom_runtime) sym_needed = 1;
else if (kt == RAY_SYM)
ctx.keys[i].raw_ok = ray_sym_domain_raw_pin(ray_sym_vec_domain(col), &ctx.keys[i].raw) ? 1 : 0;
}
ctx.n_keys = n_sort_keys;
ctx.k = k;
ctx.tbl = tbl;
ray_graph_t* g = ray_graph_new(tbl);
if (!g) { fpk_unpin_keys(ctx.keys, n_sort_keys); return NULL; }
ray_op_t* pred_dag = compile_expr_dag(g, where_expr);
if (!pred_dag) { ray_graph_free(g); fpk_unpin_keys(ctx.keys, n_sort_keys); return NULL; }
if (fp_compile_pred(g, pred_dag, tbl, &ctx.pred) != 0) {
fp_pred_cleanup(&ctx.pred);
ray_graph_free(g);
fpk_unpin_keys(ctx.keys, n_sort_keys);
return NULL;
}
if (sym_needed) {
ray_sym_strings_borrow(&ctx.sym_strings, &ctx.sym_count);
if (!ctx.sym_strings) { ray_graph_free(g); fpk_unpin_keys(ctx.keys, n_sort_keys); return NULL; }
}
atomic_store_explicit(&ctx.oom, 0, memory_order_relaxed);
ray_pool_t* pool = ray_pool_get();
uint32_t nw = pool ? ray_pool_total_workers(pool) : 1;
ray_t* idx_hdr = NULL;
ray_t* hn_hdr = NULL;
ctx.heap_idx = (int64_t*)scratch_alloc(&idx_hdr,
(size_t)nw * (size_t)k * sizeof(int64_t));
ctx.heap_n = (int32_t*)scratch_calloc(&hn_hdr,
(size_t)nw * sizeof(int32_t));
if (!ctx.heap_idx || !ctx.heap_n) {
if (idx_hdr) scratch_free(idx_hdr);
if (hn_hdr) scratch_free(hn_hdr);
fp_pred_cleanup(&ctx.pred);
ray_graph_free(g);
fpk_unpin_keys(ctx.keys, n_sort_keys);
return NULL;
}
if (pool) ray_pool_dispatch(pool, fpk_par_fn, &ctx, nrows);
else fpk_par_fn(&ctx, 0, 0, nrows);
if (atomic_load_explicit(&ctx.oom, memory_order_relaxed)) {
scratch_free(idx_hdr); scratch_free(hn_hdr);
fp_pred_cleanup(&ctx.pred);
ray_graph_free(g);
fpk_unpin_keys(ctx.keys, n_sort_keys);
return NULL;
}
int64_t global_idx[FPK_MAX_K];
int32_t global_n = 0;
for (uint32_t w = 0; w < nw; w++) {
int32_t hn = ctx.heap_n[w];
int64_t* hidx = &ctx.heap_idx[(size_t)w * (size_t)k];
for (int32_t i = 0; i < hn; i++) {
if (global_n < (int32_t)k) {
global_idx[global_n++] = hidx[i];
if (global_n == (int32_t)k)
fpk_heapify(&ctx, global_idx, global_n);
} else {
if (fpk_cmp(&ctx, hidx[i], global_idx[0]) >= 0) continue;
global_idx[0] = hidx[i];
fpk_sift_down(&ctx, global_idx, global_n, 0);
}
}
}
scratch_free(idx_hdr);
scratch_free(hn_hdr);
fpk_sort_final(&ctx, global_idx, global_n);
fpk_unpin_keys(ctx.keys, n_sort_keys);
ray_t* result = ray_table_new(n_out);
if (!result || RAY_IS_ERR(result)) {
fp_pred_cleanup(&ctx.pred);
ray_graph_free(g);
return result ? result : ray_error("oom", NULL);
}
int build_ok = 1;
for (uint32_t c = 0; c < n_out; c++) {
int64_t cs = out_col_syms[c];
int64_t alias = out_alias_syms ? out_alias_syms[c] : cs;
ray_t* src = ray_table_get_col(tbl, cs);
if (!src) { build_ok = 0; break; }
ray_t* col = gather_by_idx(src, global_idx, global_n);
if (!col || RAY_IS_ERR(col)) { build_ok = 0; break; }
result = ray_table_add_col(result, alias, col);
ray_release(col);
}
ray_graph_free(g);
fp_pred_cleanup(&ctx.pred);
if (!build_ok) {
ray_release(result);
return ray_error("schema", NULL);
}
return result;
}