tranfi 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +177 -0
- package/NOTICE +8 -0
- package/README.md +272 -40
- package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
- package/app/index.html +1 -1
- package/binding.gyp +55 -3
- package/csrc/arena.c +7 -5
- package/csrc/batch.c +818 -71
- package/csrc/buffer.c +84 -8
- package/csrc/cJSON.c +262 -19
- package/csrc/cJSON.h +17 -1
- package/csrc/codec_csv.c +1074 -181
- package/csrc/codec_jsonl.c +830 -118
- package/csrc/codec_table.c +108 -78
- package/csrc/codec_text.c +286 -68
- package/csrc/compiler.c +31 -3
- package/csrc/config.h +21 -0
- package/csrc/dsl.c +4722 -485
- package/csrc/expr.c +363 -55
- package/csrc/expr.h +2 -0
- package/csrc/internal.h +316 -27
- package/csrc/ir.c +65 -18
- package/csrc/ir.h +41 -0
- package/csrc/ir_schema.c +20 -5
- package/csrc/ir_serialize.c +68 -6
- package/csrc/ir_sql.c +796 -185
- package/csrc/ir_validate.c +462 -6
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +879 -30
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +171 -21
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +167 -32
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +168 -29
- package/csrc/op_cast.c +383 -55
- package/csrc/op_clip.c +30 -19
- package/csrc/op_date_trunc.c +208 -34
- package/csrc/op_datetime.c +259 -77
- package/csrc/op_derive.c +65 -97
- package/csrc/op_diff.c +146 -30
- package/csrc/op_ewma.c +149 -30
- package/csrc/op_explode.c +124 -26
- package/csrc/op_fill_down.c +125 -53
- package/csrc/op_fill_null.c +176 -31
- package/csrc/op_filter.c +89 -40
- package/csrc/op_frequency.c +571 -43
- package/csrc/op_grep.c +36 -18
- package/csrc/op_group_agg.c +1790 -119
- package/csrc/op_hash.c +48 -15
- package/csrc/op_head.c +21 -86
- package/csrc/op_interpolate.c +268 -62
- package/csrc/op_join.c +2700 -182
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +328 -53
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +141 -89
- package/csrc/op_normalize.c +363 -79
- package/csrc/op_onehot.c +345 -73
- package/csrc/op_pivot.c +1546 -162
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +2062 -166
- package/csrc/op_rename.c +41 -50
- package/csrc/op_replace.c +270 -118
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +80 -23
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +265 -65
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +30 -87
- package/csrc/op_sort.c +670 -124
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +65 -28
- package/csrc/op_split_data.c +41 -9
- package/csrc/op_stack.c +178 -222
- package/csrc/op_stats.c +206 -110
- package/csrc/op_step.c +217 -55
- package/csrc/op_tail.c +21 -12
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +260 -53
- package/csrc/op_trim.c +48 -19
- package/csrc/op_unique.c +1193 -150
- package/csrc/op_unpivot.c +100 -66
- package/csrc/op_validate.c +601 -24
- package/csrc/op_window.c +492 -51
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +872 -99
- package/csrc/recipes.c +3 -1
- package/csrc/report.c +73 -30
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +169 -1
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +70 -9
- package/napi_api.c +219 -11
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +27 -11
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +8 -11
- package/src/engines/duckdb.js +45 -12
- package/src/index.js +661 -42
- package/src/memory_policy.js +411 -0
- package/src/native.js +1 -5
- package/src/pipeline.js +454 -31
- package/src/recipe_json.js +80 -0
- package/src/server.js +10 -8
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +6 -4
- package/wasm/index.js +498 -10
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/csrc/plan.c +0 -206
package/csrc/op_fill_down.c
CHANGED
|
@@ -27,6 +27,22 @@ typedef struct {
|
|
|
27
27
|
int initialized;
|
|
28
28
|
} fill_down_state;
|
|
29
29
|
|
|
30
|
+
static int fill_down_is_target(const fill_down_state *st, const tf_batch *in, size_t c) {
|
|
31
|
+
if (st->n_cols == 0) return 1;
|
|
32
|
+
for (size_t k = 0; k < st->n_cols; k++) {
|
|
33
|
+
if (strcmp(in->col_names[c], st->cols[k]) == 0) return 1;
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static void fill_down_free_string_slots(char **slots, size_t n) {
|
|
39
|
+
if (!slots) return;
|
|
40
|
+
for (size_t i = 0; i < n; i++) {
|
|
41
|
+
free(slots[i]);
|
|
42
|
+
slots[i] = NULL;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
static int fill_down_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
31
47
|
tf_side_channels *side) {
|
|
32
48
|
(void)side;
|
|
@@ -34,93 +50,136 @@ static int fill_down_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
|
34
50
|
*out = NULL;
|
|
35
51
|
|
|
36
52
|
if (!st->initialized) {
|
|
53
|
+
char **last_vals = calloc(in->n_cols, sizeof(char *));
|
|
54
|
+
int *last_types = calloc(in->n_cols, sizeof(int));
|
|
55
|
+
int64_t *last_int = calloc(in->n_cols, sizeof(int64_t));
|
|
56
|
+
double *last_float = calloc(in->n_cols, sizeof(double));
|
|
57
|
+
int *last_bool = calloc(in->n_cols, sizeof(int));
|
|
58
|
+
int32_t *last_date = calloc(in->n_cols, sizeof(int32_t));
|
|
59
|
+
int64_t *last_timestamp = calloc(in->n_cols, sizeof(int64_t));
|
|
60
|
+
if (!last_vals || !last_types || !last_int || !last_float ||
|
|
61
|
+
!last_bool || !last_date || !last_timestamp) {
|
|
62
|
+
free(last_vals);
|
|
63
|
+
free(last_types);
|
|
64
|
+
free(last_int);
|
|
65
|
+
free(last_float);
|
|
66
|
+
free(last_bool);
|
|
67
|
+
free(last_date);
|
|
68
|
+
free(last_timestamp);
|
|
69
|
+
return TF_ERROR;
|
|
70
|
+
}
|
|
71
|
+
for (size_t c = 0; c < in->n_cols; c++) last_types[c] = -1; /* no value yet */
|
|
37
72
|
st->n_tracked = (int)in->n_cols;
|
|
38
|
-
st->last_vals =
|
|
39
|
-
st->last_types =
|
|
40
|
-
st->last_int =
|
|
41
|
-
st->last_float =
|
|
42
|
-
st->last_bool =
|
|
43
|
-
st->last_date =
|
|
44
|
-
st->last_timestamp =
|
|
45
|
-
for (size_t c = 0; c < in->n_cols; c++) st->last_types[c] = -1; /* no value yet */
|
|
73
|
+
st->last_vals = last_vals;
|
|
74
|
+
st->last_types = last_types;
|
|
75
|
+
st->last_int = last_int;
|
|
76
|
+
st->last_float = last_float;
|
|
77
|
+
st->last_bool = last_bool;
|
|
78
|
+
st->last_date = last_date;
|
|
79
|
+
st->last_timestamp = last_timestamp;
|
|
46
80
|
st->initialized = 1;
|
|
47
81
|
}
|
|
48
82
|
|
|
49
83
|
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
50
84
|
if (!ob) return TF_ERROR;
|
|
51
|
-
|
|
52
|
-
|
|
85
|
+
if (tf_batch_clone_schema(ob, in) != TF_OK) {
|
|
86
|
+
tf_batch_free(ob);
|
|
87
|
+
return TF_ERROR;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
char **pending_strings = calloc(in->n_cols, sizeof(char *));
|
|
91
|
+
if (!pending_strings) {
|
|
92
|
+
tf_batch_free(ob);
|
|
93
|
+
return TF_ERROR;
|
|
94
|
+
}
|
|
53
95
|
|
|
54
96
|
for (size_t r = 0; r < in->n_rows; r++) {
|
|
55
|
-
|
|
97
|
+
memset(pending_strings, 0, sizeof(char *) * in->n_cols);
|
|
98
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
99
|
+
goto fail;
|
|
100
|
+
}
|
|
56
101
|
|
|
57
102
|
for (size_t c = 0; c < in->n_cols; c++) {
|
|
58
|
-
|
|
59
|
-
int target = 0;
|
|
60
|
-
if (st->n_cols == 0) {
|
|
61
|
-
target = 1;
|
|
62
|
-
} else {
|
|
63
|
-
for (size_t k = 0; k < st->n_cols; k++) {
|
|
64
|
-
if (strcmp(in->col_names[c], st->cols[k]) == 0) { target = 1; break; }
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (!target) continue;
|
|
103
|
+
if (!fill_down_is_target(st, in, c)) continue;
|
|
68
104
|
|
|
69
105
|
if (!tf_batch_is_null(in, r, c)) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
break;
|
|
77
|
-
case TF_TYPE_INT64:
|
|
78
|
-
st->last_int[c] = tf_batch_get_int64(in, r, c); break;
|
|
79
|
-
case TF_TYPE_FLOAT64:
|
|
80
|
-
st->last_float[c] = tf_batch_get_float64(in, r, c); break;
|
|
81
|
-
case TF_TYPE_BOOL:
|
|
82
|
-
st->last_bool[c] = tf_batch_get_bool(in, r, c); break;
|
|
83
|
-
case TF_TYPE_DATE:
|
|
84
|
-
st->last_date[c] = tf_batch_get_date(in, r, c); break;
|
|
85
|
-
case TF_TYPE_TIMESTAMP:
|
|
86
|
-
st->last_timestamp[c] = tf_batch_get_timestamp(in, r, c); break;
|
|
87
|
-
default: break;
|
|
106
|
+
if (in->col_types[c] == TF_TYPE_STRING) {
|
|
107
|
+
const char *src = tf_batch_get_string(in, r, c);
|
|
108
|
+
pending_strings[c] = strdup(src ? src : "");
|
|
109
|
+
if (!pending_strings[c]) {
|
|
110
|
+
goto fail;
|
|
111
|
+
}
|
|
88
112
|
}
|
|
89
113
|
} else if (st->last_types[c] >= 0) {
|
|
90
114
|
/* Fill with last value */
|
|
115
|
+
int write_rc = TF_OK;
|
|
91
116
|
switch (in->col_types[c]) {
|
|
92
117
|
case TF_TYPE_STRING:
|
|
93
|
-
if (st->last_vals[c]) tf_batch_set_string(ob, r, c, st->last_vals[c]);
|
|
118
|
+
if (st->last_vals[c]) write_rc = tf_batch_set_string(ob, r, c, st->last_vals[c]);
|
|
94
119
|
break;
|
|
95
120
|
case TF_TYPE_INT64:
|
|
96
|
-
tf_batch_set_int64(ob, r, c, st->last_int[c]); break;
|
|
121
|
+
write_rc = tf_batch_set_int64(ob, r, c, st->last_int[c]); break;
|
|
97
122
|
case TF_TYPE_FLOAT64:
|
|
98
|
-
tf_batch_set_float64(ob, r, c, st->last_float[c]); break;
|
|
123
|
+
write_rc = tf_batch_set_float64(ob, r, c, st->last_float[c]); break;
|
|
99
124
|
case TF_TYPE_BOOL:
|
|
100
|
-
tf_batch_set_bool(ob, r, c, st->last_bool[c]); break;
|
|
125
|
+
write_rc = tf_batch_set_bool(ob, r, c, st->last_bool[c]); break;
|
|
101
126
|
case TF_TYPE_DATE:
|
|
102
|
-
tf_batch_set_date(ob, r, c, st->last_date[c]); break;
|
|
127
|
+
write_rc = tf_batch_set_date(ob, r, c, st->last_date[c]); break;
|
|
103
128
|
case TF_TYPE_TIMESTAMP:
|
|
104
|
-
tf_batch_set_timestamp(ob, r, c, st->last_timestamp[c]); break;
|
|
129
|
+
write_rc = tf_batch_set_timestamp(ob, r, c, st->last_timestamp[c]); break;
|
|
105
130
|
default: break;
|
|
106
131
|
}
|
|
132
|
+
if (write_rc != TF_OK) goto fail;
|
|
107
133
|
}
|
|
108
134
|
}
|
|
109
|
-
|
|
135
|
+
|
|
136
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
137
|
+
if (!fill_down_is_target(st, in, c) || tf_batch_is_null(in, r, c)) continue;
|
|
138
|
+
|
|
139
|
+
st->last_types[c] = (int)in->col_types[c];
|
|
140
|
+
switch (in->col_types[c]) {
|
|
141
|
+
case TF_TYPE_STRING:
|
|
142
|
+
free(st->last_vals[c]);
|
|
143
|
+
st->last_vals[c] = pending_strings[c];
|
|
144
|
+
pending_strings[c] = NULL;
|
|
145
|
+
break;
|
|
146
|
+
case TF_TYPE_INT64:
|
|
147
|
+
st->last_int[c] = tf_batch_get_int64(in, r, c); break;
|
|
148
|
+
case TF_TYPE_FLOAT64:
|
|
149
|
+
st->last_float[c] = tf_batch_get_float64(in, r, c); break;
|
|
150
|
+
case TF_TYPE_BOOL:
|
|
151
|
+
st->last_bool[c] = tf_batch_get_bool(in, r, c); break;
|
|
152
|
+
case TF_TYPE_DATE:
|
|
153
|
+
st->last_date[c] = tf_batch_get_date(in, r, c); break;
|
|
154
|
+
case TF_TYPE_TIMESTAMP:
|
|
155
|
+
st->last_timestamp[c] = tf_batch_get_timestamp(in, r, c); break;
|
|
156
|
+
default:
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) goto fail;
|
|
110
161
|
}
|
|
111
162
|
|
|
163
|
+
free(pending_strings);
|
|
112
164
|
*out = ob;
|
|
113
165
|
return TF_OK;
|
|
166
|
+
|
|
167
|
+
fail:
|
|
168
|
+
fill_down_free_string_slots(pending_strings, in->n_cols);
|
|
169
|
+
free(pending_strings);
|
|
170
|
+
tf_batch_free(ob);
|
|
171
|
+
return TF_ERROR;
|
|
114
172
|
}
|
|
115
173
|
|
|
116
174
|
static int fill_down_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
117
175
|
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
118
176
|
}
|
|
119
177
|
|
|
120
|
-
static void
|
|
121
|
-
fill_down_state *st = self->state;
|
|
178
|
+
static void fill_down_state_free(fill_down_state *st) {
|
|
122
179
|
if (st) {
|
|
123
|
-
|
|
180
|
+
if (st->cols) {
|
|
181
|
+
for (size_t i = 0; i < st->n_cols; i++) free(st->cols[i]);
|
|
182
|
+
}
|
|
124
183
|
free(st->cols);
|
|
125
184
|
if (st->last_vals) {
|
|
126
185
|
for (int i = 0; i < st->n_tracked; i++) free(st->last_vals[i]);
|
|
@@ -131,6 +190,10 @@ static void fill_down_destroy(tf_step *self) {
|
|
|
131
190
|
free(st->last_date); free(st->last_timestamp);
|
|
132
191
|
free(st);
|
|
133
192
|
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static void fill_down_destroy(tf_step *self) {
|
|
196
|
+
fill_down_state_free(self->state);
|
|
134
197
|
free(self);
|
|
135
198
|
}
|
|
136
199
|
|
|
@@ -144,17 +207,26 @@ tf_step *tf_fill_down_create(const cJSON *args) {
|
|
|
144
207
|
int n = cJSON_GetArraySize(columns);
|
|
145
208
|
if (n > 0) {
|
|
146
209
|
st->cols = calloc(n, sizeof(char *));
|
|
147
|
-
st->
|
|
210
|
+
if (!st->cols) { fill_down_state_free(st); return NULL; }
|
|
211
|
+
st->n_cols = (size_t)n;
|
|
148
212
|
for (int i = 0; i < n; i++) {
|
|
149
213
|
cJSON *item = cJSON_GetArrayItem(columns, i);
|
|
150
|
-
if (cJSON_IsString(item)
|
|
214
|
+
if (!cJSON_IsString(item) || !item->valuestring[0]) {
|
|
215
|
+
fill_down_state_free(st);
|
|
216
|
+
return NULL;
|
|
217
|
+
}
|
|
218
|
+
st->cols[i] = strdup(item->valuestring);
|
|
219
|
+
if (!st->cols[i]) {
|
|
220
|
+
fill_down_state_free(st);
|
|
221
|
+
return NULL;
|
|
222
|
+
}
|
|
151
223
|
}
|
|
152
224
|
}
|
|
153
225
|
}
|
|
154
226
|
}
|
|
155
227
|
|
|
156
|
-
tf_step *step =
|
|
157
|
-
if (!step) {
|
|
228
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
229
|
+
if (!step) { fill_down_state_free(st); return NULL; }
|
|
158
230
|
step->process = fill_down_process;
|
|
159
231
|
step->flush = fill_down_flush;
|
|
160
232
|
step->destroy = fill_down_destroy;
|
package/csrc/op_fill_null.c
CHANGED
|
@@ -10,71 +10,182 @@
|
|
|
10
10
|
#include <stdlib.h>
|
|
11
11
|
#include <string.h>
|
|
12
12
|
|
|
13
|
+
/* Fill-null is row-local. Audit records are opt-in and capped so diagnostics
|
|
14
|
+
* cannot become an unbounded second output stream. */
|
|
13
15
|
typedef struct {
|
|
14
16
|
char **col_names;
|
|
15
17
|
char **defaults;
|
|
16
18
|
size_t n;
|
|
19
|
+
size_t row_index;
|
|
20
|
+
size_t audit_limit;
|
|
21
|
+
size_t audit_emitted;
|
|
22
|
+
int audit;
|
|
23
|
+
tf_audit_options audit_opts;
|
|
17
24
|
} fill_null_state;
|
|
18
25
|
|
|
26
|
+
static int emit_fill_null_audit(fill_null_state *st, const tf_batch *b, size_t row,
|
|
27
|
+
size_t col, size_t row_no, tf_side_channels *side) {
|
|
28
|
+
if (!st->audit || st->audit_emitted >= st->audit_limit || !side || !side->stats) return TF_OK;
|
|
29
|
+
cJSON *obj = cJSON_CreateObject();
|
|
30
|
+
if (!obj) return TF_ERROR;
|
|
31
|
+
int rc = TF_ERROR;
|
|
32
|
+
if (tf_json_add_string(obj, "type", "audit") != TF_OK ||
|
|
33
|
+
tf_json_add_string(obj, "op", "fill-null") != TF_OK ||
|
|
34
|
+
tf_json_add_string(obj, "event", "null_filled") != TF_OK ||
|
|
35
|
+
tf_json_add_string(obj, "reason", "null_filled") != TF_OK ||
|
|
36
|
+
tf_json_add_string(obj, "channel", "audit") != TF_OK ||
|
|
37
|
+
tf_json_add_string(obj, "column", b->col_names[col] ? b->col_names[col] : "") != TF_OK ||
|
|
38
|
+
tf_json_add_number(obj, "row", (double)row_no) != TF_OK ||
|
|
39
|
+
tf_json_add_null(obj, "before") != TF_OK) {
|
|
40
|
+
goto done;
|
|
41
|
+
}
|
|
42
|
+
cJSON *after = tf_audit_cell_to_json(b, row, col, &st->audit_opts);
|
|
43
|
+
if (!after || tf_json_add_item(obj, "after", after) != TF_OK) goto done;
|
|
44
|
+
cJSON *row_obj = tf_audit_row_to_json(b, row, &st->audit_opts);
|
|
45
|
+
if (row_obj) {
|
|
46
|
+
if (tf_json_add_item(obj, "data", row_obj) != TF_OK) goto done;
|
|
47
|
+
} else if (st->audit_opts.include_row) {
|
|
48
|
+
goto done;
|
|
49
|
+
}
|
|
50
|
+
rc = tf_buffer_write_json_line(side->stats, obj);
|
|
51
|
+
done:
|
|
52
|
+
cJSON_Delete(obj);
|
|
53
|
+
if (rc == TF_OK) st->audit_emitted++;
|
|
54
|
+
return rc;
|
|
55
|
+
}
|
|
56
|
+
|
|
19
57
|
static int fill_null_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
20
58
|
tf_side_channels *side) {
|
|
21
|
-
(void)side;
|
|
22
59
|
fill_null_state *st = self->state;
|
|
23
60
|
*out = NULL;
|
|
61
|
+
size_t row_base = st->row_index;
|
|
24
62
|
|
|
25
63
|
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
26
64
|
if (!ob) return TF_ERROR;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
tf_batch_copy_row(ob, r, in, r);
|
|
31
|
-
ob->n_rows = r + 1;
|
|
65
|
+
if (tf_batch_clone_schema(ob, in) != TF_OK) {
|
|
66
|
+
tf_batch_free(ob);
|
|
67
|
+
return TF_ERROR;
|
|
32
68
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
69
|
+
size_t *changed_cols = tf_callocarray_checked(st->n > 0 ? st->n : 1, sizeof(size_t));
|
|
70
|
+
if (!changed_cols) {
|
|
71
|
+
tf_batch_free(ob);
|
|
72
|
+
return TF_ERROR;
|
|
73
|
+
}
|
|
74
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
75
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
76
|
+
free(changed_cols);
|
|
77
|
+
tf_batch_free(ob);
|
|
78
|
+
return TF_ERROR;
|
|
79
|
+
}
|
|
80
|
+
size_t changed_count = 0;
|
|
81
|
+
for (size_t k = 0; k < st->n; k++) {
|
|
82
|
+
int ci = tf_batch_col_index(in, st->col_names[k]);
|
|
83
|
+
if (ci < 0 || !ob->nulls[ci] || !ob->nulls[ci][r]) continue;
|
|
40
84
|
const char *def = st->defaults[k];
|
|
41
|
-
|
|
85
|
+
int changed = 0;
|
|
86
|
+
switch (in->col_types[ci]) {
|
|
42
87
|
case TF_TYPE_STRING:
|
|
43
|
-
tf_batch_set_string(ob, r, ci, def)
|
|
88
|
+
if (tf_batch_set_string(ob, r, (size_t)ci, def) != TF_OK) {
|
|
89
|
+
free(changed_cols);
|
|
90
|
+
tf_batch_free(ob);
|
|
91
|
+
return TF_ERROR;
|
|
92
|
+
}
|
|
93
|
+
changed = 1;
|
|
44
94
|
break;
|
|
45
95
|
case TF_TYPE_INT64: {
|
|
46
96
|
char *end;
|
|
47
97
|
int64_t v = strtoll(def, &end, 10);
|
|
48
|
-
if (*end == '\0')
|
|
98
|
+
if (*end == '\0') {
|
|
99
|
+
if (tf_batch_set_int64(ob, r, (size_t)ci, v) != TF_OK) {
|
|
100
|
+
free(changed_cols);
|
|
101
|
+
tf_batch_free(ob);
|
|
102
|
+
return TF_ERROR;
|
|
103
|
+
}
|
|
104
|
+
changed = 1;
|
|
105
|
+
}
|
|
49
106
|
break;
|
|
50
107
|
}
|
|
51
108
|
case TF_TYPE_FLOAT64: {
|
|
52
109
|
char *end;
|
|
53
110
|
double v = strtod(def, &end);
|
|
54
|
-
if (*end == '\0')
|
|
111
|
+
if (*end == '\0') {
|
|
112
|
+
if (tf_batch_set_float64(ob, r, (size_t)ci, v) != TF_OK) {
|
|
113
|
+
free(changed_cols);
|
|
114
|
+
tf_batch_free(ob);
|
|
115
|
+
return TF_ERROR;
|
|
116
|
+
}
|
|
117
|
+
changed = 1;
|
|
118
|
+
}
|
|
55
119
|
break;
|
|
56
120
|
}
|
|
57
121
|
case TF_TYPE_BOOL:
|
|
58
|
-
tf_batch_set_bool(ob, r, ci, strcmp(def, "true") == 0)
|
|
122
|
+
if (tf_batch_set_bool(ob, r, (size_t)ci, strcmp(def, "true") == 0) != TF_OK) {
|
|
123
|
+
free(changed_cols);
|
|
124
|
+
tf_batch_free(ob);
|
|
125
|
+
return TF_ERROR;
|
|
126
|
+
}
|
|
127
|
+
changed = 1;
|
|
59
128
|
break;
|
|
60
129
|
case TF_TYPE_DATE: {
|
|
61
130
|
char *end;
|
|
62
131
|
int32_t v = (int32_t)strtol(def, &end, 10);
|
|
63
|
-
if (*end == '\0')
|
|
132
|
+
if (*end == '\0') {
|
|
133
|
+
if (tf_batch_set_date(ob, r, (size_t)ci, v) != TF_OK) {
|
|
134
|
+
free(changed_cols);
|
|
135
|
+
tf_batch_free(ob);
|
|
136
|
+
return TF_ERROR;
|
|
137
|
+
}
|
|
138
|
+
changed = 1;
|
|
139
|
+
}
|
|
64
140
|
break;
|
|
65
141
|
}
|
|
66
142
|
case TF_TYPE_TIMESTAMP: {
|
|
67
143
|
char *end;
|
|
68
144
|
int64_t v = strtoll(def, &end, 10);
|
|
69
|
-
if (*end == '\0')
|
|
145
|
+
if (*end == '\0') {
|
|
146
|
+
if (tf_batch_set_timestamp(ob, r, (size_t)ci, v) != TF_OK) {
|
|
147
|
+
free(changed_cols);
|
|
148
|
+
tf_batch_free(ob);
|
|
149
|
+
return TF_ERROR;
|
|
150
|
+
}
|
|
151
|
+
changed = 1;
|
|
152
|
+
}
|
|
70
153
|
break;
|
|
71
154
|
}
|
|
72
155
|
default:
|
|
73
156
|
break;
|
|
74
157
|
}
|
|
158
|
+
if (changed) changed_cols[changed_count++] = (size_t)ci;
|
|
159
|
+
}
|
|
160
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
161
|
+
free(changed_cols);
|
|
162
|
+
tf_batch_free(ob);
|
|
163
|
+
return TF_ERROR;
|
|
164
|
+
}
|
|
165
|
+
if (changed_count > 0) {
|
|
166
|
+
size_t row_no = 0;
|
|
167
|
+
if (tf_size_add(row_base, r, &row_no) != TF_OK ||
|
|
168
|
+
tf_size_add(row_no, 1, &row_no) != TF_OK) {
|
|
169
|
+
free(changed_cols);
|
|
170
|
+
tf_batch_free(ob);
|
|
171
|
+
return TF_ERROR;
|
|
172
|
+
}
|
|
173
|
+
for (size_t i = 0; i < changed_count; i++) {
|
|
174
|
+
if (emit_fill_null_audit(st, ob, r, changed_cols[i], row_no, side) != TF_OK) {
|
|
175
|
+
free(changed_cols);
|
|
176
|
+
tf_batch_free(ob);
|
|
177
|
+
return TF_ERROR;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
75
180
|
}
|
|
76
181
|
}
|
|
77
182
|
|
|
183
|
+
if (tf_size_add(st->row_index, in->n_rows, &st->row_index) != TF_OK) {
|
|
184
|
+
free(changed_cols);
|
|
185
|
+
tf_batch_free(ob);
|
|
186
|
+
return TF_ERROR;
|
|
187
|
+
}
|
|
188
|
+
free(changed_cols);
|
|
78
189
|
*out = ob;
|
|
79
190
|
return TF_OK;
|
|
80
191
|
}
|
|
@@ -83,13 +194,22 @@ static int fill_null_flush(tf_step *self, tf_batch **out, tf_side_channels *side
|
|
|
83
194
|
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
84
195
|
}
|
|
85
196
|
|
|
86
|
-
static void
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
free(st);
|
|
197
|
+
static void fill_null_state_free(fill_null_state *st) {
|
|
198
|
+
if (!st) return;
|
|
199
|
+
tf_audit_options_free(&st->audit_opts);
|
|
200
|
+
if (st->col_names) {
|
|
201
|
+
for (size_t i = 0; i < st->n; i++) free(st->col_names[i]);
|
|
92
202
|
}
|
|
203
|
+
if (st->defaults) {
|
|
204
|
+
for (size_t i = 0; i < st->n; i++) free(st->defaults[i]);
|
|
205
|
+
}
|
|
206
|
+
free(st->col_names);
|
|
207
|
+
free(st->defaults);
|
|
208
|
+
free(st);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static void fill_null_destroy(tf_step *self) {
|
|
212
|
+
if (self) fill_null_state_free(self->state);
|
|
93
213
|
free(self);
|
|
94
214
|
}
|
|
95
215
|
|
|
@@ -101,20 +221,45 @@ tf_step *tf_fill_null_create(const cJSON *args) {
|
|
|
101
221
|
int n = cJSON_GetArraySize(mapping);
|
|
102
222
|
fill_null_state *st = calloc(1, sizeof(fill_null_state));
|
|
103
223
|
if (!st) return NULL;
|
|
104
|
-
st->
|
|
105
|
-
st->
|
|
106
|
-
st->
|
|
224
|
+
tf_audit_options_init(&st->audit_opts, 1);
|
|
225
|
+
st->col_names = tf_callocarray_checked(n > 0 ? (size_t)n : 1, sizeof(char *));
|
|
226
|
+
st->defaults = tf_callocarray_checked(n > 0 ? (size_t)n : 1, sizeof(char *));
|
|
227
|
+
st->n = (size_t)n;
|
|
228
|
+
st->audit_limit = 1000;
|
|
229
|
+
if (!st->col_names || !st->defaults) { fill_null_state_free(st); return NULL; }
|
|
107
230
|
|
|
108
231
|
int i = 0;
|
|
109
232
|
cJSON *entry = NULL;
|
|
110
233
|
cJSON_ArrayForEach(entry, mapping) {
|
|
234
|
+
if (!entry->string || !entry->string[0]) { fill_null_state_free(st); return NULL; }
|
|
111
235
|
st->col_names[i] = strdup(entry->string);
|
|
112
236
|
st->defaults[i] = strdup(cJSON_IsString(entry) ? entry->valuestring : "");
|
|
237
|
+
if (!st->col_names[i] || !st->defaults[i]) { fill_null_state_free(st); return NULL; }
|
|
113
238
|
i++;
|
|
114
239
|
}
|
|
115
240
|
|
|
116
|
-
|
|
117
|
-
|
|
241
|
+
cJSON *audit_j = cJSON_GetObjectItemCaseSensitive(args, "audit");
|
|
242
|
+
st->audit = cJSON_IsTrue(audit_j) ? 1 : 0;
|
|
243
|
+
cJSON *audit_limit_j = cJSON_GetObjectItemCaseSensitive(args, "audit_limit");
|
|
244
|
+
if (!audit_limit_j) audit_limit_j = cJSON_GetObjectItemCaseSensitive(args, "auditLimit");
|
|
245
|
+
if (audit_limit_j) {
|
|
246
|
+
size_t parsed_limit = 0;
|
|
247
|
+
if (tf_json_get_size_arg_any(args, "audit_limit", "auditLimit",
|
|
248
|
+
1, TF_MAX_AUDIT_RECORDS,
|
|
249
|
+
&parsed_limit, "fill-null") < 0) {
|
|
250
|
+
fill_null_state_free(st);
|
|
251
|
+
return NULL;
|
|
252
|
+
}
|
|
253
|
+
st->audit_limit = parsed_limit;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (tf_audit_options_parse(&st->audit_opts, args, "fill-null") != TF_OK) {
|
|
257
|
+
fill_null_state_free(st);
|
|
258
|
+
return NULL;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
262
|
+
if (!step) { fill_null_state_free(st); return NULL; }
|
|
118
263
|
step->process = fill_null_process;
|
|
119
264
|
step->flush = fill_null_flush;
|
|
120
265
|
step->destroy = fill_null_destroy;
|