tranfi 0.0.1 → 0.1.2
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/README.md +395 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-pDFMluyz.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 +69 -0
- package/csrc/arena.c +91 -0
- package/csrc/batch.c +229 -0
- package/csrc/buffer.c +78 -0
- package/csrc/cJSON.c +3143 -0
- package/csrc/cJSON.h +300 -0
- package/csrc/codec_csv.c +1058 -0
- package/csrc/codec_jsonl.c +374 -0
- package/csrc/codec_table.c +218 -0
- package/csrc/codec_text.c +229 -0
- package/csrc/compiler.c +102 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +1180 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1245 -0
- package/csrc/expr.h +56 -0
- package/csrc/internal.h +250 -0
- package/csrc/ir.c +119 -0
- package/csrc/ir.h +167 -0
- package/csrc/ir_schema.c +60 -0
- package/csrc/ir_serialize.c +104 -0
- package/csrc/ir_sql.c +1211 -0
- package/csrc/ir_validate.c +120 -0
- package/csrc/main.c +392 -0
- package/csrc/op_acf.c +133 -0
- package/csrc/op_anomaly.c +120 -0
- package/csrc/op_bin.c +109 -0
- package/csrc/op_cast.c +195 -0
- package/csrc/op_clip.c +88 -0
- package/csrc/op_date_trunc.c +181 -0
- package/csrc/op_datetime.c +212 -0
- package/csrc/op_derive.c +248 -0
- package/csrc/op_diff.c +134 -0
- package/csrc/op_ewma.c +103 -0
- package/csrc/op_explode.c +108 -0
- package/csrc/op_fill_down.c +163 -0
- package/csrc/op_fill_null.c +123 -0
- package/csrc/op_filter.c +132 -0
- package/csrc/op_frequency.c +193 -0
- package/csrc/op_grep.c +163 -0
- package/csrc/op_group_agg.c +285 -0
- package/csrc/op_hash.c +126 -0
- package/csrc/op_head.c +149 -0
- package/csrc/op_interpolate.c +239 -0
- package/csrc/op_join.c +384 -0
- package/csrc/op_label_encode.c +144 -0
- package/csrc/op_lead.c +190 -0
- package/csrc/op_normalize.c +226 -0
- package/csrc/op_onehot.c +185 -0
- package/csrc/op_pivot.c +370 -0
- package/csrc/op_registry.c +1148 -0
- package/csrc/op_rename.c +138 -0
- package/csrc/op_replace.c +202 -0
- package/csrc/op_sample.c +101 -0
- package/csrc/op_select.c +140 -0
- package/csrc/op_skip.c +152 -0
- package/csrc/op_sort.c +273 -0
- package/csrc/op_split.c +114 -0
- package/csrc/op_split_data.c +87 -0
- package/csrc/op_stack.c +315 -0
- package/csrc/op_stats.c +779 -0
- package/csrc/op_step.c +171 -0
- package/csrc/op_tail.c +96 -0
- package/csrc/op_top.c +150 -0
- package/csrc/op_trim.c +109 -0
- package/csrc/op_unique.c +300 -0
- package/csrc/op_unpivot.c +159 -0
- package/csrc/op_validate.c +71 -0
- package/csrc/op_window.c +150 -0
- package/csrc/pipeline.c +315 -0
- package/csrc/plan.c +206 -0
- package/csrc/recipes.c +102 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +463 -0
- package/csrc/report.h +22 -0
- package/csrc/tranfi.h +123 -0
- package/csrc/wasm_api.c +157 -0
- package/napi_api.c +326 -0
- package/package.json +46 -57
- package/src/cli.js +193 -0
- package/src/engines/duckdb.js +109 -0
- package/src/index.js +306 -0
- package/src/native.js +22 -0
- package/src/pipeline.js +286 -0
- package/src/server.js +277 -0
- package/src/wasm.js +19 -0
- package/wasm/index.js +244 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/LICENSE +0 -21
- package/dist/bundle.js +0 -1
- package/index.html +0 -18
- package/logo.png +0 -0
- package/src/app.css +0 -160
- package/src/app.js +0 -203
- package/src/app.vue +0 -253
- package/src/bulma-input.vue +0 -110
- package/src/common-inputs.js +0 -28
- package/src/main.js +0 -18
- package/src/transforms.js +0 -166
- package/webpack.config.js +0 -108
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_frequency.c — Value counts. Hash map → emit sorted by count desc.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"columns": ["city"]}
|
|
5
|
+
* or {} for frequency of all columns concatenated.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "internal.h"
|
|
9
|
+
#include "cJSON.h"
|
|
10
|
+
#include "date_utils.h"
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
#include <stdio.h>
|
|
14
|
+
|
|
15
|
+
typedef struct {
|
|
16
|
+
char **keys;
|
|
17
|
+
size_t *counts;
|
|
18
|
+
size_t count;
|
|
19
|
+
size_t cap;
|
|
20
|
+
} freq_map;
|
|
21
|
+
|
|
22
|
+
typedef struct {
|
|
23
|
+
char **cols;
|
|
24
|
+
size_t n_cols;
|
|
25
|
+
freq_map map;
|
|
26
|
+
} frequency_state;
|
|
27
|
+
|
|
28
|
+
static char *build_freq_key(const tf_batch *b, size_t row,
|
|
29
|
+
int *col_indices, size_t n_keys) {
|
|
30
|
+
size_t buf_cap = 256;
|
|
31
|
+
char *buf = malloc(buf_cap);
|
|
32
|
+
if (!buf) return NULL;
|
|
33
|
+
size_t buf_len = 0;
|
|
34
|
+
for (size_t k = 0; k < n_keys; k++) {
|
|
35
|
+
int c = col_indices[k];
|
|
36
|
+
if (k > 0) { if (buf_len + 1 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); } buf[buf_len++] = '\x01'; }
|
|
37
|
+
char val_buf[64];
|
|
38
|
+
const char *val;
|
|
39
|
+
size_t val_len;
|
|
40
|
+
if (c < 0 || tf_batch_is_null(b, row, c)) {
|
|
41
|
+
val = "\\N"; val_len = 2;
|
|
42
|
+
} else {
|
|
43
|
+
switch (b->col_types[c]) {
|
|
44
|
+
case TF_TYPE_STRING: val = tf_batch_get_string(b, row, c); val_len = strlen(val); break;
|
|
45
|
+
case TF_TYPE_INT64:
|
|
46
|
+
val_len = snprintf(val_buf, sizeof(val_buf), "%lld", (long long)tf_batch_get_int64(b, row, c));
|
|
47
|
+
val = val_buf; break;
|
|
48
|
+
case TF_TYPE_FLOAT64:
|
|
49
|
+
val_len = snprintf(val_buf, sizeof(val_buf), "%g", tf_batch_get_float64(b, row, c));
|
|
50
|
+
val = val_buf; break;
|
|
51
|
+
case TF_TYPE_BOOL:
|
|
52
|
+
val = tf_batch_get_bool(b, row, c) ? "T" : "F"; val_len = 1; break;
|
|
53
|
+
case TF_TYPE_DATE:
|
|
54
|
+
val_len = snprintf(val_buf, sizeof(val_buf), "%d", (int)tf_batch_get_date(b, row, c));
|
|
55
|
+
val = val_buf; break;
|
|
56
|
+
case TF_TYPE_TIMESTAMP:
|
|
57
|
+
val_len = snprintf(val_buf, sizeof(val_buf), "%lld", (long long)tf_batch_get_timestamp(b, row, c));
|
|
58
|
+
val = val_buf; break;
|
|
59
|
+
default: val = "\\N"; val_len = 2; break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
while (buf_len + val_len + 2 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); }
|
|
63
|
+
memcpy(buf + buf_len, val, val_len);
|
|
64
|
+
buf_len += val_len;
|
|
65
|
+
}
|
|
66
|
+
buf[buf_len] = '\0';
|
|
67
|
+
return buf;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static int freq_add(freq_map *map, const char *key) {
|
|
71
|
+
for (size_t i = 0; i < map->count; i++) {
|
|
72
|
+
if (strcmp(map->keys[i], key) == 0) { map->counts[i]++; return 0; }
|
|
73
|
+
}
|
|
74
|
+
if (map->count >= map->cap) {
|
|
75
|
+
size_t new_cap = map->cap ? map->cap * 2 : 64;
|
|
76
|
+
map->keys = realloc(map->keys, new_cap * sizeof(char *));
|
|
77
|
+
map->counts = realloc(map->counts, new_cap * sizeof(size_t));
|
|
78
|
+
map->cap = new_cap;
|
|
79
|
+
}
|
|
80
|
+
map->keys[map->count] = strdup(key);
|
|
81
|
+
map->counts[map->count] = 1;
|
|
82
|
+
map->count++;
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static int frequency_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
87
|
+
tf_side_channels *side) {
|
|
88
|
+
(void)side;
|
|
89
|
+
frequency_state *st = self->state;
|
|
90
|
+
*out = NULL;
|
|
91
|
+
|
|
92
|
+
size_t n_keys;
|
|
93
|
+
int *col_indices;
|
|
94
|
+
if (st->n_cols > 0) {
|
|
95
|
+
n_keys = st->n_cols;
|
|
96
|
+
col_indices = malloc(n_keys * sizeof(int));
|
|
97
|
+
for (size_t k = 0; k < n_keys; k++)
|
|
98
|
+
col_indices[k] = tf_batch_col_index(in, st->cols[k]);
|
|
99
|
+
} else {
|
|
100
|
+
n_keys = in->n_cols;
|
|
101
|
+
col_indices = malloc(n_keys * sizeof(int));
|
|
102
|
+
for (size_t k = 0; k < n_keys; k++) col_indices[k] = (int)k;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
106
|
+
char *key = build_freq_key(in, r, col_indices, n_keys);
|
|
107
|
+
if (!key) { free(col_indices); return TF_ERROR; }
|
|
108
|
+
freq_add(&st->map, key);
|
|
109
|
+
free(key);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
free(col_indices);
|
|
113
|
+
return TF_OK;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static int frequency_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
117
|
+
(void)side;
|
|
118
|
+
frequency_state *st = self->state;
|
|
119
|
+
*out = NULL;
|
|
120
|
+
|
|
121
|
+
if (st->map.count == 0) return TF_OK;
|
|
122
|
+
|
|
123
|
+
/* Sort by count descending */
|
|
124
|
+
size_t n = st->map.count;
|
|
125
|
+
size_t *indices = malloc(n * sizeof(size_t));
|
|
126
|
+
if (!indices) return TF_ERROR;
|
|
127
|
+
for (size_t i = 0; i < n; i++) indices[i] = i;
|
|
128
|
+
|
|
129
|
+
/* Simple sort by count */
|
|
130
|
+
for (size_t i = 0; i < n - 1; i++) {
|
|
131
|
+
for (size_t j = i + 1; j < n; j++) {
|
|
132
|
+
if (st->map.counts[indices[j]] > st->map.counts[indices[i]]) {
|
|
133
|
+
size_t tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
tf_batch *ob = tf_batch_create(2, n);
|
|
139
|
+
if (!ob) { free(indices); return TF_ERROR; }
|
|
140
|
+
tf_batch_set_schema(ob, 0, "value", TF_TYPE_STRING);
|
|
141
|
+
tf_batch_set_schema(ob, 1, "count", TF_TYPE_INT64);
|
|
142
|
+
|
|
143
|
+
for (size_t i = 0; i < n; i++) {
|
|
144
|
+
tf_batch_ensure_capacity(ob, i + 1);
|
|
145
|
+
tf_batch_set_string(ob, i, 0, st->map.keys[indices[i]]);
|
|
146
|
+
tf_batch_set_int64(ob, i, 1, (int64_t)st->map.counts[indices[i]]);
|
|
147
|
+
ob->n_rows = i + 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
free(indices);
|
|
151
|
+
*out = ob;
|
|
152
|
+
return TF_OK;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static void frequency_destroy(tf_step *self) {
|
|
156
|
+
frequency_state *st = self->state;
|
|
157
|
+
if (st) {
|
|
158
|
+
for (size_t i = 0; i < st->n_cols; i++) free(st->cols[i]);
|
|
159
|
+
free(st->cols);
|
|
160
|
+
for (size_t i = 0; i < st->map.count; i++) free(st->map.keys[i]);
|
|
161
|
+
free(st->map.keys); free(st->map.counts);
|
|
162
|
+
free(st);
|
|
163
|
+
}
|
|
164
|
+
free(self);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
tf_step *tf_frequency_create(const cJSON *args) {
|
|
168
|
+
frequency_state *st = calloc(1, sizeof(frequency_state));
|
|
169
|
+
if (!st) return NULL;
|
|
170
|
+
|
|
171
|
+
if (args) {
|
|
172
|
+
cJSON *columns = cJSON_GetObjectItemCaseSensitive(args, "columns");
|
|
173
|
+
if (columns && cJSON_IsArray(columns)) {
|
|
174
|
+
int n = cJSON_GetArraySize(columns);
|
|
175
|
+
if (n > 0) {
|
|
176
|
+
st->cols = calloc(n, sizeof(char *));
|
|
177
|
+
st->n_cols = n;
|
|
178
|
+
for (int i = 0; i < n; i++) {
|
|
179
|
+
cJSON *item = cJSON_GetArrayItem(columns, i);
|
|
180
|
+
if (cJSON_IsString(item)) st->cols[i] = strdup(item->valuestring);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
tf_step *step = malloc(sizeof(tf_step));
|
|
187
|
+
if (!step) { frequency_destroy(&(tf_step){.state = st}); return NULL; }
|
|
188
|
+
step->process = frequency_process;
|
|
189
|
+
step->flush = frequency_flush;
|
|
190
|
+
step->destroy = frequency_destroy;
|
|
191
|
+
step->state = st;
|
|
192
|
+
return step;
|
|
193
|
+
}
|
package/csrc/op_grep.c
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_grep.c — Substring/regex filter.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"pattern": "error", "invert": false, "column": "_line", "regex": false}
|
|
5
|
+
* For each row, checks if column value matches pattern.
|
|
6
|
+
* Uses strstr for substring mode, regexec for regex mode.
|
|
7
|
+
* Keeps or discards based on invert flag.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "internal.h"
|
|
11
|
+
#include "cJSON.h"
|
|
12
|
+
#include <stdlib.h>
|
|
13
|
+
#include <string.h>
|
|
14
|
+
#include <stdio.h>
|
|
15
|
+
#include <regex.h>
|
|
16
|
+
|
|
17
|
+
typedef struct {
|
|
18
|
+
char *pattern;
|
|
19
|
+
char *column;
|
|
20
|
+
int invert;
|
|
21
|
+
int use_regex;
|
|
22
|
+
regex_t compiled;
|
|
23
|
+
size_t rows_in;
|
|
24
|
+
size_t rows_out;
|
|
25
|
+
} grep_state;
|
|
26
|
+
|
|
27
|
+
static int grep_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
28
|
+
tf_side_channels *side) {
|
|
29
|
+
grep_state *st = self->state;
|
|
30
|
+
*out = NULL;
|
|
31
|
+
|
|
32
|
+
/* Find target column */
|
|
33
|
+
int col_idx = tf_batch_col_index(in, st->column);
|
|
34
|
+
if (col_idx < 0) {
|
|
35
|
+
/* Column not found — pass through or drop all depending on invert */
|
|
36
|
+
if (st->invert) {
|
|
37
|
+
*out = in;
|
|
38
|
+
/* Don't free — caller owns it, but we need to copy */
|
|
39
|
+
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
40
|
+
if (!ob) return TF_ERROR;
|
|
41
|
+
for (size_t c = 0; c < in->n_cols; c++)
|
|
42
|
+
tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
|
|
43
|
+
for (size_t r = 0; r < in->n_rows; r++)
|
|
44
|
+
tf_batch_copy_row(ob, r, in, r);
|
|
45
|
+
ob->n_rows = in->n_rows;
|
|
46
|
+
*out = ob;
|
|
47
|
+
}
|
|
48
|
+
return TF_OK;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Create output batch with same schema */
|
|
52
|
+
tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
|
|
53
|
+
if (!ob) return TF_ERROR;
|
|
54
|
+
for (size_t c = 0; c < in->n_cols; c++)
|
|
55
|
+
tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
|
|
56
|
+
|
|
57
|
+
size_t out_row = 0;
|
|
58
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
59
|
+
int match = 0;
|
|
60
|
+
|
|
61
|
+
if (tf_batch_is_null(in, r, (size_t)col_idx)) {
|
|
62
|
+
match = 0;
|
|
63
|
+
} else if (in->col_types[(size_t)col_idx] == TF_TYPE_STRING) {
|
|
64
|
+
const char *val = tf_batch_get_string(in, r, (size_t)col_idx);
|
|
65
|
+
if (st->use_regex) {
|
|
66
|
+
match = (regexec(&st->compiled, val, 0, NULL, 0) == 0) ? 1 : 0;
|
|
67
|
+
} else {
|
|
68
|
+
match = (strstr(val, st->pattern) != NULL) ? 1 : 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
int keep = st->invert ? !match : match;
|
|
73
|
+
if (!keep) continue;
|
|
74
|
+
|
|
75
|
+
if (tf_batch_ensure_capacity(ob, out_row + 1) != TF_OK) {
|
|
76
|
+
tf_batch_free(ob);
|
|
77
|
+
return TF_ERROR;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
tf_batch_copy_row(ob, out_row, in, r);
|
|
81
|
+
out_row++;
|
|
82
|
+
}
|
|
83
|
+
ob->n_rows = out_row;
|
|
84
|
+
|
|
85
|
+
st->rows_in += in->n_rows;
|
|
86
|
+
st->rows_out += out_row;
|
|
87
|
+
|
|
88
|
+
if (side && side->stats) {
|
|
89
|
+
char stats_buf[128];
|
|
90
|
+
snprintf(stats_buf, sizeof(stats_buf),
|
|
91
|
+
"{\"op\":\"grep\",\"rows_in\":%zu,\"rows_out\":%zu}\n",
|
|
92
|
+
in->n_rows, out_row);
|
|
93
|
+
tf_buffer_write_str(side->stats, stats_buf);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (out_row > 0) {
|
|
97
|
+
*out = ob;
|
|
98
|
+
} else {
|
|
99
|
+
tf_batch_free(ob);
|
|
100
|
+
}
|
|
101
|
+
return TF_OK;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static int grep_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
105
|
+
(void)self; (void)side;
|
|
106
|
+
*out = NULL;
|
|
107
|
+
return TF_OK;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
static void grep_destroy(tf_step *self) {
|
|
111
|
+
grep_state *st = self->state;
|
|
112
|
+
if (st) {
|
|
113
|
+
if (st->use_regex) regfree(&st->compiled);
|
|
114
|
+
free(st->pattern);
|
|
115
|
+
free(st->column);
|
|
116
|
+
free(st);
|
|
117
|
+
}
|
|
118
|
+
free(self);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
tf_step *tf_grep_create(const cJSON *args) {
|
|
122
|
+
if (!args) return NULL;
|
|
123
|
+
|
|
124
|
+
cJSON *pattern_json = cJSON_GetObjectItemCaseSensitive(args, "pattern");
|
|
125
|
+
if (!cJSON_IsString(pattern_json)) return NULL;
|
|
126
|
+
|
|
127
|
+
grep_state *st = calloc(1, sizeof(grep_state));
|
|
128
|
+
if (!st) return NULL;
|
|
129
|
+
|
|
130
|
+
st->pattern = strdup(pattern_json->valuestring);
|
|
131
|
+
if (!st->pattern) { free(st); return NULL; }
|
|
132
|
+
|
|
133
|
+
cJSON *column_json = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
134
|
+
if (cJSON_IsString(column_json))
|
|
135
|
+
st->column = strdup(column_json->valuestring);
|
|
136
|
+
else
|
|
137
|
+
st->column = strdup("_line");
|
|
138
|
+
if (!st->column) { free(st->pattern); free(st); return NULL; }
|
|
139
|
+
|
|
140
|
+
cJSON *invert_json = cJSON_GetObjectItemCaseSensitive(args, "invert");
|
|
141
|
+
st->invert = (cJSON_IsBool(invert_json) && cJSON_IsTrue(invert_json)) ? 1 : 0;
|
|
142
|
+
|
|
143
|
+
cJSON *regex_json = cJSON_GetObjectItemCaseSensitive(args, "regex");
|
|
144
|
+
st->use_regex = (cJSON_IsBool(regex_json) && cJSON_IsTrue(regex_json)) ? 1 : 0;
|
|
145
|
+
|
|
146
|
+
if (st->use_regex) {
|
|
147
|
+
int rc = regcomp(&st->compiled, st->pattern, REG_EXTENDED | REG_NOSUB);
|
|
148
|
+
if (rc != 0) {
|
|
149
|
+
free(st->pattern);
|
|
150
|
+
free(st->column);
|
|
151
|
+
free(st);
|
|
152
|
+
return NULL;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
tf_step *step = malloc(sizeof(tf_step));
|
|
157
|
+
if (!step) { free(st->pattern); free(st->column); free(st); return NULL; }
|
|
158
|
+
step->process = grep_process;
|
|
159
|
+
step->flush = grep_flush;
|
|
160
|
+
step->destroy = grep_destroy;
|
|
161
|
+
step->state = st;
|
|
162
|
+
return step;
|
|
163
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_group_agg.c — Group by + aggregate (sum/avg/count/min/max).
|
|
3
|
+
*
|
|
4
|
+
* Config: {"group_by": ["city"], "aggs": [{"column": "sales", "func": "sum", "name": "total"}]}
|
|
5
|
+
* Buffers all rows, emits on flush.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "internal.h"
|
|
9
|
+
#include "cJSON.h"
|
|
10
|
+
#include "date_utils.h"
|
|
11
|
+
#include <stdlib.h>
|
|
12
|
+
#include <string.h>
|
|
13
|
+
#include <stdio.h>
|
|
14
|
+
|
|
15
|
+
typedef enum {
|
|
16
|
+
AGG_SUM, AGG_AVG, AGG_COUNT, AGG_MIN, AGG_MAX,
|
|
17
|
+
} agg_func;
|
|
18
|
+
|
|
19
|
+
typedef struct {
|
|
20
|
+
char *column;
|
|
21
|
+
char *name;
|
|
22
|
+
agg_func func;
|
|
23
|
+
} agg_spec;
|
|
24
|
+
|
|
25
|
+
typedef struct {
|
|
26
|
+
double *sums;
|
|
27
|
+
double *mins;
|
|
28
|
+
double *maxs;
|
|
29
|
+
size_t *counts;
|
|
30
|
+
size_t n_aggs;
|
|
31
|
+
} group_accum;
|
|
32
|
+
|
|
33
|
+
typedef struct {
|
|
34
|
+
char **keys;
|
|
35
|
+
group_accum *accums;
|
|
36
|
+
size_t count;
|
|
37
|
+
size_t cap;
|
|
38
|
+
} group_map;
|
|
39
|
+
|
|
40
|
+
typedef struct {
|
|
41
|
+
char **group_cols;
|
|
42
|
+
size_t n_group_cols;
|
|
43
|
+
agg_spec *aggs;
|
|
44
|
+
size_t n_aggs;
|
|
45
|
+
group_map map;
|
|
46
|
+
} group_agg_state;
|
|
47
|
+
|
|
48
|
+
static agg_func parse_agg_func(const char *s) {
|
|
49
|
+
if (strcmp(s, "sum") == 0) return AGG_SUM;
|
|
50
|
+
if (strcmp(s, "avg") == 0) return AGG_AVG;
|
|
51
|
+
if (strcmp(s, "count") == 0) return AGG_COUNT;
|
|
52
|
+
if (strcmp(s, "min") == 0) return AGG_MIN;
|
|
53
|
+
if (strcmp(s, "max") == 0) return AGG_MAX;
|
|
54
|
+
return AGG_COUNT;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static char *build_group_key(const tf_batch *b, size_t row,
|
|
58
|
+
int *col_indices, size_t n_keys) {
|
|
59
|
+
size_t buf_cap = 256;
|
|
60
|
+
char *buf = malloc(buf_cap);
|
|
61
|
+
if (!buf) return NULL;
|
|
62
|
+
size_t buf_len = 0;
|
|
63
|
+
for (size_t k = 0; k < n_keys; k++) {
|
|
64
|
+
int c = col_indices[k];
|
|
65
|
+
if (k > 0) { buf[buf_len++] = '\x01'; }
|
|
66
|
+
char val_buf[64];
|
|
67
|
+
const char *val = "";
|
|
68
|
+
size_t val_len = 0;
|
|
69
|
+
if (c < 0 || tf_batch_is_null(b, row, c)) {
|
|
70
|
+
val = "\\N"; val_len = 2;
|
|
71
|
+
} else {
|
|
72
|
+
switch (b->col_types[c]) {
|
|
73
|
+
case TF_TYPE_STRING: val = tf_batch_get_string(b, row, c); val_len = strlen(val); break;
|
|
74
|
+
case TF_TYPE_INT64:
|
|
75
|
+
val_len = snprintf(val_buf, sizeof(val_buf), "%lld", (long long)tf_batch_get_int64(b, row, c));
|
|
76
|
+
val = val_buf; break;
|
|
77
|
+
case TF_TYPE_FLOAT64:
|
|
78
|
+
val_len = snprintf(val_buf, sizeof(val_buf), "%.17g", tf_batch_get_float64(b, row, c));
|
|
79
|
+
val = val_buf; break;
|
|
80
|
+
case TF_TYPE_BOOL:
|
|
81
|
+
val = tf_batch_get_bool(b, row, c) ? "T" : "F"; val_len = 1; break;
|
|
82
|
+
case TF_TYPE_DATE:
|
|
83
|
+
val_len = snprintf(val_buf, sizeof(val_buf), "%d", (int)tf_batch_get_date(b, row, c));
|
|
84
|
+
val = val_buf; break;
|
|
85
|
+
case TF_TYPE_TIMESTAMP:
|
|
86
|
+
val_len = snprintf(val_buf, sizeof(val_buf), "%lld", (long long)tf_batch_get_timestamp(b, row, c));
|
|
87
|
+
val = val_buf; break;
|
|
88
|
+
default: val = "\\N"; val_len = 2; break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
while (buf_len + val_len + 2 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); }
|
|
92
|
+
memcpy(buf + buf_len, val, val_len);
|
|
93
|
+
buf_len += val_len;
|
|
94
|
+
}
|
|
95
|
+
buf[buf_len] = '\0';
|
|
96
|
+
return buf;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static int find_or_add_group(group_map *map, const char *key, size_t n_aggs) {
|
|
100
|
+
for (size_t i = 0; i < map->count; i++) {
|
|
101
|
+
if (strcmp(map->keys[i], key) == 0) return (int)i;
|
|
102
|
+
}
|
|
103
|
+
if (map->count >= map->cap) {
|
|
104
|
+
size_t new_cap = map->cap ? map->cap * 2 : 64;
|
|
105
|
+
map->keys = realloc(map->keys, new_cap * sizeof(char *));
|
|
106
|
+
map->accums = realloc(map->accums, new_cap * sizeof(group_accum));
|
|
107
|
+
map->cap = new_cap;
|
|
108
|
+
}
|
|
109
|
+
size_t idx = map->count++;
|
|
110
|
+
map->keys[idx] = strdup(key);
|
|
111
|
+
group_accum *a = &map->accums[idx];
|
|
112
|
+
a->n_aggs = n_aggs;
|
|
113
|
+
a->sums = calloc(n_aggs, sizeof(double));
|
|
114
|
+
a->mins = malloc(n_aggs * sizeof(double));
|
|
115
|
+
a->maxs = malloc(n_aggs * sizeof(double));
|
|
116
|
+
a->counts = calloc(n_aggs, sizeof(size_t));
|
|
117
|
+
for (size_t i = 0; i < n_aggs; i++) { a->mins[i] = 1e308; a->maxs[i] = -1e308; }
|
|
118
|
+
return (int)idx;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static int group_agg_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
122
|
+
tf_side_channels *side) {
|
|
123
|
+
(void)side;
|
|
124
|
+
group_agg_state *st = self->state;
|
|
125
|
+
*out = NULL;
|
|
126
|
+
|
|
127
|
+
int *group_indices = malloc(st->n_group_cols * sizeof(int));
|
|
128
|
+
int *agg_indices = malloc(st->n_aggs * sizeof(int));
|
|
129
|
+
if (!group_indices || !agg_indices) { free(group_indices); free(agg_indices); return TF_ERROR; }
|
|
130
|
+
|
|
131
|
+
for (size_t k = 0; k < st->n_group_cols; k++)
|
|
132
|
+
group_indices[k] = tf_batch_col_index(in, st->group_cols[k]);
|
|
133
|
+
for (size_t k = 0; k < st->n_aggs; k++)
|
|
134
|
+
agg_indices[k] = tf_batch_col_index(in, st->aggs[k].column);
|
|
135
|
+
|
|
136
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
137
|
+
char *key = build_group_key(in, r, group_indices, st->n_group_cols);
|
|
138
|
+
if (!key) { free(group_indices); free(agg_indices); return TF_ERROR; }
|
|
139
|
+
int gi = find_or_add_group(&st->map, key, st->n_aggs);
|
|
140
|
+
free(key);
|
|
141
|
+
if (gi < 0) { free(group_indices); free(agg_indices); return TF_ERROR; }
|
|
142
|
+
|
|
143
|
+
group_accum *a = &st->map.accums[gi];
|
|
144
|
+
for (size_t k = 0; k < st->n_aggs; k++) {
|
|
145
|
+
int ci = agg_indices[k];
|
|
146
|
+
if (st->aggs[k].func == AGG_COUNT) {
|
|
147
|
+
a->counts[k]++;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (ci < 0 || tf_batch_is_null(in, r, ci)) continue;
|
|
151
|
+
double v = 0;
|
|
152
|
+
if (in->col_types[ci] == TF_TYPE_INT64) v = (double)tf_batch_get_int64(in, r, ci);
|
|
153
|
+
else if (in->col_types[ci] == TF_TYPE_FLOAT64) v = tf_batch_get_float64(in, r, ci);
|
|
154
|
+
a->sums[k] += v;
|
|
155
|
+
if (v < a->mins[k]) a->mins[k] = v;
|
|
156
|
+
if (v > a->maxs[k]) a->maxs[k] = v;
|
|
157
|
+
a->counts[k]++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
free(group_indices);
|
|
162
|
+
free(agg_indices);
|
|
163
|
+
return TF_OK;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static int group_agg_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
167
|
+
(void)side;
|
|
168
|
+
group_agg_state *st = self->state;
|
|
169
|
+
*out = NULL;
|
|
170
|
+
|
|
171
|
+
if (st->map.count == 0) return TF_OK;
|
|
172
|
+
|
|
173
|
+
size_t n_out_cols = st->n_group_cols + st->n_aggs;
|
|
174
|
+
tf_batch *ob = tf_batch_create(n_out_cols, st->map.count);
|
|
175
|
+
if (!ob) return TF_ERROR;
|
|
176
|
+
|
|
177
|
+
for (size_t k = 0; k < st->n_group_cols; k++)
|
|
178
|
+
tf_batch_set_schema(ob, k, st->group_cols[k], TF_TYPE_STRING);
|
|
179
|
+
for (size_t k = 0; k < st->n_aggs; k++)
|
|
180
|
+
tf_batch_set_schema(ob, st->n_group_cols + k, st->aggs[k].name, TF_TYPE_FLOAT64);
|
|
181
|
+
|
|
182
|
+
for (size_t g = 0; g < st->map.count; g++) {
|
|
183
|
+
tf_batch_ensure_capacity(ob, g + 1);
|
|
184
|
+
|
|
185
|
+
/* Parse group key back to column values */
|
|
186
|
+
char *key = strdup(st->map.keys[g]);
|
|
187
|
+
char *p = key;
|
|
188
|
+
for (size_t k = 0; k < st->n_group_cols; k++) {
|
|
189
|
+
char *sep = strchr(p, '\x01');
|
|
190
|
+
if (sep) *sep = '\0';
|
|
191
|
+
if (strcmp(p, "\\N") == 0)
|
|
192
|
+
tf_batch_set_null(ob, g, k);
|
|
193
|
+
else
|
|
194
|
+
tf_batch_set_string(ob, g, k, p);
|
|
195
|
+
p = sep ? sep + 1 : p + strlen(p);
|
|
196
|
+
}
|
|
197
|
+
free(key);
|
|
198
|
+
|
|
199
|
+
/* Set aggregates */
|
|
200
|
+
group_accum *a = &st->map.accums[g];
|
|
201
|
+
for (size_t k = 0; k < st->n_aggs; k++) {
|
|
202
|
+
double v = 0;
|
|
203
|
+
switch (st->aggs[k].func) {
|
|
204
|
+
case AGG_SUM: v = a->sums[k]; break;
|
|
205
|
+
case AGG_AVG: v = a->counts[k] > 0 ? a->sums[k] / a->counts[k] : 0; break;
|
|
206
|
+
case AGG_COUNT: v = (double)a->counts[k]; break;
|
|
207
|
+
case AGG_MIN: v = a->counts[k] > 0 ? a->mins[k] : 0; break;
|
|
208
|
+
case AGG_MAX: v = a->counts[k] > 0 ? a->maxs[k] : 0; break;
|
|
209
|
+
}
|
|
210
|
+
tf_batch_set_float64(ob, g, st->n_group_cols + k, v);
|
|
211
|
+
}
|
|
212
|
+
ob->n_rows = g + 1;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
*out = ob;
|
|
216
|
+
return TF_OK;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static void group_agg_destroy(tf_step *self) {
|
|
220
|
+
group_agg_state *st = self->state;
|
|
221
|
+
if (st) {
|
|
222
|
+
for (size_t i = 0; i < st->n_group_cols; i++) free(st->group_cols[i]);
|
|
223
|
+
free(st->group_cols);
|
|
224
|
+
for (size_t i = 0; i < st->n_aggs; i++) { free(st->aggs[i].column); free(st->aggs[i].name); }
|
|
225
|
+
free(st->aggs);
|
|
226
|
+
for (size_t i = 0; i < st->map.count; i++) {
|
|
227
|
+
free(st->map.keys[i]);
|
|
228
|
+
free(st->map.accums[i].sums);
|
|
229
|
+
free(st->map.accums[i].mins);
|
|
230
|
+
free(st->map.accums[i].maxs);
|
|
231
|
+
free(st->map.accums[i].counts);
|
|
232
|
+
}
|
|
233
|
+
free(st->map.keys);
|
|
234
|
+
free(st->map.accums);
|
|
235
|
+
free(st);
|
|
236
|
+
}
|
|
237
|
+
free(self);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
tf_step *tf_group_agg_create(const cJSON *args) {
|
|
241
|
+
if (!args) return NULL;
|
|
242
|
+
cJSON *group_by = cJSON_GetObjectItemCaseSensitive(args, "group_by");
|
|
243
|
+
cJSON *aggs_j = cJSON_GetObjectItemCaseSensitive(args, "aggs");
|
|
244
|
+
if (!cJSON_IsArray(group_by) || !cJSON_IsArray(aggs_j)) return NULL;
|
|
245
|
+
|
|
246
|
+
group_agg_state *st = calloc(1, sizeof(group_agg_state));
|
|
247
|
+
if (!st) return NULL;
|
|
248
|
+
|
|
249
|
+
int ng = cJSON_GetArraySize(group_by);
|
|
250
|
+
st->group_cols = calloc(ng, sizeof(char *));
|
|
251
|
+
st->n_group_cols = ng;
|
|
252
|
+
for (int i = 0; i < ng; i++) {
|
|
253
|
+
cJSON *item = cJSON_GetArrayItem(group_by, i);
|
|
254
|
+
if (cJSON_IsString(item)) st->group_cols[i] = strdup(item->valuestring);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
int na = cJSON_GetArraySize(aggs_j);
|
|
258
|
+
st->aggs = calloc(na, sizeof(agg_spec));
|
|
259
|
+
st->n_aggs = na;
|
|
260
|
+
for (int i = 0; i < na; i++) {
|
|
261
|
+
cJSON *item = cJSON_GetArrayItem(aggs_j, i);
|
|
262
|
+
cJSON *col = cJSON_GetObjectItemCaseSensitive(item, "column");
|
|
263
|
+
cJSON *func = cJSON_GetObjectItemCaseSensitive(item, "func");
|
|
264
|
+
cJSON *name = cJSON_GetObjectItemCaseSensitive(item, "name");
|
|
265
|
+
st->aggs[i].column = strdup(cJSON_IsString(col) ? col->valuestring : "");
|
|
266
|
+
st->aggs[i].func = cJSON_IsString(func) ? parse_agg_func(func->valuestring) : AGG_COUNT;
|
|
267
|
+
if (cJSON_IsString(name)) {
|
|
268
|
+
st->aggs[i].name = strdup(name->valuestring);
|
|
269
|
+
} else {
|
|
270
|
+
char buf[256];
|
|
271
|
+
snprintf(buf, sizeof(buf), "%s_%s",
|
|
272
|
+
st->aggs[i].column,
|
|
273
|
+
cJSON_IsString(func) ? func->valuestring : "count");
|
|
274
|
+
st->aggs[i].name = strdup(buf);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
tf_step *step = malloc(sizeof(tf_step));
|
|
279
|
+
if (!step) { group_agg_destroy(&(tf_step){.state = st}); return NULL; }
|
|
280
|
+
step->process = group_agg_process;
|
|
281
|
+
step->flush = group_agg_flush;
|
|
282
|
+
step->destroy = group_agg_destroy;
|
|
283
|
+
step->state = st;
|
|
284
|
+
return step;
|
|
285
|
+
}
|