tranfi 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +177 -21
- package/NOTICE +8 -0
- package/README.md +627 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-BIAIKnrp.js +160 -0
- package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/app/index.html +13 -0
- package/binding.gyp +121 -0
- package/csrc/arena.c +93 -0
- package/csrc/batch.c +976 -0
- package/csrc/buffer.c +154 -0
- package/csrc/cJSON.c +3386 -0
- package/csrc/cJSON.h +316 -0
- package/csrc/codec_csv.c +1951 -0
- package/csrc/codec_jsonl.c +1086 -0
- package/csrc/codec_table.c +248 -0
- package/csrc/codec_text.c +447 -0
- package/csrc/compiler.c +130 -0
- package/csrc/config.h +21 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +5417 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1553 -0
- package/csrc/expr.h +58 -0
- package/csrc/internal.h +539 -0
- package/csrc/ir.c +166 -0
- package/csrc/ir.h +208 -0
- package/csrc/ir_schema.c +75 -0
- package/csrc/ir_serialize.c +166 -0
- package/csrc/ir_sql.c +1822 -0
- package/csrc/ir_validate.c +576 -0
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +1241 -0
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +283 -0
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +255 -0
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +248 -0
- package/csrc/op_cast.c +523 -0
- package/csrc/op_clip.c +99 -0
- package/csrc/op_date_trunc.c +355 -0
- package/csrc/op_datetime.c +394 -0
- package/csrc/op_derive.c +216 -0
- package/csrc/op_diff.c +250 -0
- package/csrc/op_ewma.c +222 -0
- package/csrc/op_explode.c +206 -0
- package/csrc/op_fill_down.c +235 -0
- package/csrc/op_fill_null.c +268 -0
- package/csrc/op_filter.c +181 -0
- package/csrc/op_frequency.c +721 -0
- package/csrc/op_grep.c +181 -0
- package/csrc/op_group_agg.c +1956 -0
- package/csrc/op_hash.c +159 -0
- package/csrc/op_head.c +84 -0
- package/csrc/op_interpolate.c +445 -0
- package/csrc/op_join.c +2902 -0
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +419 -0
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +242 -0
- package/csrc/op_normalize.c +510 -0
- package/csrc/op_onehot.c +457 -0
- package/csrc/op_pivot.c +1754 -0
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +3044 -0
- package/csrc/op_rename.c +129 -0
- package/csrc/op_replace.c +354 -0
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +158 -0
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +340 -0
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +95 -0
- package/csrc/op_sort.c +819 -0
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +151 -0
- package/csrc/op_split_data.c +119 -0
- package/csrc/op_stack.c +271 -0
- package/csrc/op_stats.c +875 -0
- package/csrc/op_step.c +333 -0
- package/csrc/op_tail.c +105 -0
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +357 -0
- package/csrc/op_trim.c +138 -0
- package/csrc/op_unique.c +1343 -0
- package/csrc/op_unpivot.c +193 -0
- package/csrc/op_validate.c +648 -0
- package/csrc/op_window.c +591 -0
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +1088 -0
- package/csrc/recipes.c +104 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +506 -0
- package/csrc/report.h +22 -0
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +291 -0
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +218 -0
- package/napi_api.c +534 -0
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +64 -59
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +190 -0
- package/src/engines/duckdb.js +142 -0
- package/src/index.js +925 -0
- package/src/memory_policy.js +411 -0
- package/src/native.js +18 -0
- package/src/pipeline.js +709 -0
- package/src/recipe_json.js +80 -0
- package/src/server.js +279 -0
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +21 -0
- package/wasm/index.js +732 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/dist/bundle.js +0 -1
- package/index.html +0 -18
- package/src/app.css +0 -169
- package/src/app.js +0 -203
- package/src/app.vue +0 -250
- package/src/bulma-input.vue +0 -110
- package/src/common-inputs.js +0 -28
- package/src/main.js +0 -20
- package/src/transforms.js +0 -166
- package/webpack.config.js +0 -108
package/csrc/op_rowid.c
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_rowid.c - Row counters, globally or within key groups.
|
|
3
|
+
*
|
|
4
|
+
* Config examples:
|
|
5
|
+
* {"result": "_rowid"}
|
|
6
|
+
* {"columns": ["city"], "result": "city_row", "max_keys": 10000}
|
|
7
|
+
* {"columns": ["city"], "result": "city_row", "sorted": true}
|
|
8
|
+
*
|
|
9
|
+
* With no columns, appends a global 1-based row counter with O(1) state.
|
|
10
|
+
* With columns and sorted=true, counts consecutive sorted groups using only the
|
|
11
|
+
* previous key and current count. With columns and sorted=false/default, counts
|
|
12
|
+
* exact occurrences per key with key-state memory, optionally capped by
|
|
13
|
+
* max_keys.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include "internal.h"
|
|
17
|
+
#include "cJSON.h"
|
|
18
|
+
#include <stdlib.h>
|
|
19
|
+
#include <string.h>
|
|
20
|
+
#include <stdio.h>
|
|
21
|
+
#include <stdarg.h>
|
|
22
|
+
#include <stdint.h>
|
|
23
|
+
#include <limits.h>
|
|
24
|
+
|
|
25
|
+
typedef struct {
|
|
26
|
+
char *data;
|
|
27
|
+
size_t len;
|
|
28
|
+
size_t cap;
|
|
29
|
+
} key_buf;
|
|
30
|
+
|
|
31
|
+
typedef struct {
|
|
32
|
+
char **keys;
|
|
33
|
+
int64_t *counts;
|
|
34
|
+
size_t count;
|
|
35
|
+
size_t cap;
|
|
36
|
+
size_t key_bytes;
|
|
37
|
+
} rowid_map;
|
|
38
|
+
|
|
39
|
+
typedef struct {
|
|
40
|
+
char **cols;
|
|
41
|
+
size_t n_cols;
|
|
42
|
+
char *result;
|
|
43
|
+
int sorted;
|
|
44
|
+
size_t max_keys;
|
|
45
|
+
size_t max_state_bytes; /* 0 = unlimited */
|
|
46
|
+
int64_t global_count;
|
|
47
|
+
char *prev_key;
|
|
48
|
+
int seen_prev;
|
|
49
|
+
int64_t current_group_count;
|
|
50
|
+
rowid_map map;
|
|
51
|
+
} rowid_state;
|
|
52
|
+
|
|
53
|
+
static size_t rowid_retained_state_bytes(const rowid_state *st);
|
|
54
|
+
|
|
55
|
+
static int rowid_set_error(tf_side_channels *side, const char *msg) {
|
|
56
|
+
return tf_side_write_error(side, msg);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static int rowid_check_state_bytes(rowid_state *st, tf_side_channels *side) {
|
|
60
|
+
if (!st || st->max_state_bytes == 0) return TF_OK;
|
|
61
|
+
size_t retained = rowid_retained_state_bytes(st);
|
|
62
|
+
if (retained <= st->max_state_bytes) return TF_OK;
|
|
63
|
+
char msg[192];
|
|
64
|
+
snprintf(msg, sizeof(msg),
|
|
65
|
+
"rowid: max_state_bytes=%zu exceeded while tracking group row ids (%zu bytes retained)",
|
|
66
|
+
st->max_state_bytes, retained);
|
|
67
|
+
if (rowid_set_error(side, msg) != TF_OK) return TF_ERROR;
|
|
68
|
+
return TF_ERROR;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static int key_buf_init(key_buf *b) {
|
|
72
|
+
b->cap = 128;
|
|
73
|
+
b->len = 0;
|
|
74
|
+
b->data = tf_mallocarray_checked(b->cap, sizeof(char));
|
|
75
|
+
if (!b->data) return -1;
|
|
76
|
+
b->data[0] = '\0';
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static int key_buf_ensure(key_buf *b, size_t extra) {
|
|
81
|
+
size_t need = 0;
|
|
82
|
+
if (!b || !b->data ||
|
|
83
|
+
tf_size_add(b->len, extra, &need) != TF_OK ||
|
|
84
|
+
tf_size_add(need, 1, &need) != TF_OK) {
|
|
85
|
+
return -1;
|
|
86
|
+
}
|
|
87
|
+
if (need <= b->cap) return 0;
|
|
88
|
+
size_t new_cap = 0;
|
|
89
|
+
if (tf_size_grow_pow2(b->cap, need, 128, &new_cap) != TF_OK) return -1;
|
|
90
|
+
char *tmp = tf_reallocarray_checked(b->data, new_cap, sizeof(char));
|
|
91
|
+
if (!tmp) return -1;
|
|
92
|
+
b->data = tmp;
|
|
93
|
+
b->cap = new_cap;
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static int key_buf_appendn(key_buf *b, const char *s, size_t n) {
|
|
98
|
+
if (key_buf_ensure(b, n) != 0) return -1;
|
|
99
|
+
memcpy(b->data + b->len, s, n);
|
|
100
|
+
b->len += n;
|
|
101
|
+
b->data[b->len] = '\0';
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static int key_buf_append(key_buf *b, const char *s) {
|
|
106
|
+
return key_buf_appendn(b, s, strlen(s));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static int key_buf_appendf(key_buf *b, const char *fmt, ...) {
|
|
110
|
+
char tmp[128];
|
|
111
|
+
va_list ap;
|
|
112
|
+
va_start(ap, fmt);
|
|
113
|
+
int n = vsnprintf(tmp, sizeof(tmp), fmt, ap);
|
|
114
|
+
va_end(ap);
|
|
115
|
+
if (n < 0) return -1;
|
|
116
|
+
if ((size_t)n < sizeof(tmp)) return key_buf_appendn(b, tmp, (size_t)n);
|
|
117
|
+
|
|
118
|
+
size_t dyn_len = 0;
|
|
119
|
+
if (tf_size_add((size_t)n, 1, &dyn_len) != TF_OK) return -1;
|
|
120
|
+
char *dyn = tf_mallocarray_checked(dyn_len, sizeof(char));
|
|
121
|
+
if (!dyn) return -1;
|
|
122
|
+
va_start(ap, fmt);
|
|
123
|
+
vsnprintf(dyn, dyn_len, fmt, ap);
|
|
124
|
+
va_end(ap);
|
|
125
|
+
int rc = key_buf_appendn(b, dyn, (size_t)n);
|
|
126
|
+
free(dyn);
|
|
127
|
+
return rc;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static int append_cell_key(key_buf *b, const tf_batch *in, size_t row, int ci) {
|
|
131
|
+
if (tf_batch_is_null(in, row, ci)) {
|
|
132
|
+
return key_buf_appendf(b, "c%d:t%d:N;", ci, (int)in->col_types[ci]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
char num[96];
|
|
136
|
+
const char *val = NULL;
|
|
137
|
+
size_t len = 0;
|
|
138
|
+
switch (in->col_types[ci]) {
|
|
139
|
+
case TF_TYPE_STRING:
|
|
140
|
+
val = tf_batch_get_string(in, row, ci);
|
|
141
|
+
len = val ? strlen(val) : 0;
|
|
142
|
+
break;
|
|
143
|
+
case TF_TYPE_INT64:
|
|
144
|
+
snprintf(num, sizeof(num), "%lld", (long long)tf_batch_get_int64(in, row, ci));
|
|
145
|
+
val = num;
|
|
146
|
+
len = strlen(num);
|
|
147
|
+
break;
|
|
148
|
+
case TF_TYPE_FLOAT64:
|
|
149
|
+
snprintf(num, sizeof(num), "%.17g", tf_batch_get_float64(in, row, ci));
|
|
150
|
+
val = num;
|
|
151
|
+
len = strlen(num);
|
|
152
|
+
break;
|
|
153
|
+
case TF_TYPE_BOOL:
|
|
154
|
+
val = tf_batch_get_bool(in, row, ci) ? "true" : "false";
|
|
155
|
+
len = strlen(val);
|
|
156
|
+
break;
|
|
157
|
+
case TF_TYPE_DATE:
|
|
158
|
+
snprintf(num, sizeof(num), "%d", (int)tf_batch_get_date(in, row, ci));
|
|
159
|
+
val = num;
|
|
160
|
+
len = strlen(num);
|
|
161
|
+
break;
|
|
162
|
+
case TF_TYPE_TIMESTAMP:
|
|
163
|
+
snprintf(num, sizeof(num), "%lld", (long long)tf_batch_get_timestamp(in, row, ci));
|
|
164
|
+
val = num;
|
|
165
|
+
len = strlen(num);
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
val = "";
|
|
169
|
+
len = 0;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (key_buf_appendf(b, "c%d:t%d:V%zu:", ci, (int)in->col_types[ci], len) != 0) return -1;
|
|
174
|
+
if (key_buf_appendn(b, val ? val : "", len) != 0) return -1;
|
|
175
|
+
return key_buf_append(b, ";");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static char *format_key(const tf_batch *in, size_t row,
|
|
179
|
+
const int *col_indices, size_t n_cols) {
|
|
180
|
+
key_buf b;
|
|
181
|
+
if (key_buf_init(&b) != 0) return NULL;
|
|
182
|
+
for (size_t i = 0; i < n_cols; i++) {
|
|
183
|
+
if (append_cell_key(&b, in, row, col_indices[i]) != 0) {
|
|
184
|
+
free(b.data);
|
|
185
|
+
return NULL;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return b.data;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static uint32_t rowid_hash(const char *key) {
|
|
192
|
+
uint32_t h = 5381;
|
|
193
|
+
for (const char *p = key; *p; p++) {
|
|
194
|
+
h = ((h << 5) + h) ^ (uint32_t)(unsigned char)*p;
|
|
195
|
+
}
|
|
196
|
+
return h;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static int map_init(rowid_map *m, size_t cap) {
|
|
200
|
+
m->keys = tf_callocarray_checked(cap ? cap : 1, sizeof(char *));
|
|
201
|
+
m->counts = tf_callocarray_checked(cap ? cap : 1, sizeof(int64_t));
|
|
202
|
+
if (!m->keys || !m->counts) {
|
|
203
|
+
free(m->keys);
|
|
204
|
+
free(m->counts);
|
|
205
|
+
m->keys = NULL;
|
|
206
|
+
m->counts = NULL;
|
|
207
|
+
return -1;
|
|
208
|
+
}
|
|
209
|
+
m->cap = cap;
|
|
210
|
+
m->count = 0;
|
|
211
|
+
m->key_bytes = 0;
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static int map_grow(rowid_map *m) {
|
|
216
|
+
size_t new_cap = 0;
|
|
217
|
+
if (m->cap == 0) {
|
|
218
|
+
new_cap = 64;
|
|
219
|
+
} else if (tf_size_mul(m->cap, 2, &new_cap) != TF_OK) {
|
|
220
|
+
return -1;
|
|
221
|
+
}
|
|
222
|
+
char **new_keys = tf_callocarray_checked(new_cap, sizeof(char *));
|
|
223
|
+
int64_t *new_counts = tf_callocarray_checked(new_cap, sizeof(int64_t));
|
|
224
|
+
if (!new_keys || !new_counts) {
|
|
225
|
+
free(new_keys);
|
|
226
|
+
free(new_counts);
|
|
227
|
+
return -1;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (size_t i = 0; i < m->cap; i++) {
|
|
231
|
+
if (!m->keys[i]) continue;
|
|
232
|
+
uint32_t idx = rowid_hash(m->keys[i]) % new_cap;
|
|
233
|
+
while (new_keys[idx]) idx = (idx + 1) % new_cap;
|
|
234
|
+
new_keys[idx] = m->keys[i];
|
|
235
|
+
new_counts[idx] = m->counts[i];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
free(m->keys);
|
|
239
|
+
free(m->counts);
|
|
240
|
+
m->keys = new_keys;
|
|
241
|
+
m->counts = new_counts;
|
|
242
|
+
m->cap = new_cap;
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
static int map_increment(rowid_state *st, char *key, int64_t *out_count,
|
|
247
|
+
tf_side_channels *side) {
|
|
248
|
+
if (!st->map.keys && map_init(&st->map, 64) != 0) {
|
|
249
|
+
free(key);
|
|
250
|
+
return TF_ERROR;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
uint32_t idx = rowid_hash(key) % st->map.cap;
|
|
254
|
+
while (st->map.keys[idx]) {
|
|
255
|
+
if (strcmp(st->map.keys[idx], key) == 0) {
|
|
256
|
+
if (st->map.counts[idx] == INT64_MAX) {
|
|
257
|
+
free(key);
|
|
258
|
+
return TF_ERROR;
|
|
259
|
+
}
|
|
260
|
+
st->map.counts[idx]++;
|
|
261
|
+
*out_count = st->map.counts[idx];
|
|
262
|
+
free(key);
|
|
263
|
+
return TF_OK;
|
|
264
|
+
}
|
|
265
|
+
idx = (idx + 1) % st->map.cap;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (st->max_keys > 0 && st->map.count >= st->max_keys) {
|
|
269
|
+
char msg[160];
|
|
270
|
+
snprintf(msg, sizeof(msg),
|
|
271
|
+
"rowid: max_keys=%zu exceeded while tracking group row ids",
|
|
272
|
+
st->max_keys);
|
|
273
|
+
int err_rc = rowid_set_error(side, msg);
|
|
274
|
+
free(key);
|
|
275
|
+
if (err_rc != TF_OK) return TF_ERROR;
|
|
276
|
+
return TF_ERROR;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
size_t load_count = 0;
|
|
280
|
+
size_t load_limit = 0;
|
|
281
|
+
if (tf_size_mul(st->map.count, 4, &load_count) != TF_OK ||
|
|
282
|
+
tf_size_mul(st->map.cap, 3, &load_limit) != TF_OK) {
|
|
283
|
+
free(key);
|
|
284
|
+
return TF_ERROR;
|
|
285
|
+
}
|
|
286
|
+
if (load_count >= load_limit) {
|
|
287
|
+
if (map_grow(&st->map) != 0) {
|
|
288
|
+
free(key);
|
|
289
|
+
return TF_ERROR;
|
|
290
|
+
}
|
|
291
|
+
idx = rowid_hash(key) % st->map.cap;
|
|
292
|
+
while (st->map.keys[idx]) idx = (idx + 1) % st->map.cap;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
size_t key_bytes_delta = 0;
|
|
296
|
+
size_t new_key_bytes = 0;
|
|
297
|
+
size_t new_count = 0;
|
|
298
|
+
if (tf_size_add(strlen(key), 1, &key_bytes_delta) != TF_OK ||
|
|
299
|
+
tf_size_add(st->map.key_bytes, key_bytes_delta, &new_key_bytes) != TF_OK ||
|
|
300
|
+
tf_size_add(st->map.count, 1, &new_count) != TF_OK) {
|
|
301
|
+
free(key);
|
|
302
|
+
return TF_ERROR;
|
|
303
|
+
}
|
|
304
|
+
st->map.keys[idx] = key;
|
|
305
|
+
st->map.key_bytes = new_key_bytes;
|
|
306
|
+
st->map.counts[idx] = 1;
|
|
307
|
+
st->map.count = new_count;
|
|
308
|
+
if (rowid_check_state_bytes(st, side) != TF_OK) return TF_ERROR;
|
|
309
|
+
*out_count = 1;
|
|
310
|
+
return TF_OK;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
static int resolve_columns(const rowid_state *st, const tf_batch *in,
|
|
314
|
+
int **out_indices, size_t *out_n,
|
|
315
|
+
tf_side_channels *side) {
|
|
316
|
+
*out_indices = NULL;
|
|
317
|
+
*out_n = 0;
|
|
318
|
+
if (st->n_cols == 0) return TF_OK;
|
|
319
|
+
|
|
320
|
+
int *idx = tf_mallocarray_checked(st->n_cols ? st->n_cols : 1, sizeof(int));
|
|
321
|
+
if (!idx) return TF_ERROR;
|
|
322
|
+
for (size_t i = 0; i < st->n_cols; i++) {
|
|
323
|
+
idx[i] = tf_batch_col_index(in, st->cols[i]);
|
|
324
|
+
if (idx[i] < 0) {
|
|
325
|
+
char msg[256];
|
|
326
|
+
snprintf(msg, sizeof(msg), "rowid: column '%s' not found", st->cols[i]);
|
|
327
|
+
int err_rc = rowid_set_error(side, msg);
|
|
328
|
+
free(idx);
|
|
329
|
+
if (err_rc != TF_OK) return TF_ERROR;
|
|
330
|
+
return TF_ERROR;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
*out_indices = idx;
|
|
334
|
+
*out_n = st->n_cols;
|
|
335
|
+
return TF_OK;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
static int rowid_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
339
|
+
tf_side_channels *side) {
|
|
340
|
+
rowid_state *st = self->state;
|
|
341
|
+
*out = NULL;
|
|
342
|
+
|
|
343
|
+
int *col_indices = NULL;
|
|
344
|
+
size_t n_keys = 0;
|
|
345
|
+
if (resolve_columns(st, in, &col_indices, &n_keys, side) != TF_OK) return TF_ERROR;
|
|
346
|
+
|
|
347
|
+
size_t out_cols = 0;
|
|
348
|
+
if (tf_size_add(in->n_cols, 1, &out_cols) != TF_OK) {
|
|
349
|
+
free(col_indices);
|
|
350
|
+
return TF_ERROR;
|
|
351
|
+
}
|
|
352
|
+
tf_batch *ob = tf_batch_create(out_cols, in->n_rows);
|
|
353
|
+
if (!ob) { free(col_indices); return TF_ERROR; }
|
|
354
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
355
|
+
if (tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]) != TF_OK) {
|
|
356
|
+
tf_batch_free(ob);
|
|
357
|
+
free(col_indices);
|
|
358
|
+
return TF_ERROR;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (tf_batch_set_schema(ob, in->n_cols, st->result, TF_TYPE_INT64) != TF_OK) {
|
|
362
|
+
tf_batch_free(ob);
|
|
363
|
+
free(col_indices);
|
|
364
|
+
return TF_ERROR;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
368
|
+
int64_t row_number = 0;
|
|
369
|
+
if (n_keys == 0) {
|
|
370
|
+
if (st->global_count == INT64_MAX) {
|
|
371
|
+
tf_batch_free(ob);
|
|
372
|
+
free(col_indices);
|
|
373
|
+
return TF_ERROR;
|
|
374
|
+
}
|
|
375
|
+
st->global_count++;
|
|
376
|
+
row_number = st->global_count;
|
|
377
|
+
} else {
|
|
378
|
+
char *key = format_key(in, r, col_indices, n_keys);
|
|
379
|
+
if (!key) { tf_batch_free(ob); free(col_indices); return TF_ERROR; }
|
|
380
|
+
|
|
381
|
+
if (st->sorted) {
|
|
382
|
+
if (!st->seen_prev || strcmp(key, st->prev_key) != 0) {
|
|
383
|
+
free(st->prev_key);
|
|
384
|
+
st->prev_key = key;
|
|
385
|
+
st->seen_prev = 1;
|
|
386
|
+
st->current_group_count = 1;
|
|
387
|
+
if (rowid_check_state_bytes(st, side) != TF_OK) {
|
|
388
|
+
tf_batch_free(ob);
|
|
389
|
+
free(col_indices);
|
|
390
|
+
return TF_ERROR;
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
free(key);
|
|
394
|
+
if (st->current_group_count == INT64_MAX) {
|
|
395
|
+
tf_batch_free(ob);
|
|
396
|
+
free(col_indices);
|
|
397
|
+
return TF_ERROR;
|
|
398
|
+
}
|
|
399
|
+
st->current_group_count++;
|
|
400
|
+
}
|
|
401
|
+
row_number = st->current_group_count;
|
|
402
|
+
} else {
|
|
403
|
+
if (map_increment(st, key, &row_number, side) != TF_OK) {
|
|
404
|
+
tf_batch_free(ob);
|
|
405
|
+
free(col_indices);
|
|
406
|
+
return TF_ERROR;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK ||
|
|
412
|
+
tf_batch_set_int64(ob, r, in->n_cols, row_number) != TF_OK) {
|
|
413
|
+
tf_batch_free(ob);
|
|
414
|
+
free(col_indices);
|
|
415
|
+
return TF_ERROR;
|
|
416
|
+
}
|
|
417
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
418
|
+
tf_batch_free(ob);
|
|
419
|
+
free(col_indices);
|
|
420
|
+
return TF_ERROR;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
free(col_indices);
|
|
425
|
+
*out = ob;
|
|
426
|
+
return TF_OK;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
static int rowid_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
430
|
+
(void)self;
|
|
431
|
+
(void)side;
|
|
432
|
+
*out = NULL;
|
|
433
|
+
return TF_OK;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
static size_t rowid_prev_key_bytes(const rowid_state *st) {
|
|
437
|
+
size_t bytes = 0;
|
|
438
|
+
if (!st || !st->seen_prev || !st->prev_key) return 0;
|
|
439
|
+
if (tf_size_add(strlen(st->prev_key), 1, &bytes) != TF_OK) return SIZE_MAX;
|
|
440
|
+
return bytes;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
static size_t rowid_retained_state_bytes(const rowid_state *st) {
|
|
444
|
+
if (!st || st->n_cols == 0) return 0;
|
|
445
|
+
if (st->sorted) {
|
|
446
|
+
size_t bytes = rowid_prev_key_bytes(st);
|
|
447
|
+
if (st->seen_prev && tf_size_add(bytes, sizeof(int64_t), &bytes) != TF_OK) return SIZE_MAX;
|
|
448
|
+
return bytes;
|
|
449
|
+
}
|
|
450
|
+
size_t slot_width = 0;
|
|
451
|
+
size_t slot_bytes = 0;
|
|
452
|
+
size_t bytes = st->map.key_bytes;
|
|
453
|
+
if (tf_size_add(sizeof(char *), sizeof(int64_t), &slot_width) != TF_OK ||
|
|
454
|
+
tf_size_mul(st->map.cap, slot_width, &slot_bytes) != TF_OK ||
|
|
455
|
+
tf_size_add(bytes, slot_bytes, &bytes) != TF_OK) {
|
|
456
|
+
return SIZE_MAX;
|
|
457
|
+
}
|
|
458
|
+
return bytes;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static int rowid_append_stats(tf_step *self, tf_buffer *out) {
|
|
462
|
+
if (!self || !self->state || !out) return TF_ERROR;
|
|
463
|
+
rowid_state *st = self->state;
|
|
464
|
+
size_t tracked_keys = 0;
|
|
465
|
+
size_t tracked_key_bytes = 0;
|
|
466
|
+
if (st->n_cols > 0) {
|
|
467
|
+
tracked_keys = st->sorted ? (st->seen_prev ? 1u : 0u) : st->map.count;
|
|
468
|
+
tracked_key_bytes = st->sorted ? rowid_prev_key_bytes(st) : st->map.key_bytes;
|
|
469
|
+
}
|
|
470
|
+
size_t retained = rowid_retained_state_bytes(st);
|
|
471
|
+
char buf[300];
|
|
472
|
+
snprintf(buf, sizeof(buf),
|
|
473
|
+
",\"tracked_keys\":%zu,\"tracked_key_bytes\":%zu,"
|
|
474
|
+
"\"retained_state_bytes\":%zu,\"max_state_bytes\":%zu",
|
|
475
|
+
tracked_keys, tracked_key_bytes, retained, st->max_state_bytes);
|
|
476
|
+
return tf_buffer_write_str(out, buf);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
static void rowid_state_free(rowid_state *st) {
|
|
480
|
+
if (!st) return;
|
|
481
|
+
if (st->cols) {
|
|
482
|
+
for (size_t i = 0; i < st->n_cols; i++) free(st->cols[i]);
|
|
483
|
+
}
|
|
484
|
+
free(st->cols);
|
|
485
|
+
free(st->result);
|
|
486
|
+
free(st->prev_key);
|
|
487
|
+
for (size_t i = 0; i < st->map.cap; i++) free(st->map.keys ? st->map.keys[i] : NULL);
|
|
488
|
+
free(st->map.keys);
|
|
489
|
+
free(st->map.counts);
|
|
490
|
+
free(st);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
static void rowid_destroy(tf_step *self) {
|
|
494
|
+
if (!self) return;
|
|
495
|
+
rowid_state_free(self->state);
|
|
496
|
+
free(self);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
tf_step *tf_rowid_create(const cJSON *args) {
|
|
500
|
+
if (!args) return NULL;
|
|
501
|
+
|
|
502
|
+
rowid_state *st = calloc(1, sizeof(rowid_state));
|
|
503
|
+
if (!st) return NULL;
|
|
504
|
+
|
|
505
|
+
cJSON *cols = cJSON_GetObjectItemCaseSensitive(args, "columns");
|
|
506
|
+
if (cols) {
|
|
507
|
+
if (!cJSON_IsArray(cols)) {
|
|
508
|
+
tf_set_last_error("rowid: columns must be an array");
|
|
509
|
+
rowid_state_free(st);
|
|
510
|
+
return NULL;
|
|
511
|
+
}
|
|
512
|
+
int n = cJSON_GetArraySize(cols);
|
|
513
|
+
if (n < 0) { rowid_state_free(st); return NULL; }
|
|
514
|
+
st->n_cols = (size_t)n;
|
|
515
|
+
if (n > 0) {
|
|
516
|
+
st->cols = tf_callocarray_checked((size_t)n, sizeof(char *));
|
|
517
|
+
if (!st->cols) { rowid_state_free(st); return NULL; }
|
|
518
|
+
}
|
|
519
|
+
for (int i = 0; i < n; i++) {
|
|
520
|
+
cJSON *item = cJSON_GetArrayItem(cols, i);
|
|
521
|
+
if (!cJSON_IsString(item) || !item->valuestring || item->valuestring[0] == '\0') {
|
|
522
|
+
tf_set_last_error("rowid: column names must be non-empty strings");
|
|
523
|
+
rowid_state_free(st);
|
|
524
|
+
return NULL;
|
|
525
|
+
}
|
|
526
|
+
st->cols[i] = strdup(item->valuestring);
|
|
527
|
+
if (!st->cols[i]) { rowid_state_free(st); return NULL; }
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
cJSON *res = cJSON_GetObjectItemCaseSensitive(args, "result");
|
|
532
|
+
st->result = strdup(cJSON_IsString(res) && res->valuestring && res->valuestring[0] ? res->valuestring : "_rowid");
|
|
533
|
+
if (!st->result) { rowid_state_free(st); return NULL; }
|
|
534
|
+
|
|
535
|
+
cJSON *sorted = cJSON_GetObjectItemCaseSensitive(args, "sorted");
|
|
536
|
+
st->sorted = cJSON_IsBool(sorted) && cJSON_IsTrue(sorted);
|
|
537
|
+
|
|
538
|
+
size_t parsed_size = 0;
|
|
539
|
+
int has_max_keys = tf_json_get_size_arg(args, "max_keys",
|
|
540
|
+
1, TF_MAX_COUNT_ARG,
|
|
541
|
+
&parsed_size, "rowid");
|
|
542
|
+
if (has_max_keys < 0) { rowid_state_free(st); return NULL; }
|
|
543
|
+
if (has_max_keys > 0) st->max_keys = parsed_size;
|
|
544
|
+
|
|
545
|
+
int has_max_state = tf_json_get_size_arg(args, "max_state_bytes",
|
|
546
|
+
1, TF_MAX_STATE_BYTES,
|
|
547
|
+
&parsed_size, "rowid");
|
|
548
|
+
if (has_max_state < 0) { rowid_state_free(st); return NULL; }
|
|
549
|
+
if (has_max_state > 0) st->max_state_bytes = parsed_size;
|
|
550
|
+
|
|
551
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
552
|
+
if (!step) { rowid_state_free(st); return NULL; }
|
|
553
|
+
step->process = rowid_process;
|
|
554
|
+
step->flush = rowid_flush;
|
|
555
|
+
step->append_stats = rowid_append_stats;
|
|
556
|
+
step->destroy = rowid_destroy;
|
|
557
|
+
step->state = st;
|
|
558
|
+
return step;
|
|
559
|
+
}
|
package/csrc/op_sample.c
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_sample.c -- Reservoir sampling (Algorithm R). Bounded memory.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"n": 100, "seed": 123} or {"n": 100, "seed": "random"}
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include "internal.h"
|
|
8
|
+
#include "cJSON.h"
|
|
9
|
+
#include <stdint.h>
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
#include <string.h>
|
|
12
|
+
#include <time.h>
|
|
13
|
+
|
|
14
|
+
typedef struct {
|
|
15
|
+
size_t n;
|
|
16
|
+
tf_batch *buf;
|
|
17
|
+
size_t seen; /* total rows seen */
|
|
18
|
+
int has_schema;
|
|
19
|
+
uint64_t rng;
|
|
20
|
+
} sample_state;
|
|
21
|
+
|
|
22
|
+
static uint64_t sample_random_seed(void) {
|
|
23
|
+
uintptr_t stack_marker = 0;
|
|
24
|
+
uintptr_t stack_addr = (uintptr_t)&stack_marker;
|
|
25
|
+
return ((uint64_t)time(NULL) << 32) ^ (uint64_t)clock() ^ (uint64_t)stack_addr;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static uint64_t sample_next_u64(sample_state *st) {
|
|
29
|
+
st->rng += UINT64_C(0x9E3779B97F4A7C15);
|
|
30
|
+
uint64_t z = st->rng;
|
|
31
|
+
z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9);
|
|
32
|
+
z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
|
|
33
|
+
return z ^ (z >> 31);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static size_t sample_bounded(sample_state *st, size_t bound) {
|
|
37
|
+
if (bound <= 1) return 0;
|
|
38
|
+
uint64_t b = (uint64_t)bound;
|
|
39
|
+
uint64_t threshold = (uint64_t)(-b) % b;
|
|
40
|
+
for (;;) {
|
|
41
|
+
uint64_t r = sample_next_u64(st);
|
|
42
|
+
if (r >= threshold) return (size_t)(r % b);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static int sample_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
47
|
+
tf_side_channels *side) {
|
|
48
|
+
(void)side;
|
|
49
|
+
sample_state *st = self->state;
|
|
50
|
+
*out = NULL;
|
|
51
|
+
|
|
52
|
+
if (!st->has_schema) {
|
|
53
|
+
st->buf = tf_batch_create(in->n_cols, st->n);
|
|
54
|
+
if (!st->buf) return TF_ERROR;
|
|
55
|
+
if (tf_batch_clone_schema(st->buf, in) != TF_OK) return TF_ERROR;
|
|
56
|
+
st->has_schema = 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
60
|
+
if (st->seen == SIZE_MAX) {
|
|
61
|
+
tf_set_last_error("sample: row count overflow");
|
|
62
|
+
return TF_ERROR;
|
|
63
|
+
}
|
|
64
|
+
if (st->seen < st->n) {
|
|
65
|
+
if (tf_batch_copy_row(st->buf, st->seen, in, r) != TF_OK) return TF_ERROR;
|
|
66
|
+
if (tf_batch_expose_row(st->buf, st->seen) != TF_OK) return TF_ERROR;
|
|
67
|
+
} else {
|
|
68
|
+
size_t j = sample_bounded(st, st->seen + 1);
|
|
69
|
+
if (j < st->n && tf_batch_copy_row(st->buf, j, in, r) != TF_OK)
|
|
70
|
+
return TF_ERROR;
|
|
71
|
+
}
|
|
72
|
+
st->seen++;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return TF_OK;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static int sample_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
79
|
+
(void)side;
|
|
80
|
+
sample_state *st = self->state;
|
|
81
|
+
*out = NULL;
|
|
82
|
+
|
|
83
|
+
if (!st->buf || st->buf->n_rows == 0) return TF_OK;
|
|
84
|
+
|
|
85
|
+
size_t n = st->buf->n_rows;
|
|
86
|
+
tf_batch *ob = tf_batch_create(st->buf->n_cols, n);
|
|
87
|
+
if (!ob) return TF_ERROR;
|
|
88
|
+
if (tf_batch_clone_schema(ob, st->buf) != TF_OK) {
|
|
89
|
+
tf_batch_free(ob);
|
|
90
|
+
return TF_ERROR;
|
|
91
|
+
}
|
|
92
|
+
for (size_t i = 0; i < n; i++) {
|
|
93
|
+
if (tf_batch_copy_row(ob, i, st->buf, i) != TF_OK) {
|
|
94
|
+
tf_batch_free(ob);
|
|
95
|
+
return TF_ERROR;
|
|
96
|
+
}
|
|
97
|
+
if (tf_batch_expose_row(ob, i) != TF_OK) {
|
|
98
|
+
tf_batch_free(ob);
|
|
99
|
+
return TF_ERROR;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
*out = ob;
|
|
104
|
+
return TF_OK;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static void sample_destroy(tf_step *self) {
|
|
108
|
+
sample_state *st = self->state;
|
|
109
|
+
if (st) {
|
|
110
|
+
if (st->buf) tf_batch_free(st->buf);
|
|
111
|
+
free(st);
|
|
112
|
+
}
|
|
113
|
+
free(self);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
tf_step *tf_sample_create(const cJSON *args) {
|
|
117
|
+
if (!args) return NULL;
|
|
118
|
+
size_t n = 0;
|
|
119
|
+
int has_n = tf_json_get_size_arg(args, "n", 1, TF_MAX_OUTPUT_ROWS_PER_BATCH, &n, "sample");
|
|
120
|
+
if (has_n <= 0) return NULL;
|
|
121
|
+
|
|
122
|
+
sample_state *st = calloc(1, sizeof(sample_state));
|
|
123
|
+
if (!st) return NULL;
|
|
124
|
+
st->n = n;
|
|
125
|
+
st->rng = 0;
|
|
126
|
+
|
|
127
|
+
cJSON *seed_json = cJSON_GetObjectItemCaseSensitive(args, "seed");
|
|
128
|
+
if (seed_json) {
|
|
129
|
+
if (cJSON_IsString(seed_json)) {
|
|
130
|
+
if (strcmp(seed_json->valuestring, "random") != 0) {
|
|
131
|
+
tf_set_last_error("sample: seed must be an integer or 'random'");
|
|
132
|
+
free(st);
|
|
133
|
+
return NULL;
|
|
134
|
+
}
|
|
135
|
+
st->rng = sample_random_seed();
|
|
136
|
+
} else if (cJSON_IsNumber(seed_json) && seed_json->valuedouble >= 0.0) {
|
|
137
|
+
size_t parsed = 0;
|
|
138
|
+
if (tf_json_size_value(seed_json, "seed", 0, TF_MAX_SAFE_SIZE_ARG,
|
|
139
|
+
&parsed, "sample") < 0) {
|
|
140
|
+
free(st);
|
|
141
|
+
return NULL;
|
|
142
|
+
}
|
|
143
|
+
st->rng = (uint64_t)parsed;
|
|
144
|
+
} else {
|
|
145
|
+
tf_set_last_error("sample: seed must be an integer or 'random'");
|
|
146
|
+
free(st);
|
|
147
|
+
return NULL;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
152
|
+
if (!step) { free(st); return NULL; }
|
|
153
|
+
step->process = sample_process;
|
|
154
|
+
step->flush = sample_flush;
|
|
155
|
+
step->destroy = sample_destroy;
|
|
156
|
+
step->state = st;
|
|
157
|
+
return step;
|
|
158
|
+
}
|