tranfi 0.1.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.
- package/LICENSE +177 -0
- package/NOTICE +8 -0
- package/README.md +272 -40
- package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
- package/app/index.html +1 -1
- package/binding.gyp +55 -3
- package/csrc/arena.c +7 -5
- package/csrc/batch.c +818 -71
- package/csrc/buffer.c +84 -8
- package/csrc/cJSON.c +262 -19
- package/csrc/cJSON.h +17 -1
- package/csrc/codec_csv.c +1074 -181
- package/csrc/codec_jsonl.c +830 -118
- package/csrc/codec_table.c +108 -78
- package/csrc/codec_text.c +286 -68
- package/csrc/compiler.c +31 -3
- package/csrc/config.h +21 -0
- package/csrc/dsl.c +4722 -485
- package/csrc/expr.c +363 -55
- package/csrc/expr.h +2 -0
- package/csrc/internal.h +316 -27
- package/csrc/ir.c +65 -18
- package/csrc/ir.h +41 -0
- package/csrc/ir_schema.c +20 -5
- package/csrc/ir_serialize.c +68 -6
- package/csrc/ir_sql.c +796 -185
- package/csrc/ir_validate.c +462 -6
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +879 -30
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +171 -21
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +167 -32
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +168 -29
- package/csrc/op_cast.c +383 -55
- package/csrc/op_clip.c +30 -19
- package/csrc/op_date_trunc.c +208 -34
- package/csrc/op_datetime.c +259 -77
- package/csrc/op_derive.c +65 -97
- package/csrc/op_diff.c +146 -30
- package/csrc/op_ewma.c +149 -30
- package/csrc/op_explode.c +124 -26
- package/csrc/op_fill_down.c +125 -53
- package/csrc/op_fill_null.c +176 -31
- package/csrc/op_filter.c +89 -40
- package/csrc/op_frequency.c +571 -43
- package/csrc/op_grep.c +36 -18
- package/csrc/op_group_agg.c +1790 -119
- package/csrc/op_hash.c +48 -15
- package/csrc/op_head.c +21 -86
- package/csrc/op_interpolate.c +268 -62
- package/csrc/op_join.c +2700 -182
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +328 -53
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +141 -89
- package/csrc/op_normalize.c +363 -79
- package/csrc/op_onehot.c +345 -73
- package/csrc/op_pivot.c +1546 -162
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +2062 -166
- package/csrc/op_rename.c +41 -50
- package/csrc/op_replace.c +270 -118
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +80 -23
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +265 -65
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +30 -87
- package/csrc/op_sort.c +670 -124
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +65 -28
- package/csrc/op_split_data.c +41 -9
- package/csrc/op_stack.c +178 -222
- package/csrc/op_stats.c +206 -110
- package/csrc/op_step.c +217 -55
- package/csrc/op_tail.c +21 -12
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +260 -53
- package/csrc/op_trim.c +48 -19
- package/csrc/op_unique.c +1193 -150
- package/csrc/op_unpivot.c +100 -66
- package/csrc/op_validate.c +601 -24
- package/csrc/op_window.c +492 -51
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +872 -99
- package/csrc/recipes.c +3 -1
- package/csrc/report.c +73 -30
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +169 -1
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +70 -9
- package/napi_api.c +219 -11
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +27 -11
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +8 -11
- package/src/engines/duckdb.js +45 -12
- package/src/index.js +661 -42
- package/src/memory_policy.js +411 -0
- package/src/native.js +1 -5
- package/src/pipeline.js +454 -31
- package/src/recipe_json.js +80 -0
- package/src/server.js +10 -8
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +6 -4
- package/wasm/index.js +498 -10
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/csrc/plan.c +0 -206
package/csrc/op_rename.c
CHANGED
|
@@ -17,6 +17,7 @@ typedef struct {
|
|
|
17
17
|
} rename_state;
|
|
18
18
|
|
|
19
19
|
static const char *find_rename(rename_state *st, const char *name) {
|
|
20
|
+
if (!name) return "";
|
|
20
21
|
for (size_t i = 0; i < st->n_mappings; i++) {
|
|
21
22
|
if (strcmp(st->old_names[i], name) == 0)
|
|
22
23
|
return st->new_names[i];
|
|
@@ -28,6 +29,7 @@ static int rename_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
|
28
29
|
tf_side_channels *side) {
|
|
29
30
|
(void)side;
|
|
30
31
|
rename_state *st = self->state;
|
|
32
|
+
*out = NULL;
|
|
31
33
|
|
|
32
34
|
/* Create output batch with renamed columns */
|
|
33
35
|
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
@@ -35,42 +37,23 @@ static int rename_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
|
35
37
|
|
|
36
38
|
for (size_t i = 0; i < in->n_cols; i++) {
|
|
37
39
|
const char *new_name = find_rename(st, in->col_names[i]);
|
|
38
|
-
tf_batch_set_schema(ob, i, new_name, in->col_types[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
|
+
}
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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;
|
|
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;
|
|
74
57
|
}
|
|
75
58
|
|
|
76
59
|
*out = ob;
|
|
@@ -83,17 +66,19 @@ static int rename_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
|
83
66
|
return TF_OK;
|
|
84
67
|
}
|
|
85
68
|
|
|
86
|
-
static void
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
free(st->new_names[i]);
|
|
92
|
-
}
|
|
93
|
-
free(st->old_names);
|
|
94
|
-
free(st->new_names);
|
|
95
|
-
free(st);
|
|
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]);
|
|
96
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);
|
|
97
82
|
free(self);
|
|
98
83
|
}
|
|
99
84
|
|
|
@@ -108,8 +93,8 @@ tf_step *tf_rename_create(const cJSON *args) {
|
|
|
108
93
|
rename_state *st = calloc(1, sizeof(rename_state));
|
|
109
94
|
if (!st) return NULL;
|
|
110
95
|
st->n_mappings = (size_t)n;
|
|
111
|
-
st->old_names =
|
|
112
|
-
st->new_names =
|
|
96
|
+
st->old_names = calloc((size_t)n, sizeof(char *));
|
|
97
|
+
st->new_names = calloc((size_t)n, sizeof(char *));
|
|
113
98
|
if (!st->old_names || !st->new_names) {
|
|
114
99
|
free(st->old_names);
|
|
115
100
|
free(st->new_names);
|
|
@@ -120,14 +105,20 @@ tf_step *tf_rename_create(const cJSON *args) {
|
|
|
120
105
|
int i = 0;
|
|
121
106
|
cJSON *item;
|
|
122
107
|
cJSON_ArrayForEach(item, mapping) {
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
}
|
|
125
116
|
i++;
|
|
126
117
|
}
|
|
127
118
|
|
|
128
|
-
tf_step *step =
|
|
119
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
129
120
|
if (!step) {
|
|
130
|
-
|
|
121
|
+
rename_state_free(st);
|
|
131
122
|
return NULL;
|
|
132
123
|
}
|
|
133
124
|
step->process = rename_process;
|
package/csrc/op_replace.c
CHANGED
|
@@ -17,132 +17,259 @@ typedef struct {
|
|
|
17
17
|
char *replacement;
|
|
18
18
|
int use_regex;
|
|
19
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;
|
|
20
25
|
} replace_state;
|
|
21
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
|
+
|
|
22
189
|
static int replace_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
23
190
|
tf_side_channels *side) {
|
|
24
|
-
(void)side;
|
|
25
191
|
replace_state *st = self->state;
|
|
26
192
|
*out = NULL;
|
|
193
|
+
size_t row_base = st->row_index;
|
|
27
194
|
|
|
28
195
|
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
29
196
|
if (!ob) return TF_ERROR;
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
32
204
|
for (size_t r = 0; r < in->n_rows; r++) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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;
|
|
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;
|
|
78
230
|
}
|
|
79
231
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
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;
|
|
102
237
|
}
|
|
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
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);
|
|
108
252
|
free(buf);
|
|
253
|
+
tf_batch_free(ob);
|
|
254
|
+
return TF_ERROR;
|
|
109
255
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
}
|
|
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;
|
|
142
263
|
}
|
|
143
264
|
}
|
|
265
|
+
free(before_copy);
|
|
266
|
+
free(buf);
|
|
144
267
|
}
|
|
145
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
|
+
}
|
|
146
273
|
*out = ob;
|
|
147
274
|
return TF_OK;
|
|
148
275
|
}
|
|
@@ -151,15 +278,18 @@ static int replace_flush(tf_step *self, tf_batch **out, tf_side_channels *side)
|
|
|
151
278
|
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
152
279
|
}
|
|
153
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
|
+
|
|
154
291
|
static void replace_destroy(tf_step *self) {
|
|
155
|
-
|
|
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
|
-
}
|
|
292
|
+
if (self) replace_state_free(self->state);
|
|
163
293
|
free(self);
|
|
164
294
|
}
|
|
165
295
|
|
|
@@ -173,25 +303,47 @@ tf_step *tf_replace_create(const cJSON *args) {
|
|
|
173
303
|
|
|
174
304
|
replace_state *st = calloc(1, sizeof(replace_state));
|
|
175
305
|
if (!st) return NULL;
|
|
306
|
+
tf_audit_options_init(&st->audit_opts, 1);
|
|
176
307
|
st->column = strdup(col_j->valuestring);
|
|
177
308
|
st->pattern = strdup(pat_j->valuestring);
|
|
178
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; }
|
|
179
312
|
|
|
180
313
|
cJSON *regex_j = cJSON_GetObjectItemCaseSensitive(args, "regex");
|
|
181
314
|
st->use_regex = (cJSON_IsBool(regex_j) && cJSON_IsTrue(regex_j)) ? 1 : 0;
|
|
182
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
|
+
|
|
183
336
|
if (st->use_regex) {
|
|
184
337
|
int rc = regcomp(&st->compiled, st->pattern, REG_EXTENDED);
|
|
185
338
|
if (rc != 0) {
|
|
186
|
-
|
|
339
|
+
replace_state_free(st);
|
|
187
340
|
return NULL;
|
|
188
341
|
}
|
|
189
342
|
}
|
|
190
343
|
|
|
191
|
-
tf_step *step =
|
|
344
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
192
345
|
if (!step) {
|
|
193
|
-
|
|
194
|
-
free(st->column); free(st->pattern); free(st->replacement); free(st);
|
|
346
|
+
replace_state_free(st);
|
|
195
347
|
return NULL;
|
|
196
348
|
}
|
|
197
349
|
step->process = replace_process;
|