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,3044 @@
1
+ /*
2
+ * op_registry.c — Declarative registry of all built-in ops.
3
+ *
4
+ * Each entry describes an op's kind, capabilities, arguments,
5
+ * schema inference callback, and native constructor.
6
+ */
7
+
8
+ #include "ir.h"
9
+ #include "internal.h"
10
+ #include "cJSON.h"
11
+ #include <string.h>
12
+ #include <strings.h>
13
+ #include <stdio.h>
14
+ #include <stdlib.h>
15
+
16
+ /* ---- Schema inference callbacks ---- */
17
+
18
+ static void registry_schema_unknown(tf_schema *out) {
19
+ if (!out) return;
20
+ tf_schema_free(out);
21
+ out->col_names = NULL;
22
+ out->col_types = NULL;
23
+ out->n_cols = 0;
24
+ out->known = false;
25
+ }
26
+
27
+ static int registry_schema_alloc_known(tf_schema *out, size_t n_cols) {
28
+ if (!out) return TF_ERROR;
29
+ tf_schema_free(out);
30
+ size_t alloc_n = n_cols ? n_cols : 1;
31
+ out->col_names = calloc(alloc_n, sizeof(char *));
32
+ out->col_types = calloc(alloc_n, sizeof(tf_type));
33
+ if (!out->col_names || !out->col_types) {
34
+ tf_schema_free(out);
35
+ return TF_ERROR;
36
+ }
37
+ out->n_cols = n_cols;
38
+ out->known = true;
39
+ return TF_OK;
40
+ }
41
+
42
+ static int registry_schema_set_col(tf_schema *out, size_t idx,
43
+ const char *name, tf_type type) {
44
+ if (!out || !out->known || idx >= out->n_cols || !name) return TF_ERROR;
45
+ char *copy = strdup(name);
46
+ if (!copy) return TF_ERROR;
47
+ free(out->col_names[idx]);
48
+ out->col_names[idx] = copy;
49
+ out->col_types[idx] = type;
50
+ return TF_OK;
51
+ }
52
+
53
+ static int registry_schema_take_col(tf_schema *out, size_t idx,
54
+ char *name, tf_type type) {
55
+ if (!out || !out->known || idx >= out->n_cols || !name) {
56
+ free(name);
57
+ return TF_ERROR;
58
+ }
59
+ free(out->col_names[idx]);
60
+ out->col_names[idx] = name;
61
+ out->col_types[idx] = type;
62
+ return TF_OK;
63
+ }
64
+
65
+ static void registry_set_schema_error(const char *op, const char *detail) {
66
+ char buf[512];
67
+ snprintf(buf, sizeof(buf), "%s schema: %s",
68
+ op ? op : "op", (detail && detail[0]) ? detail : "schema inference failed");
69
+ tf_set_last_error(buf);
70
+ }
71
+
72
+ static int registry_schema_copy_input_columns(tf_schema *out, const tf_schema *in) {
73
+ if (!out || !in || !out->known || !in->known || out->n_cols < in->n_cols) return TF_ERROR;
74
+ for (size_t i = 0; i < in->n_cols; i++) {
75
+ if (registry_schema_set_col(out, i, in->col_names[i] ? in->col_names[i] : "",
76
+ in->col_types[i]) != TF_OK) {
77
+ tf_schema_free(out);
78
+ return TF_ERROR;
79
+ }
80
+ }
81
+ return TF_OK;
82
+ }
83
+
84
+ static int registry_schema_copy_input_plus(tf_schema *out, const tf_schema *in,
85
+ size_t n_extra) {
86
+ if (!in || !in->known) {
87
+ registry_schema_unknown(out);
88
+ return TF_OK;
89
+ }
90
+ size_t total = 0;
91
+ if (tf_size_add(in->n_cols, n_extra, &total) != TF_OK ||
92
+ registry_schema_alloc_known(out, total) != TF_OK) {
93
+ return TF_ERROR;
94
+ }
95
+ return registry_schema_copy_input_columns(out, in);
96
+ }
97
+
98
+ /* Decoders: output schema unknown until runtime */
99
+ static int infer_schema_unknown(const tf_ir_node *node,
100
+ const tf_schema *in, tf_schema *out) {
101
+ (void)node; (void)in;
102
+ registry_schema_unknown(out);
103
+ return TF_OK;
104
+ }
105
+
106
+ /* Encoders: consume schema, no output */
107
+ static int infer_schema_sink(const tf_ir_node *node,
108
+ const tf_schema *in, tf_schema *out) {
109
+ (void)node; (void)in;
110
+ registry_schema_unknown(out);
111
+ return TF_OK;
112
+ }
113
+
114
+ /* filter, head, skip, unique: output schema = input schema */
115
+ static int infer_schema_passthrough(const tf_ir_node *node,
116
+ const tf_schema *in, tf_schema *out) {
117
+ (void)node;
118
+ if (!in->known) {
119
+ registry_schema_unknown(out);
120
+ return TF_OK;
121
+ }
122
+ return tf_schema_copy_checked(out, in);
123
+ }
124
+
125
+ /* select: output schema = subset of input columns */
126
+ static int infer_schema_select(const tf_ir_node *node,
127
+ const tf_schema *in, tf_schema *out) {
128
+ if (!in->known) {
129
+ registry_schema_unknown(out);
130
+ return TF_OK;
131
+ }
132
+
133
+ cJSON *cols = cJSON_GetObjectItemCaseSensitive(node->args, "columns");
134
+ if (!cols || !cJSON_IsArray(cols)) return TF_ERROR;
135
+
136
+ if (tf_column_selectors_have_syntax_json(cols)) {
137
+ int *indices = NULL;
138
+ size_t n_indices = 0;
139
+ char *error = NULL;
140
+ int rc = tf_column_selectors_resolve_json(cols, in->col_names, in->col_types, in->n_cols,
141
+ &indices, &n_indices, &error);
142
+ if (rc != TF_OK) {
143
+ registry_set_schema_error("select", error);
144
+ free(error);
145
+ return TF_ERROR;
146
+ }
147
+ free(error);
148
+ if (registry_schema_alloc_known(out, n_indices) != TF_OK) {
149
+ free(indices);
150
+ return TF_ERROR;
151
+ }
152
+ for (size_t i = 0; i < n_indices; i++) {
153
+ int ci = indices[i];
154
+ if (registry_schema_set_col(out, i, in->col_names[ci], in->col_types[ci]) != TF_OK) {
155
+ free(indices);
156
+ tf_schema_free(out);
157
+ return TF_ERROR;
158
+ }
159
+ }
160
+ free(indices);
161
+ return TF_OK;
162
+ }
163
+
164
+ int n = cJSON_GetArraySize(cols);
165
+ if (n < 0 || registry_schema_alloc_known(out, (size_t)n) != TF_OK) return TF_ERROR;
166
+
167
+ for (int i = 0; i < n; i++) {
168
+ cJSON *item = cJSON_GetArrayItem(cols, i);
169
+ if (!cJSON_IsString(item)) {
170
+ tf_schema_free(out);
171
+ return TF_ERROR;
172
+ }
173
+ const char *name = item->valuestring;
174
+ /* Find column in input schema */
175
+ bool found = false;
176
+ for (size_t j = 0; j < in->n_cols; j++) {
177
+ if (strcmp(in->col_names[j], name) == 0) {
178
+ if (registry_schema_set_col(out, (size_t)i, name, in->col_types[j]) != TF_OK) {
179
+ tf_schema_free(out);
180
+ return TF_ERROR;
181
+ }
182
+ found = true;
183
+ break;
184
+ }
185
+ }
186
+ if (!found) {
187
+ /* Column not in input — still record it, validation can catch it */
188
+ if (registry_schema_set_col(out, (size_t)i, name, TF_TYPE_NULL) != TF_OK) {
189
+ tf_schema_free(out);
190
+ return TF_ERROR;
191
+ }
192
+ }
193
+ }
194
+ return TF_OK;
195
+ }
196
+
197
+ static int infer_schema_relocate(const tf_ir_node *node,
198
+ const tf_schema *in, tf_schema *out) {
199
+ if (!in->known) {
200
+ registry_schema_unknown(out);
201
+ return TF_OK;
202
+ }
203
+
204
+ cJSON *cols = cJSON_GetObjectItemCaseSensitive(node->args, "columns");
205
+ if (!cols || !cJSON_IsArray(cols)) return TF_ERROR;
206
+ cJSON *before = cJSON_GetObjectItemCaseSensitive(node->args, "before");
207
+ cJSON *after = cJSON_GetObjectItemCaseSensitive(node->args, "after");
208
+ if (before && after) return TF_ERROR;
209
+ if (before && !cJSON_IsString(before)) return TF_ERROR;
210
+ if (after && !cJSON_IsString(after)) return TF_ERROR;
211
+
212
+ size_t n_in = in->n_cols;
213
+ int *move_idx = NULL;
214
+ size_t n_move = 0;
215
+ char *selector_error = NULL;
216
+ int rc = tf_column_selectors_resolve_json(cols, in->col_names, in->col_types, in->n_cols,
217
+ &move_idx, &n_move, &selector_error);
218
+ if (rc != TF_OK) {
219
+ registry_set_schema_error("relocate", selector_error);
220
+ free(selector_error);
221
+ return TF_ERROR;
222
+ }
223
+ free(selector_error);
224
+
225
+ int *is_moving = calloc(n_in ? n_in : 1, sizeof(int));
226
+ int *order = malloc(n_in ? n_in * sizeof(int) : sizeof(int));
227
+ if (!is_moving || !order) {
228
+ free(move_idx); free(is_moving); free(order);
229
+ return TF_ERROR;
230
+ }
231
+
232
+ for (size_t i = 0; i < n_move; i++) {
233
+ int found = move_idx[i];
234
+ if (found < 0 || (size_t)found >= n_in || is_moving[found]) {
235
+ free(move_idx); free(is_moving); free(order);
236
+ return TF_ERROR;
237
+ }
238
+ is_moving[found] = 1;
239
+ }
240
+
241
+ const char *anchor = before ? before->valuestring : (after ? after->valuestring : NULL);
242
+ int anchor_idx = -1;
243
+ if (anchor) {
244
+ for (size_t i = 0; i < n_in; i++) {
245
+ if (strcmp(in->col_names[i], anchor) == 0) {
246
+ anchor_idx = (int)i;
247
+ break;
248
+ }
249
+ }
250
+ if (anchor_idx < 0 || is_moving[anchor_idx]) {
251
+ free(move_idx); free(is_moving); free(order);
252
+ return TF_ERROR;
253
+ }
254
+ }
255
+
256
+ size_t n_order = 0;
257
+ if (!anchor) {
258
+ for (size_t i = 0; i < n_move; i++) order[n_order++] = move_idx[i];
259
+ for (size_t i = 0; i < n_in; i++)
260
+ if (!is_moving[i]) order[n_order++] = (int)i;
261
+ } else {
262
+ for (size_t i = 0; i < n_in; i++) {
263
+ if (is_moving[i]) continue;
264
+ if (before && (int)i == anchor_idx) {
265
+ for (size_t j = 0; j < n_move; j++) order[n_order++] = move_idx[j];
266
+ }
267
+ order[n_order++] = (int)i;
268
+ if (after && (int)i == anchor_idx) {
269
+ for (size_t j = 0; j < n_move; j++) order[n_order++] = move_idx[j];
270
+ }
271
+ }
272
+ }
273
+
274
+ if (n_order != n_in) {
275
+ free(move_idx); free(is_moving); free(order);
276
+ return TF_ERROR;
277
+ }
278
+
279
+ if (registry_schema_alloc_known(out, n_in) != TF_OK) {
280
+ free(move_idx); free(is_moving); free(order);
281
+ return TF_ERROR;
282
+ }
283
+ for (size_t i = 0; i < n_in; i++) {
284
+ int ci = order[i];
285
+ if (registry_schema_set_col(out, i, in->col_names[ci], in->col_types[ci]) != TF_OK) {
286
+ free(move_idx);
287
+ free(is_moving);
288
+ free(order);
289
+ tf_schema_free(out);
290
+ return TF_ERROR;
291
+ }
292
+ }
293
+
294
+ free(move_idx);
295
+ free(is_moving);
296
+ free(order);
297
+ return TF_OK;
298
+ }
299
+
300
+ /* rename: output schema = input schema with renamed columns */
301
+ static int infer_schema_rename(const tf_ir_node *node,
302
+ const tf_schema *in, tf_schema *out) {
303
+ if (!in->known) {
304
+ registry_schema_unknown(out);
305
+ return TF_OK;
306
+ }
307
+
308
+ if (tf_schema_copy_checked(out, in) != TF_OK) return TF_ERROR;
309
+
310
+ cJSON *mapping = cJSON_GetObjectItemCaseSensitive(node->args, "mapping");
311
+ if (!mapping || !cJSON_IsObject(mapping)) return TF_OK; /* no renames */
312
+
313
+ cJSON *entry = NULL;
314
+ cJSON_ArrayForEach(entry, mapping) {
315
+ if (!cJSON_IsString(entry)) continue;
316
+ const char *old_name = entry->string;
317
+ const char *new_name = entry->valuestring;
318
+ for (size_t i = 0; i < out->n_cols; i++) {
319
+ if (strcmp(out->col_names[i], old_name) == 0) {
320
+ if (registry_schema_set_col(out, i, new_name, out->col_types[i]) != TF_OK) {
321
+ tf_schema_free(out);
322
+ return TF_ERROR;
323
+ }
324
+ break;
325
+ }
326
+ }
327
+ }
328
+ return TF_OK;
329
+ }
330
+
331
+ /* derive: output schema = input + derived columns */
332
+ static int infer_schema_derive(const tf_ir_node *node,
333
+ const tf_schema *in, tf_schema *out) {
334
+ if (!in->known) {
335
+ registry_schema_unknown(out);
336
+ return TF_OK;
337
+ }
338
+
339
+ cJSON *columns = cJSON_GetObjectItemCaseSensitive(node->args, "columns");
340
+ int n_derived = columns ? cJSON_GetArraySize(columns) : 0;
341
+ if (n_derived < 0 ||
342
+ registry_schema_copy_input_plus(out, in, (size_t)n_derived) != TF_OK) {
343
+ return TF_ERROR;
344
+ }
345
+
346
+ /* Add derived columns (type unknown at compile time) */
347
+ for (int i = 0; i < n_derived; i++) {
348
+ cJSON *item = cJSON_GetArrayItem(columns, i);
349
+ cJSON *name_j = cJSON_GetObjectItemCaseSensitive(item, "name");
350
+ const char *name = cJSON_IsString(name_j) ? name_j->valuestring : "?";
351
+ if (registry_schema_set_col(out, in->n_cols + (size_t)i, name, TF_TYPE_NULL) != TF_OK) {
352
+ tf_schema_free(out);
353
+ return TF_ERROR;
354
+ }
355
+ }
356
+ return TF_OK;
357
+ }
358
+
359
+
360
+ static int registry_across_token_matches(const char *p, const char *tok) {
361
+ return strncmp(p, tok, strlen(tok)) == 0;
362
+ }
363
+
364
+ static char *registry_across_format_name(const char *tmpl, const char *col, const char *fn) {
365
+ if (!tmpl || !tmpl[0]) tmpl = "{col}_{fn}";
366
+ if (!col) col = "";
367
+ if (!fn) fn = "";
368
+ size_t len = 0;
369
+ for (const char *p = tmpl; *p;) {
370
+ if (registry_across_token_matches(p, "{.col}")) { len += strlen(col); p += 6; }
371
+ else if (registry_across_token_matches(p, "{col}")) { len += strlen(col); p += 5; }
372
+ else if (registry_across_token_matches(p, "{.fn}")) { len += strlen(fn); p += 5; }
373
+ else if (registry_across_token_matches(p, "{fn}")) { len += strlen(fn); p += 4; }
374
+ else { len++; p++; }
375
+ }
376
+ char *out = malloc(len + 1);
377
+ if (!out) return NULL;
378
+ char *w = out;
379
+ for (const char *p = tmpl; *p;) {
380
+ const char *rep = NULL;
381
+ size_t tok_len = 0;
382
+ if (registry_across_token_matches(p, "{.col}")) { rep = col; tok_len = 6; }
383
+ else if (registry_across_token_matches(p, "{col}")) { rep = col; tok_len = 5; }
384
+ else if (registry_across_token_matches(p, "{.fn}")) { rep = fn; tok_len = 5; }
385
+ else if (registry_across_token_matches(p, "{fn}")) { rep = fn; tok_len = 4; }
386
+ if (rep) {
387
+ size_t n = strlen(rep);
388
+ memcpy(w, rep, n);
389
+ w += n;
390
+ p += tok_len;
391
+ } else {
392
+ *w++ = *p++;
393
+ }
394
+ }
395
+ *w = '\0';
396
+ return out;
397
+ }
398
+
399
+ static tf_type registry_across_output_type(const char *fn, tf_type input) {
400
+ if (!fn) return TF_TYPE_NULL;
401
+ if (strcasecmp(fn, "trim") == 0 || strcasecmp(fn, "lower") == 0 ||
402
+ strcasecmp(fn, "tolower") == 0 || strcasecmp(fn, "upper") == 0 ||
403
+ strcasecmp(fn, "toupper") == 0) {
404
+ return input == TF_TYPE_STRING ? TF_TYPE_STRING : TF_TYPE_NULL;
405
+ }
406
+ if (!(input == TF_TYPE_INT64 || input == TF_TYPE_FLOAT64)) return TF_TYPE_NULL;
407
+ if (strcasecmp(fn, "abs") == 0) return input;
408
+ if (strcasecmp(fn, "round") == 0 || strcasecmp(fn, "floor") == 0 ||
409
+ strcasecmp(fn, "ceil") == 0 || strcasecmp(fn, "ceiling") == 0) return TF_TYPE_INT64;
410
+ if (strcasecmp(fn, "sqrt") == 0 || strcasecmp(fn, "log") == 0 ||
411
+ strcasecmp(fn, "exp") == 0) return TF_TYPE_FLOAT64;
412
+ return TF_TYPE_NULL;
413
+ }
414
+
415
+ static int registry_across_functions(const tf_ir_node *node, cJSON **out_fns, int *out_n) {
416
+ cJSON *functions = cJSON_GetObjectItemCaseSensitive(node->args, "functions");
417
+ if (!functions) functions = cJSON_GetObjectItemCaseSensitive(node->args, "fns");
418
+ cJSON *fn = cJSON_GetObjectItemCaseSensitive(node->args, "fn");
419
+ if (cJSON_IsArray(functions)) {
420
+ int n = cJSON_GetArraySize(functions);
421
+ if (n <= 0) return TF_ERROR;
422
+ *out_fns = functions;
423
+ *out_n = n;
424
+ return TF_OK;
425
+ }
426
+ if (cJSON_IsString(functions) || cJSON_IsString(fn)) {
427
+ *out_fns = cJSON_IsString(functions) ? functions : fn;
428
+ *out_n = 1;
429
+ return TF_OK;
430
+ }
431
+ return TF_ERROR;
432
+ }
433
+
434
+ /* across: input schema with selected columns replaced, or input + generated columns */
435
+ static int infer_schema_across(const tf_ir_node *node,
436
+ const tf_schema *in, tf_schema *out) {
437
+ if (!in->known) {
438
+ registry_schema_unknown(out);
439
+ return TF_OK;
440
+ }
441
+
442
+ cJSON *cols = cJSON_GetObjectItemCaseSensitive(node->args, "columns");
443
+ if (!cols || !cJSON_IsArray(cols)) return TF_ERROR;
444
+ int *indices = NULL;
445
+ size_t n_indices = 0;
446
+ char *error = NULL;
447
+ int rc = tf_column_selectors_resolve_json(cols, in->col_names, in->col_types, in->n_cols,
448
+ &indices, &n_indices, &error);
449
+ if (rc != TF_OK) {
450
+ registry_set_schema_error("across", error);
451
+ free(error);
452
+ return TF_ERROR;
453
+ }
454
+ free(error);
455
+
456
+ cJSON *fns = NULL;
457
+ int n_fns = 0;
458
+ if (registry_across_functions(node, &fns, &n_fns) != TF_OK) { free(indices); return TF_ERROR; }
459
+ cJSON *names = cJSON_GetObjectItemCaseSensitive(node->args, "names");
460
+ cJSON *replace_j = cJSON_GetObjectItemCaseSensitive(node->args, "replace");
461
+ int replace = cJSON_IsBool(replace_j) ? cJSON_IsTrue(replace_j) : (n_fns == 1 && !cJSON_IsString(names));
462
+ if (replace && n_fns != 1) { free(indices); return TF_ERROR; }
463
+ const char *tmpl = cJSON_IsString(names) ? names->valuestring : (replace ? "{col}" : "{col}_{fn}");
464
+
465
+ size_t extra = 0;
466
+ size_t total = 0;
467
+ if (!replace && tf_size_mul(n_indices, (size_t)n_fns, &extra) != TF_OK) {
468
+ free(indices);
469
+ return TF_ERROR;
470
+ }
471
+ if (tf_size_add(in->n_cols, extra, &total) != TF_OK ||
472
+ registry_schema_alloc_known(out, total) != TF_OK ||
473
+ registry_schema_copy_input_columns(out, in) != TF_OK) {
474
+ free(indices);
475
+ return TF_ERROR;
476
+ }
477
+
478
+ if (replace) {
479
+ const char *fn_name = cJSON_IsArray(fns) ? cJSON_GetArrayItem(fns, 0)->valuestring : fns->valuestring;
480
+ for (size_t i = 0; i < n_indices; i++) {
481
+ size_t ci = (size_t)indices[i];
482
+ tf_type t = registry_across_output_type(fn_name, in->col_types[ci]);
483
+ if (t == TF_TYPE_NULL) { free(indices); tf_schema_free(out); return TF_ERROR; }
484
+ out->col_types[ci] = t;
485
+ }
486
+ } else {
487
+ size_t a = 0;
488
+ for (size_t i = 0; i < n_indices; i++) {
489
+ size_t ci = (size_t)indices[i];
490
+ for (int f = 0; f < n_fns; f++) {
491
+ cJSON *fn_j = cJSON_IsArray(fns) ? cJSON_GetArrayItem(fns, f) : fns;
492
+ if (!cJSON_IsString(fn_j)) { free(indices); tf_schema_free(out); return TF_ERROR; }
493
+ tf_type t = registry_across_output_type(fn_j->valuestring, in->col_types[ci]);
494
+ if (t == TF_TYPE_NULL) { free(indices); tf_schema_free(out); return TF_ERROR; }
495
+ char *name = registry_across_format_name(tmpl, in->col_names[ci], fn_j->valuestring);
496
+ if (registry_schema_take_col(out, in->n_cols + a, name, t) != TF_OK) {
497
+ free(indices);
498
+ tf_schema_free(out);
499
+ return TF_ERROR;
500
+ }
501
+ a++;
502
+ }
503
+ }
504
+ }
505
+ free(indices);
506
+ return TF_OK;
507
+ }
508
+
509
+ /* validate: input + _valid bool column */
510
+ static int infer_schema_validate(const tf_ir_node *node,
511
+ const tf_schema *in, tf_schema *out) {
512
+ (void)node;
513
+ if (!in->known) { registry_schema_unknown(out); return TF_OK; }
514
+ if (registry_schema_copy_input_plus(out, in, 1) != TF_OK) return TF_ERROR;
515
+ if (registry_schema_set_col(out, in->n_cols, "_valid", TF_TYPE_BOOL) != TF_OK) {
516
+ tf_schema_free(out);
517
+ return TF_ERROR;
518
+ }
519
+ return TF_OK;
520
+ }
521
+
522
+ static int infer_schema_assert(const tf_ir_node *node,
523
+ const tf_schema *in, tf_schema *out) {
524
+ cJSON *action_j = cJSON_GetObjectItemCaseSensitive(node->args, "action");
525
+ const char *action = cJSON_IsString(action_j) ? action_j->valuestring : "fail";
526
+ if (strcmp(action, "annotate") != 0) return infer_schema_passthrough(node, in, out);
527
+ if (!in->known) { registry_schema_unknown(out); return TF_OK; }
528
+ cJSON *result_j = cJSON_GetObjectItemCaseSensitive(node->args, "result");
529
+ const char *result = cJSON_IsString(result_j) && result_j->valuestring[0]
530
+ ? result_j->valuestring : "_assert";
531
+ if (registry_schema_copy_input_plus(out, in, 1) != TF_OK) return TF_ERROR;
532
+ if (registry_schema_set_col(out, in->n_cols, result, TF_TYPE_BOOL) != TF_OK) {
533
+ tf_schema_free(out);
534
+ return TF_ERROR;
535
+ }
536
+ return TF_OK;
537
+ }
538
+
539
+ static int infer_schema_schema(const tf_ir_node *node,
540
+ const tf_schema *in, tf_schema *out) {
541
+ cJSON *action_j = cJSON_GetObjectItemCaseSensitive(node->args, "action");
542
+ cJSON *mode_j = cJSON_GetObjectItemCaseSensitive(node->args, "mode");
543
+ const char *action = cJSON_IsString(action_j) ? action_j->valuestring :
544
+ (cJSON_IsString(mode_j) ? mode_j->valuestring : "fail");
545
+ if (strcmp(action, "annotate") != 0) return infer_schema_passthrough(node, in, out);
546
+ if (!in->known) { registry_schema_unknown(out); return TF_OK; }
547
+ cJSON *result_j = cJSON_GetObjectItemCaseSensitive(node->args, "result");
548
+ const char *result = cJSON_IsString(result_j) && result_j->valuestring[0]
549
+ ? result_j->valuestring : "_schema";
550
+ if (registry_schema_copy_input_plus(out, in, 1) != TF_OK) return TF_ERROR;
551
+ if (registry_schema_set_col(out, in->n_cols, result, TF_TYPE_BOOL) != TF_OK) {
552
+ tf_schema_free(out);
553
+ return TF_ERROR;
554
+ }
555
+ return TF_OK;
556
+ }
557
+
558
+ static int infer_schema_schema_infer(const tf_ir_node *node,
559
+ const tf_schema *in, tf_schema *out) {
560
+ (void)node; (void)in;
561
+ static const char *names[] = {
562
+ "column", "type", "nullable", "non_null", "rows_seen",
563
+ "rows_sampled", "missing", "non_missing", "observed_types", "warning"
564
+ };
565
+ static const tf_type types[] = {
566
+ TF_TYPE_STRING, TF_TYPE_STRING, TF_TYPE_BOOL, TF_TYPE_BOOL, TF_TYPE_INT64,
567
+ TF_TYPE_INT64, TF_TYPE_INT64, TF_TYPE_INT64, TF_TYPE_STRING, TF_TYPE_STRING
568
+ };
569
+ size_t n = sizeof(names) / sizeof(names[0]);
570
+ if (registry_schema_alloc_known(out, n) != TF_OK) return TF_ERROR;
571
+ for (size_t i = 0; i < n; i++) {
572
+ if (registry_schema_set_col(out, i, names[i], types[i]) != TF_OK) {
573
+ tf_schema_free(out);
574
+ return TF_ERROR;
575
+ }
576
+ }
577
+ return TF_OK;
578
+ }
579
+
580
+ static int infer_schema_add_int64_column(const tf_schema *in, tf_schema *out, const char *name) {
581
+ if (!in->known) { registry_schema_unknown(out); return TF_OK; }
582
+ if (registry_schema_copy_input_plus(out, in, 1) != TF_OK) return TF_ERROR;
583
+ if (registry_schema_set_col(out, in->n_cols, name, TF_TYPE_INT64) != TF_OK) {
584
+ tf_schema_free(out);
585
+ return TF_ERROR;
586
+ }
587
+ return TF_OK;
588
+ }
589
+
590
+ /* hash: input + _hash int column */
591
+ static int infer_schema_add_hash(const tf_ir_node *node,
592
+ const tf_schema *in, tf_schema *out) {
593
+ (void)node;
594
+ return infer_schema_add_int64_column(in, out, "_hash");
595
+ }
596
+
597
+ static int infer_schema_rleid(const tf_ir_node *node,
598
+ const tf_schema *in, tf_schema *out) {
599
+ const char *name = "_rleid";
600
+ cJSON *res = cJSON_GetObjectItemCaseSensitive(node->args, "result");
601
+ if (cJSON_IsString(res) && res->valuestring[0] != '\0') name = res->valuestring;
602
+ return infer_schema_add_int64_column(in, out, name);
603
+ }
604
+
605
+ static int infer_schema_rowid(const tf_ir_node *node,
606
+ const tf_schema *in, tf_schema *out) {
607
+ const char *name = "_rowid";
608
+ cJSON *res = cJSON_GetObjectItemCaseSensitive(node->args, "result");
609
+ if (cJSON_IsString(res) && res->valuestring[0] != '\0') name = res->valuestring;
610
+ return infer_schema_add_int64_column(in, out, name);
611
+ }
612
+
613
+ static int infer_schema_source_name(const tf_ir_node *node,
614
+ const tf_schema *in, tf_schema *out) {
615
+ if (!in->known) {
616
+ registry_schema_unknown(out);
617
+ return TF_OK;
618
+ }
619
+ const char *name = "_source";
620
+ cJSON *res = cJSON_GetObjectItemCaseSensitive(node->args, "result");
621
+ if (!cJSON_IsString(res)) res = cJSON_GetObjectItemCaseSensitive(node->args, "column");
622
+ if (!cJSON_IsString(res)) res = cJSON_GetObjectItemCaseSensitive(node->args, "as");
623
+ if (cJSON_IsString(res) && res->valuestring[0] != '\0') name = res->valuestring;
624
+
625
+ if (registry_schema_copy_input_plus(out, in, 1) != TF_OK) return TF_ERROR;
626
+ if (registry_schema_set_col(out, in->n_cols, name, TF_TYPE_STRING) != TF_OK) {
627
+ tf_schema_free(out);
628
+ return TF_ERROR;
629
+ }
630
+ return TF_OK;
631
+ }
632
+
633
+ /* frequency: output is value + count */
634
+ static int infer_schema_frequency(const tf_ir_node *node,
635
+ const tf_schema *in, tf_schema *out) {
636
+ (void)node; (void)in;
637
+ if (registry_schema_alloc_known(out, 2) != TF_OK) return TF_ERROR;
638
+ if (registry_schema_set_col(out, 0, "value", TF_TYPE_STRING) != TF_OK ||
639
+ registry_schema_set_col(out, 1, "count", TF_TYPE_INT64) != TF_OK) {
640
+ tf_schema_free(out);
641
+ return TF_ERROR;
642
+ }
643
+ return TF_OK;
644
+ }
645
+
646
+ static int schema_col_index(const tf_schema *schema, const char *name);
647
+
648
+ static int infer_schema_pivot(const tf_ir_node *node,
649
+ const tf_schema *in, tf_schema *out) {
650
+ if (!node || !node->args || !in || !in->known) {
651
+ registry_schema_unknown(out);
652
+ return TF_OK;
653
+ }
654
+
655
+ cJSON *cats = cJSON_GetObjectItemCaseSensitive(node->args, "categories");
656
+ if (!cats) {
657
+ registry_schema_unknown(out);
658
+ return TF_OK;
659
+ }
660
+ if (!cJSON_IsArray(cats)) {
661
+ registry_set_schema_error("pivot", "categories must be an array");
662
+ return TF_ERROR;
663
+ }
664
+
665
+ cJSON *name_j = cJSON_GetObjectItemCaseSensitive(node->args, "name_column");
666
+ cJSON *value_j = cJSON_GetObjectItemCaseSensitive(node->args, "value_column");
667
+ const char *name_col = cJSON_IsString(name_j) ? name_j->valuestring : NULL;
668
+ const char *value_col = cJSON_IsString(value_j) ? value_j->valuestring : NULL;
669
+ if (!name_col || !name_col[0] || !value_col || !value_col[0]) {
670
+ registry_set_schema_error("pivot", "missing name_column or value_column");
671
+ return TF_ERROR;
672
+ }
673
+
674
+ int name_idx = schema_col_index(in, name_col);
675
+ int value_idx = schema_col_index(in, value_col);
676
+ if (name_idx < 0 || value_idx < 0) {
677
+ registry_set_schema_error("pivot", "name_column or value_column not found");
678
+ return TF_ERROR;
679
+ }
680
+
681
+ int n_cats = cJSON_GetArraySize(cats);
682
+ if (n_cats <= 0) {
683
+ registry_set_schema_error("pivot", "categories cannot be empty");
684
+ return TF_ERROR;
685
+ }
686
+
687
+ size_t unique_cats = 0;
688
+ for (int i = 0; i < n_cats; i++) {
689
+ cJSON *cat = cJSON_GetArrayItem(cats, i);
690
+ if (!cJSON_IsString(cat) || !cat->valuestring || !cat->valuestring[0]) {
691
+ registry_set_schema_error("pivot", "categories must be non-empty strings");
692
+ return TF_ERROR;
693
+ }
694
+ int seen = 0;
695
+ for (int j = 0; j < i; j++) {
696
+ cJSON *prev = cJSON_GetArrayItem(cats, j);
697
+ if (cJSON_IsString(prev) && prev->valuestring &&
698
+ strcmp(prev->valuestring, cat->valuestring) == 0) {
699
+ seen = 1;
700
+ break;
701
+ }
702
+ }
703
+ if (!seen) {
704
+ size_t next_unique = 0;
705
+ if (tf_size_add(unique_cats, 1, &next_unique) != TF_OK) return TF_ERROR;
706
+ unique_cats = next_unique;
707
+ }
708
+ }
709
+
710
+ size_t n_pass = 0;
711
+ for (size_t c = 0; c < in->n_cols; c++) {
712
+ if ((int)c != name_idx && (int)c != value_idx) n_pass++;
713
+ }
714
+ size_t total = 0;
715
+ if (tf_size_add(n_pass, unique_cats, &total) != TF_OK ||
716
+ registry_schema_alloc_known(out, total) != TF_OK) {
717
+ return TF_ERROR;
718
+ }
719
+
720
+ size_t out_idx = 0;
721
+ for (size_t c = 0; c < in->n_cols; c++) {
722
+ if ((int)c == name_idx || (int)c == value_idx) continue;
723
+ if (registry_schema_set_col(out, out_idx++, in->col_names[c] ? in->col_names[c] : "",
724
+ in->col_types[c]) != TF_OK) {
725
+ tf_schema_free(out);
726
+ return TF_ERROR;
727
+ }
728
+ }
729
+
730
+ cJSON *agg_j = cJSON_GetObjectItemCaseSensitive(node->args, "agg");
731
+ const char *agg = cJSON_IsString(agg_j) ? agg_j->valuestring : NULL;
732
+ tf_type pivot_type = (agg && strcmp(agg, "count") == 0) ? TF_TYPE_INT64 : TF_TYPE_FLOAT64;
733
+ for (int i = 0; i < n_cats; i++) {
734
+ cJSON *cat = cJSON_GetArrayItem(cats, i);
735
+ int seen = 0;
736
+ for (int j = 0; j < i; j++) {
737
+ cJSON *prev = cJSON_GetArrayItem(cats, j);
738
+ if (cJSON_IsString(prev) && prev->valuestring &&
739
+ strcmp(prev->valuestring, cat->valuestring) == 0) {
740
+ seen = 1;
741
+ break;
742
+ }
743
+ }
744
+ if (seen) continue;
745
+ if (registry_schema_set_col(out, out_idx++, cat->valuestring, pivot_type) != TF_OK) {
746
+ tf_schema_free(out);
747
+ return TF_ERROR;
748
+ }
749
+ }
750
+ return TF_OK;
751
+ }
752
+
753
+ static int schema_col_index(const tf_schema *schema, const char *name) {
754
+ if (!schema || !schema->known || !name) return -1;
755
+ for (size_t i = 0; i < schema->n_cols; i++) {
756
+ if (schema->col_names[i] && strcmp(schema->col_names[i], name) == 0) return (int)i;
757
+ }
758
+ return -1;
759
+ }
760
+
761
+
762
+ static int infer_schema_append_source_column(const tf_ir_node *node,
763
+ const tf_schema *in,
764
+ tf_schema *out,
765
+ const char *default_suffix) {
766
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(node->args, "column");
767
+ const char *column = cJSON_IsString(col_j) ? col_j->valuestring : NULL;
768
+ if (!in->known || !column) {
769
+ registry_schema_unknown(out);
770
+ return TF_OK;
771
+ }
772
+
773
+ int ci = schema_col_index(in, column);
774
+ if (ci < 0) {
775
+ registry_schema_unknown(out);
776
+ return TF_OK;
777
+ }
778
+
779
+ const char *name = NULL;
780
+ cJSON *res = cJSON_GetObjectItemCaseSensitive(node->args, "result");
781
+ if (cJSON_IsString(res) && res->valuestring[0] != '\0') name = res->valuestring;
782
+
783
+ char fallback[256];
784
+ if (!name) {
785
+ snprintf(fallback, sizeof(fallback), "%s_%s", column, default_suffix);
786
+ name = fallback;
787
+ }
788
+
789
+ if (registry_schema_copy_input_plus(out, in, 1) != TF_OK) return TF_ERROR;
790
+ if (registry_schema_set_col(out, in->n_cols, name, in->col_types[ci]) != TF_OK) {
791
+ tf_schema_free(out);
792
+ return TF_ERROR;
793
+ }
794
+ return TF_OK;
795
+ }
796
+
797
+ static int infer_schema_lead(const tf_ir_node *node,
798
+ const tf_schema *in, tf_schema *out) {
799
+ return infer_schema_append_source_column(node, in, out, "lead");
800
+ }
801
+
802
+ static int infer_schema_lag(const tf_ir_node *node,
803
+ const tf_schema *in, tf_schema *out) {
804
+ return infer_schema_append_source_column(node, in, out, "lag");
805
+ }
806
+
807
+ static int infer_schema_shift(const tf_ir_node *node,
808
+ const tf_schema *in, tf_schema *out) {
809
+ const char *suffix = "shift";
810
+ cJSON *type = cJSON_GetObjectItemCaseSensitive(node->args, "type");
811
+ if (cJSON_IsString(type) && strcmp(type->valuestring, "lead") == 0) suffix = "lead";
812
+ return infer_schema_append_source_column(node, in, out, suffix);
813
+ }
814
+
815
+ static tf_type json_extract_type_from_arg(const cJSON *args) {
816
+ cJSON *type = cJSON_GetObjectItemCaseSensitive(args, "type");
817
+ const char *s = cJSON_IsString(type) ? type->valuestring : "string";
818
+ if (strcmp(s, "int") == 0 || strcmp(s, "int64") == 0) return TF_TYPE_INT64;
819
+ if (strcmp(s, "float") == 0 || strcmp(s, "float64") == 0 || strcmp(s, "number") == 0) return TF_TYPE_FLOAT64;
820
+ if (strcmp(s, "bool") == 0 || strcmp(s, "boolean") == 0) return TF_TYPE_BOOL;
821
+ return TF_TYPE_STRING;
822
+ }
823
+
824
+ static int infer_schema_json_extract(const tf_ir_node *node,
825
+ const tf_schema *in, tf_schema *out) {
826
+ if (!in->known) {
827
+ registry_schema_unknown(out);
828
+ return TF_OK;
829
+ }
830
+
831
+ cJSON *res = cJSON_GetObjectItemCaseSensitive(node->args, "result");
832
+ if (!cJSON_IsString(res) || res->valuestring[0] == '\0') return TF_ERROR;
833
+
834
+ if (registry_schema_copy_input_plus(out, in, 1) != TF_OK) return TF_ERROR;
835
+ if (registry_schema_set_col(out, in->n_cols, res->valuestring,
836
+ json_extract_type_from_arg(node->args)) != TF_OK) {
837
+ tf_schema_free(out);
838
+ return TF_ERROR;
839
+ }
840
+ return TF_OK;
841
+ }
842
+
843
+ static int infer_schema_json_schema(const tf_ir_node *node,
844
+ const tf_schema *in, tf_schema *out) {
845
+ cJSON *mode_j = cJSON_GetObjectItemCaseSensitive(node->args, "mode");
846
+ const char *mode = cJSON_IsString(mode_j) ? mode_j->valuestring : "annotate";
847
+ if (strcmp(mode, "filter") == 0 || strcmp(mode, "keep") == 0) {
848
+ return infer_schema_passthrough(node, in, out);
849
+ }
850
+
851
+ if (!in->known) {
852
+ registry_schema_unknown(out);
853
+ return TF_OK;
854
+ }
855
+
856
+ const char *result = "_valid";
857
+ cJSON *res = cJSON_GetObjectItemCaseSensitive(node->args, "result");
858
+ if (cJSON_IsString(res) && res->valuestring[0] != '\0') result = res->valuestring;
859
+
860
+ if (registry_schema_copy_input_plus(out, in, 1) != TF_OK) return TF_ERROR;
861
+ if (registry_schema_set_col(out, in->n_cols, result, TF_TYPE_BOOL) != TF_OK) {
862
+ tf_schema_free(out);
863
+ return TF_ERROR;
864
+ }
865
+ return TF_OK;
866
+ }
867
+
868
+ static tf_type json_flatten_type_from_string(const char *type_s) {
869
+ if (!type_s || strcmp(type_s, "string") == 0 || strcmp(type_s, "str") == 0 || strcmp(type_s, "json") == 0) return TF_TYPE_STRING;
870
+ if (strcmp(type_s, "int") == 0 || strcmp(type_s, "int64") == 0) return TF_TYPE_INT64;
871
+ if (strcmp(type_s, "float") == 0 || strcmp(type_s, "float64") == 0 || strcmp(type_s, "number") == 0) return TF_TYPE_FLOAT64;
872
+ if (strcmp(type_s, "bool") == 0 || strcmp(type_s, "boolean") == 0) return TF_TYPE_BOOL;
873
+ return TF_TYPE_NULL;
874
+ }
875
+
876
+ static int json_flatten_parse_string_field(const char *spec, char **name, tf_type *type) {
877
+ if (!spec) return TF_ERROR;
878
+ char *copy = strdup(spec);
879
+ if (!copy) return TF_ERROR;
880
+ char *first = strchr(copy, ':');
881
+ if (!first) { free(copy); return TF_ERROR; }
882
+ *first = '\0';
883
+ char *second = strchr(first + 1, ':');
884
+ if (second) *second = '\0';
885
+ tf_type parsed = json_flatten_type_from_string(second ? second + 1 : "string");
886
+ if (first[1] == '\0' || parsed == TF_TYPE_NULL) {
887
+ free(copy);
888
+ return TF_ERROR;
889
+ }
890
+ *name = strdup(first + 1);
891
+ *type = parsed;
892
+ free(copy);
893
+ return *name ? TF_OK : TF_ERROR;
894
+ }
895
+
896
+ static int json_flatten_field_schema(const cJSON *item, char **name, tf_type *type) {
897
+ *name = NULL;
898
+ *type = TF_TYPE_STRING;
899
+ if (cJSON_IsString(item)) return json_flatten_parse_string_field(item->valuestring, name, type);
900
+ if (!cJSON_IsObject(item)) return TF_ERROR;
901
+ cJSON *name_j = cJSON_GetObjectItemCaseSensitive(item, "name");
902
+ if (!cJSON_IsString(name_j)) name_j = cJSON_GetObjectItemCaseSensitive(item, "result");
903
+ cJSON *type_j = cJSON_GetObjectItemCaseSensitive(item, "type");
904
+ tf_type parsed = json_flatten_type_from_string(cJSON_IsString(type_j) ? type_j->valuestring : "string");
905
+ if (!cJSON_IsString(name_j) || name_j->valuestring[0] == '\0' || parsed == TF_TYPE_NULL) return TF_ERROR;
906
+ *name = strdup(name_j->valuestring);
907
+ *type = parsed;
908
+ return *name ? TF_OK : TF_ERROR;
909
+ }
910
+
911
+ static int infer_schema_json_flatten(const tf_ir_node *node,
912
+ const tf_schema *in, tf_schema *out) {
913
+ if (!in->known) {
914
+ registry_schema_unknown(out);
915
+ return TF_OK;
916
+ }
917
+ cJSON *fields = cJSON_GetObjectItemCaseSensitive(node->args, "fields");
918
+ if (!cJSON_IsArray(fields) || cJSON_GetArraySize(fields) <= 0) return TF_ERROR;
919
+ int n_fields = cJSON_GetArraySize(fields);
920
+ if (registry_schema_copy_input_plus(out, in, (size_t)n_fields) != TF_OK) return TF_ERROR;
921
+ for (int i = 0; i < n_fields; i++) {
922
+ char *name = NULL;
923
+ tf_type type = TF_TYPE_STRING;
924
+ if (json_flatten_field_schema(cJSON_GetArrayItem(fields, i), &name, &type) != TF_OK) {
925
+ free(name);
926
+ tf_schema_free(out);
927
+ return TF_ERROR;
928
+ }
929
+ if (registry_schema_take_col(out, in->n_cols + (size_t)i, name, type) != TF_OK) {
930
+ tf_schema_free(out);
931
+ return TF_ERROR;
932
+ }
933
+ }
934
+ return TF_OK;
935
+ }
936
+
937
+
938
+ /* group-agg: typed group columns + aggregate columns (float64) */
939
+ static int infer_schema_group_agg(const tf_ir_node *node,
940
+ const tf_schema *in, tf_schema *out) {
941
+ cJSON *group_by = cJSON_GetObjectItemCaseSensitive(node->args, "group_by");
942
+ cJSON *aggs = cJSON_GetObjectItemCaseSensitive(node->args, "aggs");
943
+ int ng = group_by ? cJSON_GetArraySize(group_by) : 0;
944
+ int na = aggs ? cJSON_GetArraySize(aggs) : 0;
945
+ if (ng < 0 || na < 0) return TF_ERROR;
946
+ size_t total = 0;
947
+ if (tf_size_add((size_t)ng, (size_t)na, &total) != TF_OK ||
948
+ registry_schema_alloc_known(out, total) != TF_OK) return TF_ERROR;
949
+ for (int i = 0; i < ng; i++) {
950
+ cJSON *item = cJSON_GetArrayItem(group_by, i);
951
+ const char *name = cJSON_IsString(item) ? item->valuestring : "?";
952
+ int input_idx = schema_col_index(in, name);
953
+ tf_type type = input_idx >= 0 ? in->col_types[input_idx] : TF_TYPE_STRING;
954
+ if (registry_schema_set_col(out, (size_t)i, name, type) != TF_OK) {
955
+ tf_schema_free(out);
956
+ return TF_ERROR;
957
+ }
958
+ }
959
+ for (int i = 0; i < na; i++) {
960
+ cJSON *item = cJSON_GetArrayItem(aggs, i);
961
+ cJSON *name_j = cJSON_GetObjectItemCaseSensitive(item, "name");
962
+ const char *name = name_j && cJSON_IsString(name_j) ? name_j->valuestring : "?";
963
+ if (registry_schema_set_col(out, (size_t)ng + (size_t)i, name, TF_TYPE_FLOAT64) != TF_OK) {
964
+ tf_schema_free(out);
965
+ return TF_ERROR;
966
+ }
967
+ }
968
+ return TF_OK;
969
+ }
970
+
971
+ /* stats/scan: output schema is known from requested or profile-default stats */
972
+ static int infer_schema_stats(const tf_ir_node *node,
973
+ const tf_schema *in, tf_schema *out) {
974
+ (void)in;
975
+ int w_count = 0, w_missing = 0, w_complete_rate = 0;
976
+ int w_sum = 0, w_avg = 0, w_min = 0, w_max = 0;
977
+ int w_var = 0, w_stddev = 0;
978
+ int w_median = 0, w_p25 = 0, w_p75 = 0;
979
+ int w_skewness = 0, w_kurtosis = 0;
980
+ int w_distinct = 0, w_hist = 0, w_sample = 0;
981
+
982
+ cJSON *arr = node ? cJSON_GetObjectItemCaseSensitive(node->args, "stats") : NULL;
983
+ if (arr && cJSON_IsArray(arr)) {
984
+ int n = cJSON_GetArraySize(arr);
985
+ for (int i = 0; i < n; i++) {
986
+ cJSON *item = cJSON_GetArrayItem(arr, i);
987
+ if (!cJSON_IsString(item)) continue;
988
+ const char *s = item->valuestring;
989
+ if (strcmp(s, "count") == 0) w_count = 1;
990
+ else if (strcmp(s, "missing") == 0) w_missing = 1;
991
+ else if (strcmp(s, "complete_rate") == 0) w_complete_rate = 1;
992
+ else if (strcmp(s, "sum") == 0) w_sum = 1;
993
+ else if (strcmp(s, "avg") == 0 || strcmp(s, "mean") == 0) w_avg = 1;
994
+ else if (strcmp(s, "min") == 0) w_min = 1;
995
+ else if (strcmp(s, "max") == 0) w_max = 1;
996
+ else if (strcmp(s, "var") == 0 || strcmp(s, "variance") == 0) w_var = 1;
997
+ else if (strcmp(s, "stddev") == 0 || strcmp(s, "sd") == 0) w_stddev = 1;
998
+ else if (strcmp(s, "median") == 0) w_median = 1;
999
+ else if (strcmp(s, "p25") == 0 || strcmp(s, "q1") == 0) w_p25 = 1;
1000
+ else if (strcmp(s, "p75") == 0 || strcmp(s, "q3") == 0) w_p75 = 1;
1001
+ else if (strcmp(s, "skewness") == 0 || strcmp(s, "skew") == 0) w_skewness = 1;
1002
+ else if (strcmp(s, "kurtosis") == 0 || strcmp(s, "kurt") == 0) w_kurtosis = 1;
1003
+ else if (strcmp(s, "distinct") == 0) w_distinct = 1;
1004
+ else if (strcmp(s, "hist") == 0) w_hist = 1;
1005
+ else if (strcmp(s, "sample") == 0) w_sample = 1;
1006
+ }
1007
+ } else if (node && node->op && strcmp(node->op, "scan") == 0) {
1008
+ w_count = w_missing = w_complete_rate = 1;
1009
+ w_distinct = 1;
1010
+ w_min = w_max = w_avg = w_stddev = 1;
1011
+ w_p25 = w_median = w_p75 = 1;
1012
+ w_hist = w_sample = 1;
1013
+ } else {
1014
+ /* Default: basic + variance + median */
1015
+ w_count = w_sum = w_avg = w_min = w_max = 1;
1016
+ w_var = w_stddev = w_median = 1;
1017
+ }
1018
+
1019
+ size_t n_cols = 1; /* column */
1020
+ if (w_count) n_cols++;
1021
+ if (w_missing) n_cols++;
1022
+ if (w_complete_rate) n_cols++;
1023
+ if (w_sum) n_cols++;
1024
+ if (w_avg) n_cols++;
1025
+ if (w_min) n_cols++;
1026
+ if (w_max) n_cols++;
1027
+ if (w_var) n_cols++;
1028
+ if (w_stddev) n_cols++;
1029
+ if (w_median) n_cols++;
1030
+ if (w_p25) n_cols++;
1031
+ if (w_p75) n_cols++;
1032
+ if (w_skewness) n_cols++;
1033
+ if (w_kurtosis) n_cols++;
1034
+ if (w_distinct) n_cols++;
1035
+ if (w_hist) n_cols++;
1036
+ if (w_sample) n_cols++;
1037
+
1038
+ if (registry_schema_alloc_known(out, n_cols) != TF_OK) return TF_ERROR;
1039
+
1040
+ size_t ci = 0;
1041
+ #define ADD_STAT_COLUMN(name, type) do { \
1042
+ if (registry_schema_set_col(out, ci, (name), (type)) != TF_OK) { \
1043
+ tf_schema_free(out); \
1044
+ return TF_ERROR; \
1045
+ } \
1046
+ ci++; \
1047
+ } while (0)
1048
+
1049
+ ADD_STAT_COLUMN("column", TF_TYPE_STRING);
1050
+ if (w_count) ADD_STAT_COLUMN("count", TF_TYPE_INT64);
1051
+ if (w_missing) ADD_STAT_COLUMN("missing", TF_TYPE_INT64);
1052
+ if (w_complete_rate) ADD_STAT_COLUMN("complete_rate", TF_TYPE_FLOAT64);
1053
+ if (w_sum) ADD_STAT_COLUMN("sum", TF_TYPE_FLOAT64);
1054
+ if (w_avg) ADD_STAT_COLUMN("avg", TF_TYPE_FLOAT64);
1055
+ if (w_min) ADD_STAT_COLUMN("min", TF_TYPE_FLOAT64);
1056
+ if (w_max) ADD_STAT_COLUMN("max", TF_TYPE_FLOAT64);
1057
+ if (w_var) ADD_STAT_COLUMN("var", TF_TYPE_FLOAT64);
1058
+ if (w_stddev) ADD_STAT_COLUMN("stddev", TF_TYPE_FLOAT64);
1059
+ if (w_median) ADD_STAT_COLUMN("median", TF_TYPE_FLOAT64);
1060
+ if (w_p25) ADD_STAT_COLUMN("p25", TF_TYPE_FLOAT64);
1061
+ if (w_p75) ADD_STAT_COLUMN("p75", TF_TYPE_FLOAT64);
1062
+ if (w_skewness) ADD_STAT_COLUMN("skewness", TF_TYPE_FLOAT64);
1063
+ if (w_kurtosis) ADD_STAT_COLUMN("kurtosis", TF_TYPE_FLOAT64);
1064
+ if (w_distinct) ADD_STAT_COLUMN("distinct", TF_TYPE_INT64);
1065
+ if (w_hist) ADD_STAT_COLUMN("hist", TF_TYPE_STRING);
1066
+ if (w_sample) ADD_STAT_COLUMN("sample", TF_TYPE_STRING);
1067
+ #undef ADD_STAT_COLUMN
1068
+
1069
+ return TF_OK;
1070
+ }
1071
+
1072
+ /* ---- Built-in ops table ---- */
1073
+
1074
+ static tf_arg_desc csv_decode_args[] = {
1075
+ {"delimiter", "string", false, "\",\""},
1076
+ {"header", "bool", false, "true"},
1077
+ {"batch_size","int", false, "1024"},
1078
+ {"repair", "bool", false, "false"},
1079
+ {"mode", "string", false, "permissive"},
1080
+ {"strict", "bool", false, "false"},
1081
+ {"max_error_bytes", "int", false, "4096"},
1082
+ {"max_record_bytes", "int", false, "67108864"},
1083
+ {"max_columns", "int", false, "8192"},
1084
+ {"nulls", "string|array", false, """"},
1085
+ {"na", "string|array", false, """"},
1086
+ {"quoted_nulls", "bool", false, "true"},
1087
+ {"audit", "bool", false, "false"},
1088
+ {"audit_limit", "int", false, "1000"},
1089
+ {"audit_include_row", "bool", false, "true"},
1090
+ {"audit_columns", "string[]", false, NULL},
1091
+ {"audit_redact", "string[]", false, NULL},
1092
+ {"audit_hash_columns", "string[]", false, NULL},
1093
+ {"audit_max_bytes", "size", false, "0"},
1094
+ {"audit_max_cell_bytes", "size", false, "0"},
1095
+ };
1096
+
1097
+ static tf_arg_desc csv_encode_args[] = {
1098
+ {"delimiter", "string", false, "\",\""},
1099
+ {"header", "bool", false, "true"},
1100
+ };
1101
+
1102
+ static tf_arg_desc jsonl_decode_args[] = {
1103
+ {"batch_size", "int", false, "1024"},
1104
+ {"on_error", "string", false, "skip"},
1105
+ {"max_error_bytes", "int", false, "4096"},
1106
+ {"max_record_bytes", "int", false, "67108864"},
1107
+ {"max_columns", "int", false, "8192"},
1108
+ };
1109
+
1110
+ static tf_arg_desc jsonl_encode_args[] = {
1111
+ {0} /* no args */
1112
+ };
1113
+
1114
+ static tf_arg_desc text_decode_args[] = {
1115
+ {"batch_size", "int", false, "1024"},
1116
+ {"max_error_bytes", "int", false, "4096"},
1117
+ {"max_record_bytes", "int", false, "67108864"},
1118
+ {"max_columns", "int", false, "8192"},
1119
+ };
1120
+
1121
+ static tf_arg_desc text_encode_args[] = {
1122
+ {0} /* no args */
1123
+ };
1124
+
1125
+ static tf_arg_desc grep_args[] = {
1126
+ {"pattern", "string", true, NULL},
1127
+ {"invert", "bool", false, "false"},
1128
+ {"column", "string", false, "\"_line\""},
1129
+ {"regex", "bool", false, "false"},
1130
+ };
1131
+
1132
+ static tf_arg_desc filter_args[] = {
1133
+ {"expr", "string", true, NULL},
1134
+ {"audit", "bool", false, "false"},
1135
+ {"audit_limit", "int", false, "1000"},
1136
+ {"audit_include_row", "bool", false, "true"},
1137
+ {"audit_columns", "string[]", false, NULL},
1138
+ {"audit_redact", "string[]", false, NULL},
1139
+ {"audit_hash_columns", "string[]", false, NULL},
1140
+ {"audit_max_bytes", "int", false, NULL},
1141
+ {"audit_max_cell_bytes", "int", false, NULL},
1142
+ };
1143
+
1144
+ static tf_arg_desc select_args[] = {
1145
+ {"columns", "string[]", true, NULL},
1146
+ };
1147
+
1148
+ static tf_arg_desc relocate_args[] = {
1149
+ {"columns", "string[]", true, NULL},
1150
+ {"before", "string", false, NULL},
1151
+ {"after", "string", false, NULL},
1152
+ };
1153
+
1154
+ static tf_arg_desc rename_args[] = {
1155
+ {"mapping", "map", true, NULL},
1156
+ };
1157
+
1158
+ static tf_arg_desc head_args[] = {
1159
+ {"n", "int", true, NULL},
1160
+ };
1161
+
1162
+ static tf_arg_desc skip_args[] = {
1163
+ {"n", "int", true, NULL},
1164
+ };
1165
+
1166
+ static tf_arg_desc derive_args[] = {
1167
+ {"columns", "map[]", true, NULL},
1168
+ };
1169
+
1170
+
1171
+ static tf_arg_desc across_args[] = {
1172
+ {"columns", "string[]", true, NULL},
1173
+ {"fn", "string", false, NULL},
1174
+ {"functions", "string[]", false, NULL},
1175
+ {"names", "string", false, NULL},
1176
+ {"replace", "bool", false, NULL},
1177
+ };
1178
+
1179
+ static tf_arg_desc stats_args[] = {
1180
+ {"stats", "string[]", false, NULL},
1181
+ };
1182
+
1183
+ static tf_arg_desc scan_args[] = {
1184
+ {"stats", "string[]", false, NULL},
1185
+ };
1186
+
1187
+ static tf_arg_desc unique_args[] = {
1188
+ {"columns", "string[]", false, NULL},
1189
+ {"max_keys", "int", false, NULL},
1190
+ {"max_state_bytes", "int", false, NULL},
1191
+ {"spill_dir", "string", false, NULL},
1192
+ {"spill_memory_bytes", "int", false, NULL},
1193
+ {"spill_run_rows", "int", false, NULL},
1194
+ {"spill_output_rows", "int", false, NULL},
1195
+ {"sorted", "bool", false, "false"},
1196
+ {"mode", "string", false, "\"exact\""},
1197
+ {"approx", "bool", false, "false"},
1198
+ {"bloom_bytes", "int", false, NULL},
1199
+ {"bloom_hashes", "int", false, "7"},
1200
+ };
1201
+
1202
+ static tf_arg_desc sort_args[] = {
1203
+ {"columns", "map[]", true, NULL},
1204
+ {"spill_dir", "string", false, NULL},
1205
+ {"spill_memory_bytes", "int", false, NULL},
1206
+ {"spill_run_rows", "int", false, NULL},
1207
+ {"spill_output_rows", "int", false, NULL},
1208
+ };
1209
+
1210
+ static tf_arg_desc validate_args[] = {
1211
+ {"expr", "string", false, NULL},
1212
+ {"rules", "array|object", false, NULL},
1213
+ {"rules_file", "string", false, NULL},
1214
+ {"audit", "bool", false, "false"},
1215
+ {"audit_limit", "int", false, "1000"},
1216
+ {"audit_include_row", "bool", false, "true"},
1217
+ {"audit_columns", "string[]", false, NULL},
1218
+ {"audit_redact", "string[]", false, NULL},
1219
+ {"audit_hash_columns", "string[]", false, NULL},
1220
+ {"audit_max_bytes", "int", false, NULL},
1221
+ {"audit_max_cell_bytes", "int", false, NULL},
1222
+ {"max_failures", "int", false, NULL},
1223
+ {"max_failure_rate", "number", false, NULL},
1224
+ {"warn_failure_rate", "number", false, NULL},
1225
+ {"name", "string", false, "\"validate\""},
1226
+ {"message", "string", false, "\"\""},
1227
+ };
1228
+
1229
+ static tf_arg_desc assert_args[] = {
1230
+ {"expr", "string", false, NULL},
1231
+ {"aggregate", "string", false, NULL},
1232
+ {"column", "string", false, NULL},
1233
+ {"op", "string", false, NULL},
1234
+ {"value", "number", false, NULL},
1235
+ {"tolerance", "number", false, "1e-12"},
1236
+ {"rel", "bool", false, "true"},
1237
+ {"action", "string", false, "\"fail\""},
1238
+ {"name", "string", false, "\"assert\""},
1239
+ {"message", "string", false, "\"\""},
1240
+ {"result", "string", false, "\"_assert\""},
1241
+ {"audit", "bool", false, "false"},
1242
+ {"audit_limit", "int", false, "1000"},
1243
+ {"audit_include_row", "bool", false, "true"},
1244
+ {"audit_columns", "string[]", false, NULL},
1245
+ {"audit_redact", "string[]", false, NULL},
1246
+ {"audit_hash_columns", "string[]", false, NULL},
1247
+ {"audit_max_bytes", "int", false, NULL},
1248
+ {"audit_max_cell_bytes", "int", false, NULL},
1249
+ };
1250
+
1251
+ static tf_arg_desc quarantine_args[] = {
1252
+ {"expr", "string", true, NULL},
1253
+ {"name", "string", false, "\"quarantine\""},
1254
+ {"message", "string", false, "\"\""},
1255
+ {"audit_include_row", "bool", false, "true"},
1256
+ {"audit_columns", "string[]", false, NULL},
1257
+ {"audit_redact", "string[]", false, NULL},
1258
+ {"audit_hash_columns", "string[]", false, NULL},
1259
+ {"audit_max_bytes", "size", false, "0"},
1260
+ {"audit_max_cell_bytes", "size", false, "0"},
1261
+ };
1262
+
1263
+ static tf_arg_desc tee_args[] = {
1264
+ {"expr", "string", false, NULL},
1265
+ {"channel", "string", false, "\"samples\""},
1266
+ {"columns", "string[]", false, NULL},
1267
+ {"limit", "int", false, "1000"},
1268
+ {"every", "int", false, "1"},
1269
+ {"name", "string", false, "\"tee\""},
1270
+ {"include_row", "bool", false, "true"},
1271
+ {"audit_include_row", "bool", false, "true"},
1272
+ {"audit_columns", "string[]", false, NULL},
1273
+ {"audit_redact", "string[]", false, NULL},
1274
+ {"audit_hash_columns", "string[]", false, NULL},
1275
+ {"audit_max_bytes", "size", false, "0"},
1276
+ {"audit_max_cell_bytes", "size", false, "0"},
1277
+ };
1278
+
1279
+ static tf_arg_desc schema_args[] = {
1280
+ {"columns", "map", false, NULL},
1281
+ {"required", "string[]", false, NULL},
1282
+ {"non_null", "string[]", false, NULL},
1283
+ {"nullable", "string[]", false, NULL},
1284
+ {"values", "map", false, NULL},
1285
+ {"min", "map", false, NULL},
1286
+ {"max", "map", false, NULL},
1287
+ {"regex", "map", false, NULL},
1288
+ {"max_regex_pattern_bytes", "int", false, "4096"},
1289
+ {"max_regex_cell_bytes", "int", false, "65536"},
1290
+ {"audit_include_row", "bool", false, "true"},
1291
+ {"audit_columns", "string[]", false, NULL},
1292
+ {"audit_redact", "string[]", false, NULL},
1293
+ {"audit_hash_columns", "string[]", false, NULL},
1294
+ {"audit_max_bytes", "int", false, NULL},
1295
+ {"audit_max_cell_bytes", "int", false, NULL},
1296
+ {"mode", "string", false, "\"fail\""},
1297
+ {"action", "string", false, "\"fail\""},
1298
+ {"name", "string", false, "\"schema\""},
1299
+ {"message", "string", false, "\"\""},
1300
+ {"result", "string", false, "\"_schema\""},
1301
+ };
1302
+
1303
+ static tf_arg_desc schema_infer_args[] = {
1304
+ {"rows", "int", false, "10000"},
1305
+ };
1306
+
1307
+ static tf_arg_desc trim_args[] = {
1308
+ {"columns", "string[]", false, NULL},
1309
+ };
1310
+
1311
+ static tf_arg_desc fill_null_args[] = {
1312
+ {"mapping", "map", true, NULL},
1313
+ {"audit", "bool", false, "false"},
1314
+ {"audit_limit", "int", false, "1000"},
1315
+ {"audit_include_row", "bool", false, "true"},
1316
+ {"audit_columns", "string[]", false, NULL},
1317
+ {"audit_redact", "string[]", false, NULL},
1318
+ {"audit_hash_columns", "string[]", false, NULL},
1319
+ {"audit_max_bytes", "size", false, "0"},
1320
+ {"audit_max_cell_bytes", "size", false, "0"},
1321
+ };
1322
+
1323
+ static tf_arg_desc cast_args[] = {
1324
+ {"mapping", "map", true, NULL},
1325
+ {"audit", "bool", false, "false"},
1326
+ {"audit_limit", "int", false, "1000"},
1327
+ {"audit_include_row", "bool", false, "true"},
1328
+ {"audit_columns", "string[]", false, NULL},
1329
+ {"audit_redact", "string[]", false, NULL},
1330
+ {"audit_hash_columns", "string[]", false, NULL},
1331
+ {"audit_max_bytes", "size", false, "0"},
1332
+ {"audit_max_cell_bytes", "size", false, "0"},
1333
+ };
1334
+
1335
+ static tf_arg_desc clip_args[] = {
1336
+ {"column", "string", true, NULL},
1337
+ {"min", "float", false, NULL},
1338
+ {"max", "float", false, NULL},
1339
+ };
1340
+
1341
+ static tf_arg_desc replace_args[] = {
1342
+ {"column", "string", true, NULL},
1343
+ {"pattern", "string", true, NULL},
1344
+ {"replacement", "string", true, NULL},
1345
+ {"regex", "bool", false, "false"},
1346
+ {"audit", "bool", false, "false"},
1347
+ {"audit_limit", "int", false, "1000"},
1348
+ {"audit_include_row", "bool", false, "true"},
1349
+ {"audit_columns", "string[]", false, NULL},
1350
+ {"audit_redact", "string[]", false, NULL},
1351
+ {"audit_hash_columns", "string[]", false, NULL},
1352
+ {"audit_max_bytes", "size", false, "0"},
1353
+ {"audit_max_cell_bytes", "size", false, "0"},
1354
+ };
1355
+
1356
+ static tf_arg_desc hash_args[] = {
1357
+ {"columns", "string[]", false, NULL},
1358
+ };
1359
+
1360
+ static tf_arg_desc bin_args[] = {
1361
+ {"column", "string", true, NULL},
1362
+ {"boundaries", "float[]", true, NULL},
1363
+ {"missing", "string", false, "\"error\""},
1364
+ {"on_type_error", "string", false, "\"fail\""},
1365
+ };
1366
+
1367
+ static tf_arg_desc fill_down_args[] = {
1368
+ {"columns", "string[]", false, NULL},
1369
+ };
1370
+
1371
+ static tf_arg_desc step_args[] = {
1372
+ {"column", "string", true, NULL},
1373
+ {"func", "string", true, NULL},
1374
+ {"result", "string", false, NULL},
1375
+ {"missing", "string", false, "\"error\""},
1376
+ {"on_type_error", "string", false, "\"fail\""},
1377
+ };
1378
+
1379
+ static tf_arg_desc window_args[] = {
1380
+ {"column", "string", true, NULL},
1381
+ {"size", "int", true, NULL},
1382
+ {"func", "string", true, NULL},
1383
+ {"result", "string", false, NULL},
1384
+ {"missing", "string", false, "\"error\""},
1385
+ {"on_type_error", "string", false, "\"fail\""},
1386
+ };
1387
+
1388
+ static tf_arg_desc rolling_args[] = {
1389
+ {"column", "string", true, NULL},
1390
+ {"size", "int", true, NULL},
1391
+ {"result", "string", false, NULL},
1392
+ {"missing", "string", false, "\"error\""},
1393
+ {"on_type_error", "string", false, "\"fail\""},
1394
+ };
1395
+
1396
+ static tf_arg_desc rolling_bool_args[] = {
1397
+ {"column", "string", true, NULL},
1398
+ {"size", "int", true, NULL},
1399
+ {"result", "string", false, NULL},
1400
+ {"nulls", "string", false, "\"ignore\""},
1401
+ };
1402
+
1403
+ static tf_arg_desc explode_args[] = {
1404
+ {"column", "string", true, NULL},
1405
+ {"delimiter", "string", false, "\",\""},
1406
+ {"max_tokens_per_row", "int", false, NULL},
1407
+ {"max_output_rows_per_input_row", "int", false, NULL},
1408
+ {"max_output_rows_per_batch", "int", false, NULL},
1409
+ {"max_token_bytes", "int", false, NULL},
1410
+ };
1411
+
1412
+ static tf_arg_desc split_args[] = {
1413
+ {"column", "string", true, NULL},
1414
+ {"delimiter", "string", false, "\" \""},
1415
+ {"names", "string[]", true, NULL},
1416
+ };
1417
+
1418
+ static tf_arg_desc unpivot_args[] = {
1419
+ {"columns", "string[]", true, NULL},
1420
+ {"max_output_rows_per_input_row", "int", false, NULL},
1421
+ {"max_output_rows_per_batch", "int", false, NULL},
1422
+ };
1423
+
1424
+ static tf_arg_desc tail_args[] = {
1425
+ {"n", "int", true, NULL},
1426
+ };
1427
+
1428
+ static tf_arg_desc top_args[] = {
1429
+ {"n", "int", true, NULL},
1430
+ {"column", "string", true, NULL},
1431
+ {"desc", "bool", false, "true"},
1432
+ {"with_ties", "bool", false, "false"},
1433
+ };
1434
+
1435
+ static tf_arg_desc sample_args[] = {
1436
+ {"n", "int", true, NULL},
1437
+ {"seed", "int|string", false, "0"},
1438
+ };
1439
+
1440
+ static tf_arg_desc group_agg_args[] = {
1441
+ {"group_by", "string[]", true, NULL},
1442
+ {"aggs", "map[]", true, NULL},
1443
+ {"max_groups", "int", false, NULL},
1444
+ {"max_state_bytes", "int", false, NULL},
1445
+ {"spill_dir", "string", false, NULL},
1446
+ {"spill_memory_bytes", "int", false, NULL},
1447
+ {"spill_run_rows", "int", false, NULL},
1448
+ {"spill_output_rows", "int", false, NULL},
1449
+ {"sorted", "bool", false, "false"},
1450
+ };
1451
+
1452
+ static tf_arg_desc frequency_args[] = {
1453
+ {"columns", "string[]", false, NULL},
1454
+ {"max_values", "int", false, NULL},
1455
+ {"max_state_bytes", "int", false, NULL},
1456
+ {"overflow", "string", false, "\"error\""},
1457
+ {"other", "string", false, "\"__other__\""},
1458
+ {"audit", "bool", false, "false"},
1459
+ {"audit_limit", "int", false, "1000"},
1460
+ {"audit_include_row", "bool", false, "true"},
1461
+ {"audit_columns", "string[]", false, NULL},
1462
+ {"audit_redact", "string[]", false, NULL},
1463
+ {"audit_hash_columns", "string[]", false, NULL},
1464
+ {"audit_max_bytes", "size", false, "0"},
1465
+ {"audit_max_cell_bytes", "size", false, "0"},
1466
+ };
1467
+
1468
+ static tf_arg_desc datetime_args[] = {
1469
+ {"column", "string", true, NULL},
1470
+ {"extract", "string[]", false, NULL},
1471
+ {"missing", "string", false, "\"error\""},
1472
+ {"on_type_error", "string", false, "\"fail\""},
1473
+ };
1474
+
1475
+ static tf_arg_desc pivot_args[] = {
1476
+ {"name_column", "string", true, NULL},
1477
+ {"value_column", "string", true, NULL},
1478
+ {"agg", "string", false, "\"first\""},
1479
+ {"categories", "array", false, NULL},
1480
+ {"max_categories", "int", false, NULL},
1481
+ {"sorted", "bool", false, "false"},
1482
+ {"spill_dir", "string", false, NULL},
1483
+ {"spill_memory_bytes", "int", false, NULL},
1484
+ {"spill_run_rows", "int", false, NULL},
1485
+ {"spill_output_rows", "int", false, NULL},
1486
+ };
1487
+
1488
+ static tf_arg_desc join_args[] = {
1489
+ {"file", "string", true, NULL},
1490
+ {"on", "string", true, NULL},
1491
+ {"how", "string", false, "\"inner\""},
1492
+ {"max_lookup_rows", "int", false, NULL},
1493
+ {"max_lookup_keys", "int", false, NULL},
1494
+ {"max_lookup_bytes", "int", false, NULL},
1495
+ {"max_state_bytes", "int", false, NULL},
1496
+ {"max_matches_per_row", "int", false, NULL},
1497
+ {"max_output_rows", "int", false, NULL},
1498
+ {"spill_dir", "string", false, NULL},
1499
+ {"spill_memory_bytes", "int", false, NULL},
1500
+ {"spill_run_rows", "int", false, NULL},
1501
+ {"spill_output_rows", "int", false, NULL},
1502
+ {"sorted", "bool", false, "false"},
1503
+ };
1504
+
1505
+ static tf_arg_desc set_file_args[] = {
1506
+ {"file", "string", true, NULL},
1507
+ {"columns", "string[]", false, NULL},
1508
+ {"max_lookup_rows", "int", false, NULL},
1509
+ {"max_lookup_keys", "int", false, NULL},
1510
+ {"max_lookup_bytes", "int", false, NULL},
1511
+ {"max_output_keys", "int", false, NULL},
1512
+ {"max_state_bytes", "int", false, NULL},
1513
+ {"spill_dir", "string", false, NULL},
1514
+ {"spill_memory_bytes", "int", false, NULL},
1515
+ {"spill_run_rows", "int", false, NULL},
1516
+ {"spill_output_rows", "int", false, NULL},
1517
+ {"sorted", "bool", false, "false"},
1518
+ };
1519
+
1520
+ static tf_arg_desc union_args[] = {
1521
+ {"file", "string", true, NULL},
1522
+ {"columns", "string[]", false, NULL},
1523
+ {"max_lookup_rows", "int", false, NULL},
1524
+ {"max_lookup_bytes", "int", false, NULL},
1525
+ {"max_output_keys", "int", false, NULL},
1526
+ {"max_state_bytes", "int", false, NULL},
1527
+ {"spill_dir", "string", false, NULL},
1528
+ {"spill_memory_bytes", "int", false, NULL},
1529
+ {"spill_run_rows", "int", false, NULL},
1530
+ {"spill_output_rows", "int", false, NULL},
1531
+ {"sorted", "bool", false, "false"},
1532
+ };
1533
+
1534
+ static tf_arg_desc union_all_args[] = {
1535
+ {"file", "string", true, NULL},
1536
+ {"max_lookup_rows", "int", false, NULL},
1537
+ {"max_lookup_bytes", "int", false, NULL},
1538
+ };
1539
+
1540
+ static tf_arg_desc stack_args[] = {
1541
+ {"file", "string", true, NULL},
1542
+ {"tag", "string", false, NULL},
1543
+ {"tag_value", "string", false, NULL},
1544
+ };
1545
+
1546
+ static tf_arg_desc lead_args[] = {
1547
+ {"column", "string", true, NULL},
1548
+ {"offset", "int", false, "1"},
1549
+ {"result", "string", false, NULL},
1550
+ };
1551
+
1552
+ static tf_arg_desc lag_args[] = {
1553
+ {"column", "string", true, NULL},
1554
+ {"offset", "int", false, "1"},
1555
+ {"result", "string", false, NULL},
1556
+ };
1557
+
1558
+ static tf_arg_desc shift_args[] = {
1559
+ {"column", "string", true, NULL},
1560
+ {"offset", "int", false, "1"},
1561
+ {"result", "string", false, NULL},
1562
+ {"type", "string", false, "\"lag\""},
1563
+ };
1564
+
1565
+ static tf_arg_desc rowid_args[] = {
1566
+ {"columns", "string[]", false, NULL},
1567
+ {"result", "string", false, "\"_rowid\""},
1568
+ {"sorted", "bool", false, "false"},
1569
+ {"max_keys", "int", false, NULL},
1570
+ {"max_state_bytes", "int", false, NULL},
1571
+ };
1572
+
1573
+ static tf_arg_desc rleid_args[] = {
1574
+ {"columns", "string[]", true, NULL},
1575
+ {"result", "string", false, "\"_rleid\""},
1576
+ };
1577
+
1578
+ static tf_arg_desc date_trunc_args[] = {
1579
+ {"column", "string", true, NULL},
1580
+ {"trunc", "string", true, NULL},
1581
+ {"result", "string", false, NULL},
1582
+ {"missing", "string", false, "\"error\""},
1583
+ {"on_type_error", "string", false, "\"fail\""},
1584
+ };
1585
+
1586
+ static tf_arg_desc onehot_args[] = {
1587
+ {"column", "string", true, NULL},
1588
+ {"drop", "bool", false, "false"},
1589
+ {"categories", "array", false, NULL},
1590
+ {"max_categories", "int", false, NULL},
1591
+ {"max_state_bytes", "int", false, NULL},
1592
+ {"unknown", "string", false, NULL},
1593
+ };
1594
+
1595
+ static tf_arg_desc label_encode_args[] = {
1596
+ {"column", "string", true, NULL},
1597
+ {"result", "string", false, NULL},
1598
+ {"categories", "array", false, NULL},
1599
+ {"max_categories", "int", false, NULL},
1600
+ {"max_state_bytes", "int", false, NULL},
1601
+ {"unknown", "string", false, NULL},
1602
+ };
1603
+
1604
+ static tf_arg_desc ewma_args[] = {
1605
+ {"column", "string", true, NULL},
1606
+ {"alpha", "float", true, NULL},
1607
+ {"result", "string", false, NULL},
1608
+ {"missing", "string", false, "\"error\""},
1609
+ {"on_type_error", "string", false, "\"fail\""},
1610
+ };
1611
+
1612
+ static tf_arg_desc diff_args[] = {
1613
+ {"column", "string", true, NULL},
1614
+ {"order", "int", false, "1"},
1615
+ {"result", "string", false, NULL},
1616
+ {"missing", "string", false, "\"error\""},
1617
+ {"on_type_error", "string", false, "\"fail\""},
1618
+ };
1619
+
1620
+ static tf_arg_desc anomaly_args[] = {
1621
+ {"column", "string", true, NULL},
1622
+ {"threshold", "float", false, "3.0"},
1623
+ {"result", "string", false, NULL},
1624
+ {"missing", "string", false, "\"error\""},
1625
+ {"on_type_error", "string", false, "\"fail\""},
1626
+ };
1627
+
1628
+ static tf_arg_desc split_data_args[] = {
1629
+ {"ratio", "float", false, "0.8"},
1630
+ {"result", "string", false, "\"_split\""},
1631
+ {"seed", "int", false, "42"},
1632
+ };
1633
+
1634
+ static tf_arg_desc source_name_args[] = {
1635
+ {"result", "string", false, "\"_source\""},
1636
+ {"default", "string", false, "\"\""},
1637
+ };
1638
+
1639
+ static tf_arg_desc interpolate_args[] = {
1640
+ {"column", "string", true, NULL},
1641
+ {"method", "string", false, "\"linear\""},
1642
+ {"missing", "string", false, "\"error\""},
1643
+ {"on_type_error", "string", false, "\"fail\""},
1644
+ };
1645
+
1646
+ static tf_arg_desc normalize_args[] = {
1647
+ {"columns", "string[]", true, NULL},
1648
+ {"method", "string", false, "\"minmax\""},
1649
+ {"missing", "string", false, "\"error\""},
1650
+ {"on_type_error", "string", false, "\"fail\""},
1651
+ {"audit", "bool", false, "false"},
1652
+ {"audit_limit", "int", false, "1000"},
1653
+ {"audit_include_row", "bool", false, "true"},
1654
+ {"audit_columns", "string[]", false, NULL},
1655
+ {"audit_redact", "string[]", false, NULL},
1656
+ {"audit_hash_columns", "string[]", false, NULL},
1657
+ {"audit_max_bytes", "size", false, "0"},
1658
+ {"audit_max_cell_bytes", "size", false, "0"},
1659
+ };
1660
+
1661
+ static tf_arg_desc acf_args[] = {
1662
+ {"column", "string", true, NULL},
1663
+ {"lags", "int", false, "20"},
1664
+ {"missing", "string", false, "\"error\""},
1665
+ {"on_type_error", "string", false, "\"fail\""},
1666
+ };
1667
+
1668
+ static tf_arg_desc json_extract_args[] = {
1669
+ {"column", "string", false, "\"_line\""},
1670
+ {"path", "string", true, NULL},
1671
+ {"result", "string", true, NULL},
1672
+ {"type", "string", false, "\"string\""},
1673
+ };
1674
+
1675
+ static tf_arg_desc json_filter_args[] = {
1676
+ {"column", "string", false, "\"_line\""},
1677
+ {"path", "string", true, NULL},
1678
+ {"op", "string", false, "\"exists\""},
1679
+ {"value", "string", false, NULL},
1680
+ {"type", "string", false, "\"auto\""},
1681
+ };
1682
+
1683
+ static tf_arg_desc json_schema_args[] = {
1684
+ {"column", "string", false, "\"_line\""},
1685
+ {"schema", "object|string|bool", true, NULL},
1686
+ {"mode", "string", false, "\"annotate\""},
1687
+ {"result", "string", false, "\"_valid\""},
1688
+ {"audit", "bool", false, "false"},
1689
+ {"audit_limit", "int", false, "1000"},
1690
+ {"audit_include_row", "bool", false, "true"},
1691
+ {"audit_columns", "string[]", false, NULL},
1692
+ {"audit_redact", "string[]", false, NULL},
1693
+ {"audit_hash_columns", "string[]", false, NULL},
1694
+ {"audit_max_bytes", "int", false, NULL},
1695
+ {"audit_max_cell_bytes", "int", false, NULL},
1696
+ };
1697
+
1698
+ static tf_arg_desc json_flatten_args[] = {
1699
+ {"column", "string", false, "\"_line\""},
1700
+ {"fields", "object[]|string[]", true, NULL},
1701
+ };
1702
+
1703
+ static tf_arg_desc table_encode_args[] = {
1704
+ {"max_width", "int", false, "40"},
1705
+ {"max_rows", "int", false, "0"},
1706
+ };
1707
+
1708
+
1709
+ const char *tf_memory_class_name(tf_memory_class cls) {
1710
+ switch (cls) {
1711
+ case TF_MEM_ROW_LOCAL: return "row_local";
1712
+ case TF_MEM_BOUNDED_STATE: return "bounded_state";
1713
+ case TF_MEM_KEY_STATE: return "key_state";
1714
+ case TF_MEM_BLOCKING: return "blocking";
1715
+ case TF_MEM_EXTERNAL: return "external";
1716
+ default: return "unknown";
1717
+ }
1718
+ }
1719
+
1720
+ const char *tf_emit_class_name(tf_emit_class cls) {
1721
+ switch (cls) {
1722
+ case TF_EMIT_PER_BATCH: return "per_batch";
1723
+ case TF_EMIT_ON_FLUSH: return "on_flush";
1724
+ case TF_EMIT_SIDE_ONLY: return "side_only";
1725
+ case TF_EMIT_MIXED: return "mixed";
1726
+ default: return "unknown";
1727
+ }
1728
+ }
1729
+
1730
+ const char *tf_schema_class_name(tf_schema_class cls) {
1731
+ switch (cls) {
1732
+ case TF_SCHEMA_STABLE: return "stable";
1733
+ case TF_SCHEMA_PARAMETRIC: return "parametric";
1734
+ case TF_SCHEMA_DATA_DEPENDENT: return "data_dependent";
1735
+ default: return "unknown";
1736
+ }
1737
+ }
1738
+
1739
+ static tf_op_entry builtin_ops[] = {
1740
+ {
1741
+ .name = "codec.csv.decode",
1742
+ .kind = TF_OP_DECODER,
1743
+ .tier = TF_TIER_CORE,
1744
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1745
+ .memory_class = TF_MEM_BOUNDED_STATE,
1746
+ .emit_class = TF_EMIT_PER_BATCH,
1747
+ .schema_class = TF_SCHEMA_DATA_DEPENDENT,
1748
+ .state_estimate = "O(batch_size * columns)",
1749
+ .args = csv_decode_args,
1750
+ .n_args = 20,
1751
+ .infer_schema = infer_schema_unknown,
1752
+ .create_native = (void *(*)(const cJSON *))tf_csv_decoder_create,
1753
+ },
1754
+ {
1755
+ .name = "codec.csv.encode",
1756
+ .kind = TF_OP_ENCODER,
1757
+ .tier = TF_TIER_CORE,
1758
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1759
+ .memory_class = TF_MEM_BOUNDED_STATE,
1760
+ .emit_class = TF_EMIT_PER_BATCH,
1761
+ .schema_class = TF_SCHEMA_STABLE,
1762
+ .state_estimate = "O(output_chunk)",
1763
+ .args = csv_encode_args,
1764
+ .n_args = 2,
1765
+ .infer_schema = infer_schema_sink,
1766
+ .create_native = (void *(*)(const cJSON *))tf_csv_encoder_create,
1767
+ },
1768
+ {
1769
+ .name = "codec.jsonl.decode",
1770
+ .kind = TF_OP_DECODER,
1771
+ .tier = TF_TIER_CORE,
1772
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1773
+ .memory_class = TF_MEM_BOUNDED_STATE,
1774
+ .emit_class = TF_EMIT_PER_BATCH,
1775
+ .schema_class = TF_SCHEMA_DATA_DEPENDENT,
1776
+ .state_estimate = "O(batch_size)",
1777
+ .args = jsonl_decode_args,
1778
+ .n_args = 3,
1779
+ .infer_schema = infer_schema_unknown,
1780
+ .create_native = (void *(*)(const cJSON *))tf_jsonl_decoder_create,
1781
+ },
1782
+ {
1783
+ .name = "codec.jsonl.encode",
1784
+ .kind = TF_OP_ENCODER,
1785
+ .tier = TF_TIER_CORE,
1786
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1787
+ .memory_class = TF_MEM_BOUNDED_STATE,
1788
+ .emit_class = TF_EMIT_PER_BATCH,
1789
+ .schema_class = TF_SCHEMA_STABLE,
1790
+ .state_estimate = "O(output_chunk)",
1791
+ .args = jsonl_encode_args,
1792
+ .n_args = 0,
1793
+ .infer_schema = infer_schema_sink,
1794
+ .create_native = (void *(*)(const cJSON *))tf_jsonl_encoder_create,
1795
+ },
1796
+ {
1797
+ .name = "codec.text.decode",
1798
+ .kind = TF_OP_DECODER,
1799
+ .tier = TF_TIER_CORE,
1800
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1801
+ .memory_class = TF_MEM_BOUNDED_STATE,
1802
+ .emit_class = TF_EMIT_PER_BATCH,
1803
+ .schema_class = TF_SCHEMA_PARAMETRIC,
1804
+ .state_estimate = "O(batch_size)",
1805
+ .args = text_decode_args,
1806
+ .n_args = 1,
1807
+ .infer_schema = infer_schema_unknown,
1808
+ .create_native = (void *(*)(const cJSON *))tf_text_decoder_create,
1809
+ },
1810
+ {
1811
+ .name = "codec.text.encode",
1812
+ .kind = TF_OP_ENCODER,
1813
+ .tier = TF_TIER_CORE,
1814
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1815
+ .memory_class = TF_MEM_BOUNDED_STATE,
1816
+ .emit_class = TF_EMIT_PER_BATCH,
1817
+ .schema_class = TF_SCHEMA_STABLE,
1818
+ .state_estimate = "O(output_chunk)",
1819
+ .args = text_encode_args,
1820
+ .n_args = 0,
1821
+ .infer_schema = infer_schema_sink,
1822
+ .create_native = (void *(*)(const cJSON *))tf_text_encoder_create,
1823
+ },
1824
+ {
1825
+ .name = "grep",
1826
+ .kind = TF_OP_TRANSFORM,
1827
+ .tier = TF_TIER_CORE,
1828
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1829
+ .memory_class = TF_MEM_ROW_LOCAL,
1830
+ .emit_class = TF_EMIT_PER_BATCH,
1831
+ .schema_class = TF_SCHEMA_STABLE,
1832
+ .state_estimate = "O(batch_rows)",
1833
+ .args = grep_args,
1834
+ .n_args = 4,
1835
+ .infer_schema = infer_schema_passthrough,
1836
+ .create_native = (void *(*)(const cJSON *))tf_grep_create,
1837
+ },
1838
+ {
1839
+ .name = "filter",
1840
+ .kind = TF_OP_TRANSFORM,
1841
+ .tier = TF_TIER_CORE,
1842
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1843
+ .memory_class = TF_MEM_ROW_LOCAL,
1844
+ .emit_class = TF_EMIT_PER_BATCH,
1845
+ .schema_class = TF_SCHEMA_STABLE,
1846
+ .state_estimate = "O(batch_rows * columns)",
1847
+ .args = filter_args,
1848
+ .n_args = 9,
1849
+ .infer_schema = infer_schema_passthrough,
1850
+ .create_native = (void *(*)(const cJSON *))tf_filter_create,
1851
+ },
1852
+ {
1853
+ .name = "select",
1854
+ .kind = TF_OP_TRANSFORM,
1855
+ .tier = TF_TIER_CORE,
1856
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1857
+ .memory_class = TF_MEM_ROW_LOCAL,
1858
+ .emit_class = TF_EMIT_PER_BATCH,
1859
+ .schema_class = TF_SCHEMA_PARAMETRIC,
1860
+ .state_estimate = "O(batch_rows * selected_columns)",
1861
+ .args = select_args,
1862
+ .n_args = 1,
1863
+ .infer_schema = infer_schema_select,
1864
+ .create_native = (void *(*)(const cJSON *))tf_select_create,
1865
+ },
1866
+ {
1867
+ .name = "rename",
1868
+ .kind = TF_OP_TRANSFORM,
1869
+ .tier = TF_TIER_CORE,
1870
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1871
+ .memory_class = TF_MEM_ROW_LOCAL,
1872
+ .emit_class = TF_EMIT_PER_BATCH,
1873
+ .schema_class = TF_SCHEMA_PARAMETRIC,
1874
+ .state_estimate = "O(columns)",
1875
+ .args = rename_args,
1876
+ .n_args = 1,
1877
+ .infer_schema = infer_schema_rename,
1878
+ .create_native = (void *(*)(const cJSON *))tf_rename_create,
1879
+ },
1880
+ {
1881
+ .name = "head",
1882
+ .kind = TF_OP_TRANSFORM,
1883
+ .tier = TF_TIER_CORE,
1884
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1885
+ .memory_class = TF_MEM_BOUNDED_STATE,
1886
+ .emit_class = TF_EMIT_PER_BATCH,
1887
+ .schema_class = TF_SCHEMA_STABLE,
1888
+ .state_estimate = "O(1)",
1889
+ .args = head_args,
1890
+ .n_args = 1,
1891
+ .infer_schema = infer_schema_passthrough,
1892
+ .create_native = (void *(*)(const cJSON *))tf_head_create,
1893
+ },
1894
+ {
1895
+ .name = "slice-head",
1896
+ .kind = TF_OP_TRANSFORM,
1897
+ .tier = TF_TIER_CORE,
1898
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1899
+ .memory_class = TF_MEM_BOUNDED_STATE,
1900
+ .emit_class = TF_EMIT_PER_BATCH,
1901
+ .schema_class = TF_SCHEMA_STABLE,
1902
+ .state_estimate = "O(1)",
1903
+ .args = head_args,
1904
+ .n_args = 1,
1905
+ .infer_schema = infer_schema_passthrough,
1906
+ .create_native = (void *(*)(const cJSON *))tf_head_create,
1907
+ },
1908
+ {
1909
+ .name = "skip",
1910
+ .kind = TF_OP_TRANSFORM,
1911
+ .tier = TF_TIER_CORE,
1912
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1913
+ .memory_class = TF_MEM_BOUNDED_STATE,
1914
+ .emit_class = TF_EMIT_PER_BATCH,
1915
+ .schema_class = TF_SCHEMA_STABLE,
1916
+ .state_estimate = "O(1)",
1917
+ .args = skip_args,
1918
+ .n_args = 1,
1919
+ .infer_schema = infer_schema_passthrough,
1920
+ .create_native = (void *(*)(const cJSON *))tf_skip_create,
1921
+ },
1922
+ {
1923
+ .name = "derive",
1924
+ .kind = TF_OP_TRANSFORM,
1925
+ .tier = TF_TIER_CORE,
1926
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1927
+ .memory_class = TF_MEM_ROW_LOCAL,
1928
+ .emit_class = TF_EMIT_PER_BATCH,
1929
+ .schema_class = TF_SCHEMA_PARAMETRIC,
1930
+ .state_estimate = "O(batch_rows * derived_columns)",
1931
+ .args = derive_args,
1932
+ .n_args = 1,
1933
+ .infer_schema = infer_schema_derive,
1934
+ .create_native = (void *(*)(const cJSON *))tf_derive_create,
1935
+ },
1936
+
1937
+ {
1938
+ .name = "source-name",
1939
+ .kind = TF_OP_TRANSFORM,
1940
+ .tier = TF_TIER_CORE,
1941
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1942
+ .memory_class = TF_MEM_ROW_LOCAL,
1943
+ .emit_class = TF_EMIT_PER_BATCH,
1944
+ .schema_class = TF_SCHEMA_PARAMETRIC,
1945
+ .state_estimate = "O(batch_rows)",
1946
+ .args = source_name_args,
1947
+ .n_args = 2,
1948
+ .infer_schema = infer_schema_source_name,
1949
+ .create_native = (void *(*)(const cJSON *))tf_source_name_create,
1950
+ },
1951
+
1952
+ {
1953
+ .name = "across",
1954
+ .kind = TF_OP_TRANSFORM,
1955
+ .tier = TF_TIER_CORE,
1956
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1957
+ .memory_class = TF_MEM_ROW_LOCAL,
1958
+ .emit_class = TF_EMIT_PER_BATCH,
1959
+ .schema_class = TF_SCHEMA_PARAMETRIC,
1960
+ .state_estimate = "O(batch_rows * selected_columns * functions)",
1961
+ .args = across_args,
1962
+ .n_args = 5,
1963
+ .infer_schema = infer_schema_across,
1964
+ .create_native = (void *(*)(const cJSON *))tf_across_create,
1965
+ },
1966
+ {
1967
+ .name = "stats",
1968
+ .kind = TF_OP_TRANSFORM,
1969
+ .tier = TF_TIER_CORE,
1970
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1971
+ .memory_class = TF_MEM_BOUNDED_STATE,
1972
+ .emit_class = TF_EMIT_ON_FLUSH,
1973
+ .schema_class = TF_SCHEMA_PARAMETRIC,
1974
+ .state_estimate = "O(columns * requested_stats)",
1975
+ .args = stats_args,
1976
+ .n_args = 1,
1977
+ .infer_schema = infer_schema_stats,
1978
+ .create_native = (void *(*)(const cJSON *))tf_stats_create,
1979
+ },
1980
+ {
1981
+ .name = "scan",
1982
+ .kind = TF_OP_TRANSFORM,
1983
+ .tier = TF_TIER_CORE,
1984
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1985
+ .memory_class = TF_MEM_BOUNDED_STATE,
1986
+ .emit_class = TF_EMIT_ON_FLUSH,
1987
+ .schema_class = TF_SCHEMA_PARAMETRIC,
1988
+ .state_estimate = "O(columns * profile_stats)",
1989
+ .args = scan_args,
1990
+ .n_args = 1,
1991
+ .infer_schema = infer_schema_stats,
1992
+ .create_native = (void *(*)(const cJSON *))tf_scan_create,
1993
+ },
1994
+ {
1995
+ .name = "unique",
1996
+ .kind = TF_OP_TRANSFORM,
1997
+ .tier = TF_TIER_CORE,
1998
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
1999
+ .memory_class = TF_MEM_KEY_STATE,
2000
+ .emit_class = TF_EMIT_PER_BATCH,
2001
+ .schema_class = TF_SCHEMA_STABLE,
2002
+ .state_estimate = "O(1) sorted, O(distinct_keys) exact unsorted or capped by max_keys/max_state_bytes, O(bloom_bytes) approximate, or O(spill_run_rows * columns) RAM with spill_dir",
2003
+ .args = unique_args,
2004
+ .n_args = 12,
2005
+ .infer_schema = infer_schema_passthrough,
2006
+ .create_native = (void *(*)(const cJSON *))tf_unique_create,
2007
+ },
2008
+ {
2009
+ .name = "sort",
2010
+ .kind = TF_OP_TRANSFORM,
2011
+ .tier = TF_TIER_CORE,
2012
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2013
+ .memory_class = TF_MEM_BLOCKING,
2014
+ .emit_class = TF_EMIT_ON_FLUSH,
2015
+ .schema_class = TF_SCHEMA_STABLE,
2016
+ .state_estimate = "O(input_rows)",
2017
+ .args = sort_args,
2018
+ .n_args = 5,
2019
+ .infer_schema = infer_schema_passthrough,
2020
+ .create_native = (void *(*)(const cJSON *))tf_sort_create,
2021
+ },
2022
+ /* ---- Aliases ---- */
2023
+ {
2024
+ .name = "reorder",
2025
+ .kind = TF_OP_TRANSFORM,
2026
+ .tier = TF_TIER_CORE,
2027
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2028
+ .memory_class = TF_MEM_ROW_LOCAL,
2029
+ .emit_class = TF_EMIT_PER_BATCH,
2030
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2031
+ .state_estimate = "O(batch_rows * selected_columns)",
2032
+ .args = select_args,
2033
+ .n_args = 1,
2034
+ .infer_schema = infer_schema_select,
2035
+ .create_native = (void *(*)(const cJSON *))tf_select_create,
2036
+ },
2037
+ {
2038
+ .name = "relocate",
2039
+ .kind = TF_OP_TRANSFORM,
2040
+ .tier = TF_TIER_CORE,
2041
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2042
+ .memory_class = TF_MEM_ROW_LOCAL,
2043
+ .emit_class = TF_EMIT_PER_BATCH,
2044
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2045
+ .state_estimate = "O(batch_rows * columns)",
2046
+ .args = relocate_args,
2047
+ .n_args = 3,
2048
+ .infer_schema = infer_schema_relocate,
2049
+ .create_native = (void *(*)(const cJSON *))tf_relocate_create,
2050
+ },
2051
+ {
2052
+ .name = "dedup",
2053
+ .kind = TF_OP_TRANSFORM,
2054
+ .tier = TF_TIER_CORE,
2055
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2056
+ .memory_class = TF_MEM_KEY_STATE,
2057
+ .emit_class = TF_EMIT_PER_BATCH,
2058
+ .schema_class = TF_SCHEMA_STABLE,
2059
+ .state_estimate = "O(1) sorted, O(distinct_keys) exact unsorted or capped by max_keys/max_state_bytes, O(bloom_bytes) approximate, or O(spill_run_rows * columns) RAM with spill_dir",
2060
+ .args = unique_args,
2061
+ .n_args = 12,
2062
+ .infer_schema = infer_schema_passthrough,
2063
+ .create_native = (void *(*)(const cJSON *))tf_unique_create,
2064
+ },
2065
+ /* ---- Simple streaming ---- */
2066
+ {
2067
+ .name = "validate",
2068
+ .kind = TF_OP_TRANSFORM,
2069
+ .tier = TF_TIER_CORE,
2070
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2071
+ .memory_class = TF_MEM_ROW_LOCAL,
2072
+ .emit_class = TF_EMIT_MIXED,
2073
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2074
+ .state_estimate = "O(batch_rows * columns)",
2075
+ .args = validate_args,
2076
+ .n_args = 16,
2077
+ .infer_schema = infer_schema_validate,
2078
+ .create_native = (void *(*)(const cJSON *))tf_validate_create,
2079
+ },
2080
+ {
2081
+ .name = "assert",
2082
+ .kind = TF_OP_TRANSFORM,
2083
+ .tier = TF_TIER_CORE,
2084
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2085
+ .memory_class = TF_MEM_ROW_LOCAL,
2086
+ .emit_class = TF_EMIT_MIXED,
2087
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2088
+ .state_estimate = "O(batch_rows * columns) row mode, O(1) aggregate mode",
2089
+ .args = assert_args,
2090
+ .n_args = 19,
2091
+ .infer_schema = infer_schema_assert,
2092
+ .create_native = (void *(*)(const cJSON *))tf_assert_create,
2093
+ },
2094
+ {
2095
+ .name = "quarantine",
2096
+ .kind = TF_OP_TRANSFORM,
2097
+ .tier = TF_TIER_CORE,
2098
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2099
+ .memory_class = TF_MEM_ROW_LOCAL,
2100
+ .emit_class = TF_EMIT_MIXED,
2101
+ .schema_class = TF_SCHEMA_STABLE,
2102
+ .state_estimate = "O(batch_rows * columns)",
2103
+ .args = quarantine_args,
2104
+ .n_args = 9,
2105
+ .infer_schema = infer_schema_passthrough,
2106
+ .create_native = (void *(*)(const cJSON *))tf_quarantine_create,
2107
+ },
2108
+ {
2109
+ .name = "tee",
2110
+ .kind = TF_OP_TRANSFORM,
2111
+ .tier = TF_TIER_CORE,
2112
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2113
+ .memory_class = TF_MEM_ROW_LOCAL,
2114
+ .emit_class = TF_EMIT_MIXED,
2115
+ .schema_class = TF_SCHEMA_STABLE,
2116
+ .state_estimate = "O(batch_rows * selected_columns)",
2117
+ .args = tee_args,
2118
+ .n_args = 13,
2119
+ .infer_schema = infer_schema_passthrough,
2120
+ .create_native = (void *(*)(const cJSON *))tf_tee_create,
2121
+ },
2122
+ {
2123
+ .name = "schema",
2124
+ .kind = TF_OP_TRANSFORM,
2125
+ .tier = TF_TIER_CORE,
2126
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2127
+ .memory_class = TF_MEM_ROW_LOCAL,
2128
+ .emit_class = TF_EMIT_MIXED,
2129
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2130
+ .state_estimate = "O(batch_rows * declared_columns)",
2131
+ .args = schema_args,
2132
+ .n_args = 13,
2133
+ .infer_schema = infer_schema_schema,
2134
+ .create_native = (void *(*)(const cJSON *))tf_schema_create,
2135
+ },
2136
+ {
2137
+ .name = "schema-infer",
2138
+ .kind = TF_OP_TRANSFORM,
2139
+ .tier = TF_TIER_CORE,
2140
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2141
+ .memory_class = TF_MEM_BOUNDED_STATE,
2142
+ .emit_class = TF_EMIT_ON_FLUSH,
2143
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2144
+ .state_estimate = "O(columns)",
2145
+ .args = schema_infer_args,
2146
+ .n_args = 1,
2147
+ .infer_schema = infer_schema_schema_infer,
2148
+ .create_native = (void *(*)(const cJSON *))tf_schema_infer_create,
2149
+ },
2150
+ {
2151
+ .name = "trim",
2152
+ .kind = TF_OP_TRANSFORM,
2153
+ .tier = TF_TIER_CORE,
2154
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2155
+ .memory_class = TF_MEM_ROW_LOCAL,
2156
+ .emit_class = TF_EMIT_PER_BATCH,
2157
+ .schema_class = TF_SCHEMA_STABLE,
2158
+ .state_estimate = "O(batch_rows * columns)",
2159
+ .args = trim_args,
2160
+ .n_args = 1,
2161
+ .infer_schema = infer_schema_passthrough,
2162
+ .create_native = (void *(*)(const cJSON *))tf_trim_create,
2163
+ },
2164
+ {
2165
+ .name = "fill-null",
2166
+ .kind = TF_OP_TRANSFORM,
2167
+ .tier = TF_TIER_CORE,
2168
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2169
+ .memory_class = TF_MEM_ROW_LOCAL,
2170
+ .emit_class = TF_EMIT_PER_BATCH,
2171
+ .schema_class = TF_SCHEMA_STABLE,
2172
+ .state_estimate = "O(batch_rows * columns)",
2173
+ .args = fill_null_args,
2174
+ .n_args = 9,
2175
+ .infer_schema = infer_schema_passthrough,
2176
+ .create_native = (void *(*)(const cJSON *))tf_fill_null_create,
2177
+ },
2178
+ {
2179
+ .name = "cast",
2180
+ .kind = TF_OP_TRANSFORM,
2181
+ .tier = TF_TIER_CORE,
2182
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2183
+ .memory_class = TF_MEM_ROW_LOCAL,
2184
+ .emit_class = TF_EMIT_PER_BATCH,
2185
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2186
+ .state_estimate = "O(batch_rows * columns)",
2187
+ .args = cast_args,
2188
+ .n_args = 9,
2189
+ .infer_schema = infer_schema_passthrough, /* type changes at runtime */
2190
+ .create_native = (void *(*)(const cJSON *))tf_cast_create,
2191
+ },
2192
+ {
2193
+ .name = "clip",
2194
+ .kind = TF_OP_TRANSFORM,
2195
+ .tier = TF_TIER_CORE,
2196
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2197
+ .memory_class = TF_MEM_ROW_LOCAL,
2198
+ .emit_class = TF_EMIT_PER_BATCH,
2199
+ .schema_class = TF_SCHEMA_STABLE,
2200
+ .state_estimate = "O(batch_rows)",
2201
+ .args = clip_args,
2202
+ .n_args = 3,
2203
+ .infer_schema = infer_schema_passthrough,
2204
+ .create_native = (void *(*)(const cJSON *))tf_clip_create,
2205
+ },
2206
+ {
2207
+ .name = "replace",
2208
+ .kind = TF_OP_TRANSFORM,
2209
+ .tier = TF_TIER_CORE,
2210
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2211
+ .memory_class = TF_MEM_ROW_LOCAL,
2212
+ .emit_class = TF_EMIT_PER_BATCH,
2213
+ .schema_class = TF_SCHEMA_STABLE,
2214
+ .state_estimate = "O(batch_rows)",
2215
+ .args = replace_args,
2216
+ .n_args = 12,
2217
+ .infer_schema = infer_schema_passthrough,
2218
+ .create_native = (void *(*)(const cJSON *))tf_replace_create,
2219
+ },
2220
+ {
2221
+ .name = "hash",
2222
+ .kind = TF_OP_TRANSFORM,
2223
+ .tier = TF_TIER_CORE,
2224
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2225
+ .memory_class = TF_MEM_ROW_LOCAL,
2226
+ .emit_class = TF_EMIT_PER_BATCH,
2227
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2228
+ .state_estimate = "O(batch_rows * selected_columns)",
2229
+ .args = hash_args,
2230
+ .n_args = 1,
2231
+ .infer_schema = infer_schema_add_hash,
2232
+ .create_native = (void *(*)(const cJSON *))tf_hash_create,
2233
+ },
2234
+ {
2235
+ .name = "bin",
2236
+ .kind = TF_OP_TRANSFORM,
2237
+ .tier = TF_TIER_CORE,
2238
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2239
+ .memory_class = TF_MEM_ROW_LOCAL,
2240
+ .emit_class = TF_EMIT_PER_BATCH,
2241
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2242
+ .state_estimate = "O(boundaries)",
2243
+ .args = bin_args,
2244
+ .n_args = 4,
2245
+ .infer_schema = infer_schema_passthrough, /* adds column at runtime */
2246
+ .create_native = (void *(*)(const cJSON *))tf_bin_create,
2247
+ },
2248
+ /* ---- Stateful streaming ---- */
2249
+ {
2250
+ .name = "fill-down",
2251
+ .kind = TF_OP_TRANSFORM,
2252
+ .tier = TF_TIER_CORE,
2253
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2254
+ .memory_class = TF_MEM_BOUNDED_STATE,
2255
+ .emit_class = TF_EMIT_PER_BATCH,
2256
+ .schema_class = TF_SCHEMA_STABLE,
2257
+ .state_estimate = "O(columns)",
2258
+ .args = fill_down_args,
2259
+ .n_args = 1,
2260
+ .infer_schema = infer_schema_passthrough,
2261
+ .create_native = (void *(*)(const cJSON *))tf_fill_down_create,
2262
+ },
2263
+ {
2264
+ .name = "step",
2265
+ .kind = TF_OP_TRANSFORM,
2266
+ .tier = TF_TIER_CORE,
2267
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2268
+ .memory_class = TF_MEM_BOUNDED_STATE,
2269
+ .emit_class = TF_EMIT_PER_BATCH,
2270
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2271
+ .state_estimate = "O(1)",
2272
+ .args = step_args,
2273
+ .n_args = 3,
2274
+ .infer_schema = infer_schema_passthrough, /* adds column at runtime */
2275
+ .create_native = (void *(*)(const cJSON *))tf_step_create,
2276
+ },
2277
+ {
2278
+ .name = "window",
2279
+ .kind = TF_OP_TRANSFORM,
2280
+ .tier = TF_TIER_CORE,
2281
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2282
+ .memory_class = TF_MEM_BOUNDED_STATE,
2283
+ .emit_class = TF_EMIT_PER_BATCH,
2284
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2285
+ .state_estimate = "O(window_size)",
2286
+ .args = window_args,
2287
+ .n_args = 6,
2288
+ .infer_schema = infer_schema_passthrough, /* adds column at runtime */
2289
+ .create_native = (void *(*)(const cJSON *))tf_window_create,
2290
+ },
2291
+ {
2292
+ .name = "rolling-sum",
2293
+ .kind = TF_OP_TRANSFORM,
2294
+ .tier = TF_TIER_CORE,
2295
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2296
+ .memory_class = TF_MEM_BOUNDED_STATE,
2297
+ .emit_class = TF_EMIT_PER_BATCH,
2298
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2299
+ .state_estimate = "O(window_size)",
2300
+ .args = rolling_args,
2301
+ .n_args = 5,
2302
+ .infer_schema = infer_schema_passthrough,
2303
+ .create_native = (void *(*)(const cJSON *))tf_rolling_sum_create,
2304
+ },
2305
+ {
2306
+ .name = "rolling-mean",
2307
+ .kind = TF_OP_TRANSFORM,
2308
+ .tier = TF_TIER_CORE,
2309
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2310
+ .memory_class = TF_MEM_BOUNDED_STATE,
2311
+ .emit_class = TF_EMIT_PER_BATCH,
2312
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2313
+ .state_estimate = "O(window_size)",
2314
+ .args = rolling_args,
2315
+ .n_args = 5,
2316
+ .infer_schema = infer_schema_passthrough,
2317
+ .create_native = (void *(*)(const cJSON *))tf_rolling_mean_create,
2318
+ },
2319
+ {
2320
+ .name = "rolling-min",
2321
+ .kind = TF_OP_TRANSFORM,
2322
+ .tier = TF_TIER_CORE,
2323
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2324
+ .memory_class = TF_MEM_BOUNDED_STATE,
2325
+ .emit_class = TF_EMIT_PER_BATCH,
2326
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2327
+ .state_estimate = "O(window_size)",
2328
+ .args = rolling_args,
2329
+ .n_args = 5,
2330
+ .infer_schema = infer_schema_passthrough,
2331
+ .create_native = (void *(*)(const cJSON *))tf_rolling_min_create,
2332
+ },
2333
+ {
2334
+ .name = "rolling-max",
2335
+ .kind = TF_OP_TRANSFORM,
2336
+ .tier = TF_TIER_CORE,
2337
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2338
+ .memory_class = TF_MEM_BOUNDED_STATE,
2339
+ .emit_class = TF_EMIT_PER_BATCH,
2340
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2341
+ .state_estimate = "O(window_size)",
2342
+ .args = rolling_args,
2343
+ .n_args = 5,
2344
+ .infer_schema = infer_schema_passthrough,
2345
+ .create_native = (void *(*)(const cJSON *))tf_rolling_max_create,
2346
+ },
2347
+ {
2348
+ .name = "rolling-any",
2349
+ .kind = TF_OP_TRANSFORM,
2350
+ .tier = TF_TIER_CORE,
2351
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2352
+ .memory_class = TF_MEM_BOUNDED_STATE,
2353
+ .emit_class = TF_EMIT_PER_BATCH,
2354
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2355
+ .state_estimate = "O(window_size)",
2356
+ .args = rolling_bool_args,
2357
+ .n_args = 4,
2358
+ .infer_schema = infer_schema_passthrough,
2359
+ .create_native = (void *(*)(const cJSON *))tf_rolling_any_create,
2360
+ },
2361
+ {
2362
+ .name = "rolling-all",
2363
+ .kind = TF_OP_TRANSFORM,
2364
+ .tier = TF_TIER_CORE,
2365
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2366
+ .memory_class = TF_MEM_BOUNDED_STATE,
2367
+ .emit_class = TF_EMIT_PER_BATCH,
2368
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2369
+ .state_estimate = "O(window_size)",
2370
+ .args = rolling_bool_args,
2371
+ .n_args = 4,
2372
+ .infer_schema = infer_schema_passthrough,
2373
+ .create_native = (void *(*)(const cJSON *))tf_rolling_all_create,
2374
+ },
2375
+ /* ---- Row-multiplying streaming ---- */
2376
+ {
2377
+ .name = "explode",
2378
+ .kind = TF_OP_TRANSFORM,
2379
+ .tier = TF_TIER_CORE,
2380
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2381
+ .memory_class = TF_MEM_ROW_LOCAL,
2382
+ .emit_class = TF_EMIT_PER_BATCH,
2383
+ .schema_class = TF_SCHEMA_STABLE,
2384
+ .state_estimate = "O(batch_rows)",
2385
+ .args = explode_args,
2386
+ .n_args = 2,
2387
+ .infer_schema = infer_schema_passthrough,
2388
+ .create_native = (void *(*)(const cJSON *))tf_explode_create,
2389
+ },
2390
+ {
2391
+ .name = "split",
2392
+ .kind = TF_OP_TRANSFORM,
2393
+ .tier = TF_TIER_CORE,
2394
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2395
+ .memory_class = TF_MEM_ROW_LOCAL,
2396
+ .emit_class = TF_EMIT_PER_BATCH,
2397
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2398
+ .state_estimate = "O(batch_rows * output_columns)",
2399
+ .args = split_args,
2400
+ .n_args = 3,
2401
+ .infer_schema = infer_schema_passthrough, /* adds columns at runtime */
2402
+ .create_native = (void *(*)(const cJSON *))tf_split_create,
2403
+ },
2404
+ {
2405
+ .name = "unpivot",
2406
+ .kind = TF_OP_TRANSFORM,
2407
+ .tier = TF_TIER_CORE,
2408
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2409
+ .memory_class = TF_MEM_ROW_LOCAL,
2410
+ .emit_class = TF_EMIT_PER_BATCH,
2411
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2412
+ .state_estimate = "O(batch_rows * unpivot_columns)",
2413
+ .args = unpivot_args,
2414
+ .n_args = 1,
2415
+ .infer_schema = infer_schema_passthrough, /* schema changes at runtime */
2416
+ .create_native = (void *(*)(const cJSON *))tf_unpivot_create,
2417
+ },
2418
+ /* ---- Buffering ---- */
2419
+ {
2420
+ .name = "tail",
2421
+ .kind = TF_OP_TRANSFORM,
2422
+ .tier = TF_TIER_CORE,
2423
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2424
+ .memory_class = TF_MEM_BOUNDED_STATE,
2425
+ .emit_class = TF_EMIT_ON_FLUSH,
2426
+ .schema_class = TF_SCHEMA_STABLE,
2427
+ .state_estimate = "O(n)",
2428
+ .args = tail_args,
2429
+ .n_args = 1,
2430
+ .infer_schema = infer_schema_passthrough,
2431
+ .create_native = (void *(*)(const cJSON *))tf_tail_create,
2432
+ },
2433
+ {
2434
+ .name = "slice-tail",
2435
+ .kind = TF_OP_TRANSFORM,
2436
+ .tier = TF_TIER_CORE,
2437
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2438
+ .memory_class = TF_MEM_BOUNDED_STATE,
2439
+ .emit_class = TF_EMIT_ON_FLUSH,
2440
+ .schema_class = TF_SCHEMA_STABLE,
2441
+ .state_estimate = "O(n)",
2442
+ .args = tail_args,
2443
+ .n_args = 1,
2444
+ .infer_schema = infer_schema_passthrough,
2445
+ .create_native = (void *(*)(const cJSON *))tf_tail_create,
2446
+ },
2447
+ {
2448
+ .name = "top",
2449
+ .kind = TF_OP_TRANSFORM,
2450
+ .tier = TF_TIER_CORE,
2451
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2452
+ .memory_class = TF_MEM_BOUNDED_STATE,
2453
+ .emit_class = TF_EMIT_ON_FLUSH,
2454
+ .schema_class = TF_SCHEMA_STABLE,
2455
+ .state_estimate = "O(n)",
2456
+ .args = top_args,
2457
+ .n_args = 3,
2458
+ .infer_schema = infer_schema_passthrough,
2459
+ .create_native = (void *(*)(const cJSON *))tf_top_create,
2460
+ },
2461
+ {
2462
+ .name = "top-k",
2463
+ .kind = TF_OP_TRANSFORM,
2464
+ .tier = TF_TIER_CORE,
2465
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2466
+ .memory_class = TF_MEM_BOUNDED_STATE,
2467
+ .emit_class = TF_EMIT_ON_FLUSH,
2468
+ .schema_class = TF_SCHEMA_STABLE,
2469
+ .state_estimate = "O(n)",
2470
+ .args = top_args,
2471
+ .n_args = 3,
2472
+ .infer_schema = infer_schema_passthrough,
2473
+ .create_native = (void *(*)(const cJSON *))tf_top_k_create,
2474
+ },
2475
+ {
2476
+ .name = "bottom-k",
2477
+ .kind = TF_OP_TRANSFORM,
2478
+ .tier = TF_TIER_CORE,
2479
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2480
+ .memory_class = TF_MEM_BOUNDED_STATE,
2481
+ .emit_class = TF_EMIT_ON_FLUSH,
2482
+ .schema_class = TF_SCHEMA_STABLE,
2483
+ .state_estimate = "O(n)",
2484
+ .args = top_args,
2485
+ .n_args = 3,
2486
+ .infer_schema = infer_schema_passthrough,
2487
+ .create_native = (void *(*)(const cJSON *))tf_bottom_k_create,
2488
+ },
2489
+ {
2490
+ .name = "slice-min",
2491
+ .kind = TF_OP_TRANSFORM,
2492
+ .tier = TF_TIER_CORE,
2493
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2494
+ .memory_class = TF_MEM_BOUNDED_STATE,
2495
+ .emit_class = TF_EMIT_ON_FLUSH,
2496
+ .schema_class = TF_SCHEMA_STABLE,
2497
+ .state_estimate = "O(n)",
2498
+ .args = top_args,
2499
+ .n_args = 4,
2500
+ .infer_schema = infer_schema_passthrough,
2501
+ .create_native = (void *(*)(const cJSON *))tf_slice_min_create,
2502
+ },
2503
+ {
2504
+ .name = "slice-max",
2505
+ .kind = TF_OP_TRANSFORM,
2506
+ .tier = TF_TIER_CORE,
2507
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2508
+ .memory_class = TF_MEM_BOUNDED_STATE,
2509
+ .emit_class = TF_EMIT_ON_FLUSH,
2510
+ .schema_class = TF_SCHEMA_STABLE,
2511
+ .state_estimate = "O(n)",
2512
+ .args = top_args,
2513
+ .n_args = 4,
2514
+ .infer_schema = infer_schema_passthrough,
2515
+ .create_native = (void *(*)(const cJSON *))tf_slice_max_create,
2516
+ },
2517
+ {
2518
+ .name = "sample",
2519
+ .kind = TF_OP_TRANSFORM,
2520
+ .tier = TF_TIER_CORE,
2521
+ .caps = TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE,
2522
+ .memory_class = TF_MEM_BOUNDED_STATE,
2523
+ .emit_class = TF_EMIT_ON_FLUSH,
2524
+ .schema_class = TF_SCHEMA_STABLE,
2525
+ .state_estimate = "O(n)",
2526
+ .args = sample_args,
2527
+ .n_args = 2,
2528
+ .infer_schema = infer_schema_passthrough,
2529
+ .create_native = (void *(*)(const cJSON *))tf_sample_create,
2530
+ },
2531
+ {
2532
+ .name = "group-agg",
2533
+ .kind = TF_OP_TRANSFORM,
2534
+ .tier = TF_TIER_CORE,
2535
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2536
+ .memory_class = TF_MEM_KEY_STATE,
2537
+ .emit_class = TF_EMIT_MIXED,
2538
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2539
+ .state_estimate = "O(1) sorted, O(distinct_groups) unsorted or capped by max_groups/max_state_bytes, or O(spill_run_rows * columns) RAM with spill_dir",
2540
+ .args = group_agg_args,
2541
+ .n_args = 9,
2542
+ .infer_schema = infer_schema_group_agg,
2543
+ .create_native = (void *(*)(const cJSON *))tf_group_agg_create,
2544
+ },
2545
+ {
2546
+ .name = "frequency",
2547
+ .kind = TF_OP_TRANSFORM,
2548
+ .tier = TF_TIER_CORE,
2549
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2550
+ .memory_class = TF_MEM_KEY_STATE,
2551
+ .emit_class = TF_EMIT_ON_FLUSH,
2552
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2553
+ .state_estimate = "O(distinct_values) or capped by max_values/max_state_bytes",
2554
+ .args = frequency_args,
2555
+ .n_args = 13,
2556
+ .infer_schema = infer_schema_frequency,
2557
+ .create_native = (void *(*)(const cJSON *))tf_frequency_create,
2558
+ },
2559
+ /* ---- Complex ---- */
2560
+ {
2561
+ .name = "datetime",
2562
+ .kind = TF_OP_TRANSFORM,
2563
+ .tier = TF_TIER_CORE,
2564
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2565
+ .memory_class = TF_MEM_ROW_LOCAL,
2566
+ .emit_class = TF_EMIT_PER_BATCH,
2567
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2568
+ .state_estimate = "O(batch_rows)",
2569
+ .args = datetime_args,
2570
+ .n_args = 4,
2571
+ .infer_schema = infer_schema_passthrough, /* adds columns at runtime */
2572
+ .create_native = (void *(*)(const cJSON *))tf_datetime_create,
2573
+ },
2574
+ {
2575
+ .name = "json-extract",
2576
+ .kind = TF_OP_TRANSFORM,
2577
+ .tier = TF_TIER_CORE,
2578
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2579
+ .memory_class = TF_MEM_ROW_LOCAL,
2580
+ .emit_class = TF_EMIT_PER_BATCH,
2581
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2582
+ .state_estimate = "O(batch_rows + path_segments)",
2583
+ .args = json_extract_args,
2584
+ .n_args = 4,
2585
+ .infer_schema = infer_schema_json_extract,
2586
+ .create_native = (void *(*)(const cJSON *))tf_json_extract_create,
2587
+ },
2588
+ {
2589
+ .name = "json-filter",
2590
+ .kind = TF_OP_TRANSFORM,
2591
+ .tier = TF_TIER_CORE,
2592
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2593
+ .memory_class = TF_MEM_ROW_LOCAL,
2594
+ .emit_class = TF_EMIT_PER_BATCH,
2595
+ .schema_class = TF_SCHEMA_STABLE,
2596
+ .state_estimate = "O(batch_rows + path_segments)",
2597
+ .args = json_filter_args,
2598
+ .n_args = 5,
2599
+ .infer_schema = infer_schema_passthrough,
2600
+ .create_native = (void *(*)(const cJSON *))tf_json_filter_create,
2601
+ },
2602
+ {
2603
+ .name = "json-schema",
2604
+ .kind = TF_OP_TRANSFORM,
2605
+ .tier = TF_TIER_CORE,
2606
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2607
+ .memory_class = TF_MEM_ROW_LOCAL,
2608
+ .emit_class = TF_EMIT_PER_BATCH,
2609
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2610
+ .state_estimate = "O(batch_rows + schema_nodes)",
2611
+ .args = json_schema_args,
2612
+ .n_args = 12,
2613
+ .infer_schema = infer_schema_json_schema,
2614
+ .create_native = (void *(*)(const cJSON *))tf_json_schema_create,
2615
+ },
2616
+ {
2617
+ .name = "json-flatten",
2618
+ .kind = TF_OP_TRANSFORM,
2619
+ .tier = TF_TIER_CORE,
2620
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2621
+ .memory_class = TF_MEM_ROW_LOCAL,
2622
+ .emit_class = TF_EMIT_PER_BATCH,
2623
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2624
+ .state_estimate = "O(batch_rows * fields + path_segments)",
2625
+ .args = json_flatten_args,
2626
+ .n_args = 2,
2627
+ .infer_schema = infer_schema_json_flatten,
2628
+ .create_native = (void *(*)(const cJSON *))tf_json_flatten_create,
2629
+ },
2630
+ {
2631
+ .name = "flatten",
2632
+ .kind = TF_OP_TRANSFORM,
2633
+ .tier = TF_TIER_CORE,
2634
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2635
+ .memory_class = TF_MEM_ROW_LOCAL,
2636
+ .emit_class = TF_EMIT_PER_BATCH,
2637
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2638
+ .state_estimate = "O(batch_rows * fields)",
2639
+ .args = NULL,
2640
+ .n_args = 0,
2641
+ .infer_schema = infer_schema_passthrough,
2642
+ .create_native = NULL, /* passthrough — no-op at runtime */
2643
+ },
2644
+ /* ---- Pivot / Join ---- */
2645
+ {
2646
+ .name = "pivot",
2647
+ .kind = TF_OP_TRANSFORM,
2648
+ .tier = TF_TIER_CORE,
2649
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2650
+ .memory_class = TF_MEM_BLOCKING,
2651
+ .emit_class = TF_EMIT_ON_FLUSH,
2652
+ .schema_class = TF_SCHEMA_DATA_DEPENDENT,
2653
+ .state_estimate = "O(input_rows + distinct_categories), O(current_group + categories) with categories+sorted=true, or external spill with categories/max_categories",
2654
+ .args = pivot_args,
2655
+ .n_args = 10,
2656
+ .infer_schema = infer_schema_pivot,
2657
+ .create_native = (void *(*)(const cJSON *))tf_pivot_create,
2658
+ },
2659
+ {
2660
+ .name = "join",
2661
+ .kind = TF_OP_TRANSFORM,
2662
+ .tier = TF_TIER_CORE,
2663
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2664
+ .memory_class = TF_MEM_KEY_STATE,
2665
+ .emit_class = TF_EMIT_PER_BATCH,
2666
+ .schema_class = TF_SCHEMA_DATA_DEPENDENT,
2667
+ .state_estimate = "O(current_lookup_run) sorted, O(lookup_rows + lookup_keys + output_batch) unsorted or capped by max_lookup_*/max_state_bytes/max_output_rows, or O(spill_run_rows * columns + max_matches_per_row) RAM with spill_dir",
2668
+ .args = join_args,
2669
+ .n_args = 14,
2670
+ .infer_schema = infer_schema_passthrough, /* schema depends on lookup file */
2671
+ .create_native = (void *(*)(const cJSON *))tf_join_create,
2672
+ },
2673
+ {
2674
+ .name = "semi-join",
2675
+ .kind = TF_OP_TRANSFORM,
2676
+ .tier = TF_TIER_CORE,
2677
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2678
+ .memory_class = TF_MEM_KEY_STATE,
2679
+ .emit_class = TF_EMIT_PER_BATCH,
2680
+ .schema_class = TF_SCHEMA_STABLE,
2681
+ .state_estimate = "O(1) sorted, O(lookup_keys) unsorted or capped by max_lookup_*/max_state_bytes/max_output_rows, or O(spill_run_rows * columns) RAM with spill_dir",
2682
+ .args = join_args,
2683
+ .n_args = 14,
2684
+ .infer_schema = infer_schema_passthrough,
2685
+ .create_native = (void *(*)(const cJSON *))tf_semi_join_create,
2686
+ },
2687
+ {
2688
+ .name = "anti-join",
2689
+ .kind = TF_OP_TRANSFORM,
2690
+ .tier = TF_TIER_CORE,
2691
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2692
+ .memory_class = TF_MEM_KEY_STATE,
2693
+ .emit_class = TF_EMIT_PER_BATCH,
2694
+ .schema_class = TF_SCHEMA_STABLE,
2695
+ .state_estimate = "O(1) sorted, O(lookup_keys) unsorted or capped by max_lookup_*/max_state_bytes/max_output_rows, or O(spill_run_rows * columns) RAM with spill_dir",
2696
+ .args = join_args,
2697
+ .n_args = 14,
2698
+ .infer_schema = infer_schema_passthrough,
2699
+ .create_native = (void *(*)(const cJSON *))tf_anti_join_create,
2700
+ },
2701
+ {
2702
+ .name = "intersect",
2703
+ .kind = TF_OP_TRANSFORM,
2704
+ .tier = TF_TIER_CORE,
2705
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2706
+ .memory_class = TF_MEM_KEY_STATE,
2707
+ .emit_class = TF_EMIT_PER_BATCH,
2708
+ .schema_class = TF_SCHEMA_STABLE,
2709
+ .state_estimate = "O(1) sorted, O(lookup_keys + emitted_keys) unsorted or capped by max_lookup_*/max_output_keys/max_state_bytes",
2710
+ .args = set_file_args,
2711
+ .n_args = 12,
2712
+ .infer_schema = infer_schema_passthrough,
2713
+ .create_native = (void *(*)(const cJSON *))tf_intersect_create,
2714
+ },
2715
+ {
2716
+ .name = "setdiff",
2717
+ .kind = TF_OP_TRANSFORM,
2718
+ .tier = TF_TIER_CORE,
2719
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2720
+ .memory_class = TF_MEM_KEY_STATE,
2721
+ .emit_class = TF_EMIT_PER_BATCH,
2722
+ .schema_class = TF_SCHEMA_STABLE,
2723
+ .state_estimate = "O(1) sorted, O(lookup_keys + emitted_keys) unsorted or capped by max_lookup_*/max_output_keys/max_state_bytes",
2724
+ .args = set_file_args,
2725
+ .n_args = 12,
2726
+ .infer_schema = infer_schema_passthrough,
2727
+ .create_native = (void *(*)(const cJSON *))tf_setdiff_create,
2728
+ },
2729
+ {
2730
+ .name = "intersect-all",
2731
+ .kind = TF_OP_TRANSFORM,
2732
+ .tier = TF_TIER_CORE,
2733
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2734
+ .memory_class = TF_MEM_KEY_STATE,
2735
+ .emit_class = TF_EMIT_PER_BATCH,
2736
+ .schema_class = TF_SCHEMA_STABLE,
2737
+ .state_estimate = "O(lookup_keys) unsorted with max_lookup_keys/max_state_bytes; O(spill_run_rows * columns) RAM with spill_dir; O(1) current-run state with sorted=true; emits min(left_count, lookup_count) rows per key",
2738
+ .args = set_file_args,
2739
+ .n_args = 12,
2740
+ .infer_schema = infer_schema_passthrough,
2741
+ .create_native = (void *(*)(const cJSON *))tf_intersect_all_create,
2742
+ },
2743
+ {
2744
+ .name = "setdiff-all",
2745
+ .kind = TF_OP_TRANSFORM,
2746
+ .tier = TF_TIER_CORE,
2747
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2748
+ .memory_class = TF_MEM_KEY_STATE,
2749
+ .emit_class = TF_EMIT_PER_BATCH,
2750
+ .schema_class = TF_SCHEMA_STABLE,
2751
+ .state_estimate = "O(lookup_keys) unsorted with max_lookup_keys/max_state_bytes; O(spill_run_rows * columns) RAM with spill_dir; O(1) current-run state with sorted=true; emits max(left_count - lookup_count, 0) rows per key",
2752
+ .args = set_file_args,
2753
+ .n_args = 12,
2754
+ .infer_schema = infer_schema_passthrough,
2755
+ .create_native = (void *(*)(const cJSON *))tf_setdiff_all_create,
2756
+ },
2757
+ {
2758
+ .name = "union",
2759
+ .kind = TF_OP_TRANSFORM,
2760
+ .tier = TF_TIER_CORE,
2761
+ .caps = TF_CAP_STREAMING | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2762
+ .memory_class = TF_MEM_KEY_STATE,
2763
+ .emit_class = TF_EMIT_MIXED,
2764
+ .schema_class = TF_SCHEMA_STABLE,
2765
+ .state_estimate = "O(emitted_keys), capped by max_output_keys/max_state_bytes; O(previous_left_key + current_file_key) with sorted=true; O(spill_run_rows * columns) RAM with spill_dir",
2766
+ .args = union_args,
2767
+ .n_args = 11,
2768
+ .infer_schema = infer_schema_passthrough,
2769
+ .create_native = (void *(*)(const cJSON *))tf_union_create,
2770
+ },
2771
+ {
2772
+ .name = "union-all",
2773
+ .kind = TF_OP_TRANSFORM,
2774
+ .tier = TF_TIER_CORE,
2775
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2776
+ .memory_class = TF_MEM_BOUNDED_STATE,
2777
+ .emit_class = TF_EMIT_MIXED,
2778
+ .schema_class = TF_SCHEMA_STABLE,
2779
+ .state_estimate = "O(batch_rows)",
2780
+ .args = union_all_args,
2781
+ .n_args = 3,
2782
+ .infer_schema = infer_schema_passthrough,
2783
+ .create_native = (void *(*)(const cJSON *))tf_union_all_create,
2784
+ },
2785
+ {
2786
+ .name = "stack",
2787
+ .kind = TF_OP_TRANSFORM,
2788
+ .tier = TF_TIER_CORE,
2789
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_FS | TF_CAP_DETERMINISTIC,
2790
+ .memory_class = TF_MEM_BOUNDED_STATE,
2791
+ .emit_class = TF_EMIT_MIXED,
2792
+ .schema_class = TF_SCHEMA_STABLE,
2793
+ .state_estimate = "O(batch_rows)",
2794
+ .args = stack_args,
2795
+ .n_args = 3,
2796
+ .infer_schema = infer_schema_passthrough,
2797
+ .create_native = (void *(*)(const cJSON *))tf_stack_create,
2798
+ },
2799
+ {
2800
+ .name = "lead",
2801
+ .kind = TF_OP_TRANSFORM,
2802
+ .tier = TF_TIER_CORE,
2803
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2804
+ .memory_class = TF_MEM_BOUNDED_STATE,
2805
+ .emit_class = TF_EMIT_PER_BATCH,
2806
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2807
+ .state_estimate = "O(offset)",
2808
+ .args = lead_args,
2809
+ .n_args = 3,
2810
+ .infer_schema = infer_schema_lead,
2811
+ .create_native = (void *(*)(const cJSON *))tf_lead_create,
2812
+ },
2813
+ {
2814
+ .name = "lag",
2815
+ .kind = TF_OP_TRANSFORM,
2816
+ .tier = TF_TIER_CORE,
2817
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2818
+ .memory_class = TF_MEM_BOUNDED_STATE,
2819
+ .emit_class = TF_EMIT_PER_BATCH,
2820
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2821
+ .state_estimate = "O(offset * row_width)",
2822
+ .args = lag_args,
2823
+ .n_args = 3,
2824
+ .infer_schema = infer_schema_lag,
2825
+ .create_native = (void *(*)(const cJSON *))tf_lag_create,
2826
+ },
2827
+ {
2828
+ .name = "shift",
2829
+ .kind = TF_OP_TRANSFORM,
2830
+ .tier = TF_TIER_CORE,
2831
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2832
+ .memory_class = TF_MEM_BOUNDED_STATE,
2833
+ .emit_class = TF_EMIT_PER_BATCH,
2834
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2835
+ .state_estimate = "O(offset * row_width)",
2836
+ .args = shift_args,
2837
+ .n_args = 4,
2838
+ .infer_schema = infer_schema_shift,
2839
+ .create_native = (void *(*)(const cJSON *))tf_shift_create,
2840
+ },
2841
+ {
2842
+ .name = "rowid",
2843
+ .kind = TF_OP_TRANSFORM,
2844
+ .tier = TF_TIER_CORE,
2845
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2846
+ .memory_class = TF_MEM_KEY_STATE,
2847
+ .emit_class = TF_EMIT_PER_BATCH,
2848
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2849
+ .state_estimate = "O(1) global/sorted, O(distinct_keys) unsorted or capped by max_keys/max_state_bytes",
2850
+ .args = rowid_args,
2851
+ .n_args = 5,
2852
+ .infer_schema = infer_schema_rowid,
2853
+ .create_native = (void *(*)(const cJSON *))tf_rowid_create,
2854
+ },
2855
+ {
2856
+ .name = "rleid",
2857
+ .kind = TF_OP_TRANSFORM,
2858
+ .tier = TF_TIER_CORE,
2859
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2860
+ .memory_class = TF_MEM_BOUNDED_STATE,
2861
+ .emit_class = TF_EMIT_PER_BATCH,
2862
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2863
+ .state_estimate = "O(key_width)",
2864
+ .args = rleid_args,
2865
+ .n_args = 2,
2866
+ .infer_schema = infer_schema_rleid,
2867
+ .create_native = (void *(*)(const cJSON *))tf_rleid_create,
2868
+ },
2869
+ {
2870
+ .name = "date-trunc",
2871
+ .kind = TF_OP_TRANSFORM,
2872
+ .tier = TF_TIER_CORE,
2873
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2874
+ .memory_class = TF_MEM_ROW_LOCAL,
2875
+ .emit_class = TF_EMIT_PER_BATCH,
2876
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2877
+ .state_estimate = "O(batch_rows)",
2878
+ .args = date_trunc_args,
2879
+ .n_args = 5,
2880
+ .infer_schema = infer_schema_passthrough,
2881
+ .create_native = (void *(*)(const cJSON *))tf_date_trunc_create,
2882
+ },
2883
+ {
2884
+ .name = "onehot",
2885
+ .kind = TF_OP_TRANSFORM,
2886
+ .tier = TF_TIER_CORE,
2887
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2888
+ .memory_class = TF_MEM_KEY_STATE,
2889
+ .emit_class = TF_EMIT_PER_BATCH,
2890
+ .schema_class = TF_SCHEMA_DATA_DEPENDENT,
2891
+ .state_estimate = "O(distinct_categories) or capped by max_categories/max_state_bytes",
2892
+ .args = onehot_args,
2893
+ .n_args = 6,
2894
+ .infer_schema = infer_schema_passthrough,
2895
+ .create_native = (void *(*)(const cJSON *))tf_onehot_create,
2896
+ },
2897
+ {
2898
+ .name = "label-encode",
2899
+ .kind = TF_OP_TRANSFORM,
2900
+ .tier = TF_TIER_CORE,
2901
+ .caps = TF_CAP_STREAMING | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2902
+ .memory_class = TF_MEM_KEY_STATE,
2903
+ .emit_class = TF_EMIT_PER_BATCH,
2904
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2905
+ .state_estimate = "O(distinct_categories) or capped by max_categories/max_state_bytes",
2906
+ .args = label_encode_args,
2907
+ .n_args = 6,
2908
+ .infer_schema = infer_schema_passthrough,
2909
+ .create_native = (void *(*)(const cJSON *))tf_label_encode_create,
2910
+ },
2911
+ {
2912
+ .name = "ewma",
2913
+ .kind = TF_OP_TRANSFORM,
2914
+ .tier = TF_TIER_CORE,
2915
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2916
+ .memory_class = TF_MEM_BOUNDED_STATE,
2917
+ .emit_class = TF_EMIT_PER_BATCH,
2918
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2919
+ .state_estimate = "O(1)",
2920
+ .args = ewma_args,
2921
+ .n_args = 5,
2922
+ .infer_schema = infer_schema_passthrough,
2923
+ .create_native = (void *(*)(const cJSON *))tf_ewma_create,
2924
+ },
2925
+ {
2926
+ .name = "diff",
2927
+ .kind = TF_OP_TRANSFORM,
2928
+ .tier = TF_TIER_CORE,
2929
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2930
+ .memory_class = TF_MEM_BOUNDED_STATE,
2931
+ .emit_class = TF_EMIT_PER_BATCH,
2932
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2933
+ .state_estimate = "O(order)",
2934
+ .args = diff_args,
2935
+ .n_args = 3,
2936
+ .infer_schema = infer_schema_passthrough,
2937
+ .create_native = (void *(*)(const cJSON *))tf_diff_create,
2938
+ },
2939
+ {
2940
+ .name = "anomaly",
2941
+ .kind = TF_OP_TRANSFORM,
2942
+ .tier = TF_TIER_CORE,
2943
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2944
+ .memory_class = TF_MEM_BOUNDED_STATE,
2945
+ .emit_class = TF_EMIT_PER_BATCH,
2946
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2947
+ .state_estimate = "O(1)",
2948
+ .args = anomaly_args,
2949
+ .n_args = 5,
2950
+ .infer_schema = infer_schema_passthrough,
2951
+ .create_native = (void *(*)(const cJSON *))tf_anomaly_create,
2952
+ },
2953
+ {
2954
+ .name = "split-data",
2955
+ .kind = TF_OP_TRANSFORM,
2956
+ .tier = TF_TIER_CORE,
2957
+ .caps = TF_CAP_STREAMING | TF_CAP_BOUNDED_MEMORY | TF_CAP_BROWSER_SAFE,
2958
+ .memory_class = TF_MEM_BOUNDED_STATE,
2959
+ .emit_class = TF_EMIT_PER_BATCH,
2960
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2961
+ .state_estimate = "O(1)",
2962
+ .args = split_data_args,
2963
+ .n_args = 3,
2964
+ .infer_schema = infer_schema_passthrough,
2965
+ .create_native = (void *(*)(const cJSON *))tf_split_data_create,
2966
+ },
2967
+ {
2968
+ .name = "interpolate",
2969
+ .kind = TF_OP_TRANSFORM,
2970
+ .tier = TF_TIER_CORE,
2971
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2972
+ .memory_class = TF_MEM_BLOCKING,
2973
+ .emit_class = TF_EMIT_ON_FLUSH,
2974
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2975
+ .state_estimate = "O(input_rows)",
2976
+ .args = interpolate_args,
2977
+ .n_args = 4,
2978
+ .infer_schema = infer_schema_passthrough,
2979
+ .create_native = (void *(*)(const cJSON *))tf_interpolate_create,
2980
+ },
2981
+ {
2982
+ .name = "normalize",
2983
+ .kind = TF_OP_TRANSFORM,
2984
+ .tier = TF_TIER_CORE,
2985
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
2986
+ .memory_class = TF_MEM_BLOCKING,
2987
+ .emit_class = TF_EMIT_ON_FLUSH,
2988
+ .schema_class = TF_SCHEMA_PARAMETRIC,
2989
+ .state_estimate = "O(input_rows)",
2990
+ .args = normalize_args,
2991
+ .n_args = 12,
2992
+ .infer_schema = infer_schema_passthrough,
2993
+ .create_native = (void *(*)(const cJSON *))tf_normalize_create,
2994
+ },
2995
+ {
2996
+ .name = "acf",
2997
+ .kind = TF_OP_TRANSFORM,
2998
+ .tier = TF_TIER_CORE,
2999
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
3000
+ .memory_class = TF_MEM_BLOCKING,
3001
+ .emit_class = TF_EMIT_ON_FLUSH,
3002
+ .schema_class = TF_SCHEMA_PARAMETRIC,
3003
+ .state_estimate = "O(input_rows)",
3004
+ .args = acf_args,
3005
+ .n_args = 4,
3006
+ .infer_schema = infer_schema_passthrough,
3007
+ .create_native = (void *(*)(const cJSON *))tf_acf_create,
3008
+ },
3009
+ {
3010
+ .name = "codec.table.encode",
3011
+ .kind = TF_OP_ENCODER,
3012
+ .tier = TF_TIER_CORE,
3013
+ .caps = TF_CAP_BROWSER_SAFE | TF_CAP_DETERMINISTIC,
3014
+ .memory_class = TF_MEM_BLOCKING,
3015
+ .emit_class = TF_EMIT_ON_FLUSH,
3016
+ .schema_class = TF_SCHEMA_STABLE,
3017
+ .state_estimate = "O(input_rows)",
3018
+ .args = table_encode_args,
3019
+ .n_args = 2,
3020
+ .infer_schema = infer_schema_sink,
3021
+ .create_native = (void *(*)(const cJSON *))tf_table_encoder_create,
3022
+ },
3023
+ };
3024
+
3025
+ static const size_t n_builtin_ops = sizeof(builtin_ops) / sizeof(builtin_ops[0]);
3026
+
3027
+ /* ---- Public API ---- */
3028
+
3029
+ const tf_op_entry *tf_op_registry_find(const char *name) {
3030
+ for (size_t i = 0; i < n_builtin_ops; i++) {
3031
+ if (strcmp(builtin_ops[i].name, name) == 0)
3032
+ return &builtin_ops[i];
3033
+ }
3034
+ return NULL;
3035
+ }
3036
+
3037
+ size_t tf_op_registry_count(void) {
3038
+ return n_builtin_ops;
3039
+ }
3040
+
3041
+ const tf_op_entry *tf_op_registry_get(size_t index) {
3042
+ if (index >= n_builtin_ops) return NULL;
3043
+ return &builtin_ops[index];
3044
+ }