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_cast.c
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* op_cast.c
|
|
2
|
+
* op_cast.c - Type conversion.
|
|
3
3
|
*
|
|
4
4
|
* Config: {"mapping": {"age": "int", "score": "float", "active": "bool"}}
|
|
5
5
|
*/
|
|
@@ -7,16 +7,80 @@
|
|
|
7
7
|
#include "internal.h"
|
|
8
8
|
#include "date_utils.h"
|
|
9
9
|
#include "cJSON.h"
|
|
10
|
+
#include <errno.h>
|
|
10
11
|
#include <stdlib.h>
|
|
11
12
|
#include <string.h>
|
|
12
13
|
#include <stdio.h>
|
|
13
14
|
|
|
15
|
+
/* Cast is row-local. Audit records are opt-in and capped so parse/coercion
|
|
16
|
+
* diagnostics cannot become an unbounded second output stream. */
|
|
17
|
+
typedef enum {
|
|
18
|
+
CAST_ON_ERROR_COERCE = 0,
|
|
19
|
+
CAST_ON_ERROR_FAIL,
|
|
20
|
+
CAST_ON_ERROR_NULL,
|
|
21
|
+
} cast_error_policy;
|
|
22
|
+
|
|
14
23
|
typedef struct {
|
|
15
24
|
char **col_names;
|
|
16
25
|
tf_type *target_types;
|
|
17
26
|
size_t n;
|
|
27
|
+
size_t row_index;
|
|
28
|
+
size_t audit_limit;
|
|
29
|
+
size_t audit_emitted;
|
|
30
|
+
size_t coercion_failures;
|
|
31
|
+
size_t coercion_nulled;
|
|
32
|
+
int audit;
|
|
33
|
+
cast_error_policy on_error;
|
|
34
|
+
tf_audit_options audit_opts;
|
|
18
35
|
} cast_state;
|
|
19
36
|
|
|
37
|
+
typedef struct {
|
|
38
|
+
size_t col;
|
|
39
|
+
tf_type src_t;
|
|
40
|
+
tf_type dst_t;
|
|
41
|
+
const char *event;
|
|
42
|
+
const char *reason;
|
|
43
|
+
} cast_audit_event;
|
|
44
|
+
|
|
45
|
+
static const char *cast_error_policy_name(cast_error_policy p) {
|
|
46
|
+
switch (p) {
|
|
47
|
+
case CAST_ON_ERROR_COERCE: return "coerce";
|
|
48
|
+
case CAST_ON_ERROR_FAIL: return "fail";
|
|
49
|
+
case CAST_ON_ERROR_NULL: return "null";
|
|
50
|
+
default: return "coerce";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static int parse_cast_error_policy(const char *s, cast_error_policy *out) {
|
|
55
|
+
if (!s || !s[0] || strcmp(s, "coerce") == 0 || strcmp(s, "legacy") == 0 ||
|
|
56
|
+
strcmp(s, "default") == 0) {
|
|
57
|
+
*out = CAST_ON_ERROR_COERCE;
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
if (strcmp(s, "fail") == 0 || strcmp(s, "error") == 0 || strcmp(s, "strict") == 0) {
|
|
61
|
+
*out = CAST_ON_ERROR_FAIL;
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
if (strcmp(s, "null") == 0 || strcmp(s, "nulling") == 0) {
|
|
65
|
+
*out = CAST_ON_ERROR_NULL;
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static const char *cast_type_name(tf_type t) {
|
|
72
|
+
switch (t) {
|
|
73
|
+
case TF_TYPE_BOOL: return "bool";
|
|
74
|
+
case TF_TYPE_INT64: return "int";
|
|
75
|
+
case TF_TYPE_FLOAT64: return "float";
|
|
76
|
+
case TF_TYPE_STRING: return "string";
|
|
77
|
+
case TF_TYPE_DATE: return "date";
|
|
78
|
+
case TF_TYPE_TIMESTAMP: return "timestamp";
|
|
79
|
+
case TF_TYPE_NULL: return "null";
|
|
80
|
+
default: return "unknown";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
20
84
|
static tf_type parse_type(const char *s) {
|
|
21
85
|
if (strcmp(s, "int") == 0 || strcmp(s, "int64") == 0) return TF_TYPE_INT64;
|
|
22
86
|
if (strcmp(s, "float") == 0 || strcmp(s, "float64") == 0) return TF_TYPE_FLOAT64;
|
|
@@ -27,14 +91,125 @@ static tf_type parse_type(const char *s) {
|
|
|
27
91
|
return TF_TYPE_NULL;
|
|
28
92
|
}
|
|
29
93
|
|
|
94
|
+
static int only_ascii_ws(const char *s) {
|
|
95
|
+
if (!s) return 1;
|
|
96
|
+
while (*s) {
|
|
97
|
+
if (*s != ' ' && *s != '\t' && *s != '\n' && *s != '\r' && *s != '\f' && *s != '\v') return 0;
|
|
98
|
+
s++;
|
|
99
|
+
}
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static int strict_int_parse_ok(const char *s, const char **reason) {
|
|
104
|
+
char *end = NULL;
|
|
105
|
+
errno = 0;
|
|
106
|
+
(void)strtoll(s ? s : "", &end, 10);
|
|
107
|
+
if (!s || end == s) { if (reason) *reason = "invalid_integer"; return 0; }
|
|
108
|
+
if (errno == ERANGE) { if (reason) *reason = "integer_out_of_range"; return 0; }
|
|
109
|
+
if (!only_ascii_ws(end)) { if (reason) *reason = "trailing_characters"; return 0; }
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static int strict_float_parse_ok(const char *s, const char **reason) {
|
|
114
|
+
char *end = NULL;
|
|
115
|
+
errno = 0;
|
|
116
|
+
(void)strtod(s ? s : "", &end);
|
|
117
|
+
if (!s || end == s) { if (reason) *reason = "invalid_float"; return 0; }
|
|
118
|
+
if (errno == ERANGE) { if (reason) *reason = "float_out_of_range"; return 0; }
|
|
119
|
+
if (!only_ascii_ws(end)) { if (reason) *reason = "trailing_characters"; return 0; }
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static int strict_date_parse_ok(const char *s, const char **reason) {
|
|
124
|
+
int y, m, d, nread = 0;
|
|
125
|
+
if (s && sscanf(s, "%d-%d-%d%n", &y, &m, &d, &nread) == 3) {
|
|
126
|
+
if (only_ascii_ws(s + nread)) return 1;
|
|
127
|
+
if (reason) *reason = "trailing_characters";
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
if (reason) *reason = "invalid_date";
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static int strict_timestamp_parse_ok(const char *s, const char **reason) {
|
|
135
|
+
int y, mo, d, h, mi, se, nread = 0;
|
|
136
|
+
if (s && sscanf(s, "%d-%d-%dT%d:%d:%d%n", &y, &mo, &d, &h, &mi, &se, &nread) == 6) {
|
|
137
|
+
if (only_ascii_ws(s + nread)) return 1;
|
|
138
|
+
if (reason) *reason = "trailing_characters";
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
nread = 0;
|
|
142
|
+
if (s && sscanf(s, "%d-%d-%d %d:%d:%d%n", &y, &mo, &d, &h, &mi, &se, &nread) == 6) {
|
|
143
|
+
if (only_ascii_ws(s + nread)) return 1;
|
|
144
|
+
if (reason) *reason = "trailing_characters";
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
nread = 0;
|
|
148
|
+
if (s && sscanf(s, "%d-%d-%d%n", &y, &mo, &d, &nread) == 3) {
|
|
149
|
+
if (only_ascii_ws(s + nread)) return 1;
|
|
150
|
+
if (reason) *reason = "trailing_characters";
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
if (reason) *reason = "invalid_timestamp";
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static int emit_cast_audit(cast_state *st, const tf_batch *before_b, const tf_batch *after_b,
|
|
158
|
+
size_t row, size_t col, size_t row_no, tf_type src_t, tf_type dst_t,
|
|
159
|
+
const char *event, const char *reason, tf_side_channels *side) {
|
|
160
|
+
if (!st->audit || st->audit_emitted >= st->audit_limit || !side || !side->stats) return TF_OK;
|
|
161
|
+
cJSON *obj = cJSON_CreateObject();
|
|
162
|
+
if (!obj) return TF_ERROR;
|
|
163
|
+
int rc = TF_ERROR;
|
|
164
|
+
if (tf_json_add_string(obj, "type", "audit") != TF_OK ||
|
|
165
|
+
tf_json_add_string(obj, "op", "cast") != TF_OK ||
|
|
166
|
+
tf_json_add_string(obj, "event", event ? event : "value_changed") != TF_OK ||
|
|
167
|
+
tf_json_add_string(obj, "reason", reason ? reason : "type_cast") != TF_OK ||
|
|
168
|
+
tf_json_add_string(obj, "channel", "audit") != TF_OK) {
|
|
169
|
+
goto done;
|
|
170
|
+
}
|
|
171
|
+
const char *column_name = before_b->col_names[col] ? before_b->col_names[col] : "";
|
|
172
|
+
if (tf_json_add_string(obj, "column", column_name) != TF_OK ||
|
|
173
|
+
tf_json_add_string(obj, "from_type", cast_type_name(src_t)) != TF_OK ||
|
|
174
|
+
tf_json_add_string(obj, "to_type", cast_type_name(dst_t)) != TF_OK ||
|
|
175
|
+
tf_json_add_string(obj, "expected", cast_type_name(dst_t)) != TF_OK ||
|
|
176
|
+
tf_json_add_string(obj, "on_error", cast_error_policy_name(st->on_error)) != TF_OK) {
|
|
177
|
+
goto done;
|
|
178
|
+
}
|
|
179
|
+
if (src_t == TF_TYPE_STRING && !tf_batch_is_null(before_b, row, col)) {
|
|
180
|
+
if (tf_json_add_audit_string(obj, "actual", &st->audit_opts, column_name,
|
|
181
|
+
tf_batch_get_string(before_b, row, col)) != TF_OK)
|
|
182
|
+
goto done;
|
|
183
|
+
}
|
|
184
|
+
if (tf_json_add_string(obj, "action", "cast") != TF_OK ||
|
|
185
|
+
tf_json_add_number(obj, "row", (double)row_no) != TF_OK) {
|
|
186
|
+
goto done;
|
|
187
|
+
}
|
|
188
|
+
cJSON *before = tf_audit_cell_to_json(before_b, row, col, &st->audit_opts);
|
|
189
|
+
if (!before || tf_json_add_item(obj, "before", before) != TF_OK) goto done;
|
|
190
|
+
cJSON *after = tf_audit_cell_to_json(after_b, row, col, &st->audit_opts);
|
|
191
|
+
if (!after || tf_json_add_item(obj, "after", after) != TF_OK) goto done;
|
|
192
|
+
cJSON *row_obj = tf_audit_row_to_json(after_b, row, &st->audit_opts);
|
|
193
|
+
if (row_obj) {
|
|
194
|
+
if (tf_json_add_item(obj, "data", row_obj) != TF_OK) goto done;
|
|
195
|
+
} else if (st->audit_opts.include_row) {
|
|
196
|
+
goto done;
|
|
197
|
+
}
|
|
198
|
+
rc = tf_buffer_write_json_line(side->stats, obj);
|
|
199
|
+
done:
|
|
200
|
+
cJSON_Delete(obj);
|
|
201
|
+
if (rc == TF_OK) st->audit_emitted++;
|
|
202
|
+
return rc;
|
|
203
|
+
}
|
|
204
|
+
|
|
30
205
|
static int cast_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
31
206
|
tf_side_channels *side) {
|
|
32
|
-
(void)side;
|
|
33
207
|
cast_state *st = self->state;
|
|
34
208
|
*out = NULL;
|
|
209
|
+
size_t row_base = st->row_index;
|
|
35
210
|
|
|
36
|
-
|
|
37
|
-
|
|
211
|
+
tf_type *out_types = tf_mallocarray_checked(in->n_cols > 0 ? in->n_cols : 1,
|
|
212
|
+
sizeof(tf_type));
|
|
38
213
|
if (!out_types) return TF_ERROR;
|
|
39
214
|
for (size_t c = 0; c < in->n_cols; c++) out_types[c] = in->col_types[c];
|
|
40
215
|
for (size_t k = 0; k < st->n; k++) {
|
|
@@ -42,84 +217,116 @@ static int cast_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
|
42
217
|
if (ci >= 0) out_types[ci] = st->target_types[k];
|
|
43
218
|
}
|
|
44
219
|
|
|
220
|
+
cast_audit_event *audit_events = NULL;
|
|
45
221
|
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
46
222
|
if (!ob) { free(out_types); return TF_ERROR; }
|
|
47
|
-
for (size_t c = 0; c < in->n_cols; c++)
|
|
48
|
-
tf_batch_set_schema(ob, c, in->col_names[c], out_types[c]);
|
|
223
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
224
|
+
if (tf_batch_set_schema(ob, c, in->col_names[c], out_types[c]) != TF_OK) goto fail;
|
|
225
|
+
}
|
|
226
|
+
if (st->audit) {
|
|
227
|
+
audit_events = tf_callocarray_checked(in->n_cols > 0 ? in->n_cols : 1,
|
|
228
|
+
sizeof(cast_audit_event));
|
|
229
|
+
if (!audit_events) goto fail;
|
|
230
|
+
}
|
|
49
231
|
|
|
50
232
|
for (size_t r = 0; r < in->n_rows; r++) {
|
|
51
|
-
|
|
233
|
+
size_t needed_rows = 0;
|
|
234
|
+
if (tf_size_add(r, 1, &needed_rows) != TF_OK ||
|
|
235
|
+
tf_batch_ensure_capacity(ob, needed_rows) != TF_OK) {
|
|
236
|
+
goto fail;
|
|
237
|
+
}
|
|
238
|
+
size_t audit_count = 0;
|
|
239
|
+
int row_failed = 0;
|
|
240
|
+
size_t row_failed_col = 0;
|
|
241
|
+
const char *row_failed_reason = NULL;
|
|
52
242
|
for (size_t c = 0; c < in->n_cols; c++) {
|
|
243
|
+
if (row_failed) {
|
|
244
|
+
if (tf_batch_is_null(in, r, c)) {
|
|
245
|
+
if (tf_batch_set_null(ob, r, c) != TF_OK) goto fail;
|
|
246
|
+
} else if (in->col_types[c] == out_types[c]) {
|
|
247
|
+
if (tf_batch_copy_cell(ob, r, c, in, r, c) != TF_OK) goto fail;
|
|
248
|
+
} else if (tf_batch_set_null(ob, r, c) != TF_OK) {
|
|
249
|
+
goto fail;
|
|
250
|
+
}
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
53
253
|
if (tf_batch_is_null(in, r, c)) {
|
|
54
|
-
tf_batch_set_null(ob, r, c);
|
|
254
|
+
if (tf_batch_set_null(ob, r, c) != TF_OK) goto fail;
|
|
55
255
|
continue;
|
|
56
256
|
}
|
|
57
257
|
tf_type src_t = in->col_types[c];
|
|
58
258
|
tf_type dst_t = out_types[c];
|
|
259
|
+
const char *failure_reason = NULL;
|
|
260
|
+
int write_rc = TF_OK;
|
|
59
261
|
|
|
60
262
|
if (src_t == dst_t) {
|
|
61
|
-
|
|
62
|
-
switch (src_t) {
|
|
63
|
-
case TF_TYPE_BOOL: tf_batch_set_bool(ob, r, c, tf_batch_get_bool(in, r, c)); break;
|
|
64
|
-
case TF_TYPE_INT64: tf_batch_set_int64(ob, r, c, tf_batch_get_int64(in, r, c)); break;
|
|
65
|
-
case TF_TYPE_FLOAT64: tf_batch_set_float64(ob, r, c, tf_batch_get_float64(in, r, c)); break;
|
|
66
|
-
case TF_TYPE_STRING: tf_batch_set_string(ob, r, c, tf_batch_get_string(in, r, c)); break;
|
|
67
|
-
case TF_TYPE_DATE: tf_batch_set_date(ob, r, c, tf_batch_get_date(in, r, c)); break;
|
|
68
|
-
case TF_TYPE_TIMESTAMP: tf_batch_set_timestamp(ob, r, c, tf_batch_get_timestamp(in, r, c)); break;
|
|
69
|
-
default: tf_batch_set_null(ob, r, c); break;
|
|
70
|
-
}
|
|
263
|
+
if (tf_batch_copy_cell(ob, r, c, in, r, c) != TF_OK) goto fail;
|
|
71
264
|
continue;
|
|
72
265
|
}
|
|
73
266
|
|
|
74
|
-
/* Conversion */
|
|
75
267
|
if (dst_t == TF_TYPE_STRING) {
|
|
76
|
-
char buf[64];
|
|
77
268
|
switch (src_t) {
|
|
78
|
-
case TF_TYPE_INT64:
|
|
79
|
-
case TF_TYPE_FLOAT64:
|
|
80
|
-
case TF_TYPE_BOOL:
|
|
81
|
-
case TF_TYPE_DATE:
|
|
82
|
-
case TF_TYPE_TIMESTAMP:
|
|
83
|
-
|
|
269
|
+
case TF_TYPE_INT64:
|
|
270
|
+
case TF_TYPE_FLOAT64:
|
|
271
|
+
case TF_TYPE_BOOL:
|
|
272
|
+
case TF_TYPE_DATE:
|
|
273
|
+
case TF_TYPE_TIMESTAMP:
|
|
274
|
+
write_rc = tf_batch_copy_cell_as_string(ob, r, c, in, r, c);
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
failure_reason = "unsupported_conversion";
|
|
278
|
+
write_rc = tf_batch_set_string(ob, r, c, "");
|
|
279
|
+
break;
|
|
84
280
|
}
|
|
85
|
-
tf_batch_set_string(ob, r, c, buf);
|
|
86
281
|
} else if (dst_t == TF_TYPE_INT64) {
|
|
87
282
|
int64_t v = 0;
|
|
88
283
|
if (src_t == TF_TYPE_FLOAT64) v = (int64_t)tf_batch_get_float64(in, r, c);
|
|
89
|
-
else if (src_t == TF_TYPE_STRING) {
|
|
90
|
-
|
|
284
|
+
else if (src_t == TF_TYPE_STRING) {
|
|
285
|
+
const char *s = tf_batch_get_string(in, r, c);
|
|
286
|
+
v = strtoll(s ? s : "", NULL, 10);
|
|
287
|
+
strict_int_parse_ok(s, &failure_reason);
|
|
288
|
+
} else if (src_t == TF_TYPE_BOOL) v = tf_batch_get_bool(in, r, c) ? 1 : 0;
|
|
91
289
|
else if (src_t == TF_TYPE_TIMESTAMP) v = tf_batch_get_timestamp(in, r, c);
|
|
92
|
-
|
|
290
|
+
else failure_reason = "unsupported_conversion";
|
|
291
|
+
write_rc = tf_batch_set_int64(ob, r, c, v);
|
|
93
292
|
} else if (dst_t == TF_TYPE_FLOAT64) {
|
|
94
293
|
double v = 0;
|
|
95
294
|
if (src_t == TF_TYPE_INT64) v = (double)tf_batch_get_int64(in, r, c);
|
|
96
|
-
else if (src_t == TF_TYPE_STRING) {
|
|
97
|
-
|
|
98
|
-
|
|
295
|
+
else if (src_t == TF_TYPE_STRING) {
|
|
296
|
+
const char *s = tf_batch_get_string(in, r, c);
|
|
297
|
+
v = strtod(s ? s : "", NULL);
|
|
298
|
+
strict_float_parse_ok(s, &failure_reason);
|
|
299
|
+
} else if (src_t == TF_TYPE_BOOL) v = tf_batch_get_bool(in, r, c) ? 1.0 : 0.0;
|
|
300
|
+
else failure_reason = "unsupported_conversion";
|
|
301
|
+
write_rc = tf_batch_set_float64(ob, r, c, v);
|
|
99
302
|
} else if (dst_t == TF_TYPE_BOOL) {
|
|
100
303
|
bool v = false;
|
|
101
304
|
if (src_t == TF_TYPE_INT64) v = tf_batch_get_int64(in, r, c) != 0;
|
|
102
305
|
else if (src_t == TF_TYPE_FLOAT64) v = tf_batch_get_float64(in, r, c) != 0.0;
|
|
103
|
-
else if (src_t == TF_TYPE_STRING)
|
|
104
|
-
|
|
306
|
+
else if (src_t == TF_TYPE_STRING) {
|
|
307
|
+
const char *s = tf_batch_get_string(in, r, c);
|
|
308
|
+
v = s && strlen(s) > 0 && strcmp(s, "false") != 0;
|
|
309
|
+
if (!s || (strcmp(s, "true") != 0 && strcmp(s, "false") != 0)) failure_reason = "non_canonical_bool";
|
|
310
|
+
} else failure_reason = "unsupported_conversion";
|
|
311
|
+
write_rc = tf_batch_set_bool(ob, r, c, v);
|
|
105
312
|
} else if (dst_t == TF_TYPE_DATE) {
|
|
106
313
|
int32_t v = 0;
|
|
107
314
|
if (src_t == TF_TYPE_STRING) {
|
|
108
315
|
const char *s = tf_batch_get_string(in, r, c);
|
|
109
316
|
int y, m, d;
|
|
110
|
-
if (sscanf(s, "%d-%d-%d", &y, &m, &d) == 3)
|
|
317
|
+
if (s && sscanf(s, "%d-%d-%d", &y, &m, &d) == 3)
|
|
111
318
|
v = tf_date_from_ymd(y, m, d);
|
|
319
|
+
strict_date_parse_ok(s, &failure_reason);
|
|
112
320
|
} else if (src_t == TF_TYPE_TIMESTAMP) {
|
|
113
321
|
v = (int32_t)(tf_batch_get_timestamp(in, r, c) / (86400LL * 1000000LL));
|
|
114
|
-
}
|
|
115
|
-
tf_batch_set_date(ob, r, c, v);
|
|
322
|
+
} else failure_reason = "unsupported_conversion";
|
|
323
|
+
write_rc = tf_batch_set_date(ob, r, c, v);
|
|
116
324
|
} else if (dst_t == TF_TYPE_TIMESTAMP) {
|
|
117
325
|
int64_t v = 0;
|
|
118
326
|
if (src_t == TF_TYPE_STRING) {
|
|
119
327
|
const char *s = tf_batch_get_string(in, r, c);
|
|
120
|
-
size_t slen = strlen(s);
|
|
328
|
+
size_t slen = s ? strlen(s) : 0;
|
|
121
329
|
int32_t dv;
|
|
122
|
-
/* Try timestamp first, then date at midnight */
|
|
123
330
|
if (slen >= 19) {
|
|
124
331
|
int y, mo, d, h, mi, se;
|
|
125
332
|
if (sscanf(s, "%d-%d-%dT%d:%d:%d", &y, &mo, &d, &h, &mi, &se) == 6)
|
|
@@ -133,35 +340,116 @@ static int cast_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
|
133
340
|
v = (int64_t)dv * 86400LL * 1000000LL;
|
|
134
341
|
}
|
|
135
342
|
}
|
|
343
|
+
strict_timestamp_parse_ok(s, &failure_reason);
|
|
136
344
|
} else if (src_t == TF_TYPE_DATE) {
|
|
137
345
|
v = (int64_t)tf_batch_get_date(in, r, c) * 86400LL * 1000000LL;
|
|
138
346
|
} else if (src_t == TF_TYPE_INT64) {
|
|
139
347
|
v = tf_batch_get_int64(in, r, c);
|
|
140
|
-
}
|
|
141
|
-
tf_batch_set_timestamp(ob, r, c, v);
|
|
348
|
+
} else failure_reason = "unsupported_conversion";
|
|
349
|
+
write_rc = tf_batch_set_timestamp(ob, r, c, v);
|
|
142
350
|
} else {
|
|
143
|
-
|
|
351
|
+
failure_reason = dst_t == TF_TYPE_NULL ? "invalid_target_type" : "unsupported_conversion";
|
|
352
|
+
write_rc = tf_batch_set_null(ob, r, c);
|
|
353
|
+
}
|
|
354
|
+
if (write_rc != TF_OK) goto fail;
|
|
355
|
+
|
|
356
|
+
if (failure_reason) {
|
|
357
|
+
st->coercion_failures++;
|
|
358
|
+
if (st->on_error == CAST_ON_ERROR_NULL || st->on_error == CAST_ON_ERROR_FAIL) {
|
|
359
|
+
if (tf_batch_set_null(ob, r, c) != TF_OK) goto fail;
|
|
360
|
+
if (st->on_error == CAST_ON_ERROR_NULL) st->coercion_nulled++;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (st->audit) {
|
|
365
|
+
const char *event = failure_reason ? "coercion_failed" : "value_changed";
|
|
366
|
+
const char *reason = failure_reason ? failure_reason : "type_cast";
|
|
367
|
+
if (st->audit_emitted < st->audit_limit &&
|
|
368
|
+
audit_count < st->audit_limit - st->audit_emitted) {
|
|
369
|
+
audit_events[audit_count++] = (cast_audit_event){
|
|
370
|
+
.col = c,
|
|
371
|
+
.src_t = src_t,
|
|
372
|
+
.dst_t = dst_t,
|
|
373
|
+
.event = event,
|
|
374
|
+
.reason = reason,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (failure_reason && st->on_error == CAST_ON_ERROR_FAIL) {
|
|
380
|
+
row_failed = 1;
|
|
381
|
+
row_failed_col = c;
|
|
382
|
+
row_failed_reason = failure_reason;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) goto fail;
|
|
386
|
+
if (audit_count > 0 || row_failed) {
|
|
387
|
+
size_t row_no = 0;
|
|
388
|
+
if (tf_size_add(row_base, r, &row_no) != TF_OK ||
|
|
389
|
+
tf_size_add(row_no, 1, &row_no) != TF_OK) {
|
|
390
|
+
goto fail;
|
|
391
|
+
}
|
|
392
|
+
for (size_t i = 0; i < audit_count; i++) {
|
|
393
|
+
cast_audit_event *ev = &audit_events[i];
|
|
394
|
+
if (emit_cast_audit(st, in, ob, r, ev->col, row_no, ev->src_t,
|
|
395
|
+
ev->dst_t, ev->event, ev->reason, side) != TF_OK) {
|
|
396
|
+
goto fail;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (row_failed) {
|
|
400
|
+
char msg[512];
|
|
401
|
+
snprintf(msg, sizeof(msg), "cast failed at row %zu column '%s': %s",
|
|
402
|
+
row_no,
|
|
403
|
+
in->col_names[row_failed_col] ? in->col_names[row_failed_col] : "",
|
|
404
|
+
row_failed_reason ? row_failed_reason : "type_cast");
|
|
405
|
+
tf_set_last_error(msg);
|
|
406
|
+
goto fail;
|
|
144
407
|
}
|
|
145
408
|
}
|
|
146
|
-
ob->n_rows = r + 1;
|
|
147
409
|
}
|
|
148
410
|
|
|
411
|
+
if (tf_size_add(st->row_index, in->n_rows, &st->row_index) != TF_OK) goto fail;
|
|
412
|
+
free(audit_events);
|
|
149
413
|
free(out_types);
|
|
150
414
|
*out = ob;
|
|
151
415
|
return TF_OK;
|
|
416
|
+
|
|
417
|
+
fail:
|
|
418
|
+
free(audit_events);
|
|
419
|
+
tf_batch_free(ob);
|
|
420
|
+
free(out_types);
|
|
421
|
+
return TF_ERROR;
|
|
152
422
|
}
|
|
153
423
|
|
|
154
424
|
static int cast_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
155
425
|
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
156
426
|
}
|
|
157
427
|
|
|
158
|
-
static
|
|
159
|
-
|
|
160
|
-
|
|
428
|
+
static int cast_append_stats(tf_step *self, tf_buffer *out) {
|
|
429
|
+
if (!self || !self->state || !out) return TF_ERROR;
|
|
430
|
+
cast_state *st = (cast_state *)self->state;
|
|
431
|
+
char buf[256];
|
|
432
|
+
snprintf(buf, sizeof(buf),
|
|
433
|
+
",\"on_error\":\"%s\",\"coercion_failures\":%zu,"
|
|
434
|
+
"\"coercion_nulled\":%zu,\"audit_emitted\":%zu",
|
|
435
|
+
cast_error_policy_name(st->on_error), st->coercion_failures,
|
|
436
|
+
st->coercion_nulled, st->audit_emitted);
|
|
437
|
+
return tf_buffer_write_str(out, buf);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
static void cast_state_free(cast_state *st) {
|
|
441
|
+
if (!st) return;
|
|
442
|
+
tf_audit_options_free(&st->audit_opts);
|
|
443
|
+
if (st->col_names) {
|
|
161
444
|
for (size_t i = 0; i < st->n; i++) free(st->col_names[i]);
|
|
162
|
-
free(st->col_names); free(st->target_types);
|
|
163
|
-
free(st);
|
|
164
445
|
}
|
|
446
|
+
free(st->col_names);
|
|
447
|
+
free(st->target_types);
|
|
448
|
+
free(st);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
static void cast_destroy(tf_step *self) {
|
|
452
|
+
if (self) cast_state_free(self->state);
|
|
165
453
|
free(self);
|
|
166
454
|
}
|
|
167
455
|
|
|
@@ -173,23 +461,63 @@ tf_step *tf_cast_create(const cJSON *args) {
|
|
|
173
461
|
int n = cJSON_GetArraySize(mapping);
|
|
174
462
|
cast_state *st = calloc(1, sizeof(cast_state));
|
|
175
463
|
if (!st) return NULL;
|
|
176
|
-
st->
|
|
177
|
-
st->
|
|
178
|
-
st->
|
|
464
|
+
tf_audit_options_init(&st->audit_opts, 1);
|
|
465
|
+
st->col_names = tf_callocarray_checked(n > 0 ? (size_t)n : 1, sizeof(char *));
|
|
466
|
+
st->target_types = tf_callocarray_checked(n > 0 ? (size_t)n : 1, sizeof(tf_type));
|
|
467
|
+
st->n = (size_t)n;
|
|
468
|
+
st->audit_limit = 1000;
|
|
469
|
+
st->on_error = CAST_ON_ERROR_COERCE;
|
|
470
|
+
if (!st->col_names || !st->target_types) { cast_state_free(st); return NULL; }
|
|
179
471
|
|
|
180
472
|
int i = 0;
|
|
181
473
|
cJSON *entry = NULL;
|
|
182
474
|
cJSON_ArrayForEach(entry, mapping) {
|
|
183
|
-
|
|
475
|
+
if (!entry->string || !entry->string[0]) { cast_state_free(st); return NULL; }
|
|
476
|
+
st->col_names[i] = tf_strdup_checked(entry->string);
|
|
184
477
|
st->target_types[i] = cJSON_IsString(entry) ? parse_type(entry->valuestring) : TF_TYPE_NULL;
|
|
478
|
+
if (!st->col_names[i]) { cast_state_free(st); return NULL; }
|
|
185
479
|
i++;
|
|
186
480
|
}
|
|
187
481
|
|
|
188
|
-
|
|
189
|
-
if (!
|
|
482
|
+
cJSON *on_error_j = cJSON_GetObjectItemCaseSensitive(args, "on_error");
|
|
483
|
+
if (!on_error_j) on_error_j = cJSON_GetObjectItemCaseSensitive(args, "onError");
|
|
484
|
+
if (!on_error_j) on_error_j = cJSON_GetObjectItemCaseSensitive(args, "on_coercion_error");
|
|
485
|
+
if (!on_error_j) on_error_j = cJSON_GetObjectItemCaseSensitive(args, "onCoercionError");
|
|
486
|
+
if (on_error_j) {
|
|
487
|
+
if (!cJSON_IsString(on_error_j) ||
|
|
488
|
+
!parse_cast_error_policy(on_error_j->valuestring, &st->on_error)) {
|
|
489
|
+
tf_set_last_error("cast: on_error must be coerce, fail, or null");
|
|
490
|
+
cast_state_free(st);
|
|
491
|
+
return NULL;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
cJSON *audit_j = cJSON_GetObjectItemCaseSensitive(args, "audit");
|
|
496
|
+
st->audit = cJSON_IsTrue(audit_j) ? 1 : 0;
|
|
497
|
+
cJSON *audit_limit_j = cJSON_GetObjectItemCaseSensitive(args, "audit_limit");
|
|
498
|
+
if (!audit_limit_j) audit_limit_j = cJSON_GetObjectItemCaseSensitive(args, "auditLimit");
|
|
499
|
+
if (audit_limit_j) {
|
|
500
|
+
size_t parsed_limit = 0;
|
|
501
|
+
if (tf_json_get_size_arg_any(args, "audit_limit", "auditLimit",
|
|
502
|
+
1, TF_MAX_AUDIT_RECORDS,
|
|
503
|
+
&parsed_limit, "cast") < 0) {
|
|
504
|
+
cast_state_free(st);
|
|
505
|
+
return NULL;
|
|
506
|
+
}
|
|
507
|
+
st->audit_limit = parsed_limit;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (tf_audit_options_parse(&st->audit_opts, args, "cast") != TF_OK) {
|
|
511
|
+
cast_state_free(st);
|
|
512
|
+
return NULL;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
516
|
+
if (!step) { cast_state_free(st); return NULL; }
|
|
190
517
|
step->process = cast_process;
|
|
191
518
|
step->flush = cast_flush;
|
|
192
519
|
step->destroy = cast_destroy;
|
|
520
|
+
step->append_stats = cast_append_stats;
|
|
193
521
|
step->state = st;
|
|
194
522
|
return step;
|
|
195
523
|
}
|
package/csrc/op_clip.c
CHANGED
|
@@ -25,29 +25,39 @@ static int clip_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
|
25
25
|
|
|
26
26
|
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
27
27
|
if (!ob) return TF_ERROR;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
tf_batch_copy_row(ob, r, in, r);
|
|
32
|
-
ob->n_rows = r + 1;
|
|
28
|
+
if (tf_batch_clone_schema(ob, in) != TF_OK) {
|
|
29
|
+
tf_batch_free(ob);
|
|
30
|
+
return TF_ERROR;
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
int ci = tf_batch_col_index(in, st->column);
|
|
33
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
34
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
35
|
+
tf_batch_free(ob);
|
|
36
|
+
return TF_ERROR;
|
|
37
|
+
}
|
|
38
|
+
if (ci >= 0 && !tf_batch_is_null(in, r, (size_t)ci)) {
|
|
39
|
+
if (in->col_types[ci] == TF_TYPE_INT64) {
|
|
40
|
+
int64_t v = tf_batch_get_int64(in, r, (size_t)ci);
|
|
41
41
|
if (st->has_min && v < (int64_t)st->min_val) v = (int64_t)st->min_val;
|
|
42
42
|
if (st->has_max && v > (int64_t)st->max_val) v = (int64_t)st->max_val;
|
|
43
|
-
tf_batch_set_int64(ob, r, ci, v)
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (tf_batch_set_int64(ob, r, (size_t)ci, v) != TF_OK) {
|
|
44
|
+
tf_batch_free(ob);
|
|
45
|
+
return TF_ERROR;
|
|
46
|
+
}
|
|
47
|
+
} else if (in->col_types[ci] == TF_TYPE_FLOAT64) {
|
|
48
|
+
double v = tf_batch_get_float64(in, r, (size_t)ci);
|
|
46
49
|
if (st->has_min && v < st->min_val) v = st->min_val;
|
|
47
50
|
if (st->has_max && v > st->max_val) v = st->max_val;
|
|
48
|
-
tf_batch_set_float64(ob, r, ci, v)
|
|
51
|
+
if (tf_batch_set_float64(ob, r, (size_t)ci, v) != TF_OK) {
|
|
52
|
+
tf_batch_free(ob);
|
|
53
|
+
return TF_ERROR;
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
}
|
|
57
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
58
|
+
tf_batch_free(ob);
|
|
59
|
+
return TF_ERROR;
|
|
60
|
+
}
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
*out = ob;
|
|
@@ -69,16 +79,17 @@ tf_step *tf_clip_create(const cJSON *args) {
|
|
|
69
79
|
cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
70
80
|
if (!cJSON_IsString(col_j)) return NULL;
|
|
71
81
|
|
|
72
|
-
clip_state *st =
|
|
82
|
+
clip_state *st = tf_callocarray_checked(1, sizeof(clip_state));
|
|
73
83
|
if (!st) return NULL;
|
|
74
|
-
st->column =
|
|
84
|
+
st->column = tf_strdup_checked(col_j->valuestring);
|
|
85
|
+
if (!st->column) { free(st); return NULL; }
|
|
75
86
|
|
|
76
87
|
cJSON *min_j = cJSON_GetObjectItemCaseSensitive(args, "min");
|
|
77
88
|
if (cJSON_IsNumber(min_j)) { st->min_val = min_j->valuedouble; st->has_min = 1; }
|
|
78
89
|
cJSON *max_j = cJSON_GetObjectItemCaseSensitive(args, "max");
|
|
79
90
|
if (cJSON_IsNumber(max_j)) { st->max_val = max_j->valuedouble; st->has_max = 1; }
|
|
80
91
|
|
|
81
|
-
tf_step *step =
|
|
92
|
+
tf_step *step = tf_callocarray_checked(1, sizeof(tf_step));
|
|
82
93
|
if (!step) { free(st->column); free(st); return NULL; }
|
|
83
94
|
step->process = clip_process;
|
|
84
95
|
step->flush = clip_flush;
|