tranfi 0.0.2 → 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/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,212 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_datetime.c — Extract date/time components from date strings.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"column": "date", "extract": ["year", "month", "day"]}
|
|
5
|
+
* Supports: YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, epoch seconds.
|
|
6
|
+
* Components: year, month, day, hour, minute, second, weekday, epoch
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
#include "internal.h"
|
|
10
|
+
#include "date_utils.h"
|
|
11
|
+
#include "cJSON.h"
|
|
12
|
+
#include <stdlib.h>
|
|
13
|
+
#include <string.h>
|
|
14
|
+
#include <stdio.h>
|
|
15
|
+
|
|
16
|
+
typedef struct {
|
|
17
|
+
char *column;
|
|
18
|
+
int w_year, w_month, w_day, w_hour, w_minute, w_second, w_weekday, w_epoch;
|
|
19
|
+
} datetime_state;
|
|
20
|
+
|
|
21
|
+
/* Days in each month (non-leap) */
|
|
22
|
+
static int days_in_month(int m, int y) {
|
|
23
|
+
static const int d[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
|
|
24
|
+
if (m == 2 && ((y%4==0 && y%100!=0) || y%400==0)) return 29;
|
|
25
|
+
return (m >= 1 && m <= 12) ? d[m] : 30;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Zeller-like weekday: 0=Sunday..6=Saturday */
|
|
29
|
+
static int weekday(int y, int m, int d) {
|
|
30
|
+
/* Tomohiko Sakamoto's algorithm */
|
|
31
|
+
static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
|
|
32
|
+
if (m < 3) y--;
|
|
33
|
+
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Convert date to epoch (seconds since 1970-01-01) */
|
|
37
|
+
static int64_t date_to_epoch(int y, int mo, int d, int h, int mi, int s) {
|
|
38
|
+
/* Simplified: assume Gregorian, no timezone */
|
|
39
|
+
int64_t days = 0;
|
|
40
|
+
for (int yr = 1970; yr < y; yr++) {
|
|
41
|
+
days += 365 + ((yr%4==0 && yr%100!=0) || yr%400==0);
|
|
42
|
+
}
|
|
43
|
+
for (int m = 1; m < mo; m++) days += days_in_month(m, y);
|
|
44
|
+
days += d - 1;
|
|
45
|
+
return days * 86400 + h * 3600 + mi * 60 + s;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static int parse_date(const char *s, int *y, int *mo, int *d, int *h, int *mi, int *se) {
|
|
49
|
+
*y = *mo = *d = *h = *mi = *se = 0;
|
|
50
|
+
|
|
51
|
+
/* Try epoch (pure number) */
|
|
52
|
+
char *end;
|
|
53
|
+
double epoch = strtod(s, &end);
|
|
54
|
+
if (*end == '\0' && end != s) {
|
|
55
|
+
/* Convert epoch to components */
|
|
56
|
+
int64_t ts = (int64_t)epoch;
|
|
57
|
+
*se = ts % 60; ts /= 60;
|
|
58
|
+
*mi = ts % 60; ts /= 60;
|
|
59
|
+
*h = ts % 24; ts /= 24;
|
|
60
|
+
/* Days since 1970-01-01 */
|
|
61
|
+
int64_t dd = ts;
|
|
62
|
+
*y = 1970;
|
|
63
|
+
while (1) {
|
|
64
|
+
int dy = 365 + ((*y%4==0 && *y%100!=0) || *y%400==0);
|
|
65
|
+
if (dd < dy) break;
|
|
66
|
+
dd -= dy;
|
|
67
|
+
(*y)++;
|
|
68
|
+
}
|
|
69
|
+
*mo = 1;
|
|
70
|
+
while (1) {
|
|
71
|
+
int dm = days_in_month(*mo, *y);
|
|
72
|
+
if (dd < dm) break;
|
|
73
|
+
dd -= dm;
|
|
74
|
+
(*mo)++;
|
|
75
|
+
}
|
|
76
|
+
*d = (int)dd + 1;
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Try YYYY-MM-DD [HH:MM:SS] */
|
|
81
|
+
int n = sscanf(s, "%d-%d-%d %d:%d:%d", y, mo, d, h, mi, se);
|
|
82
|
+
return n >= 3;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static int datetime_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
86
|
+
tf_side_channels *side) {
|
|
87
|
+
(void)side;
|
|
88
|
+
datetime_state *st = self->state;
|
|
89
|
+
*out = NULL;
|
|
90
|
+
|
|
91
|
+
/* Count extra columns */
|
|
92
|
+
size_t n_extra = 0;
|
|
93
|
+
if (st->w_year) n_extra++;
|
|
94
|
+
if (st->w_month) n_extra++;
|
|
95
|
+
if (st->w_day) n_extra++;
|
|
96
|
+
if (st->w_hour) n_extra++;
|
|
97
|
+
if (st->w_minute) n_extra++;
|
|
98
|
+
if (st->w_second) n_extra++;
|
|
99
|
+
if (st->w_weekday) n_extra++;
|
|
100
|
+
if (st->w_epoch) n_extra++;
|
|
101
|
+
|
|
102
|
+
tf_batch *ob = tf_batch_create(in->n_cols + n_extra, in->n_rows);
|
|
103
|
+
if (!ob) return TF_ERROR;
|
|
104
|
+
for (size_t c = 0; c < in->n_cols; c++)
|
|
105
|
+
tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
|
|
106
|
+
|
|
107
|
+
size_t ei = in->n_cols;
|
|
108
|
+
char col_prefix[256];
|
|
109
|
+
snprintf(col_prefix, sizeof(col_prefix), "%s_", st->column);
|
|
110
|
+
char col_name[256];
|
|
111
|
+
if (st->w_year) { snprintf(col_name, sizeof(col_name), "%syear", col_prefix); tf_batch_set_schema(ob, ei++, col_name, TF_TYPE_INT64); }
|
|
112
|
+
if (st->w_month) { snprintf(col_name, sizeof(col_name), "%smonth", col_prefix); tf_batch_set_schema(ob, ei++, col_name, TF_TYPE_INT64); }
|
|
113
|
+
if (st->w_day) { snprintf(col_name, sizeof(col_name), "%sday", col_prefix); tf_batch_set_schema(ob, ei++, col_name, TF_TYPE_INT64); }
|
|
114
|
+
if (st->w_hour) { snprintf(col_name, sizeof(col_name), "%shour", col_prefix); tf_batch_set_schema(ob, ei++, col_name, TF_TYPE_INT64); }
|
|
115
|
+
if (st->w_minute) { snprintf(col_name, sizeof(col_name), "%sminute", col_prefix); tf_batch_set_schema(ob, ei++, col_name, TF_TYPE_INT64); }
|
|
116
|
+
if (st->w_second) { snprintf(col_name, sizeof(col_name), "%ssecond", col_prefix); tf_batch_set_schema(ob, ei++, col_name, TF_TYPE_INT64); }
|
|
117
|
+
if (st->w_weekday) { snprintf(col_name, sizeof(col_name), "%sweekday", col_prefix); tf_batch_set_schema(ob, ei++, col_name, TF_TYPE_INT64); }
|
|
118
|
+
if (st->w_epoch) { snprintf(col_name, sizeof(col_name), "%sepoch", col_prefix); tf_batch_set_schema(ob, ei++, col_name, TF_TYPE_INT64); }
|
|
119
|
+
|
|
120
|
+
int ci = tf_batch_col_index(in, st->column);
|
|
121
|
+
|
|
122
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
123
|
+
tf_batch_copy_row(ob, r, in, r);
|
|
124
|
+
|
|
125
|
+
ei = in->n_cols;
|
|
126
|
+
if (ci >= 0 && !tf_batch_is_null(in, r, ci)) {
|
|
127
|
+
int y = 0, mo = 0, d = 0, h = 0, mi = 0, se = 0;
|
|
128
|
+
int parsed = 0;
|
|
129
|
+
if (in->col_types[ci] == TF_TYPE_STRING) {
|
|
130
|
+
parsed = parse_date(tf_batch_get_string(in, r, ci), &y, &mo, &d, &h, &mi, &se);
|
|
131
|
+
} else if (in->col_types[ci] == TF_TYPE_DATE) {
|
|
132
|
+
tf_date_to_ymd(tf_batch_get_date(in, r, ci), &y, &mo, &d);
|
|
133
|
+
parsed = 1;
|
|
134
|
+
} else if (in->col_types[ci] == TF_TYPE_TIMESTAMP) {
|
|
135
|
+
int frac;
|
|
136
|
+
tf_timestamp_to_parts(tf_batch_get_timestamp(in, r, ci), &y, &mo, &d, &h, &mi, &se, &frac);
|
|
137
|
+
parsed = 1;
|
|
138
|
+
}
|
|
139
|
+
if (parsed) {
|
|
140
|
+
if (st->w_year) tf_batch_set_int64(ob, r, ei++, y);
|
|
141
|
+
if (st->w_month) tf_batch_set_int64(ob, r, ei++, mo);
|
|
142
|
+
if (st->w_day) tf_batch_set_int64(ob, r, ei++, d);
|
|
143
|
+
if (st->w_hour) tf_batch_set_int64(ob, r, ei++, h);
|
|
144
|
+
if (st->w_minute) tf_batch_set_int64(ob, r, ei++, mi);
|
|
145
|
+
if (st->w_second) tf_batch_set_int64(ob, r, ei++, se);
|
|
146
|
+
if (st->w_weekday) tf_batch_set_int64(ob, r, ei++, weekday(y, mo, d));
|
|
147
|
+
if (st->w_epoch) tf_batch_set_int64(ob, r, ei++, date_to_epoch(y, mo, d, h, mi, se));
|
|
148
|
+
} else {
|
|
149
|
+
for (size_t k = in->n_cols; k < in->n_cols + n_extra; k++)
|
|
150
|
+
tf_batch_set_null(ob, r, k);
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
for (size_t k = in->n_cols; k < in->n_cols + n_extra; k++)
|
|
154
|
+
tf_batch_set_null(ob, r, k);
|
|
155
|
+
}
|
|
156
|
+
ob->n_rows = r + 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
*out = ob;
|
|
160
|
+
return TF_OK;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
static int datetime_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
164
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
static void datetime_destroy(tf_step *self) {
|
|
168
|
+
datetime_state *st = self->state;
|
|
169
|
+
if (st) { free(st->column); free(st); }
|
|
170
|
+
free(self);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
tf_step *tf_datetime_create(const cJSON *args) {
|
|
174
|
+
if (!args) return NULL;
|
|
175
|
+
cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
176
|
+
if (!cJSON_IsString(col_j)) return NULL;
|
|
177
|
+
|
|
178
|
+
datetime_state *st = calloc(1, sizeof(datetime_state));
|
|
179
|
+
if (!st) return NULL;
|
|
180
|
+
st->column = strdup(col_j->valuestring);
|
|
181
|
+
|
|
182
|
+
cJSON *extract = cJSON_GetObjectItemCaseSensitive(args, "extract");
|
|
183
|
+
if (extract && cJSON_IsArray(extract)) {
|
|
184
|
+
int n = cJSON_GetArraySize(extract);
|
|
185
|
+
for (int i = 0; i < n; i++) {
|
|
186
|
+
cJSON *item = cJSON_GetArrayItem(extract, i);
|
|
187
|
+
if (!cJSON_IsString(item)) continue;
|
|
188
|
+
const char *s = item->valuestring;
|
|
189
|
+
if (strcmp(s, "year") == 0) st->w_year = 1;
|
|
190
|
+
else if (strcmp(s, "month") == 0) st->w_month = 1;
|
|
191
|
+
else if (strcmp(s, "day") == 0) st->w_day = 1;
|
|
192
|
+
else if (strcmp(s, "hour") == 0) st->w_hour = 1;
|
|
193
|
+
else if (strcmp(s, "minute") == 0) st->w_minute = 1;
|
|
194
|
+
else if (strcmp(s, "second") == 0) st->w_second = 1;
|
|
195
|
+
else if (strcmp(s, "weekday") == 0) st->w_weekday = 1;
|
|
196
|
+
else if (strcmp(s, "epoch") == 0) st->w_epoch = 1;
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
/* Default: all */
|
|
200
|
+
st->w_year = st->w_month = st->w_day = 1;
|
|
201
|
+
st->w_hour = st->w_minute = st->w_second = 1;
|
|
202
|
+
st->w_weekday = st->w_epoch = 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
tf_step *step = malloc(sizeof(tf_step));
|
|
206
|
+
if (!step) { free(st->column); free(st); return NULL; }
|
|
207
|
+
step->process = datetime_process;
|
|
208
|
+
step->flush = datetime_flush;
|
|
209
|
+
step->destroy = datetime_destroy;
|
|
210
|
+
step->state = st;
|
|
211
|
+
return step;
|
|
212
|
+
}
|
package/csrc/op_derive.c
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_derive.c — Add computed columns using arithmetic expressions.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"columns": [{"name": "total", "expr": "col(price)*col(qty)"}]}
|
|
5
|
+
* For each row, evaluates each expression and appends the result as a new column.
|
|
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 *name;
|
|
17
|
+
tf_expr *expr;
|
|
18
|
+
} derive_col;
|
|
19
|
+
|
|
20
|
+
typedef struct {
|
|
21
|
+
derive_col *cols;
|
|
22
|
+
size_t n_cols;
|
|
23
|
+
int types_resolved; /* have we determined output types? */
|
|
24
|
+
tf_type *col_types; /* resolved type per derived column */
|
|
25
|
+
} derive_state;
|
|
26
|
+
|
|
27
|
+
/* Evaluate first row to determine derived column types */
|
|
28
|
+
static void resolve_types(derive_state *st, const tf_batch *in) {
|
|
29
|
+
st->col_types = calloc(st->n_cols, sizeof(tf_type));
|
|
30
|
+
if (!st->col_types) return;
|
|
31
|
+
|
|
32
|
+
for (size_t d = 0; d < st->n_cols; d++) {
|
|
33
|
+
if (in->n_rows == 0) {
|
|
34
|
+
/* No rows to sample; default to FLOAT64 for arithmetic */
|
|
35
|
+
st->col_types[d] = TF_TYPE_FLOAT64;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
tf_eval_result val;
|
|
39
|
+
tf_expr_eval_val(st->cols[d].expr, in, 0, &val);
|
|
40
|
+
switch (val.type) {
|
|
41
|
+
case TF_TYPE_INT64: st->col_types[d] = TF_TYPE_INT64; break;
|
|
42
|
+
case TF_TYPE_FLOAT64: st->col_types[d] = TF_TYPE_FLOAT64; break;
|
|
43
|
+
case TF_TYPE_STRING: st->col_types[d] = TF_TYPE_STRING; break;
|
|
44
|
+
case TF_TYPE_BOOL: st->col_types[d] = TF_TYPE_BOOL; break;
|
|
45
|
+
case TF_TYPE_DATE: st->col_types[d] = TF_TYPE_DATE; break;
|
|
46
|
+
case TF_TYPE_TIMESTAMP: st->col_types[d] = TF_TYPE_TIMESTAMP; break;
|
|
47
|
+
default: st->col_types[d] = TF_TYPE_FLOAT64; break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
st->types_resolved = 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static void set_derived_value(tf_batch *ob, size_t row, size_t col,
|
|
54
|
+
tf_type col_type, const tf_eval_result *val) {
|
|
55
|
+
if (val->type == TF_TYPE_NULL) {
|
|
56
|
+
tf_batch_set_null(ob, row, col);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Convert value to the column's type */
|
|
61
|
+
switch (col_type) {
|
|
62
|
+
case TF_TYPE_INT64:
|
|
63
|
+
if (val->type == TF_TYPE_INT64)
|
|
64
|
+
tf_batch_set_int64(ob, row, col, val->i);
|
|
65
|
+
else if (val->type == TF_TYPE_FLOAT64)
|
|
66
|
+
tf_batch_set_int64(ob, row, col, (int64_t)val->f);
|
|
67
|
+
else
|
|
68
|
+
tf_batch_set_null(ob, row, col);
|
|
69
|
+
break;
|
|
70
|
+
case TF_TYPE_FLOAT64:
|
|
71
|
+
if (val->type == TF_TYPE_FLOAT64)
|
|
72
|
+
tf_batch_set_float64(ob, row, col, val->f);
|
|
73
|
+
else if (val->type == TF_TYPE_INT64)
|
|
74
|
+
tf_batch_set_float64(ob, row, col, (double)val->i);
|
|
75
|
+
else
|
|
76
|
+
tf_batch_set_null(ob, row, col);
|
|
77
|
+
break;
|
|
78
|
+
case TF_TYPE_STRING:
|
|
79
|
+
if (val->type == TF_TYPE_STRING)
|
|
80
|
+
tf_batch_set_string(ob, row, col, val->s);
|
|
81
|
+
else
|
|
82
|
+
tf_batch_set_null(ob, row, col);
|
|
83
|
+
break;
|
|
84
|
+
case TF_TYPE_BOOL:
|
|
85
|
+
if (val->type == TF_TYPE_BOOL)
|
|
86
|
+
tf_batch_set_bool(ob, row, col, val->b);
|
|
87
|
+
else
|
|
88
|
+
tf_batch_set_null(ob, row, col);
|
|
89
|
+
break;
|
|
90
|
+
case TF_TYPE_DATE:
|
|
91
|
+
if (val->type == TF_TYPE_DATE)
|
|
92
|
+
tf_batch_set_date(ob, row, col, val->date);
|
|
93
|
+
else
|
|
94
|
+
tf_batch_set_null(ob, row, col);
|
|
95
|
+
break;
|
|
96
|
+
case TF_TYPE_TIMESTAMP:
|
|
97
|
+
if (val->type == TF_TYPE_TIMESTAMP)
|
|
98
|
+
tf_batch_set_timestamp(ob, row, col, val->i);
|
|
99
|
+
else
|
|
100
|
+
tf_batch_set_null(ob, row, col);
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
tf_batch_set_null(ob, row, col);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static int derive_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
109
|
+
tf_side_channels *side) {
|
|
110
|
+
(void)side;
|
|
111
|
+
derive_state *st = self->state;
|
|
112
|
+
*out = NULL;
|
|
113
|
+
|
|
114
|
+
/* Resolve types on first batch */
|
|
115
|
+
if (!st->types_resolved) {
|
|
116
|
+
resolve_types(st, in);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
size_t out_n_cols = in->n_cols + st->n_cols;
|
|
120
|
+
tf_batch *ob = tf_batch_create(out_n_cols, in->n_rows > 0 ? in->n_rows : 1);
|
|
121
|
+
if (!ob) return TF_ERROR;
|
|
122
|
+
|
|
123
|
+
/* Copy input schema */
|
|
124
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
125
|
+
tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
|
|
126
|
+
}
|
|
127
|
+
/* Set derived column schemas with resolved types */
|
|
128
|
+
for (size_t d = 0; d < st->n_cols; d++) {
|
|
129
|
+
tf_batch_set_schema(ob, in->n_cols + d, st->cols[d].name, st->col_types[d]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
133
|
+
tf_batch_ensure_capacity(ob, r + 1);
|
|
134
|
+
|
|
135
|
+
/* Copy input columns */
|
|
136
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
137
|
+
if (tf_batch_is_null(in, r, c)) {
|
|
138
|
+
tf_batch_set_null(ob, r, c);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
switch (in->col_types[c]) {
|
|
142
|
+
case TF_TYPE_BOOL:
|
|
143
|
+
tf_batch_set_bool(ob, r, c, tf_batch_get_bool(in, r, c));
|
|
144
|
+
break;
|
|
145
|
+
case TF_TYPE_INT64:
|
|
146
|
+
tf_batch_set_int64(ob, r, c, tf_batch_get_int64(in, r, c));
|
|
147
|
+
break;
|
|
148
|
+
case TF_TYPE_FLOAT64:
|
|
149
|
+
tf_batch_set_float64(ob, r, c, tf_batch_get_float64(in, r, c));
|
|
150
|
+
break;
|
|
151
|
+
case TF_TYPE_STRING:
|
|
152
|
+
tf_batch_set_string(ob, r, c, tf_batch_get_string(in, r, c));
|
|
153
|
+
break;
|
|
154
|
+
case TF_TYPE_DATE:
|
|
155
|
+
tf_batch_set_date(ob, r, c, tf_batch_get_date(in, r, c));
|
|
156
|
+
break;
|
|
157
|
+
case TF_TYPE_TIMESTAMP:
|
|
158
|
+
tf_batch_set_timestamp(ob, r, c, tf_batch_get_timestamp(in, r, c));
|
|
159
|
+
break;
|
|
160
|
+
default:
|
|
161
|
+
tf_batch_set_null(ob, r, c);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Evaluate derived columns */
|
|
167
|
+
for (size_t d = 0; d < st->n_cols; d++) {
|
|
168
|
+
size_t col_idx = in->n_cols + d;
|
|
169
|
+
tf_eval_result val;
|
|
170
|
+
if (tf_expr_eval_val(st->cols[d].expr, in, r, &val) != TF_OK) {
|
|
171
|
+
tf_batch_set_null(ob, r, col_idx);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
set_derived_value(ob, r, col_idx, st->col_types[d], &val);
|
|
175
|
+
}
|
|
176
|
+
ob->n_rows = r + 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (ob->n_rows > 0) {
|
|
180
|
+
*out = ob;
|
|
181
|
+
} else {
|
|
182
|
+
tf_batch_free(ob);
|
|
183
|
+
}
|
|
184
|
+
return TF_OK;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
static int derive_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
188
|
+
(void)self; (void)side;
|
|
189
|
+
*out = NULL;
|
|
190
|
+
return TF_OK;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static void derive_destroy(tf_step *self) {
|
|
194
|
+
derive_state *st = self->state;
|
|
195
|
+
if (st) {
|
|
196
|
+
for (size_t i = 0; i < st->n_cols; i++) {
|
|
197
|
+
free(st->cols[i].name);
|
|
198
|
+
tf_expr_free(st->cols[i].expr);
|
|
199
|
+
}
|
|
200
|
+
free(st->cols);
|
|
201
|
+
free(st->col_types);
|
|
202
|
+
free(st);
|
|
203
|
+
}
|
|
204
|
+
free(self);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
tf_step *tf_derive_create(const cJSON *args) {
|
|
208
|
+
if (!args) return NULL;
|
|
209
|
+
cJSON *columns = cJSON_GetObjectItemCaseSensitive(args, "columns");
|
|
210
|
+
if (!columns || !cJSON_IsArray(columns)) return NULL;
|
|
211
|
+
|
|
212
|
+
int n = cJSON_GetArraySize(columns);
|
|
213
|
+
if (n <= 0) return NULL;
|
|
214
|
+
|
|
215
|
+
derive_state *st = calloc(1, sizeof(derive_state));
|
|
216
|
+
if (!st) return NULL;
|
|
217
|
+
st->cols = calloc(n, sizeof(derive_col));
|
|
218
|
+
if (!st->cols) { free(st); return NULL; }
|
|
219
|
+
st->n_cols = n;
|
|
220
|
+
|
|
221
|
+
for (int i = 0; i < n; i++) {
|
|
222
|
+
cJSON *item = cJSON_GetArrayItem(columns, i);
|
|
223
|
+
cJSON *name_j = cJSON_GetObjectItemCaseSensitive(item, "name");
|
|
224
|
+
cJSON *expr_j = cJSON_GetObjectItemCaseSensitive(item, "expr");
|
|
225
|
+
if (!cJSON_IsString(name_j) || !cJSON_IsString(expr_j)) goto fail;
|
|
226
|
+
|
|
227
|
+
st->cols[i].name = strdup(name_j->valuestring);
|
|
228
|
+
st->cols[i].expr = tf_expr_parse(expr_j->valuestring);
|
|
229
|
+
if (!st->cols[i].name || !st->cols[i].expr) goto fail;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
tf_step *step = malloc(sizeof(tf_step));
|
|
233
|
+
if (!step) goto fail;
|
|
234
|
+
step->process = derive_process;
|
|
235
|
+
step->flush = derive_flush;
|
|
236
|
+
step->destroy = derive_destroy;
|
|
237
|
+
step->state = st;
|
|
238
|
+
return step;
|
|
239
|
+
|
|
240
|
+
fail:
|
|
241
|
+
for (int i = 0; i < n; i++) {
|
|
242
|
+
free(st->cols[i].name);
|
|
243
|
+
tf_expr_free(st->cols[i].expr);
|
|
244
|
+
}
|
|
245
|
+
free(st->cols);
|
|
246
|
+
free(st);
|
|
247
|
+
return NULL;
|
|
248
|
+
}
|
package/csrc/op_diff.c
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_diff.c — First (or higher-order) differencing.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"column": "price", "order": 1, "result": "price_diff"}
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include "internal.h"
|
|
8
|
+
#include "cJSON.h"
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
#include <string.h>
|
|
11
|
+
#include <stdio.h>
|
|
12
|
+
|
|
13
|
+
#define MAX_DIFF_ORDER 8
|
|
14
|
+
|
|
15
|
+
typedef struct {
|
|
16
|
+
char *column;
|
|
17
|
+
char *result;
|
|
18
|
+
int order;
|
|
19
|
+
double prev[MAX_DIFF_ORDER]; /* circular buffer of previous values */
|
|
20
|
+
int count; /* rows seen so far */
|
|
21
|
+
} diff_state;
|
|
22
|
+
|
|
23
|
+
static double get_numeric(const tf_batch *b, size_t r, int ci) {
|
|
24
|
+
if (b->col_types[ci] == TF_TYPE_INT64) return (double)tf_batch_get_int64(b, r, ci);
|
|
25
|
+
if (b->col_types[ci] == TF_TYPE_FLOAT64) return tf_batch_get_float64(b, r, ci);
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static int diff_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
30
|
+
tf_side_channels *side) {
|
|
31
|
+
(void)side;
|
|
32
|
+
diff_state *st = self->state;
|
|
33
|
+
*out = NULL;
|
|
34
|
+
|
|
35
|
+
tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
|
|
36
|
+
if (!ob) return TF_ERROR;
|
|
37
|
+
for (size_t c = 0; c < in->n_cols; c++)
|
|
38
|
+
tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
|
|
39
|
+
tf_batch_set_schema(ob, in->n_cols, st->result, TF_TYPE_FLOAT64);
|
|
40
|
+
|
|
41
|
+
int ci = tf_batch_col_index(in, st->column);
|
|
42
|
+
|
|
43
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
44
|
+
tf_batch_copy_row(ob, r, in, r);
|
|
45
|
+
|
|
46
|
+
if (ci < 0 || tf_batch_is_null(in, r, ci)) {
|
|
47
|
+
tf_batch_set_null(ob, r, in->n_cols);
|
|
48
|
+
ob->n_rows = r + 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
double val = get_numeric(in, r, ci);
|
|
53
|
+
|
|
54
|
+
if (st->count < st->order) {
|
|
55
|
+
/* Not enough history yet */
|
|
56
|
+
st->prev[st->count] = val;
|
|
57
|
+
st->count++;
|
|
58
|
+
tf_batch_set_null(ob, r, in->n_cols);
|
|
59
|
+
ob->n_rows = r + 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Compute difference using binomial coefficients:
|
|
64
|
+
* diff(order=1): val - prev[0]
|
|
65
|
+
* diff(order=2): val - 2*prev[0] + prev[1]
|
|
66
|
+
* General: sum_{k=0}^{order} (-1)^k * C(order,k) * x_{n-k}
|
|
67
|
+
*/
|
|
68
|
+
double result = 0;
|
|
69
|
+
int binom = 1;
|
|
70
|
+
int sign = 1;
|
|
71
|
+
/* x_n (current value) */
|
|
72
|
+
result = val;
|
|
73
|
+
/* x_{n-1} ... x_{n-order} from prev buffer (most recent first) */
|
|
74
|
+
for (int k = 1; k <= st->order; k++) {
|
|
75
|
+
binom = binom * (st->order - k + 1) / k;
|
|
76
|
+
sign = -sign;
|
|
77
|
+
/* prev[0] is the most recent previous value */
|
|
78
|
+
result += sign * binom * st->prev[k - 1];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
tf_batch_set_float64(ob, r, in->n_cols, result);
|
|
82
|
+
ob->n_rows = r + 1;
|
|
83
|
+
|
|
84
|
+
/* Shift prev buffer: move everything down, put val at [0] */
|
|
85
|
+
for (int k = st->order - 1; k > 0; k--)
|
|
86
|
+
st->prev[k] = st->prev[k - 1];
|
|
87
|
+
st->prev[0] = val;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
*out = ob;
|
|
91
|
+
return TF_OK;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static int diff_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 diff_destroy(tf_step *self) {
|
|
99
|
+
diff_state *st = self->state;
|
|
100
|
+
if (st) { free(st->column); free(st->result); free(st); }
|
|
101
|
+
free(self);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
tf_step *tf_diff_create(const cJSON *args) {
|
|
105
|
+
if (!args) return NULL;
|
|
106
|
+
cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
107
|
+
if (!cJSON_IsString(col_j)) return NULL;
|
|
108
|
+
|
|
109
|
+
diff_state *st = calloc(1, sizeof(diff_state));
|
|
110
|
+
if (!st) return NULL;
|
|
111
|
+
st->column = strdup(col_j->valuestring);
|
|
112
|
+
|
|
113
|
+
cJSON *order_j = cJSON_GetObjectItemCaseSensitive(args, "order");
|
|
114
|
+
st->order = (cJSON_IsNumber(order_j) && order_j->valueint > 0) ?
|
|
115
|
+
order_j->valueint : 1;
|
|
116
|
+
if (st->order > MAX_DIFF_ORDER) st->order = MAX_DIFF_ORDER;
|
|
117
|
+
|
|
118
|
+
cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
|
|
119
|
+
if (cJSON_IsString(res_j)) {
|
|
120
|
+
st->result = strdup(res_j->valuestring);
|
|
121
|
+
} else {
|
|
122
|
+
char buf[256];
|
|
123
|
+
snprintf(buf, sizeof(buf), "%s_diff", st->column);
|
|
124
|
+
st->result = strdup(buf);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
tf_step *step = malloc(sizeof(tf_step));
|
|
128
|
+
if (!step) { free(st->column); free(st->result); free(st); return NULL; }
|
|
129
|
+
step->process = diff_process;
|
|
130
|
+
step->flush = diff_flush;
|
|
131
|
+
step->destroy = diff_destroy;
|
|
132
|
+
step->state = st;
|
|
133
|
+
return step;
|
|
134
|
+
}
|
package/csrc/op_ewma.c
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* op_ewma.c — Exponentially weighted moving average.
|
|
3
|
+
*
|
|
4
|
+
* Config: {"column": "price", "alpha": 0.3, "result": "price_ewma"}
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include "internal.h"
|
|
8
|
+
#include "cJSON.h"
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
#include <string.h>
|
|
11
|
+
#include <stdio.h>
|
|
12
|
+
|
|
13
|
+
typedef struct {
|
|
14
|
+
char *column;
|
|
15
|
+
char *result;
|
|
16
|
+
double alpha;
|
|
17
|
+
double ewma;
|
|
18
|
+
int initialized;
|
|
19
|
+
} ewma_state;
|
|
20
|
+
|
|
21
|
+
static double get_numeric(const tf_batch *b, size_t r, int ci) {
|
|
22
|
+
if (b->col_types[ci] == TF_TYPE_INT64) return (double)tf_batch_get_int64(b, r, ci);
|
|
23
|
+
if (b->col_types[ci] == TF_TYPE_FLOAT64) return tf_batch_get_float64(b, r, ci);
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static int ewma_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
28
|
+
tf_side_channels *side) {
|
|
29
|
+
(void)side;
|
|
30
|
+
ewma_state *st = self->state;
|
|
31
|
+
*out = NULL;
|
|
32
|
+
|
|
33
|
+
tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
|
|
34
|
+
if (!ob) return TF_ERROR;
|
|
35
|
+
for (size_t c = 0; c < in->n_cols; c++)
|
|
36
|
+
tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
|
|
37
|
+
tf_batch_set_schema(ob, in->n_cols, st->result, TF_TYPE_FLOAT64);
|
|
38
|
+
|
|
39
|
+
int ci = tf_batch_col_index(in, st->column);
|
|
40
|
+
|
|
41
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
42
|
+
tf_batch_copy_row(ob, r, in, r);
|
|
43
|
+
|
|
44
|
+
if (ci < 0 || tf_batch_is_null(in, r, ci)) {
|
|
45
|
+
tf_batch_set_null(ob, r, in->n_cols);
|
|
46
|
+
ob->n_rows = r + 1;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
double val = get_numeric(in, r, ci);
|
|
51
|
+
if (!st->initialized) {
|
|
52
|
+
st->ewma = val;
|
|
53
|
+
st->initialized = 1;
|
|
54
|
+
} else {
|
|
55
|
+
st->ewma = st->alpha * val + (1.0 - st->alpha) * st->ewma;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
tf_batch_set_float64(ob, r, in->n_cols, st->ewma);
|
|
59
|
+
ob->n_rows = r + 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
*out = ob;
|
|
63
|
+
return TF_OK;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static int ewma_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
67
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static void ewma_destroy(tf_step *self) {
|
|
71
|
+
ewma_state *st = self->state;
|
|
72
|
+
if (st) { free(st->column); free(st->result); free(st); }
|
|
73
|
+
free(self);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
tf_step *tf_ewma_create(const cJSON *args) {
|
|
77
|
+
if (!args) return NULL;
|
|
78
|
+
cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
79
|
+
cJSON *alpha_j = cJSON_GetObjectItemCaseSensitive(args, "alpha");
|
|
80
|
+
if (!cJSON_IsString(col_j) || !cJSON_IsNumber(alpha_j)) return NULL;
|
|
81
|
+
|
|
82
|
+
ewma_state *st = calloc(1, sizeof(ewma_state));
|
|
83
|
+
if (!st) return NULL;
|
|
84
|
+
st->column = strdup(col_j->valuestring);
|
|
85
|
+
st->alpha = alpha_j->valuedouble;
|
|
86
|
+
|
|
87
|
+
cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
|
|
88
|
+
if (cJSON_IsString(res_j)) {
|
|
89
|
+
st->result = strdup(res_j->valuestring);
|
|
90
|
+
} else {
|
|
91
|
+
char buf[256];
|
|
92
|
+
snprintf(buf, sizeof(buf), "%s_ewma", st->column);
|
|
93
|
+
st->result = strdup(buf);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
tf_step *step = malloc(sizeof(tf_step));
|
|
97
|
+
if (!step) { free(st->column); free(st->result); free(st); return NULL; }
|
|
98
|
+
step->process = ewma_process;
|
|
99
|
+
step->flush = ewma_flush;
|
|
100
|
+
step->destroy = ewma_destroy;
|
|
101
|
+
step->state = st;
|
|
102
|
+
return step;
|
|
103
|
+
}
|