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_step.c
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_step.c — Running aggregations: running-sum, running-avg, running-min,
|
|
3
|
+
* running-max, running-count, delta, lag, ratio.
|
|
4
|
+
*
|
|
5
|
+
* Config: {"column": "price", "func": "running-sum", "result": "cumsum"}
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "internal.h"
|
|
9
|
+
#include "cJSON.h"
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
#include <string.h>
|
|
12
|
+
#include <stdio.h>
|
|
13
|
+
#include <math.h>
|
|
14
|
+
#include <stdint.h>
|
|
15
|
+
|
|
16
|
+
typedef enum {
|
|
17
|
+
STEP_RUNNING_SUM,
|
|
18
|
+
STEP_RUNNING_AVG,
|
|
19
|
+
STEP_RUNNING_MIN,
|
|
20
|
+
STEP_RUNNING_MAX,
|
|
21
|
+
STEP_RUNNING_COUNT,
|
|
22
|
+
STEP_DELTA,
|
|
23
|
+
STEP_LAG,
|
|
24
|
+
STEP_RATIO,
|
|
25
|
+
} step_func;
|
|
26
|
+
|
|
27
|
+
typedef enum {
|
|
28
|
+
STEP_MISSING_ERROR,
|
|
29
|
+
STEP_MISSING_NULL,
|
|
30
|
+
STEP_MISSING_IGNORE,
|
|
31
|
+
} step_missing_policy;
|
|
32
|
+
|
|
33
|
+
typedef enum {
|
|
34
|
+
STEP_TYPE_FAIL,
|
|
35
|
+
STEP_TYPE_NULL,
|
|
36
|
+
} step_type_policy;
|
|
37
|
+
|
|
38
|
+
typedef struct {
|
|
39
|
+
char *column;
|
|
40
|
+
char *result;
|
|
41
|
+
step_func func;
|
|
42
|
+
double running_sum;
|
|
43
|
+
double running_min;
|
|
44
|
+
double running_max;
|
|
45
|
+
size_t running_count;
|
|
46
|
+
double prev_val;
|
|
47
|
+
int has_prev;
|
|
48
|
+
step_missing_policy missing;
|
|
49
|
+
step_type_policy on_type_error;
|
|
50
|
+
} step_state;
|
|
51
|
+
|
|
52
|
+
static int parse_func(const char *s, step_func *out) {
|
|
53
|
+
if (!s || !out) return TF_ERROR;
|
|
54
|
+
if (strcmp(s, "running-sum") == 0 || strcmp(s, "cumsum") == 0) *out = STEP_RUNNING_SUM;
|
|
55
|
+
else if (strcmp(s, "running-avg") == 0 || strcmp(s, "cumavg") == 0) *out = STEP_RUNNING_AVG;
|
|
56
|
+
else if (strcmp(s, "running-min") == 0) *out = STEP_RUNNING_MIN;
|
|
57
|
+
else if (strcmp(s, "running-max") == 0) *out = STEP_RUNNING_MAX;
|
|
58
|
+
else if (strcmp(s, "running-count") == 0) *out = STEP_RUNNING_COUNT;
|
|
59
|
+
else if (strcmp(s, "delta") == 0) *out = STEP_DELTA;
|
|
60
|
+
else if (strcmp(s, "lag") == 0) *out = STEP_LAG;
|
|
61
|
+
else if (strcmp(s, "ratio") == 0) *out = STEP_RATIO;
|
|
62
|
+
else return TF_ERROR;
|
|
63
|
+
return TF_OK;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static int step_is_numeric_func(step_func func) {
|
|
67
|
+
return func != STEP_RUNNING_COUNT;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static int step_is_numeric_type(tf_type type) {
|
|
71
|
+
return type == TF_TYPE_INT64 || type == TF_TYPE_FLOAT64;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static double get_numeric(const tf_batch *b, size_t r, int ci) {
|
|
75
|
+
if (b->col_types[ci] == TF_TYPE_INT64) return (double)tf_batch_get_int64(b, r, ci);
|
|
76
|
+
return tf_batch_get_float64(b, r, ci);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static void step_set_col_error(const char *column, const char *suffix) {
|
|
80
|
+
char msg[512];
|
|
81
|
+
snprintf(msg, sizeof(msg), "step: column '%s' %s", column ? column : "", suffix);
|
|
82
|
+
tf_set_last_error(msg);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static int step_parse_missing_policy(const cJSON *args, step_missing_policy *out) {
|
|
86
|
+
*out = STEP_MISSING_ERROR;
|
|
87
|
+
const cJSON *j = cJSON_GetObjectItemCaseSensitive(args, "missing");
|
|
88
|
+
if (!j) return TF_OK;
|
|
89
|
+
if (!cJSON_IsString(j)) {
|
|
90
|
+
tf_set_last_error("step: missing must be error, null, or ignore");
|
|
91
|
+
return TF_ERROR;
|
|
92
|
+
}
|
|
93
|
+
if (strcmp(j->valuestring, "error") == 0) *out = STEP_MISSING_ERROR;
|
|
94
|
+
else if (strcmp(j->valuestring, "null") == 0) *out = STEP_MISSING_NULL;
|
|
95
|
+
else if (strcmp(j->valuestring, "ignore") == 0) *out = STEP_MISSING_IGNORE;
|
|
96
|
+
else {
|
|
97
|
+
tf_set_last_error("step: missing must be error, null, or ignore");
|
|
98
|
+
return TF_ERROR;
|
|
99
|
+
}
|
|
100
|
+
return TF_OK;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static int step_parse_type_policy(const cJSON *args, step_type_policy *out) {
|
|
104
|
+
*out = STEP_TYPE_FAIL;
|
|
105
|
+
const cJSON *j = cJSON_GetObjectItemCaseSensitive(args, "on_type_error");
|
|
106
|
+
if (!j) return TF_OK;
|
|
107
|
+
if (!cJSON_IsString(j)) {
|
|
108
|
+
tf_set_last_error("step: on_type_error must be fail or null");
|
|
109
|
+
return TF_ERROR;
|
|
110
|
+
}
|
|
111
|
+
if (strcmp(j->valuestring, "fail") == 0) *out = STEP_TYPE_FAIL;
|
|
112
|
+
else if (strcmp(j->valuestring, "null") == 0) *out = STEP_TYPE_NULL;
|
|
113
|
+
else {
|
|
114
|
+
tf_set_last_error("step: on_type_error must be fail or null");
|
|
115
|
+
return TF_ERROR;
|
|
116
|
+
}
|
|
117
|
+
return TF_OK;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static int step_passthrough(tf_batch *in, tf_batch **out) {
|
|
121
|
+
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
122
|
+
if (!ob) return TF_ERROR;
|
|
123
|
+
if (tf_batch_clone_schema(ob, in) != TF_OK) {
|
|
124
|
+
tf_batch_free(ob);
|
|
125
|
+
return TF_ERROR;
|
|
126
|
+
}
|
|
127
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
128
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK ||
|
|
129
|
+
tf_batch_expose_row(ob, r) != TF_OK) {
|
|
130
|
+
tf_batch_free(ob);
|
|
131
|
+
return TF_ERROR;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
*out = ob;
|
|
135
|
+
return TF_OK;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static int step_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
139
|
+
tf_side_channels *side) {
|
|
140
|
+
(void)side;
|
|
141
|
+
step_state *st = self->state;
|
|
142
|
+
*out = NULL;
|
|
143
|
+
|
|
144
|
+
int ci = tf_batch_col_index(in, st->column);
|
|
145
|
+
int force_null = 0;
|
|
146
|
+
if (ci < 0) {
|
|
147
|
+
if (st->missing == STEP_MISSING_ERROR) {
|
|
148
|
+
step_set_col_error(st->column, "not found");
|
|
149
|
+
return TF_ERROR;
|
|
150
|
+
}
|
|
151
|
+
if (st->missing == STEP_MISSING_IGNORE) {
|
|
152
|
+
return step_passthrough(in, out);
|
|
153
|
+
}
|
|
154
|
+
force_null = 1;
|
|
155
|
+
} else if (step_is_numeric_func(st->func) &&
|
|
156
|
+
!step_is_numeric_type(in->col_types[(size_t)ci])) {
|
|
157
|
+
if (st->on_type_error == STEP_TYPE_FAIL) {
|
|
158
|
+
step_set_col_error(st->column, "must be numeric");
|
|
159
|
+
return TF_ERROR;
|
|
160
|
+
}
|
|
161
|
+
force_null = 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const char *extra_names[1] = {st->result};
|
|
165
|
+
tf_type extra_types[1] = {TF_TYPE_FLOAT64};
|
|
166
|
+
tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
|
|
167
|
+
if (!ob) return TF_ERROR;
|
|
168
|
+
if (tf_batch_clone_with_extra_cols(ob, in, extra_names, extra_types, 1) != TF_OK) {
|
|
169
|
+
tf_batch_free(ob);
|
|
170
|
+
return TF_ERROR;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
174
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
175
|
+
tf_batch_free(ob);
|
|
176
|
+
return TF_ERROR;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (force_null || tf_batch_is_null(in, r, (size_t)ci)) {
|
|
180
|
+
if (tf_batch_set_null(ob, r, in->n_cols) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
181
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
double val = step_is_numeric_func(st->func) ? get_numeric(in, r, ci) : 0.0;
|
|
186
|
+
double result = 0;
|
|
187
|
+
double next_running_sum = st->running_sum;
|
|
188
|
+
double next_running_min = st->running_min;
|
|
189
|
+
double next_running_max = st->running_max;
|
|
190
|
+
size_t next_running_count = st->running_count;
|
|
191
|
+
double next_prev_val = st->prev_val;
|
|
192
|
+
int next_has_prev = st->has_prev;
|
|
193
|
+
int write_null = 0;
|
|
194
|
+
|
|
195
|
+
switch (st->func) {
|
|
196
|
+
case STEP_RUNNING_SUM:
|
|
197
|
+
next_running_sum += val;
|
|
198
|
+
result = next_running_sum;
|
|
199
|
+
break;
|
|
200
|
+
case STEP_RUNNING_AVG:
|
|
201
|
+
if (next_running_count == SIZE_MAX) { tf_batch_free(ob); return TF_ERROR; }
|
|
202
|
+
next_running_sum += val;
|
|
203
|
+
next_running_count++;
|
|
204
|
+
result = next_running_sum / next_running_count;
|
|
205
|
+
break;
|
|
206
|
+
case STEP_RUNNING_MIN:
|
|
207
|
+
if (next_running_count == SIZE_MAX) { tf_batch_free(ob); return TF_ERROR; }
|
|
208
|
+
if (next_running_count == 0 || val < next_running_min)
|
|
209
|
+
next_running_min = val;
|
|
210
|
+
next_running_count++;
|
|
211
|
+
result = next_running_min;
|
|
212
|
+
break;
|
|
213
|
+
case STEP_RUNNING_MAX:
|
|
214
|
+
if (next_running_count == SIZE_MAX) { tf_batch_free(ob); return TF_ERROR; }
|
|
215
|
+
if (next_running_count == 0 || val > next_running_max)
|
|
216
|
+
next_running_max = val;
|
|
217
|
+
next_running_count++;
|
|
218
|
+
result = next_running_max;
|
|
219
|
+
break;
|
|
220
|
+
case STEP_RUNNING_COUNT:
|
|
221
|
+
if (next_running_count == SIZE_MAX) { tf_batch_free(ob); return TF_ERROR; }
|
|
222
|
+
next_running_count++;
|
|
223
|
+
result = (double)next_running_count;
|
|
224
|
+
break;
|
|
225
|
+
case STEP_DELTA:
|
|
226
|
+
if (st->has_prev) result = val - st->prev_val;
|
|
227
|
+
else write_null = 1;
|
|
228
|
+
next_prev_val = val;
|
|
229
|
+
next_has_prev = 1;
|
|
230
|
+
break;
|
|
231
|
+
case STEP_LAG:
|
|
232
|
+
if (st->has_prev) result = st->prev_val;
|
|
233
|
+
else write_null = 1;
|
|
234
|
+
next_prev_val = val;
|
|
235
|
+
next_has_prev = 1;
|
|
236
|
+
break;
|
|
237
|
+
case STEP_RATIO:
|
|
238
|
+
if (st->has_prev && st->prev_val != 0) result = val / st->prev_val;
|
|
239
|
+
else write_null = 1;
|
|
240
|
+
next_prev_val = val;
|
|
241
|
+
next_has_prev = 1;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (write_null) {
|
|
246
|
+
if (tf_batch_set_null(ob, r, in->n_cols) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
247
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
248
|
+
st->prev_val = next_prev_val;
|
|
249
|
+
st->has_prev = next_has_prev;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (tf_batch_set_float64(ob, r, in->n_cols, result) != TF_OK) {
|
|
254
|
+
tf_batch_free(ob);
|
|
255
|
+
return TF_ERROR;
|
|
256
|
+
}
|
|
257
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
258
|
+
tf_batch_free(ob);
|
|
259
|
+
return TF_ERROR;
|
|
260
|
+
}
|
|
261
|
+
st->running_sum = next_running_sum;
|
|
262
|
+
st->running_min = next_running_min;
|
|
263
|
+
st->running_max = next_running_max;
|
|
264
|
+
st->running_count = next_running_count;
|
|
265
|
+
st->prev_val = next_prev_val;
|
|
266
|
+
st->has_prev = next_has_prev;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
*out = ob;
|
|
270
|
+
return TF_OK;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static int step_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
274
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
static void step_destroy(tf_step *self) {
|
|
278
|
+
step_state *st = self->state;
|
|
279
|
+
if (st) { free(st->column); free(st->result); free(st); }
|
|
280
|
+
free(self);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
static char *step_default_result_name(const char *column, const char *func) {
|
|
284
|
+
char *suffix = tf_string_append_suffix_checked("_", func);
|
|
285
|
+
if (!suffix) return NULL;
|
|
286
|
+
char *result = tf_string_append_suffix_checked(column, suffix);
|
|
287
|
+
free(suffix);
|
|
288
|
+
return result;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
tf_step *tf_step_create(const cJSON *args) {
|
|
292
|
+
if (!args) return NULL;
|
|
293
|
+
cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
294
|
+
cJSON *func_j = cJSON_GetObjectItemCaseSensitive(args, "func");
|
|
295
|
+
if (!cJSON_IsString(col_j) || !col_j->valuestring[0] ||
|
|
296
|
+
!cJSON_IsString(func_j) || !func_j->valuestring[0]) {
|
|
297
|
+
tf_set_last_error("step: column and func are required");
|
|
298
|
+
return NULL;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
step_state *st = tf_callocarray_checked(1, sizeof(step_state));
|
|
302
|
+
if (!st) return NULL;
|
|
303
|
+
st->column = tf_strdup_checked(col_j->valuestring);
|
|
304
|
+
if (!st->column) { free(st); return NULL; }
|
|
305
|
+
if (parse_func(func_j->valuestring, &st->func) != TF_OK) {
|
|
306
|
+
tf_set_last_error("step: func must be running-sum, running-avg, running-min, running-max, running-count, delta, lag, or ratio");
|
|
307
|
+
free(st->column);
|
|
308
|
+
free(st);
|
|
309
|
+
return NULL;
|
|
310
|
+
}
|
|
311
|
+
if (step_parse_missing_policy(args, &st->missing) != TF_OK ||
|
|
312
|
+
step_parse_type_policy(args, &st->on_type_error) != TF_OK) {
|
|
313
|
+
free(st->column);
|
|
314
|
+
free(st);
|
|
315
|
+
return NULL;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
|
|
319
|
+
if (cJSON_IsString(res_j)) {
|
|
320
|
+
st->result = tf_strdup_checked(res_j->valuestring);
|
|
321
|
+
} else {
|
|
322
|
+
st->result = step_default_result_name(st->column, func_j->valuestring);
|
|
323
|
+
}
|
|
324
|
+
if (!st->result) { free(st->column); free(st); return NULL; }
|
|
325
|
+
|
|
326
|
+
tf_step *step = tf_callocarray_checked(1, sizeof(tf_step));
|
|
327
|
+
if (!step) { free(st->column); free(st->result); free(st); return NULL; }
|
|
328
|
+
step->process = step_process;
|
|
329
|
+
step->flush = step_flush;
|
|
330
|
+
step->destroy = step_destroy;
|
|
331
|
+
step->state = st;
|
|
332
|
+
return step;
|
|
333
|
+
}
|
package/csrc/op_tail.c
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_tail.c — Last N rows via circular buffer. Bounded memory.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"n": 5}
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include "internal.h"
|
|
8
|
+
#include "cJSON.h"
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
#include <string.h>
|
|
11
|
+
|
|
12
|
+
typedef struct {
|
|
13
|
+
size_t limit;
|
|
14
|
+
tf_batch *buf; /* circular buffer of rows */
|
|
15
|
+
size_t head; /* next write position */
|
|
16
|
+
size_t count; /* total rows seen */
|
|
17
|
+
int has_schema;
|
|
18
|
+
} tail_state;
|
|
19
|
+
|
|
20
|
+
static int tail_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
21
|
+
tf_side_channels *side) {
|
|
22
|
+
(void)side;
|
|
23
|
+
tail_state *st = self->state;
|
|
24
|
+
*out = NULL;
|
|
25
|
+
|
|
26
|
+
if (!st->has_schema) {
|
|
27
|
+
st->buf = tf_batch_create(in->n_cols, st->limit);
|
|
28
|
+
if (!st->buf) return TF_ERROR;
|
|
29
|
+
if (tf_batch_clone_schema(st->buf, in) != TF_OK) return TF_ERROR;
|
|
30
|
+
st->buf->n_rows = 0;
|
|
31
|
+
st->has_schema = 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
35
|
+
size_t dst = st->head % st->limit;
|
|
36
|
+
if (tf_batch_copy_row(st->buf, dst, in, r) != TF_OK) return TF_ERROR;
|
|
37
|
+
st->head++;
|
|
38
|
+
st->count++;
|
|
39
|
+
if (st->buf->n_rows < st->limit &&
|
|
40
|
+
tf_batch_expose_row(st->buf, st->buf->n_rows) != TF_OK) return TF_ERROR;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return TF_OK;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static int tail_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
47
|
+
(void)side;
|
|
48
|
+
tail_state *st = self->state;
|
|
49
|
+
*out = NULL;
|
|
50
|
+
|
|
51
|
+
if (!st->buf || st->buf->n_rows == 0) return TF_OK;
|
|
52
|
+
|
|
53
|
+
size_t n = st->buf->n_rows;
|
|
54
|
+
tf_batch *ob = tf_batch_create(st->buf->n_cols, n);
|
|
55
|
+
if (!ob) return TF_ERROR;
|
|
56
|
+
if (tf_batch_clone_schema(ob, st->buf) != TF_OK) {
|
|
57
|
+
tf_batch_free(ob);
|
|
58
|
+
return TF_ERROR;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Read from circular buffer in order */
|
|
62
|
+
size_t start = (st->head >= n) ? (st->head - n) % st->limit : 0;
|
|
63
|
+
for (size_t i = 0; i < n; i++) {
|
|
64
|
+
size_t src = (start + i) % st->limit;
|
|
65
|
+
if (tf_batch_copy_row(ob, i, st->buf, src) != TF_OK) {
|
|
66
|
+
tf_batch_free(ob);
|
|
67
|
+
return TF_ERROR;
|
|
68
|
+
}
|
|
69
|
+
if (tf_batch_expose_row(ob, i) != TF_OK) {
|
|
70
|
+
tf_batch_free(ob);
|
|
71
|
+
return TF_ERROR;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
*out = ob;
|
|
76
|
+
return TF_OK;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static void tail_destroy(tf_step *self) {
|
|
80
|
+
tail_state *st = self->state;
|
|
81
|
+
if (st) {
|
|
82
|
+
if (st->buf) tf_batch_free(st->buf);
|
|
83
|
+
free(st);
|
|
84
|
+
}
|
|
85
|
+
free(self);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
tf_step *tf_tail_create(const cJSON *args) {
|
|
89
|
+
if (!args) return NULL;
|
|
90
|
+
size_t n = 0;
|
|
91
|
+
int has_n = tf_json_get_size_arg(args, "n", 1, TF_MAX_OUTPUT_ROWS_PER_BATCH, &n, "tail");
|
|
92
|
+
if (has_n <= 0) return NULL;
|
|
93
|
+
|
|
94
|
+
tail_state *st = calloc(1, sizeof(tail_state));
|
|
95
|
+
if (!st) return NULL;
|
|
96
|
+
st->limit = n;
|
|
97
|
+
|
|
98
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
99
|
+
if (!step) { free(st); return NULL; }
|
|
100
|
+
step->process = tail_process;
|
|
101
|
+
step->flush = tail_flush;
|
|
102
|
+
step->destroy = tail_destroy;
|
|
103
|
+
step->state = st;
|
|
104
|
+
return step;
|
|
105
|
+
}
|