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.
Files changed (109) hide show
  1. package/README.md +395 -0
  2. package/app/assets/index-6quYZ5Ap.css +5 -0
  3. package/app/assets/index-pDFMluyz.js +160 -0
  4. package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  5. package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  6. package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  7. package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  8. package/app/index.html +13 -0
  9. package/binding.gyp +69 -0
  10. package/csrc/arena.c +91 -0
  11. package/csrc/batch.c +229 -0
  12. package/csrc/buffer.c +78 -0
  13. package/csrc/cJSON.c +3143 -0
  14. package/csrc/cJSON.h +300 -0
  15. package/csrc/codec_csv.c +1058 -0
  16. package/csrc/codec_jsonl.c +374 -0
  17. package/csrc/codec_table.c +218 -0
  18. package/csrc/codec_text.c +229 -0
  19. package/csrc/compiler.c +102 -0
  20. package/csrc/date_utils.h +94 -0
  21. package/csrc/dsl.c +1180 -0
  22. package/csrc/dsl.h +22 -0
  23. package/csrc/expr.c +1245 -0
  24. package/csrc/expr.h +56 -0
  25. package/csrc/internal.h +250 -0
  26. package/csrc/ir.c +119 -0
  27. package/csrc/ir.h +167 -0
  28. package/csrc/ir_schema.c +60 -0
  29. package/csrc/ir_serialize.c +104 -0
  30. package/csrc/ir_sql.c +1211 -0
  31. package/csrc/ir_validate.c +120 -0
  32. package/csrc/main.c +392 -0
  33. package/csrc/op_acf.c +133 -0
  34. package/csrc/op_anomaly.c +120 -0
  35. package/csrc/op_bin.c +109 -0
  36. package/csrc/op_cast.c +195 -0
  37. package/csrc/op_clip.c +88 -0
  38. package/csrc/op_date_trunc.c +181 -0
  39. package/csrc/op_datetime.c +212 -0
  40. package/csrc/op_derive.c +248 -0
  41. package/csrc/op_diff.c +134 -0
  42. package/csrc/op_ewma.c +103 -0
  43. package/csrc/op_explode.c +108 -0
  44. package/csrc/op_fill_down.c +163 -0
  45. package/csrc/op_fill_null.c +123 -0
  46. package/csrc/op_filter.c +132 -0
  47. package/csrc/op_frequency.c +193 -0
  48. package/csrc/op_grep.c +163 -0
  49. package/csrc/op_group_agg.c +285 -0
  50. package/csrc/op_hash.c +126 -0
  51. package/csrc/op_head.c +149 -0
  52. package/csrc/op_interpolate.c +239 -0
  53. package/csrc/op_join.c +384 -0
  54. package/csrc/op_label_encode.c +144 -0
  55. package/csrc/op_lead.c +190 -0
  56. package/csrc/op_normalize.c +226 -0
  57. package/csrc/op_onehot.c +185 -0
  58. package/csrc/op_pivot.c +370 -0
  59. package/csrc/op_registry.c +1148 -0
  60. package/csrc/op_rename.c +138 -0
  61. package/csrc/op_replace.c +202 -0
  62. package/csrc/op_sample.c +101 -0
  63. package/csrc/op_select.c +140 -0
  64. package/csrc/op_skip.c +152 -0
  65. package/csrc/op_sort.c +273 -0
  66. package/csrc/op_split.c +114 -0
  67. package/csrc/op_split_data.c +87 -0
  68. package/csrc/op_stack.c +315 -0
  69. package/csrc/op_stats.c +779 -0
  70. package/csrc/op_step.c +171 -0
  71. package/csrc/op_tail.c +96 -0
  72. package/csrc/op_top.c +150 -0
  73. package/csrc/op_trim.c +109 -0
  74. package/csrc/op_unique.c +300 -0
  75. package/csrc/op_unpivot.c +159 -0
  76. package/csrc/op_validate.c +71 -0
  77. package/csrc/op_window.c +150 -0
  78. package/csrc/pipeline.c +315 -0
  79. package/csrc/plan.c +206 -0
  80. package/csrc/recipes.c +102 -0
  81. package/csrc/recipes.h +27 -0
  82. package/csrc/report.c +463 -0
  83. package/csrc/report.h +22 -0
  84. package/csrc/tranfi.h +123 -0
  85. package/csrc/wasm_api.c +157 -0
  86. package/napi_api.c +326 -0
  87. package/package.json +46 -57
  88. package/src/cli.js +193 -0
  89. package/src/engines/duckdb.js +109 -0
  90. package/src/index.js +306 -0
  91. package/src/native.js +22 -0
  92. package/src/pipeline.js +286 -0
  93. package/src/server.js +277 -0
  94. package/src/wasm.js +19 -0
  95. package/wasm/index.js +244 -0
  96. package/wasm/package.json +1 -0
  97. package/wasm/tranfi_core.js +0 -0
  98. package/LICENSE +0 -21
  99. package/dist/bundle.js +0 -1
  100. package/index.html +0 -18
  101. package/logo.png +0 -0
  102. package/src/app.css +0 -160
  103. package/src/app.js +0 -203
  104. package/src/app.vue +0 -253
  105. package/src/bulma-input.vue +0 -110
  106. package/src/common-inputs.js +0 -28
  107. package/src/main.js +0 -18
  108. package/src/transforms.js +0 -166
  109. package/webpack.config.js +0 -108
@@ -0,0 +1,120 @@
1
+ /*
2
+ * op_anomaly.c — Streaming anomaly detection via z-score.
3
+ * Uses Welford's online algorithm for mean/variance.
4
+ *
5
+ * Config: {"column": "price", "threshold": 3.0, "result": "price_anomaly"}
6
+ */
7
+
8
+ #include "internal.h"
9
+ #include "cJSON.h"
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+ #include <stdio.h>
13
+ #include <math.h>
14
+
15
+ typedef struct {
16
+ char *column;
17
+ char *result;
18
+ double threshold;
19
+ /* Welford's online algorithm */
20
+ size_t count;
21
+ double mean;
22
+ double m2;
23
+ } anomaly_state;
24
+
25
+ static double get_numeric(const tf_batch *b, size_t r, int ci) {
26
+ if (b->col_types[ci] == TF_TYPE_INT64) return (double)tf_batch_get_int64(b, r, ci);
27
+ if (b->col_types[ci] == TF_TYPE_FLOAT64) return tf_batch_get_float64(b, r, ci);
28
+ return 0;
29
+ }
30
+
31
+ static int anomaly_process(tf_step *self, tf_batch *in, tf_batch **out,
32
+ tf_side_channels *side) {
33
+ (void)side;
34
+ anomaly_state *st = self->state;
35
+ *out = NULL;
36
+
37
+ tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
38
+ if (!ob) return TF_ERROR;
39
+ for (size_t c = 0; c < in->n_cols; c++)
40
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
41
+ tf_batch_set_schema(ob, in->n_cols, st->result, TF_TYPE_INT64);
42
+
43
+ int ci = tf_batch_col_index(in, st->column);
44
+
45
+ for (size_t r = 0; r < in->n_rows; r++) {
46
+ tf_batch_copy_row(ob, r, in, r);
47
+
48
+ if (ci < 0 || tf_batch_is_null(in, r, ci)) {
49
+ tf_batch_set_int64(ob, r, in->n_cols, 0);
50
+ ob->n_rows = r + 1;
51
+ continue;
52
+ }
53
+
54
+ double val = get_numeric(in, r, ci);
55
+
56
+ /* Update Welford's */
57
+ st->count++;
58
+ double delta = val - st->mean;
59
+ st->mean += delta / (double)st->count;
60
+ double delta2 = val - st->mean;
61
+ st->m2 += delta * delta2;
62
+
63
+ /* Compute z-score (need at least 2 data points for variance) */
64
+ int is_anomaly = 0;
65
+ if (st->count >= 2) {
66
+ double var = st->m2 / (double)(st->count - 1);
67
+ double std = sqrt(var);
68
+ if (std > 0) {
69
+ double z = fabs((val - st->mean) / std);
70
+ is_anomaly = z > st->threshold ? 1 : 0;
71
+ }
72
+ }
73
+
74
+ tf_batch_set_int64(ob, r, in->n_cols, is_anomaly);
75
+ ob->n_rows = r + 1;
76
+ }
77
+
78
+ *out = ob;
79
+ return TF_OK;
80
+ }
81
+
82
+ static int anomaly_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
83
+ (void)self; (void)side; *out = NULL; return TF_OK;
84
+ }
85
+
86
+ static void anomaly_destroy(tf_step *self) {
87
+ anomaly_state *st = self->state;
88
+ if (st) { free(st->column); free(st->result); free(st); }
89
+ free(self);
90
+ }
91
+
92
+ tf_step *tf_anomaly_create(const cJSON *args) {
93
+ if (!args) return NULL;
94
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
95
+ if (!cJSON_IsString(col_j)) return NULL;
96
+
97
+ anomaly_state *st = calloc(1, sizeof(anomaly_state));
98
+ if (!st) return NULL;
99
+ st->column = strdup(col_j->valuestring);
100
+
101
+ cJSON *thresh_j = cJSON_GetObjectItemCaseSensitive(args, "threshold");
102
+ st->threshold = cJSON_IsNumber(thresh_j) ? thresh_j->valuedouble : 3.0;
103
+
104
+ cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
105
+ if (cJSON_IsString(res_j)) {
106
+ st->result = strdup(res_j->valuestring);
107
+ } else {
108
+ char buf[256];
109
+ snprintf(buf, sizeof(buf), "%s_anomaly", st->column);
110
+ st->result = strdup(buf);
111
+ }
112
+
113
+ tf_step *step = malloc(sizeof(tf_step));
114
+ if (!step) { free(st->column); free(st->result); free(st); return NULL; }
115
+ step->process = anomaly_process;
116
+ step->flush = anomaly_flush;
117
+ step->destroy = anomaly_destroy;
118
+ step->state = st;
119
+ return step;
120
+ }
package/csrc/op_bin.c ADDED
@@ -0,0 +1,109 @@
1
+ /*
2
+ * op_bin.c — Discretize numeric column into labeled bins.
3
+ *
4
+ * Config: {"column": "age", "boundaries": [18, 30, 50, 65]}
5
+ * Adds <col>_bin column with labels like "<18", "18-30", "30-50", "50-65", "65+"
6
+ */
7
+
8
+ #include "internal.h"
9
+ #include "cJSON.h"
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+ #include <stdio.h>
13
+
14
+ typedef struct {
15
+ char *column;
16
+ double *boundaries;
17
+ size_t n_boundaries;
18
+ } bin_state;
19
+
20
+ static int bin_process(tf_step *self, tf_batch *in, tf_batch **out,
21
+ tf_side_channels *side) {
22
+ (void)side;
23
+ bin_state *st = self->state;
24
+ *out = NULL;
25
+
26
+ char bin_col_name[256];
27
+ snprintf(bin_col_name, sizeof(bin_col_name), "%s_bin", st->column);
28
+
29
+ tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
30
+ if (!ob) return TF_ERROR;
31
+ for (size_t c = 0; c < in->n_cols; c++)
32
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
33
+ tf_batch_set_schema(ob, in->n_cols, bin_col_name, TF_TYPE_STRING);
34
+
35
+ int ci = tf_batch_col_index(in, st->column);
36
+
37
+ for (size_t r = 0; r < in->n_rows; r++) {
38
+ tf_batch_copy_row(ob, r, in, r);
39
+
40
+ if (ci < 0 || tf_batch_is_null(in, r, ci)) {
41
+ tf_batch_set_null(ob, r, in->n_cols);
42
+ } else {
43
+ double val = 0;
44
+ if (in->col_types[ci] == TF_TYPE_INT64) val = (double)tf_batch_get_int64(in, r, ci);
45
+ else if (in->col_types[ci] == TF_TYPE_FLOAT64) val = tf_batch_get_float64(in, r, ci);
46
+
47
+ char label[64];
48
+ if (st->n_boundaries == 0) {
49
+ snprintf(label, sizeof(label), "%g", val);
50
+ } else if (val < st->boundaries[0]) {
51
+ snprintf(label, sizeof(label), "<%g", st->boundaries[0]);
52
+ } else {
53
+ size_t b;
54
+ for (b = 1; b < st->n_boundaries; b++) {
55
+ if (val < st->boundaries[b]) break;
56
+ }
57
+ if (b < st->n_boundaries) {
58
+ snprintf(label, sizeof(label), "%g-%g", st->boundaries[b - 1], st->boundaries[b]);
59
+ } else {
60
+ snprintf(label, sizeof(label), "%g+", st->boundaries[st->n_boundaries - 1]);
61
+ }
62
+ }
63
+ tf_batch_set_string(ob, r, in->n_cols, label);
64
+ }
65
+ ob->n_rows = r + 1;
66
+ }
67
+
68
+ *out = ob;
69
+ return TF_OK;
70
+ }
71
+
72
+ static int bin_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
73
+ (void)self; (void)side; *out = NULL; return TF_OK;
74
+ }
75
+
76
+ static void bin_destroy(tf_step *self) {
77
+ bin_state *st = self->state;
78
+ if (st) { free(st->column); free(st->boundaries); free(st); }
79
+ free(self);
80
+ }
81
+
82
+ tf_step *tf_bin_create(const cJSON *args) {
83
+ if (!args) return NULL;
84
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
85
+ if (!cJSON_IsString(col_j)) return NULL;
86
+
87
+ bin_state *st = calloc(1, sizeof(bin_state));
88
+ if (!st) return NULL;
89
+ st->column = strdup(col_j->valuestring);
90
+
91
+ cJSON *bounds = cJSON_GetObjectItemCaseSensitive(args, "boundaries");
92
+ if (bounds && cJSON_IsArray(bounds)) {
93
+ int n = cJSON_GetArraySize(bounds);
94
+ st->boundaries = malloc(n * sizeof(double));
95
+ st->n_boundaries = n;
96
+ for (int i = 0; i < n; i++) {
97
+ cJSON *item = cJSON_GetArrayItem(bounds, i);
98
+ st->boundaries[i] = cJSON_IsNumber(item) ? item->valuedouble : 0;
99
+ }
100
+ }
101
+
102
+ tf_step *step = malloc(sizeof(tf_step));
103
+ if (!step) { free(st->column); free(st->boundaries); free(st); return NULL; }
104
+ step->process = bin_process;
105
+ step->flush = bin_flush;
106
+ step->destroy = bin_destroy;
107
+ step->state = st;
108
+ return step;
109
+ }
package/csrc/op_cast.c ADDED
@@ -0,0 +1,195 @@
1
+ /*
2
+ * op_cast.c — Type conversion.
3
+ *
4
+ * Config: {"mapping": {"age": "int", "score": "float", "active": "bool"}}
5
+ */
6
+
7
+ #include "internal.h"
8
+ #include "date_utils.h"
9
+ #include "cJSON.h"
10
+ #include <stdlib.h>
11
+ #include <string.h>
12
+ #include <stdio.h>
13
+
14
+ typedef struct {
15
+ char **col_names;
16
+ tf_type *target_types;
17
+ size_t n;
18
+ } cast_state;
19
+
20
+ static tf_type parse_type(const char *s) {
21
+ if (strcmp(s, "int") == 0 || strcmp(s, "int64") == 0) return TF_TYPE_INT64;
22
+ if (strcmp(s, "float") == 0 || strcmp(s, "float64") == 0) return TF_TYPE_FLOAT64;
23
+ if (strcmp(s, "string") == 0 || strcmp(s, "str") == 0) return TF_TYPE_STRING;
24
+ if (strcmp(s, "bool") == 0 || strcmp(s, "boolean") == 0) return TF_TYPE_BOOL;
25
+ if (strcmp(s, "date") == 0) return TF_TYPE_DATE;
26
+ if (strcmp(s, "timestamp") == 0 || strcmp(s, "datetime") == 0) return TF_TYPE_TIMESTAMP;
27
+ return TF_TYPE_NULL;
28
+ }
29
+
30
+ static int cast_process(tf_step *self, tf_batch *in, tf_batch **out,
31
+ tf_side_channels *side) {
32
+ (void)side;
33
+ cast_state *st = self->state;
34
+ *out = NULL;
35
+
36
+ /* Determine output types */
37
+ tf_type *out_types = malloc(in->n_cols * sizeof(tf_type));
38
+ if (!out_types) return TF_ERROR;
39
+ for (size_t c = 0; c < in->n_cols; c++) out_types[c] = in->col_types[c];
40
+ for (size_t k = 0; k < st->n; k++) {
41
+ int ci = tf_batch_col_index(in, st->col_names[k]);
42
+ if (ci >= 0) out_types[ci] = st->target_types[k];
43
+ }
44
+
45
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
46
+ if (!ob) { free(out_types); return TF_ERROR; }
47
+ for (size_t c = 0; c < in->n_cols; c++)
48
+ tf_batch_set_schema(ob, c, in->col_names[c], out_types[c]);
49
+
50
+ for (size_t r = 0; r < in->n_rows; r++) {
51
+ tf_batch_ensure_capacity(ob, r + 1);
52
+ for (size_t c = 0; c < in->n_cols; c++) {
53
+ if (tf_batch_is_null(in, r, c)) {
54
+ tf_batch_set_null(ob, r, c);
55
+ continue;
56
+ }
57
+ tf_type src_t = in->col_types[c];
58
+ tf_type dst_t = out_types[c];
59
+
60
+ if (src_t == dst_t) {
61
+ /* No conversion needed */
62
+ switch (src_t) {
63
+ case TF_TYPE_BOOL: tf_batch_set_bool(ob, r, c, tf_batch_get_bool(in, r, c)); break;
64
+ case TF_TYPE_INT64: tf_batch_set_int64(ob, r, c, tf_batch_get_int64(in, r, c)); break;
65
+ case TF_TYPE_FLOAT64: tf_batch_set_float64(ob, r, c, tf_batch_get_float64(in, r, c)); break;
66
+ case TF_TYPE_STRING: tf_batch_set_string(ob, r, c, tf_batch_get_string(in, r, c)); break;
67
+ case TF_TYPE_DATE: tf_batch_set_date(ob, r, c, tf_batch_get_date(in, r, c)); break;
68
+ case TF_TYPE_TIMESTAMP: tf_batch_set_timestamp(ob, r, c, tf_batch_get_timestamp(in, r, c)); break;
69
+ default: tf_batch_set_null(ob, r, c); break;
70
+ }
71
+ continue;
72
+ }
73
+
74
+ /* Conversion */
75
+ if (dst_t == TF_TYPE_STRING) {
76
+ char buf[64];
77
+ switch (src_t) {
78
+ case TF_TYPE_INT64: snprintf(buf, sizeof(buf), "%lld", (long long)tf_batch_get_int64(in, r, c)); break;
79
+ case TF_TYPE_FLOAT64: snprintf(buf, sizeof(buf), "%g", tf_batch_get_float64(in, r, c)); break;
80
+ case TF_TYPE_BOOL: snprintf(buf, sizeof(buf), "%s", tf_batch_get_bool(in, r, c) ? "true" : "false"); break;
81
+ case TF_TYPE_DATE: tf_date_format(tf_batch_get_date(in, r, c), buf, sizeof(buf)); break;
82
+ case TF_TYPE_TIMESTAMP: tf_timestamp_format(tf_batch_get_timestamp(in, r, c), buf, sizeof(buf)); break;
83
+ default: buf[0] = '\0'; break;
84
+ }
85
+ tf_batch_set_string(ob, r, c, buf);
86
+ } else if (dst_t == TF_TYPE_INT64) {
87
+ int64_t v = 0;
88
+ if (src_t == TF_TYPE_FLOAT64) v = (int64_t)tf_batch_get_float64(in, r, c);
89
+ else if (src_t == TF_TYPE_STRING) { char *end; v = strtoll(tf_batch_get_string(in, r, c), &end, 10); }
90
+ else if (src_t == TF_TYPE_BOOL) v = tf_batch_get_bool(in, r, c) ? 1 : 0;
91
+ else if (src_t == TF_TYPE_TIMESTAMP) v = tf_batch_get_timestamp(in, r, c);
92
+ tf_batch_set_int64(ob, r, c, v);
93
+ } else if (dst_t == TF_TYPE_FLOAT64) {
94
+ double v = 0;
95
+ if (src_t == TF_TYPE_INT64) v = (double)tf_batch_get_int64(in, r, c);
96
+ else if (src_t == TF_TYPE_STRING) { char *end; v = strtod(tf_batch_get_string(in, r, c), &end); }
97
+ else if (src_t == TF_TYPE_BOOL) v = tf_batch_get_bool(in, r, c) ? 1.0 : 0.0;
98
+ tf_batch_set_float64(ob, r, c, v);
99
+ } else if (dst_t == TF_TYPE_BOOL) {
100
+ bool v = false;
101
+ if (src_t == TF_TYPE_INT64) v = tf_batch_get_int64(in, r, c) != 0;
102
+ else if (src_t == TF_TYPE_FLOAT64) v = tf_batch_get_float64(in, r, c) != 0.0;
103
+ else if (src_t == TF_TYPE_STRING) v = strlen(tf_batch_get_string(in, r, c)) > 0 && strcmp(tf_batch_get_string(in, r, c), "false") != 0;
104
+ tf_batch_set_bool(ob, r, c, v);
105
+ } else if (dst_t == TF_TYPE_DATE) {
106
+ int32_t v = 0;
107
+ if (src_t == TF_TYPE_STRING) {
108
+ const char *s = tf_batch_get_string(in, r, c);
109
+ int y, m, d;
110
+ if (sscanf(s, "%d-%d-%d", &y, &m, &d) == 3)
111
+ v = tf_date_from_ymd(y, m, d);
112
+ } else if (src_t == TF_TYPE_TIMESTAMP) {
113
+ v = (int32_t)(tf_batch_get_timestamp(in, r, c) / (86400LL * 1000000LL));
114
+ }
115
+ tf_batch_set_date(ob, r, c, v);
116
+ } else if (dst_t == TF_TYPE_TIMESTAMP) {
117
+ int64_t v = 0;
118
+ if (src_t == TF_TYPE_STRING) {
119
+ const char *s = tf_batch_get_string(in, r, c);
120
+ size_t slen = strlen(s);
121
+ int32_t dv;
122
+ /* Try timestamp first, then date at midnight */
123
+ if (slen >= 19) {
124
+ int y, mo, d, h, mi, se;
125
+ if (sscanf(s, "%d-%d-%dT%d:%d:%d", &y, &mo, &d, &h, &mi, &se) == 6)
126
+ v = tf_timestamp_from_parts(y, mo, d, h, mi, se, 0);
127
+ else if (sscanf(s, "%d-%d-%d %d:%d:%d", &y, &mo, &d, &h, &mi, &se) == 6)
128
+ v = tf_timestamp_from_parts(y, mo, d, h, mi, se, 0);
129
+ } else if (slen == 10) {
130
+ int y, m, d;
131
+ if (sscanf(s, "%d-%d-%d", &y, &m, &d) == 3) {
132
+ dv = tf_date_from_ymd(y, m, d);
133
+ v = (int64_t)dv * 86400LL * 1000000LL;
134
+ }
135
+ }
136
+ } else if (src_t == TF_TYPE_DATE) {
137
+ v = (int64_t)tf_batch_get_date(in, r, c) * 86400LL * 1000000LL;
138
+ } else if (src_t == TF_TYPE_INT64) {
139
+ v = tf_batch_get_int64(in, r, c);
140
+ }
141
+ tf_batch_set_timestamp(ob, r, c, v);
142
+ } else {
143
+ tf_batch_set_null(ob, r, c);
144
+ }
145
+ }
146
+ ob->n_rows = r + 1;
147
+ }
148
+
149
+ free(out_types);
150
+ *out = ob;
151
+ return TF_OK;
152
+ }
153
+
154
+ static int cast_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
155
+ (void)self; (void)side; *out = NULL; return TF_OK;
156
+ }
157
+
158
+ static void cast_destroy(tf_step *self) {
159
+ cast_state *st = self->state;
160
+ if (st) {
161
+ for (size_t i = 0; i < st->n; i++) free(st->col_names[i]);
162
+ free(st->col_names); free(st->target_types);
163
+ free(st);
164
+ }
165
+ free(self);
166
+ }
167
+
168
+ tf_step *tf_cast_create(const cJSON *args) {
169
+ if (!args) return NULL;
170
+ cJSON *mapping = cJSON_GetObjectItemCaseSensitive(args, "mapping");
171
+ if (!mapping || !cJSON_IsObject(mapping)) return NULL;
172
+
173
+ int n = cJSON_GetArraySize(mapping);
174
+ cast_state *st = calloc(1, sizeof(cast_state));
175
+ if (!st) return NULL;
176
+ st->col_names = calloc(n, sizeof(char *));
177
+ st->target_types = calloc(n, sizeof(tf_type));
178
+ st->n = n;
179
+
180
+ int i = 0;
181
+ cJSON *entry = NULL;
182
+ cJSON_ArrayForEach(entry, mapping) {
183
+ st->col_names[i] = strdup(entry->string);
184
+ st->target_types[i] = cJSON_IsString(entry) ? parse_type(entry->valuestring) : TF_TYPE_NULL;
185
+ i++;
186
+ }
187
+
188
+ tf_step *step = malloc(sizeof(tf_step));
189
+ if (!step) { cast_destroy(&(tf_step){.state = st}); return NULL; }
190
+ step->process = cast_process;
191
+ step->flush = cast_flush;
192
+ step->destroy = cast_destroy;
193
+ step->state = st;
194
+ return step;
195
+ }
package/csrc/op_clip.c ADDED
@@ -0,0 +1,88 @@
1
+ /*
2
+ * op_clip.c — Clamp numeric values to [min, max].
3
+ *
4
+ * Config: {"column": "score", "min": 0, "max": 100}
5
+ */
6
+
7
+ #include "internal.h"
8
+ #include "cJSON.h"
9
+ #include <stdlib.h>
10
+ #include <string.h>
11
+
12
+ typedef struct {
13
+ char *column;
14
+ double min_val;
15
+ double max_val;
16
+ int has_min;
17
+ int has_max;
18
+ } clip_state;
19
+
20
+ static int clip_process(tf_step *self, tf_batch *in, tf_batch **out,
21
+ tf_side_channels *side) {
22
+ (void)side;
23
+ clip_state *st = self->state;
24
+ *out = NULL;
25
+
26
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
27
+ if (!ob) return TF_ERROR;
28
+ for (size_t c = 0; c < in->n_cols; c++)
29
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
30
+ for (size_t r = 0; r < in->n_rows; r++) {
31
+ tf_batch_copy_row(ob, r, in, r);
32
+ ob->n_rows = r + 1;
33
+ }
34
+
35
+ int ci = tf_batch_col_index(ob, st->column);
36
+ if (ci >= 0) {
37
+ for (size_t r = 0; r < ob->n_rows; r++) {
38
+ if (tf_batch_is_null(ob, r, ci)) continue;
39
+ if (ob->col_types[ci] == TF_TYPE_INT64) {
40
+ int64_t v = tf_batch_get_int64(ob, r, ci);
41
+ if (st->has_min && v < (int64_t)st->min_val) v = (int64_t)st->min_val;
42
+ if (st->has_max && v > (int64_t)st->max_val) v = (int64_t)st->max_val;
43
+ tf_batch_set_int64(ob, r, ci, v);
44
+ } else if (ob->col_types[ci] == TF_TYPE_FLOAT64) {
45
+ double v = tf_batch_get_float64(ob, r, ci);
46
+ if (st->has_min && v < st->min_val) v = st->min_val;
47
+ if (st->has_max && v > st->max_val) v = st->max_val;
48
+ tf_batch_set_float64(ob, r, ci, v);
49
+ }
50
+ }
51
+ }
52
+
53
+ *out = ob;
54
+ return TF_OK;
55
+ }
56
+
57
+ static int clip_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
58
+ (void)self; (void)side; *out = NULL; return TF_OK;
59
+ }
60
+
61
+ static void clip_destroy(tf_step *self) {
62
+ clip_state *st = self->state;
63
+ if (st) { free(st->column); free(st); }
64
+ free(self);
65
+ }
66
+
67
+ tf_step *tf_clip_create(const cJSON *args) {
68
+ if (!args) return NULL;
69
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
70
+ if (!cJSON_IsString(col_j)) return NULL;
71
+
72
+ clip_state *st = calloc(1, sizeof(clip_state));
73
+ if (!st) return NULL;
74
+ st->column = strdup(col_j->valuestring);
75
+
76
+ cJSON *min_j = cJSON_GetObjectItemCaseSensitive(args, "min");
77
+ if (cJSON_IsNumber(min_j)) { st->min_val = min_j->valuedouble; st->has_min = 1; }
78
+ cJSON *max_j = cJSON_GetObjectItemCaseSensitive(args, "max");
79
+ if (cJSON_IsNumber(max_j)) { st->max_val = max_j->valuedouble; st->has_max = 1; }
80
+
81
+ tf_step *step = malloc(sizeof(tf_step));
82
+ if (!step) { free(st->column); free(st); return NULL; }
83
+ step->process = clip_process;
84
+ step->flush = clip_flush;
85
+ step->destroy = clip_destroy;
86
+ step->state = st;
87
+ return step;
88
+ }
@@ -0,0 +1,181 @@
1
+ /*
2
+ * op_date_trunc.c — Truncate date/timestamp to a granularity.
3
+ *
4
+ * Config: {"column": "ts", "trunc": "month"}
5
+ *
6
+ * Supported granularities: year, month, day, hour, minute, second.
7
+ * Input: TF_TYPE_DATE, TF_TYPE_TIMESTAMP, or TF_TYPE_STRING (auto-parsed).
8
+ * Output: same type as input (truncated in place).
9
+ */
10
+
11
+ #include "internal.h"
12
+ #include "date_utils.h"
13
+ #include "cJSON.h"
14
+ #include <stdlib.h>
15
+ #include <string.h>
16
+ #include <stdio.h>
17
+
18
+ typedef enum {
19
+ TRUNC_YEAR,
20
+ TRUNC_MONTH,
21
+ TRUNC_DAY,
22
+ TRUNC_HOUR,
23
+ TRUNC_MINUTE,
24
+ TRUNC_SECOND,
25
+ } trunc_level;
26
+
27
+ typedef struct {
28
+ char *column;
29
+ char *result;
30
+ trunc_level level;
31
+ } date_trunc_state;
32
+
33
+ static trunc_level parse_level(const char *s) {
34
+ if (strcmp(s, "year") == 0) return TRUNC_YEAR;
35
+ if (strcmp(s, "month") == 0) return TRUNC_MONTH;
36
+ if (strcmp(s, "day") == 0) return TRUNC_DAY;
37
+ if (strcmp(s, "hour") == 0) return TRUNC_HOUR;
38
+ if (strcmp(s, "minute") == 0) return TRUNC_MINUTE;
39
+ if (strcmp(s, "second") == 0) return TRUNC_SECOND;
40
+ return TRUNC_DAY;
41
+ }
42
+
43
+ /* Truncate a date (days since epoch) to the given level. */
44
+ static int32_t trunc_date(int32_t days, trunc_level level) {
45
+ int y, m, d;
46
+ tf_date_to_ymd(days, &y, &m, &d);
47
+ switch (level) {
48
+ case TRUNC_YEAR: return tf_date_from_ymd(y, 1, 1);
49
+ case TRUNC_MONTH: return tf_date_from_ymd(y, m, 1);
50
+ default: return days; /* day or finer — date is already at day granularity */
51
+ }
52
+ }
53
+
54
+ /* Truncate a timestamp (microseconds since epoch) to the given level. */
55
+ static int64_t trunc_timestamp(int64_t us, trunc_level level) {
56
+ int y, mo, d, h, mi, s, frac;
57
+ tf_timestamp_to_parts(us, &y, &mo, &d, &h, &mi, &s, &frac);
58
+ switch (level) {
59
+ case TRUNC_YEAR: return tf_timestamp_from_parts(y, 1, 1, 0, 0, 0, 0);
60
+ case TRUNC_MONTH: return tf_timestamp_from_parts(y, mo, 1, 0, 0, 0, 0);
61
+ case TRUNC_DAY: return tf_timestamp_from_parts(y, mo, d, 0, 0, 0, 0);
62
+ case TRUNC_HOUR: return tf_timestamp_from_parts(y, mo, d, h, 0, 0, 0);
63
+ case TRUNC_MINUTE: return tf_timestamp_from_parts(y, mo, d, h, mi, 0, 0);
64
+ case TRUNC_SECOND: return tf_timestamp_from_parts(y, mo, d, h, mi, s, 0);
65
+ }
66
+ return us;
67
+ }
68
+
69
+ /* Try to parse a date string. Returns 1 on success. */
70
+ static int parse_date_string(const char *s, int *y, int *m, int *d) {
71
+ return sscanf(s, "%d-%d-%d", y, m, d) == 3;
72
+ }
73
+
74
+ static int date_trunc_process(tf_step *self, tf_batch *in, tf_batch **out,
75
+ tf_side_channels *side) {
76
+ (void)side;
77
+ date_trunc_state *st = self->state;
78
+ *out = NULL;
79
+
80
+ int ci = tf_batch_col_index(in, st->column);
81
+ int in_place = (strcmp(st->result, st->column) == 0);
82
+
83
+ size_t out_cols = in_place ? in->n_cols : in->n_cols + 1;
84
+ tf_batch *ob = tf_batch_create(out_cols, in->n_rows);
85
+ if (!ob) return TF_ERROR;
86
+
87
+ for (size_t c = 0; c < in->n_cols; c++)
88
+ tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]);
89
+
90
+ size_t result_col;
91
+ if (in_place) {
92
+ result_col = (size_t)ci;
93
+ } else {
94
+ result_col = in->n_cols;
95
+ /* Output type matches input column type */
96
+ tf_type out_type = (ci >= 0) ? in->col_types[ci] : TF_TYPE_STRING;
97
+ tf_batch_set_schema(ob, result_col, st->result, out_type);
98
+ }
99
+
100
+ for (size_t r = 0; r < in->n_rows; r++) {
101
+ tf_batch_copy_row(ob, r, in, r);
102
+
103
+ if (ci < 0 || tf_batch_is_null(in, r, ci)) {
104
+ if (!in_place) tf_batch_set_null(ob, r, result_col);
105
+ ob->n_rows = r + 1;
106
+ continue;
107
+ }
108
+
109
+ switch (in->col_types[ci]) {
110
+ case TF_TYPE_DATE: {
111
+ int32_t d = tf_batch_get_date(in, r, ci);
112
+ tf_batch_set_date(ob, r, result_col, trunc_date(d, st->level));
113
+ break;
114
+ }
115
+ case TF_TYPE_TIMESTAMP: {
116
+ int64_t ts = tf_batch_get_timestamp(in, r, ci);
117
+ tf_batch_set_timestamp(ob, r, result_col, trunc_timestamp(ts, st->level));
118
+ break;
119
+ }
120
+ case TF_TYPE_STRING: {
121
+ const char *s = tf_batch_get_string(in, r, ci);
122
+ int y, m, d;
123
+ if (parse_date_string(s, &y, &m, &d)) {
124
+ int32_t days = tf_date_from_ymd(y, m, d);
125
+ int32_t trunc = trunc_date(days, st->level);
126
+ char buf[16];
127
+ tf_date_format(trunc, buf, sizeof(buf));
128
+ tf_batch_set_string(ob, r, result_col, buf);
129
+ } else {
130
+ tf_batch_set_string(ob, r, result_col, s);
131
+ }
132
+ break;
133
+ }
134
+ default:
135
+ if (!in_place) tf_batch_set_null(ob, r, result_col);
136
+ break;
137
+ }
138
+ ob->n_rows = r + 1;
139
+ }
140
+
141
+ *out = ob;
142
+ return TF_OK;
143
+ }
144
+
145
+ static int date_trunc_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
146
+ (void)self; (void)side; *out = NULL; return TF_OK;
147
+ }
148
+
149
+ static void date_trunc_destroy(tf_step *self) {
150
+ date_trunc_state *st = self->state;
151
+ if (st) { free(st->column); free(st->result); free(st); }
152
+ free(self);
153
+ }
154
+
155
+ tf_step *tf_date_trunc_create(const cJSON *args) {
156
+ if (!args) return NULL;
157
+ cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
158
+ cJSON *trunc_j = cJSON_GetObjectItemCaseSensitive(args, "trunc");
159
+ if (!cJSON_IsString(col_j) || !cJSON_IsString(trunc_j)) return NULL;
160
+
161
+ date_trunc_state *st = calloc(1, sizeof(date_trunc_state));
162
+ if (!st) return NULL;
163
+ st->column = strdup(col_j->valuestring);
164
+ st->level = parse_level(trunc_j->valuestring);
165
+
166
+ cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
167
+ if (cJSON_IsString(res_j)) {
168
+ st->result = strdup(res_j->valuestring);
169
+ } else {
170
+ /* Default: overwrite in place */
171
+ st->result = strdup(st->column);
172
+ }
173
+
174
+ tf_step *step = malloc(sizeof(tf_step));
175
+ if (!step) { free(st->column); free(st->result); free(st); return NULL; }
176
+ step->process = date_trunc_process;
177
+ step->flush = date_trunc_flush;
178
+ step->destroy = date_trunc_destroy;
179
+ step->state = st;
180
+ return step;
181
+ }