tranfi 0.0.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.
- package/LICENSE +177 -21
- package/NOTICE +8 -0
- package/README.md +627 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-BIAIKnrp.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 +121 -0
- package/csrc/arena.c +93 -0
- package/csrc/batch.c +976 -0
- package/csrc/buffer.c +154 -0
- package/csrc/cJSON.c +3386 -0
- package/csrc/cJSON.h +316 -0
- package/csrc/codec_csv.c +1951 -0
- package/csrc/codec_jsonl.c +1086 -0
- package/csrc/codec_table.c +248 -0
- package/csrc/codec_text.c +447 -0
- package/csrc/compiler.c +130 -0
- package/csrc/config.h +21 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +5417 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1553 -0
- package/csrc/expr.h +58 -0
- package/csrc/internal.h +539 -0
- package/csrc/ir.c +166 -0
- package/csrc/ir.h +208 -0
- package/csrc/ir_schema.c +75 -0
- package/csrc/ir_serialize.c +166 -0
- package/csrc/ir_sql.c +1822 -0
- package/csrc/ir_validate.c +576 -0
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +1241 -0
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +283 -0
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +255 -0
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +248 -0
- package/csrc/op_cast.c +523 -0
- package/csrc/op_clip.c +99 -0
- package/csrc/op_date_trunc.c +355 -0
- package/csrc/op_datetime.c +394 -0
- package/csrc/op_derive.c +216 -0
- package/csrc/op_diff.c +250 -0
- package/csrc/op_ewma.c +222 -0
- package/csrc/op_explode.c +206 -0
- package/csrc/op_fill_down.c +235 -0
- package/csrc/op_fill_null.c +268 -0
- package/csrc/op_filter.c +181 -0
- package/csrc/op_frequency.c +721 -0
- package/csrc/op_grep.c +181 -0
- package/csrc/op_group_agg.c +1956 -0
- package/csrc/op_hash.c +159 -0
- package/csrc/op_head.c +84 -0
- package/csrc/op_interpolate.c +445 -0
- package/csrc/op_join.c +2902 -0
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +419 -0
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +242 -0
- package/csrc/op_normalize.c +510 -0
- package/csrc/op_onehot.c +457 -0
- package/csrc/op_pivot.c +1754 -0
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +3044 -0
- package/csrc/op_rename.c +129 -0
- package/csrc/op_replace.c +354 -0
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +158 -0
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +340 -0
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +95 -0
- package/csrc/op_sort.c +819 -0
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +151 -0
- package/csrc/op_split_data.c +119 -0
- package/csrc/op_stack.c +271 -0
- package/csrc/op_stats.c +875 -0
- package/csrc/op_step.c +333 -0
- package/csrc/op_tail.c +105 -0
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +357 -0
- package/csrc/op_trim.c +138 -0
- package/csrc/op_unique.c +1343 -0
- package/csrc/op_unpivot.c +193 -0
- package/csrc/op_validate.c +648 -0
- package/csrc/op_window.c +591 -0
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +1088 -0
- package/csrc/recipes.c +104 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +506 -0
- package/csrc/report.h +22 -0
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +291 -0
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +218 -0
- package/napi_api.c +534 -0
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +64 -59
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +190 -0
- package/src/engines/duckdb.js +142 -0
- package/src/index.js +925 -0
- package/src/memory_policy.js +411 -0
- package/src/native.js +18 -0
- package/src/pipeline.js +709 -0
- package/src/recipe_json.js +80 -0
- package/src/server.js +279 -0
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +21 -0
- package/wasm/index.js +732 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- 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,355 @@
|
|
|
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 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
|
+
|
|
38
|
+
typedef struct {
|
|
39
|
+
char *column;
|
|
40
|
+
char *result;
|
|
41
|
+
trunc_level level;
|
|
42
|
+
date_trunc_missing_policy missing;
|
|
43
|
+
date_trunc_type_policy on_type_error;
|
|
44
|
+
} date_trunc_state;
|
|
45
|
+
|
|
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;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Truncate a date (days since epoch) to the given level. */
|
|
62
|
+
static int32_t trunc_date(int32_t days, trunc_level level) {
|
|
63
|
+
int y, m, d;
|
|
64
|
+
tf_date_to_ymd(days, &y, &m, &d);
|
|
65
|
+
switch (level) {
|
|
66
|
+
case TRUNC_YEAR: return tf_date_from_ymd(y, 1, 1);
|
|
67
|
+
case TRUNC_MONTH: return tf_date_from_ymd(y, m, 1);
|
|
68
|
+
default: return days; /* day or finer — date is already at day granularity */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Truncate a timestamp (microseconds since epoch) to the given level. */
|
|
73
|
+
static int64_t trunc_timestamp(int64_t us, trunc_level level) {
|
|
74
|
+
int y, mo, d, h, mi, s, frac;
|
|
75
|
+
tf_timestamp_to_parts(us, &y, &mo, &d, &h, &mi, &s, &frac);
|
|
76
|
+
switch (level) {
|
|
77
|
+
case TRUNC_YEAR: return tf_timestamp_from_parts(y, 1, 1, 0, 0, 0, 0);
|
|
78
|
+
case TRUNC_MONTH: return tf_timestamp_from_parts(y, mo, 1, 0, 0, 0, 0);
|
|
79
|
+
case TRUNC_DAY: return tf_timestamp_from_parts(y, mo, d, 0, 0, 0, 0);
|
|
80
|
+
case TRUNC_HOUR: return tf_timestamp_from_parts(y, mo, d, h, 0, 0, 0);
|
|
81
|
+
case TRUNC_MINUTE: return tf_timestamp_from_parts(y, mo, d, h, mi, 0, 0);
|
|
82
|
+
case TRUNC_SECOND: return tf_timestamp_from_parts(y, mo, d, h, mi, s, 0);
|
|
83
|
+
}
|
|
84
|
+
return us;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Try to parse a date string. Returns 1 on success. */
|
|
88
|
+
static int parse_date_string(const char *s, int *y, int *m, int *d) {
|
|
89
|
+
return sscanf(s, "%d-%d-%d", y, m, d) == 3;
|
|
90
|
+
}
|
|
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
|
+
|
|
194
|
+
static int date_trunc_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
195
|
+
tf_side_channels *side) {
|
|
196
|
+
date_trunc_state *st = self->state;
|
|
197
|
+
*out = NULL;
|
|
198
|
+
|
|
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
|
+
|
|
216
|
+
int in_place = (strcmp(st->result, st->column) == 0);
|
|
217
|
+
|
|
218
|
+
size_t out_cols = in_place ? in->n_cols : in->n_cols + 1;
|
|
219
|
+
tf_batch *ob = tf_batch_create(out_cols, in->n_rows);
|
|
220
|
+
if (!ob) return TF_ERROR;
|
|
221
|
+
|
|
222
|
+
size_t result_col;
|
|
223
|
+
if (in_place) {
|
|
224
|
+
if (tf_batch_clone_schema(ob, in) != TF_OK) {
|
|
225
|
+
tf_batch_free(ob);
|
|
226
|
+
return TF_ERROR;
|
|
227
|
+
}
|
|
228
|
+
result_col = (size_t)ci;
|
|
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
|
+
}
|
|
236
|
+
result_col = in->n_cols;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
240
|
+
if (tf_batch_copy_row(ob, r, in, r) != TF_OK) {
|
|
241
|
+
tf_batch_free(ob);
|
|
242
|
+
return TF_ERROR;
|
|
243
|
+
}
|
|
244
|
+
|
|
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; }
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
switch (in->col_types[ci]) {
|
|
255
|
+
case TF_TYPE_DATE: {
|
|
256
|
+
int32_t d = tf_batch_get_date(in, r, ci);
|
|
257
|
+
if (tf_batch_set_date(ob, r, result_col, trunc_date(d, st->level)) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case TF_TYPE_TIMESTAMP: {
|
|
261
|
+
int64_t ts = tf_batch_get_timestamp(in, r, ci);
|
|
262
|
+
if (tf_batch_set_timestamp(ob, r, result_col, trunc_timestamp(ts, st->level)) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case TF_TYPE_STRING: {
|
|
266
|
+
const char *s = tf_batch_get_string(in, r, ci);
|
|
267
|
+
int y, m, d;
|
|
268
|
+
if (s && parse_date_string(s, &y, &m, &d)) {
|
|
269
|
+
int32_t days = tf_date_from_ymd(y, m, d);
|
|
270
|
+
int32_t trunc = trunc_date(days, st->level);
|
|
271
|
+
char buf[32];
|
|
272
|
+
tf_date_format(trunc, buf, sizeof(buf));
|
|
273
|
+
if (tf_batch_set_string(ob, r, result_col, buf) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
274
|
+
} else {
|
|
275
|
+
if (tf_batch_set_string(ob, r, result_col, s ? s : "") != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
default:
|
|
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;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
*out = ob;
|
|
290
|
+
return TF_OK;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static int date_trunc_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
294
|
+
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
295
|
+
}
|
|
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
|
+
|
|
304
|
+
static void date_trunc_destroy(tf_step *self) {
|
|
305
|
+
if (self) date_trunc_state_free(self->state);
|
|
306
|
+
free(self);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
tf_step *tf_date_trunc_create(const cJSON *args) {
|
|
310
|
+
if (!args) return NULL;
|
|
311
|
+
cJSON *col_j = cJSON_GetObjectItemCaseSensitive(args, "column");
|
|
312
|
+
cJSON *trunc_j = cJSON_GetObjectItemCaseSensitive(args, "trunc");
|
|
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
|
+
}
|
|
317
|
+
|
|
318
|
+
date_trunc_state *st = calloc(1, sizeof(date_trunc_state));
|
|
319
|
+
if (!st) return NULL;
|
|
320
|
+
st->column = strdup(col_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
|
+
}
|
|
327
|
+
|
|
328
|
+
cJSON *res_j = cJSON_GetObjectItemCaseSensitive(args, "result");
|
|
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
|
+
}
|
|
335
|
+
st->result = strdup(res_j->valuestring);
|
|
336
|
+
} else {
|
|
337
|
+
/* Default: overwrite in place */
|
|
338
|
+
st->result = strdup(st->column);
|
|
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
|
+
}
|
|
347
|
+
|
|
348
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
349
|
+
if (!step) { date_trunc_state_free(st); return NULL; }
|
|
350
|
+
step->process = date_trunc_process;
|
|
351
|
+
step->flush = date_trunc_flush;
|
|
352
|
+
step->destroy = date_trunc_destroy;
|
|
353
|
+
step->state = st;
|
|
354
|
+
return step;
|
|
355
|
+
}
|