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.
- package/LICENSE +177 -21
- package/NOTICE +8 -0
- package/README.md +627 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-BIAIKnrp.js +160 -0
- package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/app/index.html +13 -0
- package/binding.gyp +121 -0
- package/csrc/arena.c +93 -0
- package/csrc/batch.c +976 -0
- package/csrc/buffer.c +154 -0
- package/csrc/cJSON.c +3386 -0
- package/csrc/cJSON.h +316 -0
- package/csrc/codec_csv.c +1951 -0
- package/csrc/codec_jsonl.c +1086 -0
- package/csrc/codec_table.c +248 -0
- package/csrc/codec_text.c +447 -0
- package/csrc/compiler.c +130 -0
- package/csrc/config.h +21 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +5417 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1553 -0
- package/csrc/expr.h +58 -0
- package/csrc/internal.h +539 -0
- package/csrc/ir.c +166 -0
- package/csrc/ir.h +208 -0
- package/csrc/ir_schema.c +75 -0
- package/csrc/ir_serialize.c +166 -0
- package/csrc/ir_sql.c +1822 -0
- package/csrc/ir_validate.c +576 -0
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +1241 -0
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +283 -0
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +255 -0
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +248 -0
- package/csrc/op_cast.c +523 -0
- package/csrc/op_clip.c +99 -0
- package/csrc/op_date_trunc.c +355 -0
- package/csrc/op_datetime.c +394 -0
- package/csrc/op_derive.c +216 -0
- package/csrc/op_diff.c +250 -0
- package/csrc/op_ewma.c +222 -0
- package/csrc/op_explode.c +206 -0
- package/csrc/op_fill_down.c +235 -0
- package/csrc/op_fill_null.c +268 -0
- package/csrc/op_filter.c +181 -0
- package/csrc/op_frequency.c +721 -0
- package/csrc/op_grep.c +181 -0
- package/csrc/op_group_agg.c +1956 -0
- package/csrc/op_hash.c +159 -0
- package/csrc/op_head.c +84 -0
- package/csrc/op_interpolate.c +445 -0
- package/csrc/op_join.c +2902 -0
- 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 +419 -0
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +242 -0
- package/csrc/op_normalize.c +510 -0
- package/csrc/op_onehot.c +457 -0
- package/csrc/op_pivot.c +1754 -0
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +3044 -0
- package/csrc/op_rename.c +129 -0
- package/csrc/op_replace.c +354 -0
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +158 -0
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +340 -0
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +95 -0
- package/csrc/op_sort.c +819 -0
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +151 -0
- package/csrc/op_split_data.c +119 -0
- package/csrc/op_stack.c +271 -0
- package/csrc/op_stats.c +875 -0
- package/csrc/op_step.c +333 -0
- package/csrc/op_tail.c +105 -0
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +357 -0
- package/csrc/op_trim.c +138 -0
- package/csrc/op_unique.c +1343 -0
- package/csrc/op_unpivot.c +193 -0
- package/csrc/op_validate.c +648 -0
- package/csrc/op_window.c +591 -0
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +1088 -0
- package/csrc/recipes.c +104 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +506 -0
- package/csrc/report.h +22 -0
- 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 +291 -0
- 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 +218 -0
- package/napi_api.c +534 -0
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +64 -59
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +190 -0
- package/src/engines/duckdb.js +142 -0
- package/src/index.js +925 -0
- package/src/memory_policy.js +411 -0
- package/src/native.js +18 -0
- package/src/pipeline.js +709 -0
- package/src/recipe_json.js +80 -0
- package/src/server.js +279 -0
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +21 -0
- package/wasm/index.js +732 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/dist/bundle.js +0 -1
- package/index.html +0 -18
- package/src/app.css +0 -169
- package/src/app.js +0 -203
- package/src/app.vue +0 -250
- package/src/bulma-input.vue +0 -110
- package/src/common-inputs.js +0 -28
- package/src/main.js +0 -20
- package/src/transforms.js +0 -166
- package/webpack.config.js +0 -108
package/csrc/op_onehot.c
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_onehot.c — One-hot encoding of a categorical column.
|
|
3
|
+
* Expands a single column into N binary (0/1) columns.
|
|
4
|
+
*
|
|
5
|
+
* Config: {"column": "city", "drop": false,
|
|
6
|
+
* "categories": ["Paris", "London"],
|
|
7
|
+
* "max_categories": 1000,
|
|
8
|
+
* "max_state_bytes": 1048576,
|
|
9
|
+
* "unknown": "error" | "other" | "null"}
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
#include "internal.h"
|
|
13
|
+
#include "cJSON.h"
|
|
14
|
+
#include <stdlib.h>
|
|
15
|
+
#include <string.h>
|
|
16
|
+
#include <stdio.h>
|
|
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
|
+
|
|
25
|
+
typedef struct {
|
|
26
|
+
char *value; /* category value string */
|
|
27
|
+
char *col_name; /* generated column name: "column_value" */
|
|
28
|
+
} onehot_category;
|
|
29
|
+
|
|
30
|
+
typedef struct {
|
|
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;
|
|
40
|
+
} onehot_state;
|
|
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
|
+
|
|
80
|
+
static const char *get_string_value(const tf_batch *b, size_t r, int ci, char *buf, size_t bufsz) {
|
|
81
|
+
if (tf_batch_is_null(b, r, ci)) return NULL;
|
|
82
|
+
switch (b->col_types[ci]) {
|
|
83
|
+
case TF_TYPE_STRING: return tf_batch_get_string(b, r, ci);
|
|
84
|
+
case TF_TYPE_INT64:
|
|
85
|
+
snprintf(buf, bufsz, "%lld", (long long)tf_batch_get_int64(b, r, ci));
|
|
86
|
+
return buf;
|
|
87
|
+
case TF_TYPE_FLOAT64:
|
|
88
|
+
snprintf(buf, bufsz, "%.17g", tf_batch_get_float64(b, r, ci));
|
|
89
|
+
return buf;
|
|
90
|
+
case TF_TYPE_BOOL:
|
|
91
|
+
return tf_batch_get_bool(b, r, ci) ? "true" : "false";
|
|
92
|
+
default: return NULL;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static int find_category(const onehot_state *st, const char *val) {
|
|
97
|
+
for (size_t i = 0; i < st->n_cats; i++) {
|
|
98
|
+
if (strcmp(st->cats[i].value, val) == 0) return (int)i;
|
|
99
|
+
}
|
|
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;
|
|
106
|
+
if (st->n_cats >= st->cap) {
|
|
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));
|
|
114
|
+
if (!tmp) return -1;
|
|
115
|
+
st->cats = tmp;
|
|
116
|
+
st->cap = newcap;
|
|
117
|
+
}
|
|
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;
|
|
144
|
+
st->n_cats++;
|
|
145
|
+
return (int)(st->n_cats - 1);
|
|
146
|
+
}
|
|
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
|
+
|
|
215
|
+
static int onehot_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
216
|
+
tf_side_channels *side) {
|
|
217
|
+
onehot_state *st = self->state;
|
|
218
|
+
*out = NULL;
|
|
219
|
+
|
|
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;
|
|
225
|
+
|
|
226
|
+
if (ci >= 0) {
|
|
227
|
+
char buf[64];
|
|
228
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
229
|
+
const char *val = get_string_value(in, r, ci, buf, sizeof(buf));
|
|
230
|
+
if (resolve_category(st, val, &matches[r], side) != 0) {
|
|
231
|
+
free(matches);
|
|
232
|
+
return TF_ERROR;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
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
|
+
}
|
|
243
|
+
tf_batch *ob = tf_batch_create(out_cols, in->n_rows);
|
|
244
|
+
if (!ob) { free(matches); return TF_ERROR; }
|
|
245
|
+
|
|
246
|
+
size_t oc = 0;
|
|
247
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
248
|
+
if (st->drop && ci >= 0 && c == (size_t)ci) continue;
|
|
249
|
+
if (tf_batch_set_schema(ob, oc, in->col_names[c], in->col_types[c]) != TF_OK) goto fail;
|
|
250
|
+
oc++;
|
|
251
|
+
}
|
|
252
|
+
for (size_t i = 0; i < st->n_cats; i++) {
|
|
253
|
+
if (tf_batch_set_schema(ob, oc + i, st->cats[i].col_name, TF_TYPE_INT64) != TF_OK) goto fail;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
257
|
+
oc = 0;
|
|
258
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
259
|
+
if (st->drop && ci >= 0 && c == (size_t)ci) continue;
|
|
260
|
+
if (tf_batch_copy_cell(ob, r, oc, in, r, c) != TF_OK) goto fail;
|
|
261
|
+
oc++;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
int match = matches[r];
|
|
265
|
+
for (size_t i = 0; i < st->n_cats; i++) {
|
|
266
|
+
if (tf_batch_set_int64(ob, r, oc + i, (int)i == match ? 1 : 0) != TF_OK) goto fail;
|
|
267
|
+
}
|
|
268
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) goto fail;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
free(matches);
|
|
272
|
+
*out = ob;
|
|
273
|
+
return TF_OK;
|
|
274
|
+
|
|
275
|
+
fail:
|
|
276
|
+
free(matches);
|
|
277
|
+
tf_batch_free(ob);
|
|
278
|
+
return TF_ERROR;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
static int onehot_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
282
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
283
|
+
}
|
|
284
|
+
|
|
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;
|
|
314
|
+
onehot_state *st = self->state;
|
|
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);
|
|
341
|
+
}
|
|
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;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
tf_step *tf_onehot_create(const cJSON *args) {
|
|
365
|
+
if (!args) return NULL;
|
|
366
|
+
cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
367
|
+
if (!cJSON_IsString(col_j)) return NULL;
|
|
368
|
+
|
|
369
|
+
onehot_state *st = calloc(1, sizeof(onehot_state));
|
|
370
|
+
if (!st) return NULL;
|
|
371
|
+
st->column = strdup(col_j->valuestring);
|
|
372
|
+
if (!st->column) { onehot_state_free(st); return NULL; }
|
|
373
|
+
st->unknown = TF_CAT_UNKNOWN_ADD;
|
|
374
|
+
|
|
375
|
+
cJSON *drop_j = cJSON_GetObjectItemCaseSensitive(args, "drop");
|
|
376
|
+
st->drop = cJSON_IsBool(drop_j) && cJSON_IsTrue(drop_j) ? 1 : 0;
|
|
377
|
+
|
|
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; }
|
|
451
|
+
step->process = onehot_process;
|
|
452
|
+
step->flush = onehot_flush;
|
|
453
|
+
step->append_stats = onehot_append_stats;
|
|
454
|
+
step->destroy = onehot_destroy;
|
|
455
|
+
step->state = st;
|
|
456
|
+
return step;
|
|
457
|
+
}
|