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.
Files changed (151) hide show
  1. package/LICENSE +177 -21
  2. package/NOTICE +8 -0
  3. package/README.md +627 -0
  4. package/app/assets/index-6quYZ5Ap.css +5 -0
  5. package/app/assets/index-BIAIKnrp.js +160 -0
  6. package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  7. package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  8. package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  9. package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  10. package/app/index.html +13 -0
  11. package/binding.gyp +121 -0
  12. package/csrc/arena.c +93 -0
  13. package/csrc/batch.c +976 -0
  14. package/csrc/buffer.c +154 -0
  15. package/csrc/cJSON.c +3386 -0
  16. package/csrc/cJSON.h +316 -0
  17. package/csrc/codec_csv.c +1951 -0
  18. package/csrc/codec_jsonl.c +1086 -0
  19. package/csrc/codec_table.c +248 -0
  20. package/csrc/codec_text.c +447 -0
  21. package/csrc/compiler.c +130 -0
  22. package/csrc/config.h +21 -0
  23. package/csrc/date_utils.h +94 -0
  24. package/csrc/dsl.c +5417 -0
  25. package/csrc/dsl.h +22 -0
  26. package/csrc/expr.c +1553 -0
  27. package/csrc/expr.h +58 -0
  28. package/csrc/internal.h +539 -0
  29. package/csrc/ir.c +166 -0
  30. package/csrc/ir.h +208 -0
  31. package/csrc/ir_schema.c +75 -0
  32. package/csrc/ir_serialize.c +166 -0
  33. package/csrc/ir_sql.c +1822 -0
  34. package/csrc/ir_validate.c +576 -0
  35. package/csrc/json_path.c +210 -0
  36. package/csrc/main.c +1241 -0
  37. package/csrc/memory_estimate.c +477 -0
  38. package/csrc/op_acf.c +283 -0
  39. package/csrc/op_across.c +477 -0
  40. package/csrc/op_anomaly.c +255 -0
  41. package/csrc/op_assert.c +761 -0
  42. package/csrc/op_bin.c +248 -0
  43. package/csrc/op_cast.c +523 -0
  44. package/csrc/op_clip.c +99 -0
  45. package/csrc/op_date_trunc.c +355 -0
  46. package/csrc/op_datetime.c +394 -0
  47. package/csrc/op_derive.c +216 -0
  48. package/csrc/op_diff.c +250 -0
  49. package/csrc/op_ewma.c +222 -0
  50. package/csrc/op_explode.c +206 -0
  51. package/csrc/op_fill_down.c +235 -0
  52. package/csrc/op_fill_null.c +268 -0
  53. package/csrc/op_filter.c +181 -0
  54. package/csrc/op_frequency.c +721 -0
  55. package/csrc/op_grep.c +181 -0
  56. package/csrc/op_group_agg.c +1956 -0
  57. package/csrc/op_hash.c +159 -0
  58. package/csrc/op_head.c +84 -0
  59. package/csrc/op_interpolate.c +445 -0
  60. package/csrc/op_join.c +2902 -0
  61. package/csrc/op_json_extract.c +227 -0
  62. package/csrc/op_json_filter.c +384 -0
  63. package/csrc/op_json_flatten.c +293 -0
  64. package/csrc/op_json_schema.c +503 -0
  65. package/csrc/op_label_encode.c +419 -0
  66. package/csrc/op_lag.c +181 -0
  67. package/csrc/op_lead.c +242 -0
  68. package/csrc/op_normalize.c +510 -0
  69. package/csrc/op_onehot.c +457 -0
  70. package/csrc/op_pivot.c +1754 -0
  71. package/csrc/op_quarantine.c +189 -0
  72. package/csrc/op_registry.c +3044 -0
  73. package/csrc/op_rename.c +129 -0
  74. package/csrc/op_replace.c +354 -0
  75. package/csrc/op_rleid.c +297 -0
  76. package/csrc/op_rowid.c +559 -0
  77. package/csrc/op_sample.c +158 -0
  78. package/csrc/op_schema.c +1341 -0
  79. package/csrc/op_schema_infer.c +252 -0
  80. package/csrc/op_select.c +340 -0
  81. package/csrc/op_set.c +3449 -0
  82. package/csrc/op_skip.c +95 -0
  83. package/csrc/op_sort.c +819 -0
  84. package/csrc/op_source_name.c +120 -0
  85. package/csrc/op_split.c +151 -0
  86. package/csrc/op_split_data.c +119 -0
  87. package/csrc/op_stack.c +271 -0
  88. package/csrc/op_stats.c +875 -0
  89. package/csrc/op_step.c +333 -0
  90. package/csrc/op_tail.c +105 -0
  91. package/csrc/op_tee.c +338 -0
  92. package/csrc/op_top.c +357 -0
  93. package/csrc/op_trim.c +138 -0
  94. package/csrc/op_unique.c +1343 -0
  95. package/csrc/op_unpivot.c +193 -0
  96. package/csrc/op_validate.c +648 -0
  97. package/csrc/op_window.c +591 -0
  98. package/csrc/path_policy.c +85 -0
  99. package/csrc/pipeline.c +1088 -0
  100. package/csrc/recipes.c +104 -0
  101. package/csrc/recipes.h +27 -0
  102. package/csrc/report.c +506 -0
  103. package/csrc/report.h +22 -0
  104. package/csrc/selector.c +1097 -0
  105. package/csrc/size_utils.c +348 -0
  106. package/csrc/spill.c +317 -0
  107. package/csrc/spill.h +21 -0
  108. package/csrc/tranfi.h +291 -0
  109. package/csrc/transform.h +209 -0
  110. package/csrc/transform_api.c +2237 -0
  111. package/csrc/transform_categorical.c +923 -0
  112. package/csrc/transform_internal.h +472 -0
  113. package/csrc/transform_json.c +3812 -0
  114. package/csrc/transform_numeric.c +1966 -0
  115. package/csrc/transform_sha256.c +154 -0
  116. package/csrc/transform_wasm.h +162 -0
  117. package/csrc/transform_wasm_api.c +1373 -0
  118. package/csrc/wasm_api.c +218 -0
  119. package/napi_api.c +534 -0
  120. package/napi_transform.c +1648 -0
  121. package/napi_transform.h +8 -0
  122. package/package.json +64 -59
  123. package/scripts/install-native.js +76 -0
  124. package/scripts/prepack.js +64 -0
  125. package/scripts/sync-csrc.js +23 -0
  126. package/src/cli.js +190 -0
  127. package/src/engines/duckdb.js +142 -0
  128. package/src/index.js +925 -0
  129. package/src/memory_policy.js +411 -0
  130. package/src/native.js +18 -0
  131. package/src/pipeline.js +709 -0
  132. package/src/recipe_json.js +80 -0
  133. package/src/server.js +279 -0
  134. package/src/transform.js +403 -0
  135. package/src/transform_error.js +10 -0
  136. package/src/wasm.js +21 -0
  137. package/wasm/index.js +732 -0
  138. package/wasm/package.json +1 -0
  139. package/wasm/tranfi_core.js +0 -0
  140. package/wasm/transform.js +1156 -0
  141. package/wasm/worker.js +786 -0
  142. package/dist/bundle.js +0 -1
  143. package/index.html +0 -18
  144. package/src/app.css +0 -169
  145. package/src/app.js +0 -203
  146. package/src/app.vue +0 -250
  147. package/src/bulma-input.vue +0 -110
  148. package/src/common-inputs.js +0 -28
  149. package/src/main.js +0 -20
  150. package/src/transforms.js +0 -166
  151. package/webpack.config.js +0 -108
@@ -0,0 +1,477 @@
1
+ /*
2
+ * op_across.c -- Apply deterministic row-local functions across selected columns.
3
+ *
4
+ * Config:
5
+ * {"columns":["starts_with(score_)"], "fn":"round"}
6
+ * {"columns":["name"], "functions":["trim","lower"], "replace":false,
7
+ * "names":"{col}_{fn}"}
8
+ *
9
+ * This is a compact Tranfi subset of dplyr across(): selector expansion plus a
10
+ * whitelist of scalar functions. It is not a lambda or grouped-evaluation runtime.
11
+ */
12
+
13
+ #include "internal.h"
14
+ #include "cJSON.h"
15
+ #include <ctype.h>
16
+ #include <limits.h>
17
+ #include <math.h>
18
+ #include <stdint.h>
19
+ #include <stdio.h>
20
+ #include <stdlib.h>
21
+ #include <string.h>
22
+ #include <strings.h>
23
+
24
+ typedef enum {
25
+ ACROSS_FN_TRIM,
26
+ ACROSS_FN_LOWER,
27
+ ACROSS_FN_UPPER,
28
+ ACROSS_FN_ABS,
29
+ ACROSS_FN_ROUND,
30
+ ACROSS_FN_FLOOR,
31
+ ACROSS_FN_CEIL,
32
+ ACROSS_FN_SQRT,
33
+ ACROSS_FN_LOG,
34
+ ACROSS_FN_EXP
35
+ } across_fn_kind;
36
+
37
+ typedef struct {
38
+ char **selectors;
39
+ size_t n_selectors;
40
+ char **fn_names;
41
+ across_fn_kind *fns;
42
+ size_t n_fns;
43
+ char *names_template;
44
+ int replace;
45
+ } across_state;
46
+
47
+ static int across_write_error(tf_side_channels *side, const char *msg,
48
+ const char *column, const char *fn) {
49
+ if (!side || !side->errors) return TF_OK;
50
+ cJSON *obj = cJSON_CreateObject();
51
+ if (!obj) return TF_ERROR;
52
+ int rc = TF_ERROR;
53
+ if (tf_json_add_string(obj, "op", "across") != TF_OK ||
54
+ tf_json_add_string(obj, "error", msg ? msg : "across error") != TF_OK) {
55
+ goto done;
56
+ }
57
+ if (column && tf_json_add_string(obj, "column", column) != TF_OK) goto done;
58
+ if (fn && tf_json_add_string(obj, "function", fn) != TF_OK) goto done;
59
+ rc = tf_buffer_write_json_line(side->errors, obj);
60
+ done:
61
+ cJSON_Delete(obj);
62
+ return rc;
63
+ }
64
+
65
+ static int across_fn_from_name(const char *name, across_fn_kind *out) {
66
+ if (!name || !out) return TF_ERROR;
67
+ if (strcasecmp(name, "trim") == 0) { *out = ACROSS_FN_TRIM; return TF_OK; }
68
+ if (strcasecmp(name, "lower") == 0 || strcasecmp(name, "tolower") == 0) { *out = ACROSS_FN_LOWER; return TF_OK; }
69
+ if (strcasecmp(name, "upper") == 0 || strcasecmp(name, "toupper") == 0) { *out = ACROSS_FN_UPPER; return TF_OK; }
70
+ if (strcasecmp(name, "abs") == 0) { *out = ACROSS_FN_ABS; return TF_OK; }
71
+ if (strcasecmp(name, "round") == 0) { *out = ACROSS_FN_ROUND; return TF_OK; }
72
+ if (strcasecmp(name, "floor") == 0) { *out = ACROSS_FN_FLOOR; return TF_OK; }
73
+ if (strcasecmp(name, "ceil") == 0 || strcasecmp(name, "ceiling") == 0) { *out = ACROSS_FN_CEIL; return TF_OK; }
74
+ if (strcasecmp(name, "sqrt") == 0) { *out = ACROSS_FN_SQRT; return TF_OK; }
75
+ if (strcasecmp(name, "log") == 0) { *out = ACROSS_FN_LOG; return TF_OK; }
76
+ if (strcasecmp(name, "exp") == 0) { *out = ACROSS_FN_EXP; return TF_OK; }
77
+ return TF_ERROR;
78
+ }
79
+
80
+ static int across_fn_is_string(across_fn_kind fn) {
81
+ return fn == ACROSS_FN_TRIM || fn == ACROSS_FN_LOWER || fn == ACROSS_FN_UPPER;
82
+ }
83
+
84
+ static int across_fn_is_numeric(across_fn_kind fn) {
85
+ return fn == ACROSS_FN_ABS || fn == ACROSS_FN_ROUND || fn == ACROSS_FN_FLOOR ||
86
+ fn == ACROSS_FN_CEIL || fn == ACROSS_FN_SQRT || fn == ACROSS_FN_LOG ||
87
+ fn == ACROSS_FN_EXP;
88
+ }
89
+
90
+ static tf_type across_output_type(across_fn_kind fn, tf_type input) {
91
+ if (across_fn_is_string(fn)) return input == TF_TYPE_STRING ? TF_TYPE_STRING : TF_TYPE_NULL;
92
+ if (!across_fn_is_numeric(fn)) return TF_TYPE_NULL;
93
+ if (!(input == TF_TYPE_INT64 || input == TF_TYPE_FLOAT64)) return TF_TYPE_NULL;
94
+ if (fn == ACROSS_FN_ABS) return input;
95
+ if (fn == ACROSS_FN_ROUND || fn == ACROSS_FN_FLOOR || fn == ACROSS_FN_CEIL) return TF_TYPE_INT64;
96
+ return TF_TYPE_FLOAT64;
97
+ }
98
+
99
+ static int across_set_string_transform(tf_batch *out, size_t row, size_t col,
100
+ across_fn_kind fn, const char *s) {
101
+ if (!s) return tf_batch_set_null(out, row, col);
102
+ const char *start = s;
103
+ size_t len = strlen(s);
104
+ if (fn == ACROSS_FN_TRIM) {
105
+ while (*start && isspace((unsigned char)*start)) start++;
106
+ len = strlen(start);
107
+ while (len > 0 && isspace((unsigned char)start[len - 1])) len--;
108
+ }
109
+ size_t cap = 0;
110
+ if (tf_size_add(len, 1, &cap) != TF_OK) return TF_ERROR;
111
+ char *buf = tf_mallocarray_checked(cap, sizeof(char));
112
+ if (!buf) return TF_ERROR;
113
+ for (size_t i = 0; i < len; i++) {
114
+ unsigned char ch = (unsigned char)start[i];
115
+ if (fn == ACROSS_FN_LOWER) buf[i] = (char)tolower(ch);
116
+ else if (fn == ACROSS_FN_UPPER) buf[i] = (char)toupper(ch);
117
+ else buf[i] = (char)ch;
118
+ }
119
+ buf[len] = '\0';
120
+ int rc = tf_batch_set_string(out, row, col, buf);
121
+ free(buf);
122
+ return rc;
123
+ }
124
+
125
+ static int across_get_numeric(const tf_batch *in, size_t row, size_t col, double *out) {
126
+ if (in->col_types[col] == TF_TYPE_INT64) {
127
+ *out = (double)tf_batch_get_int64(in, row, col);
128
+ return TF_OK;
129
+ }
130
+ if (in->col_types[col] == TF_TYPE_FLOAT64) {
131
+ *out = tf_batch_get_float64(in, row, col);
132
+ return TF_OK;
133
+ }
134
+ return TF_ERROR;
135
+ }
136
+
137
+ static int across_double_to_i64(double v, int64_t *out) {
138
+ if (!isfinite(v) || v < (double)INT64_MIN || v > (double)INT64_MAX) return TF_ERROR;
139
+ *out = (int64_t)v;
140
+ return TF_OK;
141
+ }
142
+
143
+ static int across_apply_cell(tf_batch *out, size_t out_row, size_t out_col,
144
+ const tf_batch *in, size_t in_row, size_t in_col,
145
+ across_fn_kind fn) {
146
+ if (tf_batch_is_null(in, in_row, in_col)) return tf_batch_set_null(out, out_row, out_col);
147
+ if (across_fn_is_string(fn)) {
148
+ return across_set_string_transform(out, out_row, out_col, fn,
149
+ tf_batch_get_string(in, in_row, in_col));
150
+ }
151
+
152
+ if (fn == ACROSS_FN_ABS && in->col_types[in_col] == TF_TYPE_INT64) {
153
+ int64_t v = tf_batch_get_int64(in, in_row, in_col);
154
+ if (v == INT64_MIN) return tf_batch_set_null(out, out_row, out_col);
155
+ return tf_batch_set_int64(out, out_row, out_col, v < 0 ? -v : v);
156
+ }
157
+
158
+ double v = 0.0;
159
+ if (across_get_numeric(in, in_row, in_col, &v) != TF_OK) return tf_batch_set_null(out, out_row, out_col);
160
+
161
+ if (fn == ACROSS_FN_ABS) {
162
+ return tf_batch_set_float64(out, out_row, out_col, fabs(v));
163
+ } else if (fn == ACROSS_FN_ROUND || fn == ACROSS_FN_FLOOR || fn == ACROSS_FN_CEIL) {
164
+ double rv = fn == ACROSS_FN_ROUND ? round(v) : (fn == ACROSS_FN_FLOOR ? floor(v) : ceil(v));
165
+ int64_t iv = 0;
166
+ if (across_double_to_i64(rv, &iv) != TF_OK) return tf_batch_set_null(out, out_row, out_col);
167
+ return tf_batch_set_int64(out, out_row, out_col, iv);
168
+ } else if (fn == ACROSS_FN_SQRT) {
169
+ if (v < 0.0) return tf_batch_set_null(out, out_row, out_col);
170
+ return tf_batch_set_float64(out, out_row, out_col, sqrt(v));
171
+ } else if (fn == ACROSS_FN_LOG) {
172
+ if (v <= 0.0) return tf_batch_set_null(out, out_row, out_col);
173
+ return tf_batch_set_float64(out, out_row, out_col, log(v));
174
+ } else if (fn == ACROSS_FN_EXP) {
175
+ double ev = exp(v);
176
+ if (!isfinite(ev)) return tf_batch_set_null(out, out_row, out_col);
177
+ return tf_batch_set_float64(out, out_row, out_col, ev);
178
+ }
179
+ return tf_batch_set_null(out, out_row, out_col);
180
+ }
181
+
182
+ static int token_matches(const char *p, const char *tok) {
183
+ return strncmp(p, tok, strlen(tok)) == 0;
184
+ }
185
+
186
+ static int across_add_len(size_t *len, size_t add) {
187
+ if (!len) return TF_ERROR;
188
+ return tf_size_add(*len, add, len);
189
+ }
190
+
191
+ static char *across_format_name(const char *tmpl, const char *col, const char *fn) {
192
+ if (!tmpl || !tmpl[0]) tmpl = "{col}_{fn}";
193
+ if (!col) col = "";
194
+ if (!fn) fn = "";
195
+ size_t len = 0;
196
+ for (const char *p = tmpl; *p;) {
197
+ if (token_matches(p, "{.col}")) {
198
+ if (across_add_len(&len, strlen(col)) != TF_OK) return NULL;
199
+ p += 6;
200
+ } else if (token_matches(p, "{col}")) {
201
+ if (across_add_len(&len, strlen(col)) != TF_OK) return NULL;
202
+ p += 5;
203
+ } else if (token_matches(p, "{.fn}")) {
204
+ if (across_add_len(&len, strlen(fn)) != TF_OK) return NULL;
205
+ p += 5;
206
+ } else if (token_matches(p, "{fn}")) {
207
+ if (across_add_len(&len, strlen(fn)) != TF_OK) return NULL;
208
+ p += 4;
209
+ } else {
210
+ if (across_add_len(&len, 1) != TF_OK) return NULL;
211
+ p++;
212
+ }
213
+ }
214
+ size_t alloc_len = 0;
215
+ if (tf_size_add(len, 1, &alloc_len) != TF_OK) return NULL;
216
+ char *out = tf_mallocarray_checked(alloc_len, sizeof(char));
217
+ if (!out) return NULL;
218
+ char *w = out;
219
+ for (const char *p = tmpl; *p;) {
220
+ const char *rep = NULL;
221
+ size_t tok_len = 0;
222
+ if (token_matches(p, "{.col}")) { rep = col; tok_len = 6; }
223
+ else if (token_matches(p, "{col}")) { rep = col; tok_len = 5; }
224
+ else if (token_matches(p, "{.fn}")) { rep = fn; tok_len = 5; }
225
+ else if (token_matches(p, "{fn}")) { rep = fn; tok_len = 4; }
226
+ if (rep) {
227
+ size_t n = strlen(rep);
228
+ memcpy(w, rep, n);
229
+ w += n;
230
+ p += tok_len;
231
+ } else {
232
+ *w++ = *p++;
233
+ }
234
+ }
235
+ *w = '\0';
236
+ return out;
237
+ }
238
+
239
+ static int across_process(tf_step *self, tf_batch *in, tf_batch **out,
240
+ tf_side_channels *side) {
241
+ across_state *st = self->state;
242
+ *out = NULL;
243
+
244
+ int rc = TF_ERROR;
245
+ int *indices = NULL;
246
+ size_t n_indices = 0;
247
+ char *selector_error = NULL;
248
+ int *selected_pos = NULL;
249
+ tf_type *out_types = NULL;
250
+ char **append_names = NULL;
251
+ size_t *append_src = NULL;
252
+ size_t *append_fn = NULL;
253
+ tf_batch *ob = NULL;
254
+ size_t n_appended = 0;
255
+ size_t out_n_cols = 0;
256
+
257
+ if (tf_column_selectors_resolve(st->selectors, st->n_selectors,
258
+ in->col_names, in->col_types, in->n_cols,
259
+ &indices, &n_indices, &selector_error) != TF_OK) {
260
+ if (across_write_error(side, selector_error ? selector_error : "selector resolution failed", NULL, NULL) != TF_OK)
261
+ goto done;
262
+ goto done;
263
+ }
264
+
265
+ size_t selected_pos_count = in->n_cols ? in->n_cols : 1;
266
+ selected_pos = tf_mallocarray_checked(selected_pos_count, sizeof(int));
267
+ if (!selected_pos) goto done;
268
+ for (size_t i = 0; i < selected_pos_count; i++) selected_pos[i] = -1;
269
+ for (size_t i = 0; i < n_indices; i++) {
270
+ if (indices[i] < 0 || (size_t)indices[i] >= in->n_cols) goto done;
271
+ selected_pos[indices[i]] = (int)i;
272
+ }
273
+
274
+ if (st->replace && st->n_fns != 1) {
275
+ if (across_write_error(side, "replace=true requires exactly one function", NULL, NULL) != TF_OK)
276
+ goto done;
277
+ goto done;
278
+ }
279
+
280
+ if (!st->replace && tf_size_mul(n_indices, st->n_fns, &n_appended) != TF_OK) goto done;
281
+ if (tf_size_add(in->n_cols, n_appended, &out_n_cols) != TF_OK) goto done;
282
+ out_types = tf_callocarray_checked(out_n_cols ? out_n_cols : 1, sizeof(tf_type));
283
+ append_names = n_appended ? tf_callocarray_checked(n_appended, sizeof(char *)) : NULL;
284
+ append_src = n_appended ? tf_callocarray_checked(n_appended, sizeof(size_t)) : NULL;
285
+ append_fn = n_appended ? tf_callocarray_checked(n_appended, sizeof(size_t)) : NULL;
286
+ if (!out_types || (n_appended && (!append_names || !append_src || !append_fn))) goto done;
287
+
288
+ for (size_t c = 0; c < in->n_cols; c++) out_types[c] = in->col_types[c];
289
+ if (st->replace) {
290
+ for (size_t i = 0; i < n_indices; i++) {
291
+ size_t ci = (size_t)indices[i];
292
+ tf_type t = across_output_type(st->fns[0], in->col_types[ci]);
293
+ if (t == TF_TYPE_NULL) {
294
+ if (across_write_error(side, "function is incompatible with column type",
295
+ in->col_names[ci], st->fn_names[0]) != TF_OK)
296
+ goto done;
297
+ goto done;
298
+ }
299
+ out_types[ci] = t;
300
+ }
301
+ } else {
302
+ size_t a = 0;
303
+ for (size_t i = 0; i < n_indices; i++) {
304
+ size_t ci = (size_t)indices[i];
305
+ for (size_t f = 0; f < st->n_fns; f++) {
306
+ tf_type t = across_output_type(st->fns[f], in->col_types[ci]);
307
+ if (t == TF_TYPE_NULL) {
308
+ if (across_write_error(side, "function is incompatible with column type",
309
+ in->col_names[ci], st->fn_names[f]) != TF_OK)
310
+ goto done;
311
+ goto done;
312
+ }
313
+ append_src[a] = ci;
314
+ append_fn[a] = f;
315
+ append_names[a] = across_format_name(st->names_template, in->col_names[ci], st->fn_names[f]);
316
+ if (!append_names[a]) goto done;
317
+ out_types[in->n_cols + a] = t;
318
+ a++;
319
+ }
320
+ }
321
+ }
322
+
323
+ ob = tf_batch_create(out_n_cols, in->n_rows > 0 ? in->n_rows : 1);
324
+ if (!ob) goto done;
325
+ for (size_t c = 0; c < in->n_cols; c++) {
326
+ if (tf_batch_set_schema(ob, c, in->col_names[c], out_types[c]) != TF_OK) goto done;
327
+ }
328
+ for (size_t a = 0; a < n_appended; a++) {
329
+ if (tf_batch_set_schema(ob, in->n_cols + a, append_names[a], out_types[in->n_cols + a]) != TF_OK) goto done;
330
+ }
331
+
332
+ for (size_t r = 0; r < in->n_rows; r++) {
333
+ size_t need_rows = 0;
334
+ if (tf_size_add(r, 1, &need_rows) != TF_OK ||
335
+ tf_batch_ensure_capacity(ob, need_rows) != TF_OK)
336
+ goto done;
337
+ for (size_t c = 0; c < in->n_cols; c++) {
338
+ int write_rc = (st->replace && selected_pos[c] >= 0)
339
+ ? across_apply_cell(ob, r, c, in, r, c, st->fns[0])
340
+ : tf_batch_copy_cell(ob, r, c, in, r, c);
341
+ if (write_rc != TF_OK) goto done;
342
+ }
343
+ for (size_t a = 0; a < n_appended; a++) {
344
+ if (across_apply_cell(ob, r, in->n_cols + a, in, r, append_src[a], st->fns[append_fn[a]]) != TF_OK)
345
+ goto done;
346
+ }
347
+ if (tf_batch_expose_row(ob, r) != TF_OK) goto done;
348
+ }
349
+
350
+ *out = ob;
351
+ ob = NULL;
352
+ rc = TF_OK;
353
+
354
+ done:
355
+ if (ob) tf_batch_free(ob);
356
+ for (size_t k = 0; k < n_appended; k++) free(append_names ? append_names[k] : NULL);
357
+ free(out_types);
358
+ free(append_names);
359
+ free(append_src);
360
+ free(append_fn);
361
+ free(selected_pos);
362
+ free(indices);
363
+ free(selector_error);
364
+ return rc;
365
+ }
366
+
367
+ static int across_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
368
+ (void)self; (void)side; *out = NULL; return TF_OK;
369
+ }
370
+
371
+ static void across_destroy(tf_step *self) {
372
+ across_state *st = self->state;
373
+ if (st) {
374
+ for (size_t i = 0; i < st->n_selectors; i++) free(st->selectors[i]);
375
+ free(st->selectors);
376
+ for (size_t i = 0; i < st->n_fns; i++) free(st->fn_names[i]);
377
+ free(st->fn_names);
378
+ free(st->fns);
379
+ free(st->names_template);
380
+ free(st);
381
+ }
382
+ free(self);
383
+ }
384
+
385
+ static int parse_string_array(const cJSON *arr, char ***out_items, size_t *out_n) {
386
+ if (!cJSON_IsArray(arr)) return TF_ERROR;
387
+ int n = cJSON_GetArraySize((cJSON *)arr);
388
+ if (n <= 0) return TF_ERROR;
389
+ char **items = tf_callocarray_checked((size_t)n, sizeof(char *));
390
+ if (!items) return TF_ERROR;
391
+ for (int i = 0; i < n; i++) {
392
+ cJSON *item = cJSON_GetArrayItem((cJSON *)arr, i);
393
+ if (!cJSON_IsString(item) || !item->valuestring[0]) {
394
+ for (int j = 0; j < i; j++) free(items[j]);
395
+ free(items);
396
+ return TF_ERROR;
397
+ }
398
+ items[i] = strdup(item->valuestring);
399
+ if (!items[i]) {
400
+ for (int j = 0; j < i; j++) free(items[j]);
401
+ free(items);
402
+ return TF_ERROR;
403
+ }
404
+ }
405
+ *out_items = items;
406
+ *out_n = (size_t)n;
407
+ return TF_OK;
408
+ }
409
+
410
+ tf_step *tf_across_create(const cJSON *args) {
411
+ if (!args) return NULL;
412
+ cJSON *cols = cJSON_GetObjectItemCaseSensitive(args, "columns");
413
+ if (!cJSON_IsArray(cols)) return NULL;
414
+
415
+ across_state *st = calloc(1, sizeof(across_state));
416
+ if (!st) return NULL;
417
+ if (parse_string_array(cols, &st->selectors, &st->n_selectors) != TF_OK) goto fail;
418
+
419
+ cJSON *functions = cJSON_GetObjectItemCaseSensitive(args, "functions");
420
+ if (!functions) functions = cJSON_GetObjectItemCaseSensitive(args, "fns");
421
+ cJSON *fn = cJSON_GetObjectItemCaseSensitive(args, "fn");
422
+ if (functions) {
423
+ if (cJSON_IsString(functions)) {
424
+ st->fn_names = tf_callocarray_checked(1, sizeof(char *));
425
+ st->fns = tf_callocarray_checked(1, sizeof(across_fn_kind));
426
+ if (!st->fn_names || !st->fns) goto fail;
427
+ st->fn_names[0] = strdup(functions->valuestring);
428
+ st->n_fns = 1;
429
+ } else if (parse_string_array(functions, &st->fn_names, &st->n_fns) != TF_OK) {
430
+ goto fail;
431
+ }
432
+ } else if (cJSON_IsString(fn)) {
433
+ st->fn_names = tf_callocarray_checked(1, sizeof(char *));
434
+ st->fns = tf_callocarray_checked(1, sizeof(across_fn_kind));
435
+ if (!st->fn_names || !st->fns) goto fail;
436
+ st->fn_names[0] = strdup(fn->valuestring);
437
+ st->n_fns = 1;
438
+ } else {
439
+ goto fail;
440
+ }
441
+
442
+ if (!st->fns) {
443
+ st->fns = tf_callocarray_checked(st->n_fns, sizeof(across_fn_kind));
444
+ if (!st->fns) goto fail;
445
+ }
446
+ for (size_t i = 0; i < st->n_fns; i++) {
447
+ if (!st->fn_names[i] || across_fn_from_name(st->fn_names[i], &st->fns[i]) != TF_OK) goto fail;
448
+ }
449
+
450
+ cJSON *names = cJSON_GetObjectItemCaseSensitive(args, "names");
451
+ cJSON *replace = cJSON_GetObjectItemCaseSensitive(args, "replace");
452
+ st->replace = cJSON_IsBool(replace) ? cJSON_IsTrue(replace) : (st->n_fns == 1 && !cJSON_IsString(names));
453
+ if (st->replace && st->n_fns != 1) goto fail;
454
+ const char *tmpl = cJSON_IsString(names) ? names->valuestring : (st->replace ? "{col}" : "{col}_{fn}");
455
+ st->names_template = strdup(tmpl);
456
+ if (!st->names_template) goto fail;
457
+
458
+ tf_step *step = calloc(1, sizeof(tf_step));
459
+ if (!step) goto fail;
460
+ step->process = across_process;
461
+ step->flush = across_flush;
462
+ step->destroy = across_destroy;
463
+ step->state = st;
464
+ return step;
465
+
466
+ fail:
467
+ if (st) {
468
+ for (size_t i = 0; i < st->n_selectors; i++) free(st->selectors ? st->selectors[i] : NULL);
469
+ free(st->selectors);
470
+ for (size_t i = 0; i < st->n_fns; i++) free(st->fn_names ? st->fn_names[i] : NULL);
471
+ free(st->fn_names);
472
+ free(st->fns);
473
+ free(st->names_template);
474
+ free(st);
475
+ }
476
+ return NULL;
477
+ }