tranfi 0.1.2 → 0.2.1
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 +443 -51
- 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 +352 -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 +81 -41
- 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_onehot.c
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* op_onehot.c — One-hot encoding of a categorical column.
|
|
3
3
|
* Expands a single column into N binary (0/1) columns.
|
|
4
4
|
*
|
|
5
|
-
* Config: {"column": "city", "drop": false
|
|
5
|
+
* Config: {"column": "city", "drop": false,
|
|
6
|
+
* "categories": ["Paris", "London"],
|
|
7
|
+
* "max_categories": 1000,
|
|
8
|
+
* "max_state_bytes": 1048576,
|
|
9
|
+
* "unknown": "error" | "other" | "null"}
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
#include "internal.h"
|
|
@@ -11,19 +15,68 @@
|
|
|
11
15
|
#include <string.h>
|
|
12
16
|
#include <stdio.h>
|
|
13
17
|
|
|
18
|
+
typedef enum {
|
|
19
|
+
TF_CAT_UNKNOWN_ADD,
|
|
20
|
+
TF_CAT_UNKNOWN_ERROR,
|
|
21
|
+
TF_CAT_UNKNOWN_OTHER,
|
|
22
|
+
TF_CAT_UNKNOWN_NULL
|
|
23
|
+
} tf_cat_unknown_policy;
|
|
24
|
+
|
|
14
25
|
typedef struct {
|
|
15
26
|
char *value; /* category value string */
|
|
16
27
|
char *col_name; /* generated column name: "column_value" */
|
|
17
28
|
} onehot_category;
|
|
18
29
|
|
|
19
30
|
typedef struct {
|
|
20
|
-
char
|
|
21
|
-
int
|
|
22
|
-
|
|
23
|
-
size_t
|
|
24
|
-
|
|
31
|
+
char *column;
|
|
32
|
+
int drop; /* drop original column */
|
|
33
|
+
size_t max_categories; /* 0 = unlimited */
|
|
34
|
+
size_t max_state_bytes; /* 0 = unlimited */
|
|
35
|
+
int categories_declared;
|
|
36
|
+
tf_cat_unknown_policy unknown;
|
|
37
|
+
onehot_category *cats;
|
|
38
|
+
size_t n_cats;
|
|
39
|
+
size_t cap;
|
|
25
40
|
} onehot_state;
|
|
26
41
|
|
|
42
|
+
static const char *OTHER_CATEGORY = "__other__";
|
|
43
|
+
|
|
44
|
+
static size_t onehot_retained_state_bytes(const onehot_state *st);
|
|
45
|
+
|
|
46
|
+
static int onehot_write_error(tf_side_channels *side, const char *msg) {
|
|
47
|
+
return tf_side_write_error(side, msg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static int onehot_unknown_error(const onehot_state *st, const char *val,
|
|
51
|
+
tf_side_channels *side) {
|
|
52
|
+
char msg[256];
|
|
53
|
+
snprintf(msg, sizeof(msg),
|
|
54
|
+
"onehot: unknown category '%s' for column '%s'",
|
|
55
|
+
val ? val : "", st->column ? st->column : "");
|
|
56
|
+
return onehot_write_error(side, msg);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static int onehot_limit_error(const onehot_state *st, const char *val,
|
|
60
|
+
tf_side_channels *side) {
|
|
61
|
+
char msg[256];
|
|
62
|
+
snprintf(msg, sizeof(msg),
|
|
63
|
+
"onehot: max_categories=%zu exceeded while tracking category '%s'",
|
|
64
|
+
st->max_categories, val ? val : "");
|
|
65
|
+
return onehot_write_error(side, msg);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static int onehot_check_state_bytes(onehot_state *st, tf_side_channels *side) {
|
|
69
|
+
if (!st || st->max_state_bytes == 0) return TF_OK;
|
|
70
|
+
size_t retained = onehot_retained_state_bytes(st);
|
|
71
|
+
if (retained <= st->max_state_bytes) return TF_OK;
|
|
72
|
+
char msg[192];
|
|
73
|
+
snprintf(msg, sizeof(msg),
|
|
74
|
+
"onehot: max_state_bytes=%zu exceeded while tracking categories (%zu bytes retained)",
|
|
75
|
+
st->max_state_bytes, retained);
|
|
76
|
+
if (onehot_write_error(side, msg) != TF_OK) return TF_ERROR;
|
|
77
|
+
return TF_ERROR;
|
|
78
|
+
}
|
|
79
|
+
|
|
27
80
|
static const char *get_string_value(const tf_batch *b, size_t r, int ci, char *buf, size_t bufsz) {
|
|
28
81
|
if (tf_batch_is_null(b, r, ci)) return NULL;
|
|
29
82
|
switch (b->col_types[ci]) {
|
|
@@ -40,127 +93,272 @@ static const char *get_string_value(const tf_batch *b, size_t r, int ci, char *b
|
|
|
40
93
|
}
|
|
41
94
|
}
|
|
42
95
|
|
|
43
|
-
static int
|
|
96
|
+
static int find_category(const onehot_state *st, const char *val) {
|
|
44
97
|
for (size_t i = 0; i < st->n_cats; i++) {
|
|
45
|
-
if (strcmp(st->cats[i].value, val) == 0)
|
|
46
|
-
return (int)i;
|
|
98
|
+
if (strcmp(st->cats[i].value, val) == 0) return (int)i;
|
|
47
99
|
}
|
|
48
|
-
|
|
100
|
+
return -1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static int add_category(onehot_state *st, const char *val) {
|
|
104
|
+
int existing = find_category(st, val);
|
|
105
|
+
if (existing >= 0) return existing;
|
|
49
106
|
if (st->n_cats >= st->cap) {
|
|
50
|
-
size_t
|
|
51
|
-
|
|
107
|
+
size_t min_cap = 0, newcap = 0;
|
|
108
|
+
if (tf_size_add(st->n_cats, 1, &min_cap) != TF_OK ||
|
|
109
|
+
tf_size_grow_pow2(st->cap, min_cap, 16, &newcap) != TF_OK) {
|
|
110
|
+
return -1;
|
|
111
|
+
}
|
|
112
|
+
onehot_category *tmp = tf_reallocarray_checked(st->cats, newcap,
|
|
113
|
+
sizeof(onehot_category));
|
|
52
114
|
if (!tmp) return -1;
|
|
53
115
|
st->cats = tmp;
|
|
54
116
|
st->cap = newcap;
|
|
55
117
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
st->
|
|
118
|
+
char *value = strdup(val);
|
|
119
|
+
if (!value) return -1;
|
|
120
|
+
size_t column_len = 0, value_len = 0, name_len = 0, name_cap = 0;
|
|
121
|
+
if (tf_string_length_bounded(st->column, TF_MAX_COLUMN_NAME_BYTES,
|
|
122
|
+
&column_len, "onehot", "column") != TF_OK ||
|
|
123
|
+
tf_string_length_bounded(val, TF_MAX_COLUMN_NAME_BYTES,
|
|
124
|
+
&value_len, "onehot", "category value") != TF_OK ||
|
|
125
|
+
tf_size_add(column_len, 1, &name_len) != TF_OK ||
|
|
126
|
+
tf_size_add(name_len, value_len, &name_len) != TF_OK ||
|
|
127
|
+
tf_check_byte_limit(name_len, TF_MAX_COLUMN_NAME_BYTES,
|
|
128
|
+
"onehot", "generated column name") != TF_OK ||
|
|
129
|
+
tf_size_add(name_len, 1, &name_cap) != TF_OK) {
|
|
130
|
+
free(value);
|
|
131
|
+
return -1;
|
|
132
|
+
}
|
|
133
|
+
char *col_name = tf_mallocarray_checked(name_cap, sizeof(char));
|
|
134
|
+
if (!col_name) {
|
|
135
|
+
free(value);
|
|
136
|
+
return -1;
|
|
137
|
+
}
|
|
138
|
+
memcpy(col_name, st->column, column_len);
|
|
139
|
+
col_name[column_len] = '_';
|
|
140
|
+
memcpy(col_name + column_len + 1, val, value_len);
|
|
141
|
+
col_name[name_len] = '\0';
|
|
142
|
+
st->cats[st->n_cats].value = value;
|
|
143
|
+
st->cats[st->n_cats].col_name = col_name;
|
|
60
144
|
st->n_cats++;
|
|
61
145
|
return (int)(st->n_cats - 1);
|
|
62
146
|
}
|
|
63
147
|
|
|
148
|
+
static int resolve_other(onehot_state *st, int *idx, tf_side_channels *side) {
|
|
149
|
+
int other = find_category(st, OTHER_CATEGORY);
|
|
150
|
+
if (other >= 0) { *idx = other; return 0; }
|
|
151
|
+
if (st->max_categories > 0 && st->n_cats >= st->max_categories) {
|
|
152
|
+
if (onehot_limit_error(st, OTHER_CATEGORY, side) != TF_OK) return -1;
|
|
153
|
+
return -1;
|
|
154
|
+
}
|
|
155
|
+
other = add_category(st, OTHER_CATEGORY);
|
|
156
|
+
if (other < 0) return -1;
|
|
157
|
+
if (onehot_check_state_bytes(st, side) != TF_OK) return -1;
|
|
158
|
+
*idx = other;
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static int resolve_unknown(onehot_state *st, const char *val, int *idx,
|
|
163
|
+
tf_side_channels *side) {
|
|
164
|
+
switch (st->unknown) {
|
|
165
|
+
case TF_CAT_UNKNOWN_NULL:
|
|
166
|
+
*idx = -1;
|
|
167
|
+
return 0;
|
|
168
|
+
case TF_CAT_UNKNOWN_OTHER:
|
|
169
|
+
return resolve_other(st, idx, side);
|
|
170
|
+
case TF_CAT_UNKNOWN_ERROR:
|
|
171
|
+
if (onehot_unknown_error(st, val, side) != TF_OK) return -1;
|
|
172
|
+
return -1;
|
|
173
|
+
case TF_CAT_UNKNOWN_ADD:
|
|
174
|
+
default:
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static int resolve_category(onehot_state *st, const char *val, int *idx,
|
|
181
|
+
tf_side_channels *side) {
|
|
182
|
+
*idx = -1;
|
|
183
|
+
if (!val) return 0;
|
|
184
|
+
|
|
185
|
+
int existing = find_category(st, val);
|
|
186
|
+
if (existing >= 0) { *idx = existing; return 0; }
|
|
187
|
+
|
|
188
|
+
if (st->categories_declared) {
|
|
189
|
+
int rc = resolve_unknown(st, val, idx, side);
|
|
190
|
+
return rc == 0 ? 0 : -1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (st->max_categories > 0 && st->unknown == TF_CAT_UNKNOWN_OTHER) {
|
|
194
|
+
int other = find_category(st, OTHER_CATEGORY);
|
|
195
|
+
size_t reserve = other >= 0 ? 0 : 1;
|
|
196
|
+
if (st->n_cats + reserve >= st->max_categories) {
|
|
197
|
+
return resolve_other(st, idx, side);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (st->max_categories > 0 && st->n_cats >= st->max_categories) {
|
|
202
|
+
int rc = resolve_unknown(st, val, idx, side);
|
|
203
|
+
if (rc == 0) return 0;
|
|
204
|
+
if (onehot_limit_error(st, val, side) != TF_OK) return -1;
|
|
205
|
+
return -1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
int added = add_category(st, val);
|
|
209
|
+
if (added < 0) return -1;
|
|
210
|
+
if (onehot_check_state_bytes(st, side) != TF_OK) return -1;
|
|
211
|
+
*idx = added;
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
64
215
|
static int onehot_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
65
216
|
tf_side_channels *side) {
|
|
66
|
-
(void)side;
|
|
67
217
|
onehot_state *st = self->state;
|
|
68
218
|
*out = NULL;
|
|
69
219
|
|
|
70
220
|
int ci = tf_batch_col_index(in, st->column);
|
|
221
|
+
size_t match_count = in->n_rows ? in->n_rows : 1;
|
|
222
|
+
int *matches = tf_mallocarray_checked(match_count, sizeof(int));
|
|
223
|
+
if (!matches) return TF_ERROR;
|
|
224
|
+
for (size_t r = 0; r < in->n_rows; r++) matches[r] = -1;
|
|
71
225
|
|
|
72
|
-
/* First pass: discover any new categories in this batch */
|
|
73
|
-
size_t cats_before = st->n_cats;
|
|
74
226
|
if (ci >= 0) {
|
|
75
227
|
char buf[64];
|
|
76
228
|
for (size_t r = 0; r < in->n_rows; r++) {
|
|
77
229
|
const char *val = get_string_value(in, r, ci, buf, sizeof(buf));
|
|
78
|
-
if (
|
|
230
|
+
if (resolve_category(st, val, &matches[r], side) != 0) {
|
|
231
|
+
free(matches);
|
|
232
|
+
return TF_ERROR;
|
|
233
|
+
}
|
|
79
234
|
}
|
|
80
235
|
}
|
|
81
|
-
(void)cats_before;
|
|
82
|
-
|
|
83
|
-
/* Compute output column count */
|
|
84
|
-
size_t out_cols = st->drop ? (in->n_cols - 1 + st->n_cats) : (in->n_cols + st->n_cats);
|
|
85
236
|
|
|
237
|
+
size_t input_cols = (st->drop && ci >= 0 && in->n_cols > 0) ? in->n_cols - 1 : in->n_cols;
|
|
238
|
+
size_t out_cols = 0;
|
|
239
|
+
if (tf_size_add(input_cols, st->n_cats, &out_cols) != TF_OK) {
|
|
240
|
+
free(matches);
|
|
241
|
+
return TF_ERROR;
|
|
242
|
+
}
|
|
86
243
|
tf_batch *ob = tf_batch_create(out_cols, in->n_rows);
|
|
87
|
-
if (!ob) return TF_ERROR;
|
|
244
|
+
if (!ob) { free(matches); return TF_ERROR; }
|
|
88
245
|
|
|
89
|
-
/* Set schema: copy input cols (optionally skipping target), append onehot cols */
|
|
90
246
|
size_t oc = 0;
|
|
91
247
|
for (size_t c = 0; c < in->n_cols; c++) {
|
|
92
248
|
if (st->drop && ci >= 0 && c == (size_t)ci) continue;
|
|
93
|
-
tf_batch_set_schema(ob, oc, in->col_names[c], in->col_types[c]);
|
|
249
|
+
if (tf_batch_set_schema(ob, oc, in->col_names[c], in->col_types[c]) != TF_OK) goto fail;
|
|
94
250
|
oc++;
|
|
95
251
|
}
|
|
96
252
|
for (size_t i = 0; i < st->n_cats; i++) {
|
|
97
|
-
tf_batch_set_schema(ob, oc + i, st->cats[i].col_name, TF_TYPE_INT64);
|
|
253
|
+
if (tf_batch_set_schema(ob, oc + i, st->cats[i].col_name, TF_TYPE_INT64) != TF_OK) goto fail;
|
|
98
254
|
}
|
|
99
255
|
|
|
100
|
-
/* Fill rows */
|
|
101
|
-
char buf[64];
|
|
102
256
|
for (size_t r = 0; r < in->n_rows; r++) {
|
|
103
|
-
/* Copy input columns */
|
|
104
257
|
oc = 0;
|
|
105
258
|
for (size_t c = 0; c < in->n_cols; c++) {
|
|
106
259
|
if (st->drop && ci >= 0 && c == (size_t)ci) continue;
|
|
107
|
-
if (
|
|
108
|
-
tf_batch_set_null(ob, r, oc);
|
|
109
|
-
} else {
|
|
110
|
-
switch (in->col_types[c]) {
|
|
111
|
-
case TF_TYPE_STRING:
|
|
112
|
-
tf_batch_set_string(ob, r, oc, tf_batch_get_string(in, r, c)); break;
|
|
113
|
-
case TF_TYPE_INT64:
|
|
114
|
-
tf_batch_set_int64(ob, r, oc, tf_batch_get_int64(in, r, c)); break;
|
|
115
|
-
case TF_TYPE_FLOAT64:
|
|
116
|
-
tf_batch_set_float64(ob, r, oc, tf_batch_get_float64(in, r, c)); break;
|
|
117
|
-
case TF_TYPE_BOOL:
|
|
118
|
-
tf_batch_set_bool(ob, r, oc, tf_batch_get_bool(in, r, c)); break;
|
|
119
|
-
case TF_TYPE_DATE:
|
|
120
|
-
tf_batch_set_date(ob, r, oc, tf_batch_get_date(in, r, c)); break;
|
|
121
|
-
case TF_TYPE_TIMESTAMP:
|
|
122
|
-
tf_batch_set_timestamp(ob, r, oc, tf_batch_get_timestamp(in, r, c)); break;
|
|
123
|
-
default: tf_batch_set_null(ob, r, oc); break;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
260
|
+
if (tf_batch_copy_cell(ob, r, oc, in, r, c) != TF_OK) goto fail;
|
|
126
261
|
oc++;
|
|
127
262
|
}
|
|
128
263
|
|
|
129
|
-
|
|
130
|
-
const char *val = (ci >= 0) ? get_string_value(in, r, ci, buf, sizeof(buf)) : NULL;
|
|
131
|
-
int match = -1;
|
|
132
|
-
if (val) {
|
|
133
|
-
for (size_t i = 0; i < st->n_cats; i++) {
|
|
134
|
-
if (strcmp(st->cats[i].value, val) == 0) { match = (int)i; break; }
|
|
135
|
-
}
|
|
136
|
-
}
|
|
264
|
+
int match = matches[r];
|
|
137
265
|
for (size_t i = 0; i < st->n_cats; i++) {
|
|
138
|
-
tf_batch_set_int64(ob, r, oc + i, (int)i == match ? 1 : 0);
|
|
266
|
+
if (tf_batch_set_int64(ob, r, oc + i, (int)i == match ? 1 : 0) != TF_OK) goto fail;
|
|
139
267
|
}
|
|
140
|
-
|
|
141
|
-
ob->n_rows = r + 1;
|
|
268
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) goto fail;
|
|
142
269
|
}
|
|
143
270
|
|
|
271
|
+
free(matches);
|
|
144
272
|
*out = ob;
|
|
145
273
|
return TF_OK;
|
|
274
|
+
|
|
275
|
+
fail:
|
|
276
|
+
free(matches);
|
|
277
|
+
tf_batch_free(ob);
|
|
278
|
+
return TF_ERROR;
|
|
146
279
|
}
|
|
147
280
|
|
|
148
281
|
static int onehot_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
149
282
|
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
150
283
|
}
|
|
151
284
|
|
|
152
|
-
static
|
|
285
|
+
static size_t onehot_category_value_bytes(const onehot_state *st) {
|
|
286
|
+
size_t total = 0;
|
|
287
|
+
if (!st) return 0;
|
|
288
|
+
for (size_t i = 0; i < st->n_cats; i++) {
|
|
289
|
+
if (st->cats[i].value) total += strlen(st->cats[i].value) + 1;
|
|
290
|
+
}
|
|
291
|
+
return total;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
static size_t onehot_category_output_name_bytes(const onehot_state *st) {
|
|
295
|
+
size_t total = 0;
|
|
296
|
+
if (!st) return 0;
|
|
297
|
+
for (size_t i = 0; i < st->n_cats; i++) {
|
|
298
|
+
if (st->cats[i].col_name) total += strlen(st->cats[i].col_name) + 1;
|
|
299
|
+
}
|
|
300
|
+
return total;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
static size_t onehot_retained_state_bytes(const onehot_state *st) {
|
|
304
|
+
if (!st) return 0;
|
|
305
|
+
size_t total = st->cap * sizeof(onehot_category);
|
|
306
|
+
total += onehot_category_value_bytes(st);
|
|
307
|
+
total += onehot_category_output_name_bytes(st);
|
|
308
|
+
if (st->column) total += strlen(st->column) + 1;
|
|
309
|
+
return total;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
static int onehot_append_stats(tf_step *self, tf_buffer *out) {
|
|
313
|
+
if (!self || !self->state || !out) return TF_ERROR;
|
|
153
314
|
onehot_state *st = self->state;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
315
|
+
char buf[320];
|
|
316
|
+
snprintf(buf, sizeof(buf),
|
|
317
|
+
",\"tracked_categories\":%zu,\"category_value_bytes\":%zu,"
|
|
318
|
+
"\"category_output_name_bytes\":%zu,\"retained_state_bytes\":%zu,"
|
|
319
|
+
"\"max_state_bytes\":%zu",
|
|
320
|
+
st->n_cats, onehot_category_value_bytes(st),
|
|
321
|
+
onehot_category_output_name_bytes(st), onehot_retained_state_bytes(st),
|
|
322
|
+
st->max_state_bytes);
|
|
323
|
+
return tf_buffer_write_str(out, buf);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
static void onehot_state_free(onehot_state *st) {
|
|
327
|
+
if (!st) return;
|
|
328
|
+
for (size_t i = 0; i < st->n_cats; i++) {
|
|
329
|
+
free(st->cats[i].value);
|
|
330
|
+
free(st->cats[i].col_name);
|
|
331
|
+
}
|
|
332
|
+
free(st->cats);
|
|
333
|
+
free(st->column);
|
|
334
|
+
free(st);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
static void onehot_destroy(tf_step *self) {
|
|
338
|
+
if (self) {
|
|
339
|
+
onehot_state_free(self->state);
|
|
340
|
+
free(self);
|
|
162
341
|
}
|
|
163
|
-
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
static int parse_unknown_policy(const cJSON *args, tf_cat_unknown_policy *policy,
|
|
345
|
+
int *specified) {
|
|
346
|
+
cJSON *unknown_j = cJSON_GetObjectItemCaseSensitive(args, "unknown");
|
|
347
|
+
*specified = 0;
|
|
348
|
+
if (!unknown_j) return 0;
|
|
349
|
+
if (!cJSON_IsString(unknown_j)) {
|
|
350
|
+
tf_set_last_error("onehot: unknown must be one of error, other, null");
|
|
351
|
+
return -1;
|
|
352
|
+
}
|
|
353
|
+
*specified = 1;
|
|
354
|
+
if (strcmp(unknown_j->valuestring, "error") == 0) *policy = TF_CAT_UNKNOWN_ERROR;
|
|
355
|
+
else if (strcmp(unknown_j->valuestring, "other") == 0) *policy = TF_CAT_UNKNOWN_OTHER;
|
|
356
|
+
else if (strcmp(unknown_j->valuestring, "null") == 0) *policy = TF_CAT_UNKNOWN_NULL;
|
|
357
|
+
else {
|
|
358
|
+
tf_set_last_error("onehot: unknown must be one of error, other, null");
|
|
359
|
+
return -1;
|
|
360
|
+
}
|
|
361
|
+
return 0;
|
|
164
362
|
}
|
|
165
363
|
|
|
166
364
|
tf_step *tf_onehot_create(const cJSON *args) {
|
|
@@ -171,14 +369,88 @@ tf_step *tf_onehot_create(const cJSON *args) {
|
|
|
171
369
|
onehot_state *st = calloc(1, sizeof(onehot_state));
|
|
172
370
|
if (!st) return NULL;
|
|
173
371
|
st->column = strdup(col_j->valuestring);
|
|
372
|
+
if (!st->column) { onehot_state_free(st); return NULL; }
|
|
373
|
+
st->unknown = TF_CAT_UNKNOWN_ADD;
|
|
174
374
|
|
|
175
375
|
cJSON *drop_j = cJSON_GetObjectItemCaseSensitive(args, "drop");
|
|
176
376
|
st->drop = cJSON_IsBool(drop_j) && cJSON_IsTrue(drop_j) ? 1 : 0;
|
|
177
377
|
|
|
178
|
-
|
|
179
|
-
|
|
378
|
+
size_t parsed_size = 0;
|
|
379
|
+
int has_max_categories = tf_json_get_size_arg(args, "max_categories",
|
|
380
|
+
1, TF_MAX_COUNT_ARG,
|
|
381
|
+
&parsed_size, "onehot");
|
|
382
|
+
if (has_max_categories < 0) { onehot_state_free(st); return NULL; }
|
|
383
|
+
if (has_max_categories > 0) st->max_categories = parsed_size;
|
|
384
|
+
|
|
385
|
+
int has_max_state = tf_json_get_size_arg(args, "max_state_bytes",
|
|
386
|
+
1, TF_MAX_STATE_BYTES,
|
|
387
|
+
&parsed_size, "onehot");
|
|
388
|
+
if (has_max_state < 0) { onehot_state_free(st); return NULL; }
|
|
389
|
+
if (has_max_state > 0) st->max_state_bytes = parsed_size;
|
|
390
|
+
|
|
391
|
+
int unknown_specified = 0;
|
|
392
|
+
if (parse_unknown_policy(args, &st->unknown, &unknown_specified) != 0) {
|
|
393
|
+
onehot_state_free(st);
|
|
394
|
+
return NULL;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
cJSON *cats_j = cJSON_GetObjectItemCaseSensitive(args, "categories");
|
|
398
|
+
if (cats_j) {
|
|
399
|
+
if (!cJSON_IsArray(cats_j)) {
|
|
400
|
+
tf_set_last_error("onehot: categories must be an array");
|
|
401
|
+
onehot_state_free(st);
|
|
402
|
+
return NULL;
|
|
403
|
+
}
|
|
404
|
+
st->categories_declared = 1;
|
|
405
|
+
cJSON *item = NULL;
|
|
406
|
+
cJSON_ArrayForEach(item, cats_j) {
|
|
407
|
+
if (!cJSON_IsString(item)) {
|
|
408
|
+
tf_set_last_error("onehot: categories must contain strings");
|
|
409
|
+
onehot_state_free(st);
|
|
410
|
+
return NULL;
|
|
411
|
+
}
|
|
412
|
+
if (find_category(st, item->valuestring) < 0 &&
|
|
413
|
+
st->max_categories > 0 && st->n_cats >= st->max_categories) {
|
|
414
|
+
tf_set_last_error("onehot: categories exceed max_categories");
|
|
415
|
+
onehot_state_free(st);
|
|
416
|
+
return NULL;
|
|
417
|
+
}
|
|
418
|
+
if (add_category(st, item->valuestring) < 0) {
|
|
419
|
+
onehot_state_free(st);
|
|
420
|
+
return NULL;
|
|
421
|
+
}
|
|
422
|
+
if (onehot_check_state_bytes(st, NULL) != TF_OK) {
|
|
423
|
+
onehot_state_free(st);
|
|
424
|
+
return NULL;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (st->categories_declared && !unknown_specified)
|
|
430
|
+
st->unknown = TF_CAT_UNKNOWN_ERROR;
|
|
431
|
+
|
|
432
|
+
if (st->categories_declared && st->unknown == TF_CAT_UNKNOWN_OTHER &&
|
|
433
|
+
find_category(st, OTHER_CATEGORY) < 0) {
|
|
434
|
+
if (st->max_categories > 0 && st->n_cats >= st->max_categories) {
|
|
435
|
+
tf_set_last_error("onehot: max_categories leaves no room for other category");
|
|
436
|
+
onehot_state_free(st);
|
|
437
|
+
return NULL;
|
|
438
|
+
}
|
|
439
|
+
if (add_category(st, OTHER_CATEGORY) < 0) {
|
|
440
|
+
onehot_state_free(st);
|
|
441
|
+
return NULL;
|
|
442
|
+
}
|
|
443
|
+
if (onehot_check_state_bytes(st, NULL) != TF_OK) {
|
|
444
|
+
onehot_state_free(st);
|
|
445
|
+
return NULL;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
450
|
+
if (!step) { onehot_state_free(st); return NULL; }
|
|
180
451
|
step->process = onehot_process;
|
|
181
452
|
step->flush = onehot_flush;
|
|
453
|
+
step->append_stats = onehot_append_stats;
|
|
182
454
|
step->destroy = onehot_destroy;
|
|
183
455
|
step->state = st;
|
|
184
456
|
return step;
|