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
@@ -0,0 +1,138 @@
1
+ /*
2
+ * op_rename.c — Rename columns.
3
+ *
4
+ * Config: {"mapping": {"old_name": "new_name", ...}}
5
+ * Creates output batch with renamed columns. Unmatched columns pass through.
6
+ */
7
+
8
+ #include "internal.h"
9
+ #include "cJSON.h"
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+
13
+ typedef struct {
14
+ char **old_names;
15
+ char **new_names;
16
+ size_t n_mappings;
17
+ } rename_state;
18
+
19
+ static const char *find_rename(rename_state *st, const char *name) {
20
+ for (size_t i = 0; i < st->n_mappings; i++) {
21
+ if (strcmp(st->old_names[i], name) == 0)
22
+ return st->new_names[i];
23
+ }
24
+ return name; /* unchanged */
25
+ }
26
+
27
+ static int rename_process(tf_step *self, tf_batch *in, tf_batch **out,
28
+ tf_side_channels *side) {
29
+ (void)side;
30
+ rename_state *st = self->state;
31
+
32
+ /* Create output batch with renamed columns */
33
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
34
+ if (!ob) return TF_ERROR;
35
+
36
+ for (size_t i = 0; i < in->n_cols; i++) {
37
+ const char *new_name = find_rename(st, in->col_names[i]);
38
+ tf_batch_set_schema(ob, i, new_name, in->col_types[i]);
39
+ }
40
+
41
+ /* Copy all rows */
42
+ for (size_t r = 0; r < in->n_rows; r++) {
43
+ tf_batch_ensure_capacity(ob, r + 1);
44
+ for (size_t c = 0; c < in->n_cols; c++) {
45
+ if (tf_batch_is_null(in, r, c)) {
46
+ tf_batch_set_null(ob, r, c);
47
+ continue;
48
+ }
49
+ switch (in->col_types[c]) {
50
+ case TF_TYPE_BOOL:
51
+ tf_batch_set_bool(ob, r, c, tf_batch_get_bool(in, r, c));
52
+ break;
53
+ case TF_TYPE_INT64:
54
+ tf_batch_set_int64(ob, r, c, tf_batch_get_int64(in, r, c));
55
+ break;
56
+ case TF_TYPE_FLOAT64:
57
+ tf_batch_set_float64(ob, r, c, tf_batch_get_float64(in, r, c));
58
+ break;
59
+ case TF_TYPE_STRING:
60
+ tf_batch_set_string(ob, r, c, tf_batch_get_string(in, r, c));
61
+ break;
62
+ case TF_TYPE_DATE:
63
+ tf_batch_set_date(ob, r, c, tf_batch_get_date(in, r, c));
64
+ break;
65
+ case TF_TYPE_TIMESTAMP:
66
+ tf_batch_set_timestamp(ob, r, c, tf_batch_get_timestamp(in, r, c));
67
+ break;
68
+ default:
69
+ tf_batch_set_null(ob, r, c);
70
+ break;
71
+ }
72
+ }
73
+ ob->n_rows = r + 1;
74
+ }
75
+
76
+ *out = ob;
77
+ return TF_OK;
78
+ }
79
+
80
+ static int rename_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
81
+ (void)self; (void)side;
82
+ *out = NULL;
83
+ return TF_OK;
84
+ }
85
+
86
+ static void rename_destroy(tf_step *self) {
87
+ rename_state *st = self->state;
88
+ if (st) {
89
+ for (size_t i = 0; i < st->n_mappings; i++) {
90
+ free(st->old_names[i]);
91
+ free(st->new_names[i]);
92
+ }
93
+ free(st->old_names);
94
+ free(st->new_names);
95
+ free(st);
96
+ }
97
+ free(self);
98
+ }
99
+
100
+ tf_step *tf_rename_create(const cJSON *args) {
101
+ if (!args) return NULL;
102
+ cJSON *mapping = cJSON_GetObjectItemCaseSensitive(args, "mapping");
103
+ if (!mapping || !cJSON_IsObject(mapping)) return NULL;
104
+
105
+ int n = cJSON_GetArraySize(mapping);
106
+ if (n <= 0) return NULL;
107
+
108
+ rename_state *st = calloc(1, sizeof(rename_state));
109
+ if (!st) return NULL;
110
+ st->n_mappings = (size_t)n;
111
+ st->old_names = malloc(n * sizeof(char *));
112
+ st->new_names = malloc(n * sizeof(char *));
113
+ if (!st->old_names || !st->new_names) {
114
+ free(st->old_names);
115
+ free(st->new_names);
116
+ free(st);
117
+ return NULL;
118
+ }
119
+
120
+ int i = 0;
121
+ cJSON *item;
122
+ cJSON_ArrayForEach(item, mapping) {
123
+ st->old_names[i] = strdup(item->string);
124
+ st->new_names[i] = strdup(cJSON_IsString(item) ? item->valuestring : item->string);
125
+ i++;
126
+ }
127
+
128
+ tf_step *step = malloc(sizeof(tf_step));
129
+ if (!step) {
130
+ rename_destroy(&(tf_step){.state = st});
131
+ return NULL;
132
+ }
133
+ step->process = rename_process;
134
+ step->flush = rename_flush;
135
+ step->destroy = rename_destroy;
136
+ step->state = st;
137
+ return step;
138
+ }
@@ -0,0 +1,202 @@
1
+ /*
2
+ * op_replace.c — String find/replace (substring or regex).
3
+ *
4
+ * Config: {"column": "name", "pattern": "foo", "replacement": "bar", "regex": false}
5
+ * In regex mode, & in replacement refers to the whole match.
6
+ */
7
+
8
+ #include "internal.h"
9
+ #include "cJSON.h"
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+ #include <regex.h>
13
+
14
+ typedef struct {
15
+ char *column;
16
+ char *pattern;
17
+ char *replacement;
18
+ int use_regex;
19
+ regex_t compiled;
20
+ } replace_state;
21
+
22
+ static int replace_process(tf_step *self, tf_batch *in, tf_batch **out,
23
+ tf_side_channels *side) {
24
+ (void)side;
25
+ replace_state *st = self->state;
26
+ *out = NULL;
27
+
28
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
29
+ if (!ob) return TF_ERROR;
30
+ for (size_t c = 0; c < in->n_cols; c++)
31
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
32
+ for (size_t r = 0; r < in->n_rows; r++) {
33
+ tf_batch_copy_row(ob, r, in, r);
34
+ ob->n_rows = r + 1;
35
+ }
36
+
37
+ int ci = tf_batch_col_index(ob, st->column);
38
+ if (ci >= 0 && ob->col_types[ci] == TF_TYPE_STRING) {
39
+ if (st->use_regex) {
40
+ /* Regex mode: use regexec to find matches */
41
+ for (size_t r = 0; r < ob->n_rows; r++) {
42
+ if (tf_batch_is_null(ob, r, ci)) continue;
43
+ const char *val = tf_batch_get_string(ob, r, ci);
44
+ /* Build result by repeatedly matching and replacing */
45
+ size_t val_len = strlen(val);
46
+ size_t buf_cap = val_len * 2 + 64;
47
+ char *buf = malloc(buf_cap);
48
+ if (!buf) continue;
49
+ size_t buf_len = 0;
50
+ const char *p = val;
51
+ regmatch_t match;
52
+ int replaced = 0;
53
+ while (regexec(&st->compiled, p, 1, &match, 0) == 0 && match.rm_so >= 0) {
54
+ replaced = 1;
55
+ size_t prefix = (size_t)match.rm_so;
56
+ size_t match_len = (size_t)(match.rm_eo - match.rm_so);
57
+ /* Expand replacement: & refers to whole match */
58
+ size_t rep_need = 0;
59
+ for (const char *rp = st->replacement; *rp; rp++) {
60
+ if (*rp == '&') rep_need += match_len;
61
+ else rep_need++;
62
+ }
63
+ size_t need = buf_len + prefix + rep_need + 1;
64
+ if (need > buf_cap) {
65
+ buf_cap = need * 2;
66
+ char *nb = realloc(buf, buf_cap);
67
+ if (!nb) { free(buf); buf = NULL; break; }
68
+ buf = nb;
69
+ }
70
+ memcpy(buf + buf_len, p, prefix);
71
+ buf_len += prefix;
72
+ for (const char *rp = st->replacement; *rp; rp++) {
73
+ if (*rp == '&') {
74
+ memcpy(buf + buf_len, p + match.rm_so, match_len);
75
+ buf_len += match_len;
76
+ } else {
77
+ buf[buf_len++] = *rp;
78
+ }
79
+ }
80
+ p += match.rm_eo;
81
+ if (match_len == 0) {
82
+ /* Zero-length match: copy one char to avoid infinite loop */
83
+ if (*p) {
84
+ if (buf_len + 2 > buf_cap) {
85
+ buf_cap = (buf_len + 2) * 2;
86
+ char *nb = realloc(buf, buf_cap);
87
+ if (!nb) { free(buf); buf = NULL; break; }
88
+ buf = nb;
89
+ }
90
+ buf[buf_len++] = *p++;
91
+ } else break;
92
+ }
93
+ }
94
+ if (buf && replaced) {
95
+ /* Copy remaining */
96
+ size_t rest = strlen(p);
97
+ if (buf_len + rest + 1 > buf_cap) {
98
+ buf_cap = buf_len + rest + 1;
99
+ char *nb = realloc(buf, buf_cap);
100
+ if (!nb) { free(buf); continue; }
101
+ buf = nb;
102
+ }
103
+ memcpy(buf + buf_len, p, rest);
104
+ buf_len += rest;
105
+ buf[buf_len] = '\0';
106
+ tf_batch_set_string(ob, r, ci, buf);
107
+ }
108
+ free(buf);
109
+ }
110
+ } else {
111
+ /* Substring mode */
112
+ size_t pat_len = strlen(st->pattern);
113
+ size_t rep_len = strlen(st->replacement);
114
+ if (pat_len > 0) {
115
+ for (size_t r = 0; r < ob->n_rows; r++) {
116
+ if (tf_batch_is_null(ob, r, ci)) continue;
117
+ const char *val = tf_batch_get_string(ob, r, ci);
118
+ /* Count occurrences */
119
+ size_t count = 0;
120
+ const char *p = val;
121
+ while ((p = strstr(p, st->pattern)) != NULL) { count++; p += pat_len; }
122
+ if (count == 0) continue;
123
+
124
+ size_t new_len = strlen(val) + count * (rep_len - pat_len);
125
+ char *buf = malloc(new_len + 1);
126
+ if (!buf) continue;
127
+ char *dst = buf;
128
+ p = val;
129
+ const char *found;
130
+ while ((found = strstr(p, st->pattern)) != NULL) {
131
+ size_t prefix = found - p;
132
+ memcpy(dst, p, prefix);
133
+ dst += prefix;
134
+ memcpy(dst, st->replacement, rep_len);
135
+ dst += rep_len;
136
+ p = found + pat_len;
137
+ }
138
+ strcpy(dst, p);
139
+ tf_batch_set_string(ob, r, ci, buf);
140
+ free(buf);
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ *out = ob;
147
+ return TF_OK;
148
+ }
149
+
150
+ static int replace_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
151
+ (void)self; (void)side; *out = NULL; return TF_OK;
152
+ }
153
+
154
+ static void replace_destroy(tf_step *self) {
155
+ replace_state *st = self->state;
156
+ if (st) {
157
+ if (st->use_regex) regfree(&st->compiled);
158
+ free(st->column);
159
+ free(st->pattern);
160
+ free(st->replacement);
161
+ free(st);
162
+ }
163
+ free(self);
164
+ }
165
+
166
+ tf_step *tf_replace_create(const cJSON *args) {
167
+ if (!args) return NULL;
168
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
169
+ cJSON *pat_j = cJSON_GetObjectItemCaseSensitive(args, "pattern");
170
+ cJSON *rep_j = cJSON_GetObjectItemCaseSensitive(args, "replacement");
171
+ if (!cJSON_IsString(col_j) || !cJSON_IsString(pat_j) || !cJSON_IsString(rep_j))
172
+ return NULL;
173
+
174
+ replace_state *st = calloc(1, sizeof(replace_state));
175
+ if (!st) return NULL;
176
+ st->column = strdup(col_j->valuestring);
177
+ st->pattern = strdup(pat_j->valuestring);
178
+ st->replacement = strdup(rep_j->valuestring);
179
+
180
+ cJSON *regex_j = cJSON_GetObjectItemCaseSensitive(args, "regex");
181
+ st->use_regex = (cJSON_IsBool(regex_j) && cJSON_IsTrue(regex_j)) ? 1 : 0;
182
+
183
+ if (st->use_regex) {
184
+ int rc = regcomp(&st->compiled, st->pattern, REG_EXTENDED);
185
+ if (rc != 0) {
186
+ free(st->column); free(st->pattern); free(st->replacement); free(st);
187
+ return NULL;
188
+ }
189
+ }
190
+
191
+ tf_step *step = malloc(sizeof(tf_step));
192
+ if (!step) {
193
+ if (st->use_regex) regfree(&st->compiled);
194
+ free(st->column); free(st->pattern); free(st->replacement); free(st);
195
+ return NULL;
196
+ }
197
+ step->process = replace_process;
198
+ step->flush = replace_flush;
199
+ step->destroy = replace_destroy;
200
+ step->state = st;
201
+ return step;
202
+ }
@@ -0,0 +1,101 @@
1
+ /*
2
+ * op_sample.c — Reservoir sampling (Algorithm R). Bounded memory.
3
+ *
4
+ * Config: {"n": 100}
5
+ */
6
+
7
+ #include "internal.h"
8
+ #include "cJSON.h"
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+ #include <time.h>
12
+
13
+ typedef struct {
14
+ size_t n;
15
+ tf_batch *buf;
16
+ size_t seen; /* total rows seen */
17
+ int has_schema;
18
+ unsigned seed;
19
+ } sample_state;
20
+
21
+ static int sample_process(tf_step *self, tf_batch *in, tf_batch **out,
22
+ tf_side_channels *side) {
23
+ (void)side;
24
+ sample_state *st = self->state;
25
+ *out = NULL;
26
+
27
+ if (!st->has_schema) {
28
+ st->buf = tf_batch_create(in->n_cols, st->n);
29
+ if (!st->buf) return TF_ERROR;
30
+ for (size_t c = 0; c < in->n_cols; c++)
31
+ tf_batch_set_schema(st->buf, c, in->col_names[c], in->col_types[c]);
32
+ st->has_schema = 1;
33
+ }
34
+
35
+ for (size_t r = 0; r < in->n_rows; r++) {
36
+ if (st->seen < st->n) {
37
+ /* Fill reservoir */
38
+ tf_batch_copy_row(st->buf, st->seen, in, r);
39
+ st->buf->n_rows = st->seen + 1;
40
+ } else {
41
+ /* Replace with probability n/seen */
42
+ size_t j = (size_t)(rand_r(&st->seed) % (st->seen + 1));
43
+ if (j < st->n) {
44
+ tf_batch_copy_row(st->buf, j, in, r);
45
+ }
46
+ }
47
+ st->seen++;
48
+ }
49
+
50
+ return TF_OK;
51
+ }
52
+
53
+ static int sample_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
54
+ (void)side;
55
+ sample_state *st = self->state;
56
+ *out = NULL;
57
+
58
+ if (!st->buf || st->buf->n_rows == 0) return TF_OK;
59
+
60
+ /* Copy buffer to output */
61
+ size_t n = st->buf->n_rows;
62
+ tf_batch *ob = tf_batch_create(st->buf->n_cols, n);
63
+ if (!ob) return TF_ERROR;
64
+ for (size_t c = 0; c < st->buf->n_cols; c++)
65
+ tf_batch_set_schema(ob, c, st->buf->col_names[c], st->buf->col_types[c]);
66
+ for (size_t i = 0; i < n; i++) {
67
+ tf_batch_copy_row(ob, i, st->buf, i);
68
+ ob->n_rows = i + 1;
69
+ }
70
+
71
+ *out = ob;
72
+ return TF_OK;
73
+ }
74
+
75
+ static void sample_destroy(tf_step *self) {
76
+ sample_state *st = self->state;
77
+ if (st) {
78
+ if (st->buf) tf_batch_free(st->buf);
79
+ free(st);
80
+ }
81
+ free(self);
82
+ }
83
+
84
+ tf_step *tf_sample_create(const cJSON *args) {
85
+ if (!args) return NULL;
86
+ cJSON *n_json = cJSON_GetObjectItemCaseSensitive(args, "n");
87
+ if (!cJSON_IsNumber(n_json) || n_json->valueint <= 0) return NULL;
88
+
89
+ sample_state *st = calloc(1, sizeof(sample_state));
90
+ if (!st) return NULL;
91
+ st->n = (size_t)n_json->valueint;
92
+ st->seed = (unsigned)time(NULL);
93
+
94
+ tf_step *step = malloc(sizeof(tf_step));
95
+ if (!step) { free(st); return NULL; }
96
+ step->process = sample_process;
97
+ step->flush = sample_flush;
98
+ step->destroy = sample_destroy;
99
+ step->state = st;
100
+ return step;
101
+ }
@@ -0,0 +1,140 @@
1
+ /*
2
+ * op_select.c — Select and reorder columns.
3
+ *
4
+ * Config: {"columns": ["name", "age"]}
5
+ * Creates output batch with only the specified columns in the given order.
6
+ */
7
+
8
+ #include "internal.h"
9
+ #include "cJSON.h"
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+ #include <stdio.h>
13
+
14
+ typedef struct {
15
+ char **col_names;
16
+ size_t n_cols;
17
+ } select_state;
18
+
19
+ static int select_process(tf_step *self, tf_batch *in, tf_batch **out,
20
+ tf_side_channels *side) {
21
+ select_state *st = self->state;
22
+ *out = NULL;
23
+
24
+ /* Resolve column indices */
25
+ int *indices = malloc(st->n_cols * sizeof(int));
26
+ if (!indices) return TF_ERROR;
27
+
28
+ for (size_t i = 0; i < st->n_cols; i++) {
29
+ indices[i] = tf_batch_col_index(in, st->col_names[i]);
30
+ if (indices[i] < 0 && side && side->errors) {
31
+ char buf[128];
32
+ snprintf(buf, sizeof(buf),
33
+ "{\"op\":\"select\",\"error\":\"column '%s' not found\"}\n",
34
+ st->col_names[i]);
35
+ tf_buffer_write_str(side->errors, buf);
36
+ }
37
+ }
38
+
39
+ /* Create output batch */
40
+ tf_batch *ob = tf_batch_create(st->n_cols, in->n_rows);
41
+ if (!ob) { free(indices); return TF_ERROR; }
42
+
43
+ for (size_t i = 0; i < st->n_cols; i++) {
44
+ if (indices[i] >= 0) {
45
+ tf_batch_set_schema(ob, i, st->col_names[i], in->col_types[indices[i]]);
46
+ } else {
47
+ tf_batch_set_schema(ob, i, st->col_names[i], TF_TYPE_NULL);
48
+ }
49
+ }
50
+
51
+ /* Copy rows */
52
+ for (size_t r = 0; r < in->n_rows; r++) {
53
+ tf_batch_ensure_capacity(ob, r + 1);
54
+ for (size_t i = 0; i < st->n_cols; i++) {
55
+ int ci = indices[i];
56
+ if (ci < 0 || tf_batch_is_null(in, r, ci)) {
57
+ tf_batch_set_null(ob, r, i);
58
+ continue;
59
+ }
60
+ switch (in->col_types[ci]) {
61
+ case TF_TYPE_BOOL:
62
+ tf_batch_set_bool(ob, r, i, tf_batch_get_bool(in, r, ci));
63
+ break;
64
+ case TF_TYPE_INT64:
65
+ tf_batch_set_int64(ob, r, i, tf_batch_get_int64(in, r, ci));
66
+ break;
67
+ case TF_TYPE_FLOAT64:
68
+ tf_batch_set_float64(ob, r, i, tf_batch_get_float64(in, r, ci));
69
+ break;
70
+ case TF_TYPE_STRING:
71
+ tf_batch_set_string(ob, r, i, tf_batch_get_string(in, r, ci));
72
+ break;
73
+ case TF_TYPE_DATE:
74
+ tf_batch_set_date(ob, r, i, tf_batch_get_date(in, r, ci));
75
+ break;
76
+ case TF_TYPE_TIMESTAMP:
77
+ tf_batch_set_timestamp(ob, r, i, tf_batch_get_timestamp(in, r, ci));
78
+ break;
79
+ default:
80
+ tf_batch_set_null(ob, r, i);
81
+ break;
82
+ }
83
+ }
84
+ ob->n_rows = r + 1;
85
+ }
86
+
87
+ free(indices);
88
+ *out = ob;
89
+ return TF_OK;
90
+ }
91
+
92
+ static int select_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
93
+ (void)self; (void)side;
94
+ *out = NULL;
95
+ return TF_OK;
96
+ }
97
+
98
+ static void select_destroy(tf_step *self) {
99
+ select_state *st = self->state;
100
+ if (st) {
101
+ for (size_t i = 0; i < st->n_cols; i++) free(st->col_names[i]);
102
+ free(st->col_names);
103
+ free(st);
104
+ }
105
+ free(self);
106
+ }
107
+
108
+ tf_step *tf_select_create(const cJSON *args) {
109
+ if (!args) return NULL;
110
+ cJSON *cols = cJSON_GetObjectItemCaseSensitive(args, "columns");
111
+ if (!cJSON_IsArray(cols)) return NULL;
112
+
113
+ int n = cJSON_GetArraySize(cols);
114
+ if (n <= 0) return NULL;
115
+
116
+ select_state *st = calloc(1, sizeof(select_state));
117
+ if (!st) return NULL;
118
+ st->n_cols = (size_t)n;
119
+ st->col_names = malloc(n * sizeof(char *));
120
+ if (!st->col_names) { free(st); return NULL; }
121
+
122
+ for (int i = 0; i < n; i++) {
123
+ cJSON *item = cJSON_GetArrayItem(cols, i);
124
+ if (!cJSON_IsString(item)) {
125
+ for (int j = 0; j < i; j++) free(st->col_names[j]);
126
+ free(st->col_names);
127
+ free(st);
128
+ return NULL;
129
+ }
130
+ st->col_names[i] = strdup(item->valuestring);
131
+ }
132
+
133
+ tf_step *step = malloc(sizeof(tf_step));
134
+ if (!step) { select_destroy(&(tf_step){.state = st}); return NULL; }
135
+ step->process = select_process;
136
+ step->flush = select_flush;
137
+ step->destroy = select_destroy;
138
+ step->state = st;
139
+ return step;
140
+ }