tranfi 0.0.1 → 0.1.2

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 (109) hide show
  1. package/README.md +395 -0
  2. package/app/assets/index-6quYZ5Ap.css +5 -0
  3. package/app/assets/index-pDFMluyz.js +160 -0
  4. package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  5. package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  6. package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  7. package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  8. package/app/index.html +13 -0
  9. package/binding.gyp +69 -0
  10. package/csrc/arena.c +91 -0
  11. package/csrc/batch.c +229 -0
  12. package/csrc/buffer.c +78 -0
  13. package/csrc/cJSON.c +3143 -0
  14. package/csrc/cJSON.h +300 -0
  15. package/csrc/codec_csv.c +1058 -0
  16. package/csrc/codec_jsonl.c +374 -0
  17. package/csrc/codec_table.c +218 -0
  18. package/csrc/codec_text.c +229 -0
  19. package/csrc/compiler.c +102 -0
  20. package/csrc/date_utils.h +94 -0
  21. package/csrc/dsl.c +1180 -0
  22. package/csrc/dsl.h +22 -0
  23. package/csrc/expr.c +1245 -0
  24. package/csrc/expr.h +56 -0
  25. package/csrc/internal.h +250 -0
  26. package/csrc/ir.c +119 -0
  27. package/csrc/ir.h +167 -0
  28. package/csrc/ir_schema.c +60 -0
  29. package/csrc/ir_serialize.c +104 -0
  30. package/csrc/ir_sql.c +1211 -0
  31. package/csrc/ir_validate.c +120 -0
  32. package/csrc/main.c +392 -0
  33. package/csrc/op_acf.c +133 -0
  34. package/csrc/op_anomaly.c +120 -0
  35. package/csrc/op_bin.c +109 -0
  36. package/csrc/op_cast.c +195 -0
  37. package/csrc/op_clip.c +88 -0
  38. package/csrc/op_date_trunc.c +181 -0
  39. package/csrc/op_datetime.c +212 -0
  40. package/csrc/op_derive.c +248 -0
  41. package/csrc/op_diff.c +134 -0
  42. package/csrc/op_ewma.c +103 -0
  43. package/csrc/op_explode.c +108 -0
  44. package/csrc/op_fill_down.c +163 -0
  45. package/csrc/op_fill_null.c +123 -0
  46. package/csrc/op_filter.c +132 -0
  47. package/csrc/op_frequency.c +193 -0
  48. package/csrc/op_grep.c +163 -0
  49. package/csrc/op_group_agg.c +285 -0
  50. package/csrc/op_hash.c +126 -0
  51. package/csrc/op_head.c +149 -0
  52. package/csrc/op_interpolate.c +239 -0
  53. package/csrc/op_join.c +384 -0
  54. package/csrc/op_label_encode.c +144 -0
  55. package/csrc/op_lead.c +190 -0
  56. package/csrc/op_normalize.c +226 -0
  57. package/csrc/op_onehot.c +185 -0
  58. package/csrc/op_pivot.c +370 -0
  59. package/csrc/op_registry.c +1148 -0
  60. package/csrc/op_rename.c +138 -0
  61. package/csrc/op_replace.c +202 -0
  62. package/csrc/op_sample.c +101 -0
  63. package/csrc/op_select.c +140 -0
  64. package/csrc/op_skip.c +152 -0
  65. package/csrc/op_sort.c +273 -0
  66. package/csrc/op_split.c +114 -0
  67. package/csrc/op_split_data.c +87 -0
  68. package/csrc/op_stack.c +315 -0
  69. package/csrc/op_stats.c +779 -0
  70. package/csrc/op_step.c +171 -0
  71. package/csrc/op_tail.c +96 -0
  72. package/csrc/op_top.c +150 -0
  73. package/csrc/op_trim.c +109 -0
  74. package/csrc/op_unique.c +300 -0
  75. package/csrc/op_unpivot.c +159 -0
  76. package/csrc/op_validate.c +71 -0
  77. package/csrc/op_window.c +150 -0
  78. package/csrc/pipeline.c +315 -0
  79. package/csrc/plan.c +206 -0
  80. package/csrc/recipes.c +102 -0
  81. package/csrc/recipes.h +27 -0
  82. package/csrc/report.c +463 -0
  83. package/csrc/report.h +22 -0
  84. package/csrc/tranfi.h +123 -0
  85. package/csrc/wasm_api.c +157 -0
  86. package/napi_api.c +326 -0
  87. package/package.json +46 -57
  88. package/src/cli.js +193 -0
  89. package/src/engines/duckdb.js +109 -0
  90. package/src/index.js +306 -0
  91. package/src/native.js +22 -0
  92. package/src/pipeline.js +286 -0
  93. package/src/server.js +277 -0
  94. package/src/wasm.js +19 -0
  95. package/wasm/index.js +244 -0
  96. package/wasm/package.json +1 -0
  97. package/wasm/tranfi_core.js +0 -0
  98. package/LICENSE +0 -21
  99. package/dist/bundle.js +0 -1
  100. package/index.html +0 -18
  101. package/logo.png +0 -0
  102. package/src/app.css +0 -160
  103. package/src/app.js +0 -203
  104. package/src/app.vue +0 -253
  105. package/src/bulma-input.vue +0 -110
  106. package/src/common-inputs.js +0 -28
  107. package/src/main.js +0 -18
  108. package/src/transforms.js +0 -166
  109. package/webpack.config.js +0 -108
package/csrc/op_join.c ADDED
@@ -0,0 +1,384 @@
1
+ /*
2
+ * op_join.c — Hash join with lookup CSV file.
3
+ *
4
+ * Config: {"file": "lookup.csv", "on": "id" or "left=right", "how": "inner|left"}
5
+ * Loads lookup file into memory once, then streams main data through with
6
+ * hash map probes. Supports inner and left join.
7
+ */
8
+
9
+ #include "internal.h"
10
+ #include "date_utils.h"
11
+ #include "cJSON.h"
12
+ #include <stdlib.h>
13
+ #include <string.h>
14
+ #include <stdio.h>
15
+
16
+ /* Hash map bucket: key → array of row indices in lookup batch */
17
+ typedef struct {
18
+ char *key;
19
+ size_t *rows;
20
+ size_t n_rows;
21
+ size_t rows_cap;
22
+ } join_bucket;
23
+
24
+ typedef struct {
25
+ join_bucket *buckets;
26
+ size_t n_buckets; /* power of 2 */
27
+ size_t count;
28
+ } join_hash_map;
29
+
30
+ typedef struct {
31
+ char *file;
32
+ char *left_col;
33
+ char *right_col;
34
+ int how; /* 0=inner, 1=left */
35
+
36
+ tf_batch *lookup;
37
+ int loaded;
38
+ int lookup_join_col;
39
+
40
+ /* Lookup columns to include in output (excluding join key) */
41
+ int *lookup_out_cols;
42
+ size_t n_lookup_out;
43
+
44
+ join_hash_map map;
45
+ } join_state;
46
+
47
+ /* FNV-1a hash */
48
+ static uint64_t fnv1a(const char *s) {
49
+ uint64_t h = 14695981039346656037ULL;
50
+ while (*s) { h ^= (uint8_t)*s++; h *= 1099511628211ULL; }
51
+ return h;
52
+ }
53
+
54
+ static void map_init(join_hash_map *m, size_t hint) {
55
+ size_t n = 64;
56
+ while (n < hint * 2) n *= 2;
57
+ m->buckets = calloc(n, sizeof(join_bucket));
58
+ m->n_buckets = n;
59
+ m->count = 0;
60
+ }
61
+
62
+ static void map_insert(join_hash_map *m, const char *key, size_t row) {
63
+ uint64_t h = fnv1a(key);
64
+ size_t idx = h & (m->n_buckets - 1);
65
+ /* Linear probe */
66
+ while (m->buckets[idx].key) {
67
+ if (strcmp(m->buckets[idx].key, key) == 0) {
68
+ /* Add row to existing bucket */
69
+ join_bucket *b = &m->buckets[idx];
70
+ if (b->n_rows >= b->rows_cap) {
71
+ b->rows_cap = b->rows_cap ? b->rows_cap * 2 : 4;
72
+ b->rows = realloc(b->rows, b->rows_cap * sizeof(size_t));
73
+ }
74
+ b->rows[b->n_rows++] = row;
75
+ return;
76
+ }
77
+ idx = (idx + 1) & (m->n_buckets - 1);
78
+ }
79
+ /* New key */
80
+ join_bucket *b = &m->buckets[idx];
81
+ b->key = strdup(key);
82
+ b->rows = malloc(4 * sizeof(size_t));
83
+ b->rows_cap = 4;
84
+ b->rows[0] = row;
85
+ b->n_rows = 1;
86
+ m->count++;
87
+ }
88
+
89
+ static join_bucket *map_find(join_hash_map *m, const char *key) {
90
+ if (!m->buckets) return NULL;
91
+ uint64_t h = fnv1a(key);
92
+ size_t idx = h & (m->n_buckets - 1);
93
+ while (m->buckets[idx].key) {
94
+ if (strcmp(m->buckets[idx].key, key) == 0)
95
+ return &m->buckets[idx];
96
+ idx = (idx + 1) & (m->n_buckets - 1);
97
+ }
98
+ return NULL;
99
+ }
100
+
101
+ static void map_free(join_hash_map *m) {
102
+ if (!m->buckets) return;
103
+ for (size_t i = 0; i < m->n_buckets; i++) {
104
+ if (m->buckets[i].key) {
105
+ free(m->buckets[i].key);
106
+ free(m->buckets[i].rows);
107
+ }
108
+ }
109
+ free(m->buckets);
110
+ m->buckets = NULL;
111
+ }
112
+
113
+ /* Format a cell as string for join key comparison */
114
+ static char *format_join_key(const tf_batch *b, size_t row, int col) {
115
+ if (tf_batch_is_null(b, row, col)) return strdup("\\N");
116
+ char buf[64];
117
+ switch (b->col_types[col]) {
118
+ case TF_TYPE_STRING: return strdup(tf_batch_get_string(b, row, col));
119
+ case TF_TYPE_INT64:
120
+ snprintf(buf, sizeof(buf), "%lld", (long long)tf_batch_get_int64(b, row, col));
121
+ return strdup(buf);
122
+ case TF_TYPE_FLOAT64:
123
+ snprintf(buf, sizeof(buf), "%.17g", tf_batch_get_float64(b, row, col));
124
+ return strdup(buf);
125
+ case TF_TYPE_BOOL:
126
+ return strdup(tf_batch_get_bool(b, row, col) ? "true" : "false");
127
+ case TF_TYPE_DATE:
128
+ snprintf(buf, sizeof(buf), "%d", (int)tf_batch_get_date(b, row, col));
129
+ return strdup(buf);
130
+ case TF_TYPE_TIMESTAMP:
131
+ snprintf(buf, sizeof(buf), "%lld", (long long)tf_batch_get_timestamp(b, row, col));
132
+ return strdup(buf);
133
+ default:
134
+ return strdup("\\N");
135
+ }
136
+ }
137
+
138
+ /* Copy a cell from src batch to dst batch (different column index) */
139
+ static void copy_cell(tf_batch *dst, size_t dr, size_t dc,
140
+ const tf_batch *src, size_t sr, int sc) {
141
+ if (tf_batch_is_null(src, sr, sc)) {
142
+ tf_batch_set_null(dst, dr, dc);
143
+ return;
144
+ }
145
+ switch (src->col_types[sc]) {
146
+ case TF_TYPE_BOOL: tf_batch_set_bool(dst, dr, dc, tf_batch_get_bool(src, sr, sc)); break;
147
+ case TF_TYPE_INT64: tf_batch_set_int64(dst, dr, dc, tf_batch_get_int64(src, sr, sc)); break;
148
+ case TF_TYPE_FLOAT64: tf_batch_set_float64(dst, dr, dc, tf_batch_get_float64(src, sr, sc)); break;
149
+ case TF_TYPE_STRING: tf_batch_set_string(dst, dr, dc, tf_batch_get_string(src, sr, sc)); break;
150
+ case TF_TYPE_DATE: tf_batch_set_date(dst, dr, dc, tf_batch_get_date(src, sr, sc)); break;
151
+ case TF_TYPE_TIMESTAMP: tf_batch_set_timestamp(dst, dr, dc, tf_batch_get_timestamp(src, sr, sc)); break;
152
+ default: tf_batch_set_null(dst, dr, dc); break;
153
+ }
154
+ }
155
+
156
+ static int load_lookup(join_state *st) {
157
+ FILE *f = fopen(st->file, "rb");
158
+ if (!f) return TF_ERROR;
159
+
160
+ /* Read entire file */
161
+ fseek(f, 0, SEEK_END);
162
+ long fsize = ftell(f);
163
+ fseek(f, 0, SEEK_SET);
164
+ if (fsize <= 0) { fclose(f); return TF_ERROR; }
165
+
166
+ uint8_t *data = malloc((size_t)fsize);
167
+ if (!data) { fclose(f); return TF_ERROR; }
168
+ size_t nread = fread(data, 1, (size_t)fsize, f);
169
+ fclose(f);
170
+ if (nread != (size_t)fsize) { free(data); return TF_ERROR; }
171
+
172
+ /* Decode using CSV decoder */
173
+ tf_decoder *dec = tf_csv_decoder_create(NULL);
174
+ if (!dec) { free(data); return TF_ERROR; }
175
+
176
+ tf_batch **batches = NULL;
177
+ size_t n_batches = 0;
178
+ if (dec->decode(dec, data, nread, &batches, &n_batches) != TF_OK) {
179
+ dec->destroy(dec);
180
+ free(data);
181
+ return TF_ERROR;
182
+ }
183
+
184
+ tf_batch **flush_batches = NULL;
185
+ size_t n_flush = 0;
186
+ dec->flush(dec, &flush_batches, &n_flush);
187
+
188
+ /* Count total rows and merge into single batch */
189
+ size_t total_rows = 0;
190
+ size_t total_batches = n_batches + n_flush;
191
+ tf_batch **all_batches = malloc(total_batches * sizeof(tf_batch *));
192
+ if (!all_batches) { dec->destroy(dec); free(data); return TF_ERROR; }
193
+ for (size_t i = 0; i < n_batches; i++) {
194
+ all_batches[i] = batches[i];
195
+ total_rows += batches[i]->n_rows;
196
+ }
197
+ for (size_t i = 0; i < n_flush; i++) {
198
+ all_batches[n_batches + i] = flush_batches[i];
199
+ total_rows += flush_batches[i]->n_rows;
200
+ }
201
+
202
+ if (total_batches == 0 || total_rows == 0) {
203
+ free(all_batches);
204
+ free(batches); free(flush_batches);
205
+ dec->destroy(dec); free(data);
206
+ return TF_ERROR;
207
+ }
208
+
209
+ /* Use first batch as schema source */
210
+ tf_batch *first = all_batches[0];
211
+ tf_batch *merged = tf_batch_create(first->n_cols, total_rows);
212
+ if (!merged) {
213
+ for (size_t i = 0; i < total_batches; i++) tf_batch_free(all_batches[i]);
214
+ free(all_batches); free(batches); free(flush_batches);
215
+ dec->destroy(dec); free(data);
216
+ return TF_ERROR;
217
+ }
218
+ for (size_t c = 0; c < first->n_cols; c++)
219
+ tf_batch_set_schema(merged, c, first->col_names[c], first->col_types[c]);
220
+
221
+ size_t dst_row = 0;
222
+ for (size_t b = 0; b < total_batches; b++) {
223
+ for (size_t r = 0; r < all_batches[b]->n_rows; r++) {
224
+ tf_batch_copy_row(merged, dst_row, all_batches[b], r);
225
+ merged->n_rows = ++dst_row;
226
+ }
227
+ }
228
+
229
+ for (size_t i = 0; i < total_batches; i++) tf_batch_free(all_batches[i]);
230
+ free(all_batches);
231
+ free(batches);
232
+ free(flush_batches);
233
+ dec->destroy(dec);
234
+ free(data);
235
+
236
+ st->lookup = merged;
237
+
238
+ /* Find join column in lookup */
239
+ st->lookup_join_col = tf_batch_col_index(merged, st->right_col);
240
+ if (st->lookup_join_col < 0) return TF_ERROR;
241
+
242
+ /* Determine output columns (all except join key) */
243
+ st->lookup_out_cols = malloc(merged->n_cols * sizeof(int));
244
+ st->n_lookup_out = 0;
245
+ for (size_t c = 0; c < merged->n_cols; c++) {
246
+ if ((int)c != st->lookup_join_col)
247
+ st->lookup_out_cols[st->n_lookup_out++] = (int)c;
248
+ }
249
+
250
+ /* Build hash map */
251
+ map_init(&st->map, merged->n_rows);
252
+ for (size_t r = 0; r < merged->n_rows; r++) {
253
+ char *key = format_join_key(merged, r, st->lookup_join_col);
254
+ if (key) { map_insert(&st->map, key, r); free(key); }
255
+ }
256
+
257
+ return TF_OK;
258
+ }
259
+
260
+ static int join_process(tf_step *self, tf_batch *in, tf_batch **out,
261
+ tf_side_channels *side) {
262
+ (void)side;
263
+ join_state *st = self->state;
264
+ *out = NULL;
265
+
266
+ if (!st->loaded) {
267
+ if (load_lookup(st) != TF_OK) return TF_ERROR;
268
+ st->loaded = 1;
269
+ }
270
+
271
+ int left_ci = tf_batch_col_index(in, st->left_col);
272
+ if (left_ci < 0) return TF_ERROR;
273
+
274
+ size_t n_out_cols = in->n_cols + st->n_lookup_out;
275
+
276
+ /* Worst case: each input row matches multiple lookup rows.
277
+ * Start with input size, grow as needed. */
278
+ size_t out_cap = in->n_rows > 0 ? in->n_rows : 16;
279
+ tf_batch *ob = tf_batch_create(n_out_cols, out_cap);
280
+ if (!ob) return TF_ERROR;
281
+
282
+ /* Set schema: main columns + lookup columns */
283
+ for (size_t c = 0; c < in->n_cols; c++)
284
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
285
+ for (size_t k = 0; k < st->n_lookup_out; k++) {
286
+ int lc = st->lookup_out_cols[k];
287
+ tf_batch_set_schema(ob, in->n_cols + k,
288
+ st->lookup->col_names[lc],
289
+ st->lookup->col_types[lc]);
290
+ }
291
+
292
+ size_t out_row = 0;
293
+ for (size_t r = 0; r < in->n_rows; r++) {
294
+ char *key = format_join_key(in, r, left_ci);
295
+ if (!key) continue;
296
+
297
+ join_bucket *bucket = map_find(&st->map, key);
298
+ free(key);
299
+
300
+ if (bucket) {
301
+ /* Emit one row per match */
302
+ for (size_t m = 0; m < bucket->n_rows; m++) {
303
+ size_t lr = bucket->rows[m];
304
+ tf_batch_ensure_capacity(ob, out_row + 1);
305
+ /* Copy main columns */
306
+ for (size_t c = 0; c < in->n_cols; c++)
307
+ copy_cell(ob, out_row, c, in, r, (int)c);
308
+ /* Copy lookup columns */
309
+ for (size_t k = 0; k < st->n_lookup_out; k++)
310
+ copy_cell(ob, out_row, in->n_cols + k, st->lookup, lr, st->lookup_out_cols[k]);
311
+ ob->n_rows = ++out_row;
312
+ }
313
+ } else if (st->how == 1) {
314
+ /* Left join: emit main + nulls */
315
+ tf_batch_ensure_capacity(ob, out_row + 1);
316
+ for (size_t c = 0; c < in->n_cols; c++)
317
+ copy_cell(ob, out_row, c, in, r, (int)c);
318
+ for (size_t k = 0; k < st->n_lookup_out; k++)
319
+ tf_batch_set_null(ob, out_row, in->n_cols + k);
320
+ ob->n_rows = ++out_row;
321
+ }
322
+ /* Inner join + no match: skip */
323
+ }
324
+
325
+ if (out_row > 0)
326
+ *out = ob;
327
+ else
328
+ tf_batch_free(ob);
329
+
330
+ return TF_OK;
331
+ }
332
+
333
+ static int join_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
334
+ (void)self; (void)side; *out = NULL; return TF_OK;
335
+ }
336
+
337
+ static void join_destroy(tf_step *self) {
338
+ join_state *st = self->state;
339
+ if (st) {
340
+ free(st->file);
341
+ free(st->left_col);
342
+ free(st->right_col);
343
+ if (st->lookup) tf_batch_free(st->lookup);
344
+ free(st->lookup_out_cols);
345
+ map_free(&st->map);
346
+ free(st);
347
+ }
348
+ free(self);
349
+ }
350
+
351
+ tf_step *tf_join_create(const cJSON *args) {
352
+ if (!args) return NULL;
353
+ cJSON *file_j = cJSON_GetObjectItemCaseSensitive(args, "file");
354
+ cJSON *on_j = cJSON_GetObjectItemCaseSensitive(args, "on");
355
+ if (!cJSON_IsString(file_j) || !cJSON_IsString(on_j)) return NULL;
356
+
357
+ join_state *st = calloc(1, sizeof(join_state));
358
+ if (!st) return NULL;
359
+
360
+ st->file = strdup(file_j->valuestring);
361
+
362
+ /* Parse "on" field: "col" or "left_col=right_col" */
363
+ const char *on = on_j->valuestring;
364
+ const char *eq = strchr(on, '=');
365
+ if (eq) {
366
+ st->left_col = strndup(on, (size_t)(eq - on));
367
+ st->right_col = strdup(eq + 1);
368
+ } else {
369
+ st->left_col = strdup(on);
370
+ st->right_col = strdup(on);
371
+ }
372
+
373
+ cJSON *how_j = cJSON_GetObjectItemCaseSensitive(args, "how");
374
+ if (cJSON_IsString(how_j) && strcmp(how_j->valuestring, "left") == 0)
375
+ st->how = 1;
376
+
377
+ tf_step *step = malloc(sizeof(tf_step));
378
+ if (!step) { join_destroy(&(tf_step){.state = st}); return NULL; }
379
+ step->process = join_process;
380
+ step->flush = join_flush;
381
+ step->destroy = join_destroy;
382
+ step->state = st;
383
+ return step;
384
+ }
@@ -0,0 +1,144 @@
1
+ /*
2
+ * op_label_encode.c — Map categorical values to sequential integers.
3
+ *
4
+ * Config: {"column": "city", "result": "city_encoded"}
5
+ */
6
+
7
+ #include "internal.h"
8
+ #include "cJSON.h"
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+ #include <stdio.h>
12
+
13
+ typedef struct label_entry {
14
+ char *value;
15
+ int64_t label;
16
+ } label_entry;
17
+
18
+ typedef struct {
19
+ char *column;
20
+ char *result;
21
+ label_entry *entries;
22
+ size_t n_entries;
23
+ size_t cap;
24
+ int64_t next_label;
25
+ } label_encode_state;
26
+
27
+ static const char *get_string_value(const tf_batch *b, size_t r, int ci, char *buf, size_t bufsz) {
28
+ if (tf_batch_is_null(b, r, ci)) return NULL;
29
+ switch (b->col_types[ci]) {
30
+ case TF_TYPE_STRING: return tf_batch_get_string(b, r, ci);
31
+ case TF_TYPE_INT64:
32
+ snprintf(buf, bufsz, "%lld", (long long)tf_batch_get_int64(b, r, ci));
33
+ return buf;
34
+ case TF_TYPE_FLOAT64:
35
+ snprintf(buf, bufsz, "%.17g", tf_batch_get_float64(b, r, ci));
36
+ return buf;
37
+ case TF_TYPE_BOOL:
38
+ return tf_batch_get_bool(b, r, ci) ? "true" : "false";
39
+ default: return NULL;
40
+ }
41
+ }
42
+
43
+ static int64_t lookup_or_assign(label_encode_state *st, const char *val) {
44
+ for (size_t i = 0; i < st->n_entries; i++) {
45
+ if (strcmp(st->entries[i].value, val) == 0)
46
+ return st->entries[i].label;
47
+ }
48
+ /* New value — assign next label */
49
+ if (st->n_entries >= st->cap) {
50
+ size_t newcap = st->cap ? st->cap * 2 : 16;
51
+ label_entry *tmp = realloc(st->entries, newcap * sizeof(label_entry));
52
+ if (!tmp) return -1;
53
+ st->entries = tmp;
54
+ st->cap = newcap;
55
+ }
56
+ st->entries[st->n_entries].value = strdup(val);
57
+ st->entries[st->n_entries].label = st->next_label;
58
+ st->n_entries++;
59
+ return st->next_label++;
60
+ }
61
+
62
+ static int label_encode_process(tf_step *self, tf_batch *in, tf_batch **out,
63
+ tf_side_channels *side) {
64
+ (void)side;
65
+ label_encode_state *st = self->state;
66
+ *out = NULL;
67
+
68
+ tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
69
+ if (!ob) return TF_ERROR;
70
+ for (size_t c = 0; c < in->n_cols; c++)
71
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
72
+ tf_batch_set_schema(ob, in->n_cols, st->result, TF_TYPE_INT64);
73
+
74
+ int ci = tf_batch_col_index(in, st->column);
75
+
76
+ for (size_t r = 0; r < in->n_rows; r++) {
77
+ tf_batch_copy_row(ob, r, in, r);
78
+
79
+ if (ci < 0 || tf_batch_is_null(in, r, ci)) {
80
+ tf_batch_set_null(ob, r, in->n_cols);
81
+ ob->n_rows = r + 1;
82
+ continue;
83
+ }
84
+
85
+ char buf[64];
86
+ const char *val = get_string_value(in, r, ci, buf, sizeof(buf));
87
+ if (!val) {
88
+ tf_batch_set_null(ob, r, in->n_cols);
89
+ ob->n_rows = r + 1;
90
+ continue;
91
+ }
92
+
93
+ int64_t label = lookup_or_assign(st, val);
94
+ tf_batch_set_int64(ob, r, in->n_cols, label);
95
+ ob->n_rows = r + 1;
96
+ }
97
+
98
+ *out = ob;
99
+ return TF_OK;
100
+ }
101
+
102
+ static int label_encode_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
103
+ (void)self; (void)side; *out = NULL; return TF_OK;
104
+ }
105
+
106
+ static void label_encode_destroy(tf_step *self) {
107
+ label_encode_state *st = self->state;
108
+ if (st) {
109
+ for (size_t i = 0; i < st->n_entries; i++)
110
+ free(st->entries[i].value);
111
+ free(st->entries);
112
+ free(st->column);
113
+ free(st->result);
114
+ free(st);
115
+ }
116
+ free(self);
117
+ }
118
+
119
+ tf_step *tf_label_encode_create(const cJSON *args) {
120
+ if (!args) return NULL;
121
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
122
+ if (!cJSON_IsString(col_j)) return NULL;
123
+
124
+ label_encode_state *st = calloc(1, sizeof(label_encode_state));
125
+ if (!st) return NULL;
126
+ st->column = strdup(col_j->valuestring);
127
+
128
+ cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
129
+ if (cJSON_IsString(res_j)) {
130
+ st->result = strdup(res_j->valuestring);
131
+ } else {
132
+ char buf[256];
133
+ snprintf(buf, sizeof(buf), "%s_encoded", st->column);
134
+ st->result = strdup(buf);
135
+ }
136
+
137
+ tf_step *step = malloc(sizeof(tf_step));
138
+ if (!step) { free(st->column); free(st->result); free(st); return NULL; }
139
+ step->process = label_encode_process;
140
+ step->flush = label_encode_flush;
141
+ step->destroy = label_encode_destroy;
142
+ step->state = st;
143
+ return step;
144
+ }