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.
- package/LICENSE +177 -0
- package/NOTICE +8 -0
- package/README.md +272 -40
- package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
- package/app/index.html +1 -1
- package/binding.gyp +55 -3
- package/csrc/arena.c +7 -5
- package/csrc/batch.c +818 -71
- package/csrc/buffer.c +84 -8
- package/csrc/cJSON.c +262 -19
- package/csrc/cJSON.h +17 -1
- package/csrc/codec_csv.c +1074 -181
- package/csrc/codec_jsonl.c +830 -118
- package/csrc/codec_table.c +108 -78
- package/csrc/codec_text.c +286 -68
- package/csrc/compiler.c +31 -3
- package/csrc/config.h +21 -0
- package/csrc/dsl.c +4722 -485
- package/csrc/expr.c +363 -55
- package/csrc/expr.h +2 -0
- package/csrc/internal.h +316 -27
- package/csrc/ir.c +65 -18
- package/csrc/ir.h +41 -0
- package/csrc/ir_schema.c +20 -5
- package/csrc/ir_serialize.c +68 -6
- package/csrc/ir_sql.c +796 -185
- package/csrc/ir_validate.c +462 -6
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +879 -30
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +171 -21
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +167 -32
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +168 -29
- package/csrc/op_cast.c +383 -55
- package/csrc/op_clip.c +30 -19
- package/csrc/op_date_trunc.c +208 -34
- package/csrc/op_datetime.c +259 -77
- package/csrc/op_derive.c +65 -97
- package/csrc/op_diff.c +146 -30
- package/csrc/op_ewma.c +149 -30
- package/csrc/op_explode.c +124 -26
- package/csrc/op_fill_down.c +125 -53
- package/csrc/op_fill_null.c +176 -31
- package/csrc/op_filter.c +89 -40
- package/csrc/op_frequency.c +571 -43
- package/csrc/op_grep.c +36 -18
- package/csrc/op_group_agg.c +1790 -119
- package/csrc/op_hash.c +48 -15
- package/csrc/op_head.c +21 -86
- package/csrc/op_interpolate.c +268 -62
- package/csrc/op_join.c +2700 -182
- 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 +328 -53
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +141 -89
- package/csrc/op_normalize.c +363 -79
- package/csrc/op_onehot.c +345 -73
- package/csrc/op_pivot.c +1546 -162
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +2062 -166
- package/csrc/op_rename.c +41 -50
- package/csrc/op_replace.c +270 -118
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +80 -23
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +265 -65
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +30 -87
- package/csrc/op_sort.c +670 -124
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +65 -28
- package/csrc/op_split_data.c +41 -9
- package/csrc/op_stack.c +178 -222
- package/csrc/op_stats.c +206 -110
- package/csrc/op_step.c +217 -55
- package/csrc/op_tail.c +21 -12
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +260 -53
- package/csrc/op_trim.c +48 -19
- package/csrc/op_unique.c +1193 -150
- package/csrc/op_unpivot.c +100 -66
- package/csrc/op_validate.c +601 -24
- package/csrc/op_window.c +492 -51
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +872 -99
- package/csrc/recipes.c +3 -1
- package/csrc/report.c +73 -30
- 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 +169 -1
- 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 +70 -9
- package/napi_api.c +219 -11
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +27 -11
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +8 -11
- package/src/engines/duckdb.js +45 -12
- package/src/index.js +661 -42
- package/src/memory_policy.js +411 -0
- package/src/native.js +1 -5
- package/src/pipeline.js +454 -31
- package/src/recipe_json.js +80 -0
- package/src/server.js +10 -8
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +6 -4
- package/wasm/index.js +498 -10
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/csrc/plan.c +0 -206
package/csrc/codec_jsonl.c
CHANGED
|
@@ -15,15 +15,67 @@
|
|
|
15
15
|
#include <stdlib.h>
|
|
16
16
|
#include <string.h>
|
|
17
17
|
#include <stdio.h>
|
|
18
|
+
#include <math.h>
|
|
18
19
|
|
|
19
20
|
#define DEFAULT_BATCH_SIZE 1024
|
|
21
|
+
#define DEFAULT_MAX_ERROR_BYTES 4096
|
|
22
|
+
#define DEFAULT_MAX_RECORD_BYTES (64 * 1024 * 1024)
|
|
20
23
|
|
|
21
24
|
/* ================================================================
|
|
22
25
|
* JSONL Decoder
|
|
23
26
|
* ================================================================ */
|
|
24
27
|
|
|
28
|
+
typedef enum {
|
|
29
|
+
JSONL_ERROR_SKIP = 0,
|
|
30
|
+
JSONL_ERROR_FAIL,
|
|
31
|
+
JSONL_ERROR_WARN,
|
|
32
|
+
JSONL_ERROR_QUARANTINE,
|
|
33
|
+
} jsonl_error_action;
|
|
34
|
+
|
|
35
|
+
static const char *jsonl_error_action_name(jsonl_error_action action) {
|
|
36
|
+
switch (action) {
|
|
37
|
+
case JSONL_ERROR_FAIL: return "fail";
|
|
38
|
+
case JSONL_ERROR_WARN: return "warn";
|
|
39
|
+
case JSONL_ERROR_QUARANTINE: return "quarantine";
|
|
40
|
+
case JSONL_ERROR_SKIP:
|
|
41
|
+
default: return "skip";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static int parse_jsonl_error_action(const char *s, jsonl_error_action *out) {
|
|
46
|
+
if (!s || strcmp(s, "skip") == 0 || strcmp(s, "ignore") == 0) {
|
|
47
|
+
*out = JSONL_ERROR_SKIP;
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
if (strcmp(s, "fail") == 0 || strcmp(s, "error") == 0 || strcmp(s, "stop") == 0) {
|
|
51
|
+
*out = JSONL_ERROR_FAIL;
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
if (strcmp(s, "warn") == 0 || strcmp(s, "warning") == 0) {
|
|
55
|
+
*out = JSONL_ERROR_WARN;
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
if (strcmp(s, "quarantine") == 0 || strcmp(s, "dead-letter") == 0 || strcmp(s, "dead_letter") == 0) {
|
|
59
|
+
*out = JSONL_ERROR_QUARANTINE;
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
typedef struct {
|
|
66
|
+
const char *name;
|
|
67
|
+
size_t column;
|
|
68
|
+
} jsonl_field;
|
|
69
|
+
|
|
25
70
|
typedef struct {
|
|
26
71
|
size_t batch_size;
|
|
72
|
+
jsonl_error_action on_error;
|
|
73
|
+
size_t max_error_bytes;
|
|
74
|
+
size_t max_record_bytes;
|
|
75
|
+
size_t line_number;
|
|
76
|
+
size_t byte_offset;
|
|
77
|
+
size_t bad_records;
|
|
78
|
+
int pending_lf; /* A CR at the end of the previous input chunk. */
|
|
27
79
|
|
|
28
80
|
/* Line accumulator */
|
|
29
81
|
tf_buffer line_buf;
|
|
@@ -33,6 +85,9 @@ typedef struct {
|
|
|
33
85
|
tf_type *col_types;
|
|
34
86
|
size_t n_cols;
|
|
35
87
|
int schema_ready;
|
|
88
|
+
jsonl_field *fields; /* Sorted names, with original order breaking duplicate ties. */
|
|
89
|
+
size_t *aliases;
|
|
90
|
+
cJSON **values; /* Borrowed from the current parsed record only. */
|
|
36
91
|
|
|
37
92
|
/* Current batch */
|
|
38
93
|
tf_batch *batch;
|
|
@@ -43,12 +98,13 @@ static tf_type json_to_type(const cJSON *val) {
|
|
|
43
98
|
if (cJSON_IsNumber(val)) {
|
|
44
99
|
/* Check if integer */
|
|
45
100
|
double d = val->valuedouble;
|
|
46
|
-
if (d
|
|
101
|
+
if (d >= -9007199254740992.0 && d <= 9007199254740992.0 && d == (double)(int64_t)d)
|
|
47
102
|
return TF_TYPE_INT64;
|
|
48
103
|
return TF_TYPE_FLOAT64;
|
|
49
104
|
}
|
|
50
105
|
if (cJSON_IsString(val)) return TF_TYPE_STRING;
|
|
51
106
|
if (cJSON_IsBool(val)) return TF_TYPE_BOOL;
|
|
107
|
+
if (cJSON_IsArray(val) || cJSON_IsObject(val)) return TF_TYPE_STRING;
|
|
52
108
|
return TF_TYPE_NULL;
|
|
53
109
|
}
|
|
54
110
|
|
|
@@ -61,117 +117,601 @@ static tf_type widen_type(tf_type current, tf_type incoming) {
|
|
|
61
117
|
return TF_TYPE_STRING;
|
|
62
118
|
}
|
|
63
119
|
|
|
120
|
+
static size_t jsonl_type_size(tf_type t) {
|
|
121
|
+
switch (t) {
|
|
122
|
+
case TF_TYPE_BOOL: return sizeof(uint8_t);
|
|
123
|
+
case TF_TYPE_INT64: return sizeof(int64_t);
|
|
124
|
+
case TF_TYPE_FLOAT64: return sizeof(double);
|
|
125
|
+
case TF_TYPE_STRING: return sizeof(char *);
|
|
126
|
+
case TF_TYPE_DATE: return sizeof(int32_t);
|
|
127
|
+
case TF_TYPE_TIMESTAMP: return sizeof(int64_t);
|
|
128
|
+
default: return 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static void jsonl_free_schema_arrays(char **col_names, tf_type *col_types, size_t n_names) {
|
|
133
|
+
if (col_names) {
|
|
134
|
+
for (size_t i = 0; i < n_names; i++) free(col_names[i]);
|
|
135
|
+
}
|
|
136
|
+
free(col_names);
|
|
137
|
+
free(col_types);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static int jsonl_convert_batch_column(tf_batch *batch, size_t col, tf_type new_type) {
|
|
141
|
+
if (!batch || col >= batch->n_cols || batch->col_types[col] == new_type) return TF_OK;
|
|
142
|
+
|
|
143
|
+
tf_type old_type = batch->col_types[col];
|
|
144
|
+
size_t sz = jsonl_type_size(new_type);
|
|
145
|
+
void *new_col = NULL;
|
|
146
|
+
uint8_t *new_nulls = NULL;
|
|
147
|
+
if (sz == 0) {
|
|
148
|
+
batch->col_types[col] = new_type;
|
|
149
|
+
batch->columns[col] = NULL;
|
|
150
|
+
batch->nulls[col] = NULL;
|
|
151
|
+
return TF_OK;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
size_t column_bytes = 0;
|
|
155
|
+
if (tf_size_mul(sz, batch->capacity, &column_bytes) != TF_OK) return TF_ERROR;
|
|
156
|
+
new_col = tf_arena_alloc(batch->arena, column_bytes);
|
|
157
|
+
new_nulls = tf_arena_alloc(batch->arena, batch->capacity);
|
|
158
|
+
if (!new_col || !new_nulls) return TF_ERROR;
|
|
159
|
+
memset(new_nulls, 1, batch->capacity);
|
|
160
|
+
|
|
161
|
+
for (size_t r = 0; r < batch->n_rows; r++) {
|
|
162
|
+
int was_null = (!batch->nulls[col] || batch->nulls[col][r] != 0);
|
|
163
|
+
if (was_null) continue;
|
|
164
|
+
|
|
165
|
+
if (new_type == TF_TYPE_FLOAT64 && old_type == TF_TYPE_INT64) {
|
|
166
|
+
((double *)new_col)[r] = (double)((int64_t *)batch->columns[col])[r];
|
|
167
|
+
new_nulls[r] = 0;
|
|
168
|
+
} else if (new_type == TF_TYPE_FLOAT64 && old_type == TF_TYPE_FLOAT64) {
|
|
169
|
+
((double *)new_col)[r] = ((double *)batch->columns[col])[r];
|
|
170
|
+
new_nulls[r] = 0;
|
|
171
|
+
} else if (new_type == TF_TYPE_STRING) {
|
|
172
|
+
char numbuf[64];
|
|
173
|
+
const char *s = NULL;
|
|
174
|
+
switch (old_type) {
|
|
175
|
+
case TF_TYPE_BOOL:
|
|
176
|
+
s = ((uint8_t *)batch->columns[col])[r] ? "true" : "false";
|
|
177
|
+
break;
|
|
178
|
+
case TF_TYPE_INT64:
|
|
179
|
+
snprintf(numbuf, sizeof(numbuf), "%lld",
|
|
180
|
+
(long long)((int64_t *)batch->columns[col])[r]);
|
|
181
|
+
s = numbuf;
|
|
182
|
+
break;
|
|
183
|
+
case TF_TYPE_FLOAT64:
|
|
184
|
+
if (tf_format_float64(numbuf, sizeof(numbuf),
|
|
185
|
+
((double *)batch->columns[col])[r]) != TF_OK)
|
|
186
|
+
return TF_ERROR;
|
|
187
|
+
s = numbuf;
|
|
188
|
+
break;
|
|
189
|
+
case TF_TYPE_STRING:
|
|
190
|
+
s = ((char **)batch->columns[col])[r];
|
|
191
|
+
break;
|
|
192
|
+
default:
|
|
193
|
+
s = "";
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
size_t len = 0, bytes = 0;
|
|
197
|
+
const char *safe_s = s ? s : "";
|
|
198
|
+
if (tf_string_length_bounded(safe_s, TF_MAX_CELL_BYTES,
|
|
199
|
+
&len, "jsonl", "cell") != TF_OK ||
|
|
200
|
+
tf_size_add(len, 1, &bytes) != TF_OK)
|
|
201
|
+
return TF_ERROR;
|
|
202
|
+
char *copy = tf_arena_alloc(batch->arena, bytes);
|
|
203
|
+
if (!copy) return TF_ERROR;
|
|
204
|
+
memcpy(copy, safe_s, len);
|
|
205
|
+
copy[len] = '\0';
|
|
206
|
+
((char **)new_col)[r] = copy;
|
|
207
|
+
new_nulls[r] = 0;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
batch->col_types[col] = new_type;
|
|
212
|
+
batch->columns[col] = new_col;
|
|
213
|
+
batch->nulls[col] = new_nulls;
|
|
214
|
+
return TF_OK;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static int jsonl_widen_column(jsonl_decoder_state *st, size_t col, tf_type incoming) {
|
|
218
|
+
tf_type widened = widen_type(st->col_types[col], incoming);
|
|
219
|
+
if (widened == st->col_types[col]) return TF_OK;
|
|
220
|
+
if (jsonl_convert_batch_column(st->batch, col, widened) != TF_OK) return TF_ERROR;
|
|
221
|
+
st->col_types[col] = widened;
|
|
222
|
+
return TF_OK;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static int emit_jsonl_malformed(jsonl_decoder_state *st, const char *line, size_t len,
|
|
226
|
+
size_t line_no, const char *reason,
|
|
227
|
+
tf_side_channels *side) {
|
|
228
|
+
st->bad_records++;
|
|
229
|
+
if (!side || !side->errors) return TF_OK;
|
|
230
|
+
|
|
231
|
+
size_t keep = len;
|
|
232
|
+
int truncated = 0;
|
|
233
|
+
if (st->max_error_bytes > 0 && keep > st->max_error_bytes) {
|
|
234
|
+
keep = st->max_error_bytes;
|
|
235
|
+
truncated = 1;
|
|
236
|
+
}
|
|
237
|
+
char *raw = malloc(keep + 1);
|
|
238
|
+
if (!raw) return TF_ERROR;
|
|
239
|
+
memcpy(raw, line, keep);
|
|
240
|
+
raw[keep] = '\0';
|
|
241
|
+
|
|
242
|
+
cJSON *obj = cJSON_CreateObject();
|
|
243
|
+
if (!obj) { free(raw); return TF_ERROR; }
|
|
244
|
+
int rc = TF_ERROR;
|
|
245
|
+
if (tf_json_add_string(obj, "type", "jsonl_malformed") != TF_OK ||
|
|
246
|
+
tf_json_add_string(obj, "op", "codec.jsonl.decode") != TF_OK ||
|
|
247
|
+
tf_json_add_string(obj, "action", jsonl_error_action_name(st->on_error)) != TF_OK ||
|
|
248
|
+
tf_json_add_string(obj, "severity", st->on_error == JSONL_ERROR_WARN ? "warning" : "error") != TF_OK ||
|
|
249
|
+
tf_json_add_number(obj, "line", (double)line_no) != TF_OK ||
|
|
250
|
+
tf_json_add_string(obj, "message", reason ? reason : "invalid JSONL record") != TF_OK ||
|
|
251
|
+
tf_json_add_number(obj, "raw_bytes", (double)len) != TF_OK ||
|
|
252
|
+
tf_json_add_string(obj, "raw", raw) != TF_OK ||
|
|
253
|
+
(truncated && tf_json_add_bool(obj, "truncated", 1) != TF_OK)) {
|
|
254
|
+
goto done;
|
|
255
|
+
}
|
|
256
|
+
rc = tf_buffer_write_json_line(side->errors, obj);
|
|
257
|
+
done:
|
|
258
|
+
cJSON_Delete(obj);
|
|
259
|
+
free(raw);
|
|
260
|
+
return rc;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
static int handle_jsonl_malformed(jsonl_decoder_state *st, const char *line, size_t len,
|
|
264
|
+
size_t line_no, const char *reason,
|
|
265
|
+
tf_side_channels *side) {
|
|
266
|
+
if (st->on_error == JSONL_ERROR_SKIP) {
|
|
267
|
+
st->bad_records++;
|
|
268
|
+
return TF_OK;
|
|
269
|
+
}
|
|
270
|
+
if (emit_jsonl_malformed(st, line, len, line_no, reason, side) != TF_OK)
|
|
271
|
+
return TF_ERROR;
|
|
272
|
+
if (st->on_error == JSONL_ERROR_FAIL) {
|
|
273
|
+
char msg[256];
|
|
274
|
+
snprintf(msg, sizeof(msg), "jsonl decode failed at line %zu: %s",
|
|
275
|
+
line_no, reason ? reason : "invalid JSONL record");
|
|
276
|
+
tf_set_last_error(msg);
|
|
277
|
+
return TF_ERROR;
|
|
278
|
+
}
|
|
279
|
+
return TF_OK;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
static char *jsonl_record_preview(jsonl_decoder_state *st,
|
|
283
|
+
const uint8_t *prefix, size_t prefix_len,
|
|
284
|
+
const uint8_t *suffix, size_t suffix_len,
|
|
285
|
+
int *truncated_out) {
|
|
286
|
+
size_t total = prefix_len + suffix_len;
|
|
287
|
+
size_t keep = total;
|
|
288
|
+
int truncated = 0;
|
|
289
|
+
if (st->max_error_bytes > 0 && keep > st->max_error_bytes) {
|
|
290
|
+
keep = st->max_error_bytes;
|
|
291
|
+
truncated = 1;
|
|
292
|
+
}
|
|
293
|
+
char *raw = malloc(keep + 1);
|
|
294
|
+
if (!raw) return NULL;
|
|
295
|
+
size_t copied = 0;
|
|
296
|
+
size_t n = prefix_len < keep ? prefix_len : keep;
|
|
297
|
+
if (n > 0 && prefix) {
|
|
298
|
+
memcpy(raw, prefix, n);
|
|
299
|
+
copied += n;
|
|
300
|
+
}
|
|
301
|
+
if (copied < keep && suffix) {
|
|
302
|
+
size_t m = keep - copied;
|
|
303
|
+
if (m > suffix_len) m = suffix_len;
|
|
304
|
+
if (m > 0) {
|
|
305
|
+
memcpy(raw + copied, suffix, m);
|
|
306
|
+
copied += m;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
raw[copied] = '\0';
|
|
310
|
+
if (truncated_out) *truncated_out = truncated;
|
|
311
|
+
return raw;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
static int emit_jsonl_record_size_diagnostic(jsonl_decoder_state *st,
|
|
315
|
+
const uint8_t *prefix, size_t prefix_len,
|
|
316
|
+
const uint8_t *suffix, size_t suffix_len,
|
|
317
|
+
size_t line_no, size_t byte_offset,
|
|
318
|
+
size_t observed_len,
|
|
319
|
+
tf_side_channels *side) {
|
|
320
|
+
if (!side || !side->errors) return TF_OK;
|
|
321
|
+
int truncated = 0;
|
|
322
|
+
char *raw = jsonl_record_preview(st, prefix, prefix_len, suffix, suffix_len, &truncated);
|
|
323
|
+
if (!raw) return TF_ERROR;
|
|
324
|
+
|
|
325
|
+
cJSON *obj = cJSON_CreateObject();
|
|
326
|
+
if (!obj) { free(raw); return TF_ERROR; }
|
|
327
|
+
int rc = TF_ERROR;
|
|
328
|
+
if (tf_json_add_string(obj, "type", "jsonl_record_too_large") != TF_OK ||
|
|
329
|
+
tf_json_add_string(obj, "op", "codec.jsonl.decode") != TF_OK ||
|
|
330
|
+
tf_json_add_string(obj, "action", "fail") != TF_OK ||
|
|
331
|
+
tf_json_add_string(obj, "severity", "error") != TF_OK ||
|
|
332
|
+
tf_json_add_number(obj, "line", (double)line_no) != TF_OK ||
|
|
333
|
+
tf_json_add_number(obj, "byte_offset", (double)byte_offset) != TF_OK ||
|
|
334
|
+
tf_json_add_number(obj, "max_record_bytes", (double)st->max_record_bytes) != TF_OK ||
|
|
335
|
+
tf_json_add_number(obj, "observed_bytes", (double)observed_len) != TF_OK ||
|
|
336
|
+
tf_json_add_string(obj, "message", "JSONL record exceeds max_record_bytes") != TF_OK ||
|
|
337
|
+
tf_json_add_number(obj, "raw_bytes", (double)observed_len) != TF_OK ||
|
|
338
|
+
tf_json_add_string(obj, "raw", raw) != TF_OK ||
|
|
339
|
+
(truncated && tf_json_add_bool(obj, "truncated", 1) != TF_OK)) {
|
|
340
|
+
goto done;
|
|
341
|
+
}
|
|
342
|
+
rc = tf_buffer_write_json_line(side->errors, obj);
|
|
343
|
+
done:
|
|
344
|
+
cJSON_Delete(obj);
|
|
345
|
+
free(raw);
|
|
346
|
+
return rc;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static int fail_jsonl_record_limit(jsonl_decoder_state *st,
|
|
350
|
+
const uint8_t *prefix, size_t prefix_len,
|
|
351
|
+
const uint8_t *suffix, size_t suffix_len,
|
|
352
|
+
size_t line_no, size_t byte_offset,
|
|
353
|
+
size_t observed_len,
|
|
354
|
+
tf_side_channels *side) {
|
|
355
|
+
if (emit_jsonl_record_size_diagnostic(st, prefix, prefix_len, suffix, suffix_len,
|
|
356
|
+
line_no, byte_offset, observed_len, side) != TF_OK)
|
|
357
|
+
return TF_ERROR;
|
|
358
|
+
char err[256];
|
|
359
|
+
snprintf(err, sizeof(err),
|
|
360
|
+
"jsonl record exceeds max_record_bytes at line %zu: max %zu bytes, observed %zu bytes",
|
|
361
|
+
line_no, st->max_record_bytes, observed_len);
|
|
362
|
+
tf_set_last_error(err);
|
|
363
|
+
return TF_ERROR;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
static int check_jsonl_incoming_record_limit(jsonl_decoder_state *st,
|
|
367
|
+
const uint8_t *data, size_t len,
|
|
368
|
+
tf_side_channels *side) {
|
|
369
|
+
if (st->max_record_bytes == 0 || len == 0) return TF_OK;
|
|
370
|
+
|
|
371
|
+
size_t prefix_len = tf_buffer_readable(&st->line_buf);
|
|
372
|
+
const uint8_t *prefix = prefix_len > 0 ? st->line_buf.data + st->line_buf.read_pos : NULL;
|
|
373
|
+
size_t incoming_base = st->byte_offset + prefix_len;
|
|
374
|
+
size_t line_no = st->line_number + 1;
|
|
375
|
+
size_t line_offset = st->byte_offset;
|
|
376
|
+
size_t segment_start = 0;
|
|
377
|
+
size_t current_len = prefix_len;
|
|
378
|
+
|
|
379
|
+
for (size_t i = 0; i < len; i++) {
|
|
380
|
+
if (data[i] == '\n' || data[i] == '\r') {
|
|
381
|
+
if (data[i] == '\r' && i + 1 < len && data[i + 1] == '\n') i++;
|
|
382
|
+
prefix = NULL;
|
|
383
|
+
prefix_len = 0;
|
|
384
|
+
current_len = 0;
|
|
385
|
+
segment_start = i + 1;
|
|
386
|
+
line_offset = incoming_base + i + 1;
|
|
387
|
+
line_no++;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
current_len++;
|
|
391
|
+
if (current_len > st->max_record_bytes) {
|
|
392
|
+
size_t suffix_len = i - segment_start + 1;
|
|
393
|
+
return fail_jsonl_record_limit(st, prefix, prefix_len,
|
|
394
|
+
data + segment_start, suffix_len,
|
|
395
|
+
line_no, line_offset, current_len, side);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return TF_OK;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
static int check_jsonl_buffer_record_limit(jsonl_decoder_state *st, size_t observed_len,
|
|
402
|
+
size_t line_no, size_t byte_offset,
|
|
403
|
+
tf_side_channels *side) {
|
|
404
|
+
if (st->max_record_bytes == 0 || observed_len <= st->max_record_bytes) return TF_OK;
|
|
405
|
+
const uint8_t *buf = st->line_buf.data + st->line_buf.read_pos;
|
|
406
|
+
return fail_jsonl_record_limit(st, buf, observed_len, NULL, 0,
|
|
407
|
+
line_no, byte_offset, observed_len, side);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static int emit_jsonl_batch(tf_batch *batch, tf_batch ***out, size_t *n_out, size_t *out_cap) {
|
|
411
|
+
if (*n_out >= *out_cap) {
|
|
412
|
+
size_t need = 0;
|
|
413
|
+
size_t new_cap = 0;
|
|
414
|
+
if (tf_size_add(*n_out, 1, &need) != TF_OK ||
|
|
415
|
+
tf_size_grow_pow2(*out_cap, need, 4, &new_cap) != TF_OK) {
|
|
416
|
+
return TF_ERROR;
|
|
417
|
+
}
|
|
418
|
+
tf_batch **tmp = tf_reallocarray_checked(*out, new_cap, sizeof(tf_batch *));
|
|
419
|
+
if (!tmp) return TF_ERROR;
|
|
420
|
+
*out = tmp;
|
|
421
|
+
*out_cap = new_cap;
|
|
422
|
+
}
|
|
423
|
+
(*out)[(*n_out)++] = batch;
|
|
424
|
+
return TF_OK;
|
|
425
|
+
}
|
|
426
|
+
|
|
64
427
|
static tf_batch *make_jsonl_batch(jsonl_decoder_state *st) {
|
|
65
428
|
tf_batch *b = tf_batch_create(st->n_cols, st->batch_size);
|
|
66
429
|
if (!b) return NULL;
|
|
67
430
|
for (size_t i = 0; i < st->n_cols; i++) {
|
|
68
|
-
tf_batch_set_schema(b, i, st->col_names[i], st->col_types[i])
|
|
431
|
+
if (tf_batch_set_schema(b, i, st->col_names[i], st->col_types[i]) != TF_OK) {
|
|
432
|
+
tf_batch_free(b);
|
|
433
|
+
return NULL;
|
|
434
|
+
}
|
|
69
435
|
}
|
|
70
436
|
return b;
|
|
71
437
|
}
|
|
72
438
|
|
|
73
|
-
static int
|
|
439
|
+
static int jsonl_field_compare(const void *a, const void *b) {
|
|
440
|
+
const jsonl_field *x = a, *y = b;
|
|
441
|
+
int cmp = strcmp(x->name, y->name);
|
|
442
|
+
return cmp ? cmp : (x->column > y->column) - (x->column < y->column);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
static size_t jsonl_find_column(const jsonl_decoder_state *st, const char *name) {
|
|
446
|
+
size_t lo = 0, hi = st->n_cols;
|
|
447
|
+
while (lo < hi) {
|
|
448
|
+
size_t mid = lo + (hi - lo) / 2;
|
|
449
|
+
if (strcmp(st->fields[mid].name, name) < 0) lo = mid + 1;
|
|
450
|
+
else hi = mid;
|
|
451
|
+
}
|
|
452
|
+
return lo < st->n_cols && strcmp(st->fields[lo].name, name) == 0
|
|
453
|
+
? st->fields[lo].column : st->n_cols;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
static int jsonl_index_schema(jsonl_decoder_state *st) {
|
|
457
|
+
size_t n = st->n_cols ? st->n_cols : 1;
|
|
458
|
+
st->fields = tf_callocarray_checked(n, sizeof(*st->fields));
|
|
459
|
+
st->aliases = tf_callocarray_checked(n, sizeof(*st->aliases));
|
|
460
|
+
st->values = tf_callocarray_checked(n, sizeof(*st->values));
|
|
461
|
+
if (!st->fields || !st->aliases || !st->values) return TF_ERROR;
|
|
462
|
+
for (size_t c = 0; c < st->n_cols; c++)
|
|
463
|
+
st->fields[c] = (jsonl_field){st->col_names[c], c};
|
|
464
|
+
qsort(st->fields, st->n_cols, sizeof(*st->fields), jsonl_field_compare);
|
|
465
|
+
for (size_t c = 0; c < st->n_cols; c++)
|
|
466
|
+
st->aliases[c] = jsonl_find_column(st, st->col_names[c]);
|
|
467
|
+
return TF_OK;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
static int add_json_row(jsonl_decoder_state *st) {
|
|
74
471
|
if (!st->batch) {
|
|
75
472
|
st->batch = make_jsonl_batch(st);
|
|
76
473
|
if (!st->batch) return TF_ERROR;
|
|
77
474
|
}
|
|
78
475
|
|
|
79
476
|
size_t row = st->batch->n_rows;
|
|
80
|
-
|
|
477
|
+
size_t need_rows = 0;
|
|
478
|
+
if (tf_size_add(row, 1, &need_rows) != TF_OK ||
|
|
479
|
+
tf_batch_ensure_capacity(st->batch, need_rows) != TF_OK)
|
|
480
|
+
return TF_ERROR;
|
|
81
481
|
|
|
82
482
|
for (size_t c = 0; c < st->n_cols; c++) {
|
|
83
|
-
cJSON *val =
|
|
483
|
+
cJSON *val = st->values[st->aliases[c]];
|
|
484
|
+
int rc = TF_OK;
|
|
84
485
|
if (!val || cJSON_IsNull(val)) {
|
|
85
|
-
tf_batch_set_null(st->batch, row, c);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
486
|
+
rc = tf_batch_set_null(st->batch, row, c);
|
|
487
|
+
} else {
|
|
488
|
+
switch (st->col_types[c]) {
|
|
489
|
+
case TF_TYPE_BOOL:
|
|
490
|
+
rc = tf_batch_set_bool(st->batch, row, c, cJSON_IsTrue(val));
|
|
491
|
+
break;
|
|
492
|
+
case TF_TYPE_INT64:
|
|
493
|
+
rc = tf_batch_set_int64(st->batch, row, c, (int64_t)val->valuedouble);
|
|
494
|
+
break;
|
|
495
|
+
case TF_TYPE_FLOAT64:
|
|
496
|
+
rc = tf_batch_set_float64(st->batch, row, c, val->valuedouble);
|
|
497
|
+
break;
|
|
498
|
+
case TF_TYPE_STRING:
|
|
499
|
+
if (cJSON_IsString(val)) {
|
|
500
|
+
rc = tf_batch_set_string(st->batch, row, c, val->valuestring ? val->valuestring : "");
|
|
501
|
+
} else {
|
|
502
|
+
char *printed = cJSON_PrintUnformatted(val);
|
|
503
|
+
if (!printed) return TF_ERROR;
|
|
504
|
+
rc = tf_batch_set_string(st->batch, row, c, printed);
|
|
505
|
+
free(printed);
|
|
506
|
+
}
|
|
507
|
+
break;
|
|
508
|
+
default:
|
|
509
|
+
rc = tf_batch_set_null(st->batch, row, c);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
110
512
|
}
|
|
513
|
+
if (rc != TF_OK) return TF_ERROR;
|
|
111
514
|
}
|
|
112
|
-
st->batch
|
|
515
|
+
if (tf_batch_expose_row(st->batch, row) != TF_OK) return TF_ERROR;
|
|
113
516
|
st->rows_buffered++;
|
|
114
517
|
return TF_OK;
|
|
115
518
|
}
|
|
116
519
|
|
|
520
|
+
|
|
521
|
+
static int jsonl_space(unsigned char c) {
|
|
522
|
+
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* cJSON owns structural parsing. Check lexemes it deliberately accepts more
|
|
526
|
+
* loosely, before its C strings can lose embedded NULs. */
|
|
527
|
+
static int jsonl_valid_lexemes(const char *text, size_t n) {
|
|
528
|
+
const unsigned char *s = (const unsigned char *)text;
|
|
529
|
+
for (size_t i = 0; i < n;) {
|
|
530
|
+
unsigned char c = s[i++];
|
|
531
|
+
if (c == '"') {
|
|
532
|
+
int closed = 0;
|
|
533
|
+
while (i < n) {
|
|
534
|
+
c = s[i++];
|
|
535
|
+
if (c == '"') { closed = 1; break; }
|
|
536
|
+
if (c < 0x20) return 0;
|
|
537
|
+
if (c == '\\') {
|
|
538
|
+
if (i == n) return 0;
|
|
539
|
+
c = s[i++];
|
|
540
|
+
if (c == 'u') {
|
|
541
|
+
if (n - i < 4) return 0;
|
|
542
|
+
unsigned value = 0;
|
|
543
|
+
for (size_t k = 0; k < 4; k++) {
|
|
544
|
+
unsigned char h = s[i++];
|
|
545
|
+
if (h >= '0' && h <= '9') value = value * 16 + h - '0';
|
|
546
|
+
else if (h >= 'a' && h <= 'f') value = value * 16 + h - 'a' + 10;
|
|
547
|
+
else if (h >= 'A' && h <= 'F') value = value * 16 + h - 'A' + 10;
|
|
548
|
+
else return 0;
|
|
549
|
+
}
|
|
550
|
+
if (!value) return 0;
|
|
551
|
+
} else if (!strchr("\"\\/bfnrt", c)) return 0;
|
|
552
|
+
} else if (c >= 0x80) {
|
|
553
|
+
unsigned value, minimum;
|
|
554
|
+
size_t extra;
|
|
555
|
+
if (c >= 0xc2 && c <= 0xdf) { value = c & 31; extra = 1; minimum = 0x80; }
|
|
556
|
+
else if (c >= 0xe0 && c <= 0xef) { value = c & 15; extra = 2; minimum = 0x800; }
|
|
557
|
+
else if (c >= 0xf0 && c <= 0xf4) { value = c & 7; extra = 3; minimum = 0x10000; }
|
|
558
|
+
else return 0;
|
|
559
|
+
if (n - i < extra) return 0;
|
|
560
|
+
while (extra--) {
|
|
561
|
+
if ((s[i] & 0xc0) != 0x80) return 0;
|
|
562
|
+
value = (value << 6) | (s[i++] & 63);
|
|
563
|
+
}
|
|
564
|
+
if (value < minimum || value > 0x10ffff ||
|
|
565
|
+
(value >= 0xd800 && value <= 0xdfff)) return 0;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (!closed) return 0;
|
|
569
|
+
} else if (c == '-' || (c >= '0' && c <= '9')) {
|
|
570
|
+
i--;
|
|
571
|
+
if (s[i] == '-' && ++i == n) return 0;
|
|
572
|
+
if (s[i] == '0') i++;
|
|
573
|
+
else {
|
|
574
|
+
if (s[i] < '1' || s[i] > '9') return 0;
|
|
575
|
+
do { i++; } while (i < n && s[i] >= '0' && s[i] <= '9');
|
|
576
|
+
}
|
|
577
|
+
if (i < n && s[i] == '.') {
|
|
578
|
+
i++;
|
|
579
|
+
size_t start = i;
|
|
580
|
+
while (i < n && s[i] >= '0' && s[i] <= '9') i++;
|
|
581
|
+
if (i == start) return 0;
|
|
582
|
+
}
|
|
583
|
+
if (i < n && (s[i] == 'e' || s[i] == 'E')) {
|
|
584
|
+
i++;
|
|
585
|
+
if (i < n && (s[i] == '+' || s[i] == '-')) i++;
|
|
586
|
+
size_t start = i;
|
|
587
|
+
while (i < n && s[i] >= '0' && s[i] <= '9') i++;
|
|
588
|
+
if (i == start) return 0;
|
|
589
|
+
}
|
|
590
|
+
if (i < n && !jsonl_space(s[i]) && s[i] != ',' && s[i] != ']' && s[i] != '}')
|
|
591
|
+
return 0;
|
|
592
|
+
} else if (c >= 0x80 || (c < 0x20 && !jsonl_space(c))) return 0;
|
|
593
|
+
}
|
|
594
|
+
return 1;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
static int jsonl_finite_values(const cJSON *obj) {
|
|
598
|
+
for (const cJSON *v = obj; v; v = v->next) {
|
|
599
|
+
if (cJSON_IsNumber(v) && !isfinite(v->valuedouble)) return 0;
|
|
600
|
+
if (v->child && !jsonl_finite_values(v->child)) return 0;
|
|
601
|
+
}
|
|
602
|
+
return 1;
|
|
603
|
+
}
|
|
604
|
+
|
|
117
605
|
static int process_jsonl_line(jsonl_decoder_state *st, const char *line, size_t len,
|
|
118
|
-
tf_batch ***out, size_t *n_out,
|
|
606
|
+
size_t line_no, tf_batch ***out, size_t *n_out,
|
|
607
|
+
size_t *out_cap, tf_side_channels *side) {
|
|
119
608
|
/* Skip empty lines */
|
|
120
609
|
if (len == 0) return TF_OK;
|
|
121
610
|
|
|
122
|
-
|
|
123
|
-
|
|
611
|
+
if (!jsonl_valid_lexemes(line, len))
|
|
612
|
+
return handle_jsonl_malformed(st, line, len, line_no, "invalid JSON", side);
|
|
613
|
+
const char *end = NULL;
|
|
614
|
+
cJSON *obj = cJSON_ParseWithLengthOpts(line, len, &end, 0);
|
|
615
|
+
if (!obj && cJSON_ParseHadAllocationFailure()) {
|
|
616
|
+
return TF_ERROR;
|
|
617
|
+
}
|
|
618
|
+
while (end && end < line + len && jsonl_space((unsigned char)*end)) end++;
|
|
619
|
+
if (obj && (end != line + len || !jsonl_finite_values(obj))) {
|
|
620
|
+
cJSON_Delete(obj);
|
|
621
|
+
return handle_jsonl_malformed(st, line, len, line_no, "invalid JSON", side);
|
|
622
|
+
}
|
|
124
623
|
if (!obj || !cJSON_IsObject(obj)) {
|
|
624
|
+
const char *reason = obj ? "JSONL record is not an object" : "invalid JSON";
|
|
125
625
|
cJSON_Delete(obj);
|
|
126
|
-
return
|
|
626
|
+
return handle_jsonl_malformed(st, line, len, line_no, reason, side);
|
|
127
627
|
}
|
|
128
628
|
|
|
129
629
|
if (!st->schema_ready) {
|
|
130
630
|
/* Discover schema from first object */
|
|
131
631
|
int n = cJSON_GetArraySize(obj);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
632
|
+
if (n < 0) { cJSON_Delete(obj); return TF_ERROR; }
|
|
633
|
+
size_t n_cols = (size_t)n;
|
|
634
|
+
if (n_cols > TF_MAX_COLUMNS) {
|
|
635
|
+
char err[256];
|
|
636
|
+
snprintf(err, sizeof(err),
|
|
637
|
+
"jsonl schema exceeds max_columns: max %zu columns, observed %zu",
|
|
638
|
+
TF_MAX_COLUMNS, n_cols);
|
|
639
|
+
tf_set_last_error(err);
|
|
640
|
+
cJSON_Delete(obj);
|
|
641
|
+
return TF_ERROR;
|
|
642
|
+
}
|
|
643
|
+
char **col_names = tf_callocarray_checked(n_cols ? n_cols : 1, sizeof(char *));
|
|
644
|
+
tf_type *col_types = tf_callocarray_checked(n_cols ? n_cols : 1, sizeof(tf_type));
|
|
645
|
+
if (!col_names || !col_types) {
|
|
646
|
+
free(col_names);
|
|
647
|
+
free(col_types);
|
|
648
|
+
cJSON_Delete(obj);
|
|
649
|
+
return TF_ERROR;
|
|
650
|
+
}
|
|
135
651
|
|
|
136
|
-
|
|
652
|
+
size_t i = 0;
|
|
653
|
+
size_t schema_bytes = 0;
|
|
137
654
|
cJSON *item;
|
|
138
655
|
cJSON_ArrayForEach(item, obj) {
|
|
139
|
-
|
|
140
|
-
|
|
656
|
+
const char *raw_name = item->string ? item->string : "";
|
|
657
|
+
size_t name_len = 0, name_bytes = 0, next_schema_bytes = 0;
|
|
658
|
+
if (tf_string_length_bounded(raw_name, TF_MAX_COLUMN_NAME_BYTES,
|
|
659
|
+
&name_len, "jsonl", "column name") != TF_OK ||
|
|
660
|
+
tf_size_add(name_len, 1, &name_bytes) != TF_OK ||
|
|
661
|
+
tf_size_add(schema_bytes, name_bytes, &next_schema_bytes) != TF_OK ||
|
|
662
|
+
tf_check_byte_limit(next_schema_bytes, TF_MAX_SCHEMA_BYTES,
|
|
663
|
+
"jsonl", "schema") != TF_OK) {
|
|
664
|
+
jsonl_free_schema_arrays(col_names, col_types, i);
|
|
665
|
+
cJSON_Delete(obj);
|
|
666
|
+
return TF_ERROR;
|
|
667
|
+
}
|
|
668
|
+
col_names[i] = malloc(name_bytes);
|
|
669
|
+
if (!col_names[i]) {
|
|
670
|
+
jsonl_free_schema_arrays(col_names, col_types, i);
|
|
671
|
+
cJSON_Delete(obj);
|
|
672
|
+
return TF_ERROR;
|
|
673
|
+
}
|
|
674
|
+
memcpy(col_names[i], raw_name, name_len);
|
|
675
|
+
col_names[i][name_len] = '\0';
|
|
676
|
+
col_types[i] = json_to_type(item);
|
|
677
|
+
schema_bytes = next_schema_bytes;
|
|
141
678
|
i++;
|
|
142
679
|
}
|
|
680
|
+
st->n_cols = n_cols;
|
|
681
|
+
st->col_names = col_names;
|
|
682
|
+
st->col_types = col_types;
|
|
683
|
+
if (jsonl_index_schema(st) != TF_OK) { cJSON_Delete(obj); return TF_ERROR; }
|
|
143
684
|
st->schema_ready = 1;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
685
|
+
}
|
|
686
|
+
memset(st->values, 0, st->n_cols * sizeof(*st->values));
|
|
687
|
+
cJSON *item;
|
|
688
|
+
cJSON_ArrayForEach(item, obj) {
|
|
689
|
+
if (!item->string) continue;
|
|
690
|
+
size_t c = jsonl_find_column(st, item->string);
|
|
691
|
+
if (c == st->n_cols) continue;
|
|
692
|
+
/* cJSON lookup historically selects the first duplicate value. */
|
|
693
|
+
if (!st->values[c]) st->values[c] = item;
|
|
694
|
+
if (jsonl_widen_column(st, c, json_to_type(item)) != TF_OK) {
|
|
695
|
+
cJSON_Delete(obj);
|
|
696
|
+
return TF_ERROR;
|
|
154
697
|
}
|
|
155
698
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
699
|
+
for (size_t c = 0; c < st->n_cols; c++) {
|
|
700
|
+
cJSON *value = st->values[st->aliases[c]];
|
|
701
|
+
/* Duplicate schema names also receive that first value; ensure its
|
|
702
|
+
* numeric range is representable before a typed setter casts it. */
|
|
703
|
+
if (value && jsonl_widen_column(st, c, json_to_type(value)) != TF_OK) {
|
|
704
|
+
cJSON_Delete(obj);
|
|
705
|
+
return TF_ERROR;
|
|
161
706
|
}
|
|
162
707
|
}
|
|
163
|
-
|
|
164
|
-
int rc = add_json_row(st, obj);
|
|
708
|
+
int rc = add_json_row(st);
|
|
165
709
|
cJSON_Delete(obj);
|
|
166
710
|
if (rc != TF_OK) return rc;
|
|
167
711
|
|
|
168
712
|
/* Emit batch if full */
|
|
169
713
|
if (st->rows_buffered >= st->batch_size) {
|
|
170
|
-
if (
|
|
171
|
-
*out_cap = (*out_cap == 0) ? 4 : *out_cap * 2;
|
|
172
|
-
*out = realloc(*out, *out_cap * sizeof(tf_batch *));
|
|
173
|
-
}
|
|
174
|
-
(*out)[(*n_out)++] = st->batch;
|
|
714
|
+
if (emit_jsonl_batch(st->batch, out, n_out, out_cap) != TF_OK) return TF_ERROR;
|
|
175
715
|
st->batch = NULL;
|
|
176
716
|
st->rows_buffered = 0;
|
|
177
717
|
}
|
|
@@ -180,11 +720,21 @@ static int process_jsonl_line(jsonl_decoder_state *st, const char *line, size_t
|
|
|
180
720
|
}
|
|
181
721
|
|
|
182
722
|
static int jsonl_decode(tf_decoder *self, const uint8_t *data, size_t len,
|
|
183
|
-
tf_batch ***out, size_t *n_out) {
|
|
723
|
+
tf_batch ***out, size_t *n_out, tf_side_channels *side) {
|
|
184
724
|
jsonl_decoder_state *st = self->state;
|
|
185
725
|
*out = NULL;
|
|
186
726
|
*n_out = 0;
|
|
187
727
|
|
|
728
|
+
if (len && st->pending_lf) {
|
|
729
|
+
st->pending_lf = 0;
|
|
730
|
+
if (data[0] == '\n') {
|
|
731
|
+
data++;
|
|
732
|
+
len--;
|
|
733
|
+
st->byte_offset++;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (!len) return TF_OK;
|
|
737
|
+
if (check_jsonl_incoming_record_limit(st, data, len, side) != TF_OK) return TF_ERROR;
|
|
188
738
|
if (tf_buffer_write(&st->line_buf, data, len) != TF_OK) return TF_ERROR;
|
|
189
739
|
|
|
190
740
|
size_t out_cap = 0;
|
|
@@ -195,10 +745,14 @@ static int jsonl_decode(tf_decoder *self, const uint8_t *data, size_t len,
|
|
|
195
745
|
for (size_t i = 0; i < buf_len; i++) {
|
|
196
746
|
if (buf[i] == '\n' || buf[i] == '\r') {
|
|
197
747
|
size_t line_len = i - line_start;
|
|
198
|
-
|
|
748
|
+
size_t line_no = ++st->line_number;
|
|
749
|
+
if (buf[i] == '\r') {
|
|
750
|
+
if (i + 1 < buf_len && buf[i + 1] == '\n') i++;
|
|
751
|
+
else if (i + 1 == buf_len) st->pending_lf = 1;
|
|
752
|
+
}
|
|
199
753
|
if (line_len > 0) {
|
|
200
754
|
if (process_jsonl_line(st, (const char *)buf + line_start, line_len,
|
|
201
|
-
out, n_out, &out_cap) != TF_OK)
|
|
755
|
+
line_no, out, n_out, &out_cap, side) != TF_OK)
|
|
202
756
|
return TF_ERROR;
|
|
203
757
|
}
|
|
204
758
|
line_start = i + 1;
|
|
@@ -206,11 +760,12 @@ static int jsonl_decode(tf_decoder *self, const uint8_t *data, size_t len,
|
|
|
206
760
|
}
|
|
207
761
|
|
|
208
762
|
st->line_buf.read_pos += line_start;
|
|
763
|
+
st->byte_offset += line_start;
|
|
209
764
|
tf_buffer_compact(&st->line_buf);
|
|
210
765
|
return TF_OK;
|
|
211
766
|
}
|
|
212
767
|
|
|
213
|
-
static int jsonl_flush(tf_decoder *self, tf_batch ***out, size_t *n_out) {
|
|
768
|
+
static int jsonl_flush(tf_decoder *self, tf_batch ***out, size_t *n_out, tf_side_channels *side) {
|
|
214
769
|
jsonl_decoder_state *st = self->state;
|
|
215
770
|
*out = NULL;
|
|
216
771
|
*n_out = 0;
|
|
@@ -220,17 +775,20 @@ static int jsonl_flush(tf_decoder *self, tf_batch ***out, size_t *n_out) {
|
|
|
220
775
|
size_t remaining = tf_buffer_readable(&st->line_buf);
|
|
221
776
|
if (remaining > 0) {
|
|
222
777
|
uint8_t *buf = st->line_buf.data + st->line_buf.read_pos;
|
|
223
|
-
|
|
778
|
+
size_t line_no = ++st->line_number;
|
|
779
|
+
size_t record_offset = st->byte_offset;
|
|
780
|
+
if (check_jsonl_buffer_record_limit(st, remaining, line_no, record_offset, side) != TF_OK)
|
|
781
|
+
return TF_ERROR;
|
|
782
|
+
if (process_jsonl_line(st, (const char *)buf, remaining, line_no,
|
|
783
|
+
out, n_out, &out_cap, side) != TF_OK)
|
|
784
|
+
return TF_ERROR;
|
|
224
785
|
st->line_buf.read_pos = st->line_buf.len;
|
|
786
|
+
st->byte_offset += remaining;
|
|
225
787
|
}
|
|
226
788
|
|
|
227
789
|
/* Emit remaining batch */
|
|
228
790
|
if (st->batch && st->rows_buffered > 0) {
|
|
229
|
-
if (
|
|
230
|
-
out_cap = (*n_out == 0) ? 1 : out_cap * 2;
|
|
231
|
-
*out = realloc(*out, out_cap * sizeof(tf_batch *));
|
|
232
|
-
}
|
|
233
|
-
(*out)[(*n_out)++] = st->batch;
|
|
791
|
+
if (emit_jsonl_batch(st->batch, out, n_out, &out_cap) != TF_OK) return TF_ERROR;
|
|
234
792
|
st->batch = NULL;
|
|
235
793
|
st->rows_buffered = 0;
|
|
236
794
|
}
|
|
@@ -243,9 +801,14 @@ static void jsonl_decoder_destroy(tf_decoder *self) {
|
|
|
243
801
|
if (st) {
|
|
244
802
|
tf_buffer_free(&st->line_buf);
|
|
245
803
|
if (st->batch) tf_batch_free(st->batch);
|
|
246
|
-
|
|
804
|
+
if (st->col_names) {
|
|
805
|
+
for (size_t i = 0; i < st->n_cols; i++) free(st->col_names[i]);
|
|
806
|
+
}
|
|
247
807
|
free(st->col_names);
|
|
248
808
|
free(st->col_types);
|
|
809
|
+
free(st->fields);
|
|
810
|
+
free(st->aliases);
|
|
811
|
+
free(st->values);
|
|
249
812
|
free(st);
|
|
250
813
|
}
|
|
251
814
|
free(self);
|
|
@@ -256,10 +819,28 @@ tf_decoder *tf_jsonl_decoder_create(const cJSON *args) {
|
|
|
256
819
|
if (!st) return NULL;
|
|
257
820
|
|
|
258
821
|
st->batch_size = DEFAULT_BATCH_SIZE;
|
|
822
|
+
st->on_error = JSONL_ERROR_SKIP;
|
|
823
|
+
st->max_error_bytes = DEFAULT_MAX_ERROR_BYTES;
|
|
824
|
+
st->max_record_bytes = DEFAULT_MAX_RECORD_BYTES;
|
|
259
825
|
if (args) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
826
|
+
size_t parsed_size = 0;
|
|
827
|
+
int has_batch_size = tf_json_get_size_arg(args, "batch_size", 1, TF_MAX_BATCH_ROWS, &parsed_size, "jsonl");
|
|
828
|
+
if (has_batch_size < 0) { free(st); return NULL; }
|
|
829
|
+
if (has_batch_size > 0) st->batch_size = parsed_size;
|
|
830
|
+
cJSON *on_error = cJSON_GetObjectItemCaseSensitive(args, "on_error");
|
|
831
|
+
if (cJSON_IsString(on_error)) {
|
|
832
|
+
if (!parse_jsonl_error_action(on_error->valuestring, &st->on_error)) {
|
|
833
|
+
tf_set_last_error("jsonl: on_error must be skip, fail, warn, or quarantine");
|
|
834
|
+
free(st);
|
|
835
|
+
return NULL;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
int has_max_error = tf_json_get_size_arg(args, "max_error_bytes", 0, TF_MAX_ERROR_BYTES, &parsed_size, "jsonl");
|
|
839
|
+
if (has_max_error < 0) { free(st); return NULL; }
|
|
840
|
+
if (has_max_error > 0) st->max_error_bytes = parsed_size;
|
|
841
|
+
int has_max_record = tf_json_get_size_arg(args, "max_record_bytes", 0, TF_MAX_RECORD_BYTES, &parsed_size, "jsonl");
|
|
842
|
+
if (has_max_record < 0) { free(st); return NULL; }
|
|
843
|
+
if (has_max_record > 0) st->max_record_bytes = parsed_size;
|
|
263
844
|
}
|
|
264
845
|
|
|
265
846
|
tf_buffer_init(&st->line_buf);
|
|
@@ -277,79 +858,200 @@ tf_decoder *tf_jsonl_decoder_create(const cJSON *args) {
|
|
|
277
858
|
* JSONL Encoder
|
|
278
859
|
* ================================================================ */
|
|
279
860
|
|
|
861
|
+
typedef struct {
|
|
862
|
+
char **names;
|
|
863
|
+
char **key_prefixes;
|
|
864
|
+
size_t *key_prefix_lens;
|
|
865
|
+
size_t n_cols;
|
|
866
|
+
} jsonl_encoder_state;
|
|
867
|
+
|
|
868
|
+
static int jsonl_write_hex_escape(tf_buffer *out, unsigned char ch) {
|
|
869
|
+
static const char hex[] = "0123456789abcdef";
|
|
870
|
+
char esc[6] = {
|
|
871
|
+
'\\', 'u', '0', '0',
|
|
872
|
+
hex[(ch >> 4) & 0x0f],
|
|
873
|
+
hex[ch & 0x0f]
|
|
874
|
+
};
|
|
875
|
+
return tf_buffer_write(out, (const uint8_t *)esc, sizeof(esc));
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
static int jsonl_write_escaped_string(tf_buffer *out, const char *s) {
|
|
879
|
+
if (tf_buffer_write(out, (const uint8_t *)"\"", 1) != TF_OK) return TF_ERROR;
|
|
880
|
+
const unsigned char *start = (const unsigned char *)(s ? s : "");
|
|
881
|
+
const unsigned char *p = start;
|
|
882
|
+
while (*p) {
|
|
883
|
+
unsigned char ch = *p;
|
|
884
|
+
int needs_escape = ch < 0x20 || ch == '"' || ch == '\\';
|
|
885
|
+
if (!needs_escape) {
|
|
886
|
+
p++;
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (p > start && tf_buffer_write(out, start, (size_t)(p - start)) != TF_OK)
|
|
891
|
+
return TF_ERROR;
|
|
892
|
+
|
|
893
|
+
int rc = TF_OK;
|
|
894
|
+
switch (ch) {
|
|
895
|
+
case '"': rc = tf_buffer_write_str(out, "\\\""); break;
|
|
896
|
+
case '\\': rc = tf_buffer_write_str(out, "\\\\"); break;
|
|
897
|
+
case '\b': rc = tf_buffer_write_str(out, "\\b"); break;
|
|
898
|
+
case '\f': rc = tf_buffer_write_str(out, "\\f"); break;
|
|
899
|
+
case '\n': rc = tf_buffer_write_str(out, "\\n"); break;
|
|
900
|
+
case '\r': rc = tf_buffer_write_str(out, "\\r"); break;
|
|
901
|
+
case '\t': rc = tf_buffer_write_str(out, "\\t"); break;
|
|
902
|
+
default: rc = jsonl_write_hex_escape(out, ch); break;
|
|
903
|
+
}
|
|
904
|
+
if (rc != TF_OK) return TF_ERROR;
|
|
905
|
+
p++;
|
|
906
|
+
start = p;
|
|
907
|
+
}
|
|
908
|
+
if (p > start && tf_buffer_write(out, start, (size_t)(p - start)) != TF_OK)
|
|
909
|
+
return TF_ERROR;
|
|
910
|
+
return tf_buffer_write(out, (const uint8_t *)"\"", 1);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
static void jsonl_encoder_clear_cache(jsonl_encoder_state *st) {
|
|
914
|
+
if (!st) return;
|
|
915
|
+
if (st->names) {
|
|
916
|
+
for (size_t i = 0; i < st->n_cols; i++) free(st->names[i]);
|
|
917
|
+
}
|
|
918
|
+
if (st->key_prefixes) {
|
|
919
|
+
for (size_t i = 0; i < st->n_cols; i++) free(st->key_prefixes[i]);
|
|
920
|
+
}
|
|
921
|
+
free(st->names);
|
|
922
|
+
free(st->key_prefixes);
|
|
923
|
+
free(st->key_prefix_lens);
|
|
924
|
+
st->names = NULL;
|
|
925
|
+
st->key_prefixes = NULL;
|
|
926
|
+
st->key_prefix_lens = NULL;
|
|
927
|
+
st->n_cols = 0;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
static int jsonl_encoder_cache_matches(const jsonl_encoder_state *st, const tf_batch *in) {
|
|
931
|
+
if (!st || !in || st->n_cols != in->n_cols || !st->names) return 0;
|
|
932
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
933
|
+
const char *name = in->col_names[c] ? in->col_names[c] : "";
|
|
934
|
+
if (strcmp(st->names[c], name) != 0) return 0;
|
|
935
|
+
}
|
|
936
|
+
return 1;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
static int jsonl_build_key_prefix(const char *name, int comma,
|
|
940
|
+
char **out_prefix, size_t *out_len) {
|
|
941
|
+
tf_buffer tmp;
|
|
942
|
+
tf_buffer_init(&tmp);
|
|
943
|
+
int rc = TF_ERROR;
|
|
944
|
+
if (comma && tf_buffer_write(&tmp, (const uint8_t *)",", 1) != TF_OK) goto done;
|
|
945
|
+
if (jsonl_write_escaped_string(&tmp, name) != TF_OK) goto done;
|
|
946
|
+
if (tf_buffer_write(&tmp, (const uint8_t *)":", 1) != TF_OK) goto done;
|
|
947
|
+
|
|
948
|
+
size_t len = tf_buffer_readable(&tmp);
|
|
949
|
+
size_t bytes = 0;
|
|
950
|
+
if (tf_size_add(len, 1, &bytes) != TF_OK) goto done;
|
|
951
|
+
char *copy = tf_mallocarray_checked(bytes, sizeof(char));
|
|
952
|
+
if (!copy) goto done;
|
|
953
|
+
if (len > 0) memcpy(copy, tmp.data + tmp.read_pos, len);
|
|
954
|
+
copy[len] = '\0';
|
|
955
|
+
*out_prefix = copy;
|
|
956
|
+
*out_len = len;
|
|
957
|
+
rc = TF_OK;
|
|
958
|
+
done:
|
|
959
|
+
tf_buffer_free(&tmp);
|
|
960
|
+
return rc;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
static int jsonl_encoder_build_cache(jsonl_encoder_state *st, const tf_batch *in) {
|
|
964
|
+
jsonl_encoder_clear_cache(st);
|
|
965
|
+
size_t n = in->n_cols;
|
|
966
|
+
st->names = tf_callocarray_checked(n ? n : 1, sizeof(char *));
|
|
967
|
+
st->key_prefixes = tf_callocarray_checked(n ? n : 1, sizeof(char *));
|
|
968
|
+
st->key_prefix_lens = tf_callocarray_checked(n ? n : 1, sizeof(size_t));
|
|
969
|
+
if (!st->names || !st->key_prefixes || !st->key_prefix_lens) {
|
|
970
|
+
jsonl_encoder_clear_cache(st);
|
|
971
|
+
return TF_ERROR;
|
|
972
|
+
}
|
|
973
|
+
st->n_cols = n;
|
|
974
|
+
|
|
975
|
+
for (size_t c = 0; c < n; c++) {
|
|
976
|
+
const char *name = in->col_names[c] ? in->col_names[c] : "";
|
|
977
|
+
st->names[c] = tf_strdup_checked(name);
|
|
978
|
+
if (!st->names[c]) {
|
|
979
|
+
jsonl_encoder_clear_cache(st);
|
|
980
|
+
return TF_ERROR;
|
|
981
|
+
}
|
|
982
|
+
if (jsonl_build_key_prefix(name, c > 0, &st->key_prefixes[c],
|
|
983
|
+
&st->key_prefix_lens[c]) != TF_OK) {
|
|
984
|
+
jsonl_encoder_clear_cache(st);
|
|
985
|
+
return TF_ERROR;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return TF_OK;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
static int jsonl_encoder_ensure_cache(jsonl_encoder_state *st, const tf_batch *in) {
|
|
992
|
+
if (jsonl_encoder_cache_matches(st, in)) return TF_OK;
|
|
993
|
+
return jsonl_encoder_build_cache(st, in);
|
|
994
|
+
}
|
|
995
|
+
|
|
280
996
|
static int jsonl_encode(tf_encoder *self, tf_batch *in, tf_buffer *out) {
|
|
281
|
-
|
|
997
|
+
jsonl_encoder_state *st = self->state;
|
|
998
|
+
if (jsonl_encoder_ensure_cache(st, in) != TF_OK) return TF_ERROR;
|
|
282
999
|
char numbuf[64];
|
|
1000
|
+
#define JSONL_WRITE(expr) do { if ((expr) != TF_OK) return TF_ERROR; } while (0)
|
|
283
1001
|
|
|
284
1002
|
for (size_t r = 0; r < in->n_rows; r++) {
|
|
285
|
-
tf_buffer_write(out, (const uint8_t *)"{", 1);
|
|
1003
|
+
JSONL_WRITE(tf_buffer_write(out, (const uint8_t *)"{", 1));
|
|
286
1004
|
for (size_t c = 0; c < in->n_cols; c++) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
/* Key */
|
|
290
|
-
tf_buffer_write(out, (const uint8_t *)"\"", 1);
|
|
291
|
-
tf_buffer_write_str(out, in->col_names[c]);
|
|
292
|
-
tf_buffer_write(out, (const uint8_t *)"\":", 2);
|
|
1005
|
+
JSONL_WRITE(tf_buffer_write(out, (const uint8_t *)st->key_prefixes[c],
|
|
1006
|
+
st->key_prefix_lens[c]));
|
|
293
1007
|
|
|
294
1008
|
/* Value */
|
|
295
1009
|
if (tf_batch_is_null(in, r, c)) {
|
|
296
|
-
tf_buffer_write_str(out, "null");
|
|
1010
|
+
JSONL_WRITE(tf_buffer_write_str(out, "null"));
|
|
297
1011
|
continue;
|
|
298
1012
|
}
|
|
299
1013
|
switch (in->col_types[c]) {
|
|
300
1014
|
case TF_TYPE_BOOL:
|
|
301
|
-
tf_buffer_write_str(out, tf_batch_get_bool(in, r, c) ? "true" : "false");
|
|
1015
|
+
JSONL_WRITE(tf_buffer_write_str(out, tf_batch_get_bool(in, r, c) ? "true" : "false"));
|
|
302
1016
|
break;
|
|
303
1017
|
case TF_TYPE_INT64:
|
|
304
1018
|
snprintf(numbuf, sizeof(numbuf), "%lld",
|
|
305
1019
|
(long long)tf_batch_get_int64(in, r, c));
|
|
306
|
-
tf_buffer_write_str(out, numbuf);
|
|
1020
|
+
JSONL_WRITE(tf_buffer_write_str(out, numbuf));
|
|
307
1021
|
break;
|
|
308
1022
|
case TF_TYPE_FLOAT64:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
break;
|
|
313
|
-
case TF_TYPE_STRING: {
|
|
314
|
-
const char *s = tf_batch_get_string(in, r, c);
|
|
315
|
-
tf_buffer_write(out, (const uint8_t *)"\"", 1);
|
|
316
|
-
/* Escape special characters */
|
|
317
|
-
for (const char *p = s; *p; p++) {
|
|
318
|
-
switch (*p) {
|
|
319
|
-
case '"': tf_buffer_write_str(out, "\\\""); break;
|
|
320
|
-
case '\\': tf_buffer_write_str(out, "\\\\"); break;
|
|
321
|
-
case '\n': tf_buffer_write_str(out, "\\n"); break;
|
|
322
|
-
case '\r': tf_buffer_write_str(out, "\\r"); break;
|
|
323
|
-
case '\t': tf_buffer_write_str(out, "\\t"); break;
|
|
324
|
-
default: tf_buffer_write(out, (const uint8_t *)p, 1); break;
|
|
325
|
-
}
|
|
1023
|
+
if (!isfinite(tf_batch_get_float64(in, r, c))) {
|
|
1024
|
+
tf_set_last_error("jsonl cannot encode nonfinite numbers");
|
|
1025
|
+
return TF_ERROR;
|
|
326
1026
|
}
|
|
327
|
-
|
|
1027
|
+
if (tf_format_float64(numbuf, sizeof(numbuf),
|
|
1028
|
+
tf_batch_get_float64(in, r, c)) != TF_OK)
|
|
1029
|
+
return TF_ERROR;
|
|
1030
|
+
JSONL_WRITE(tf_buffer_write_str(out, numbuf));
|
|
1031
|
+
break;
|
|
1032
|
+
case TF_TYPE_STRING:
|
|
1033
|
+
JSONL_WRITE(jsonl_write_escaped_string(out, tf_batch_get_string(in, r, c)));
|
|
328
1034
|
break;
|
|
329
|
-
}
|
|
330
1035
|
case TF_TYPE_DATE: {
|
|
331
|
-
char dbuf[
|
|
1036
|
+
char dbuf[32];
|
|
332
1037
|
tf_date_format(tf_batch_get_date(in, r, c), dbuf, sizeof(dbuf));
|
|
333
|
-
|
|
334
|
-
tf_buffer_write_str(out, dbuf);
|
|
335
|
-
tf_buffer_write(out, (const uint8_t *)"\"", 1);
|
|
1038
|
+
JSONL_WRITE(jsonl_write_escaped_string(out, dbuf));
|
|
336
1039
|
break;
|
|
337
1040
|
}
|
|
338
1041
|
case TF_TYPE_TIMESTAMP: {
|
|
339
1042
|
char tsbuf[40];
|
|
340
1043
|
tf_timestamp_format(tf_batch_get_timestamp(in, r, c), tsbuf, sizeof(tsbuf));
|
|
341
|
-
|
|
342
|
-
tf_buffer_write_str(out, tsbuf);
|
|
343
|
-
tf_buffer_write(out, (const uint8_t *)"\"", 1);
|
|
1044
|
+
JSONL_WRITE(jsonl_write_escaped_string(out, tsbuf));
|
|
344
1045
|
break;
|
|
345
1046
|
}
|
|
346
1047
|
default:
|
|
347
|
-
tf_buffer_write_str(out, "null");
|
|
1048
|
+
JSONL_WRITE(tf_buffer_write_str(out, "null"));
|
|
348
1049
|
break;
|
|
349
1050
|
}
|
|
350
1051
|
}
|
|
351
|
-
tf_buffer_write(out, (const uint8_t *)"}\n", 2);
|
|
1052
|
+
JSONL_WRITE(tf_buffer_write(out, (const uint8_t *)"}\n", 2));
|
|
352
1053
|
}
|
|
1054
|
+
#undef JSONL_WRITE
|
|
353
1055
|
return TF_OK;
|
|
354
1056
|
}
|
|
355
1057
|
|
|
@@ -359,16 +1061,26 @@ static int jsonl_encoder_flush(tf_encoder *self, tf_buffer *out) {
|
|
|
359
1061
|
}
|
|
360
1062
|
|
|
361
1063
|
static void jsonl_encoder_destroy(tf_encoder *self) {
|
|
1064
|
+
jsonl_encoder_state *st = self->state;
|
|
1065
|
+
if (st) {
|
|
1066
|
+
jsonl_encoder_clear_cache(st);
|
|
1067
|
+
free(st);
|
|
1068
|
+
}
|
|
362
1069
|
free(self);
|
|
363
1070
|
}
|
|
364
1071
|
|
|
365
1072
|
tf_encoder *tf_jsonl_encoder_create(const cJSON *args) {
|
|
366
1073
|
(void)args;
|
|
1074
|
+
jsonl_encoder_state *st = calloc(1, sizeof(jsonl_encoder_state));
|
|
1075
|
+
if (!st) return NULL;
|
|
367
1076
|
tf_encoder *enc = malloc(sizeof(tf_encoder));
|
|
368
|
-
if (!enc)
|
|
1077
|
+
if (!enc) {
|
|
1078
|
+
free(st);
|
|
1079
|
+
return NULL;
|
|
1080
|
+
}
|
|
369
1081
|
enc->encode = jsonl_encode;
|
|
370
1082
|
enc->flush = jsonl_encoder_flush;
|
|
371
1083
|
enc->destroy = jsonl_encoder_destroy;
|
|
372
|
-
enc->state =
|
|
1084
|
+
enc->state = st;
|
|
373
1085
|
return enc;
|
|
374
1086
|
}
|