tranfi 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +177 -21
- package/NOTICE +8 -0
- package/README.md +627 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-BIAIKnrp.js +160 -0
- package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/app/index.html +13 -0
- package/binding.gyp +121 -0
- package/csrc/arena.c +93 -0
- package/csrc/batch.c +976 -0
- package/csrc/buffer.c +154 -0
- package/csrc/cJSON.c +3386 -0
- package/csrc/cJSON.h +316 -0
- package/csrc/codec_csv.c +1951 -0
- package/csrc/codec_jsonl.c +1086 -0
- package/csrc/codec_table.c +248 -0
- package/csrc/codec_text.c +447 -0
- package/csrc/compiler.c +130 -0
- package/csrc/config.h +21 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +5417 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1553 -0
- package/csrc/expr.h +58 -0
- package/csrc/internal.h +539 -0
- package/csrc/ir.c +166 -0
- package/csrc/ir.h +208 -0
- package/csrc/ir_schema.c +75 -0
- package/csrc/ir_serialize.c +166 -0
- package/csrc/ir_sql.c +1822 -0
- package/csrc/ir_validate.c +576 -0
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +1241 -0
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +283 -0
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +255 -0
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +248 -0
- package/csrc/op_cast.c +523 -0
- package/csrc/op_clip.c +99 -0
- package/csrc/op_date_trunc.c +355 -0
- package/csrc/op_datetime.c +394 -0
- package/csrc/op_derive.c +216 -0
- package/csrc/op_diff.c +250 -0
- package/csrc/op_ewma.c +222 -0
- package/csrc/op_explode.c +206 -0
- package/csrc/op_fill_down.c +235 -0
- package/csrc/op_fill_null.c +268 -0
- package/csrc/op_filter.c +181 -0
- package/csrc/op_frequency.c +721 -0
- package/csrc/op_grep.c +181 -0
- package/csrc/op_group_agg.c +1956 -0
- package/csrc/op_hash.c +159 -0
- package/csrc/op_head.c +84 -0
- package/csrc/op_interpolate.c +445 -0
- package/csrc/op_join.c +2902 -0
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +419 -0
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +242 -0
- package/csrc/op_normalize.c +510 -0
- package/csrc/op_onehot.c +457 -0
- package/csrc/op_pivot.c +1754 -0
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +3044 -0
- package/csrc/op_rename.c +129 -0
- package/csrc/op_replace.c +354 -0
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +158 -0
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +340 -0
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +95 -0
- package/csrc/op_sort.c +819 -0
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +151 -0
- package/csrc/op_split_data.c +119 -0
- package/csrc/op_stack.c +271 -0
- package/csrc/op_stats.c +875 -0
- package/csrc/op_step.c +333 -0
- package/csrc/op_tail.c +105 -0
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +357 -0
- package/csrc/op_trim.c +138 -0
- package/csrc/op_unique.c +1343 -0
- package/csrc/op_unpivot.c +193 -0
- package/csrc/op_validate.c +648 -0
- package/csrc/op_window.c +591 -0
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +1088 -0
- package/csrc/recipes.c +104 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +506 -0
- package/csrc/report.h +22 -0
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +291 -0
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +218 -0
- package/napi_api.c +534 -0
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +64 -59
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +190 -0
- package/src/engines/duckdb.js +142 -0
- package/src/index.js +925 -0
- package/src/memory_policy.js +411 -0
- package/src/native.js +18 -0
- package/src/pipeline.js +709 -0
- package/src/recipe_json.js +80 -0
- package/src/server.js +279 -0
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +21 -0
- package/wasm/index.js +732 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/dist/bundle.js +0 -1
- package/index.html +0 -18
- package/src/app.css +0 -169
- package/src/app.js +0 -203
- package/src/app.vue +0 -250
- package/src/bulma-input.vue +0 -110
- package/src/common-inputs.js +0 -28
- package/src/main.js +0 -20
- package/src/transforms.js +0 -166
- package/webpack.config.js +0 -108
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_source_name.c -- Append the current host-provided source name.
|
|
3
|
+
*
|
|
4
|
+
* The source name is pipeline metadata set by the embedding host before push()
|
|
5
|
+
* or input-boundary flushes. The op itself is row-local and never opens files.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "internal.h"
|
|
9
|
+
#include "cJSON.h"
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
#include <string.h>
|
|
12
|
+
#include <stdio.h>
|
|
13
|
+
|
|
14
|
+
typedef struct {
|
|
15
|
+
char *result;
|
|
16
|
+
char *default_value;
|
|
17
|
+
} source_name_state;
|
|
18
|
+
|
|
19
|
+
static int source_name_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
20
|
+
tf_side_channels *side) {
|
|
21
|
+
source_name_state *st = self ? self->state : NULL;
|
|
22
|
+
if (!st || !in || !out) return TF_ERROR;
|
|
23
|
+
*out = NULL;
|
|
24
|
+
|
|
25
|
+
if (tf_batch_col_index(in, st->result) >= 0) {
|
|
26
|
+
char msg[256];
|
|
27
|
+
snprintf(msg, sizeof(msg), "source-name result column already exists: %s", st->result);
|
|
28
|
+
tf_set_last_error(msg);
|
|
29
|
+
return TF_ERROR;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const char *extra_names[1] = {st->result};
|
|
33
|
+
tf_type extra_types[1] = {TF_TYPE_STRING};
|
|
34
|
+
tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows > 0 ? in->n_rows : 1);
|
|
35
|
+
if (!ob) return TF_ERROR;
|
|
36
|
+
if (tf_batch_clone_with_extra_cols(ob, in, extra_names, extra_types, 1) != TF_OK) {
|
|
37
|
+
tf_batch_free(ob);
|
|
38
|
+
return TF_ERROR;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const char *value = st->default_value ? st->default_value : "";
|
|
42
|
+
if (side && side->source_name && side->source_name[0]) value = side->source_name;
|
|
43
|
+
|
|
44
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
45
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
46
|
+
tf_batch_free(ob);
|
|
47
|
+
return TF_ERROR;
|
|
48
|
+
}
|
|
49
|
+
if (tf_batch_set_string(ob, r, in->n_cols, value) != TF_OK) {
|
|
50
|
+
tf_batch_free(ob);
|
|
51
|
+
return TF_ERROR;
|
|
52
|
+
}
|
|
53
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
54
|
+
tf_batch_free(ob);
|
|
55
|
+
return TF_ERROR;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (ob->n_rows > 0) {
|
|
60
|
+
*out = ob;
|
|
61
|
+
} else {
|
|
62
|
+
tf_batch_free(ob);
|
|
63
|
+
}
|
|
64
|
+
return TF_OK;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static int source_name_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
68
|
+
(void)self;
|
|
69
|
+
(void)side;
|
|
70
|
+
*out = NULL;
|
|
71
|
+
return TF_OK;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static void source_name_destroy(tf_step *self) {
|
|
75
|
+
if (!self) return;
|
|
76
|
+
source_name_state *st = self->state;
|
|
77
|
+
if (st) {
|
|
78
|
+
free(st->result);
|
|
79
|
+
free(st->default_value);
|
|
80
|
+
free(st);
|
|
81
|
+
}
|
|
82
|
+
free(self);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
tf_step *tf_source_name_create(const cJSON *args) {
|
|
86
|
+
const char *result = "_source";
|
|
87
|
+
const char *default_value = "";
|
|
88
|
+
if (args) {
|
|
89
|
+
cJSON *res = cJSON_GetObjectItemCaseSensitive(args, "result");
|
|
90
|
+
if (!cJSON_IsString(res)) res = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
91
|
+
if (!cJSON_IsString(res)) res = cJSON_GetObjectItemCaseSensitive(args, "as");
|
|
92
|
+
if (cJSON_IsString(res) && res->valuestring && res->valuestring[0]) result = res->valuestring;
|
|
93
|
+
cJSON *def = cJSON_GetObjectItemCaseSensitive(args, "default");
|
|
94
|
+
if (cJSON_IsString(def) && def->valuestring) default_value = def->valuestring;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
source_name_state *st = calloc(1, sizeof(source_name_state));
|
|
98
|
+
if (!st) return NULL;
|
|
99
|
+
st->result = strdup(result);
|
|
100
|
+
st->default_value = strdup(default_value);
|
|
101
|
+
if (!st->result || !st->default_value) {
|
|
102
|
+
free(st->result);
|
|
103
|
+
free(st->default_value);
|
|
104
|
+
free(st);
|
|
105
|
+
return NULL;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
109
|
+
if (!step) {
|
|
110
|
+
free(st->result);
|
|
111
|
+
free(st->default_value);
|
|
112
|
+
free(st);
|
|
113
|
+
return NULL;
|
|
114
|
+
}
|
|
115
|
+
step->process = source_name_process;
|
|
116
|
+
step->flush = source_name_flush;
|
|
117
|
+
step->destroy = source_name_destroy;
|
|
118
|
+
step->state = st;
|
|
119
|
+
return step;
|
|
120
|
+
}
|
package/csrc/op_split.c
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_split.c — Split column into multiple columns.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"column": "name", "delimiter": " ", "names": ["first", "last"]}
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include "internal.h"
|
|
8
|
+
#include "cJSON.h"
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
#include <string.h>
|
|
11
|
+
|
|
12
|
+
typedef struct {
|
|
13
|
+
char *column;
|
|
14
|
+
char *delimiter;
|
|
15
|
+
char **names;
|
|
16
|
+
size_t n_names;
|
|
17
|
+
} split_state;
|
|
18
|
+
|
|
19
|
+
static int split_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
20
|
+
tf_side_channels *side) {
|
|
21
|
+
(void)side;
|
|
22
|
+
split_state *st = self->state;
|
|
23
|
+
*out = NULL;
|
|
24
|
+
|
|
25
|
+
size_t out_cols = 0;
|
|
26
|
+
if (tf_size_add(in->n_cols, st->n_names, &out_cols) != TF_OK) return TF_ERROR;
|
|
27
|
+
tf_batch *ob = tf_batch_create(out_cols, in->n_rows);
|
|
28
|
+
if (!ob) return TF_ERROR;
|
|
29
|
+
|
|
30
|
+
/* Copy existing schema */
|
|
31
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
32
|
+
if (tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]) != TF_OK) {
|
|
33
|
+
tf_batch_free(ob);
|
|
34
|
+
return TF_ERROR;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/* Add new columns */
|
|
38
|
+
for (size_t k = 0; k < st->n_names; k++) {
|
|
39
|
+
if (tf_batch_set_schema(ob, in->n_cols + k, st->names[k], TF_TYPE_STRING) != TF_OK) {
|
|
40
|
+
tf_batch_free(ob);
|
|
41
|
+
return TF_ERROR;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
int ci = tf_batch_col_index(in, st->column);
|
|
46
|
+
size_t delim_len = strlen(st->delimiter);
|
|
47
|
+
|
|
48
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
49
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
50
|
+
tf_batch_free(ob);
|
|
51
|
+
return TF_ERROR;
|
|
52
|
+
}
|
|
53
|
+
/* Initialize new columns as null */
|
|
54
|
+
for (size_t k = 0; k < st->n_names; k++) {
|
|
55
|
+
if (tf_batch_set_null(ob, r, in->n_cols + k) != TF_OK) {
|
|
56
|
+
tf_batch_free(ob);
|
|
57
|
+
return TF_ERROR;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (ci >= 0 && !tf_batch_is_null(in, r, ci) && in->col_types[ci] == TF_TYPE_STRING) {
|
|
62
|
+
const char *val = tf_batch_get_string(in, r, ci);
|
|
63
|
+
const char *p = val ? val : "";
|
|
64
|
+
for (size_t k = 0; k < st->n_names && *p; k++) {
|
|
65
|
+
const char *found = strstr(p, st->delimiter);
|
|
66
|
+
size_t tok_len = found ? (size_t)(found - p) : strlen(p);
|
|
67
|
+
char *tok = malloc(tok_len + 1);
|
|
68
|
+
if (!tok) {
|
|
69
|
+
tf_batch_free(ob);
|
|
70
|
+
return TF_ERROR;
|
|
71
|
+
}
|
|
72
|
+
memcpy(tok, p, tok_len);
|
|
73
|
+
tok[tok_len] = '\0';
|
|
74
|
+
int rc = tf_batch_set_string(ob, r, in->n_cols + k, tok);
|
|
75
|
+
free(tok);
|
|
76
|
+
if (rc != TF_OK) {
|
|
77
|
+
tf_batch_free(ob);
|
|
78
|
+
return TF_ERROR;
|
|
79
|
+
}
|
|
80
|
+
if (found) p = found + delim_len;
|
|
81
|
+
else break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
85
|
+
tf_batch_free(ob);
|
|
86
|
+
return TF_ERROR;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
*out = ob;
|
|
91
|
+
return TF_OK;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static int split_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
95
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static void split_state_free(split_state *st) {
|
|
99
|
+
if (!st) return;
|
|
100
|
+
free(st->column);
|
|
101
|
+
free(st->delimiter);
|
|
102
|
+
if (st->names) {
|
|
103
|
+
for (size_t i = 0; i < st->n_names; i++) free(st->names[i]);
|
|
104
|
+
}
|
|
105
|
+
free(st->names);
|
|
106
|
+
free(st);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static void split_destroy(tf_step *self) {
|
|
110
|
+
split_state_free(self ? self->state : NULL);
|
|
111
|
+
free(self);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
tf_step *tf_split_create(const cJSON *args) {
|
|
115
|
+
if (!args) return NULL;
|
|
116
|
+
cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
117
|
+
cJSON *names_j = cJSON_GetObjectItemCaseSensitive(args, "names");
|
|
118
|
+
if (!cJSON_IsString(col_j) || !cJSON_IsArray(names_j)) return NULL;
|
|
119
|
+
|
|
120
|
+
int n = cJSON_GetArraySize(names_j);
|
|
121
|
+
if (n <= 0) return NULL;
|
|
122
|
+
|
|
123
|
+
split_state *st = calloc(1, sizeof(split_state));
|
|
124
|
+
if (!st) return NULL;
|
|
125
|
+
st->column = strdup(col_j->valuestring);
|
|
126
|
+
if (!st->column) { split_state_free(st); return NULL; }
|
|
127
|
+
|
|
128
|
+
cJSON *delim_j = cJSON_GetObjectItemCaseSensitive(args, "delimiter");
|
|
129
|
+
const char *delim = cJSON_IsString(delim_j) ? delim_j->valuestring : " ";
|
|
130
|
+
if (delim[0] == '\0') { split_state_free(st); return NULL; }
|
|
131
|
+
st->delimiter = strdup(delim);
|
|
132
|
+
if (!st->delimiter) { split_state_free(st); return NULL; }
|
|
133
|
+
|
|
134
|
+
st->names = calloc((size_t)n, sizeof(char *));
|
|
135
|
+
if (!st->names) { split_state_free(st); return NULL; }
|
|
136
|
+
st->n_names = (size_t)n;
|
|
137
|
+
for (int i = 0; i < n; i++) {
|
|
138
|
+
cJSON *item = cJSON_GetArrayItem(names_j, i);
|
|
139
|
+
if (!cJSON_IsString(item)) { split_state_free(st); return NULL; }
|
|
140
|
+
st->names[i] = strdup(item->valuestring);
|
|
141
|
+
if (!st->names[i]) { split_state_free(st); return NULL; }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
145
|
+
if (!step) { split_state_free(st); return NULL; }
|
|
146
|
+
step->process = split_process;
|
|
147
|
+
step->flush = split_flush;
|
|
148
|
+
step->destroy = split_destroy;
|
|
149
|
+
step->state = st;
|
|
150
|
+
return step;
|
|
151
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_split_data.c — Train/test split with probabilistic assignment.
|
|
3
|
+
* Uses LCG PRNG for reproducible splitting.
|
|
4
|
+
*
|
|
5
|
+
* Config: {"ratio": 0.8, "result": "_split", "seed": 42}
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "internal.h"
|
|
9
|
+
#include "cJSON.h"
|
|
10
|
+
#include <math.h>
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
#include <stdio.h>
|
|
14
|
+
|
|
15
|
+
typedef struct {
|
|
16
|
+
char *result;
|
|
17
|
+
double ratio;
|
|
18
|
+
uint64_t seed;
|
|
19
|
+
uint64_t row_index;
|
|
20
|
+
} split_data_state;
|
|
21
|
+
|
|
22
|
+
/* Simple LCG for deterministic random */
|
|
23
|
+
static double lcg_random(uint64_t seed, uint64_t index) {
|
|
24
|
+
uint64_t x = seed ^ (index * 6364136223846793005ULL + 1442695040888963407ULL);
|
|
25
|
+
x = x * 6364136223846793005ULL + 1442695040888963407ULL;
|
|
26
|
+
x = x * 6364136223846793005ULL + 1442695040888963407ULL;
|
|
27
|
+
return (double)(x >> 33) / (double)(1ULL << 31);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static int split_data_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
31
|
+
tf_side_channels *side) {
|
|
32
|
+
(void)side;
|
|
33
|
+
split_data_state *st = self->state;
|
|
34
|
+
*out = NULL;
|
|
35
|
+
|
|
36
|
+
const char *extra_names[1] = {st->result};
|
|
37
|
+
tf_type extra_types[1] = {TF_TYPE_STRING};
|
|
38
|
+
tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
|
|
39
|
+
if (!ob) return TF_ERROR;
|
|
40
|
+
if (tf_batch_clone_with_extra_cols(ob, in, extra_names, extra_types, 1) != TF_OK) {
|
|
41
|
+
tf_batch_free(ob);
|
|
42
|
+
return TF_ERROR;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
46
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
47
|
+
tf_batch_free(ob);
|
|
48
|
+
return TF_ERROR;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
double rval = lcg_random(st->seed, st->row_index);
|
|
52
|
+
const char *label = (rval < st->ratio) ? "train" : "test";
|
|
53
|
+
if (tf_batch_set_string(ob, r, in->n_cols, label) != TF_OK) {
|
|
54
|
+
tf_batch_free(ob);
|
|
55
|
+
return TF_ERROR;
|
|
56
|
+
}
|
|
57
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
58
|
+
tf_batch_free(ob);
|
|
59
|
+
return TF_ERROR;
|
|
60
|
+
}
|
|
61
|
+
st->row_index++;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
*out = ob;
|
|
65
|
+
return TF_OK;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static int split_data_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
69
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static void split_data_destroy(tf_step *self) {
|
|
73
|
+
split_data_state *st = self->state;
|
|
74
|
+
if (st) { free(st->result); free(st); }
|
|
75
|
+
free(self);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
tf_step *tf_split_data_create(const cJSON *args) {
|
|
79
|
+
if (!args) return NULL;
|
|
80
|
+
|
|
81
|
+
split_data_state *st = calloc(1, sizeof(split_data_state));
|
|
82
|
+
if (!st) return NULL;
|
|
83
|
+
|
|
84
|
+
cJSON *ratio_j = cJSON_GetObjectItemCaseSensitive(args, "ratio");
|
|
85
|
+
st->ratio = 0.8;
|
|
86
|
+
if (ratio_j) {
|
|
87
|
+
if (!cJSON_IsNumber(ratio_j) || !isfinite(ratio_j->valuedouble) ||
|
|
88
|
+
ratio_j->valuedouble < 0.0 || ratio_j->valuedouble > 1.0) {
|
|
89
|
+
tf_set_last_error("split-data: ratio must be a finite number between 0 and 1");
|
|
90
|
+
free(st);
|
|
91
|
+
return NULL;
|
|
92
|
+
}
|
|
93
|
+
st->ratio = ratio_j->valuedouble;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
cJSON *seed_j = cJSON_GetObjectItemCaseSensitive(args, "seed");
|
|
97
|
+
st->seed = 42;
|
|
98
|
+
if (seed_j) {
|
|
99
|
+
size_t parsed_seed = 0;
|
|
100
|
+
if (tf_json_size_value(seed_j, "seed", 0, TF_MAX_SAFE_SIZE_ARG,
|
|
101
|
+
&parsed_seed, "split-data") < 0) {
|
|
102
|
+
free(st);
|
|
103
|
+
return NULL;
|
|
104
|
+
}
|
|
105
|
+
st->seed = (uint64_t)parsed_seed;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
|
|
109
|
+
st->result = strdup(cJSON_IsString(res_j) ? res_j->valuestring : "_split");
|
|
110
|
+
if (!st->result) { free(st); return NULL; }
|
|
111
|
+
|
|
112
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
113
|
+
if (!step) { free(st->result); free(st); return NULL; }
|
|
114
|
+
step->process = split_data_process;
|
|
115
|
+
step->flush = split_data_flush;
|
|
116
|
+
step->destroy = split_data_destroy;
|
|
117
|
+
step->state = st;
|
|
118
|
+
return step;
|
|
119
|
+
}
|
package/csrc/op_stack.c
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_stack.c — Vertically concatenate a second CSV file into the stream.
|
|
3
|
+
*
|
|
4
|
+
* Passes through all input batches, then on flush reads and appends
|
|
5
|
+
* rows from a second CSV file. Optionally adds a tag column to
|
|
6
|
+
* identify the source.
|
|
7
|
+
*
|
|
8
|
+
* Args:
|
|
9
|
+
* file (string, required) — path to CSV file to append
|
|
10
|
+
* tag (string, optional) — name of source-identifying column
|
|
11
|
+
* tag_value (string, optional) — value for tag column on appended rows
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#include "internal.h"
|
|
15
|
+
#include "cJSON.h"
|
|
16
|
+
#include <stdlib.h>
|
|
17
|
+
#include <string.h>
|
|
18
|
+
#include <stdio.h>
|
|
19
|
+
|
|
20
|
+
typedef struct {
|
|
21
|
+
char *file_path;
|
|
22
|
+
char *validated_file_path;
|
|
23
|
+
char *tag_col; /* NULL if no tag */
|
|
24
|
+
char *tag_value; /* value for appended rows */
|
|
25
|
+
char *tag_value_in; /* value for passthrough rows (filename or "input") */
|
|
26
|
+
FILE *file;
|
|
27
|
+
tf_decoder *decoder;
|
|
28
|
+
tf_batch **pending_batches;
|
|
29
|
+
size_t n_pending_batches;
|
|
30
|
+
size_t pending_batch_index;
|
|
31
|
+
int file_started;
|
|
32
|
+
int file_done;
|
|
33
|
+
} stack_state;
|
|
34
|
+
|
|
35
|
+
static void stack_clear_pending(stack_state *st) {
|
|
36
|
+
if (!st || !st->pending_batches) return;
|
|
37
|
+
tf_batch_array_free_items(st->pending_batches + st->pending_batch_index,
|
|
38
|
+
st->n_pending_batches - st->pending_batch_index);
|
|
39
|
+
free(st->pending_batches);
|
|
40
|
+
st->pending_batches = NULL;
|
|
41
|
+
st->n_pending_batches = 0;
|
|
42
|
+
st->pending_batch_index = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static void stack_close_file_reader(stack_state *st) {
|
|
46
|
+
if (!st) return;
|
|
47
|
+
stack_clear_pending(st);
|
|
48
|
+
if (st->decoder) {
|
|
49
|
+
st->decoder->destroy(st->decoder);
|
|
50
|
+
st->decoder = NULL;
|
|
51
|
+
}
|
|
52
|
+
if (st->file) {
|
|
53
|
+
fclose(st->file);
|
|
54
|
+
st->file = NULL;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static void stack_state_free(stack_state *st) {
|
|
59
|
+
if (!st) return;
|
|
60
|
+
stack_close_file_reader(st);
|
|
61
|
+
free(st->file_path);
|
|
62
|
+
free(st->validated_file_path);
|
|
63
|
+
free(st->tag_col);
|
|
64
|
+
free(st->tag_value);
|
|
65
|
+
free(st->tag_value_in);
|
|
66
|
+
free(st);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static void stack_destroy(tf_step *self) {
|
|
70
|
+
if (self) stack_state_free(self->state);
|
|
71
|
+
free(self);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
TF_WARN_UNUSED static int stack_write_error(tf_side_channels *side, const char *msg) {
|
|
75
|
+
return tf_side_write_error(side, msg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static int stack_open_file_reader(stack_state *st, tf_side_channels *side) {
|
|
79
|
+
if (st->file_started) return TF_OK;
|
|
80
|
+
st->file_started = 1;
|
|
81
|
+
st->file = tf_policy_fopen_read(st->file_path, st->validated_file_path);
|
|
82
|
+
if (!st->file) {
|
|
83
|
+
if (stack_write_error(side, "stack: cannot open file") != TF_OK) return TF_ERROR;
|
|
84
|
+
return TF_ERROR;
|
|
85
|
+
}
|
|
86
|
+
st->decoder = tf_csv_decoder_create(NULL);
|
|
87
|
+
if (!st->decoder) {
|
|
88
|
+
stack_close_file_reader(st);
|
|
89
|
+
return TF_ERROR;
|
|
90
|
+
}
|
|
91
|
+
return TF_OK;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static int stack_take_pending_batch(stack_state *st, tf_batch **out) {
|
|
95
|
+
if (!st->pending_batches || st->pending_batch_index >= st->n_pending_batches) {
|
|
96
|
+
stack_clear_pending(st);
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
*out = st->pending_batches[st->pending_batch_index];
|
|
100
|
+
st->pending_batches[st->pending_batch_index] = NULL;
|
|
101
|
+
st->pending_batch_index++;
|
|
102
|
+
if (st->pending_batch_index >= st->n_pending_batches) stack_clear_pending(st);
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static int stack_next_decoded_file_batch(stack_state *st, tf_batch **out,
|
|
107
|
+
tf_side_channels *side) {
|
|
108
|
+
*out = NULL;
|
|
109
|
+
if (stack_take_pending_batch(st, out)) return TF_OK;
|
|
110
|
+
if (st->file_done) return TF_OK;
|
|
111
|
+
if (stack_open_file_reader(st, side) != TF_OK) return TF_ERROR;
|
|
112
|
+
|
|
113
|
+
for (;;) {
|
|
114
|
+
uint8_t buf[64 * 1024];
|
|
115
|
+
size_t n = fread(buf, 1, sizeof(buf), st->file);
|
|
116
|
+
tf_batch **batches = NULL;
|
|
117
|
+
size_t n_batches = 0;
|
|
118
|
+
int rc = TF_OK;
|
|
119
|
+
|
|
120
|
+
if (n > 0) {
|
|
121
|
+
rc = st->decoder->decode(st->decoder, buf, n, &batches, &n_batches, side);
|
|
122
|
+
} else {
|
|
123
|
+
if (ferror(st->file)) {
|
|
124
|
+
int err_rc = stack_write_error(side, "stack: failed reading file");
|
|
125
|
+
stack_close_file_reader(st);
|
|
126
|
+
st->file_done = 1;
|
|
127
|
+
if (err_rc != TF_OK) return TF_ERROR;
|
|
128
|
+
return TF_ERROR;
|
|
129
|
+
}
|
|
130
|
+
rc = st->decoder->flush(st->decoder, &batches, &n_batches, side);
|
|
131
|
+
st->file_done = 1;
|
|
132
|
+
stack_close_file_reader(st);
|
|
133
|
+
}
|
|
134
|
+
if (rc != TF_OK) {
|
|
135
|
+
tf_batch_array_free(batches, n_batches);
|
|
136
|
+
stack_close_file_reader(st);
|
|
137
|
+
st->file_done = 1;
|
|
138
|
+
return TF_ERROR;
|
|
139
|
+
}
|
|
140
|
+
st->pending_batches = batches;
|
|
141
|
+
st->n_pending_batches = n_batches;
|
|
142
|
+
st->pending_batch_index = 0;
|
|
143
|
+
if (stack_take_pending_batch(st, out)) return TF_OK;
|
|
144
|
+
if (st->file_done) return TF_OK;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/*
|
|
149
|
+
* Add tag column to a batch. Returns a new batch with the tag column prepended.
|
|
150
|
+
*/
|
|
151
|
+
static tf_batch *add_tag_column(tf_batch *in, const char *tag_col, const char *tag_value) {
|
|
152
|
+
size_t out_cols = 0;
|
|
153
|
+
if (tf_size_add(in->n_cols, 1, &out_cols) != TF_OK) return NULL;
|
|
154
|
+
tf_batch *out = tf_batch_create(out_cols, in->n_rows);
|
|
155
|
+
if (!out) return NULL;
|
|
156
|
+
|
|
157
|
+
if (tf_batch_set_schema(out, 0, tag_col, TF_TYPE_STRING) != TF_OK) goto fail;
|
|
158
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
159
|
+
if (tf_batch_set_schema(out, c + 1, in->col_names[c], in->col_types[c]) != TF_OK) goto fail;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
163
|
+
if (tf_batch_ensure_capacity(out, r + 1) != TF_OK) goto fail;
|
|
164
|
+
if (tf_batch_set_string(out, r, 0, tag_value ? tag_value : "") != TF_OK) goto fail;
|
|
165
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
166
|
+
if (tf_batch_copy_cell(out, r, c + 1, in, r, c) != TF_OK) goto fail;
|
|
167
|
+
}
|
|
168
|
+
if (tf_batch_expose_row(out, r) != TF_OK) goto fail;
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
|
|
172
|
+
fail:
|
|
173
|
+
tf_batch_free(out);
|
|
174
|
+
return NULL;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static int stack_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
178
|
+
tf_side_channels *side) {
|
|
179
|
+
stack_state *st = self->state;
|
|
180
|
+
(void)side;
|
|
181
|
+
*out = NULL;
|
|
182
|
+
|
|
183
|
+
if (st->tag_col) {
|
|
184
|
+
*out = add_tag_column(in, st->tag_col, st->tag_value_in);
|
|
185
|
+
return *out ? TF_OK : TF_ERROR;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
189
|
+
if (!ob) return TF_ERROR;
|
|
190
|
+
if (tf_batch_clone_schema(ob, in) != TF_OK) {
|
|
191
|
+
tf_batch_free(ob);
|
|
192
|
+
return TF_ERROR;
|
|
193
|
+
}
|
|
194
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
195
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
196
|
+
tf_batch_free(ob);
|
|
197
|
+
return TF_ERROR;
|
|
198
|
+
}
|
|
199
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
200
|
+
tf_batch_free(ob);
|
|
201
|
+
return TF_ERROR;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
*out = ob;
|
|
205
|
+
return TF_OK;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
static int stack_flush_next(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
209
|
+
stack_state *st = self->state;
|
|
210
|
+
*out = NULL;
|
|
211
|
+
|
|
212
|
+
for (;;) {
|
|
213
|
+
tf_batch *batch = NULL;
|
|
214
|
+
if (stack_next_decoded_file_batch(st, &batch, side) != TF_OK) return TF_ERROR;
|
|
215
|
+
if (!batch) return TF_OK;
|
|
216
|
+
if (batch->n_rows == 0) {
|
|
217
|
+
tf_batch_free(batch);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (!st->tag_col) {
|
|
221
|
+
*out = batch;
|
|
222
|
+
return TF_OK;
|
|
223
|
+
}
|
|
224
|
+
*out = add_tag_column(batch, st->tag_col, st->tag_value);
|
|
225
|
+
tf_batch_free(batch);
|
|
226
|
+
return *out ? TF_OK : TF_ERROR;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
static int stack_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
231
|
+
return stack_flush_next(self, out, side);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
tf_step *tf_stack_create(const cJSON *args) {
|
|
235
|
+
if (!args) return NULL;
|
|
236
|
+
|
|
237
|
+
cJSON *file = cJSON_GetObjectItemCaseSensitive(args, "file");
|
|
238
|
+
if (!cJSON_IsString(file) || !file->valuestring[0]) return NULL;
|
|
239
|
+
|
|
240
|
+
stack_state *st = calloc(1, sizeof(stack_state));
|
|
241
|
+
if (!st) return NULL;
|
|
242
|
+
|
|
243
|
+
st->file_path = strdup(file->valuestring);
|
|
244
|
+
if (!st->file_path) { stack_state_free(st); return NULL; }
|
|
245
|
+
const char *validated = tf_policy_validated_path_arg(args, "file");
|
|
246
|
+
if (validated) {
|
|
247
|
+
st->validated_file_path = strdup(validated);
|
|
248
|
+
if (!st->validated_file_path) { stack_state_free(st); return NULL; }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
cJSON *tag = cJSON_GetObjectItemCaseSensitive(args, "tag");
|
|
252
|
+
if (cJSON_IsString(tag) && tag->valuestring[0]) {
|
|
253
|
+
st->tag_col = strdup(tag->valuestring);
|
|
254
|
+
cJSON *tv = cJSON_GetObjectItemCaseSensitive(args, "tag_value");
|
|
255
|
+
st->tag_value = strdup(cJSON_IsString(tv) ? tv->valuestring : st->file_path);
|
|
256
|
+
st->tag_value_in = strdup("input");
|
|
257
|
+
if (!st->tag_col || !st->tag_value || !st->tag_value_in) {
|
|
258
|
+
stack_state_free(st);
|
|
259
|
+
return NULL;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
264
|
+
if (!step) { stack_state_free(st); return NULL; }
|
|
265
|
+
step->process = stack_process;
|
|
266
|
+
step->flush = stack_flush;
|
|
267
|
+
step->flush_next = stack_flush_next;
|
|
268
|
+
step->destroy = stack_destroy;
|
|
269
|
+
step->state = st;
|
|
270
|
+
return step;
|
|
271
|
+
}
|