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_frequency.c
CHANGED
|
@@ -15,25 +15,89 @@
|
|
|
15
15
|
typedef struct {
|
|
16
16
|
char **keys;
|
|
17
17
|
size_t *counts;
|
|
18
|
+
size_t *errors;
|
|
18
19
|
size_t count;
|
|
19
20
|
size_t cap;
|
|
21
|
+
size_t key_bytes;
|
|
20
22
|
} freq_map;
|
|
21
23
|
|
|
22
24
|
typedef struct {
|
|
23
25
|
char **cols;
|
|
24
26
|
size_t n_cols;
|
|
27
|
+
size_t max_values; /* 0 = unlimited */
|
|
28
|
+
size_t max_state_bytes; /* 0 = unlimited */
|
|
29
|
+
int approximate;
|
|
30
|
+
int overflow_other;
|
|
31
|
+
char *other_label;
|
|
32
|
+
size_t other_count;
|
|
33
|
+
size_t approx_replacements;
|
|
34
|
+
size_t row_index;
|
|
35
|
+
size_t audit_limit;
|
|
36
|
+
size_t audit_emitted;
|
|
37
|
+
int audit;
|
|
38
|
+
tf_audit_options audit_opts;
|
|
25
39
|
freq_map map;
|
|
26
40
|
} frequency_state;
|
|
27
41
|
|
|
42
|
+
static size_t frequency_retained_state_bytes(const frequency_state *st);
|
|
43
|
+
|
|
44
|
+
static int frequency_write_error(tf_side_channels *side, const char *msg) {
|
|
45
|
+
return tf_side_write_error(side, msg);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static int frequency_limit_error(const frequency_state *st, tf_side_channels *side) {
|
|
49
|
+
char msg[160];
|
|
50
|
+
snprintf(msg, sizeof(msg),
|
|
51
|
+
"frequency: max_values=%zu exceeded while tracking exact counts",
|
|
52
|
+
st->max_values);
|
|
53
|
+
return frequency_write_error(side, msg);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static int frequency_check_state_bytes(frequency_state *st, tf_side_channels *side) {
|
|
57
|
+
if (!st || st->max_state_bytes == 0) return 0;
|
|
58
|
+
size_t retained = frequency_retained_state_bytes(st);
|
|
59
|
+
if (retained <= st->max_state_bytes) return 0;
|
|
60
|
+
char msg[208];
|
|
61
|
+
snprintf(msg, sizeof(msg),
|
|
62
|
+
"frequency: max_state_bytes=%zu exceeded while tracking %s counts (%zu bytes retained)",
|
|
63
|
+
st->max_state_bytes, st->approximate ? "approximate" : "exact", retained);
|
|
64
|
+
if (frequency_write_error(side, msg) != TF_OK) return -1;
|
|
65
|
+
return -1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static int freq_buf_ensure(char **buf, size_t *cap, size_t len, size_t extra) {
|
|
69
|
+
size_t need = 0;
|
|
70
|
+
if (!buf || !*buf || !cap ||
|
|
71
|
+
tf_size_add(len, extra, &need) != TF_OK ||
|
|
72
|
+
tf_size_add(need, 1, &need) != TF_OK) {
|
|
73
|
+
return TF_ERROR;
|
|
74
|
+
}
|
|
75
|
+
if (need <= *cap) return TF_OK;
|
|
76
|
+
|
|
77
|
+
size_t new_cap = 0;
|
|
78
|
+
if (tf_size_grow_pow2(*cap, need, 256, &new_cap) != TF_OK) return TF_ERROR;
|
|
79
|
+
char *tmp = tf_reallocarray_checked(*buf, new_cap, sizeof(char));
|
|
80
|
+
if (!tmp) return TF_ERROR;
|
|
81
|
+
*buf = tmp;
|
|
82
|
+
*cap = new_cap;
|
|
83
|
+
return TF_OK;
|
|
84
|
+
}
|
|
85
|
+
|
|
28
86
|
static char *build_freq_key(const tf_batch *b, size_t row,
|
|
29
87
|
int *col_indices, size_t n_keys) {
|
|
30
88
|
size_t buf_cap = 256;
|
|
31
|
-
char *buf =
|
|
89
|
+
char *buf = tf_mallocarray_checked(buf_cap, sizeof(char));
|
|
32
90
|
if (!buf) return NULL;
|
|
33
91
|
size_t buf_len = 0;
|
|
34
92
|
for (size_t k = 0; k < n_keys; k++) {
|
|
35
93
|
int c = col_indices[k];
|
|
36
|
-
if (k > 0) {
|
|
94
|
+
if (k > 0) {
|
|
95
|
+
if (freq_buf_ensure(&buf, &buf_cap, buf_len, 1) != TF_OK) {
|
|
96
|
+
free(buf);
|
|
97
|
+
return NULL;
|
|
98
|
+
}
|
|
99
|
+
buf[buf_len++] = '\x01';
|
|
100
|
+
}
|
|
37
101
|
char val_buf[64];
|
|
38
102
|
const char *val;
|
|
39
103
|
size_t val_len;
|
|
@@ -46,7 +110,11 @@ static char *build_freq_key(const tf_batch *b, size_t row,
|
|
|
46
110
|
val_len = snprintf(val_buf, sizeof(val_buf), "%lld", (long long)tf_batch_get_int64(b, row, c));
|
|
47
111
|
val = val_buf; break;
|
|
48
112
|
case TF_TYPE_FLOAT64:
|
|
49
|
-
|
|
113
|
+
if (tf_format_float64(val_buf, sizeof(val_buf), tf_batch_get_float64(b, row, c)) != TF_OK) {
|
|
114
|
+
free(buf);
|
|
115
|
+
return NULL;
|
|
116
|
+
}
|
|
117
|
+
val_len = strlen(val_buf);
|
|
50
118
|
val = val_buf; break;
|
|
51
119
|
case TF_TYPE_BOOL:
|
|
52
120
|
val = tf_batch_get_bool(b, row, c) ? "T" : "F"; val_len = 1; break;
|
|
@@ -59,7 +127,10 @@ static char *build_freq_key(const tf_batch *b, size_t row,
|
|
|
59
127
|
default: val = "\\N"; val_len = 2; break;
|
|
60
128
|
}
|
|
61
129
|
}
|
|
62
|
-
|
|
130
|
+
if (freq_buf_ensure(&buf, &buf_cap, buf_len, val_len) != TF_OK) {
|
|
131
|
+
free(buf);
|
|
132
|
+
return NULL;
|
|
133
|
+
}
|
|
63
134
|
memcpy(buf + buf_len, val, val_len);
|
|
64
135
|
buf_len += val_len;
|
|
65
136
|
}
|
|
@@ -67,25 +138,244 @@ static char *build_freq_key(const tf_batch *b, size_t row,
|
|
|
67
138
|
return buf;
|
|
68
139
|
}
|
|
69
140
|
|
|
70
|
-
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
static const char *frequency_format_key_for_audit(frequency_state *st, const char *key,
|
|
144
|
+
const tf_batch *b, size_t row,
|
|
145
|
+
char *buf, size_t buf_size) {
|
|
146
|
+
const char *src = key ? key : "";
|
|
147
|
+
if (!st || !buf || buf_size == 0) return src;
|
|
148
|
+
int redact = 0;
|
|
149
|
+
int hash = 0;
|
|
150
|
+
if (b && row < b->n_rows) {
|
|
151
|
+
if (st->n_cols > 0) {
|
|
152
|
+
for (size_t i = 0; i < st->n_cols; i++) {
|
|
153
|
+
int ci = tf_batch_col_index(b, st->cols[i]);
|
|
154
|
+
if (ci < 0) continue;
|
|
155
|
+
const char *name = b->col_names[ci] ? b->col_names[ci] : "";
|
|
156
|
+
if (tf_audit_column_is_redacted(&st->audit_opts, name)) redact = 1;
|
|
157
|
+
if (tf_audit_column_is_hashed(&st->audit_opts, name)) hash = 1;
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
for (size_t c = 0; c < b->n_cols; c++) {
|
|
161
|
+
const char *name = b->col_names[c] ? b->col_names[c] : "";
|
|
162
|
+
if (tf_audit_column_is_redacted(&st->audit_opts, name)) redact = 1;
|
|
163
|
+
if (tf_audit_column_is_hashed(&st->audit_opts, name)) hash = 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (redact) {
|
|
168
|
+
snprintf(buf, buf_size, "[REDACTED]");
|
|
169
|
+
return buf;
|
|
170
|
+
}
|
|
171
|
+
if (hash) {
|
|
172
|
+
if (tf_audit_hash_string(src, buf, buf_size) == TF_OK) return buf;
|
|
173
|
+
return "[HASHED]";
|
|
174
|
+
}
|
|
175
|
+
if (st->audit_opts.max_cell_bytes > 0 && strlen(src) > st->audit_opts.max_cell_bytes) {
|
|
176
|
+
size_t n = st->audit_opts.max_cell_bytes;
|
|
177
|
+
if (n + 4 > buf_size) n = buf_size > 4 ? buf_size - 4 : 0;
|
|
178
|
+
if (n > 0) memcpy(buf, src, n);
|
|
179
|
+
snprintf(buf + n, buf_size - n, "...");
|
|
180
|
+
return buf;
|
|
181
|
+
}
|
|
182
|
+
return src;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static int emit_frequency_overflow_audit(frequency_state *st, const char *key,
|
|
186
|
+
const tf_batch *b, size_t row,
|
|
187
|
+
size_t row_no, tf_side_channels *side) {
|
|
188
|
+
if (!st->audit || st->audit_emitted >= st->audit_limit || !side || !side->stats) return TF_OK;
|
|
189
|
+
cJSON *obj = cJSON_CreateObject();
|
|
190
|
+
if (!obj) return TF_ERROR;
|
|
191
|
+
int rc = TF_ERROR;
|
|
192
|
+
if (tf_json_add_string(obj, "type", "audit") != TF_OK ||
|
|
193
|
+
tf_json_add_string(obj, "op", "frequency") != TF_OK ||
|
|
194
|
+
tf_json_add_string(obj, "event", "category_overflow") != TF_OK ||
|
|
195
|
+
tf_json_add_string(obj, "reason", "max_values_overflow") != TF_OK ||
|
|
196
|
+
tf_json_add_string(obj, "channel", "audit") != TF_OK ||
|
|
197
|
+
tf_json_add_string(obj, "action", "other") != TF_OK) {
|
|
198
|
+
goto done;
|
|
199
|
+
}
|
|
200
|
+
char key_buf[256];
|
|
201
|
+
const char *safe_key = frequency_format_key_for_audit(st, key, b, row, key_buf, sizeof(key_buf));
|
|
202
|
+
if (tf_json_add_string(obj, "value", safe_key ? safe_key : "") != TF_OK ||
|
|
203
|
+
tf_json_add_string(obj, "bucket", st->other_label ? st->other_label : "__other__") != TF_OK ||
|
|
204
|
+
tf_json_add_number(obj, "row", (double)row_no) != TF_OK ||
|
|
205
|
+
tf_json_add_number(obj, "max_values", (double)st->max_values) != TF_OK ||
|
|
206
|
+
tf_json_add_number(obj, "tracked_values", (double)st->map.count) != TF_OK) {
|
|
207
|
+
goto done;
|
|
208
|
+
}
|
|
209
|
+
cJSON *row_obj = tf_audit_row_to_json(b, row, &st->audit_opts);
|
|
210
|
+
if (row_obj) {
|
|
211
|
+
if (tf_json_add_item(obj, "data", row_obj) != TF_OK) goto done;
|
|
212
|
+
} else if (st->audit_opts.include_row) {
|
|
213
|
+
goto done;
|
|
214
|
+
}
|
|
215
|
+
rc = tf_buffer_write_json_line(side->stats, obj);
|
|
216
|
+
done:
|
|
217
|
+
cJSON_Delete(obj);
|
|
218
|
+
if (rc == TF_OK) st->audit_emitted++;
|
|
219
|
+
return rc;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
static int freq_find(const freq_map *map, const char *key, size_t *idx_out) {
|
|
71
223
|
for (size_t i = 0; i < map->count; i++) {
|
|
72
|
-
if (strcmp(map->keys[i], key) == 0) {
|
|
224
|
+
if (strcmp(map->keys[i], key) == 0) {
|
|
225
|
+
if (idx_out) *idx_out = i;
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
static int freq_map_reserve(freq_map *map, size_t need) {
|
|
233
|
+
if (need <= map->cap) return 0;
|
|
234
|
+
size_t new_cap = 0;
|
|
235
|
+
if (tf_size_grow_pow2(map->cap, need, 64, &new_cap) != TF_OK) return -1;
|
|
236
|
+
|
|
237
|
+
char **new_keys = tf_callocarray_checked(new_cap, sizeof(char *));
|
|
238
|
+
size_t *new_counts = tf_callocarray_checked(new_cap, sizeof(size_t));
|
|
239
|
+
size_t *new_errors = tf_callocarray_checked(new_cap, sizeof(size_t));
|
|
240
|
+
if (!new_keys || !new_counts || !new_errors) {
|
|
241
|
+
free(new_keys);
|
|
242
|
+
free(new_counts);
|
|
243
|
+
free(new_errors);
|
|
244
|
+
return -1;
|
|
73
245
|
}
|
|
74
|
-
if (map->count
|
|
75
|
-
size_t
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
246
|
+
if (map->count > 0) {
|
|
247
|
+
size_t key_bytes = 0;
|
|
248
|
+
size_t count_bytes = 0;
|
|
249
|
+
if (tf_size_mul(map->count, sizeof(char *), &key_bytes) != TF_OK ||
|
|
250
|
+
tf_size_mul(map->count, sizeof(size_t), &count_bytes) != TF_OK) {
|
|
251
|
+
free(new_keys);
|
|
252
|
+
free(new_counts);
|
|
253
|
+
free(new_errors);
|
|
254
|
+
return -1;
|
|
255
|
+
}
|
|
256
|
+
memcpy(new_keys, map->keys, key_bytes);
|
|
257
|
+
memcpy(new_counts, map->counts, count_bytes);
|
|
258
|
+
memcpy(new_errors, map->errors, count_bytes);
|
|
259
|
+
}
|
|
260
|
+
free(map->keys);
|
|
261
|
+
free(map->counts);
|
|
262
|
+
free(map->errors);
|
|
263
|
+
map->keys = new_keys;
|
|
264
|
+
map->counts = new_counts;
|
|
265
|
+
map->errors = new_errors;
|
|
266
|
+
map->cap = new_cap;
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
static int freq_map_add_entry(freq_map *map, const char *key,
|
|
271
|
+
size_t count, size_t error) {
|
|
272
|
+
size_t need = 0;
|
|
273
|
+
if (tf_size_add(map->count, 1, &need) != TF_OK ||
|
|
274
|
+
freq_map_reserve(map, need) != 0) {
|
|
275
|
+
return -1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
char *dup = strdup(key);
|
|
279
|
+
if (!dup) return -1;
|
|
280
|
+
size_t key_bytes_delta = 0;
|
|
281
|
+
size_t new_key_bytes = 0;
|
|
282
|
+
if (tf_size_add(strlen(dup), 1, &key_bytes_delta) != TF_OK ||
|
|
283
|
+
tf_size_add(map->key_bytes, key_bytes_delta, &new_key_bytes) != TF_OK) {
|
|
284
|
+
free(dup);
|
|
285
|
+
return -1;
|
|
79
286
|
}
|
|
80
|
-
map->keys[map->count] =
|
|
81
|
-
map->
|
|
287
|
+
map->keys[map->count] = dup;
|
|
288
|
+
map->key_bytes = new_key_bytes;
|
|
289
|
+
map->counts[map->count] = count;
|
|
290
|
+
map->errors[map->count] = error;
|
|
82
291
|
map->count++;
|
|
83
292
|
return 0;
|
|
84
293
|
}
|
|
85
294
|
|
|
295
|
+
static size_t freq_approx_victim(const freq_map *map) {
|
|
296
|
+
size_t victim = 0;
|
|
297
|
+
for (size_t i = 1; i < map->count; i++) {
|
|
298
|
+
if (map->counts[i] < map->counts[victim] ||
|
|
299
|
+
(map->counts[i] == map->counts[victim] &&
|
|
300
|
+
(map->errors[i] < map->errors[victim] ||
|
|
301
|
+
(map->errors[i] == map->errors[victim] &&
|
|
302
|
+
strcmp(map->keys[i], map->keys[victim]) < 0)))) {
|
|
303
|
+
victim = i;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return victim;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
static int freq_approx_replace(frequency_state *st, const char *key) {
|
|
310
|
+
freq_map *map = &st->map;
|
|
311
|
+
size_t victim = freq_approx_victim(map);
|
|
312
|
+
size_t old_count = map->counts[victim];
|
|
313
|
+
size_t new_count = 0;
|
|
314
|
+
size_t replacements = 0;
|
|
315
|
+
if (tf_size_add(old_count, 1, &new_count) != TF_OK ||
|
|
316
|
+
tf_size_add(st->approx_replacements, 1, &replacements) != TF_OK) {
|
|
317
|
+
return -1;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
char *dup = strdup(key);
|
|
321
|
+
if (!dup) return -1;
|
|
322
|
+
size_t old_key_bytes = 0;
|
|
323
|
+
size_t new_key_bytes_delta = 0;
|
|
324
|
+
size_t new_key_bytes = 0;
|
|
325
|
+
if (tf_size_add(strlen(map->keys[victim]), 1, &old_key_bytes) != TF_OK ||
|
|
326
|
+
tf_size_add(strlen(dup), 1, &new_key_bytes_delta) != TF_OK ||
|
|
327
|
+
map->key_bytes < old_key_bytes ||
|
|
328
|
+
tf_size_add(map->key_bytes - old_key_bytes, new_key_bytes_delta, &new_key_bytes) != TF_OK) {
|
|
329
|
+
free(dup);
|
|
330
|
+
return -1;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
free(map->keys[victim]);
|
|
334
|
+
map->keys[victim] = dup;
|
|
335
|
+
map->key_bytes = new_key_bytes;
|
|
336
|
+
map->counts[victim] = new_count;
|
|
337
|
+
map->errors[victim] = old_count;
|
|
338
|
+
st->approx_replacements = replacements;
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
static int freq_add(frequency_state *st, const char *key, const tf_batch *b, size_t row, size_t row_no, tf_side_channels *side) {
|
|
343
|
+
freq_map *map = &st->map;
|
|
344
|
+
if (st->overflow_other && st->other_label && strcmp(key, st->other_label) == 0) {
|
|
345
|
+
if (tf_size_add(st->other_count, 1, &st->other_count) != TF_OK) return -1;
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
size_t idx = 0;
|
|
349
|
+
if (freq_find(map, key, &idx)) {
|
|
350
|
+
if (tf_size_add(map->counts[idx], 1, &map->counts[idx]) != TF_OK) return -1;
|
|
351
|
+
return 0;
|
|
352
|
+
}
|
|
353
|
+
if (st->approximate) {
|
|
354
|
+
if (st->max_values == 0) return -1;
|
|
355
|
+
if (map->count < st->max_values) {
|
|
356
|
+
if (freq_map_add_entry(map, key, 1, 0) != 0) return -1;
|
|
357
|
+
} else {
|
|
358
|
+
if (freq_approx_replace(st, key) != 0) return -1;
|
|
359
|
+
}
|
|
360
|
+
if (frequency_check_state_bytes(st, side) != 0) return -1;
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
if (st->max_values > 0 && map->count >= st->max_values) {
|
|
364
|
+
if (st->overflow_other) {
|
|
365
|
+
if (tf_size_add(st->other_count, 1, &st->other_count) != TF_OK) return -1;
|
|
366
|
+
if (emit_frequency_overflow_audit(st, key, b, row, row_no, side) != TF_OK) return -1;
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
if (frequency_limit_error(st, side) != TF_OK) return -1;
|
|
370
|
+
return -1;
|
|
371
|
+
}
|
|
372
|
+
if (freq_map_add_entry(map, key, 1, 0) != 0) return -1;
|
|
373
|
+
if (frequency_check_state_bytes(st, side) != 0) return -1;
|
|
374
|
+
return 0;
|
|
375
|
+
}
|
|
376
|
+
|
|
86
377
|
static int frequency_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
87
378
|
tf_side_channels *side) {
|
|
88
|
-
(void)side;
|
|
89
379
|
frequency_state *st = self->state;
|
|
90
380
|
*out = NULL;
|
|
91
381
|
|
|
@@ -93,22 +383,46 @@ static int frequency_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
|
93
383
|
int *col_indices;
|
|
94
384
|
if (st->n_cols > 0) {
|
|
95
385
|
n_keys = st->n_cols;
|
|
96
|
-
col_indices =
|
|
97
|
-
|
|
386
|
+
col_indices = tf_mallocarray_checked(n_keys ? n_keys : 1, sizeof(int));
|
|
387
|
+
if (!col_indices) return TF_ERROR;
|
|
388
|
+
for (size_t k = 0; k < n_keys; k++) {
|
|
98
389
|
col_indices[k] = tf_batch_col_index(in, st->cols[k]);
|
|
390
|
+
if (col_indices[k] < 0) {
|
|
391
|
+
char msg[256];
|
|
392
|
+
snprintf(msg, sizeof(msg), "frequency: column '%s' not found",
|
|
393
|
+
st->cols[k] ? st->cols[k] : "");
|
|
394
|
+
int err_rc = frequency_write_error(side, msg);
|
|
395
|
+
free(col_indices);
|
|
396
|
+
if (err_rc != TF_OK) return TF_ERROR;
|
|
397
|
+
return TF_ERROR;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
99
400
|
} else {
|
|
100
401
|
n_keys = in->n_cols;
|
|
101
|
-
col_indices =
|
|
402
|
+
col_indices = tf_mallocarray_checked(n_keys ? n_keys : 1, sizeof(int));
|
|
403
|
+
if (!col_indices) return TF_ERROR;
|
|
102
404
|
for (size_t k = 0; k < n_keys; k++) col_indices[k] = (int)k;
|
|
103
405
|
}
|
|
104
406
|
|
|
407
|
+
size_t row_base = st->row_index;
|
|
105
408
|
for (size_t r = 0; r < in->n_rows; r++) {
|
|
106
409
|
char *key = build_freq_key(in, r, col_indices, n_keys);
|
|
107
410
|
if (!key) { free(col_indices); return TF_ERROR; }
|
|
108
|
-
|
|
411
|
+
size_t row_no = 0;
|
|
412
|
+
if (tf_size_add(row_base, r, &row_no) != TF_OK ||
|
|
413
|
+
tf_size_add(row_no, 1, &row_no) != TF_OK ||
|
|
414
|
+
freq_add(st, key, in, r, row_no, side) != 0) {
|
|
415
|
+
free(key);
|
|
416
|
+
free(col_indices);
|
|
417
|
+
return TF_ERROR;
|
|
418
|
+
}
|
|
109
419
|
free(key);
|
|
110
420
|
}
|
|
111
421
|
|
|
422
|
+
if (tf_size_add(st->row_index, in->n_rows, &st->row_index) != TF_OK) {
|
|
423
|
+
free(col_indices);
|
|
424
|
+
return TF_ERROR;
|
|
425
|
+
}
|
|
112
426
|
free(col_indices);
|
|
113
427
|
return TF_OK;
|
|
114
428
|
}
|
|
@@ -118,75 +432,289 @@ static int frequency_flush(tf_step *self, tf_batch **out, tf_side_channels *side
|
|
|
118
432
|
frequency_state *st = self->state;
|
|
119
433
|
*out = NULL;
|
|
120
434
|
|
|
121
|
-
|
|
435
|
+
size_t n = 0;
|
|
436
|
+
if (tf_size_add(st->map.count, st->other_count > 0 ? 1u : 0u, &n) != TF_OK) return TF_ERROR;
|
|
437
|
+
if (n == 0) return TF_OK;
|
|
122
438
|
|
|
123
|
-
|
|
124
|
-
size_t
|
|
125
|
-
size_t *
|
|
126
|
-
|
|
439
|
+
const char **keys = tf_mallocarray_checked(n, sizeof(char *));
|
|
440
|
+
size_t *counts = tf_mallocarray_checked(n, sizeof(size_t));
|
|
441
|
+
size_t *errors = tf_mallocarray_checked(n, sizeof(size_t));
|
|
442
|
+
size_t *indices = tf_mallocarray_checked(n, sizeof(size_t));
|
|
443
|
+
if (!keys || !counts || !errors || !indices) {
|
|
444
|
+
free(keys); free(counts); free(errors); free(indices);
|
|
445
|
+
return TF_ERROR;
|
|
446
|
+
}
|
|
447
|
+
for (size_t i = 0; i < st->map.count; i++) {
|
|
448
|
+
keys[i] = st->map.keys[i];
|
|
449
|
+
counts[i] = st->map.counts[i];
|
|
450
|
+
errors[i] = st->map.errors[i];
|
|
451
|
+
}
|
|
452
|
+
if (st->other_count > 0) {
|
|
453
|
+
keys[st->map.count] = st->other_label ? st->other_label : "__other__";
|
|
454
|
+
counts[st->map.count] = st->other_count;
|
|
455
|
+
errors[st->map.count] = 0;
|
|
456
|
+
}
|
|
127
457
|
for (size_t i = 0; i < n; i++) indices[i] = i;
|
|
128
458
|
|
|
129
|
-
/* Simple sort by count */
|
|
459
|
+
/* Simple sort by count desc, error asc, then value asc for deterministic ties. */
|
|
130
460
|
for (size_t i = 0; i < n - 1; i++) {
|
|
131
461
|
for (size_t j = i + 1; j < n; j++) {
|
|
132
|
-
|
|
462
|
+
size_t a = indices[i], b = indices[j];
|
|
463
|
+
if (counts[b] > counts[a] ||
|
|
464
|
+
(counts[b] == counts[a] &&
|
|
465
|
+
(errors[b] < errors[a] ||
|
|
466
|
+
(errors[b] == errors[a] && strcmp(keys[b], keys[a]) < 0)))) {
|
|
133
467
|
size_t tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp;
|
|
134
468
|
}
|
|
135
469
|
}
|
|
136
470
|
}
|
|
137
471
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
tf_batch_set_schema(ob,
|
|
472
|
+
size_t out_cols = st->approximate ? 3u : 2u;
|
|
473
|
+
tf_batch *ob = tf_batch_create(out_cols, n);
|
|
474
|
+
if (!ob) { free(keys); free(counts); free(errors); free(indices); return TF_ERROR; }
|
|
475
|
+
if (tf_batch_set_schema(ob, 0, "value", TF_TYPE_STRING) != TF_OK ||
|
|
476
|
+
tf_batch_set_schema(ob, 1, "count", TF_TYPE_INT64) != TF_OK) {
|
|
477
|
+
tf_batch_free(ob);
|
|
478
|
+
free(keys); free(counts); free(errors); free(indices);
|
|
479
|
+
return TF_ERROR;
|
|
480
|
+
}
|
|
481
|
+
if (st->approximate &&
|
|
482
|
+
tf_batch_set_schema(ob, 2, "error", TF_TYPE_INT64) != TF_OK) {
|
|
483
|
+
tf_batch_free(ob);
|
|
484
|
+
free(keys); free(counts); free(errors); free(indices);
|
|
485
|
+
return TF_ERROR;
|
|
486
|
+
}
|
|
142
487
|
|
|
143
488
|
for (size_t i = 0; i < n; i++) {
|
|
144
|
-
tf_batch_ensure_capacity(ob, i + 1)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
489
|
+
if (tf_batch_ensure_capacity(ob, i + 1) != TF_OK ||
|
|
490
|
+
tf_batch_set_string(ob, i, 0, keys[indices[i]]) != TF_OK ||
|
|
491
|
+
tf_batch_set_int64(ob, i, 1, (int64_t)counts[indices[i]]) != TF_OK) {
|
|
492
|
+
tf_batch_free(ob);
|
|
493
|
+
free(keys); free(counts); free(errors); free(indices);
|
|
494
|
+
return TF_ERROR;
|
|
495
|
+
}
|
|
496
|
+
if (st->approximate &&
|
|
497
|
+
tf_batch_set_int64(ob, i, 2, (int64_t)errors[indices[i]]) != TF_OK) {
|
|
498
|
+
tf_batch_free(ob);
|
|
499
|
+
free(keys); free(counts); free(errors); free(indices);
|
|
500
|
+
return TF_ERROR;
|
|
501
|
+
}
|
|
502
|
+
if (tf_batch_expose_row(ob, i) != TF_OK) {
|
|
503
|
+
tf_batch_free(ob);
|
|
504
|
+
free(keys); free(counts); free(errors); free(indices);
|
|
505
|
+
return TF_ERROR;
|
|
506
|
+
}
|
|
148
507
|
}
|
|
149
508
|
|
|
150
|
-
free(indices);
|
|
509
|
+
free(keys); free(counts); free(errors); free(indices);
|
|
151
510
|
*out = ob;
|
|
152
511
|
return TF_OK;
|
|
153
512
|
}
|
|
154
513
|
|
|
155
|
-
static
|
|
514
|
+
static size_t frequency_retained_state_bytes(const frequency_state *st) {
|
|
515
|
+
if (!st) return 0;
|
|
516
|
+
size_t slot_width = 0;
|
|
517
|
+
size_t slot_bytes = 0;
|
|
518
|
+
size_t bytes = st->map.key_bytes;
|
|
519
|
+
if (tf_size_add(sizeof(char *), sizeof(size_t), &slot_width) != TF_OK ||
|
|
520
|
+
tf_size_add(slot_width, sizeof(size_t), &slot_width) != TF_OK ||
|
|
521
|
+
tf_size_mul(st->map.cap, slot_width, &slot_bytes) != TF_OK ||
|
|
522
|
+
tf_size_add(bytes, slot_bytes, &bytes) != TF_OK) {
|
|
523
|
+
return SIZE_MAX;
|
|
524
|
+
}
|
|
525
|
+
if (st->other_label) {
|
|
526
|
+
size_t label_bytes = 0;
|
|
527
|
+
if (tf_size_add(strlen(st->other_label), 1, &label_bytes) != TF_OK ||
|
|
528
|
+
tf_size_add(bytes, label_bytes, &bytes) != TF_OK) {
|
|
529
|
+
return SIZE_MAX;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return bytes;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
static int frequency_append_stats(tf_step *self, tf_buffer *out) {
|
|
536
|
+
if (!self || !self->state || !out) return TF_ERROR;
|
|
156
537
|
frequency_state *st = self->state;
|
|
538
|
+
char buf[420];
|
|
539
|
+
snprintf(buf, sizeof(buf),
|
|
540
|
+
",\"tracked_values\":%zu,\"tracked_key_bytes\":%zu,"
|
|
541
|
+
"\"overflow_count\":%zu,\"retained_state_bytes\":%zu,\"max_state_bytes\":%zu",
|
|
542
|
+
st->map.count, st->map.key_bytes, st->other_count,
|
|
543
|
+
frequency_retained_state_bytes(st), st->max_state_bytes);
|
|
544
|
+
if (tf_buffer_write_str(out, buf) != TF_OK) return TF_ERROR;
|
|
545
|
+
if (st->approximate) {
|
|
546
|
+
snprintf(buf, sizeof(buf),
|
|
547
|
+
",\"approximate\":true,\"approx_replacements\":%zu",
|
|
548
|
+
st->approx_replacements);
|
|
549
|
+
if (tf_buffer_write_str(out, buf) != TF_OK) return TF_ERROR;
|
|
550
|
+
}
|
|
551
|
+
return TF_OK;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
static void frequency_state_free(frequency_state *st) {
|
|
157
555
|
if (st) {
|
|
158
|
-
|
|
556
|
+
tf_audit_options_free(&st->audit_opts);
|
|
557
|
+
if (st->cols) {
|
|
558
|
+
for (size_t i = 0; i < st->n_cols; i++) free(st->cols[i]);
|
|
559
|
+
}
|
|
159
560
|
free(st->cols);
|
|
561
|
+
free(st->other_label);
|
|
160
562
|
for (size_t i = 0; i < st->map.count; i++) free(st->map.keys[i]);
|
|
161
|
-
free(st->map.keys); free(st->map.counts);
|
|
563
|
+
free(st->map.keys); free(st->map.counts); free(st->map.errors);
|
|
162
564
|
free(st);
|
|
163
565
|
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
static void frequency_destroy(tf_step *self) {
|
|
569
|
+
if (self) frequency_state_free(self->state);
|
|
164
570
|
free(self);
|
|
165
571
|
}
|
|
166
572
|
|
|
167
573
|
tf_step *tf_frequency_create(const cJSON *args) {
|
|
168
574
|
frequency_state *st = calloc(1, sizeof(frequency_state));
|
|
169
575
|
if (!st) return NULL;
|
|
576
|
+
tf_audit_options_init(&st->audit_opts, 1);
|
|
577
|
+
st->audit_limit = 1000;
|
|
170
578
|
|
|
171
579
|
if (args) {
|
|
580
|
+
size_t parsed_size = 0;
|
|
581
|
+
int has_max_values = tf_json_get_size_arg(args, "max_values",
|
|
582
|
+
1, TF_MAX_COUNT_ARG,
|
|
583
|
+
&parsed_size, "frequency");
|
|
584
|
+
if (has_max_values < 0) { frequency_state_free(st); return NULL; }
|
|
585
|
+
if (has_max_values > 0) st->max_values = parsed_size;
|
|
586
|
+
|
|
587
|
+
int has_max_state = tf_json_get_size_arg(args, "max_state_bytes",
|
|
588
|
+
1, TF_MAX_STATE_BYTES,
|
|
589
|
+
&parsed_size, "frequency");
|
|
590
|
+
if (has_max_state < 0) { frequency_state_free(st); return NULL; }
|
|
591
|
+
if (has_max_state > 0) st->max_state_bytes = parsed_size;
|
|
592
|
+
|
|
593
|
+
int mode_approx = -1;
|
|
594
|
+
cJSON *mode = cJSON_GetObjectItemCaseSensitive(args, "mode");
|
|
595
|
+
if (mode) {
|
|
596
|
+
if (!cJSON_IsString(mode) || !mode->valuestring) {
|
|
597
|
+
tf_set_last_error("frequency: mode must be exact or approx");
|
|
598
|
+
frequency_state_free(st);
|
|
599
|
+
return NULL;
|
|
600
|
+
}
|
|
601
|
+
if (strcmp(mode->valuestring, "approx") == 0) {
|
|
602
|
+
mode_approx = 1;
|
|
603
|
+
} else if (strcmp(mode->valuestring, "exact") == 0) {
|
|
604
|
+
mode_approx = 0;
|
|
605
|
+
} else {
|
|
606
|
+
tf_set_last_error("frequency: mode must be exact or approx");
|
|
607
|
+
frequency_state_free(st);
|
|
608
|
+
return NULL;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
cJSON *approx = cJSON_GetObjectItemCaseSensitive(args, "approx");
|
|
612
|
+
if (approx) {
|
|
613
|
+
if (!cJSON_IsBool(approx)) {
|
|
614
|
+
tf_set_last_error("frequency: approx must be true or false");
|
|
615
|
+
frequency_state_free(st);
|
|
616
|
+
return NULL;
|
|
617
|
+
}
|
|
618
|
+
int approx_value = cJSON_IsTrue(approx) ? 1 : 0;
|
|
619
|
+
if (mode_approx >= 0 && mode_approx != approx_value) {
|
|
620
|
+
tf_set_last_error("frequency: mode and approx conflict");
|
|
621
|
+
frequency_state_free(st);
|
|
622
|
+
return NULL;
|
|
623
|
+
}
|
|
624
|
+
st->approximate = approx_value;
|
|
625
|
+
} else if (mode_approx >= 0) {
|
|
626
|
+
st->approximate = mode_approx;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
cJSON *audit_j = cJSON_GetObjectItemCaseSensitive(args, "audit");
|
|
630
|
+
st->audit = cJSON_IsTrue(audit_j) ? 1 : 0;
|
|
631
|
+
cJSON *audit_limit_j = cJSON_GetObjectItemCaseSensitive(args, "audit_limit");
|
|
632
|
+
if (!audit_limit_j) audit_limit_j = cJSON_GetObjectItemCaseSensitive(args, "auditLimit");
|
|
633
|
+
if (audit_limit_j) {
|
|
634
|
+
if (tf_json_get_size_arg_any(args, "audit_limit", "auditLimit",
|
|
635
|
+
1, TF_MAX_AUDIT_RECORDS,
|
|
636
|
+
&parsed_size, "frequency") < 0) {
|
|
637
|
+
frequency_state_free(st);
|
|
638
|
+
return NULL;
|
|
639
|
+
}
|
|
640
|
+
st->audit_limit = parsed_size;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (tf_audit_options_parse(&st->audit_opts, args, "frequency") != TF_OK) {
|
|
644
|
+
frequency_state_free(st);
|
|
645
|
+
return NULL;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
cJSON *overflow = cJSON_GetObjectItemCaseSensitive(args, "overflow");
|
|
649
|
+
if (cJSON_IsString(overflow)) {
|
|
650
|
+
if (strcmp(overflow->valuestring, "other") == 0) {
|
|
651
|
+
st->overflow_other = 1;
|
|
652
|
+
} else if (strcmp(overflow->valuestring, "error") != 0) {
|
|
653
|
+
tf_set_last_error("frequency: overflow must be error or other");
|
|
654
|
+
frequency_state_free(st);
|
|
655
|
+
return NULL;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
cJSON *other = cJSON_GetObjectItemCaseSensitive(args, "other");
|
|
659
|
+
const char *other_label = cJSON_IsString(other) ? other->valuestring : "__other__";
|
|
660
|
+
if (st->approximate) {
|
|
661
|
+
if (st->max_values == 0) {
|
|
662
|
+
tf_set_last_error("frequency: mode=approx requires max_values");
|
|
663
|
+
frequency_state_free(st);
|
|
664
|
+
return NULL;
|
|
665
|
+
}
|
|
666
|
+
if (st->overflow_other) {
|
|
667
|
+
tf_set_last_error("frequency: mode=approx and overflow=other are mutually exclusive");
|
|
668
|
+
frequency_state_free(st);
|
|
669
|
+
return NULL;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (st->overflow_other) {
|
|
673
|
+
if (st->max_values == 0) {
|
|
674
|
+
tf_set_last_error("frequency: overflow=other requires max_values");
|
|
675
|
+
frequency_state_free(st);
|
|
676
|
+
return NULL;
|
|
677
|
+
}
|
|
678
|
+
if (!other_label || other_label[0] == '\0') {
|
|
679
|
+
tf_set_last_error("frequency: other label cannot be empty");
|
|
680
|
+
frequency_state_free(st);
|
|
681
|
+
return NULL;
|
|
682
|
+
}
|
|
683
|
+
st->other_label = strdup(other_label);
|
|
684
|
+
if (!st->other_label) { frequency_state_free(st); return NULL; }
|
|
685
|
+
}
|
|
686
|
+
|
|
172
687
|
cJSON *columns = cJSON_GetObjectItemCaseSensitive(args, "columns");
|
|
173
|
-
if (columns
|
|
688
|
+
if (columns) {
|
|
689
|
+
if (!cJSON_IsArray(columns)) {
|
|
690
|
+
tf_set_last_error("frequency: columns must be an array");
|
|
691
|
+
frequency_state_free(st);
|
|
692
|
+
return NULL;
|
|
693
|
+
}
|
|
174
694
|
int n = cJSON_GetArraySize(columns);
|
|
175
695
|
if (n > 0) {
|
|
176
|
-
st->cols =
|
|
177
|
-
st->
|
|
696
|
+
st->cols = tf_callocarray_checked((size_t)n, sizeof(char *));
|
|
697
|
+
if (!st->cols) { frequency_state_free(st); return NULL; }
|
|
698
|
+
st->n_cols = (size_t)n;
|
|
178
699
|
for (int i = 0; i < n; i++) {
|
|
179
700
|
cJSON *item = cJSON_GetArrayItem(columns, i);
|
|
180
|
-
if (cJSON_IsString(item)
|
|
701
|
+
if (!cJSON_IsString(item) || !item->valuestring || !item->valuestring[0]) {
|
|
702
|
+
tf_set_last_error("frequency: column names must be non-empty strings");
|
|
703
|
+
frequency_state_free(st);
|
|
704
|
+
return NULL;
|
|
705
|
+
}
|
|
706
|
+
st->cols[i] = strdup(item->valuestring);
|
|
707
|
+
if (!st->cols[i]) { frequency_state_free(st); return NULL; }
|
|
181
708
|
}
|
|
182
709
|
}
|
|
183
710
|
}
|
|
184
711
|
}
|
|
185
712
|
|
|
186
|
-
tf_step *step =
|
|
187
|
-
if (!step) {
|
|
713
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
714
|
+
if (!step) { frequency_state_free(st); return NULL; }
|
|
188
715
|
step->process = frequency_process;
|
|
189
716
|
step->flush = frequency_flush;
|
|
717
|
+
step->append_stats = frequency_append_stats;
|
|
190
718
|
step->destroy = frequency_destroy;
|
|
191
719
|
step->state = st;
|
|
192
720
|
return step;
|