tranfi 0.1.2 → 0.2.1
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 +443 -51
- 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 +352 -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 +81 -41
- 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/batch.c
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
#include "internal.h"
|
|
6
|
+
#include "cJSON.h"
|
|
7
|
+
#include "date_utils.h"
|
|
8
|
+
#include <stdio.h>
|
|
6
9
|
#include <stdlib.h>
|
|
7
10
|
#include <string.h>
|
|
8
11
|
|
|
@@ -19,6 +22,15 @@ static size_t type_size(tf_type t) {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
tf_batch *tf_batch_create(size_t n_cols, size_t capacity) {
|
|
25
|
+
if (n_cols > TF_MAX_COLUMNS) {
|
|
26
|
+
char err[256];
|
|
27
|
+
snprintf(err, sizeof(err),
|
|
28
|
+
"batch: columns exceeds maximum %zu, observed %zu",
|
|
29
|
+
TF_MAX_COLUMNS, n_cols);
|
|
30
|
+
tf_set_last_error(err);
|
|
31
|
+
return NULL;
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
tf_arena *arena = tf_arena_create(0);
|
|
23
35
|
if (!arena) return NULL;
|
|
24
36
|
|
|
@@ -29,10 +41,19 @@ tf_batch *tf_batch_create(size_t n_cols, size_t capacity) {
|
|
|
29
41
|
b->n_cols = n_cols;
|
|
30
42
|
b->n_rows = 0;
|
|
31
43
|
b->capacity = capacity;
|
|
32
|
-
b->
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
b->schema_name_bytes = 0;
|
|
45
|
+
size_t names_bytes = 0, types_bytes = 0, columns_bytes = 0, nulls_bytes = 0;
|
|
46
|
+
if (tf_size_mul(n_cols, sizeof(char *), &names_bytes) != TF_OK ||
|
|
47
|
+
tf_size_mul(n_cols, sizeof(tf_type), &types_bytes) != TF_OK ||
|
|
48
|
+
tf_size_mul(n_cols, sizeof(void *), &columns_bytes) != TF_OK ||
|
|
49
|
+
tf_size_mul(n_cols, sizeof(uint8_t *), &nulls_bytes) != TF_OK) {
|
|
50
|
+
tf_arena_free(arena);
|
|
51
|
+
return NULL;
|
|
52
|
+
}
|
|
53
|
+
b->col_names = tf_arena_alloc(arena, names_bytes);
|
|
54
|
+
b->col_types = tf_arena_alloc(arena, types_bytes);
|
|
55
|
+
b->columns = tf_arena_alloc(arena, columns_bytes);
|
|
56
|
+
b->nulls = tf_arena_alloc(arena, nulls_bytes);
|
|
36
57
|
|
|
37
58
|
if (!b->col_names || !b->col_types || !b->columns || !b->nulls) {
|
|
38
59
|
tf_arena_free(arena);
|
|
@@ -50,18 +71,38 @@ tf_batch *tf_batch_create(size_t n_cols, size_t capacity) {
|
|
|
50
71
|
}
|
|
51
72
|
|
|
52
73
|
int tf_batch_set_schema(tf_batch *b, size_t col, const char *name, tf_type type) {
|
|
53
|
-
if (col >= b->n_cols) return TF_ERROR;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
if (!b || col >= b->n_cols) return TF_ERROR;
|
|
75
|
+
const char *safe_name = name ? name : "";
|
|
76
|
+
size_t name_len = 0, name_bytes = 0, schema_bytes = 0;
|
|
77
|
+
if (tf_string_length_bounded(safe_name, TF_MAX_COLUMN_NAME_BYTES,
|
|
78
|
+
&name_len, "batch", "column name") != TF_OK ||
|
|
79
|
+
tf_size_add(name_len, 1, &name_bytes) != TF_OK ||
|
|
80
|
+
tf_size_add(b->schema_name_bytes, name_bytes, &schema_bytes) != TF_OK ||
|
|
81
|
+
tf_check_byte_limit(schema_bytes, TF_MAX_SCHEMA_BYTES,
|
|
82
|
+
"batch", "schema") != TF_OK)
|
|
83
|
+
return TF_ERROR;
|
|
84
|
+
char *name_copy = tf_arena_alloc(b->arena, name_bytes);
|
|
85
|
+
if (!name_copy) return TF_ERROR;
|
|
86
|
+
memcpy(name_copy, safe_name, name_len);
|
|
87
|
+
name_copy[name_len] = '\0';
|
|
88
|
+
|
|
89
|
+
void *column = NULL;
|
|
90
|
+
uint8_t *nulls = NULL;
|
|
58
91
|
size_t sz = type_size(type);
|
|
59
92
|
if (sz > 0 && b->capacity > 0) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
93
|
+
size_t column_bytes = 0;
|
|
94
|
+
if (tf_size_mul(sz, b->capacity, &column_bytes) != TF_OK) return TF_ERROR;
|
|
95
|
+
column = tf_arena_alloc(b->arena, column_bytes);
|
|
96
|
+
nulls = tf_arena_alloc(b->arena, b->capacity);
|
|
97
|
+
if (!column || !nulls) return TF_ERROR;
|
|
98
|
+
memset(nulls, 1, b->capacity); /* all null by default */
|
|
64
99
|
}
|
|
100
|
+
|
|
101
|
+
b->col_names[col] = name_copy;
|
|
102
|
+
b->col_types[col] = type;
|
|
103
|
+
b->columns[col] = column;
|
|
104
|
+
b->nulls[col] = nulls;
|
|
105
|
+
b->schema_name_bytes = schema_bytes;
|
|
65
106
|
return TF_OK;
|
|
66
107
|
}
|
|
67
108
|
|
|
@@ -70,19 +111,23 @@ int tf_batch_ensure_capacity(tf_batch *b, size_t min_rows) {
|
|
|
70
111
|
|
|
71
112
|
/* We need to reallocate columns — but arena doesn't support realloc.
|
|
72
113
|
* Allocate new larger arrays and copy. */
|
|
73
|
-
size_t new_cap =
|
|
74
|
-
|
|
114
|
+
size_t new_cap = 0;
|
|
115
|
+
if (tf_size_grow_pow2(b->capacity, min_rows, 16, &new_cap) != TF_OK) return TF_ERROR;
|
|
75
116
|
|
|
76
117
|
for (size_t i = 0; i < b->n_cols; i++) {
|
|
77
118
|
size_t sz = type_size(b->col_types[i]);
|
|
78
119
|
if (sz == 0) continue;
|
|
79
120
|
|
|
80
|
-
|
|
121
|
+
size_t column_bytes = 0;
|
|
122
|
+
size_t copy_bytes = 0;
|
|
123
|
+
if (tf_size_mul(sz, new_cap, &column_bytes) != TF_OK ||
|
|
124
|
+
tf_size_mul(sz, b->n_rows, ©_bytes) != TF_OK) return TF_ERROR;
|
|
125
|
+
void *new_col = tf_arena_alloc(b->arena, column_bytes);
|
|
81
126
|
uint8_t *new_null = tf_arena_alloc(b->arena, new_cap);
|
|
82
127
|
if (!new_col || !new_null) return TF_ERROR;
|
|
83
128
|
|
|
84
129
|
if (b->columns[i] && b->n_rows > 0) {
|
|
85
|
-
memcpy(new_col, b->columns[i],
|
|
130
|
+
memcpy(new_col, b->columns[i], copy_bytes);
|
|
86
131
|
memcpy(new_null, b->nulls[i], b->n_rows);
|
|
87
132
|
}
|
|
88
133
|
memset(new_null + b->n_rows, 1, new_cap - b->n_rows);
|
|
@@ -93,127 +138,536 @@ int tf_batch_ensure_capacity(tf_batch *b, size_t min_rows) {
|
|
|
93
138
|
return TF_OK;
|
|
94
139
|
}
|
|
95
140
|
|
|
141
|
+
int tf_batch_expose_row(tf_batch *b, size_t row) {
|
|
142
|
+
if (!b) return TF_ERROR;
|
|
143
|
+
size_t next_rows = 0;
|
|
144
|
+
if (tf_size_add(row, 1, &next_rows) != TF_OK) return TF_ERROR;
|
|
145
|
+
if (next_rows > b->capacity) return TF_ERROR;
|
|
146
|
+
b->n_rows = next_rows;
|
|
147
|
+
return TF_OK;
|
|
148
|
+
}
|
|
149
|
+
|
|
96
150
|
/* ---- Setters ---- */
|
|
97
151
|
|
|
98
|
-
|
|
99
|
-
if (row
|
|
100
|
-
|
|
152
|
+
int tf_batch_set_null(tf_batch *b, size_t row, size_t col) {
|
|
153
|
+
if (!b || row >= b->capacity || col >= b->n_cols) return TF_ERROR;
|
|
154
|
+
if (!b->nulls[col]) return TF_OK;
|
|
155
|
+
b->nulls[col][row] = 1;
|
|
156
|
+
return TF_OK;
|
|
101
157
|
}
|
|
102
158
|
|
|
103
|
-
|
|
104
|
-
if (row
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
159
|
+
int tf_batch_set_bool(tf_batch *b, size_t row, size_t col, bool val) {
|
|
160
|
+
if (!b || row >= b->capacity || col >= b->n_cols ||
|
|
161
|
+
b->col_types[col] != TF_TYPE_BOOL || !b->columns[col] || !b->nulls[col]) return TF_ERROR;
|
|
162
|
+
((uint8_t *)b->columns[col])[row] = val ? 1 : 0;
|
|
163
|
+
b->nulls[col][row] = 0;
|
|
164
|
+
return TF_OK;
|
|
108
165
|
}
|
|
109
166
|
|
|
110
|
-
|
|
111
|
-
if (row
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
167
|
+
int tf_batch_set_int64(tf_batch *b, size_t row, size_t col, int64_t val) {
|
|
168
|
+
if (!b || row >= b->capacity || col >= b->n_cols ||
|
|
169
|
+
b->col_types[col] != TF_TYPE_INT64 || !b->columns[col] || !b->nulls[col]) return TF_ERROR;
|
|
170
|
+
((int64_t *)b->columns[col])[row] = val;
|
|
171
|
+
b->nulls[col][row] = 0;
|
|
172
|
+
return TF_OK;
|
|
115
173
|
}
|
|
116
174
|
|
|
117
|
-
|
|
118
|
-
if (row
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
175
|
+
int tf_batch_set_float64(tf_batch *b, size_t row, size_t col, double val) {
|
|
176
|
+
if (!b || row >= b->capacity || col >= b->n_cols ||
|
|
177
|
+
b->col_types[col] != TF_TYPE_FLOAT64 || !b->columns[col] || !b->nulls[col]) return TF_ERROR;
|
|
178
|
+
((double *)b->columns[col])[row] = val;
|
|
179
|
+
b->nulls[col][row] = 0;
|
|
180
|
+
return TF_OK;
|
|
122
181
|
}
|
|
123
182
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
183
|
+
int tf_batch_set_string(tf_batch *b, size_t row, size_t col, const char *val) {
|
|
184
|
+
size_t len = 0;
|
|
185
|
+
if (tf_string_length_bounded(val, TF_MAX_CELL_BYTES,
|
|
186
|
+
&len, "batch", "cell") != TF_OK)
|
|
187
|
+
return TF_ERROR;
|
|
188
|
+
return tf_batch_set_string_len(b, row, col, val, len);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
int tf_batch_set_string_len(tf_batch *b, size_t row, size_t col,
|
|
192
|
+
const char *val, size_t len) {
|
|
193
|
+
if (!b || row >= b->capacity || col >= b->n_cols || !val ||
|
|
194
|
+
b->col_types[col] != TF_TYPE_STRING || !b->columns[col] || !b->nulls[col]) return TF_ERROR;
|
|
195
|
+
if (tf_check_byte_limit(len, TF_MAX_CELL_BYTES, "batch", "cell") != TF_OK)
|
|
196
|
+
return TF_ERROR;
|
|
197
|
+
size_t bytes = 0;
|
|
198
|
+
if (tf_size_add(len, 1, &bytes) != TF_OK) return TF_ERROR;
|
|
199
|
+
char *copy = tf_arena_alloc(b->arena, bytes);
|
|
200
|
+
if (!copy) return TF_ERROR;
|
|
201
|
+
memcpy(copy, val, len);
|
|
202
|
+
copy[len] = '\0';
|
|
203
|
+
((char **)b->columns[col])[row] = copy;
|
|
204
|
+
b->nulls[col][row] = 0;
|
|
205
|
+
return TF_OK;
|
|
129
206
|
}
|
|
130
207
|
|
|
131
|
-
|
|
132
|
-
if (row
|
|
133
|
-
|
|
134
|
-
|
|
208
|
+
int tf_batch_set_date(tf_batch *b, size_t row, size_t col, int32_t val) {
|
|
209
|
+
if (!b || row >= b->capacity || col >= b->n_cols ||
|
|
210
|
+
b->col_types[col] != TF_TYPE_DATE || !b->columns[col] || !b->nulls[col]) return TF_ERROR;
|
|
211
|
+
((int32_t *)b->columns[col])[row] = val;
|
|
212
|
+
b->nulls[col][row] = 0;
|
|
213
|
+
return TF_OK;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
int tf_batch_set_timestamp(tf_batch *b, size_t row, size_t col, int64_t val) {
|
|
217
|
+
if (!b || row >= b->capacity || col >= b->n_cols ||
|
|
218
|
+
b->col_types[col] != TF_TYPE_TIMESTAMP || !b->columns[col] || !b->nulls[col]) return TF_ERROR;
|
|
219
|
+
((int64_t *)b->columns[col])[row] = val;
|
|
220
|
+
b->nulls[col][row] = 0;
|
|
221
|
+
return TF_OK;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
int tf_batch_set_cell_value(tf_batch *b, size_t row, size_t col,
|
|
225
|
+
tf_type type, int is_null, const tf_cell_value *value) {
|
|
226
|
+
if (!b || col >= b->n_cols || b->col_types[col] != type) return TF_ERROR;
|
|
227
|
+
if (is_null) return tf_batch_set_null(b, row, col);
|
|
228
|
+
if (!value) return TF_ERROR;
|
|
229
|
+
|
|
230
|
+
switch (type) {
|
|
231
|
+
case TF_TYPE_BOOL:
|
|
232
|
+
return tf_batch_set_bool(b, row, col, value->b);
|
|
233
|
+
case TF_TYPE_INT64:
|
|
234
|
+
return tf_batch_set_int64(b, row, col, value->i64);
|
|
235
|
+
case TF_TYPE_FLOAT64:
|
|
236
|
+
return tf_batch_set_float64(b, row, col, value->f64);
|
|
237
|
+
case TF_TYPE_STRING:
|
|
238
|
+
return tf_batch_set_string(b, row, col, value->str ? value->str : "");
|
|
239
|
+
case TF_TYPE_DATE:
|
|
240
|
+
return tf_batch_set_date(b, row, col, value->date);
|
|
241
|
+
case TF_TYPE_TIMESTAMP:
|
|
242
|
+
return tf_batch_set_timestamp(b, row, col, value->i64);
|
|
243
|
+
default:
|
|
244
|
+
return tf_batch_set_null(b, row, col);
|
|
135
245
|
}
|
|
136
246
|
}
|
|
137
247
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
248
|
+
int tf_batch_set_owned_cell_value(tf_batch *b, size_t row, size_t col,
|
|
249
|
+
tf_type type, int is_null,
|
|
250
|
+
const tf_owned_cell_value *value) {
|
|
251
|
+
tf_cell_value tmp;
|
|
252
|
+
memset(&tmp, 0, sizeof(tmp));
|
|
253
|
+
if (value) {
|
|
254
|
+
switch (type) {
|
|
255
|
+
case TF_TYPE_BOOL:
|
|
256
|
+
tmp.b = value->b != 0;
|
|
257
|
+
break;
|
|
258
|
+
case TF_TYPE_INT64:
|
|
259
|
+
tmp.i64 = value->i64;
|
|
260
|
+
break;
|
|
261
|
+
case TF_TYPE_FLOAT64:
|
|
262
|
+
tmp.f64 = value->f64;
|
|
263
|
+
break;
|
|
264
|
+
case TF_TYPE_STRING:
|
|
265
|
+
tmp.str = value->str;
|
|
266
|
+
break;
|
|
267
|
+
case TF_TYPE_DATE:
|
|
268
|
+
tmp.date = value->date;
|
|
269
|
+
break;
|
|
270
|
+
case TF_TYPE_TIMESTAMP:
|
|
271
|
+
tmp.i64 = value->i64;
|
|
272
|
+
break;
|
|
273
|
+
default:
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
142
276
|
}
|
|
277
|
+
return tf_batch_set_cell_value(b, row, col, type, is_null, value ? &tmp : NULL);
|
|
143
278
|
}
|
|
144
279
|
|
|
145
280
|
/* ---- Getters ---- */
|
|
146
281
|
|
|
282
|
+
size_t tf_batch_num_rows(const tf_batch *b) {
|
|
283
|
+
return b ? b->n_rows : 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
size_t tf_batch_num_cols(const tf_batch *b) {
|
|
287
|
+
return b ? b->n_cols : 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const char *tf_batch_col_name(const tf_batch *b, size_t col) {
|
|
291
|
+
if (!b || col >= b->n_cols) return NULL;
|
|
292
|
+
return b->col_names[col];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
tf_type tf_batch_col_type(const tf_batch *b, size_t col) {
|
|
296
|
+
if (!b || col >= b->n_cols) return TF_TYPE_NULL;
|
|
297
|
+
return b->col_types[col];
|
|
298
|
+
}
|
|
299
|
+
|
|
147
300
|
bool tf_batch_is_null(const tf_batch *b, size_t row, size_t col) {
|
|
148
|
-
if (row >= b->n_rows || col >= b->n_cols) return true;
|
|
301
|
+
if (!b || row >= b->n_rows || col >= b->n_cols) return true;
|
|
302
|
+
if (!b->nulls[col]) return true;
|
|
149
303
|
return b->nulls[col][row] != 0;
|
|
150
304
|
}
|
|
151
305
|
|
|
152
306
|
bool tf_batch_get_bool(const tf_batch *b, size_t row, size_t col) {
|
|
153
|
-
if (row >= b->n_rows || col >= b->n_cols) return false;
|
|
307
|
+
if (!b || row >= b->n_rows || col >= b->n_cols) return false;
|
|
154
308
|
return ((uint8_t *)b->columns[col])[row] != 0;
|
|
155
309
|
}
|
|
156
310
|
|
|
157
311
|
int64_t tf_batch_get_int64(const tf_batch *b, size_t row, size_t col) {
|
|
158
|
-
if (row >= b->n_rows || col >= b->n_cols) return 0;
|
|
312
|
+
if (!b || row >= b->n_rows || col >= b->n_cols) return 0;
|
|
159
313
|
return ((int64_t *)b->columns[col])[row];
|
|
160
314
|
}
|
|
161
315
|
|
|
162
316
|
double tf_batch_get_float64(const tf_batch *b, size_t row, size_t col) {
|
|
163
|
-
if (row >= b->n_rows || col >= b->n_cols) return 0.0;
|
|
317
|
+
if (!b || row >= b->n_rows || col >= b->n_cols) return 0.0;
|
|
164
318
|
return ((double *)b->columns[col])[row];
|
|
165
319
|
}
|
|
166
320
|
|
|
167
321
|
const char *tf_batch_get_string(const tf_batch *b, size_t row, size_t col) {
|
|
168
|
-
if (row >= b->n_rows || col >= b->n_cols) return NULL;
|
|
322
|
+
if (!b || row >= b->n_rows || col >= b->n_cols) return NULL;
|
|
169
323
|
return ((char **)b->columns[col])[row];
|
|
170
324
|
}
|
|
171
325
|
|
|
172
326
|
int32_t tf_batch_get_date(const tf_batch *b, size_t row, size_t col) {
|
|
173
|
-
if (row >= b->n_rows || col >= b->n_cols) return 0;
|
|
327
|
+
if (!b || row >= b->n_rows || col >= b->n_cols) return 0;
|
|
174
328
|
return ((int32_t *)b->columns[col])[row];
|
|
175
329
|
}
|
|
176
330
|
|
|
177
331
|
int64_t tf_batch_get_timestamp(const tf_batch *b, size_t row, size_t col) {
|
|
178
|
-
if (row >= b->n_rows || col >= b->n_cols) return 0;
|
|
332
|
+
if (!b || row >= b->n_rows || col >= b->n_cols) return 0;
|
|
179
333
|
return ((int64_t *)b->columns[col])[row];
|
|
180
334
|
}
|
|
181
335
|
|
|
182
|
-
int
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
336
|
+
int tf_batch_copy_cell(tf_batch *dst, size_t dst_row, size_t dst_col,
|
|
337
|
+
const tf_batch *src, size_t src_row, size_t src_col) {
|
|
338
|
+
if (!dst || !src || src_row >= src->n_rows || src_col >= src->n_cols || dst_col >= dst->n_cols)
|
|
339
|
+
return TF_ERROR;
|
|
340
|
+
|
|
341
|
+
tf_type type = src->col_types[src_col];
|
|
342
|
+
int is_null = tf_batch_is_null(src, src_row, src_col);
|
|
343
|
+
tf_cell_value value;
|
|
344
|
+
memset(&value, 0, sizeof(value));
|
|
345
|
+
if (!is_null) {
|
|
346
|
+
switch (type) {
|
|
191
347
|
case TF_TYPE_BOOL:
|
|
192
|
-
|
|
348
|
+
value.b = tf_batch_get_bool(src, src_row, src_col);
|
|
193
349
|
break;
|
|
194
350
|
case TF_TYPE_INT64:
|
|
195
|
-
|
|
351
|
+
value.i64 = tf_batch_get_int64(src, src_row, src_col);
|
|
196
352
|
break;
|
|
197
353
|
case TF_TYPE_FLOAT64:
|
|
198
|
-
|
|
354
|
+
value.f64 = tf_batch_get_float64(src, src_row, src_col);
|
|
199
355
|
break;
|
|
200
356
|
case TF_TYPE_STRING:
|
|
201
|
-
|
|
357
|
+
value.str = tf_batch_get_string(src, src_row, src_col);
|
|
202
358
|
break;
|
|
203
359
|
case TF_TYPE_DATE:
|
|
204
|
-
|
|
360
|
+
value.date = tf_batch_get_date(src, src_row, src_col);
|
|
205
361
|
break;
|
|
206
362
|
case TF_TYPE_TIMESTAMP:
|
|
207
|
-
|
|
363
|
+
value.i64 = tf_batch_get_timestamp(src, src_row, src_col);
|
|
208
364
|
break;
|
|
209
365
|
default:
|
|
210
|
-
tf_batch_set_null(dst, dst_row, c);
|
|
211
366
|
break;
|
|
212
367
|
}
|
|
213
368
|
}
|
|
369
|
+
return tf_batch_set_cell_value(dst, dst_row, dst_col, type, is_null, &value);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
int tf_batch_copy_cell_index(tf_batch *dst, size_t dst_row, size_t dst_col,
|
|
373
|
+
const tf_batch *src, size_t src_row, int src_col) {
|
|
374
|
+
if (src_col < 0) return TF_ERROR;
|
|
375
|
+
return tf_batch_copy_cell(dst, dst_row, dst_col, src, src_row, (size_t)src_col);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
int tf_batch_format_cell_as_string(const tf_batch *src, size_t src_row, size_t src_col,
|
|
379
|
+
tf_cell_string_format format, char *buf, size_t buf_size,
|
|
380
|
+
const char **out) {
|
|
381
|
+
if (!src || !buf || buf_size == 0 || !out || src_row >= src->n_rows || src_col >= src->n_cols) {
|
|
382
|
+
return TF_ERROR;
|
|
383
|
+
}
|
|
384
|
+
*out = NULL;
|
|
385
|
+
buf[0] = '\0';
|
|
386
|
+
if (tf_batch_is_null(src, src_row, src_col)) return TF_OK;
|
|
387
|
+
|
|
388
|
+
int n = 0;
|
|
389
|
+
switch (src->col_types[src_col]) {
|
|
390
|
+
case TF_TYPE_STRING:
|
|
391
|
+
*out = tf_batch_get_string(src, src_row, src_col);
|
|
392
|
+
return TF_OK;
|
|
393
|
+
case TF_TYPE_INT64:
|
|
394
|
+
n = snprintf(buf, buf_size, "%lld",
|
|
395
|
+
(long long)tf_batch_get_int64(src, src_row, src_col));
|
|
396
|
+
if (n < 0 || (size_t)n >= buf_size) return TF_ERROR;
|
|
397
|
+
*out = buf;
|
|
398
|
+
return TF_OK;
|
|
399
|
+
case TF_TYPE_FLOAT64:
|
|
400
|
+
if (format == TF_CELL_STRING_HUMAN) {
|
|
401
|
+
n = snprintf(buf, buf_size, "%g", tf_batch_get_float64(src, src_row, src_col));
|
|
402
|
+
if (n < 0 || (size_t)n >= buf_size) return TF_ERROR;
|
|
403
|
+
} else {
|
|
404
|
+
if (tf_format_float64(buf, buf_size,
|
|
405
|
+
tf_batch_get_float64(src, src_row, src_col)) != TF_OK) {
|
|
406
|
+
return TF_ERROR;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
*out = buf;
|
|
410
|
+
return TF_OK;
|
|
411
|
+
case TF_TYPE_BOOL:
|
|
412
|
+
*out = tf_batch_get_bool(src, src_row, src_col) ? "true" : "false";
|
|
413
|
+
return TF_OK;
|
|
414
|
+
case TF_TYPE_DATE:
|
|
415
|
+
if (format == TF_CELL_STRING_NUMERIC_TIME) {
|
|
416
|
+
n = snprintf(buf, buf_size, "%d", (int)tf_batch_get_date(src, src_row, src_col));
|
|
417
|
+
if (n < 0 || (size_t)n >= buf_size) return TF_ERROR;
|
|
418
|
+
} else {
|
|
419
|
+
tf_date_format(tf_batch_get_date(src, src_row, src_col), buf, buf_size);
|
|
420
|
+
}
|
|
421
|
+
*out = buf;
|
|
422
|
+
return TF_OK;
|
|
423
|
+
case TF_TYPE_TIMESTAMP:
|
|
424
|
+
if (format == TF_CELL_STRING_NUMERIC_TIME) {
|
|
425
|
+
n = snprintf(buf, buf_size, "%lld",
|
|
426
|
+
(long long)tf_batch_get_timestamp(src, src_row, src_col));
|
|
427
|
+
if (n < 0 || (size_t)n >= buf_size) return TF_ERROR;
|
|
428
|
+
} else {
|
|
429
|
+
tf_timestamp_format(tf_batch_get_timestamp(src, src_row, src_col), buf, buf_size);
|
|
430
|
+
}
|
|
431
|
+
*out = buf;
|
|
432
|
+
return TF_OK;
|
|
433
|
+
default:
|
|
434
|
+
return TF_OK;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
int tf_batch_copy_cell_as_string(tf_batch *dst, size_t dst_row, size_t dst_col,
|
|
439
|
+
const tf_batch *src, size_t src_row, size_t src_col) {
|
|
440
|
+
if (!dst || !src || src_row >= src->n_rows || src_col >= src->n_cols ||
|
|
441
|
+
dst_col >= dst->n_cols || dst->col_types[dst_col] != TF_TYPE_STRING) {
|
|
442
|
+
return TF_ERROR;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
char buf[64];
|
|
446
|
+
const char *text = NULL;
|
|
447
|
+
if (tf_batch_format_cell_as_string(src, src_row, src_col, TF_CELL_STRING_ROUNDTRIP,
|
|
448
|
+
buf, sizeof(buf), &text) != TF_OK) {
|
|
449
|
+
return TF_ERROR;
|
|
450
|
+
}
|
|
451
|
+
if (!text) {
|
|
452
|
+
return tf_batch_set_null(dst, dst_row, dst_col);
|
|
453
|
+
}
|
|
454
|
+
return tf_batch_set_string(dst, dst_row, dst_col, text);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
int tf_batch_clone_schema(tf_batch *dst, const tf_batch *src) {
|
|
458
|
+
if (!dst || !src || dst->n_cols < src->n_cols) return TF_ERROR;
|
|
459
|
+
for (size_t c = 0; c < src->n_cols; c++) {
|
|
460
|
+
if (tf_batch_set_schema(dst, c, src->col_names[c], src->col_types[c]) != TF_OK)
|
|
461
|
+
return TF_ERROR;
|
|
462
|
+
}
|
|
214
463
|
return TF_OK;
|
|
215
464
|
}
|
|
216
465
|
|
|
466
|
+
int tf_batch_clone_with_extra_cols(tf_batch *dst, const tf_batch *src,
|
|
467
|
+
const char **names, const tf_type *types,
|
|
468
|
+
size_t n_extra) {
|
|
469
|
+
if (!dst || !src) return TF_ERROR;
|
|
470
|
+
size_t needed_cols = 0;
|
|
471
|
+
if (tf_size_add(src->n_cols, n_extra, &needed_cols) != TF_OK || dst->n_cols < needed_cols)
|
|
472
|
+
return TF_ERROR;
|
|
473
|
+
if (n_extra > 0 && (!names || !types)) return TF_ERROR;
|
|
474
|
+
if (tf_batch_clone_schema(dst, src) != TF_OK) return TF_ERROR;
|
|
475
|
+
for (size_t i = 0; i < n_extra; i++) {
|
|
476
|
+
if (tf_batch_set_schema(dst, src->n_cols + i, names[i], types[i]) != TF_OK)
|
|
477
|
+
return TF_ERROR;
|
|
478
|
+
}
|
|
479
|
+
return TF_OK;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
int tf_batch_clone_with_selected_extra_cols(tf_batch *dst,
|
|
483
|
+
const tf_batch *src,
|
|
484
|
+
const tf_batch *extra,
|
|
485
|
+
const int *extra_cols,
|
|
486
|
+
size_t n_extra) {
|
|
487
|
+
if (!dst || !src) return TF_ERROR;
|
|
488
|
+
size_t needed_cols = 0;
|
|
489
|
+
if (tf_size_add(src->n_cols, n_extra, &needed_cols) != TF_OK || dst->n_cols < needed_cols)
|
|
490
|
+
return TF_ERROR;
|
|
491
|
+
if (n_extra > 0 && (!extra || !extra_cols)) return TF_ERROR;
|
|
492
|
+
if (tf_batch_clone_schema(dst, src) != TF_OK) return TF_ERROR;
|
|
493
|
+
for (size_t i = 0; i < n_extra; i++) {
|
|
494
|
+
if (extra_cols[i] < 0 || (size_t)extra_cols[i] >= extra->n_cols)
|
|
495
|
+
return TF_ERROR;
|
|
496
|
+
size_t src_col = (size_t)extra_cols[i];
|
|
497
|
+
if (tf_batch_set_schema(dst, src->n_cols + i,
|
|
498
|
+
extra->col_names[src_col],
|
|
499
|
+
extra->col_types[src_col]) != TF_OK)
|
|
500
|
+
return TF_ERROR;
|
|
501
|
+
}
|
|
502
|
+
return TF_OK;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
int tf_batch_copy_selected_row(tf_batch *dst, size_t dst_row,
|
|
506
|
+
const tf_batch *src, size_t src_row,
|
|
507
|
+
const size_t *cols, size_t n_cols) {
|
|
508
|
+
if (!dst || !src || !cols || src_row >= src->n_rows || dst->n_cols < n_cols) return TF_ERROR;
|
|
509
|
+
size_t need_rows = 0;
|
|
510
|
+
if (tf_size_add(dst_row, 1, &need_rows) != TF_OK) return TF_ERROR;
|
|
511
|
+
if (tf_batch_ensure_capacity(dst, need_rows) != TF_OK) return TF_ERROR;
|
|
512
|
+
for (size_t i = 0; i < n_cols; i++) {
|
|
513
|
+
if (cols[i] == SIZE_MAX) {
|
|
514
|
+
if (tf_batch_set_null(dst, dst_row, i) != TF_OK) return TF_ERROR;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (cols[i] >= src->n_cols) return TF_ERROR;
|
|
518
|
+
if (tf_batch_copy_cell(dst, dst_row, i, src, src_row, cols[i]) != TF_OK)
|
|
519
|
+
return TF_ERROR;
|
|
520
|
+
}
|
|
521
|
+
return TF_OK;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
int tf_batch_copy_selected_columns(tf_batch *dst,
|
|
525
|
+
const tf_batch *src,
|
|
526
|
+
const size_t *cols,
|
|
527
|
+
size_t n_cols) {
|
|
528
|
+
if (!dst || !src || !cols || dst->n_cols < n_cols) return TF_ERROR;
|
|
529
|
+
if (tf_batch_ensure_capacity(dst, src->n_rows) != TF_OK) return TF_ERROR;
|
|
530
|
+
dst->n_rows = 0;
|
|
531
|
+
|
|
532
|
+
for (size_t dc = 0; dc < n_cols; dc++) {
|
|
533
|
+
size_t sc = cols[dc];
|
|
534
|
+
if (sc == SIZE_MAX) {
|
|
535
|
+
if (dst->nulls[dc] && src->n_rows > 0)
|
|
536
|
+
memset(dst->nulls[dc], 1, src->n_rows);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (sc >= src->n_cols || dst->col_types[dc] != src->col_types[sc])
|
|
540
|
+
return TF_ERROR;
|
|
541
|
+
|
|
542
|
+
if (src->n_rows == 0 || dst->col_types[dc] == TF_TYPE_NULL)
|
|
543
|
+
continue;
|
|
544
|
+
if (!dst->columns[dc] || !dst->nulls[dc] || !src->columns[sc] || !src->nulls[sc])
|
|
545
|
+
return TF_ERROR;
|
|
546
|
+
|
|
547
|
+
memcpy(dst->nulls[dc], src->nulls[sc], src->n_rows);
|
|
548
|
+
size_t bytes = 0;
|
|
549
|
+
switch (dst->col_types[dc]) {
|
|
550
|
+
case TF_TYPE_BOOL:
|
|
551
|
+
if (tf_size_mul(src->n_rows, sizeof(uint8_t), &bytes) != TF_OK) return TF_ERROR;
|
|
552
|
+
memcpy(dst->columns[dc], src->columns[sc], bytes);
|
|
553
|
+
break;
|
|
554
|
+
case TF_TYPE_INT64:
|
|
555
|
+
case TF_TYPE_TIMESTAMP:
|
|
556
|
+
if (tf_size_mul(src->n_rows, sizeof(int64_t), &bytes) != TF_OK) return TF_ERROR;
|
|
557
|
+
memcpy(dst->columns[dc], src->columns[sc], bytes);
|
|
558
|
+
break;
|
|
559
|
+
case TF_TYPE_FLOAT64:
|
|
560
|
+
if (tf_size_mul(src->n_rows, sizeof(double), &bytes) != TF_OK) return TF_ERROR;
|
|
561
|
+
memcpy(dst->columns[dc], src->columns[sc], bytes);
|
|
562
|
+
break;
|
|
563
|
+
case TF_TYPE_DATE:
|
|
564
|
+
if (tf_size_mul(src->n_rows, sizeof(int32_t), &bytes) != TF_OK) return TF_ERROR;
|
|
565
|
+
memcpy(dst->columns[dc], src->columns[sc], bytes);
|
|
566
|
+
break;
|
|
567
|
+
case TF_TYPE_STRING: {
|
|
568
|
+
char **dst_col = (char **)dst->columns[dc];
|
|
569
|
+
char **src_col = (char **)src->columns[sc];
|
|
570
|
+
for (size_t r = 0; r < src->n_rows; r++) {
|
|
571
|
+
if (src->nulls[sc][r]) continue;
|
|
572
|
+
const char *value = src_col[r] ? src_col[r] : "";
|
|
573
|
+
size_t len = 0, alloc_bytes = 0;
|
|
574
|
+
if (tf_string_length_bounded(value, TF_MAX_CELL_BYTES,
|
|
575
|
+
&len, "batch", "cell") != TF_OK ||
|
|
576
|
+
tf_size_add(len, 1, &alloc_bytes) != TF_OK)
|
|
577
|
+
return TF_ERROR;
|
|
578
|
+
char *copy = tf_arena_alloc(dst->arena, alloc_bytes);
|
|
579
|
+
if (!copy) return TF_ERROR;
|
|
580
|
+
memcpy(copy, value, alloc_bytes);
|
|
581
|
+
dst_col[r] = copy;
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
default:
|
|
586
|
+
return TF_ERROR;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (src->n_rows > 0 && tf_batch_expose_row(dst, src->n_rows - 1) != TF_OK)
|
|
591
|
+
return TF_ERROR;
|
|
592
|
+
return TF_OK;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
int tf_batch_copy_row(tf_batch *dst, size_t dst_row,
|
|
596
|
+
const tf_batch *src, size_t src_row) {
|
|
597
|
+
if (!dst || !src || src_row >= src->n_rows || dst->n_cols < src->n_cols) return TF_ERROR;
|
|
598
|
+
size_t need_rows = 0;
|
|
599
|
+
if (tf_size_add(dst_row, 1, &need_rows) != TF_OK) return TF_ERROR;
|
|
600
|
+
if (tf_batch_ensure_capacity(dst, need_rows) != TF_OK) return TF_ERROR;
|
|
601
|
+
for (size_t c = 0; c < src->n_cols; c++) {
|
|
602
|
+
if (tf_batch_copy_cell(dst, dst_row, c, src, src_row, c) != TF_OK)
|
|
603
|
+
return TF_ERROR;
|
|
604
|
+
}
|
|
605
|
+
return TF_OK;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
int tf_batch_append_row(tf_batch *dst, const tf_batch *src, size_t src_row) {
|
|
609
|
+
if (!dst || !src || dst->n_cols != src->n_cols) return TF_ERROR;
|
|
610
|
+
size_t dst_row = dst->n_rows;
|
|
611
|
+
if (tf_batch_copy_row(dst, dst_row, src, src_row) != TF_OK) return TF_ERROR;
|
|
612
|
+
return tf_batch_expose_row(dst, dst_row);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
int tf_batch_append_row_with_selected_extra(tf_batch *dst,
|
|
616
|
+
const tf_batch *left,
|
|
617
|
+
size_t left_row,
|
|
618
|
+
const tf_batch *extra,
|
|
619
|
+
size_t extra_row,
|
|
620
|
+
const int *extra_cols,
|
|
621
|
+
size_t n_extra) {
|
|
622
|
+
if (!dst || !left || left_row >= left->n_rows) return TF_ERROR;
|
|
623
|
+
size_t needed_cols = 0;
|
|
624
|
+
if (tf_size_add(left->n_cols, n_extra, &needed_cols) != TF_OK || dst->n_cols < needed_cols)
|
|
625
|
+
return TF_ERROR;
|
|
626
|
+
if (n_extra > 0 && (!extra || !extra_cols || extra_row >= extra->n_rows))
|
|
627
|
+
return TF_ERROR;
|
|
628
|
+
|
|
629
|
+
size_t dst_row = dst->n_rows;
|
|
630
|
+
size_t need_rows = 0;
|
|
631
|
+
if (tf_size_add(dst_row, 1, &need_rows) != TF_OK) return TF_ERROR;
|
|
632
|
+
if (tf_batch_ensure_capacity(dst, need_rows) != TF_OK) return TF_ERROR;
|
|
633
|
+
|
|
634
|
+
for (size_t c = 0; c < left->n_cols; c++) {
|
|
635
|
+
if (tf_batch_copy_cell(dst, dst_row, c, left, left_row, c) != TF_OK)
|
|
636
|
+
return TF_ERROR;
|
|
637
|
+
}
|
|
638
|
+
for (size_t i = 0; i < n_extra; i++) {
|
|
639
|
+
if (tf_batch_copy_cell_index(dst, dst_row, left->n_cols + i,
|
|
640
|
+
extra, extra_row, extra_cols[i]) != TF_OK)
|
|
641
|
+
return TF_ERROR;
|
|
642
|
+
}
|
|
643
|
+
return tf_batch_expose_row(dst, dst_row);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
int tf_batch_append_row_with_null_extra(tf_batch *dst,
|
|
647
|
+
const tf_batch *left,
|
|
648
|
+
size_t left_row,
|
|
649
|
+
size_t n_extra) {
|
|
650
|
+
if (!dst || !left || left_row >= left->n_rows) return TF_ERROR;
|
|
651
|
+
size_t needed_cols = 0;
|
|
652
|
+
if (tf_size_add(left->n_cols, n_extra, &needed_cols) != TF_OK || dst->n_cols < needed_cols)
|
|
653
|
+
return TF_ERROR;
|
|
654
|
+
|
|
655
|
+
size_t dst_row = dst->n_rows;
|
|
656
|
+
size_t need_rows = 0;
|
|
657
|
+
if (tf_size_add(dst_row, 1, &need_rows) != TF_OK) return TF_ERROR;
|
|
658
|
+
if (tf_batch_ensure_capacity(dst, need_rows) != TF_OK) return TF_ERROR;
|
|
659
|
+
|
|
660
|
+
for (size_t c = 0; c < left->n_cols; c++) {
|
|
661
|
+
if (tf_batch_copy_cell(dst, dst_row, c, left, left_row, c) != TF_OK)
|
|
662
|
+
return TF_ERROR;
|
|
663
|
+
}
|
|
664
|
+
for (size_t i = 0; i < n_extra; i++) {
|
|
665
|
+
if (tf_batch_set_null(dst, dst_row, left->n_cols + i) != TF_OK)
|
|
666
|
+
return TF_ERROR;
|
|
667
|
+
}
|
|
668
|
+
return tf_batch_expose_row(dst, dst_row);
|
|
669
|
+
}
|
|
670
|
+
|
|
217
671
|
int tf_batch_col_index(const tf_batch *b, const char *name) {
|
|
218
672
|
for (size_t i = 0; i < b->n_cols; i++) {
|
|
219
673
|
if (b->col_names[i] && strcmp(b->col_names[i], name) == 0)
|
|
@@ -222,8 +676,301 @@ int tf_batch_col_index(const tf_batch *b, const char *name) {
|
|
|
222
676
|
return -1;
|
|
223
677
|
}
|
|
224
678
|
|
|
679
|
+
|
|
680
|
+
/* ---- Audit / side-channel row serialization helpers ---- */
|
|
681
|
+
|
|
682
|
+
void tf_audit_options_init(tf_audit_options *opts, int default_include_row) {
|
|
683
|
+
if (!opts) return;
|
|
684
|
+
memset(opts, 0, sizeof(*opts));
|
|
685
|
+
opts->include_row = default_include_row ? 1 : 0;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
static void audit_string_list_free(char **items, size_t n_items) {
|
|
689
|
+
if (!items) return;
|
|
690
|
+
for (size_t i = 0; i < n_items; i++) free(items[i]);
|
|
691
|
+
free(items);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
void tf_audit_options_free(tf_audit_options *opts) {
|
|
695
|
+
if (!opts) return;
|
|
696
|
+
audit_string_list_free(opts->columns, opts->n_columns);
|
|
697
|
+
audit_string_list_free(opts->redact_columns, opts->n_redact_columns);
|
|
698
|
+
audit_string_list_free(opts->hash_columns, opts->n_hash_columns);
|
|
699
|
+
memset(opts, 0, sizeof(*opts));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
static int audit_add_string(char ***items, size_t *n_items, const char *value) {
|
|
703
|
+
if (!items || !n_items || !value || !value[0]) return TF_ERROR;
|
|
704
|
+
char *dup = strdup(value);
|
|
705
|
+
if (!dup) return TF_ERROR;
|
|
706
|
+
size_t next_count = 0;
|
|
707
|
+
if (tf_size_add(*n_items, 1, &next_count) != TF_OK) {
|
|
708
|
+
free(dup);
|
|
709
|
+
return TF_ERROR;
|
|
710
|
+
}
|
|
711
|
+
char **next = tf_reallocarray_checked(*items, next_count, sizeof(char *));
|
|
712
|
+
if (!next) {
|
|
713
|
+
free(dup);
|
|
714
|
+
return TF_ERROR;
|
|
715
|
+
}
|
|
716
|
+
*items = next;
|
|
717
|
+
(*items)[*n_items] = dup;
|
|
718
|
+
(*n_items)++;
|
|
719
|
+
return TF_OK;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
static char *audit_trim(char *s) {
|
|
723
|
+
while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++;
|
|
724
|
+
char *end = s + strlen(s);
|
|
725
|
+
while (end > s && (end[-1] == ' ' || end[-1] == '\t' || end[-1] == '\n' || end[-1] == '\r')) end--;
|
|
726
|
+
*end = '\0';
|
|
727
|
+
return s;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
static int audit_parse_string_list_value(const cJSON *value, char ***items, size_t *n_items,
|
|
731
|
+
const char *context, const char *name) {
|
|
732
|
+
if (!value) return TF_OK;
|
|
733
|
+
if (cJSON_IsArray(value)) {
|
|
734
|
+
int n = cJSON_GetArraySize((cJSON *)value);
|
|
735
|
+
for (int i = 0; i < n; i++) {
|
|
736
|
+
cJSON *item = cJSON_GetArrayItem((cJSON *)value, i);
|
|
737
|
+
if (!cJSON_IsString(item) || !item->valuestring[0]) goto invalid;
|
|
738
|
+
if (audit_add_string(items, n_items, item->valuestring) != TF_OK) return TF_ERROR;
|
|
739
|
+
}
|
|
740
|
+
return TF_OK;
|
|
741
|
+
}
|
|
742
|
+
if (cJSON_IsString(value)) {
|
|
743
|
+
char *copy = strdup(value->valuestring ? value->valuestring : "");
|
|
744
|
+
if (!copy) return TF_ERROR;
|
|
745
|
+
char *save = NULL;
|
|
746
|
+
for (char *tok = strtok_r(copy, ",", &save); tok; tok = strtok_r(NULL, ",", &save)) {
|
|
747
|
+
char *trimmed = audit_trim(tok);
|
|
748
|
+
if (trimmed[0] && audit_add_string(items, n_items, trimmed) != TF_OK) {
|
|
749
|
+
free(copy);
|
|
750
|
+
return TF_ERROR;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
free(copy);
|
|
754
|
+
return TF_OK;
|
|
755
|
+
}
|
|
756
|
+
invalid:
|
|
757
|
+
{
|
|
758
|
+
char msg[256];
|
|
759
|
+
snprintf(msg, sizeof(msg), "%s: %s must be a string or string array",
|
|
760
|
+
context ? context : "audit", name ? name : "audit option");
|
|
761
|
+
tf_set_last_error(msg);
|
|
762
|
+
}
|
|
763
|
+
return TF_ERROR;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
static const cJSON *audit_get_arg(const cJSON *args, const char *snake, const char *camel) {
|
|
767
|
+
const cJSON *item = args && snake ? cJSON_GetObjectItemCaseSensitive(args, snake) : NULL;
|
|
768
|
+
if (!item && args && camel) item = cJSON_GetObjectItemCaseSensitive(args, camel);
|
|
769
|
+
return item;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
int tf_audit_options_parse(tf_audit_options *opts, const cJSON *args, const char *context) {
|
|
773
|
+
if (!opts) return TF_ERROR;
|
|
774
|
+
if (!args) return TF_OK;
|
|
775
|
+
|
|
776
|
+
const cJSON *include = audit_get_arg(args, "audit_include_row", "auditIncludeRow");
|
|
777
|
+
if (include) {
|
|
778
|
+
if (!cJSON_IsBool(include)) {
|
|
779
|
+
char msg[256];
|
|
780
|
+
snprintf(msg, sizeof(msg), "%s: audit_include_row must be true or false", context ? context : "audit");
|
|
781
|
+
tf_set_last_error(msg);
|
|
782
|
+
return TF_ERROR;
|
|
783
|
+
}
|
|
784
|
+
opts->include_row = cJSON_IsTrue(include) ? 1 : 0;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (audit_parse_string_list_value(audit_get_arg(args, "audit_columns", "auditColumns"),
|
|
788
|
+
&opts->columns, &opts->n_columns, context, "audit_columns") != TF_OK ||
|
|
789
|
+
audit_parse_string_list_value(audit_get_arg(args, "audit_redact", "auditRedact"),
|
|
790
|
+
&opts->redact_columns, &opts->n_redact_columns, context, "audit_redact") != TF_OK ||
|
|
791
|
+
audit_parse_string_list_value(audit_get_arg(args, "audit_hash_columns", "auditHashColumns"),
|
|
792
|
+
&opts->hash_columns, &opts->n_hash_columns, context, "audit_hash_columns") != TF_OK) {
|
|
793
|
+
return TF_ERROR;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (tf_json_get_size_arg(args, "audit_max_bytes", 0, TF_MAX_ERROR_BYTES, &opts->max_bytes, context) < 0 ||
|
|
797
|
+
tf_json_get_size_arg(args, "auditMaxBytes", 0, TF_MAX_ERROR_BYTES, &opts->max_bytes, context) < 0 ||
|
|
798
|
+
tf_json_get_size_arg(args, "audit_max_cell_bytes", 0, TF_MAX_ERROR_BYTES, &opts->max_cell_bytes, context) < 0 ||
|
|
799
|
+
tf_json_get_size_arg(args, "auditMaxCellBytes", 0, TF_MAX_ERROR_BYTES, &opts->max_cell_bytes, context) < 0) {
|
|
800
|
+
return TF_ERROR;
|
|
801
|
+
}
|
|
802
|
+
return TF_OK;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
static int audit_name_in_list(char **items, size_t n_items, const char *name) {
|
|
806
|
+
if (!name) return 0;
|
|
807
|
+
for (size_t i = 0; i < n_items; i++) {
|
|
808
|
+
if (items[i] && strcmp(items[i], name) == 0) return 1;
|
|
809
|
+
}
|
|
810
|
+
return 0;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
int tf_audit_column_is_redacted(const tf_audit_options *opts, const char *column) {
|
|
814
|
+
return opts ? audit_name_in_list(opts->redact_columns, opts->n_redact_columns, column) : 0;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
int tf_audit_column_is_hashed(const tf_audit_options *opts, const char *column) {
|
|
818
|
+
return opts ? audit_name_in_list(opts->hash_columns, opts->n_hash_columns, column) : 0;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
int tf_audit_hash_string(const char *value, char *out, size_t out_size) {
|
|
822
|
+
if (!out || out_size < 25) return TF_ERROR;
|
|
823
|
+
uint64_t h = UINT64_C(1469598103934665603);
|
|
824
|
+
const unsigned char *p = (const unsigned char *)(value ? value : "");
|
|
825
|
+
while (*p) {
|
|
826
|
+
h ^= (uint64_t)*p++;
|
|
827
|
+
h *= UINT64_C(1099511628211);
|
|
828
|
+
}
|
|
829
|
+
snprintf(out, out_size, "fnv1a64:%016llx", (unsigned long long)h);
|
|
830
|
+
return TF_OK;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const char *tf_audit_format_string_for_column(const tf_audit_options *opts, const char *column,
|
|
834
|
+
const char *value, char *buf, size_t buf_size) {
|
|
835
|
+
const char *src = value ? value : "";
|
|
836
|
+
if (!opts || !buf || buf_size == 0) return src;
|
|
837
|
+
if (tf_audit_column_is_redacted(opts, column)) {
|
|
838
|
+
snprintf(buf, buf_size, "[REDACTED]");
|
|
839
|
+
return buf;
|
|
840
|
+
}
|
|
841
|
+
if (tf_audit_column_is_hashed(opts, column)) {
|
|
842
|
+
if (tf_audit_hash_string(src, buf, buf_size) == TF_OK) return buf;
|
|
843
|
+
return "[HASHED]";
|
|
844
|
+
}
|
|
845
|
+
if (opts->max_cell_bytes > 0 && strlen(src) > opts->max_cell_bytes) {
|
|
846
|
+
size_t n = opts->max_cell_bytes;
|
|
847
|
+
if (n + 4 > buf_size) n = buf_size > 4 ? buf_size - 4 : 0;
|
|
848
|
+
if (n > 0) memcpy(buf, src, n);
|
|
849
|
+
snprintf(buf + n, buf_size - n, "...");
|
|
850
|
+
return buf;
|
|
851
|
+
}
|
|
852
|
+
return src;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
cJSON *tf_audit_string_to_json_for_column(const tf_audit_options *opts, const char *column,
|
|
856
|
+
const char *value) {
|
|
857
|
+
char *safe = tf_audit_format_string_dup_for_column(opts, column, value);
|
|
858
|
+
if (!safe) return NULL;
|
|
859
|
+
cJSON *item = cJSON_CreateString(safe);
|
|
860
|
+
free(safe);
|
|
861
|
+
return item;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
char *tf_audit_format_string_dup_for_column(const tf_audit_options *opts,
|
|
865
|
+
const char *column,
|
|
866
|
+
const char *value) {
|
|
867
|
+
const char *src = value ? value : "";
|
|
868
|
+
if (!opts) return tf_strdup_checked(src);
|
|
869
|
+
if (tf_audit_column_is_redacted(opts, column)) return tf_strdup_checked("[REDACTED]");
|
|
870
|
+
if (tf_audit_column_is_hashed(opts, column)) {
|
|
871
|
+
char hash[32];
|
|
872
|
+
if (tf_audit_hash_string(src, hash, sizeof(hash)) != TF_OK) return tf_strdup_checked("[HASHED]");
|
|
873
|
+
return tf_strdup_checked(hash);
|
|
874
|
+
}
|
|
875
|
+
if (opts->max_cell_bytes > 0 && strlen(src) > opts->max_cell_bytes) {
|
|
876
|
+
size_t payload_len = opts->max_cell_bytes;
|
|
877
|
+
size_t with_ellipsis = 0;
|
|
878
|
+
size_t alloc_len = 0;
|
|
879
|
+
if (tf_size_add(payload_len, 3, &with_ellipsis) != TF_OK ||
|
|
880
|
+
tf_size_add(with_ellipsis, 1, &alloc_len) != TF_OK) {
|
|
881
|
+
return NULL;
|
|
882
|
+
}
|
|
883
|
+
char *out = tf_mallocarray_checked(alloc_len, sizeof(char));
|
|
884
|
+
if (!out) return NULL;
|
|
885
|
+
if (payload_len > 0) memcpy(out, src, payload_len);
|
|
886
|
+
memcpy(out + payload_len, "...", 3);
|
|
887
|
+
out[with_ellipsis] = '\0';
|
|
888
|
+
return out;
|
|
889
|
+
}
|
|
890
|
+
return tf_strdup_checked(src);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
int tf_json_add_audit_string(cJSON *obj, const char *name,
|
|
894
|
+
const tf_audit_options *opts,
|
|
895
|
+
const char *column, const char *value) {
|
|
896
|
+
cJSON *item = tf_audit_string_to_json_for_column(opts, column, value);
|
|
897
|
+
if (!item) return TF_ERROR;
|
|
898
|
+
return tf_json_add_item(obj, name, item);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
static cJSON *audit_string_json(const tf_audit_options *opts, const char *column, const char *value) {
|
|
902
|
+
return tf_audit_string_to_json_for_column(opts, column, value);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
cJSON *tf_audit_cell_to_json(const tf_batch *b, size_t row, size_t col, const tf_audit_options *opts) {
|
|
906
|
+
const char *name = b->col_names[col] ? b->col_names[col] : "";
|
|
907
|
+
if (tf_batch_is_null(b, row, col)) return cJSON_CreateNull();
|
|
908
|
+
if (tf_audit_column_is_redacted(opts, name) || tf_audit_column_is_hashed(opts, name)) {
|
|
909
|
+
char raw[128];
|
|
910
|
+
const char *text = NULL;
|
|
911
|
+
if (tf_batch_format_cell_as_string(b, row, col, TF_CELL_STRING_NUMERIC_TIME,
|
|
912
|
+
raw, sizeof(raw), &text) != TF_OK)
|
|
913
|
+
return NULL;
|
|
914
|
+
return audit_string_json(opts, name, text ? text : "");
|
|
915
|
+
}
|
|
916
|
+
switch (b->col_types[col]) {
|
|
917
|
+
case TF_TYPE_BOOL: return cJSON_CreateBool(tf_batch_get_bool(b, row, col));
|
|
918
|
+
case TF_TYPE_INT64: return cJSON_CreateNumber((double)tf_batch_get_int64(b, row, col));
|
|
919
|
+
case TF_TYPE_FLOAT64: return cJSON_CreateNumber(tf_batch_get_float64(b, row, col));
|
|
920
|
+
case TF_TYPE_STRING: return audit_string_json(opts, name, tf_batch_get_string(b, row, col));
|
|
921
|
+
case TF_TYPE_DATE: return cJSON_CreateNumber((double)tf_batch_get_date(b, row, col));
|
|
922
|
+
case TF_TYPE_TIMESTAMP: return cJSON_CreateNumber((double)tf_batch_get_timestamp(b, row, col));
|
|
923
|
+
default: return cJSON_CreateNull();
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
cJSON *tf_audit_row_to_json(const tf_batch *b, size_t row, const tf_audit_options *opts) {
|
|
928
|
+
if (!b || row >= b->n_rows) return NULL;
|
|
929
|
+
if (opts && !opts->include_row) return NULL;
|
|
930
|
+
cJSON *obj = cJSON_CreateObject();
|
|
931
|
+
if (!obj) return NULL;
|
|
932
|
+
for (size_t c = 0; c < b->n_cols; c++) {
|
|
933
|
+
const char *name = b->col_names[c] ? b->col_names[c] : "";
|
|
934
|
+
if (opts && opts->n_columns > 0 && !audit_name_in_list(opts->columns, opts->n_columns, name)) continue;
|
|
935
|
+
cJSON *value = tf_audit_cell_to_json(b, row, c, opts);
|
|
936
|
+
if (!value || tf_json_add_item(obj, name, value) != TF_OK) {
|
|
937
|
+
cJSON_Delete(obj);
|
|
938
|
+
return NULL;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (opts && opts->max_bytes > 0) {
|
|
942
|
+
char *printed = cJSON_PrintUnformatted(obj);
|
|
943
|
+
if (!printed) { cJSON_Delete(obj); return NULL; }
|
|
944
|
+
size_t n = strlen(printed);
|
|
945
|
+
free(printed);
|
|
946
|
+
if (n > opts->max_bytes) {
|
|
947
|
+
cJSON_Delete(obj);
|
|
948
|
+
obj = cJSON_CreateObject();
|
|
949
|
+
if (!obj) return NULL;
|
|
950
|
+
if (tf_json_add_bool(obj, "_audit_truncated", 1) != TF_OK ||
|
|
951
|
+
tf_json_add_number(obj, "max_bytes", (double)opts->max_bytes) != TF_OK) {
|
|
952
|
+
cJSON_Delete(obj);
|
|
953
|
+
return NULL;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return obj;
|
|
958
|
+
}
|
|
959
|
+
|
|
225
960
|
void tf_batch_free(tf_batch *b) {
|
|
226
961
|
if (!b) return;
|
|
227
962
|
tf_arena *arena = b->arena;
|
|
228
963
|
tf_arena_free(arena);
|
|
229
964
|
}
|
|
965
|
+
|
|
966
|
+
void tf_batch_array_free_items(tf_batch **batches, size_t n_batches) {
|
|
967
|
+
if (!batches) return;
|
|
968
|
+
for (size_t i = 0; i < n_batches; i++) {
|
|
969
|
+
if (batches[i]) tf_batch_free(batches[i]);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
void tf_batch_array_free(tf_batch **batches, size_t n_batches) {
|
|
974
|
+
tf_batch_array_free_items(batches, n_batches);
|
|
975
|
+
free(batches);
|
|
976
|
+
}
|