#include "splay.h"
#include "core/runtime.h"
#include "store/col.h"
#include "store/fileio.h"
#include "store/serde.h"
#include "table/sym.h"
#include "table/table.h"
#include "table/domain.h"
#include "ops/idxop.h"
#include "io/csv.h"
#include "vec/str.h"
#include "lang/format.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
static bool table_has_sym_cols(ray_t* tbl) {
int64_t nc = ray_table_ncols(tbl);
for (int64_t c = 0; c < nc; c++) {
ray_t* col = ray_table_get_col_idx(tbl, c);
if (col && !RAY_IS_ERR(col) && col->type == RAY_SYM) return true;
}
return false;
}
static bool table_has_col_named(ray_t* tbl, const char* name, size_t len) {
int64_t nc = ray_table_ncols(tbl);
for (int64_t c = 0; c < nc; c++) {
ray_t* na = ray_sym_str(ray_table_col_name(tbl, c));
if (!na) continue;
if (ray_str_len(na) == len && memcmp(ray_str_ptr(na), name, len) == 0)
return true;
}
return false;
}
static bool splay_col_name_safe(const char* name, size_t name_len) {
return name_len > 0 && name[0] != '.' &&
!memchr(name, '/', name_len) &&
!memchr(name, '\\', name_len) &&
!memchr(name, '\0', name_len);
}
static ray_err_t splay_validate_persisted_names(ray_t* tbl) {
int64_t nc = ray_table_ncols(tbl);
for (int64_t c = 0; c < nc; c++) {
ray_t* a = ray_sym_str(ray_table_col_name(tbl, c));
if (!a || RAY_IS_ERR(a)) continue;
const char* an = ray_str_ptr(a);
size_t alen = ray_str_len(a);
if (!splay_col_name_safe(an, alen)) return RAY_ERR_DOMAIN;
for (int64_t j = c + 1; j < nc; j++) {
ray_t* b = ray_sym_str(ray_table_col_name(tbl, j));
if (!b || RAY_IS_ERR(b)) continue;
const char* bn = ray_str_ptr(b);
size_t blen = ray_str_len(b);
if (!splay_col_name_safe(bn, blen)) return RAY_ERR_DOMAIN;
if (alen == blen && memcmp(an, bn, alen) == 0)
return RAY_ERR_DOMAIN;
}
}
return RAY_OK;
}
static void splay_sweep_stale(ray_t* tbl, const char* dir) {
DIR* d = opendir(dir);
if (!d) return;
struct dirent* ent;
while ((ent = readdir(d)) != NULL) {
const char* n = ent->d_name;
if (n[0] == '.') continue;
size_t nlen = strlen(n);
if (table_has_col_named(tbl, n, nlen)) continue;
if (nlen > 5 && memcmp(n + nlen - 5, ".link", 5) == 0 &&
table_has_col_named(tbl, n, nlen - 5))
continue;
char p[1024];
int pl = snprintf(p, sizeof(p), "%s/%s", dir, n);
if (pl <= 0 || (size_t)pl >= sizeof(p)) continue;
struct stat st;
if (stat(p, &st) != 0 || !S_ISREG(st.st_mode)) continue;
unlink(p);
}
closedir(d);
}
static ray_err_t splay_save_impl(ray_t* tbl, const char* dir, const char* sym_path,
bool durable) {
if (!tbl || RAY_IS_ERR(tbl)) return RAY_ERR_TYPE;
if (!dir) return RAY_ERR_IO;
ray_err_t name_err = splay_validate_persisted_names(tbl);
if (name_err != RAY_OK) return name_err;
int64_t preflight_ncols = ray_table_ncols(tbl);
for (int64_t c = 0; c < preflight_ncols; c++) {
ray_t* col = ray_table_get_col_idx(tbl, c);
ray_err_t err = ray_col_save_preflight(col);
if (err != RAY_OK) return err;
}
if (sym_path && table_has_sym_cols(tbl)) {
size_t dlen = strlen(dir);
while (dlen > 1 && dir[dlen - 1] == '/') dlen--;
const char* slash = strrchr(sym_path, '/');
const char* base = slash ? slash + 1 : sym_path;
size_t plen = slash ? (size_t)(slash - sym_path) : 0;
while (plen > 1 && sym_path[plen - 1] == '/') plen--;
if (plen == dlen && memcmp(sym_path, dir, dlen) == 0) {
size_t blen = strlen(base);
int64_t nc = ray_table_ncols(tbl);
for (int64_t c = 0; c < nc; c++) {
ray_t* na = ray_sym_str(ray_table_col_name(tbl, c));
if (!na || RAY_IS_ERR(na)) continue;
const char* n = ray_str_ptr(na);
size_t nlen = ray_str_len(na);
if ((nlen == blen && memcmp(n, base, blen) == 0) ||
(nlen == blen + 3 && memcmp(n, base, blen) == 0 &&
memcmp(n + blen, ".lk", 3) == 0))
return RAY_ERR_RESERVED;
}
}
}
ray_err_t mkdir_err = ray_mkdir_p(dir);
if (mkdir_err != RAY_OK) return mkdir_err;
ray_sym_domain_t* dom = NULL;
if (table_has_sym_cols(tbl)) {
if (!sym_path) return RAY_ERR_DOMAIN;
dom = ray_sym_domain_open_or_create(sym_path);
if (!dom) return RAY_ERR_IO;
if (ray_sym_domain_count(dom) == 0 &&
ray_sym_domain_intern(dom, "", 0) != 0) {
ray_sym_domain_release(dom);
return RAY_ERR_OOM;
}
int64_t nc = ray_table_ncols(tbl);
for (int64_t c = 0; c < nc; c++) {
ray_t* col = ray_table_get_col_idx(tbl, c);
if (!col || RAY_IS_ERR(col) || col->type != RAY_SYM) continue;
if (ray_sym_vec_domain(col) == dom) continue;
for (int64_t i = 0; i < col->len; i++) {
ray_t* s = ray_sym_vec_cell(col, i);
if (!s) { ray_sym_domain_release(dom); return RAY_ERR_CORRUPT; }
if (ray_sym_domain_intern(dom, ray_str_ptr(s),
ray_str_len(s)) < 0) {
ray_sym_domain_release(dom);
return RAY_ERR_OOM;
}
}
}
ray_err_t sym_err = ray_sym_domain_flush(dom, durable);
if (sym_err != RAY_OK) {
ray_sym_domain_release(dom);
return sym_err;
}
}
int64_t ncols = ray_table_ncols(tbl);
ray_t* schema = ray_vec_new(RAY_STR, ncols > 0 ? ncols : 1);
if (!schema || RAY_IS_ERR(schema)) {
if (schema) ray_release(schema);
if (dom) ray_sym_domain_release(dom);
return RAY_ERR_OOM;
}
for (int64_t c = 0; c < ncols; c++) {
ray_t* col = ray_table_get_col_idx(tbl, c);
if (!col) continue;
ray_t* name_atom = ray_sym_str(ray_table_col_name(tbl, c));
if (!name_atom) continue;
const char* name = ray_str_ptr(name_atom);
size_t name_len = ray_str_len(name_atom);
char path[1024];
int path_len = snprintf(path, sizeof(path), "%s/%.*s", dir,
(int)name_len, name);
if (path_len < 0 || (size_t)path_len >= sizeof(path)) {
ray_release(schema);
if (dom) ray_sym_domain_release(dom);
return RAY_ERR_RANGE;
}
ray_err_t err = (col->type == RAY_SYM)
? ray_col_save_sym_encoded(col, path, dom, durable)
: (durable ? ray_col_save(col, path)
: ray_col_save_bulk(col, path));
if (err != RAY_OK) {
ray_release(schema);
if (dom) ray_sym_domain_release(dom);
return err;
}
schema = ray_str_vec_append(schema, name, name_len);
if (!schema || RAY_IS_ERR(schema)) {
if (schema) ray_release(schema);
if (dom) ray_sym_domain_release(dom);
return RAY_ERR_OOM;
}
}
if (dom) ray_sym_domain_release(dom);
{
char path[1024];
int path_len = snprintf(path, sizeof(path), "%s/.d", dir);
if (path_len < 0 || (size_t)path_len >= sizeof(path)) {
ray_release(schema);
return RAY_ERR_RANGE;
}
ray_err_t err = durable ? ray_col_save(schema, path)
: ray_col_save_bulk(schema, path);
ray_release(schema);
if (err != RAY_OK) return err;
}
splay_sweep_stale(tbl, dir);
return RAY_OK;
}
ray_err_t ray_splay_save(ray_t* tbl, const char* dir, const char* sym_path) {
return splay_save_impl(tbl, dir, sym_path, true);
}
ray_err_t ray_splay_save_bulk(ray_t* tbl, const char* dir, const char* sym_path) {
return splay_save_impl(tbl, dir, sym_path, false);
}
static ray_t* splay_load_dom_impl(const char* dir, ray_sym_domain_t* dom,
bool use_mmap) {
if (!dir) return ray_error("io", NULL);
bool trace = getenv("RAY_CSV_TRACE") != NULL;
if (trace)
fprintf(stderr, "splayed.get: dir=%s mmap=%d\n", dir, use_mmap ? 1 : 0);
char path[1024];
int path_len = snprintf(path, sizeof(path), "%s/.d", dir);
if (path_len < 0 || (size_t)path_len >= sizeof(path))
return ray_error("range", "splayed %s: .d schema path exceeds %zu-byte buffer", dir, sizeof(path));
ray_t* schema = ray_col_load(path);
if (!schema || RAY_IS_ERR(schema)) {
if (trace)
fprintf(stderr, "splayed.get: schema load failed path=%s err=%s\n",
path, schema && RAY_IS_ERR(schema) ? ray_err_code(schema) : "io");
char codebuf[8];
const char* code = schema && RAY_IS_ERR(schema) ? ray_err_code(schema) : "io";
snprintf(codebuf, sizeof(codebuf), "%s", code);
ray_error_free(schema);
return ray_error(codebuf, "splayed %s: cannot read .d schema", dir);
}
if (schema->type != RAY_STR) {
ray_release(schema);
return ray_error("corrupt",
"splayed %s: .d is not a string vector (pre-cleanup format?)", dir);
}
int64_t ncols = schema->len;
ray_t* tbl = ray_table_new(ncols);
if (!tbl || RAY_IS_ERR(tbl)) {
ray_release(schema);
return tbl;
}
for (int64_t c = 0; c < ncols; c++) {
size_t name_len = 0;
const char* name = ray_str_vec_get(schema, c, &name_len);
if (!name) {
ray_release(schema);
ray_release(tbl);
return ray_error("corrupt", "splayed %s: unreadable .d entry %lld",
dir, (long long)c);
}
if (!splay_col_name_safe(name, name_len)) {
ray_release(schema);
ray_release(tbl);
return ray_error("corrupt",
"splayed %s: invalid column name in .d entry %lld",
dir, (long long)c);
}
int64_t name_id = ray_sym_intern(name, name_len);
path_len = snprintf(path, sizeof(path), "%s/%.*s", dir, (int)name_len, name);
if (path_len < 0 || (size_t)path_len >= sizeof(path)) {
ray_release(schema);
ray_release(tbl);
return ray_error("range", "splayed %s: column path for entry %lld exceeds %zu-byte buffer",
dir, (long long)c, sizeof(path));
}
ray_t* col = use_mmap ? ray_col_mmap_splayed_dom(path, dom)
: ray_col_load_dom(path, dom);
if (use_mmap && col && RAY_IS_ERR(col) &&
strcmp(ray_err_code(col), "nyi") == 0) {
ray_error_free(col);
col = ray_col_load_dom(path, dom);
}
if (!col || RAY_IS_ERR(col)) {
if (trace)
fprintf(stderr, "splayed.get: col load failed path=%s err=%s\n",
path, col && RAY_IS_ERR(col) ? ray_err_code(col) : "io");
ray_release(schema);
ray_release(tbl);
if (col && ray_error_msg() != NULL) return col;
char codebuf[8];
snprintf(codebuf, sizeof(codebuf), "%s",
col && RAY_IS_ERR(col) ? ray_err_code(col) : "io");
ray_error_free(col);
return ray_error(codebuf, "splayed %s: column '%.*s' failed to load",
dir, (int)name_len, name);
}
if (c > 0 && col->len != ray_table_nrows(tbl)) {
ray_t* err = ray_error("corrupt",
"splayed %s: column '%.*s' has %lld rows, expected %lld "
"(torn overwrite?)", dir, (int)name_len, name,
(long long)col->len, (long long)ray_table_nrows(tbl));
ray_release(col);
ray_release(schema);
ray_release(tbl);
return err;
}
ray_t* new_df = ray_table_add_col(tbl, name_id, col);
if (!new_df || RAY_IS_ERR(new_df)) {
ray_release(col);
ray_release(schema);
ray_release(tbl);
return new_df ? new_df : ray_error("oom", NULL);
}
ray_release(col);
tbl = new_df;
}
ray_release(schema);
return tbl;
}
static ray_t* splay_load_impl(const char* dir, const char* sym_path,
bool use_mmap) {
ray_sym_domain_t* dom = NULL;
if (sym_path) {
struct stat st;
if (stat(sym_path, &st) == 0) {
dom = ray_sym_domain_open(sym_path);
if (!dom)
return ray_error("corrupt",
"symfile %s: unreadable or invalid (bad magic, torn "
"record, or missing \"\" at position 0)", sym_path);
}
}
ray_t* tbl = splay_load_dom_impl(dir, dom, use_mmap);
if (dom) ray_sym_domain_release(dom);
return tbl;
}
ray_t* ray_splay_load(const char* dir, const char* sym_path) {
return splay_load_impl(dir, sym_path, false);
}
void ray_splay_build_indexes(const char* dir, ray_t* tbl) {
if (!dir || !tbl || RAY_IS_ERR(tbl) || tbl->type != RAY_TABLE) return;
int64_t nc = ray_table_ncols(tbl);
for (int64_t c = 0; c < nc; c++) {
ray_t* col = ray_table_get_col_idx(tbl, c);
if (!col || RAY_IS_ERR(col)) continue;
if (col->type == RAY_SYM && ray_index_kind(col) == RAY_IDX_HASH) {
ray_t* nstr = ray_sym_str(ray_table_col_name(tbl, c));
if (nstr && !RAY_IS_ERR(nstr)) {
char path[1024];
int n = snprintf(path, sizeof(path), "%s/%.*s", dir,
(int)ray_str_len(nstr), ray_str_ptr(nstr));
if (n > 0 && n < (int)sizeof(path)) {
ray_t* fc = ray_col_load(path);
if (fc && !RAY_IS_ERR(fc)) {
ray_t* fi = ray_idx_hash_fn(fc);
if (fi && !RAY_IS_ERR(fi)) {
(void)ray_col_append_index(path,
ray_index_payload(fi->index), fi->len, RAY_SYM);
ray_release(fi);
} else if (fi) {
ray_error_free(fi);
}
ray_release(fc);
} else if (fc) {
ray_error_free(fc);
}
}
}
continue;
}
if (col->type == RAY_STR && ray_index_kind(col) == RAY_IDX_HASH) {
ray_t* nstr = ray_sym_str(ray_table_col_name(tbl, c));
if (nstr && !RAY_IS_ERR(nstr)) {
char path[1024];
int n = snprintf(path, sizeof(path), "%s/%.*s", dir,
(int)ray_str_len(nstr), ray_str_ptr(nstr));
if (n > 0 && n < (int)sizeof(path))
(void)ray_col_append_index(path, ray_index_payload(col->index),
col->len, RAY_STR);
}
continue;
}
if (col->len < (1 << 16)) continue;
ray_t* idx = (col->type == RAY_STR)
? ray_index_dict_compute(col)
: ray_index_chunk_zone_compute(col, 16);
if (!idx || RAY_IS_ERR(idx)) { if (idx) ray_error_free(idx); continue; }
if (col->type != RAY_STR &&
ray_csv_hash_upgrade_check(col->type, col->len,
ray_index_payload(idx))) {
ray_t* hi = ray_idx_hash_fn(col);
if (hi && !RAY_IS_ERR(hi) && (hi->attrs & RAY_ATTR_HAS_INDEX)) {
ray_release(idx);
idx = NULL;
ray_t* nstr = ray_sym_str(ray_table_col_name(tbl, c));
if (nstr && !RAY_IS_ERR(nstr)) {
char path[1100];
int n = snprintf(path, sizeof(path), "%s/%.*s", dir,
(int)ray_str_len(nstr), ray_str_ptr(nstr));
if (n > 0 && n < (int)sizeof(path))
(void)ray_col_append_index(path,
ray_index_payload(hi->index), hi->len, hi->type);
}
ray_release(hi);
continue;
}
if (hi) { if (RAY_IS_ERR(hi)) ray_error_free(hi); else ray_release(hi); }
}
ray_t* nstr = ray_sym_str(ray_table_col_name(tbl, c));
if (nstr && !RAY_IS_ERR(nstr)) {
char path[1100];
int n = snprintf(path, sizeof(path), "%s/%.*s", dir,
(int)ray_str_len(nstr), ray_str_ptr(nstr));
if (n > 0 && n < (int)sizeof(path))
(void)ray_col_append_index(path, ray_index_payload(idx),
col->len, col->type);
}
ray_release(idx);
}
}
ray_t* ray_read_splayed(const char* dir, const char* sym_path) {
return splay_load_impl(dir, sym_path, true);
}
ray_t* ray_read_splayed_dom(const char* dir, struct ray_sym_domain_s* dom) {
return splay_load_dom_impl(dir, dom, true);
}