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_step.c ADDED
@@ -0,0 +1,171 @@
1
+ /*
2
+ * op_step.c — Running aggregations: running-sum, running-avg, running-min,
3
+ * running-max, running-count, delta, lag, ratio.
4
+ *
5
+ * Config: {"column": "price", "func": "running-sum", "result": "cumsum"}
6
+ */
7
+
8
+ #include "internal.h"
9
+ #include "cJSON.h"
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+ #include <stdio.h>
13
+ #include <math.h>
14
+
15
+ typedef enum {
16
+ STEP_RUNNING_SUM,
17
+ STEP_RUNNING_AVG,
18
+ STEP_RUNNING_MIN,
19
+ STEP_RUNNING_MAX,
20
+ STEP_RUNNING_COUNT,
21
+ STEP_DELTA,
22
+ STEP_LAG,
23
+ STEP_RATIO,
24
+ } step_func;
25
+
26
+ typedef struct {
27
+ char *column;
28
+ char *result;
29
+ step_func func;
30
+ double running_sum;
31
+ double running_min;
32
+ double running_max;
33
+ size_t running_count;
34
+ double prev_val;
35
+ int has_prev;
36
+ } step_state;
37
+
38
+ static step_func parse_func(const char *s) {
39
+ if (strcmp(s, "running-sum") == 0 || strcmp(s, "cumsum") == 0) return STEP_RUNNING_SUM;
40
+ if (strcmp(s, "running-avg") == 0 || strcmp(s, "cumavg") == 0) return STEP_RUNNING_AVG;
41
+ if (strcmp(s, "running-min") == 0) return STEP_RUNNING_MIN;
42
+ if (strcmp(s, "running-max") == 0) return STEP_RUNNING_MAX;
43
+ if (strcmp(s, "running-count") == 0) return STEP_RUNNING_COUNT;
44
+ if (strcmp(s, "delta") == 0) return STEP_DELTA;
45
+ if (strcmp(s, "lag") == 0) return STEP_LAG;
46
+ if (strcmp(s, "ratio") == 0) return STEP_RATIO;
47
+ return STEP_RUNNING_SUM;
48
+ }
49
+
50
+ static double get_numeric(const tf_batch *b, size_t r, int ci) {
51
+ if (b->col_types[ci] == TF_TYPE_INT64) return (double)tf_batch_get_int64(b, r, ci);
52
+ if (b->col_types[ci] == TF_TYPE_FLOAT64) return tf_batch_get_float64(b, r, ci);
53
+ return 0;
54
+ }
55
+
56
+ static int step_process(tf_step *self, tf_batch *in, tf_batch **out,
57
+ tf_side_channels *side) {
58
+ (void)side;
59
+ step_state *st = self->state;
60
+ *out = NULL;
61
+
62
+ tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
63
+ if (!ob) return TF_ERROR;
64
+ for (size_t c = 0; c < in->n_cols; c++)
65
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
66
+ tf_batch_set_schema(ob, in->n_cols, st->result, TF_TYPE_FLOAT64);
67
+
68
+ int ci = tf_batch_col_index(in, st->column);
69
+
70
+ for (size_t r = 0; r < in->n_rows; r++) {
71
+ tf_batch_copy_row(ob, r, in, r);
72
+
73
+ if (ci < 0 || tf_batch_is_null(in, r, ci)) {
74
+ tf_batch_set_null(ob, r, in->n_cols);
75
+ ob->n_rows = r + 1;
76
+ continue;
77
+ }
78
+
79
+ double val = get_numeric(in, r, ci);
80
+ double result = 0;
81
+
82
+ switch (st->func) {
83
+ case STEP_RUNNING_SUM:
84
+ st->running_sum += val;
85
+ result = st->running_sum;
86
+ break;
87
+ case STEP_RUNNING_AVG:
88
+ st->running_sum += val;
89
+ st->running_count++;
90
+ result = st->running_sum / st->running_count;
91
+ break;
92
+ case STEP_RUNNING_MIN:
93
+ if (st->running_count == 0 || val < st->running_min)
94
+ st->running_min = val;
95
+ st->running_count++;
96
+ result = st->running_min;
97
+ break;
98
+ case STEP_RUNNING_MAX:
99
+ if (st->running_count == 0 || val > st->running_max)
100
+ st->running_max = val;
101
+ st->running_count++;
102
+ result = st->running_max;
103
+ break;
104
+ case STEP_RUNNING_COUNT:
105
+ st->running_count++;
106
+ result = (double)st->running_count;
107
+ break;
108
+ case STEP_DELTA:
109
+ if (st->has_prev) result = val - st->prev_val;
110
+ else { tf_batch_set_null(ob, r, in->n_cols); st->prev_val = val; st->has_prev = 1; ob->n_rows = r + 1; continue; }
111
+ st->prev_val = val;
112
+ break;
113
+ case STEP_LAG:
114
+ if (st->has_prev) result = st->prev_val;
115
+ else { tf_batch_set_null(ob, r, in->n_cols); st->prev_val = val; st->has_prev = 1; ob->n_rows = r + 1; continue; }
116
+ st->prev_val = val;
117
+ break;
118
+ case STEP_RATIO:
119
+ if (st->has_prev && st->prev_val != 0) result = val / st->prev_val;
120
+ else { tf_batch_set_null(ob, r, in->n_cols); st->prev_val = val; st->has_prev = 1; ob->n_rows = r + 1; continue; }
121
+ st->prev_val = val;
122
+ break;
123
+ }
124
+
125
+ tf_batch_set_float64(ob, r, in->n_cols, result);
126
+ ob->n_rows = r + 1;
127
+ }
128
+
129
+ *out = ob;
130
+ return TF_OK;
131
+ }
132
+
133
+ static int step_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
134
+ (void)self; (void)side; *out = NULL; return TF_OK;
135
+ }
136
+
137
+ static void step_destroy(tf_step *self) {
138
+ step_state *st = self->state;
139
+ if (st) { free(st->column); free(st->result); free(st); }
140
+ free(self);
141
+ }
142
+
143
+ tf_step *tf_step_create(const cJSON *args) {
144
+ if (!args) return NULL;
145
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
146
+ cJSON *func_j = cJSON_GetObjectItemCaseSensitive(args, "func");
147
+ if (!cJSON_IsString(col_j) || !cJSON_IsString(func_j)) return NULL;
148
+
149
+ step_state *st = calloc(1, sizeof(step_state));
150
+ if (!st) return NULL;
151
+ st->column = strdup(col_j->valuestring);
152
+ st->func = parse_func(func_j->valuestring);
153
+
154
+ cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
155
+ if (cJSON_IsString(res_j)) {
156
+ st->result = strdup(res_j->valuestring);
157
+ } else {
158
+ /* Default result name */
159
+ char buf[256];
160
+ snprintf(buf, sizeof(buf), "%s_%s", st->column, func_j->valuestring);
161
+ st->result = strdup(buf);
162
+ }
163
+
164
+ tf_step *step = malloc(sizeof(tf_step));
165
+ if (!step) { free(st->column); free(st->result); free(st); return NULL; }
166
+ step->process = step_process;
167
+ step->flush = step_flush;
168
+ step->destroy = step_destroy;
169
+ step->state = st;
170
+ return step;
171
+ }
package/csrc/op_tail.c ADDED
@@ -0,0 +1,96 @@
1
+ /*
2
+ * op_tail.c — Last N rows via circular buffer. Bounded memory.
3
+ *
4
+ * Config: {"n": 5}
5
+ */
6
+
7
+ #include "internal.h"
8
+ #include "cJSON.h"
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+
12
+ typedef struct {
13
+ size_t limit;
14
+ tf_batch *buf; /* circular buffer of rows */
15
+ size_t head; /* next write position */
16
+ size_t count; /* total rows seen */
17
+ int has_schema;
18
+ } tail_state;
19
+
20
+ static int tail_process(tf_step *self, tf_batch *in, tf_batch **out,
21
+ tf_side_channels *side) {
22
+ (void)side;
23
+ tail_state *st = self->state;
24
+ *out = NULL;
25
+
26
+ if (!st->has_schema) {
27
+ st->buf = tf_batch_create(in->n_cols, st->limit);
28
+ if (!st->buf) return TF_ERROR;
29
+ for (size_t c = 0; c < in->n_cols; c++)
30
+ tf_batch_set_schema(st->buf, c, in->col_names[c], in->col_types[c]);
31
+ st->buf->n_rows = 0;
32
+ st->has_schema = 1;
33
+ }
34
+
35
+ for (size_t r = 0; r < in->n_rows; r++) {
36
+ size_t dst = st->head % st->limit;
37
+ tf_batch_copy_row(st->buf, dst, in, r);
38
+ st->head++;
39
+ st->count++;
40
+ if (st->buf->n_rows < st->limit) st->buf->n_rows++;
41
+ }
42
+
43
+ return TF_OK;
44
+ }
45
+
46
+ static int tail_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
47
+ (void)side;
48
+ tail_state *st = self->state;
49
+ *out = NULL;
50
+
51
+ if (!st->buf || st->buf->n_rows == 0) return TF_OK;
52
+
53
+ size_t n = st->buf->n_rows;
54
+ tf_batch *ob = tf_batch_create(st->buf->n_cols, n);
55
+ if (!ob) return TF_ERROR;
56
+ for (size_t c = 0; c < st->buf->n_cols; c++)
57
+ tf_batch_set_schema(ob, c, st->buf->col_names[c], st->buf->col_types[c]);
58
+
59
+ /* Read from circular buffer in order */
60
+ size_t start = (st->head >= n) ? (st->head - n) % st->limit : 0;
61
+ for (size_t i = 0; i < n; i++) {
62
+ size_t src = (start + i) % st->limit;
63
+ tf_batch_copy_row(ob, i, st->buf, src);
64
+ ob->n_rows = i + 1;
65
+ }
66
+
67
+ *out = ob;
68
+ return TF_OK;
69
+ }
70
+
71
+ static void tail_destroy(tf_step *self) {
72
+ tail_state *st = self->state;
73
+ if (st) {
74
+ if (st->buf) tf_batch_free(st->buf);
75
+ free(st);
76
+ }
77
+ free(self);
78
+ }
79
+
80
+ tf_step *tf_tail_create(const cJSON *args) {
81
+ if (!args) return NULL;
82
+ cJSON *n_json = cJSON_GetObjectItemCaseSensitive(args, "n");
83
+ if (!cJSON_IsNumber(n_json) || n_json->valueint <= 0) return NULL;
84
+
85
+ tail_state *st = calloc(1, sizeof(tail_state));
86
+ if (!st) return NULL;
87
+ st->limit = (size_t)n_json->valueint;
88
+
89
+ tf_step *step = malloc(sizeof(tf_step));
90
+ if (!step) { free(st); return NULL; }
91
+ step->process = tail_process;
92
+ step->flush = tail_flush;
93
+ step->destroy = tail_destroy;
94
+ step->state = st;
95
+ return step;
96
+ }
package/csrc/op_top.c ADDED
@@ -0,0 +1,150 @@
1
+ /*
2
+ * op_top.c — Top N rows by column via sorted buffer.
3
+ *
4
+ * Config: {"n": 10, "column": "score", "desc": true}
5
+ */
6
+
7
+ #include "internal.h"
8
+ #include "cJSON.h"
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+
12
+ typedef struct {
13
+ size_t n;
14
+ char *column;
15
+ int desc; /* 1 = highest first (default) */
16
+ tf_batch *buf;
17
+ int has_schema;
18
+ int col_idx;
19
+ } top_state;
20
+
21
+ static double top_get_val(const tf_batch *b, size_t r, int ci) {
22
+ if (tf_batch_is_null(b, r, ci)) return -1e308;
23
+ if (b->col_types[ci] == TF_TYPE_INT64) return (double)tf_batch_get_int64(b, r, ci);
24
+ if (b->col_types[ci] == TF_TYPE_FLOAT64) return tf_batch_get_float64(b, r, ci);
25
+ if (b->col_types[ci] == TF_TYPE_STRING) return 0; /* string comparison not supported for top */
26
+ return 0;
27
+ }
28
+
29
+ static int top_process(tf_step *self, tf_batch *in, tf_batch **out,
30
+ tf_side_channels *side) {
31
+ (void)side;
32
+ top_state *st = self->state;
33
+ *out = NULL;
34
+
35
+ if (!st->has_schema) {
36
+ st->buf = tf_batch_create(in->n_cols, st->n + 1);
37
+ if (!st->buf) return TF_ERROR;
38
+ for (size_t c = 0; c < in->n_cols; c++)
39
+ tf_batch_set_schema(st->buf, c, in->col_names[c], in->col_types[c]);
40
+ st->col_idx = tf_batch_col_index(in, st->column);
41
+ st->has_schema = 1;
42
+ }
43
+
44
+ for (size_t r = 0; r < in->n_rows; r++) {
45
+ double new_val = top_get_val(in, r, st->col_idx >= 0 ? st->col_idx : 0);
46
+
47
+ if (st->buf->n_rows < st->n) {
48
+ /* Buffer not full, just add */
49
+ size_t dst = st->buf->n_rows;
50
+ tf_batch_copy_row(st->buf, dst, in, r);
51
+ st->buf->n_rows++;
52
+ } else {
53
+ /* Find the worst element */
54
+ size_t worst_idx = 0;
55
+ double worst_val = top_get_val(st->buf, 0, st->col_idx >= 0 ? st->col_idx : 0);
56
+ for (size_t i = 1; i < st->buf->n_rows; i++) {
57
+ double v = top_get_val(st->buf, i, st->col_idx >= 0 ? st->col_idx : 0);
58
+ if (st->desc ? (v < worst_val) : (v > worst_val)) {
59
+ worst_val = v;
60
+ worst_idx = i;
61
+ }
62
+ }
63
+ /* Replace worst if new value is better */
64
+ int replace = st->desc ? (new_val > worst_val) : (new_val < worst_val);
65
+ if (replace) {
66
+ tf_batch_copy_row(st->buf, worst_idx, in, r);
67
+ }
68
+ }
69
+ }
70
+
71
+ return TF_OK;
72
+ }
73
+
74
+ /* Sort comparator context */
75
+ typedef struct { const tf_batch *batch; int col_idx; int desc; } top_sort_ctx;
76
+ static top_sort_ctx *g_top_ctx;
77
+
78
+ static int top_compare(const void *a, const void *b) {
79
+ size_t ra = *(const size_t *)a;
80
+ size_t rb = *(const size_t *)b;
81
+ double va = top_get_val(g_top_ctx->batch, ra, g_top_ctx->col_idx);
82
+ double vb = top_get_val(g_top_ctx->batch, rb, g_top_ctx->col_idx);
83
+ int cmp = (va > vb) - (va < vb);
84
+ return g_top_ctx->desc ? -cmp : cmp;
85
+ }
86
+
87
+ static int top_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
88
+ (void)side;
89
+ top_state *st = self->state;
90
+ *out = NULL;
91
+
92
+ if (!st->buf || st->buf->n_rows == 0) return TF_OK;
93
+
94
+ /* Sort the buffer */
95
+ size_t n = st->buf->n_rows;
96
+ size_t *indices = malloc(n * sizeof(size_t));
97
+ if (!indices) return TF_ERROR;
98
+ for (size_t i = 0; i < n; i++) indices[i] = i;
99
+
100
+ top_sort_ctx ctx = { .batch = st->buf, .col_idx = st->col_idx >= 0 ? st->col_idx : 0, .desc = st->desc };
101
+ g_top_ctx = &ctx;
102
+ qsort(indices, n, sizeof(size_t), top_compare);
103
+ g_top_ctx = NULL;
104
+
105
+ tf_batch *ob = tf_batch_create(st->buf->n_cols, n);
106
+ if (!ob) { free(indices); return TF_ERROR; }
107
+ for (size_t c = 0; c < st->buf->n_cols; c++)
108
+ tf_batch_set_schema(ob, c, st->buf->col_names[c], st->buf->col_types[c]);
109
+ for (size_t i = 0; i < n; i++) {
110
+ tf_batch_copy_row(ob, i, st->buf, indices[i]);
111
+ ob->n_rows = i + 1;
112
+ }
113
+
114
+ free(indices);
115
+ *out = ob;
116
+ return TF_OK;
117
+ }
118
+
119
+ static void top_destroy(tf_step *self) {
120
+ top_state *st = self->state;
121
+ if (st) {
122
+ free(st->column);
123
+ if (st->buf) tf_batch_free(st->buf);
124
+ free(st);
125
+ }
126
+ free(self);
127
+ }
128
+
129
+ tf_step *tf_top_create(const cJSON *args) {
130
+ if (!args) return NULL;
131
+ cJSON *n_j = cJSON_GetObjectItemCaseSensitive(args, "n");
132
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
133
+ if (!cJSON_IsNumber(n_j) || !cJSON_IsString(col_j)) return NULL;
134
+
135
+ top_state *st = calloc(1, sizeof(top_state));
136
+ if (!st) return NULL;
137
+ st->n = (size_t)n_j->valueint;
138
+ st->column = strdup(col_j->valuestring);
139
+
140
+ cJSON *desc_j = cJSON_GetObjectItemCaseSensitive(args, "desc");
141
+ st->desc = (desc_j && cJSON_IsBool(desc_j)) ? cJSON_IsTrue(desc_j) : 1; /* default desc */
142
+
143
+ tf_step *step = malloc(sizeof(tf_step));
144
+ if (!step) { free(st->column); free(st); return NULL; }
145
+ step->process = top_process;
146
+ step->flush = top_flush;
147
+ step->destroy = top_destroy;
148
+ step->state = st;
149
+ return step;
150
+ }
package/csrc/op_trim.c ADDED
@@ -0,0 +1,109 @@
1
+ /*
2
+ * op_trim.c — Trim whitespace from string columns.
3
+ *
4
+ * Config: {"columns": ["name", "city"]}
5
+ * or {} for all string columns.
6
+ */
7
+
8
+ #include "internal.h"
9
+ #include "cJSON.h"
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+ #include <ctype.h>
13
+
14
+ typedef struct {
15
+ char **cols;
16
+ size_t n_cols;
17
+ } trim_state;
18
+
19
+ static const char *trim_str(const char *s, char *buf, size_t buf_sz) {
20
+ while (*s && isspace((unsigned char)*s)) s++;
21
+ size_t len = strlen(s);
22
+ while (len > 0 && isspace((unsigned char)s[len - 1])) len--;
23
+ if (len >= buf_sz) len = buf_sz - 1;
24
+ memcpy(buf, s, len);
25
+ buf[len] = '\0';
26
+ return buf;
27
+ }
28
+
29
+ static int trim_process(tf_step *self, tf_batch *in, tf_batch **out,
30
+ tf_side_channels *side) {
31
+ (void)side;
32
+ trim_state *st = self->state;
33
+ *out = NULL;
34
+
35
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
36
+ if (!ob) return TF_ERROR;
37
+ for (size_t c = 0; c < in->n_cols; c++)
38
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
39
+
40
+ for (size_t r = 0; r < in->n_rows; r++) {
41
+ tf_batch_copy_row(ob, r, in, r);
42
+ ob->n_rows = r + 1;
43
+ }
44
+
45
+ /* Trim target columns */
46
+ for (size_t c = 0; c < in->n_cols; c++) {
47
+ if (in->col_types[c] != TF_TYPE_STRING) continue;
48
+ int target = 0;
49
+ if (st->n_cols == 0) {
50
+ target = 1; /* all string cols */
51
+ } else {
52
+ for (size_t k = 0; k < st->n_cols; k++) {
53
+ if (strcmp(in->col_names[c], st->cols[k]) == 0) { target = 1; break; }
54
+ }
55
+ }
56
+ if (!target) continue;
57
+ char buf[4096];
58
+ for (size_t r = 0; r < ob->n_rows; r++) {
59
+ if (tf_batch_is_null(ob, r, c)) continue;
60
+ const char *val = tf_batch_get_string(ob, r, c);
61
+ tf_batch_set_string(ob, r, c, trim_str(val, buf, sizeof(buf)));
62
+ }
63
+ }
64
+
65
+ *out = ob;
66
+ return TF_OK;
67
+ }
68
+
69
+ static int trim_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
70
+ (void)self; (void)side; *out = NULL; return TF_OK;
71
+ }
72
+
73
+ static void trim_destroy(tf_step *self) {
74
+ trim_state *st = self->state;
75
+ if (st) {
76
+ for (size_t i = 0; i < st->n_cols; i++) free(st->cols[i]);
77
+ free(st->cols);
78
+ free(st);
79
+ }
80
+ free(self);
81
+ }
82
+
83
+ tf_step *tf_trim_create(const cJSON *args) {
84
+ trim_state *st = calloc(1, sizeof(trim_state));
85
+ if (!st) return NULL;
86
+
87
+ if (args) {
88
+ cJSON *columns = cJSON_GetObjectItemCaseSensitive(args, "columns");
89
+ if (columns && cJSON_IsArray(columns)) {
90
+ int n = cJSON_GetArraySize(columns);
91
+ if (n > 0) {
92
+ st->cols = calloc(n, sizeof(char *));
93
+ st->n_cols = n;
94
+ for (int i = 0; i < n; i++) {
95
+ cJSON *item = cJSON_GetArrayItem(columns, i);
96
+ if (cJSON_IsString(item)) st->cols[i] = strdup(item->valuestring);
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ tf_step *step = malloc(sizeof(tf_step));
103
+ if (!step) { free(st); return NULL; }
104
+ step->process = trim_process;
105
+ step->flush = trim_flush;
106
+ step->destroy = trim_destroy;
107
+ step->state = st;
108
+ return step;
109
+ }