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
@@ -24,20 +24,38 @@ typedef enum {
24
24
  TRUNC_SECOND,
25
25
  } trunc_level;
26
26
 
27
+ typedef enum {
28
+ DATE_TRUNC_MISSING_ERROR,
29
+ DATE_TRUNC_MISSING_NULL,
30
+ DATE_TRUNC_MISSING_IGNORE,
31
+ } date_trunc_missing_policy;
32
+
33
+ typedef enum {
34
+ DATE_TRUNC_TYPE_FAIL,
35
+ DATE_TRUNC_TYPE_NULL,
36
+ } date_trunc_type_policy;
37
+
27
38
  typedef struct {
28
39
  char *column;
29
40
  char *result;
30
41
  trunc_level level;
42
+ date_trunc_missing_policy missing;
43
+ date_trunc_type_policy on_type_error;
31
44
  } date_trunc_state;
32
45
 
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;
46
+ static int parse_level(const char *s, trunc_level *out) {
47
+ if (!s) return TF_ERROR;
48
+ if (strcmp(s, "year") == 0) { *out = TRUNC_YEAR; return TF_OK; }
49
+ if (strcmp(s, "month") == 0) { *out = TRUNC_MONTH; return TF_OK; }
50
+ if (strcmp(s, "day") == 0) { *out = TRUNC_DAY; return TF_OK; }
51
+ if (strcmp(s, "hour") == 0) { *out = TRUNC_HOUR; return TF_OK; }
52
+ if (strcmp(s, "minute") == 0) { *out = TRUNC_MINUTE; return TF_OK; }
53
+ if (strcmp(s, "second") == 0) { *out = TRUNC_SECOND; return TF_OK; }
54
+ return TF_ERROR;
55
+ }
56
+
57
+ static int date_trunc_is_temporal_type(tf_type type) {
58
+ return type == TF_TYPE_STRING || type == TF_TYPE_DATE || type == TF_TYPE_TIMESTAMP;
41
59
  }
42
60
 
43
61
  /* Truncate a date (days since epoch) to the given level. */
@@ -71,71 +89,201 @@ static int parse_date_string(const char *s, int *y, int *m, int *d) {
71
89
  return sscanf(s, "%d-%d-%d", y, m, d) == 3;
72
90
  }
73
91
 
92
+ static int date_trunc_set_error(tf_side_channels *side, const char *msg) {
93
+ return tf_side_write_error(side, msg);
94
+ }
95
+
96
+ static int date_trunc_set_col_error(tf_side_channels *side, const char *column, const char *suffix) {
97
+ char msg[512];
98
+ snprintf(msg, sizeof(msg), "date-trunc: column '%s' %s", column ? column : "", suffix);
99
+ return date_trunc_set_error(side, msg);
100
+ }
101
+
102
+ static int date_trunc_parse_missing_policy(const cJSON *args, date_trunc_missing_policy *out) {
103
+ *out = DATE_TRUNC_MISSING_ERROR;
104
+ const cJSON *j = cJSON_GetObjectItemCaseSensitive(args, "missing");
105
+ if (!j) return TF_OK;
106
+ if (!cJSON_IsString(j)) {
107
+ tf_set_last_error("date-trunc: missing must be error, null, or ignore");
108
+ return TF_ERROR;
109
+ }
110
+ if (strcmp(j->valuestring, "error") == 0) *out = DATE_TRUNC_MISSING_ERROR;
111
+ else if (strcmp(j->valuestring, "null") == 0) *out = DATE_TRUNC_MISSING_NULL;
112
+ else if (strcmp(j->valuestring, "ignore") == 0) *out = DATE_TRUNC_MISSING_IGNORE;
113
+ else {
114
+ tf_set_last_error("date-trunc: missing must be error, null, or ignore");
115
+ return TF_ERROR;
116
+ }
117
+ return TF_OK;
118
+ }
119
+
120
+ static int date_trunc_parse_type_policy(const cJSON *args, date_trunc_type_policy *out) {
121
+ *out = DATE_TRUNC_TYPE_FAIL;
122
+ const cJSON *j = cJSON_GetObjectItemCaseSensitive(args, "on_type_error");
123
+ if (!j) return TF_OK;
124
+ if (!cJSON_IsString(j)) {
125
+ tf_set_last_error("date-trunc: on_type_error must be fail or null");
126
+ return TF_ERROR;
127
+ }
128
+ if (strcmp(j->valuestring, "fail") == 0) *out = DATE_TRUNC_TYPE_FAIL;
129
+ else if (strcmp(j->valuestring, "null") == 0) *out = DATE_TRUNC_TYPE_NULL;
130
+ else {
131
+ tf_set_last_error("date-trunc: on_type_error must be fail or null");
132
+ return TF_ERROR;
133
+ }
134
+ return TF_OK;
135
+ }
136
+
137
+ static int date_trunc_passthrough(tf_batch *in, tf_batch **out) {
138
+ tf_batch *ob = tf_batch_create(in->n_cols, in->n_rows);
139
+ if (!ob) return TF_ERROR;
140
+ if (tf_batch_clone_schema(ob, in) != TF_OK) {
141
+ tf_batch_free(ob);
142
+ return TF_ERROR;
143
+ }
144
+ for (size_t r = 0; r < in->n_rows; r++) {
145
+ if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
146
+ tf_batch_free(ob);
147
+ return TF_ERROR;
148
+ }
149
+ if (tf_batch_expose_row(ob, r) != TF_OK) {
150
+ tf_batch_free(ob);
151
+ return TF_ERROR;
152
+ }
153
+ }
154
+ *out = ob;
155
+ return TF_OK;
156
+ }
157
+
158
+ static int date_trunc_null_output(date_trunc_state *st, tf_batch *in, int ci, tf_batch **out) {
159
+ int in_place = ci >= 0 && strcmp(st->result, st->column) == 0;
160
+ size_t result_col = in_place ? (size_t)ci : in->n_cols;
161
+ tf_batch *ob = NULL;
162
+ if (in_place) {
163
+ ob = tf_batch_create(in->n_cols, in->n_rows);
164
+ if (!ob) return TF_ERROR;
165
+ if (tf_batch_clone_schema(ob, in) != TF_OK) {
166
+ tf_batch_free(ob);
167
+ return TF_ERROR;
168
+ }
169
+ } else {
170
+ const char *extra_names[1] = {st->result};
171
+ tf_type extra_types[1] = {ci >= 0 ? in->col_types[ci] : TF_TYPE_STRING};
172
+ ob = tf_batch_create(in->n_cols + 1, in->n_rows);
173
+ if (!ob) return TF_ERROR;
174
+ if (tf_batch_clone_with_extra_cols(ob, in, extra_names, extra_types, 1) != TF_OK) {
175
+ tf_batch_free(ob);
176
+ return TF_ERROR;
177
+ }
178
+ }
179
+ for (size_t r = 0; r < in->n_rows; r++) {
180
+ if (tf_batch_copy_row(ob, r, in, r) != TF_OK ||
181
+ tf_batch_set_null(ob, r, result_col) != TF_OK) {
182
+ tf_batch_free(ob);
183
+ return TF_ERROR;
184
+ }
185
+ if (tf_batch_expose_row(ob, r) != TF_OK) {
186
+ tf_batch_free(ob);
187
+ return TF_ERROR;
188
+ }
189
+ }
190
+ *out = ob;
191
+ return TF_OK;
192
+ }
193
+
74
194
  static int date_trunc_process(tf_step *self, tf_batch *in, tf_batch **out,
75
195
  tf_side_channels *side) {
76
- (void)side;
77
196
  date_trunc_state *st = self->state;
78
197
  *out = NULL;
79
198
 
80
199
  int ci = tf_batch_col_index(in, st->column);
200
+ if (ci < 0) {
201
+ if (st->missing == DATE_TRUNC_MISSING_ERROR) {
202
+ if (date_trunc_set_col_error(side, st->column, "not found") != TF_OK) return TF_ERROR;
203
+ return TF_ERROR;
204
+ }
205
+ if (st->missing == DATE_TRUNC_MISSING_IGNORE) return date_trunc_passthrough(in, out);
206
+ return date_trunc_null_output(st, in, ci, out);
207
+ }
208
+ if (!date_trunc_is_temporal_type(in->col_types[ci])) {
209
+ if (st->on_type_error == DATE_TRUNC_TYPE_FAIL) {
210
+ if (date_trunc_set_col_error(side, st->column, "must be string, date, or timestamp") != TF_OK) return TF_ERROR;
211
+ return TF_ERROR;
212
+ }
213
+ return date_trunc_null_output(st, in, ci, out);
214
+ }
215
+
81
216
  int in_place = (strcmp(st->result, st->column) == 0);
82
217
 
83
218
  size_t out_cols = in_place ? in->n_cols : in->n_cols + 1;
84
219
  tf_batch *ob = tf_batch_create(out_cols, in->n_rows);
85
220
  if (!ob) return TF_ERROR;
86
221
 
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
222
  size_t result_col;
91
223
  if (in_place) {
224
+ if (tf_batch_clone_schema(ob, in) != TF_OK) {
225
+ tf_batch_free(ob);
226
+ return TF_ERROR;
227
+ }
92
228
  result_col = (size_t)ci;
93
229
  } else {
230
+ const char *extra_names[1] = {st->result};
231
+ tf_type extra_types[1] = {in->col_types[ci]};
232
+ if (tf_batch_clone_with_extra_cols(ob, in, extra_names, extra_types, 1) != TF_OK) {
233
+ tf_batch_free(ob);
234
+ return TF_ERROR;
235
+ }
94
236
  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
237
  }
99
238
 
100
239
  for (size_t r = 0; r < in->n_rows; r++) {
101
- tf_batch_copy_row(ob, r, in, r);
240
+ if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
241
+ tf_batch_free(ob);
242
+ return TF_ERROR;
243
+ }
102
244
 
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;
245
+ if (tf_batch_is_null(in, r, (size_t)ci)) {
246
+ if (!in_place && tf_batch_set_null(ob, r, result_col) != TF_OK) {
247
+ tf_batch_free(ob);
248
+ return TF_ERROR;
249
+ }
250
+ if (tf_batch_expose_row(ob, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
106
251
  continue;
107
252
  }
108
253
 
109
254
  switch (in->col_types[ci]) {
110
255
  case TF_TYPE_DATE: {
111
256
  int32_t d = tf_batch_get_date(in, r, ci);
112
- tf_batch_set_date(ob, r, result_col, trunc_date(d, st->level));
257
+ if (tf_batch_set_date(ob, r, result_col, trunc_date(d, st->level)) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
113
258
  break;
114
259
  }
115
260
  case TF_TYPE_TIMESTAMP: {
116
261
  int64_t ts = tf_batch_get_timestamp(in, r, ci);
117
- tf_batch_set_timestamp(ob, r, result_col, trunc_timestamp(ts, st->level));
262
+ if (tf_batch_set_timestamp(ob, r, result_col, trunc_timestamp(ts, st->level)) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
118
263
  break;
119
264
  }
120
265
  case TF_TYPE_STRING: {
121
266
  const char *s = tf_batch_get_string(in, r, ci);
122
267
  int y, m, d;
123
- if (parse_date_string(s, &y, &m, &d)) {
268
+ if (s && parse_date_string(s, &y, &m, &d)) {
124
269
  int32_t days = tf_date_from_ymd(y, m, d);
125
270
  int32_t trunc = trunc_date(days, st->level);
126
- char buf[16];
271
+ char buf[32];
127
272
  tf_date_format(trunc, buf, sizeof(buf));
128
- tf_batch_set_string(ob, r, result_col, buf);
273
+ if (tf_batch_set_string(ob, r, result_col, buf) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
129
274
  } else {
130
- tf_batch_set_string(ob, r, result_col, s);
275
+ if (tf_batch_set_string(ob, r, result_col, s ? s : "") != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
131
276
  }
132
277
  break;
133
278
  }
134
279
  default:
135
- if (!in_place) tf_batch_set_null(ob, r, result_col);
136
- break;
280
+ tf_batch_free(ob);
281
+ return TF_ERROR;
282
+ }
283
+ if (tf_batch_expose_row(ob, r) != TF_OK) {
284
+ tf_batch_free(ob);
285
+ return TF_ERROR;
137
286
  }
138
- ob->n_rows = r + 1;
139
287
  }
140
288
 
141
289
  *out = ob;
@@ -146,9 +294,15 @@ static int date_trunc_flush(tf_step *self, tf_batch **out, tf_side_channels *sid
146
294
  (void)self; (void)side; *out = NULL; return TF_OK;
147
295
  }
148
296
 
297
+ static void date_trunc_state_free(date_trunc_state *st) {
298
+ if (!st) return;
299
+ free(st->column);
300
+ free(st->result);
301
+ free(st);
302
+ }
303
+
149
304
  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); }
305
+ if (self) date_trunc_state_free(self->state);
152
306
  free(self);
153
307
  }
154
308
 
@@ -156,23 +310,43 @@ tf_step *tf_date_trunc_create(const cJSON *args) {
156
310
  if (!args) return NULL;
157
311
  cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
158
312
  cJSON *trunc_j = cJSON_GetObjectItemCaseSensitive(args, "trunc");
159
- if (!cJSON_IsString(col_j) || !cJSON_IsString(trunc_j)) return NULL;
313
+ if (!cJSON_IsString(col_j) || !col_j->valuestring[0] || !cJSON_IsString(trunc_j) || !trunc_j->valuestring[0]) {
314
+ tf_set_last_error("date-trunc: column and trunc are required");
315
+ return NULL;
316
+ }
160
317
 
161
318
  date_trunc_state *st = calloc(1, sizeof(date_trunc_state));
162
319
  if (!st) return NULL;
163
320
  st->column = strdup(col_j->valuestring);
164
- st->level = parse_level(trunc_j->valuestring);
321
+ if (!st->column) { free(st); return NULL; }
322
+ if (parse_level(trunc_j->valuestring, &st->level) != TF_OK) {
323
+ tf_set_last_error("date-trunc: trunc must be year, month, day, hour, minute, or second");
324
+ date_trunc_state_free(st);
325
+ return NULL;
326
+ }
165
327
 
166
328
  cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
167
329
  if (cJSON_IsString(res_j)) {
330
+ if (!res_j->valuestring[0]) {
331
+ tf_set_last_error("date-trunc: result cannot be empty");
332
+ date_trunc_state_free(st);
333
+ return NULL;
334
+ }
168
335
  st->result = strdup(res_j->valuestring);
169
336
  } else {
170
337
  /* Default: overwrite in place */
171
338
  st->result = strdup(st->column);
172
339
  }
340
+ if (!st->result) { date_trunc_state_free(st); return NULL; }
341
+
342
+ if (date_trunc_parse_missing_policy(args, &st->missing) != TF_OK ||
343
+ date_trunc_parse_type_policy(args, &st->on_type_error) != TF_OK) {
344
+ date_trunc_state_free(st);
345
+ return NULL;
346
+ }
173
347
 
174
- tf_step *step = malloc(sizeof(tf_step));
175
- if (!step) { free(st->column); free(st->result); free(st); return NULL; }
348
+ tf_step *step = calloc(1, sizeof(tf_step));
349
+ if (!step) { date_trunc_state_free(st); return NULL; }
176
350
  step->process = date_trunc_process;
177
351
  step->flush = date_trunc_flush;
178
352
  step->destroy = date_trunc_destroy;