tranfi 0.1.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.
Files changed (132) hide show
  1. package/LICENSE +177 -0
  2. package/NOTICE +8 -0
  3. package/README.md +272 -40
  4. package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
  5. package/app/index.html +1 -1
  6. package/binding.gyp +55 -3
  7. package/csrc/arena.c +7 -5
  8. package/csrc/batch.c +818 -71
  9. package/csrc/buffer.c +84 -8
  10. package/csrc/cJSON.c +262 -19
  11. package/csrc/cJSON.h +17 -1
  12. package/csrc/codec_csv.c +1074 -181
  13. package/csrc/codec_jsonl.c +830 -118
  14. package/csrc/codec_table.c +108 -78
  15. package/csrc/codec_text.c +286 -68
  16. package/csrc/compiler.c +31 -3
  17. package/csrc/config.h +21 -0
  18. package/csrc/dsl.c +4722 -485
  19. package/csrc/expr.c +363 -55
  20. package/csrc/expr.h +2 -0
  21. package/csrc/internal.h +316 -27
  22. package/csrc/ir.c +65 -18
  23. package/csrc/ir.h +41 -0
  24. package/csrc/ir_schema.c +20 -5
  25. package/csrc/ir_serialize.c +68 -6
  26. package/csrc/ir_sql.c +796 -185
  27. package/csrc/ir_validate.c +462 -6
  28. package/csrc/json_path.c +210 -0
  29. package/csrc/main.c +879 -30
  30. package/csrc/memory_estimate.c +477 -0
  31. package/csrc/op_acf.c +171 -21
  32. package/csrc/op_across.c +477 -0
  33. package/csrc/op_anomaly.c +167 -32
  34. package/csrc/op_assert.c +761 -0
  35. package/csrc/op_bin.c +168 -29
  36. package/csrc/op_cast.c +383 -55
  37. package/csrc/op_clip.c +30 -19
  38. package/csrc/op_date_trunc.c +208 -34
  39. package/csrc/op_datetime.c +259 -77
  40. package/csrc/op_derive.c +65 -97
  41. package/csrc/op_diff.c +146 -30
  42. package/csrc/op_ewma.c +149 -30
  43. package/csrc/op_explode.c +124 -26
  44. package/csrc/op_fill_down.c +125 -53
  45. package/csrc/op_fill_null.c +176 -31
  46. package/csrc/op_filter.c +89 -40
  47. package/csrc/op_frequency.c +571 -43
  48. package/csrc/op_grep.c +36 -18
  49. package/csrc/op_group_agg.c +1790 -119
  50. package/csrc/op_hash.c +48 -15
  51. package/csrc/op_head.c +21 -86
  52. package/csrc/op_interpolate.c +268 -62
  53. package/csrc/op_join.c +2700 -182
  54. package/csrc/op_json_extract.c +227 -0
  55. package/csrc/op_json_filter.c +384 -0
  56. package/csrc/op_json_flatten.c +293 -0
  57. package/csrc/op_json_schema.c +503 -0
  58. package/csrc/op_label_encode.c +328 -53
  59. package/csrc/op_lag.c +181 -0
  60. package/csrc/op_lead.c +141 -89
  61. package/csrc/op_normalize.c +363 -79
  62. package/csrc/op_onehot.c +345 -73
  63. package/csrc/op_pivot.c +1546 -162
  64. package/csrc/op_quarantine.c +189 -0
  65. package/csrc/op_registry.c +2062 -166
  66. package/csrc/op_rename.c +41 -50
  67. package/csrc/op_replace.c +270 -118
  68. package/csrc/op_rleid.c +297 -0
  69. package/csrc/op_rowid.c +559 -0
  70. package/csrc/op_sample.c +80 -23
  71. package/csrc/op_schema.c +1341 -0
  72. package/csrc/op_schema_infer.c +252 -0
  73. package/csrc/op_select.c +265 -65
  74. package/csrc/op_set.c +3449 -0
  75. package/csrc/op_skip.c +30 -87
  76. package/csrc/op_sort.c +670 -124
  77. package/csrc/op_source_name.c +120 -0
  78. package/csrc/op_split.c +65 -28
  79. package/csrc/op_split_data.c +41 -9
  80. package/csrc/op_stack.c +178 -222
  81. package/csrc/op_stats.c +206 -110
  82. package/csrc/op_step.c +217 -55
  83. package/csrc/op_tail.c +21 -12
  84. package/csrc/op_tee.c +338 -0
  85. package/csrc/op_top.c +260 -53
  86. package/csrc/op_trim.c +48 -19
  87. package/csrc/op_unique.c +1193 -150
  88. package/csrc/op_unpivot.c +100 -66
  89. package/csrc/op_validate.c +601 -24
  90. package/csrc/op_window.c +492 -51
  91. package/csrc/path_policy.c +85 -0
  92. package/csrc/pipeline.c +872 -99
  93. package/csrc/recipes.c +3 -1
  94. package/csrc/report.c +73 -30
  95. package/csrc/selector.c +1097 -0
  96. package/csrc/size_utils.c +348 -0
  97. package/csrc/spill.c +317 -0
  98. package/csrc/spill.h +21 -0
  99. package/csrc/tranfi.h +169 -1
  100. package/csrc/transform.h +209 -0
  101. package/csrc/transform_api.c +2237 -0
  102. package/csrc/transform_categorical.c +923 -0
  103. package/csrc/transform_internal.h +472 -0
  104. package/csrc/transform_json.c +3812 -0
  105. package/csrc/transform_numeric.c +1966 -0
  106. package/csrc/transform_sha256.c +154 -0
  107. package/csrc/transform_wasm.h +162 -0
  108. package/csrc/transform_wasm_api.c +1373 -0
  109. package/csrc/wasm_api.c +70 -9
  110. package/napi_api.c +219 -11
  111. package/napi_transform.c +1648 -0
  112. package/napi_transform.h +8 -0
  113. package/package.json +27 -11
  114. package/scripts/install-native.js +76 -0
  115. package/scripts/prepack.js +64 -0
  116. package/scripts/sync-csrc.js +23 -0
  117. package/src/cli.js +8 -11
  118. package/src/engines/duckdb.js +45 -12
  119. package/src/index.js +661 -42
  120. package/src/memory_policy.js +411 -0
  121. package/src/native.js +1 -5
  122. package/src/pipeline.js +454 -31
  123. package/src/recipe_json.js +80 -0
  124. package/src/server.js +10 -8
  125. package/src/transform.js +403 -0
  126. package/src/transform_error.js +10 -0
  127. package/src/wasm.js +6 -4
  128. package/wasm/index.js +498 -10
  129. package/wasm/tranfi_core.js +0 -0
  130. package/wasm/transform.js +1156 -0
  131. package/wasm/worker.js +786 -0
  132. package/csrc/plan.c +0 -206
package/csrc/op_anomaly.c CHANGED
@@ -11,6 +11,18 @@
11
11
  #include <string.h>
12
12
  #include <stdio.h>
13
13
  #include <math.h>
14
+ #include <stdint.h>
15
+
16
+ typedef enum {
17
+ ANOMALY_MISSING_ERROR,
18
+ ANOMALY_MISSING_NULL,
19
+ ANOMALY_MISSING_IGNORE,
20
+ } anomaly_missing_policy;
21
+
22
+ typedef enum {
23
+ ANOMALY_TYPE_FAIL,
24
+ ANOMALY_TYPE_NULL,
25
+ } anomaly_type_policy;
14
26
 
15
27
  typedef struct {
16
28
  char *column;
@@ -20,12 +32,79 @@ typedef struct {
20
32
  size_t count;
21
33
  double mean;
22
34
  double m2;
35
+ anomaly_missing_policy missing;
36
+ anomaly_type_policy on_type_error;
23
37
  } anomaly_state;
24
38
 
25
- static double get_numeric(const tf_batch *b, size_t r, int ci) {
39
+ static int anomaly_is_numeric_type(tf_type type) {
40
+ return type == TF_TYPE_INT64 || type == TF_TYPE_FLOAT64;
41
+ }
42
+
43
+ static double anomaly_get_numeric(const tf_batch *b, size_t r, int ci) {
26
44
  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;
45
+ return tf_batch_get_float64(b, r, ci);
46
+ }
47
+
48
+ static void anomaly_set_col_error(const char *column, const char *suffix) {
49
+ char msg[512];
50
+ snprintf(msg, sizeof(msg), "anomaly: column '%s' %s", column ? column : "", suffix);
51
+ tf_set_last_error(msg);
52
+ }
53
+
54
+ static int anomaly_parse_missing_policy(const cJSON *args, anomaly_missing_policy *out) {
55
+ *out = ANOMALY_MISSING_ERROR;
56
+ const cJSON *j = cJSON_GetObjectItemCaseSensitive(args, "missing");
57
+ if (!j) return TF_OK;
58
+ if (!cJSON_IsString(j)) {
59
+ tf_set_last_error("anomaly: missing must be error, null, or ignore");
60
+ return TF_ERROR;
61
+ }
62
+ if (strcmp(j->valuestring, "error") == 0) *out = ANOMALY_MISSING_ERROR;
63
+ else if (strcmp(j->valuestring, "null") == 0) *out = ANOMALY_MISSING_NULL;
64
+ else if (strcmp(j->valuestring, "ignore") == 0) *out = ANOMALY_MISSING_IGNORE;
65
+ else {
66
+ tf_set_last_error("anomaly: missing must be error, null, or ignore");
67
+ return TF_ERROR;
68
+ }
69
+ return TF_OK;
70
+ }
71
+
72
+ static int anomaly_parse_type_policy(const cJSON *args, anomaly_type_policy *out) {
73
+ *out = ANOMALY_TYPE_FAIL;
74
+ const cJSON *j = cJSON_GetObjectItemCaseSensitive(args, "on_type_error");
75
+ if (!j) return TF_OK;
76
+ if (!cJSON_IsString(j)) {
77
+ tf_set_last_error("anomaly: on_type_error must be fail or null");
78
+ return TF_ERROR;
79
+ }
80
+ if (strcmp(j->valuestring, "fail") == 0) *out = ANOMALY_TYPE_FAIL;
81
+ else if (strcmp(j->valuestring, "null") == 0) *out = ANOMALY_TYPE_NULL;
82
+ else {
83
+ tf_set_last_error("anomaly: on_type_error must be fail or null");
84
+ return TF_ERROR;
85
+ }
86
+ return TF_OK;
87
+ }
88
+
89
+ static int anomaly_passthrough(tf_batch *in, tf_batch **out) {
90
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
91
+ if (!ob) return TF_ERROR;
92
+ if (tf_batch_clone_schema(ob, in) != TF_OK) {
93
+ tf_batch_free(ob);
94
+ return TF_ERROR;
95
+ }
96
+ for (size_t r = 0; r < in->n_rows; r++) {
97
+ if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
98
+ tf_batch_free(ob);
99
+ return TF_ERROR;
100
+ }
101
+ if (tf_batch_expose_row(ob, r) != TF_OK) {
102
+ tf_batch_free(ob);
103
+ return TF_ERROR;
104
+ }
105
+ }
106
+ *out = ob;
107
+ return TF_OK;
29
108
  }
30
109
 
31
110
  static int anomaly_process(tf_step *self, tf_batch *in, tf_batch **out,
@@ -34,45 +113,86 @@ static int anomaly_process(tf_step *self, tf_batch *in, tf_batch **out,
34
113
  anomaly_state *st = self->state;
35
114
  *out = NULL;
36
115
 
116
+ int ci = tf_batch_col_index(in, st->column);
117
+ int force_null = 0;
118
+ if (ci < 0) {
119
+ if (st->missing == ANOMALY_MISSING_ERROR) {
120
+ anomaly_set_col_error(st->column, "not found");
121
+ return TF_ERROR;
122
+ }
123
+ if (st->missing == ANOMALY_MISSING_IGNORE) return anomaly_passthrough(in, out);
124
+ force_null = 1;
125
+ } else if (!anomaly_is_numeric_type(in->col_types[ci])) {
126
+ if (st->on_type_error == ANOMALY_TYPE_FAIL) {
127
+ anomaly_set_col_error(st->column, "must be numeric");
128
+ return TF_ERROR;
129
+ }
130
+ force_null = 1;
131
+ }
132
+
133
+ const char *extra_names[1] = {st->result};
134
+ tf_type extra_types[1] = {TF_TYPE_INT64};
37
135
  tf_batch *ob = tf_batch_create(in->n_cols + 1, in->n_rows);
38
136
  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);
137
+ if (tf_batch_clone_with_extra_cols(ob, in, extra_names, extra_types, 1) != TF_OK) {
138
+ tf_batch_free(ob);
139
+ return TF_ERROR;
140
+ }
44
141
 
45
142
  for (size_t r = 0; r < in->n_rows; r++) {
46
- tf_batch_copy_row(ob, r, in, r);
143
+ if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
144
+ tf_batch_free(ob);
145
+ return TF_ERROR;
146
+ }
147
+
148
+ if (force_null) {
149
+ if (tf_batch_set_null(ob, r, in->n_cols) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
150
+ if (tf_batch_expose_row(ob, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
151
+ continue;
152
+ }
47
153
 
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;
154
+ if (tf_batch_is_null(in, r, ci)) {
155
+ if (tf_batch_set_int64(ob, r, in->n_cols, 0) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
156
+ if (tf_batch_expose_row(ob, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
51
157
  continue;
52
158
  }
53
159
 
54
- double val = get_numeric(in, r, ci);
160
+ double val = anomaly_get_numeric(in, r, ci);
55
161
 
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;
162
+ if (st->count == SIZE_MAX) {
163
+ tf_batch_free(ob);
164
+ return TF_ERROR;
165
+ }
166
+ size_t next_count = st->count + 1;
167
+ double next_mean = st->mean;
168
+ double next_m2 = st->m2;
169
+ double delta = val - next_mean;
170
+ next_mean += delta / (double)next_count;
171
+ double delta2 = val - next_mean;
172
+ next_m2 += delta * delta2;
62
173
 
63
174
  /* Compute z-score (need at least 2 data points for variance) */
64
175
  int is_anomaly = 0;
65
- if (st->count >= 2) {
66
- double var = st->m2 / (double)(st->count - 1);
176
+ if (next_count >= 2) {
177
+ double var = next_m2 / (double)(next_count - 1);
67
178
  double std = sqrt(var);
68
179
  if (std > 0) {
69
- double z = fabs((val - st->mean) / std);
180
+ double z = fabs((val - next_mean) / std);
70
181
  is_anomaly = z > st->threshold ? 1 : 0;
71
182
  }
72
183
  }
73
184
 
74
- tf_batch_set_int64(ob, r, in->n_cols, is_anomaly);
75
- ob->n_rows = r + 1;
185
+ if (tf_batch_set_int64(ob, r, in->n_cols, is_anomaly) != TF_OK) {
186
+ tf_batch_free(ob);
187
+ return TF_ERROR;
188
+ }
189
+ if (tf_batch_expose_row(ob, r) != TF_OK) {
190
+ tf_batch_free(ob);
191
+ return TF_ERROR;
192
+ }
193
+ st->count = next_count;
194
+ st->mean = next_mean;
195
+ st->m2 = next_m2;
76
196
  }
77
197
 
78
198
  *out = ob;
@@ -92,25 +212,40 @@ static void anomaly_destroy(tf_step *self) {
92
212
  tf_step *tf_anomaly_create(const cJSON *args) {
93
213
  if (!args) return NULL;
94
214
  cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
95
- if (!cJSON_IsString(col_j)) return NULL;
215
+ if (!cJSON_IsString(col_j) || !col_j->valuestring[0]) {
216
+ tf_set_last_error("anomaly: column is required");
217
+ return NULL;
218
+ }
96
219
 
97
- anomaly_state *st = calloc(1, sizeof(anomaly_state));
220
+ anomaly_state *st = tf_callocarray_checked(1, sizeof(anomaly_state));
98
221
  if (!st) return NULL;
99
- st->column = strdup(col_j->valuestring);
222
+ st->column = tf_strdup_checked(col_j->valuestring);
223
+ if (!st->column) { free(st); return NULL; }
100
224
 
101
225
  cJSON *thresh_j = cJSON_GetObjectItemCaseSensitive(args, "threshold");
102
226
  st->threshold = cJSON_IsNumber(thresh_j) ? thresh_j->valuedouble : 3.0;
227
+ if (!isfinite(st->threshold) || st->threshold < 0.0) {
228
+ tf_set_last_error("anomaly: threshold must be a non-negative finite number");
229
+ free(st->column);
230
+ free(st);
231
+ return NULL;
232
+ }
233
+ if (anomaly_parse_missing_policy(args, &st->missing) != TF_OK ||
234
+ anomaly_parse_type_policy(args, &st->on_type_error) != TF_OK) {
235
+ free(st->column);
236
+ free(st);
237
+ return NULL;
238
+ }
103
239
 
104
240
  cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
105
241
  if (cJSON_IsString(res_j)) {
106
- st->result = strdup(res_j->valuestring);
242
+ st->result = tf_strdup_checked(res_j->valuestring);
107
243
  } else {
108
- char buf[256];
109
- snprintf(buf, sizeof(buf), "%s_anomaly", st->column);
110
- st->result = strdup(buf);
244
+ st->result = tf_string_append_suffix_checked(st->column, "_anomaly");
111
245
  }
246
+ if (!st->result) { free(st->column); free(st); return NULL; }
112
247
 
113
- tf_step *step = malloc(sizeof(tf_step));
248
+ tf_step *step = tf_callocarray_checked(1, sizeof(tf_step));
114
249
  if (!step) { free(st->column); free(st->result); free(st); return NULL; }
115
250
  step->process = anomaly_process;
116
251
  step->flush = anomaly_flush;