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,227 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_json_extract.c -- Extract scalar or JSON text values from per-row JSON columns.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"column":"_line", "path":"/user/id", "result":"user_id", "type":"int"}
|
|
5
|
+
* Supports JSON Pointer plus a small JSONPath-style subset: $.a[0].b or a[0].b.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "internal.h"
|
|
9
|
+
#include "cJSON.h"
|
|
10
|
+
#include <ctype.h>
|
|
11
|
+
#include <errno.h>
|
|
12
|
+
#include <stdlib.h>
|
|
13
|
+
#include <string.h>
|
|
14
|
+
|
|
15
|
+
typedef struct {
|
|
16
|
+
char *column;
|
|
17
|
+
char *path;
|
|
18
|
+
char *result;
|
|
19
|
+
tf_type type;
|
|
20
|
+
} json_extract_state;
|
|
21
|
+
|
|
22
|
+
static tf_type parse_output_type(const char *s) {
|
|
23
|
+
if (!s || strcmp(s, "string") == 0 || strcmp(s, "str") == 0 || strcmp(s, "json") == 0)
|
|
24
|
+
return TF_TYPE_STRING;
|
|
25
|
+
if (strcmp(s, "int") == 0 || strcmp(s, "int64") == 0)
|
|
26
|
+
return TF_TYPE_INT64;
|
|
27
|
+
if (strcmp(s, "float") == 0 || strcmp(s, "float64") == 0 || strcmp(s, "number") == 0)
|
|
28
|
+
return TF_TYPE_FLOAT64;
|
|
29
|
+
if (strcmp(s, "bool") == 0 || strcmp(s, "boolean") == 0)
|
|
30
|
+
return TF_TYPE_BOOL;
|
|
31
|
+
return TF_TYPE_NULL;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static int parse_strict_int64(const char *s, int64_t *out) {
|
|
35
|
+
if (!s) return 0;
|
|
36
|
+
errno = 0;
|
|
37
|
+
char *end = NULL;
|
|
38
|
+
long long v = strtoll(s, &end, 10);
|
|
39
|
+
if (errno != 0 || end == s) return 0;
|
|
40
|
+
while (*end && isspace((unsigned char)*end)) end++;
|
|
41
|
+
if (*end != '\0') return 0;
|
|
42
|
+
*out = (int64_t)v;
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static int parse_strict_double(const char *s, double *out) {
|
|
47
|
+
if (!s) return 0;
|
|
48
|
+
errno = 0;
|
|
49
|
+
char *end = NULL;
|
|
50
|
+
double v = strtod(s, &end);
|
|
51
|
+
if (errno != 0 || end == s) return 0;
|
|
52
|
+
while (*end && isspace((unsigned char)*end)) end++;
|
|
53
|
+
if (*end != '\0') return 0;
|
|
54
|
+
*out = v;
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static int str_eq_ci(const char *a, const char *b) {
|
|
59
|
+
while (*a && *b) {
|
|
60
|
+
if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) return 0;
|
|
61
|
+
a++; b++;
|
|
62
|
+
}
|
|
63
|
+
return *a == '\0' && *b == '\0';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static int parse_bool_string(const char *s, int *out) {
|
|
67
|
+
if (!s) return 0;
|
|
68
|
+
if (str_eq_ci(s, "true") || strcmp(s, "1") == 0) { *out = 1; return 1; }
|
|
69
|
+
if (str_eq_ci(s, "false") || strcmp(s, "0") == 0) { *out = 0; return 1; }
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static int set_extracted_value(tf_batch *ob, size_t row, size_t col,
|
|
74
|
+
tf_type type, const cJSON *val) {
|
|
75
|
+
if (!val || cJSON_IsNull(val)) {
|
|
76
|
+
return tf_batch_set_null(ob, row, col);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
switch (type) {
|
|
80
|
+
case TF_TYPE_STRING: {
|
|
81
|
+
if (cJSON_IsString(val)) {
|
|
82
|
+
return tf_batch_set_string(ob, row, col, val->valuestring ? val->valuestring : "");
|
|
83
|
+
}
|
|
84
|
+
char *printed = cJSON_PrintUnformatted((cJSON *)val);
|
|
85
|
+
if (!printed) return tf_batch_set_null(ob, row, col);
|
|
86
|
+
int rc = tf_batch_set_string(ob, row, col, printed);
|
|
87
|
+
free(printed);
|
|
88
|
+
return rc;
|
|
89
|
+
}
|
|
90
|
+
case TF_TYPE_INT64: {
|
|
91
|
+
int64_t out = 0;
|
|
92
|
+
if (cJSON_IsNumber(val)) return tf_batch_set_int64(ob, row, col, (int64_t)val->valuedouble);
|
|
93
|
+
if (cJSON_IsBool(val)) return tf_batch_set_int64(ob, row, col, cJSON_IsTrue(val) ? 1 : 0);
|
|
94
|
+
if (cJSON_IsString(val) && parse_strict_int64(val->valuestring, &out)) return tf_batch_set_int64(ob, row, col, out);
|
|
95
|
+
return tf_batch_set_null(ob, row, col);
|
|
96
|
+
}
|
|
97
|
+
case TF_TYPE_FLOAT64: {
|
|
98
|
+
double out = 0.0;
|
|
99
|
+
if (cJSON_IsNumber(val)) return tf_batch_set_float64(ob, row, col, val->valuedouble);
|
|
100
|
+
if (cJSON_IsBool(val)) return tf_batch_set_float64(ob, row, col, cJSON_IsTrue(val) ? 1.0 : 0.0);
|
|
101
|
+
if (cJSON_IsString(val) && parse_strict_double(val->valuestring, &out)) return tf_batch_set_float64(ob, row, col, out);
|
|
102
|
+
return tf_batch_set_null(ob, row, col);
|
|
103
|
+
}
|
|
104
|
+
case TF_TYPE_BOOL: {
|
|
105
|
+
int out = 0;
|
|
106
|
+
if (cJSON_IsBool(val)) return tf_batch_set_bool(ob, row, col, cJSON_IsTrue(val));
|
|
107
|
+
if (cJSON_IsNumber(val)) return tf_batch_set_bool(ob, row, col, val->valuedouble != 0.0);
|
|
108
|
+
if (cJSON_IsString(val) && parse_bool_string(val->valuestring, &out)) return tf_batch_set_bool(ob, row, col, out != 0);
|
|
109
|
+
return tf_batch_set_null(ob, row, col);
|
|
110
|
+
}
|
|
111
|
+
default:
|
|
112
|
+
return tf_batch_set_null(ob, row, col);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static int json_extract_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
117
|
+
tf_side_channels *side) {
|
|
118
|
+
(void)side;
|
|
119
|
+
json_extract_state *st = self->state;
|
|
120
|
+
*out = NULL;
|
|
121
|
+
|
|
122
|
+
tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows > 0 ? in->n_rows : 1);
|
|
123
|
+
if (!ob) return TF_ERROR;
|
|
124
|
+
|
|
125
|
+
const char *extra_names[] = {st->result};
|
|
126
|
+
const tf_type extra_types[] = {st->type};
|
|
127
|
+
if (tf_batch_clone_with_extra_cols(ob, in, extra_names, extra_types, 1) != TF_OK) {
|
|
128
|
+
tf_batch_free(ob);
|
|
129
|
+
return TF_ERROR;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
int ci = tf_batch_col_index(in, st->column);
|
|
133
|
+
int can_parse = ci >= 0 && in->col_types[(size_t)ci] == TF_TYPE_STRING;
|
|
134
|
+
|
|
135
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
136
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
137
|
+
tf_batch_free(ob);
|
|
138
|
+
return TF_ERROR;
|
|
139
|
+
}
|
|
140
|
+
if (tf_batch_set_null(ob, r, in->n_cols) != TF_OK) {
|
|
141
|
+
tf_batch_free(ob);
|
|
142
|
+
return TF_ERROR;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (can_parse && !tf_batch_is_null(in, r, (size_t)ci)) {
|
|
146
|
+
const char *json = tf_batch_get_string(in, r, (size_t)ci);
|
|
147
|
+
if (json && json[0] != '\0') {
|
|
148
|
+
cJSON *root = cJSON_Parse(json);
|
|
149
|
+
if (root) {
|
|
150
|
+
const cJSON *val = tf_json_path_resolve(root, st->path);
|
|
151
|
+
int rc = set_extracted_value(ob, r, in->n_cols, st->type, val);
|
|
152
|
+
cJSON_Delete(root);
|
|
153
|
+
if (rc != TF_OK) {
|
|
154
|
+
tf_batch_free(ob);
|
|
155
|
+
return TF_ERROR;
|
|
156
|
+
}
|
|
157
|
+
} else if (cJSON_ParseHadAllocationFailure()) {
|
|
158
|
+
tf_batch_free(ob);
|
|
159
|
+
return TF_ERROR;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (tf_batch_expose_row(ob, r) != TF_OK) {
|
|
164
|
+
tf_batch_free(ob);
|
|
165
|
+
return TF_ERROR;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (ob->n_rows > 0) {
|
|
170
|
+
*out = ob;
|
|
171
|
+
} else {
|
|
172
|
+
tf_batch_free(ob);
|
|
173
|
+
}
|
|
174
|
+
return TF_OK;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
static int json_extract_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
178
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static void json_extract_state_free(json_extract_state *st) {
|
|
182
|
+
if (!st) return;
|
|
183
|
+
free(st->column);
|
|
184
|
+
free(st->path);
|
|
185
|
+
free(st->result);
|
|
186
|
+
free(st);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static void json_extract_destroy(tf_step *self) {
|
|
190
|
+
json_extract_state_free(self ? self->state : NULL);
|
|
191
|
+
free(self);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
tf_step *tf_json_extract_create(const cJSON *args) {
|
|
195
|
+
if (!args) return NULL;
|
|
196
|
+
cJSON *path_j = cJSON_GetObjectItemCaseSensitive(args, "path");
|
|
197
|
+
cJSON *result_j = cJSON_GetObjectItemCaseSensitive(args, "result");
|
|
198
|
+
if (!cJSON_IsString(path_j) || !cJSON_IsString(result_j) || result_j->valuestring[0] == '\0')
|
|
199
|
+
return NULL;
|
|
200
|
+
if (tf_json_path_validate(path_j->valuestring) != TF_OK) return NULL;
|
|
201
|
+
|
|
202
|
+
cJSON *column_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
203
|
+
const char *column = cJSON_IsString(column_j) && column_j->valuestring[0] != '\0'
|
|
204
|
+
? column_j->valuestring : "_line";
|
|
205
|
+
cJSON *type_j = cJSON_GetObjectItemCaseSensitive(args, "type");
|
|
206
|
+
tf_type type = parse_output_type(cJSON_IsString(type_j) ? type_j->valuestring : "string");
|
|
207
|
+
if (type == TF_TYPE_NULL) return NULL;
|
|
208
|
+
|
|
209
|
+
json_extract_state *st = calloc(1, sizeof(json_extract_state));
|
|
210
|
+
if (!st) return NULL;
|
|
211
|
+
st->column = strdup(column);
|
|
212
|
+
st->path = strdup(path_j->valuestring);
|
|
213
|
+
st->result = strdup(result_j->valuestring);
|
|
214
|
+
st->type = type;
|
|
215
|
+
if (!st->column || !st->path || !st->result) {
|
|
216
|
+
free(st->column); free(st->path); free(st->result); free(st);
|
|
217
|
+
return NULL;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
221
|
+
if (!step) { json_extract_state_free(st); return NULL; }
|
|
222
|
+
step->process = json_extract_process;
|
|
223
|
+
step->flush = json_extract_flush;
|
|
224
|
+
step->destroy = json_extract_destroy;
|
|
225
|
+
step->state = st;
|
|
226
|
+
return step;
|
|
227
|
+
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_json_filter.c -- Filter rows by a scalar predicate over per-row JSON text.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"column":"_line", "path":"/user/age", "op":"ge", "value":"30", "type":"float"}
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include "internal.h"
|
|
8
|
+
#include "cJSON.h"
|
|
9
|
+
#include <ctype.h>
|
|
10
|
+
#include <errno.h>
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
|
|
14
|
+
typedef enum {
|
|
15
|
+
JSON_FILTER_EXISTS,
|
|
16
|
+
JSON_FILTER_MISSING,
|
|
17
|
+
JSON_FILTER_TRUTHY,
|
|
18
|
+
JSON_FILTER_FALSEY,
|
|
19
|
+
JSON_FILTER_EQ,
|
|
20
|
+
JSON_FILTER_NE,
|
|
21
|
+
JSON_FILTER_GT,
|
|
22
|
+
JSON_FILTER_GE,
|
|
23
|
+
JSON_FILTER_LT,
|
|
24
|
+
JSON_FILTER_LE,
|
|
25
|
+
JSON_FILTER_CONTAINS,
|
|
26
|
+
JSON_FILTER_STARTS_WITH,
|
|
27
|
+
JSON_FILTER_ENDS_WITH,
|
|
28
|
+
} json_filter_op;
|
|
29
|
+
|
|
30
|
+
typedef enum {
|
|
31
|
+
JSON_FILTER_TYPE_AUTO,
|
|
32
|
+
JSON_FILTER_TYPE_STRING,
|
|
33
|
+
JSON_FILTER_TYPE_INT,
|
|
34
|
+
JSON_FILTER_TYPE_FLOAT,
|
|
35
|
+
JSON_FILTER_TYPE_BOOL,
|
|
36
|
+
} json_filter_type;
|
|
37
|
+
|
|
38
|
+
typedef struct {
|
|
39
|
+
char *column;
|
|
40
|
+
char *path;
|
|
41
|
+
char *value;
|
|
42
|
+
json_filter_op op;
|
|
43
|
+
json_filter_type type;
|
|
44
|
+
} json_filter_state;
|
|
45
|
+
|
|
46
|
+
static int str_eq_ci(const char *a, const char *b) {
|
|
47
|
+
while (*a && *b) {
|
|
48
|
+
if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) return 0;
|
|
49
|
+
a++; b++;
|
|
50
|
+
}
|
|
51
|
+
return *a == '\0' && *b == '\0';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static int parse_strict_double(const char *s, double *out) {
|
|
55
|
+
if (!s) return 0;
|
|
56
|
+
errno = 0;
|
|
57
|
+
char *end = NULL;
|
|
58
|
+
double v = strtod(s, &end);
|
|
59
|
+
if (errno != 0 || end == s) return 0;
|
|
60
|
+
while (*end && isspace((unsigned char)*end)) end++;
|
|
61
|
+
if (*end != '\0') return 0;
|
|
62
|
+
*out = v;
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static int parse_strict_int64(const char *s, int64_t *out) {
|
|
67
|
+
if (!s) return 0;
|
|
68
|
+
errno = 0;
|
|
69
|
+
char *end = NULL;
|
|
70
|
+
long long v = strtoll(s, &end, 10);
|
|
71
|
+
if (errno != 0 || end == s) return 0;
|
|
72
|
+
while (*end && isspace((unsigned char)*end)) end++;
|
|
73
|
+
if (*end != '\0') return 0;
|
|
74
|
+
*out = (int64_t)v;
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
static int parse_bool_string(const char *s, int *out) {
|
|
79
|
+
if (!s) return 0;
|
|
80
|
+
if (str_eq_ci(s, "true") || strcmp(s, "1") == 0) { *out = 1; return 1; }
|
|
81
|
+
if (str_eq_ci(s, "false") || strcmp(s, "0") == 0) { *out = 0; return 1; }
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static int json_value_to_double(const cJSON *val, double *out) {
|
|
86
|
+
if (!val || cJSON_IsNull(val)) return 0;
|
|
87
|
+
if (cJSON_IsNumber(val)) { *out = val->valuedouble; return 1; }
|
|
88
|
+
if (cJSON_IsBool(val)) { *out = cJSON_IsTrue(val) ? 1.0 : 0.0; return 1; }
|
|
89
|
+
if (cJSON_IsString(val)) return parse_strict_double(val->valuestring, out);
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static int json_value_to_int64(const cJSON *val, int64_t *out) {
|
|
94
|
+
if (!val || cJSON_IsNull(val)) return 0;
|
|
95
|
+
if (cJSON_IsNumber(val)) { *out = (int64_t)val->valuedouble; return 1; }
|
|
96
|
+
if (cJSON_IsBool(val)) { *out = cJSON_IsTrue(val) ? 1 : 0; return 1; }
|
|
97
|
+
if (cJSON_IsString(val)) return parse_strict_int64(val->valuestring, out);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static int json_value_to_bool(const cJSON *val, int *out) {
|
|
102
|
+
if (!val || cJSON_IsNull(val)) return 0;
|
|
103
|
+
if (cJSON_IsBool(val)) { *out = cJSON_IsTrue(val) ? 1 : 0; return 1; }
|
|
104
|
+
if (cJSON_IsNumber(val)) { *out = val->valuedouble != 0.0; return 1; }
|
|
105
|
+
if (cJSON_IsString(val)) return parse_bool_string(val->valuestring, out);
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static const char *json_value_to_string(const cJSON *val, char **owned) {
|
|
110
|
+
*owned = NULL;
|
|
111
|
+
if (!val || cJSON_IsNull(val)) return NULL;
|
|
112
|
+
if (cJSON_IsString(val)) return val->valuestring ? val->valuestring : "";
|
|
113
|
+
*owned = cJSON_PrintUnformatted((cJSON *)val);
|
|
114
|
+
return *owned;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static int value_truthy(const cJSON *val) {
|
|
118
|
+
if (!val || cJSON_IsNull(val) || cJSON_IsFalse(val)) return 0;
|
|
119
|
+
if (cJSON_IsTrue(val)) return 1;
|
|
120
|
+
if (cJSON_IsNumber(val)) return val->valuedouble != 0.0;
|
|
121
|
+
if (cJSON_IsString(val)) return val->valuestring && val->valuestring[0] != '\0';
|
|
122
|
+
if (cJSON_IsArray(val) || cJSON_IsObject(val)) return cJSON_GetArraySize((cJSON *)val) > 0;
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
static int string_contains(const char *haystack, const char *needle) {
|
|
127
|
+
return haystack && needle && strstr(haystack, needle) != NULL;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static int string_starts_with(const char *s, const char *prefix) {
|
|
131
|
+
if (!s || !prefix) return 0;
|
|
132
|
+
size_t n = strlen(prefix);
|
|
133
|
+
return strncmp(s, prefix, n) == 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static int string_ends_with(const char *s, const char *suffix) {
|
|
137
|
+
if (!s || !suffix) return 0;
|
|
138
|
+
size_t sl = strlen(s), nl = strlen(suffix);
|
|
139
|
+
return sl >= nl && strcmp(s + sl - nl, suffix) == 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static int compare_strings(const char *a, const char *b, json_filter_op op) {
|
|
143
|
+
int cmp = strcmp(a ? a : "", b ? b : "");
|
|
144
|
+
switch (op) {
|
|
145
|
+
case JSON_FILTER_EQ: return cmp == 0;
|
|
146
|
+
case JSON_FILTER_NE: return cmp != 0;
|
|
147
|
+
case JSON_FILTER_CONTAINS: return string_contains(a, b);
|
|
148
|
+
case JSON_FILTER_STARTS_WITH: return string_starts_with(a, b);
|
|
149
|
+
case JSON_FILTER_ENDS_WITH: return string_ends_with(a, b);
|
|
150
|
+
default: return 0;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static int compare_numeric(double a, double b, json_filter_op op) {
|
|
155
|
+
switch (op) {
|
|
156
|
+
case JSON_FILTER_EQ: return a == b;
|
|
157
|
+
case JSON_FILTER_NE: return a != b;
|
|
158
|
+
case JSON_FILTER_GT: return a > b;
|
|
159
|
+
case JSON_FILTER_GE: return a >= b;
|
|
160
|
+
case JSON_FILTER_LT: return a < b;
|
|
161
|
+
case JSON_FILTER_LE: return a <= b;
|
|
162
|
+
default: return 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static int compare_bool(int a, int b, json_filter_op op) {
|
|
167
|
+
switch (op) {
|
|
168
|
+
case JSON_FILTER_EQ: return a == b;
|
|
169
|
+
case JSON_FILTER_NE: return a != b;
|
|
170
|
+
default: return 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
static int eval_filter(const json_filter_state *st, const cJSON *val) {
|
|
175
|
+
switch (st->op) {
|
|
176
|
+
case JSON_FILTER_EXISTS: return val && !cJSON_IsNull(val);
|
|
177
|
+
case JSON_FILTER_MISSING: return !val || cJSON_IsNull(val);
|
|
178
|
+
case JSON_FILTER_TRUTHY: return value_truthy(val);
|
|
179
|
+
case JSON_FILTER_FALSEY: return !value_truthy(val);
|
|
180
|
+
default: break;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!st->value) return 0;
|
|
184
|
+
|
|
185
|
+
if (st->type == JSON_FILTER_TYPE_INT) {
|
|
186
|
+
int64_t a = 0, b = 0;
|
|
187
|
+
if (!json_value_to_int64(val, &a) || !parse_strict_int64(st->value, &b)) return 0;
|
|
188
|
+
return compare_numeric((double)a, (double)b, st->op);
|
|
189
|
+
}
|
|
190
|
+
if (st->type == JSON_FILTER_TYPE_FLOAT) {
|
|
191
|
+
double a = 0.0, b = 0.0;
|
|
192
|
+
if (!json_value_to_double(val, &a) || !parse_strict_double(st->value, &b)) return 0;
|
|
193
|
+
return compare_numeric(a, b, st->op);
|
|
194
|
+
}
|
|
195
|
+
if (st->type == JSON_FILTER_TYPE_BOOL) {
|
|
196
|
+
int a = 0, b = 0;
|
|
197
|
+
if (!json_value_to_bool(val, &a) || !parse_bool_string(st->value, &b)) return 0;
|
|
198
|
+
return compare_bool(a, b, st->op);
|
|
199
|
+
}
|
|
200
|
+
if (st->type == JSON_FILTER_TYPE_STRING) {
|
|
201
|
+
char *owned = NULL;
|
|
202
|
+
const char *a = json_value_to_string(val, &owned);
|
|
203
|
+
int ok = a ? compare_strings(a, st->value, st->op) : 0;
|
|
204
|
+
free(owned);
|
|
205
|
+
return ok;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (st->op == JSON_FILTER_GT || st->op == JSON_FILTER_GE ||
|
|
209
|
+
st->op == JSON_FILTER_LT || st->op == JSON_FILTER_LE) {
|
|
210
|
+
double a = 0.0, b = 0.0;
|
|
211
|
+
if (!json_value_to_double(val, &a) || !parse_strict_double(st->value, &b)) return 0;
|
|
212
|
+
return compare_numeric(a, b, st->op);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (cJSON_IsBool(val)) {
|
|
216
|
+
int a = 0, b = 0;
|
|
217
|
+
if (json_value_to_bool(val, &a) && parse_bool_string(st->value, &b))
|
|
218
|
+
return compare_bool(a, b, st->op);
|
|
219
|
+
}
|
|
220
|
+
if (cJSON_IsNumber(val)) {
|
|
221
|
+
double b = 0.0;
|
|
222
|
+
if (parse_strict_double(st->value, &b)) return compare_numeric(val->valuedouble, b, st->op);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
char *owned = NULL;
|
|
226
|
+
const char *a = json_value_to_string(val, &owned);
|
|
227
|
+
int ok = a ? compare_strings(a, st->value, st->op) : 0;
|
|
228
|
+
free(owned);
|
|
229
|
+
return ok;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
static int json_filter_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
233
|
+
tf_side_channels *side) {
|
|
234
|
+
(void)side;
|
|
235
|
+
json_filter_state *st = self->state;
|
|
236
|
+
*out = NULL;
|
|
237
|
+
|
|
238
|
+
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows > 0 ? in->n_rows : 1);
|
|
239
|
+
if (!ob) return TF_ERROR;
|
|
240
|
+
if (tf_batch_clone_schema(ob, in) != TF_OK) {
|
|
241
|
+
tf_batch_free(ob);
|
|
242
|
+
return TF_ERROR;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
int ci = tf_batch_col_index(in, st->column);
|
|
246
|
+
int can_parse = ci >= 0 && in->col_types[(size_t)ci] == TF_TYPE_STRING;
|
|
247
|
+
size_t out_row = 0;
|
|
248
|
+
|
|
249
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
250
|
+
const cJSON *val = NULL;
|
|
251
|
+
cJSON *root = NULL;
|
|
252
|
+
if (can_parse && !tf_batch_is_null(in, r, (size_t)ci)) {
|
|
253
|
+
const char *json = tf_batch_get_string(in, r, (size_t)ci);
|
|
254
|
+
if (json && json[0] != '\0') {
|
|
255
|
+
root = cJSON_Parse(json);
|
|
256
|
+
if (root) val = tf_json_path_resolve(root, st->path);
|
|
257
|
+
else if (cJSON_ParseHadAllocationFailure()) {
|
|
258
|
+
tf_batch_free(ob);
|
|
259
|
+
return TF_ERROR;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
int keep = eval_filter(st, val);
|
|
264
|
+
if (root) cJSON_Delete(root);
|
|
265
|
+
if (!keep) continue;
|
|
266
|
+
if (tf_batch_copy_row(ob, out_row, in, r) != TF_OK) {
|
|
267
|
+
tf_batch_free(ob);
|
|
268
|
+
return TF_ERROR;
|
|
269
|
+
}
|
|
270
|
+
if (tf_batch_expose_row(ob, out_row) != TF_OK) {
|
|
271
|
+
tf_batch_free(ob);
|
|
272
|
+
return TF_ERROR;
|
|
273
|
+
}
|
|
274
|
+
out_row++;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (ob->n_rows > 0) {
|
|
278
|
+
*out = ob;
|
|
279
|
+
} else {
|
|
280
|
+
tf_batch_free(ob);
|
|
281
|
+
}
|
|
282
|
+
return TF_OK;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
static int json_filter_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
286
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
static void json_filter_state_free(json_filter_state *st) {
|
|
290
|
+
if (!st) return;
|
|
291
|
+
free(st->column);
|
|
292
|
+
free(st->path);
|
|
293
|
+
free(st->value);
|
|
294
|
+
free(st);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
static void json_filter_destroy(tf_step *self) {
|
|
298
|
+
json_filter_state_free(self ? self->state : NULL);
|
|
299
|
+
free(self);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
static int parse_filter_op(const char *s, json_filter_op *out) {
|
|
303
|
+
if (!s || strcmp(s, "exists") == 0 || strcmp(s, "present") == 0) { *out = JSON_FILTER_EXISTS; return 1; }
|
|
304
|
+
if (strcmp(s, "missing") == 0 || strcmp(s, "absent") == 0) { *out = JSON_FILTER_MISSING; return 1; }
|
|
305
|
+
if (strcmp(s, "truthy") == 0 || strcmp(s, "true") == 0) { *out = JSON_FILTER_TRUTHY; return 1; }
|
|
306
|
+
if (strcmp(s, "falsey") == 0 || strcmp(s, "falsy") == 0 || strcmp(s, "false") == 0) { *out = JSON_FILTER_FALSEY; return 1; }
|
|
307
|
+
if (strcmp(s, "eq") == 0 || strcmp(s, "==") == 0 || strcmp(s, "=") == 0) { *out = JSON_FILTER_EQ; return 1; }
|
|
308
|
+
if (strcmp(s, "ne") == 0 || strcmp(s, "!=") == 0) { *out = JSON_FILTER_NE; return 1; }
|
|
309
|
+
if (strcmp(s, "gt") == 0 || strcmp(s, ">") == 0) { *out = JSON_FILTER_GT; return 1; }
|
|
310
|
+
if (strcmp(s, "ge") == 0 || strcmp(s, ">=") == 0) { *out = JSON_FILTER_GE; return 1; }
|
|
311
|
+
if (strcmp(s, "lt") == 0 || strcmp(s, "<") == 0) { *out = JSON_FILTER_LT; return 1; }
|
|
312
|
+
if (strcmp(s, "le") == 0 || strcmp(s, "<=") == 0) { *out = JSON_FILTER_LE; return 1; }
|
|
313
|
+
if (strcmp(s, "contains") == 0) { *out = JSON_FILTER_CONTAINS; return 1; }
|
|
314
|
+
if (strcmp(s, "starts-with") == 0 || strcmp(s, "starts_with") == 0) { *out = JSON_FILTER_STARTS_WITH; return 1; }
|
|
315
|
+
if (strcmp(s, "ends-with") == 0 || strcmp(s, "ends_with") == 0) { *out = JSON_FILTER_ENDS_WITH; return 1; }
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
static int op_requires_value(json_filter_op op) {
|
|
320
|
+
return !(op == JSON_FILTER_EXISTS || op == JSON_FILTER_MISSING ||
|
|
321
|
+
op == JSON_FILTER_TRUTHY || op == JSON_FILTER_FALSEY);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
static int parse_filter_type(const char *s, json_filter_type *out) {
|
|
325
|
+
if (!s || strcmp(s, "auto") == 0) { *out = JSON_FILTER_TYPE_AUTO; return 1; }
|
|
326
|
+
if (strcmp(s, "string") == 0 || strcmp(s, "str") == 0 || strcmp(s, "json") == 0) { *out = JSON_FILTER_TYPE_STRING; return 1; }
|
|
327
|
+
if (strcmp(s, "int") == 0 || strcmp(s, "int64") == 0) { *out = JSON_FILTER_TYPE_INT; return 1; }
|
|
328
|
+
if (strcmp(s, "float") == 0 || strcmp(s, "float64") == 0 || strcmp(s, "number") == 0) { *out = JSON_FILTER_TYPE_FLOAT; return 1; }
|
|
329
|
+
if (strcmp(s, "bool") == 0 || strcmp(s, "boolean") == 0) { *out = JSON_FILTER_TYPE_BOOL; return 1; }
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
static char *value_arg_to_string(const cJSON *value_j) {
|
|
334
|
+
if (!value_j) return NULL;
|
|
335
|
+
if (cJSON_IsString(value_j)) return strdup(value_j->valuestring ? value_j->valuestring : "");
|
|
336
|
+
char *printed = cJSON_PrintUnformatted((cJSON *)value_j);
|
|
337
|
+
if (!printed) return NULL;
|
|
338
|
+
char *out = strdup(printed);
|
|
339
|
+
free(printed);
|
|
340
|
+
return out;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
tf_step *tf_json_filter_create(const cJSON *args) {
|
|
344
|
+
if (!args) return NULL;
|
|
345
|
+
cJSON *path_j = cJSON_GetObjectItemCaseSensitive(args, "path");
|
|
346
|
+
if (!cJSON_IsString(path_j)) return NULL;
|
|
347
|
+
if (tf_json_path_validate(path_j->valuestring) != TF_OK) return NULL;
|
|
348
|
+
|
|
349
|
+
cJSON *column_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
350
|
+
const char *column = cJSON_IsString(column_j) && column_j->valuestring[0] != '\0'
|
|
351
|
+
? column_j->valuestring : "_line";
|
|
352
|
+
|
|
353
|
+
json_filter_op op = JSON_FILTER_EXISTS;
|
|
354
|
+
cJSON *op_j = cJSON_GetObjectItemCaseSensitive(args, "op");
|
|
355
|
+
if (op_j && (!cJSON_IsString(op_j) || !parse_filter_op(op_j->valuestring, &op))) return NULL;
|
|
356
|
+
|
|
357
|
+
json_filter_type type = JSON_FILTER_TYPE_AUTO;
|
|
358
|
+
cJSON *type_j = cJSON_GetObjectItemCaseSensitive(args, "type");
|
|
359
|
+
if (type_j && (!cJSON_IsString(type_j) || !parse_filter_type(type_j->valuestring, &type))) return NULL;
|
|
360
|
+
|
|
361
|
+
cJSON *value_j = cJSON_GetObjectItemCaseSensitive(args, "value");
|
|
362
|
+
char *value = value_arg_to_string(value_j);
|
|
363
|
+
if (op_requires_value(op) && !value) return NULL;
|
|
364
|
+
|
|
365
|
+
json_filter_state *st = calloc(1, sizeof(json_filter_state));
|
|
366
|
+
if (!st) { free(value); return NULL; }
|
|
367
|
+
st->column = strdup(column);
|
|
368
|
+
st->path = strdup(path_j->valuestring);
|
|
369
|
+
st->value = value;
|
|
370
|
+
st->op = op;
|
|
371
|
+
st->type = type;
|
|
372
|
+
if (!st->column || !st->path) {
|
|
373
|
+
json_filter_state_free(st);
|
|
374
|
+
return NULL;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
378
|
+
if (!step) { json_filter_state_free(st); return NULL; }
|
|
379
|
+
step->process = json_filter_process;
|
|
380
|
+
step->flush = json_filter_flush;
|
|
381
|
+
step->destroy = json_filter_destroy;
|
|
382
|
+
step->state = st;
|
|
383
|
+
return step;
|
|
384
|
+
}
|