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,129 @@
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
+ if (!name) return "";
21
+ for (size_t i = 0; i < st->n_mappings; i++) {
22
+ if (strcmp(st->old_names[i], name) == 0)
23
+ return st->new_names[i];
24
+ }
25
+ return name; /* unchanged */
26
+ }
27
+
28
+ static int rename_process(tf_step *self, tf_batch *in, tf_batch **out,
29
+ tf_side_channels *side) {
30
+ (void)side;
31
+ rename_state *st = self->state;
32
+ *out = NULL;
33
+
34
+ /* Create output batch with renamed columns */
35
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
36
+ if (!ob) return TF_ERROR;
37
+
38
+ for (size_t i = 0; i < in->n_cols; i++) {
39
+ const char *new_name = find_rename(st, in->col_names[i]);
40
+ if (tf_batch_set_schema(ob, i, new_name, in->col_types[i]) != TF_OK) {
41
+ tf_batch_free(ob);
42
+ return TF_ERROR;
43
+ }
44
+ }
45
+
46
+ size_t *cols = tf_mallocarray_checked(in->n_cols ? in->n_cols : 1, sizeof(size_t));
47
+ if (!cols) {
48
+ tf_batch_free(ob);
49
+ return TF_ERROR;
50
+ }
51
+ for (size_t i = 0; i < in->n_cols; i++) cols[i] = i;
52
+ int rc = tf_batch_copy_selected_columns(ob, in, cols, in->n_cols);
53
+ free(cols);
54
+ if (rc != TF_OK) {
55
+ tf_batch_free(ob);
56
+ return TF_ERROR;
57
+ }
58
+
59
+ *out = ob;
60
+ return TF_OK;
61
+ }
62
+
63
+ static int rename_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
64
+ (void)self; (void)side;
65
+ *out = NULL;
66
+ return TF_OK;
67
+ }
68
+
69
+ static void rename_state_free(rename_state *st) {
70
+ if (!st) return;
71
+ for (size_t i = 0; i < st->n_mappings; i++) {
72
+ if (st->old_names) free(st->old_names[i]);
73
+ if (st->new_names) free(st->new_names[i]);
74
+ }
75
+ free(st->old_names);
76
+ free(st->new_names);
77
+ free(st);
78
+ }
79
+
80
+ static void rename_destroy(tf_step *self) {
81
+ rename_state_free(self ? self->state : NULL);
82
+ free(self);
83
+ }
84
+
85
+ tf_step *tf_rename_create(const cJSON *args) {
86
+ if (!args) return NULL;
87
+ cJSON *mapping = cJSON_GetObjectItemCaseSensitive(args, "mapping");
88
+ if (!mapping || !cJSON_IsObject(mapping)) return NULL;
89
+
90
+ int n = cJSON_GetArraySize(mapping);
91
+ if (n <= 0) return NULL;
92
+
93
+ rename_state *st = calloc(1, sizeof(rename_state));
94
+ if (!st) return NULL;
95
+ st->n_mappings = (size_t)n;
96
+ st->old_names = calloc((size_t)n, sizeof(char *));
97
+ st->new_names = calloc((size_t)n, sizeof(char *));
98
+ if (!st->old_names || !st->new_names) {
99
+ free(st->old_names);
100
+ free(st->new_names);
101
+ free(st);
102
+ return NULL;
103
+ }
104
+
105
+ int i = 0;
106
+ cJSON *item;
107
+ cJSON_ArrayForEach(item, mapping) {
108
+ const char *old_name = item->string ? item->string : "";
109
+ const char *new_name = cJSON_IsString(item) ? item->valuestring : old_name;
110
+ st->old_names[i] = strdup(old_name);
111
+ st->new_names[i] = strdup(new_name ? new_name : "");
112
+ if (!st->old_names[i] || !st->new_names[i]) {
113
+ rename_state_free(st);
114
+ return NULL;
115
+ }
116
+ i++;
117
+ }
118
+
119
+ tf_step *step = calloc(1, sizeof(tf_step));
120
+ if (!step) {
121
+ rename_state_free(st);
122
+ return NULL;
123
+ }
124
+ step->process = rename_process;
125
+ step->flush = rename_flush;
126
+ step->destroy = rename_destroy;
127
+ step->state = st;
128
+ return step;
129
+ }
@@ -0,0 +1,354 @@
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
+ size_t row_index;
21
+ size_t audit_limit;
22
+ size_t audit_emitted;
23
+ int audit;
24
+ tf_audit_options audit_opts;
25
+ } replace_state;
26
+
27
+ static int replace_ensure_buffer(char **buf, size_t *cap, size_t need) {
28
+ if (!buf || !cap) return TF_ERROR;
29
+ if (need > 0) {
30
+ size_t payload_len = need - 1; /* reserve space for the trailing NUL */
31
+ if (tf_check_byte_limit(payload_len, TF_MAX_CELL_BYTES, "replace", "output cell") != TF_OK) {
32
+ return TF_ERROR;
33
+ }
34
+ }
35
+ if (need <= *cap) return TF_OK;
36
+ size_t new_cap = 0;
37
+ if (tf_size_grow_pow2(*cap, need, 64, &new_cap) != TF_OK) return TF_ERROR;
38
+ size_t max_cap = 0;
39
+ if (tf_size_add(TF_MAX_CELL_BYTES, 1, &max_cap) != TF_OK) return TF_ERROR;
40
+ if (new_cap > max_cap) new_cap = need;
41
+ char *nb = tf_reallocarray_checked(*buf, new_cap, sizeof(char));
42
+ if (!nb) return TF_ERROR;
43
+ *buf = nb;
44
+ *cap = new_cap;
45
+ return TF_OK;
46
+ }
47
+
48
+ static int replace_append_bytes(char **buf, size_t *cap, size_t *len,
49
+ const char *src, size_t n) {
50
+ if (!buf || !cap || !len || (!src && n > 0)) return TF_ERROR;
51
+ size_t end = 0;
52
+ size_t need = 0;
53
+ if (tf_size_add(*len, n, &end) != TF_OK ||
54
+ tf_size_add(end, 1, &need) != TF_OK) {
55
+ return TF_ERROR;
56
+ }
57
+ if (replace_ensure_buffer(buf, cap, need) != TF_OK) return TF_ERROR;
58
+ if (n > 0) memcpy(*buf + *len, src, n);
59
+ *len = end;
60
+ (*buf)[*len] = '\0';
61
+ return TF_OK;
62
+ }
63
+
64
+ static int replace_append_char(char **buf, size_t *cap, size_t *len, char ch) {
65
+ return replace_append_bytes(buf, cap, len, &ch, 1);
66
+ }
67
+
68
+ static int emit_replace_audit(replace_state *st, const tf_batch *b, size_t row, size_t col,
69
+ size_t row_no, const char *before, const char *after,
70
+ tf_side_channels *side) {
71
+ if (!st->audit || st->audit_emitted >= st->audit_limit || !side || !side->stats) return TF_OK;
72
+ cJSON *obj = cJSON_CreateObject();
73
+ if (!obj) return TF_ERROR;
74
+ int rc = TF_ERROR;
75
+ if (tf_json_add_string(obj, "type", "audit") != TF_OK ||
76
+ tf_json_add_string(obj, "op", "replace") != TF_OK ||
77
+ tf_json_add_string(obj, "event", "value_changed") != TF_OK ||
78
+ tf_json_add_string(obj, "reason", "replace_match") != TF_OK ||
79
+ tf_json_add_string(obj, "channel", "audit") != TF_OK) {
80
+ goto done;
81
+ }
82
+ const char *column_name = b->col_names[col] ? b->col_names[col] : "";
83
+ if (tf_json_add_string(obj, "column", column_name) != TF_OK) goto done;
84
+ if (tf_json_add_audit_string(obj, "pattern", &st->audit_opts, column_name, st->pattern) != TF_OK ||
85
+ tf_json_add_audit_string(obj, "replacement", &st->audit_opts, column_name, st->replacement) != TF_OK ||
86
+ tf_json_add_bool(obj, "regex", st->use_regex ? 1 : 0) != TF_OK ||
87
+ tf_json_add_number(obj, "row", (double)row_no) != TF_OK) {
88
+ goto done;
89
+ }
90
+ if (tf_json_add_audit_string(obj, "before", &st->audit_opts, column_name, before) != TF_OK ||
91
+ tf_json_add_audit_string(obj, "after", &st->audit_opts, column_name, after) != TF_OK) {
92
+ goto done;
93
+ }
94
+ cJSON *row_obj = tf_audit_row_to_json(b, row, &st->audit_opts);
95
+ if (row_obj) {
96
+ if (tf_json_add_item(obj, "data", row_obj) != TF_OK) goto done;
97
+ } else if (st->audit_opts.include_row) {
98
+ goto done;
99
+ }
100
+ rc = tf_buffer_write_json_line(side->stats, obj);
101
+ done:
102
+ cJSON_Delete(obj);
103
+ if (rc == TF_OK) st->audit_emitted++;
104
+ return rc;
105
+ }
106
+
107
+ static int replace_build_regex(replace_state *st, const char *val,
108
+ char **out_buf, int *out_replaced) {
109
+ *out_buf = NULL;
110
+ *out_replaced = 0;
111
+ size_t buf_cap = 0;
112
+ size_t buf_len = 0;
113
+ char *buf = NULL;
114
+ const char *p = val;
115
+ regmatch_t match;
116
+ while (regexec(&st->compiled, p, 1, &match, 0) == 0 && match.rm_so >= 0) {
117
+ *out_replaced = 1;
118
+ size_t prefix = (size_t)match.rm_so;
119
+ size_t match_len = (size_t)(match.rm_eo - match.rm_so);
120
+ if (replace_append_bytes(&buf, &buf_cap, &buf_len, p, prefix) != TF_OK) {
121
+ free(buf);
122
+ return TF_ERROR;
123
+ }
124
+ for (const char *rp = st->replacement; *rp; rp++) {
125
+ if (*rp == '&') {
126
+ if (replace_append_bytes(&buf, &buf_cap, &buf_len,
127
+ p + match.rm_so, match_len) != TF_OK) {
128
+ free(buf);
129
+ return TF_ERROR;
130
+ }
131
+ } else if (replace_append_char(&buf, &buf_cap, &buf_len, *rp) != TF_OK) {
132
+ free(buf);
133
+ return TF_ERROR;
134
+ }
135
+ }
136
+ p += match.rm_eo;
137
+ if (match_len == 0) {
138
+ if (*p) {
139
+ if (replace_append_char(&buf, &buf_cap, &buf_len, *p++) != TF_OK) {
140
+ free(buf);
141
+ return TF_ERROR;
142
+ }
143
+ } else {
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ if (*out_replaced) {
149
+ if (replace_append_bytes(&buf, &buf_cap, &buf_len, p, strlen(p)) != TF_OK) {
150
+ free(buf);
151
+ return TF_ERROR;
152
+ }
153
+ *out_buf = buf;
154
+ }
155
+ return TF_OK;
156
+ }
157
+
158
+ static int replace_build_literal(replace_state *st, const char *val,
159
+ char **out_buf, int *out_replaced) {
160
+ *out_buf = NULL;
161
+ *out_replaced = 0;
162
+ size_t pat_len = strlen(st->pattern);
163
+ if (pat_len == 0 || !strstr(val, st->pattern)) return TF_OK;
164
+
165
+ size_t rep_len = strlen(st->replacement);
166
+ size_t buf_cap = 0;
167
+ size_t buf_len = 0;
168
+ char *buf = NULL;
169
+ const char *p = val;
170
+ const char *found;
171
+ while ((found = strstr(p, st->pattern)) != NULL) {
172
+ size_t prefix = (size_t)(found - p);
173
+ if (replace_append_bytes(&buf, &buf_cap, &buf_len, p, prefix) != TF_OK ||
174
+ replace_append_bytes(&buf, &buf_cap, &buf_len, st->replacement, rep_len) != TF_OK) {
175
+ free(buf);
176
+ return TF_ERROR;
177
+ }
178
+ p = found + pat_len;
179
+ }
180
+ if (replace_append_bytes(&buf, &buf_cap, &buf_len, p, strlen(p)) != TF_OK) {
181
+ free(buf);
182
+ return TF_ERROR;
183
+ }
184
+ *out_replaced = 1;
185
+ *out_buf = buf;
186
+ return TF_OK;
187
+ }
188
+
189
+ static int replace_process(tf_step *self, tf_batch *in, tf_batch **out,
190
+ tf_side_channels *side) {
191
+ replace_state *st = self->state;
192
+ *out = NULL;
193
+ size_t row_base = st->row_index;
194
+
195
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
196
+ if (!ob) return TF_ERROR;
197
+ if (tf_batch_clone_schema(ob, in) != TF_OK) {
198
+ tf_batch_free(ob);
199
+ return TF_ERROR;
200
+ }
201
+
202
+ int ci = tf_batch_col_index(in, st->column);
203
+ int can_replace = ci >= 0 && in->col_types[ci] == TF_TYPE_STRING;
204
+ for (size_t r = 0; r < in->n_rows; r++) {
205
+ char *buf = NULL;
206
+ char *before_copy = NULL;
207
+ int replaced = 0;
208
+ const char *val = NULL;
209
+ if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
210
+ tf_batch_free(ob);
211
+ return TF_ERROR;
212
+ }
213
+ if (can_replace && !tf_batch_is_null(in, r, (size_t)ci)) {
214
+ val = tf_batch_get_string(in, r, (size_t)ci);
215
+ if (val) {
216
+ int build_rc = st->use_regex
217
+ ? replace_build_regex(st, val, &buf, &replaced)
218
+ : replace_build_literal(st, val, &buf, &replaced);
219
+ if (build_rc != TF_OK) {
220
+ tf_batch_free(ob);
221
+ return TF_ERROR;
222
+ }
223
+ if (replaced) {
224
+ if (st->audit && st->audit_emitted < st->audit_limit) {
225
+ before_copy = strdup(val);
226
+ if (!before_copy) {
227
+ free(buf);
228
+ tf_batch_free(ob);
229
+ return TF_ERROR;
230
+ }
231
+ }
232
+ if (tf_batch_set_string(ob, r, (size_t)ci, buf) != TF_OK) {
233
+ free(before_copy);
234
+ free(buf);
235
+ tf_batch_free(ob);
236
+ return TF_ERROR;
237
+ }
238
+ }
239
+ }
240
+ }
241
+ if (tf_batch_expose_row(ob, r) != TF_OK) {
242
+ free(before_copy);
243
+ free(buf);
244
+ tf_batch_free(ob);
245
+ return TF_ERROR;
246
+ }
247
+ if (replaced) {
248
+ size_t row_no = 0;
249
+ if (tf_size_add(row_base, r, &row_no) != TF_OK ||
250
+ tf_size_add(row_no, 1, &row_no) != TF_OK) {
251
+ free(before_copy);
252
+ free(buf);
253
+ tf_batch_free(ob);
254
+ return TF_ERROR;
255
+ }
256
+ const char *after = tf_batch_get_string(ob, r, (size_t)ci);
257
+ if (emit_replace_audit(st, ob, r, (size_t)ci, row_no,
258
+ before_copy ? before_copy : val, after, side) != TF_OK) {
259
+ free(before_copy);
260
+ free(buf);
261
+ tf_batch_free(ob);
262
+ return TF_ERROR;
263
+ }
264
+ }
265
+ free(before_copy);
266
+ free(buf);
267
+ }
268
+
269
+ if (tf_size_add(st->row_index, in->n_rows, &st->row_index) != TF_OK) {
270
+ tf_batch_free(ob);
271
+ return TF_ERROR;
272
+ }
273
+ *out = ob;
274
+ return TF_OK;
275
+ }
276
+
277
+ static int replace_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
278
+ (void)self; (void)side; *out = NULL; return TF_OK;
279
+ }
280
+
281
+ static void replace_state_free(replace_state *st) {
282
+ if (!st) return;
283
+ if (st->use_regex) regfree(&st->compiled);
284
+ tf_audit_options_free(&st->audit_opts);
285
+ free(st->column);
286
+ free(st->pattern);
287
+ free(st->replacement);
288
+ free(st);
289
+ }
290
+
291
+ static void replace_destroy(tf_step *self) {
292
+ if (self) replace_state_free(self->state);
293
+ free(self);
294
+ }
295
+
296
+ tf_step *tf_replace_create(const cJSON *args) {
297
+ if (!args) return NULL;
298
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
299
+ cJSON *pat_j = cJSON_GetObjectItemCaseSensitive(args, "pattern");
300
+ cJSON *rep_j = cJSON_GetObjectItemCaseSensitive(args, "replacement");
301
+ if (!cJSON_IsString(col_j) || !cJSON_IsString(pat_j) || !cJSON_IsString(rep_j))
302
+ return NULL;
303
+
304
+ replace_state *st = calloc(1, sizeof(replace_state));
305
+ if (!st) return NULL;
306
+ tf_audit_options_init(&st->audit_opts, 1);
307
+ st->column = strdup(col_j->valuestring);
308
+ st->pattern = strdup(pat_j->valuestring);
309
+ st->replacement = strdup(rep_j->valuestring);
310
+ st->audit_limit = 1000;
311
+ if (!st->column || !st->pattern || !st->replacement) { replace_state_free(st); return NULL; }
312
+
313
+ cJSON *regex_j = cJSON_GetObjectItemCaseSensitive(args, "regex");
314
+ st->use_regex = (cJSON_IsBool(regex_j) && cJSON_IsTrue(regex_j)) ? 1 : 0;
315
+
316
+ cJSON *audit_j = cJSON_GetObjectItemCaseSensitive(args, "audit");
317
+ st->audit = cJSON_IsTrue(audit_j) ? 1 : 0;
318
+ cJSON *audit_limit_j = cJSON_GetObjectItemCaseSensitive(args, "audit_limit");
319
+ if (!audit_limit_j) audit_limit_j = cJSON_GetObjectItemCaseSensitive(args, "auditLimit");
320
+ if (audit_limit_j) {
321
+ size_t parsed_limit = 0;
322
+ if (tf_json_get_size_arg_any(args, "audit_limit", "auditLimit",
323
+ 1, TF_MAX_AUDIT_RECORDS,
324
+ &parsed_limit, "replace") < 0) {
325
+ replace_state_free(st);
326
+ return NULL;
327
+ }
328
+ st->audit_limit = parsed_limit;
329
+ }
330
+
331
+ if (tf_audit_options_parse(&st->audit_opts, args, "replace") != TF_OK) {
332
+ replace_state_free(st);
333
+ return NULL;
334
+ }
335
+
336
+ if (st->use_regex) {
337
+ int rc = regcomp(&st->compiled, st->pattern, REG_EXTENDED);
338
+ if (rc != 0) {
339
+ replace_state_free(st);
340
+ return NULL;
341
+ }
342
+ }
343
+
344
+ tf_step *step = calloc(1, sizeof(tf_step));
345
+ if (!step) {
346
+ replace_state_free(st);
347
+ return NULL;
348
+ }
349
+ step->process = replace_process;
350
+ step->flush = replace_flush;
351
+ step->destroy = replace_destroy;
352
+ step->state = st;
353
+ return step;
354
+ }