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/op_join.c
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* op_join.c — Hash join with lookup CSV file.
|
|
3
3
|
*
|
|
4
|
-
* Config: {"file": "lookup.csv", "on": "id" or "left=right",
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Config: {"file": "lookup.csv", "on": "id" or "left=right",
|
|
5
|
+
* "how": "inner|left|semi|anti", "max_lookup_bytes": 1048576,
|
|
6
|
+
* "max_lookup_rows": 100000, "max_lookup_keys": 100000,
|
|
7
|
+
* "max_state_bytes": 8388608,
|
|
8
|
+
* "max_matches_per_row": 10, "max_output_rows": 1000000}
|
|
9
|
+
* Loads a capped lookup file once, then streams main data through hash probes.
|
|
10
|
+
* Inner/left joins append lookup columns. Semi/anti joins keep only left rows
|
|
11
|
+
* and never duplicate them.
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
14
|
#include "internal.h"
|
|
15
|
+
#include "spill.h"
|
|
10
16
|
#include "date_utils.h"
|
|
11
17
|
#include "cJSON.h"
|
|
12
18
|
#include <stdlib.h>
|
|
13
19
|
#include <string.h>
|
|
14
20
|
#include <stdio.h>
|
|
21
|
+
#include <limits.h>
|
|
22
|
+
#include <errno.h>
|
|
23
|
+
#include <stdint.h>
|
|
24
|
+
#include <unistd.h>
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
#define JOIN_DEFAULT_RUN_ROWS 8192
|
|
27
|
+
#define JOIN_DEFAULT_OUTPUT_ROWS 1024
|
|
28
|
+
#define JOIN_MIN_RUN_ROWS 16
|
|
29
|
+
|
|
30
|
+
/* Hash map bucket: key -> array of row indices in lookup batch */
|
|
17
31
|
typedef struct {
|
|
18
32
|
char *key;
|
|
19
33
|
size_t *rows;
|
|
@@ -25,25 +39,184 @@ typedef struct {
|
|
|
25
39
|
join_bucket *buckets;
|
|
26
40
|
size_t n_buckets; /* power of 2 */
|
|
27
41
|
size_t count;
|
|
42
|
+
size_t key_bytes;
|
|
43
|
+
size_t row_ref_capacity;
|
|
28
44
|
} join_hash_map;
|
|
29
45
|
|
|
46
|
+
typedef tf_owned_cell_value join_key_data;
|
|
47
|
+
|
|
48
|
+
typedef struct {
|
|
49
|
+
int valid;
|
|
50
|
+
int is_null;
|
|
51
|
+
tf_type type;
|
|
52
|
+
join_key_data v;
|
|
53
|
+
} join_key_value;
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
typedef struct {
|
|
57
|
+
uint64_t ordinal;
|
|
58
|
+
uint8_t *nulls;
|
|
59
|
+
join_key_data *cells;
|
|
60
|
+
} join_spill_row;
|
|
61
|
+
|
|
62
|
+
typedef struct {
|
|
63
|
+
FILE *file;
|
|
64
|
+
join_spill_row row;
|
|
65
|
+
int has_row;
|
|
66
|
+
int done;
|
|
67
|
+
} join_spill_reader;
|
|
68
|
+
|
|
30
69
|
typedef struct {
|
|
31
70
|
char *file;
|
|
71
|
+
char *validated_file;
|
|
32
72
|
char *left_col;
|
|
33
73
|
char *right_col;
|
|
34
|
-
int how; /* 0=inner, 1=left */
|
|
74
|
+
int how; /* 0=inner, 1=left, 2=semi, 3=anti */
|
|
75
|
+
int sorted; /* lookup and input are sorted by join key */
|
|
76
|
+
size_t max_lookup_rows; /* 0 = unlimited */
|
|
77
|
+
size_t max_lookup_keys; /* 0 = unlimited */
|
|
78
|
+
size_t max_lookup_bytes; /* 0 = unlimited */
|
|
79
|
+
size_t max_state_bytes; /* 0 = unlimited */
|
|
80
|
+
size_t max_matches_per_row; /* 0 = unlimited */
|
|
81
|
+
size_t max_output_rows; /* 0 = unlimited */
|
|
82
|
+
size_t output_rows; /* emitted rows across process calls */
|
|
83
|
+
size_t lookup_rows; /* decoded lookup rows retained or scanned */
|
|
84
|
+
|
|
85
|
+
int use_spill; /* exact external semi/anti filtering join */
|
|
86
|
+
char *spill_dir;
|
|
87
|
+
tf_spill_session *spill;
|
|
88
|
+
size_t spill_memory_bytes;
|
|
89
|
+
size_t configured_run_rows;
|
|
90
|
+
size_t run_rows;
|
|
91
|
+
size_t output_batch_rows;
|
|
92
|
+
|
|
93
|
+
int spill_has_schema;
|
|
94
|
+
char **spill_schema_names;
|
|
95
|
+
tf_type *spill_schema_types;
|
|
96
|
+
size_t spill_n_cols;
|
|
97
|
+
int spill_left_join_col;
|
|
98
|
+
|
|
99
|
+
char **spill_lookup_schema_names;
|
|
100
|
+
tf_type *spill_lookup_schema_types;
|
|
101
|
+
size_t spill_lookup_n_cols;
|
|
102
|
+
int spill_lookup_join_col;
|
|
103
|
+
char **spill_output_schema_names;
|
|
104
|
+
tf_type *spill_output_schema_types;
|
|
105
|
+
size_t spill_output_n_cols;
|
|
106
|
+
|
|
107
|
+
tf_batch *spill_left_buf;
|
|
108
|
+
tf_batch *spill_lookup_buf;
|
|
109
|
+
tf_batch *spill_out_buf;
|
|
110
|
+
uint64_t *spill_left_ordinals;
|
|
111
|
+
uint64_t *spill_lookup_ordinals;
|
|
112
|
+
uint64_t *spill_out_ordinals;
|
|
113
|
+
size_t spill_left_ordinal_cap;
|
|
114
|
+
size_t spill_lookup_ordinal_cap;
|
|
115
|
+
size_t spill_out_ordinal_cap;
|
|
116
|
+
uint64_t spill_next_left_ordinal;
|
|
117
|
+
uint64_t spill_next_lookup_ordinal;
|
|
118
|
+
|
|
119
|
+
char **spill_left_run_paths;
|
|
120
|
+
size_t spill_n_left_runs;
|
|
121
|
+
size_t spill_cap_left_runs;
|
|
122
|
+
char **spill_lookup_run_paths;
|
|
123
|
+
size_t spill_n_lookup_runs;
|
|
124
|
+
size_t spill_cap_lookup_runs;
|
|
125
|
+
char **spill_out_run_paths;
|
|
126
|
+
size_t spill_n_out_runs;
|
|
127
|
+
size_t spill_cap_out_runs;
|
|
128
|
+
size_t spill_left_run_seq;
|
|
129
|
+
size_t spill_lookup_run_seq;
|
|
130
|
+
size_t spill_out_run_seq;
|
|
131
|
+
|
|
132
|
+
join_spill_reader *spill_left_readers;
|
|
133
|
+
size_t spill_n_left_readers;
|
|
134
|
+
join_spill_reader *spill_lookup_readers;
|
|
135
|
+
size_t spill_n_lookup_readers;
|
|
136
|
+
join_spill_reader *spill_out_readers;
|
|
137
|
+
size_t spill_n_out_readers;
|
|
138
|
+
|
|
139
|
+
int spill_lookup_loaded;
|
|
140
|
+
int spill_key_merge_done;
|
|
141
|
+
int spill_output_merge_started;
|
|
142
|
+
int spill_output_merge_done;
|
|
143
|
+
char *spill_last_lookup_key;
|
|
144
|
+
|
|
145
|
+
size_t spill_bytes;
|
|
146
|
+
size_t spill_runs;
|
|
147
|
+
size_t spill_output_batches;
|
|
148
|
+
size_t spill_output_rows;
|
|
149
|
+
size_t spill_kept_rows;
|
|
150
|
+
size_t spill_lookup_keys;
|
|
151
|
+
size_t spill_lookup_key_bytes;
|
|
35
152
|
|
|
36
153
|
tf_batch *lookup;
|
|
37
154
|
int loaded;
|
|
38
155
|
int lookup_join_col;
|
|
156
|
+
tf_type lookup_key_type;
|
|
157
|
+
int have_lookup_key_type;
|
|
39
158
|
|
|
40
159
|
/* Lookup columns to include in output (excluding join key) */
|
|
41
160
|
int *lookup_out_cols;
|
|
42
161
|
size_t n_lookup_out;
|
|
43
162
|
|
|
44
163
|
join_hash_map map;
|
|
164
|
+
|
|
165
|
+
FILE *sorted_file;
|
|
166
|
+
tf_decoder *sorted_decoder;
|
|
167
|
+
tf_batch **sorted_batches;
|
|
168
|
+
size_t sorted_n_batches;
|
|
169
|
+
size_t sorted_batch_index;
|
|
170
|
+
tf_batch *sorted_current;
|
|
171
|
+
size_t sorted_row;
|
|
172
|
+
int sorted_flushed;
|
|
173
|
+
int sorted_have_row;
|
|
174
|
+
int sorted_exhausted;
|
|
175
|
+
join_key_value prev_lookup_key;
|
|
176
|
+
int have_prev_lookup_key;
|
|
177
|
+
join_key_value prev_left_key;
|
|
178
|
+
int have_prev_left_key;
|
|
179
|
+
tf_batch *right_run;
|
|
180
|
+
join_key_value right_run_key;
|
|
181
|
+
int have_right_run;
|
|
182
|
+
char **lookup_schema_names;
|
|
183
|
+
tf_type *lookup_schema_types;
|
|
184
|
+
size_t n_lookup_schema_cols;
|
|
45
185
|
} join_state;
|
|
46
186
|
|
|
187
|
+
static size_t join_batch_storage_bytes(const tf_batch *b);
|
|
188
|
+
static size_t join_hash_map_retained_bytes(const join_hash_map *map);
|
|
189
|
+
static size_t join_unsorted_retained_state_bytes(const join_hash_map *map,
|
|
190
|
+
const tf_batch *lookup,
|
|
191
|
+
size_t n_lookup_out);
|
|
192
|
+
static int join_check_unsorted_state_bytes(const join_state *st,
|
|
193
|
+
const join_hash_map *map,
|
|
194
|
+
const tf_batch *lookup,
|
|
195
|
+
size_t n_lookup_out,
|
|
196
|
+
tf_side_channels *side);
|
|
197
|
+
static int join_reserve_output_row(join_state *st, tf_side_channels *side);
|
|
198
|
+
static size_t join_spill_retained_state_bytes(const join_state *st);
|
|
199
|
+
|
|
200
|
+
TF_WARN_UNUSED static int join_write_error(tf_side_channels *side, const char *msg) {
|
|
201
|
+
return tf_side_write_error(side, msg);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
TF_WARN_UNUSED static int join_limit_error_context(tf_side_channels *side, const char *field,
|
|
205
|
+
size_t limit, size_t actual,
|
|
206
|
+
const char *context) {
|
|
207
|
+
char msg[224];
|
|
208
|
+
snprintf(msg, sizeof(msg),
|
|
209
|
+
"join: %s=%zu exceeded %s (%zu)",
|
|
210
|
+
field, limit, context, actual);
|
|
211
|
+
return join_write_error(side, msg);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
TF_WARN_UNUSED static int join_limit_error(tf_side_channels *side, const char *field,
|
|
215
|
+
size_t limit, size_t actual) {
|
|
216
|
+
return join_limit_error_context(side, field, limit, actual,
|
|
217
|
+
"while loading lookup side");
|
|
218
|
+
}
|
|
219
|
+
|
|
47
220
|
/* FNV-1a hash */
|
|
48
221
|
static uint64_t fnv1a(const char *s) {
|
|
49
222
|
uint64_t h = 14695981039346656037ULL;
|
|
@@ -51,39 +224,111 @@ static uint64_t fnv1a(const char *s) {
|
|
|
51
224
|
return h;
|
|
52
225
|
}
|
|
53
226
|
|
|
54
|
-
static
|
|
227
|
+
static int map_init(join_hash_map *m, size_t hint) {
|
|
55
228
|
size_t n = 64;
|
|
56
|
-
|
|
57
|
-
|
|
229
|
+
size_t target = 0;
|
|
230
|
+
if (tf_size_mul(hint, 2, &target) != TF_OK) return -1;
|
|
231
|
+
while (n < target) {
|
|
232
|
+
if (n > (SIZE_MAX / 2)) return -1;
|
|
233
|
+
n *= 2;
|
|
234
|
+
}
|
|
235
|
+
m->buckets = tf_callocarray_checked(n, sizeof(join_bucket));
|
|
236
|
+
if (!m->buckets) return -1;
|
|
58
237
|
m->n_buckets = n;
|
|
59
238
|
m->count = 0;
|
|
239
|
+
m->key_bytes = 0;
|
|
240
|
+
m->row_ref_capacity = 0;
|
|
241
|
+
return 0;
|
|
60
242
|
}
|
|
61
243
|
|
|
62
|
-
static
|
|
244
|
+
static int map_insert(join_state *st, join_hash_map *m,
|
|
245
|
+
const char *key, size_t row,
|
|
246
|
+
tf_side_channels *side) {
|
|
63
247
|
uint64_t h = fnv1a(key);
|
|
64
248
|
size_t idx = h & (m->n_buckets - 1);
|
|
65
249
|
/* Linear probe */
|
|
66
250
|
while (m->buckets[idx].key) {
|
|
67
251
|
if (strcmp(m->buckets[idx].key, key) == 0) {
|
|
252
|
+
/* Filtering joins only need key presence, not duplicate right rows. */
|
|
253
|
+
if (st->how >= 2) return 0;
|
|
254
|
+
|
|
68
255
|
/* Add row to existing bucket */
|
|
69
256
|
join_bucket *b = &m->buckets[idx];
|
|
257
|
+
if (st->max_matches_per_row > 0 && b->n_rows >= st->max_matches_per_row) {
|
|
258
|
+
if (join_limit_error_context(side, "max_matches_per_row",
|
|
259
|
+
st->max_matches_per_row, b->n_rows + 1,
|
|
260
|
+
"for lookup key") != TF_OK)
|
|
261
|
+
return -1;
|
|
262
|
+
return -1;
|
|
263
|
+
}
|
|
70
264
|
if (b->n_rows >= b->rows_cap) {
|
|
71
|
-
|
|
72
|
-
|
|
265
|
+
size_t old_cap = b->rows_cap;
|
|
266
|
+
size_t need = 0;
|
|
267
|
+
size_t new_cap = 0;
|
|
268
|
+
size_t new_row_ref_capacity = 0;
|
|
269
|
+
if (tf_size_add(b->n_rows, 1, &need) != TF_OK ||
|
|
270
|
+
tf_size_grow_pow2(b->rows_cap, need, 4, &new_cap) != TF_OK ||
|
|
271
|
+
tf_size_add(m->row_ref_capacity, new_cap - old_cap,
|
|
272
|
+
&new_row_ref_capacity) != TF_OK) {
|
|
273
|
+
return -1;
|
|
274
|
+
}
|
|
275
|
+
size_t *tmp = tf_reallocarray_checked(b->rows, new_cap, sizeof(size_t));
|
|
276
|
+
if (!tmp) return -1;
|
|
277
|
+
b->rows = tmp;
|
|
278
|
+
b->rows_cap = new_cap;
|
|
279
|
+
m->row_ref_capacity = new_row_ref_capacity;
|
|
73
280
|
}
|
|
74
281
|
b->rows[b->n_rows++] = row;
|
|
75
|
-
return;
|
|
282
|
+
return 0;
|
|
76
283
|
}
|
|
77
284
|
idx = (idx + 1) & (m->n_buckets - 1);
|
|
78
285
|
}
|
|
286
|
+
|
|
287
|
+
if (st->max_lookup_keys > 0 && m->count >= st->max_lookup_keys) {
|
|
288
|
+
if (join_limit_error(side, "max_lookup_keys", st->max_lookup_keys,
|
|
289
|
+
m->count + 1) != TF_OK)
|
|
290
|
+
return -1;
|
|
291
|
+
return -1;
|
|
292
|
+
}
|
|
293
|
+
|
|
79
294
|
/* New key */
|
|
80
295
|
join_bucket *b = &m->buckets[idx];
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
296
|
+
char *key_copy = strdup(key);
|
|
297
|
+
if (!key_copy) return -1;
|
|
298
|
+
size_t key_bytes_delta = 0;
|
|
299
|
+
size_t new_key_bytes = 0;
|
|
300
|
+
if (tf_size_add(strlen(key_copy), 1, &key_bytes_delta) != TF_OK ||
|
|
301
|
+
tf_size_add(m->key_bytes, key_bytes_delta, &new_key_bytes) != TF_OK) {
|
|
302
|
+
free(key_copy);
|
|
303
|
+
return -1;
|
|
304
|
+
}
|
|
305
|
+
size_t *rows = NULL;
|
|
306
|
+
size_t rows_cap = 0;
|
|
307
|
+
size_t n_rows = 0;
|
|
308
|
+
size_t new_row_ref_capacity = m->row_ref_capacity;
|
|
309
|
+
if (st->how >= 2) {
|
|
310
|
+
rows = NULL;
|
|
311
|
+
rows_cap = 0;
|
|
312
|
+
n_rows = 0;
|
|
313
|
+
} else {
|
|
314
|
+
if (tf_size_add(m->row_ref_capacity, 4, &new_row_ref_capacity) != TF_OK) {
|
|
315
|
+
free(key_copy);
|
|
316
|
+
return -1;
|
|
317
|
+
}
|
|
318
|
+
rows = tf_mallocarray_checked(4, sizeof(size_t));
|
|
319
|
+
if (!rows) { free(key_copy); return -1; }
|
|
320
|
+
rows_cap = 4;
|
|
321
|
+
rows[0] = row;
|
|
322
|
+
n_rows = 1;
|
|
323
|
+
}
|
|
324
|
+
b->key = key_copy;
|
|
325
|
+
b->rows = rows;
|
|
326
|
+
b->rows_cap = rows_cap;
|
|
327
|
+
b->n_rows = n_rows;
|
|
328
|
+
m->key_bytes = new_key_bytes;
|
|
329
|
+
m->row_ref_capacity = new_row_ref_capacity;
|
|
86
330
|
m->count++;
|
|
331
|
+
return 0;
|
|
87
332
|
}
|
|
88
333
|
|
|
89
334
|
static join_bucket *map_find(join_hash_map *m, const char *key) {
|
|
@@ -108,256 +353,2442 @@ static void map_free(join_hash_map *m) {
|
|
|
108
353
|
}
|
|
109
354
|
free(m->buckets);
|
|
110
355
|
m->buckets = NULL;
|
|
356
|
+
m->n_buckets = 0;
|
|
357
|
+
m->count = 0;
|
|
358
|
+
m->key_bytes = 0;
|
|
359
|
+
m->row_ref_capacity = 0;
|
|
111
360
|
}
|
|
112
361
|
|
|
113
|
-
/*
|
|
362
|
+
/* Serialize a typed join key. Tags avoid collisions such as int 1 vs string "1"
|
|
363
|
+
* and null vs a literal sentinel string. */
|
|
114
364
|
static char *format_join_key(const tf_batch *b, size_t row, int col) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
365
|
+
char buf[96];
|
|
366
|
+
tf_type type = b->col_types[col];
|
|
367
|
+
if (tf_batch_is_null(b, row, col)) {
|
|
368
|
+
snprintf(buf, sizeof(buf), "N:%d", (int)type);
|
|
369
|
+
return strdup(buf);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
switch (type) {
|
|
373
|
+
case TF_TYPE_STRING: {
|
|
374
|
+
const char *v = tf_batch_get_string(b, row, col);
|
|
375
|
+
size_t len = v ? strlen(v) : 0;
|
|
376
|
+
char prefix[48];
|
|
377
|
+
int n = snprintf(prefix, sizeof(prefix), "S:%zu:", len);
|
|
378
|
+
if (n < 0) return NULL;
|
|
379
|
+
size_t prefix_len = (size_t)n;
|
|
380
|
+
char *out = malloc(prefix_len + len + 1);
|
|
381
|
+
if (!out) return NULL;
|
|
382
|
+
memcpy(out, prefix, prefix_len);
|
|
383
|
+
if (len) memcpy(out + prefix_len, v, len);
|
|
384
|
+
out[prefix_len + len] = '\0';
|
|
385
|
+
return out;
|
|
386
|
+
}
|
|
119
387
|
case TF_TYPE_INT64:
|
|
120
|
-
snprintf(buf, sizeof(buf), "
|
|
388
|
+
snprintf(buf, sizeof(buf), "I:%lld", (long long)tf_batch_get_int64(b, row, col));
|
|
121
389
|
return strdup(buf);
|
|
122
390
|
case TF_TYPE_FLOAT64:
|
|
123
|
-
snprintf(buf, sizeof(buf), "
|
|
391
|
+
snprintf(buf, sizeof(buf), "F:%.17g", tf_batch_get_float64(b, row, col));
|
|
124
392
|
return strdup(buf);
|
|
125
393
|
case TF_TYPE_BOOL:
|
|
126
|
-
return strdup(tf_batch_get_bool(b, row, col) ? "
|
|
394
|
+
return strdup(tf_batch_get_bool(b, row, col) ? "B:1" : "B:0");
|
|
127
395
|
case TF_TYPE_DATE:
|
|
128
|
-
snprintf(buf, sizeof(buf), "
|
|
396
|
+
snprintf(buf, sizeof(buf), "D:%d", (int)tf_batch_get_date(b, row, col));
|
|
129
397
|
return strdup(buf);
|
|
130
398
|
case TF_TYPE_TIMESTAMP:
|
|
131
|
-
snprintf(buf, sizeof(buf), "
|
|
399
|
+
snprintf(buf, sizeof(buf), "T:%lld", (long long)tf_batch_get_timestamp(b, row, col));
|
|
400
|
+
return strdup(buf);
|
|
401
|
+
default:
|
|
402
|
+
snprintf(buf, sizeof(buf), "U:%d", (int)type);
|
|
132
403
|
return strdup(buf);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
static void join_key_value_clear(join_key_value *v) {
|
|
408
|
+
if (!v) return;
|
|
409
|
+
if (v->valid && !v->is_null && v->type == TF_TYPE_STRING) free(v->v.str);
|
|
410
|
+
memset(v, 0, sizeof(*v));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
static int join_key_value_set(join_key_value *dst, const tf_batch *b, size_t row, int col) {
|
|
414
|
+
if (!dst || !b || col < 0) return TF_ERROR;
|
|
415
|
+
join_key_value_clear(dst);
|
|
416
|
+
dst->valid = 1;
|
|
417
|
+
dst->type = b->col_types[col];
|
|
418
|
+
dst->is_null = tf_batch_is_null(b, row, col) ? 1 : 0;
|
|
419
|
+
if (dst->is_null) return TF_OK;
|
|
420
|
+
|
|
421
|
+
switch (dst->type) {
|
|
422
|
+
case TF_TYPE_BOOL:
|
|
423
|
+
dst->v.b = tf_batch_get_bool(b, row, col) ? 1 : 0;
|
|
424
|
+
return TF_OK;
|
|
425
|
+
case TF_TYPE_INT64:
|
|
426
|
+
dst->v.i64 = tf_batch_get_int64(b, row, col);
|
|
427
|
+
return TF_OK;
|
|
428
|
+
case TF_TYPE_FLOAT64:
|
|
429
|
+
dst->v.f64 = tf_batch_get_float64(b, row, col);
|
|
430
|
+
return TF_OK;
|
|
431
|
+
case TF_TYPE_STRING: {
|
|
432
|
+
const char *s = tf_batch_get_string(b, row, col);
|
|
433
|
+
dst->v.str = strdup(s ? s : "");
|
|
434
|
+
return dst->v.str ? TF_OK : TF_ERROR;
|
|
435
|
+
}
|
|
436
|
+
case TF_TYPE_DATE:
|
|
437
|
+
dst->v.date = tf_batch_get_date(b, row, col);
|
|
438
|
+
return TF_OK;
|
|
439
|
+
case TF_TYPE_TIMESTAMP:
|
|
440
|
+
dst->v.i64 = tf_batch_get_timestamp(b, row, col);
|
|
441
|
+
return TF_OK;
|
|
442
|
+
default:
|
|
443
|
+
dst->is_null = 1;
|
|
444
|
+
return TF_OK;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
static int join_key_value_copy(join_key_value *dst, const join_key_value *src) {
|
|
449
|
+
if (!dst || !src || !src->valid) return TF_ERROR;
|
|
450
|
+
join_key_value_clear(dst);
|
|
451
|
+
*dst = *src;
|
|
452
|
+
if (!src->is_null && src->type == TF_TYPE_STRING) {
|
|
453
|
+
dst->v.str = strdup(src->v.str ? src->v.str : "");
|
|
454
|
+
if (!dst->v.str) { memset(dst, 0, sizeof(*dst)); return TF_ERROR; }
|
|
455
|
+
}
|
|
456
|
+
return TF_OK;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
static int join_key_compare_values(const join_key_value *a, const join_key_value *b, int *cmp) {
|
|
460
|
+
if (!a || !b || !cmp || !a->valid || !b->valid) return TF_ERROR;
|
|
461
|
+
if (a->type != b->type) return TF_ERROR;
|
|
462
|
+
if (a->is_null && b->is_null) { *cmp = 0; return TF_OK; }
|
|
463
|
+
if (a->is_null) { *cmp = 1; return TF_OK; }
|
|
464
|
+
if (b->is_null) { *cmp = -1; return TF_OK; }
|
|
465
|
+
|
|
466
|
+
switch (a->type) {
|
|
467
|
+
case TF_TYPE_BOOL:
|
|
468
|
+
*cmp = (int)a->v.b - (int)b->v.b;
|
|
469
|
+
return TF_OK;
|
|
470
|
+
case TF_TYPE_INT64:
|
|
471
|
+
case TF_TYPE_TIMESTAMP:
|
|
472
|
+
*cmp = (a->v.i64 > b->v.i64) - (a->v.i64 < b->v.i64);
|
|
473
|
+
return TF_OK;
|
|
474
|
+
case TF_TYPE_FLOAT64:
|
|
475
|
+
*cmp = (a->v.f64 > b->v.f64) - (a->v.f64 < b->v.f64);
|
|
476
|
+
return TF_OK;
|
|
477
|
+
case TF_TYPE_STRING:
|
|
478
|
+
*cmp = strcmp(a->v.str ? a->v.str : "", b->v.str ? b->v.str : "");
|
|
479
|
+
return TF_OK;
|
|
480
|
+
case TF_TYPE_DATE:
|
|
481
|
+
*cmp = (a->v.date > b->v.date) - (a->v.date < b->v.date);
|
|
482
|
+
return TF_OK;
|
|
133
483
|
default:
|
|
134
|
-
|
|
484
|
+
*cmp = 0;
|
|
485
|
+
return TF_OK;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
static int join_key_compare_value_to_cell(const join_key_value *a, const tf_batch *b,
|
|
490
|
+
size_t row, int col, int *cmp) {
|
|
491
|
+
join_key_value rhs = {0};
|
|
492
|
+
if (join_key_value_set(&rhs, b, row, col) != TF_OK) return TF_ERROR;
|
|
493
|
+
int rc = join_key_compare_values(a, &rhs, cmp);
|
|
494
|
+
join_key_value_clear(&rhs);
|
|
495
|
+
return rc;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
static int csv_header_has_column(const uint8_t *data, size_t len, const char *name) {
|
|
499
|
+
if (!data || !name) return 0;
|
|
500
|
+
size_t name_len = strlen(name);
|
|
501
|
+
size_t start = 0;
|
|
502
|
+
for (size_t i = 0; i <= len; i++) {
|
|
503
|
+
int end_field = (i == len || data[i] == ',' || data[i] == '\n' || data[i] == '\r');
|
|
504
|
+
if (!end_field) continue;
|
|
505
|
+
size_t a = start;
|
|
506
|
+
size_t b = i;
|
|
507
|
+
while (a < b && (data[a] == ' ' || data[a] == '\t')) a++;
|
|
508
|
+
while (b > a && (data[b - 1] == ' ' || data[b - 1] == '\t')) b--;
|
|
509
|
+
if (b > a + 1 && data[a] == '"' && data[b - 1] == '"') {
|
|
510
|
+
a++;
|
|
511
|
+
b--;
|
|
512
|
+
}
|
|
513
|
+
if (b - a == name_len && memcmp(data + a, name, name_len) == 0) return 1;
|
|
514
|
+
if (i == len || data[i] == '\n' || data[i] == '\r') return 0;
|
|
515
|
+
start = i + 1;
|
|
516
|
+
}
|
|
517
|
+
return 0;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
static int csv_file_header_has_column(const char *path, const char *validated_path, const char *name) {
|
|
521
|
+
if (!path || !name) return 0;
|
|
522
|
+
FILE *f = tf_policy_fopen_read(path, validated_path);
|
|
523
|
+
if (!f) return 0;
|
|
524
|
+
size_t cap = 4096;
|
|
525
|
+
uint8_t *buf = malloc(cap);
|
|
526
|
+
if (!buf) { fclose(f); return 0; }
|
|
527
|
+
size_t len = 0;
|
|
528
|
+
int found_eol = 0;
|
|
529
|
+
while (len < cap) {
|
|
530
|
+
int ch = fgetc(f);
|
|
531
|
+
if (ch == EOF) break;
|
|
532
|
+
buf[len++] = (uint8_t)ch;
|
|
533
|
+
if (ch == '\n' || ch == '\r') { found_eol = 1; break; }
|
|
534
|
+
}
|
|
535
|
+
fclose(f);
|
|
536
|
+
int ok = (len > 0 && (found_eol || len < cap)) ? csv_header_has_column(buf, len, name) : 0;
|
|
537
|
+
free(buf);
|
|
538
|
+
return ok;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
static void sorted_join_free_pending_batches(join_state *st) {
|
|
542
|
+
if (!st || !st->sorted_batches) return;
|
|
543
|
+
for (size_t i = st->sorted_batch_index; i < st->sorted_n_batches; i++) {
|
|
544
|
+
if (st->sorted_batches[i]) tf_batch_free(st->sorted_batches[i]);
|
|
545
|
+
}
|
|
546
|
+
free(st->sorted_batches);
|
|
547
|
+
st->sorted_batches = NULL;
|
|
548
|
+
st->sorted_n_batches = 0;
|
|
549
|
+
st->sorted_batch_index = 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
static void sorted_join_close(join_state *st) {
|
|
553
|
+
if (!st) return;
|
|
554
|
+
if (st->sorted_current) {
|
|
555
|
+
tf_batch_free(st->sorted_current);
|
|
556
|
+
st->sorted_current = NULL;
|
|
557
|
+
}
|
|
558
|
+
sorted_join_free_pending_batches(st);
|
|
559
|
+
if (st->sorted_decoder) {
|
|
560
|
+
st->sorted_decoder->destroy(st->sorted_decoder);
|
|
561
|
+
st->sorted_decoder = NULL;
|
|
562
|
+
}
|
|
563
|
+
if (st->sorted_file) {
|
|
564
|
+
fclose(st->sorted_file);
|
|
565
|
+
st->sorted_file = NULL;
|
|
566
|
+
}
|
|
567
|
+
st->sorted_have_row = 0;
|
|
568
|
+
st->sorted_exhausted = 1;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
static void sorted_join_free_schema(join_state *st) {
|
|
572
|
+
if (!st) return;
|
|
573
|
+
for (size_t i = 0; i < st->n_lookup_schema_cols; i++) {
|
|
574
|
+
free(st->lookup_schema_names ? st->lookup_schema_names[i] : NULL);
|
|
575
|
+
}
|
|
576
|
+
free(st->lookup_schema_names);
|
|
577
|
+
free(st->lookup_schema_types);
|
|
578
|
+
st->lookup_schema_names = NULL;
|
|
579
|
+
st->lookup_schema_types = NULL;
|
|
580
|
+
st->n_lookup_schema_cols = 0;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
static int sorted_join_capture_schema(join_state *st, const tf_batch *b) {
|
|
584
|
+
if (st->n_lookup_schema_cols > 0) return TF_OK;
|
|
585
|
+
st->n_lookup_schema_cols = b->n_cols;
|
|
586
|
+
st->lookup_schema_names = tf_callocarray_checked(b->n_cols ? b->n_cols : 1, sizeof(char *));
|
|
587
|
+
st->lookup_schema_types = tf_callocarray_checked(b->n_cols ? b->n_cols : 1, sizeof(tf_type));
|
|
588
|
+
if (!st->lookup_schema_names || !st->lookup_schema_types) return TF_ERROR;
|
|
589
|
+
for (size_t c = 0; c < b->n_cols; c++) {
|
|
590
|
+
st->lookup_schema_names[c] = strdup(b->col_names[c] ? b->col_names[c] : "");
|
|
591
|
+
if (!st->lookup_schema_names[c]) return TF_ERROR;
|
|
592
|
+
st->lookup_schema_types[c] = b->col_types[c];
|
|
593
|
+
}
|
|
594
|
+
st->lookup_join_col = tf_batch_col_index(b, st->right_col);
|
|
595
|
+
if (st->lookup_join_col < 0) return TF_ERROR;
|
|
596
|
+
st->lookup_key_type = b->col_types[st->lookup_join_col];
|
|
597
|
+
st->have_lookup_key_type = 1;
|
|
598
|
+
if (st->how < 2) {
|
|
599
|
+
st->lookup_out_cols = tf_mallocarray_checked(b->n_cols ? b->n_cols : 1, sizeof(int));
|
|
600
|
+
if (!st->lookup_out_cols) return TF_ERROR;
|
|
601
|
+
st->n_lookup_out = 0;
|
|
602
|
+
for (size_t c = 0; c < b->n_cols; c++) {
|
|
603
|
+
if ((int)c != st->lookup_join_col) st->lookup_out_cols[st->n_lookup_out++] = (int)c;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return TF_OK;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
static tf_batch *sorted_join_create_run_batch(const join_state *st) {
|
|
610
|
+
tf_batch *b = tf_batch_create(st->n_lookup_schema_cols, 16);
|
|
611
|
+
if (!b) return NULL;
|
|
612
|
+
for (size_t c = 0; c < st->n_lookup_schema_cols; c++) {
|
|
613
|
+
if (tf_batch_set_schema(b, c, st->lookup_schema_names[c], st->lookup_schema_types[c]) != TF_OK) {
|
|
614
|
+
tf_batch_free(b);
|
|
615
|
+
return NULL;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return b;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
static int sorted_join_open(join_state *st, tf_side_channels *side) {
|
|
622
|
+
if (!st || st->sorted_file || st->sorted_decoder) return TF_OK;
|
|
623
|
+
if (!csv_file_header_has_column(st->file, st->validated_file, st->right_col)) {
|
|
624
|
+
if (join_write_error(side, "sorted join: lookup file is missing join column") != TF_OK)
|
|
625
|
+
return TF_ERROR;
|
|
626
|
+
return TF_ERROR;
|
|
627
|
+
}
|
|
628
|
+
st->sorted_file = tf_policy_fopen_read(st->file, st->validated_file);
|
|
629
|
+
if (!st->sorted_file) {
|
|
630
|
+
if (join_write_error(side, "sorted join: cannot open lookup file") != TF_OK)
|
|
631
|
+
return TF_ERROR;
|
|
632
|
+
return TF_ERROR;
|
|
633
|
+
}
|
|
634
|
+
st->sorted_decoder = tf_csv_decoder_create(NULL);
|
|
635
|
+
if (!st->sorted_decoder) return TF_ERROR;
|
|
636
|
+
st->lookup_join_col = -1;
|
|
637
|
+
return TF_OK;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
static int sorted_join_next_batch(join_state *st, tf_side_channels *side) {
|
|
641
|
+
if (st->sorted_current) {
|
|
642
|
+
tf_batch_free(st->sorted_current);
|
|
643
|
+
st->sorted_current = NULL;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
for (;;) {
|
|
647
|
+
if (st->sorted_batches && st->sorted_batch_index < st->sorted_n_batches) {
|
|
648
|
+
st->sorted_current = st->sorted_batches[st->sorted_batch_index++];
|
|
649
|
+
if (st->sorted_current && sorted_join_capture_schema(st, st->sorted_current) != TF_OK) {
|
|
650
|
+
if (join_write_error(side, "sorted join: failed to resolve lookup schema") != TF_OK)
|
|
651
|
+
return TF_ERROR;
|
|
652
|
+
return TF_ERROR;
|
|
653
|
+
}
|
|
654
|
+
st->sorted_row = 0;
|
|
655
|
+
if (st->sorted_current && st->sorted_current->n_rows > 0) return TF_OK;
|
|
656
|
+
if (st->sorted_current) {
|
|
657
|
+
tf_batch_free(st->sorted_current);
|
|
658
|
+
st->sorted_current = NULL;
|
|
659
|
+
}
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
sorted_join_free_pending_batches(st);
|
|
664
|
+
if (st->sorted_flushed) {
|
|
665
|
+
st->sorted_exhausted = 1;
|
|
666
|
+
return TF_OK;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
uint8_t buf[64 * 1024];
|
|
670
|
+
size_t n = fread(buf, 1, sizeof(buf), st->sorted_file);
|
|
671
|
+
tf_batch **batches = NULL;
|
|
672
|
+
size_t n_batches = 0;
|
|
673
|
+
int rc;
|
|
674
|
+
if (n > 0) {
|
|
675
|
+
rc = st->sorted_decoder->decode(st->sorted_decoder, buf, n, &batches, &n_batches, side);
|
|
676
|
+
} else {
|
|
677
|
+
if (ferror(st->sorted_file)) {
|
|
678
|
+
if (join_write_error(side, "sorted join: failed reading lookup file") != TF_OK)
|
|
679
|
+
return TF_ERROR;
|
|
680
|
+
return TF_ERROR;
|
|
681
|
+
}
|
|
682
|
+
st->sorted_flushed = 1;
|
|
683
|
+
rc = st->sorted_decoder->flush(st->sorted_decoder, &batches, &n_batches, side);
|
|
684
|
+
}
|
|
685
|
+
if (rc != TF_OK) {
|
|
686
|
+
tf_batch_array_free(batches, n_batches);
|
|
687
|
+
return TF_ERROR;
|
|
688
|
+
}
|
|
689
|
+
st->sorted_batches = batches;
|
|
690
|
+
st->sorted_n_batches = n_batches;
|
|
691
|
+
st->sorted_batch_index = 0;
|
|
692
|
+
if (n_batches == 0) {
|
|
693
|
+
free(batches);
|
|
694
|
+
st->sorted_batches = NULL;
|
|
695
|
+
if (st->sorted_flushed) {
|
|
696
|
+
st->sorted_exhausted = 1;
|
|
697
|
+
return TF_OK;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
static int sorted_join_advance_lookup(join_state *st, tf_side_channels *side) {
|
|
704
|
+
if (!st->sorted_file && sorted_join_open(st, side) != TF_OK) return TF_ERROR;
|
|
705
|
+
if (st->sorted_have_row) {
|
|
706
|
+
st->sorted_row++;
|
|
707
|
+
st->sorted_have_row = 0;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
for (;;) {
|
|
711
|
+
if (!st->sorted_current || st->sorted_row >= st->sorted_current->n_rows) {
|
|
712
|
+
if (sorted_join_next_batch(st, side) != TF_OK) return TF_ERROR;
|
|
713
|
+
if (st->sorted_exhausted) return TF_OK;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
join_key_value cur = {0};
|
|
718
|
+
if (join_key_value_set(&cur, st->sorted_current, st->sorted_row, st->lookup_join_col) != TF_OK) {
|
|
719
|
+
return TF_ERROR;
|
|
720
|
+
}
|
|
721
|
+
if (st->have_prev_lookup_key) {
|
|
722
|
+
int cmp = 0;
|
|
723
|
+
if (join_key_compare_values(&st->prev_lookup_key, &cur, &cmp) != TF_OK) {
|
|
724
|
+
join_key_value_clear(&cur);
|
|
725
|
+
if (join_write_error(side, "sorted join: lookup key type changed across batches") != TF_OK)
|
|
726
|
+
return TF_ERROR;
|
|
727
|
+
return TF_ERROR;
|
|
728
|
+
}
|
|
729
|
+
if (cmp > 0) {
|
|
730
|
+
join_key_value_clear(&cur);
|
|
731
|
+
if (join_write_error(side, "sorted join: lookup side is not sorted by join key") != TF_OK)
|
|
732
|
+
return TF_ERROR;
|
|
733
|
+
return TF_ERROR;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (join_key_value_copy(&st->prev_lookup_key, &cur) != TF_OK) {
|
|
737
|
+
join_key_value_clear(&cur);
|
|
738
|
+
return TF_ERROR;
|
|
739
|
+
}
|
|
740
|
+
join_key_value_clear(&cur);
|
|
741
|
+
st->have_prev_lookup_key = 1;
|
|
742
|
+
st->sorted_have_row = 1;
|
|
743
|
+
st->lookup_rows++;
|
|
744
|
+
return TF_OK;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
static void sorted_join_clear_right_run(join_state *st) {
|
|
749
|
+
if (!st) return;
|
|
750
|
+
if (st->right_run) {
|
|
751
|
+
tf_batch_free(st->right_run);
|
|
752
|
+
st->right_run = NULL;
|
|
753
|
+
}
|
|
754
|
+
join_key_value_clear(&st->right_run_key);
|
|
755
|
+
st->have_right_run = 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
static int sorted_join_load_next_run(join_state *st, tf_side_channels *side) {
|
|
759
|
+
sorted_join_clear_right_run(st);
|
|
760
|
+
if (!st->sorted_have_row) {
|
|
761
|
+
if (sorted_join_advance_lookup(st, side) != TF_OK) return TF_ERROR;
|
|
762
|
+
}
|
|
763
|
+
if (!st->sorted_have_row) {
|
|
764
|
+
st->sorted_exhausted = 1;
|
|
765
|
+
return TF_OK;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (join_key_value_set(&st->right_run_key, st->sorted_current, st->sorted_row,
|
|
769
|
+
st->lookup_join_col) != TF_OK) return TF_ERROR;
|
|
770
|
+
st->have_right_run = 1;
|
|
771
|
+
|
|
772
|
+
if (st->how < 2) {
|
|
773
|
+
st->right_run = sorted_join_create_run_batch(st);
|
|
774
|
+
if (!st->right_run) return TF_ERROR;
|
|
135
775
|
}
|
|
776
|
+
|
|
777
|
+
while (st->sorted_have_row) {
|
|
778
|
+
int cmp = 0;
|
|
779
|
+
if (join_key_compare_value_to_cell(&st->right_run_key, st->sorted_current,
|
|
780
|
+
st->sorted_row, st->lookup_join_col, &cmp) != TF_OK) {
|
|
781
|
+
if (join_write_error(side, "sorted join: lookup key type changed across rows") != TF_OK)
|
|
782
|
+
return TF_ERROR;
|
|
783
|
+
return TF_ERROR;
|
|
784
|
+
}
|
|
785
|
+
if (cmp != 0) break;
|
|
786
|
+
|
|
787
|
+
if (st->how < 2) {
|
|
788
|
+
if (st->max_matches_per_row > 0 && st->right_run->n_rows >= st->max_matches_per_row) {
|
|
789
|
+
if (join_limit_error_context(side, "max_matches_per_row", st->max_matches_per_row,
|
|
790
|
+
st->right_run->n_rows + 1,
|
|
791
|
+
"for sorted lookup key") != TF_OK)
|
|
792
|
+
return TF_ERROR;
|
|
793
|
+
return TF_ERROR;
|
|
794
|
+
}
|
|
795
|
+
size_t dst = st->right_run->n_rows;
|
|
796
|
+
if (tf_batch_ensure_capacity(st->right_run, dst + 1) != TF_OK) return TF_ERROR;
|
|
797
|
+
if (tf_batch_copy_row(st->right_run, dst, st->sorted_current, st->sorted_row) != TF_OK)
|
|
798
|
+
return TF_ERROR;
|
|
799
|
+
if (tf_batch_expose_row(st->right_run, dst) != TF_OK) return TF_ERROR;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (sorted_join_advance_lookup(st, side) != TF_OK) return TF_ERROR;
|
|
803
|
+
}
|
|
804
|
+
return TF_OK;
|
|
136
805
|
}
|
|
137
806
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
807
|
+
static int sorted_join_ensure_run_at_least(join_state *st, const tf_batch *left,
|
|
808
|
+
size_t left_row, int left_ci,
|
|
809
|
+
tf_side_channels *side) {
|
|
810
|
+
if (!st->have_right_run && !st->sorted_exhausted) {
|
|
811
|
+
if (sorted_join_load_next_run(st, side) != TF_OK) return TF_ERROR;
|
|
812
|
+
}
|
|
813
|
+
while (st->have_right_run) {
|
|
814
|
+
int cmp = 0;
|
|
815
|
+
if (join_key_compare_value_to_cell(&st->right_run_key, left, left_row, left_ci, &cmp) != TF_OK) {
|
|
816
|
+
if (join_write_error(side, "sorted join: left and lookup join key types differ") != TF_OK)
|
|
817
|
+
return TF_ERROR;
|
|
818
|
+
return TF_ERROR;
|
|
819
|
+
}
|
|
820
|
+
if (cmp >= 0) return TF_OK;
|
|
821
|
+
if (sorted_join_load_next_run(st, side) != TF_OK) return TF_ERROR;
|
|
822
|
+
if (st->sorted_exhausted && !st->have_right_run) return TF_OK;
|
|
823
|
+
}
|
|
824
|
+
return TF_OK;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
static int sorted_join_check_left_order(join_state *st, const tf_batch *left,
|
|
828
|
+
size_t row, int left_ci,
|
|
829
|
+
tf_side_channels *side) {
|
|
830
|
+
join_key_value cur = {0};
|
|
831
|
+
if (join_key_value_set(&cur, left, row, left_ci) != TF_OK) return TF_ERROR;
|
|
832
|
+
if (st->have_prev_left_key) {
|
|
833
|
+
int cmp = 0;
|
|
834
|
+
if (join_key_compare_values(&st->prev_left_key, &cur, &cmp) != TF_OK) {
|
|
835
|
+
join_key_value_clear(&cur);
|
|
836
|
+
if (join_write_error(side, "sorted join: left join key type changed across batches") != TF_OK)
|
|
837
|
+
return TF_ERROR;
|
|
838
|
+
return TF_ERROR;
|
|
839
|
+
}
|
|
840
|
+
if (cmp > 0) {
|
|
841
|
+
join_key_value_clear(&cur);
|
|
842
|
+
if (join_write_error(side, "sorted join: left side is not sorted by join key") != TF_OK)
|
|
843
|
+
return TF_ERROR;
|
|
844
|
+
return TF_ERROR;
|
|
845
|
+
}
|
|
144
846
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
case TF_TYPE_FLOAT64: tf_batch_set_float64(dst, dr, dc, tf_batch_get_float64(src, sr, sc)); break;
|
|
149
|
-
case TF_TYPE_STRING: tf_batch_set_string(dst, dr, dc, tf_batch_get_string(src, sr, sc)); break;
|
|
150
|
-
case TF_TYPE_DATE: tf_batch_set_date(dst, dr, dc, tf_batch_get_date(src, sr, sc)); break;
|
|
151
|
-
case TF_TYPE_TIMESTAMP: tf_batch_set_timestamp(dst, dr, dc, tf_batch_get_timestamp(src, sr, sc)); break;
|
|
152
|
-
default: tf_batch_set_null(dst, dr, dc); break;
|
|
847
|
+
if (join_key_value_copy(&st->prev_left_key, &cur) != TF_OK) {
|
|
848
|
+
join_key_value_clear(&cur);
|
|
849
|
+
return TF_ERROR;
|
|
153
850
|
}
|
|
851
|
+
join_key_value_clear(&cur);
|
|
852
|
+
st->have_prev_left_key = 1;
|
|
853
|
+
return TF_OK;
|
|
154
854
|
}
|
|
155
855
|
|
|
156
|
-
static int load_lookup(join_state *st) {
|
|
157
|
-
FILE *f =
|
|
856
|
+
static int load_lookup(join_state *st, tf_side_channels *side) {
|
|
857
|
+
FILE *f = NULL;
|
|
858
|
+
uint8_t *data = NULL;
|
|
859
|
+
tf_decoder *dec = NULL;
|
|
860
|
+
tf_batch **batches = NULL;
|
|
861
|
+
tf_batch **flush_batches = NULL;
|
|
862
|
+
tf_batch **all_batches = NULL;
|
|
863
|
+
size_t n_batches = 0, n_flush = 0, total_batches = 0, total_rows = 0;
|
|
864
|
+
tf_batch *merged = NULL;
|
|
865
|
+
int *lookup_out_cols = NULL;
|
|
866
|
+
join_hash_map map = {0};
|
|
867
|
+
|
|
868
|
+
f = tf_policy_fopen_read(st->file, st->validated_file);
|
|
158
869
|
if (!f) return TF_ERROR;
|
|
159
870
|
|
|
160
|
-
/* Read entire file */
|
|
161
|
-
fseek(f, 0, SEEK_END);
|
|
871
|
+
/* Read entire lookup file, guarded by max_lookup_bytes when provided. */
|
|
872
|
+
if (fseek(f, 0, SEEK_END) != 0) goto fail;
|
|
162
873
|
long fsize = ftell(f);
|
|
163
|
-
fseek(f, 0, SEEK_SET);
|
|
164
|
-
if (fsize <= 0)
|
|
874
|
+
if (fseek(f, 0, SEEK_SET) != 0) goto fail;
|
|
875
|
+
if (fsize <= 0) goto fail;
|
|
876
|
+
if (st->max_lookup_bytes > 0 && (size_t)fsize > st->max_lookup_bytes) {
|
|
877
|
+
if (join_limit_error(side, "max_lookup_bytes", st->max_lookup_bytes,
|
|
878
|
+
(size_t)fsize) != TF_OK)
|
|
879
|
+
goto fail;
|
|
880
|
+
goto fail;
|
|
881
|
+
}
|
|
165
882
|
|
|
166
|
-
|
|
167
|
-
if (!data)
|
|
883
|
+
data = malloc((size_t)fsize);
|
|
884
|
+
if (!data) goto fail;
|
|
168
885
|
size_t nread = fread(data, 1, (size_t)fsize, f);
|
|
169
886
|
fclose(f);
|
|
170
|
-
|
|
887
|
+
f = NULL;
|
|
888
|
+
if (nread != (size_t)fsize) goto fail;
|
|
171
889
|
|
|
172
890
|
/* Decode using CSV decoder */
|
|
173
|
-
|
|
174
|
-
if (!dec)
|
|
891
|
+
dec = tf_csv_decoder_create(NULL);
|
|
892
|
+
if (!dec) goto fail;
|
|
175
893
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (dec->decode(dec, data, nread, &batches, &n_batches) != TF_OK) {
|
|
179
|
-
dec->destroy(dec);
|
|
180
|
-
free(data);
|
|
181
|
-
return TF_ERROR;
|
|
182
|
-
}
|
|
894
|
+
if (dec->decode(dec, data, nread, &batches, &n_batches, NULL) != TF_OK) goto fail;
|
|
895
|
+
if (dec->flush(dec, &flush_batches, &n_flush, NULL) != TF_OK) goto fail;
|
|
183
896
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
/* Count total rows and merge into single batch */
|
|
189
|
-
size_t total_rows = 0;
|
|
190
|
-
size_t total_batches = n_batches + n_flush;
|
|
191
|
-
tf_batch **all_batches = malloc(total_batches * sizeof(tf_batch *));
|
|
192
|
-
if (!all_batches) { dec->destroy(dec); free(data); return TF_ERROR; }
|
|
897
|
+
total_batches = n_batches + n_flush;
|
|
898
|
+
all_batches = tf_mallocarray_checked(total_batches ? total_batches : 1, sizeof(tf_batch *));
|
|
899
|
+
if (!all_batches) goto fail;
|
|
193
900
|
for (size_t i = 0; i < n_batches; i++) {
|
|
194
901
|
all_batches[i] = batches[i];
|
|
195
902
|
total_rows += batches[i]->n_rows;
|
|
903
|
+
if (st->max_lookup_rows > 0 && total_rows > st->max_lookup_rows) {
|
|
904
|
+
if (join_limit_error(side, "max_lookup_rows", st->max_lookup_rows,
|
|
905
|
+
total_rows) != TF_OK)
|
|
906
|
+
goto fail;
|
|
907
|
+
goto fail;
|
|
908
|
+
}
|
|
196
909
|
}
|
|
197
910
|
for (size_t i = 0; i < n_flush; i++) {
|
|
198
911
|
all_batches[n_batches + i] = flush_batches[i];
|
|
199
912
|
total_rows += flush_batches[i]->n_rows;
|
|
913
|
+
if (st->max_lookup_rows > 0 && total_rows > st->max_lookup_rows) {
|
|
914
|
+
if (join_limit_error(side, "max_lookup_rows", st->max_lookup_rows,
|
|
915
|
+
total_rows) != TF_OK)
|
|
916
|
+
goto fail;
|
|
917
|
+
goto fail;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (total_batches == 0 && st->how >= 2) {
|
|
922
|
+
if (!csv_header_has_column(data, nread, st->right_col)) goto fail;
|
|
923
|
+
if (map_init(&map, 1) != 0) goto fail;
|
|
924
|
+
if (join_check_unsorted_state_bytes(st, &map, NULL, 0, side) != TF_OK) goto fail;
|
|
925
|
+
|
|
926
|
+
free(all_batches);
|
|
927
|
+
all_batches = NULL;
|
|
928
|
+
tf_batch_array_free(batches, n_batches);
|
|
929
|
+
batches = NULL;
|
|
930
|
+
tf_batch_array_free(flush_batches, n_flush);
|
|
931
|
+
flush_batches = NULL;
|
|
932
|
+
dec->destroy(dec);
|
|
933
|
+
dec = NULL;
|
|
934
|
+
free(data);
|
|
935
|
+
data = NULL;
|
|
936
|
+
|
|
937
|
+
st->lookup_rows = 0;
|
|
938
|
+
st->lookup = NULL;
|
|
939
|
+
st->lookup_join_col = -1;
|
|
940
|
+
st->lookup_out_cols = NULL;
|
|
941
|
+
st->n_lookup_out = 0;
|
|
942
|
+
st->map = map;
|
|
943
|
+
return TF_OK;
|
|
200
944
|
}
|
|
945
|
+
if (total_batches == 0) goto fail;
|
|
946
|
+
if (total_rows == 0 && st->how >= 2) {
|
|
947
|
+
tf_batch *first = all_batches[0];
|
|
948
|
+
int lookup_join_col = tf_batch_col_index(first, st->right_col);
|
|
949
|
+
if (lookup_join_col < 0) goto fail;
|
|
950
|
+
if (map_init(&map, 1) != 0) goto fail;
|
|
951
|
+
if (join_check_unsorted_state_bytes(st, &map, NULL, 0, side) != TF_OK) goto fail;
|
|
201
952
|
|
|
202
|
-
|
|
953
|
+
tf_batch_array_free(batches, n_batches);
|
|
954
|
+
batches = NULL;
|
|
955
|
+
tf_batch_array_free(flush_batches, n_flush);
|
|
956
|
+
flush_batches = NULL;
|
|
203
957
|
free(all_batches);
|
|
204
|
-
|
|
205
|
-
dec->destroy(dec);
|
|
206
|
-
|
|
958
|
+
all_batches = NULL;
|
|
959
|
+
dec->destroy(dec);
|
|
960
|
+
dec = NULL;
|
|
961
|
+
free(data);
|
|
962
|
+
data = NULL;
|
|
963
|
+
|
|
964
|
+
st->lookup_rows = 0;
|
|
965
|
+
st->lookup = NULL;
|
|
966
|
+
st->lookup_join_col = lookup_join_col;
|
|
967
|
+
st->lookup_key_type = first->col_types[lookup_join_col];
|
|
968
|
+
st->have_lookup_key_type = 1;
|
|
969
|
+
st->lookup_out_cols = NULL;
|
|
970
|
+
st->n_lookup_out = 0;
|
|
971
|
+
st->map = map;
|
|
972
|
+
return TF_OK;
|
|
207
973
|
}
|
|
974
|
+
if (total_rows == 0) goto fail;
|
|
208
975
|
|
|
209
976
|
/* Use first batch as schema source */
|
|
210
977
|
tf_batch *first = all_batches[0];
|
|
211
|
-
|
|
212
|
-
if (!merged)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
dec->destroy(dec); free(data);
|
|
216
|
-
return TF_ERROR;
|
|
978
|
+
merged = tf_batch_create(first->n_cols, total_rows);
|
|
979
|
+
if (!merged) goto fail;
|
|
980
|
+
for (size_t c = 0; c < first->n_cols; c++) {
|
|
981
|
+
if (tf_batch_set_schema(merged, c, first->col_names[c], first->col_types[c]) != TF_OK) goto fail;
|
|
217
982
|
}
|
|
218
|
-
for (size_t c = 0; c < first->n_cols; c++)
|
|
219
|
-
tf_batch_set_schema(merged, c, first->col_names[c], first->col_types[c]);
|
|
220
983
|
|
|
221
984
|
size_t dst_row = 0;
|
|
222
985
|
for (size_t b = 0; b < total_batches; b++) {
|
|
223
986
|
for (size_t r = 0; r < all_batches[b]->n_rows; r++) {
|
|
224
|
-
tf_batch_copy_row(merged, dst_row, all_batches[b], r);
|
|
225
|
-
merged
|
|
987
|
+
if (tf_batch_copy_row(merged, dst_row, all_batches[b], r) != TF_OK) goto fail;
|
|
988
|
+
if (tf_batch_expose_row(merged, dst_row) != TF_OK) goto fail;
|
|
989
|
+
dst_row++;
|
|
226
990
|
}
|
|
227
991
|
}
|
|
228
992
|
|
|
229
|
-
for (size_t i = 0; i < total_batches; i++)
|
|
993
|
+
for (size_t i = 0; i < total_batches; i++) all_batches[i] = NULL;
|
|
994
|
+
tf_batch_array_free(batches, n_batches);
|
|
995
|
+
batches = NULL;
|
|
996
|
+
tf_batch_array_free(flush_batches, n_flush);
|
|
997
|
+
flush_batches = NULL;
|
|
230
998
|
free(all_batches);
|
|
231
|
-
|
|
232
|
-
free(flush_batches);
|
|
999
|
+
all_batches = NULL;
|
|
233
1000
|
dec->destroy(dec);
|
|
1001
|
+
dec = NULL;
|
|
234
1002
|
free(data);
|
|
235
|
-
|
|
236
|
-
st->lookup = merged;
|
|
1003
|
+
data = NULL;
|
|
237
1004
|
|
|
238
1005
|
/* Find join column in lookup */
|
|
239
|
-
|
|
240
|
-
if (
|
|
1006
|
+
int lookup_join_col = tf_batch_col_index(merged, st->right_col);
|
|
1007
|
+
if (lookup_join_col < 0) goto fail;
|
|
241
1008
|
|
|
242
|
-
|
|
243
|
-
st->
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
1009
|
+
st->lookup_key_type = merged->col_types[lookup_join_col];
|
|
1010
|
+
st->have_lookup_key_type = 1;
|
|
1011
|
+
|
|
1012
|
+
size_t n_lookup_out = 0;
|
|
1013
|
+
if (st->how < 2) {
|
|
1014
|
+
/* Determine output columns for mutating joins (all except join key). */
|
|
1015
|
+
lookup_out_cols = tf_mallocarray_checked(merged->n_cols ? merged->n_cols : 1, sizeof(int));
|
|
1016
|
+
if (!lookup_out_cols) goto fail;
|
|
1017
|
+
for (size_t c = 0; c < merged->n_cols; c++) {
|
|
1018
|
+
if ((int)c != lookup_join_col)
|
|
1019
|
+
lookup_out_cols[n_lookup_out++] = (int)c;
|
|
1020
|
+
}
|
|
248
1021
|
}
|
|
249
1022
|
|
|
250
1023
|
/* Build hash map */
|
|
251
|
-
map_init(&
|
|
1024
|
+
if (map_init(&map, merged->n_rows) != 0) goto fail;
|
|
1025
|
+
const tf_batch *retained_lookup = (st->how >= 2) ? NULL : merged;
|
|
1026
|
+
size_t retained_lookup_out = (st->how >= 2) ? 0 : n_lookup_out;
|
|
1027
|
+
if (join_check_unsorted_state_bytes(st, &map, retained_lookup, retained_lookup_out, side) != TF_OK) goto fail;
|
|
252
1028
|
for (size_t r = 0; r < merged->n_rows; r++) {
|
|
253
|
-
char *key = format_join_key(merged, r,
|
|
254
|
-
if (key)
|
|
1029
|
+
char *key = format_join_key(merged, r, lookup_join_col);
|
|
1030
|
+
if (!key) goto fail;
|
|
1031
|
+
int rc = map_insert(st, &map, key, r, side);
|
|
1032
|
+
free(key);
|
|
1033
|
+
if (rc != 0) goto fail;
|
|
1034
|
+
if (join_check_unsorted_state_bytes(st, &map, retained_lookup, retained_lookup_out, side) != TF_OK) goto fail;
|
|
255
1035
|
}
|
|
256
1036
|
|
|
1037
|
+
if (st->how >= 2) {
|
|
1038
|
+
tf_batch_free(merged);
|
|
1039
|
+
merged = NULL;
|
|
1040
|
+
}
|
|
1041
|
+
st->lookup_rows = total_rows;
|
|
1042
|
+
st->lookup = merged;
|
|
1043
|
+
st->lookup_join_col = lookup_join_col;
|
|
1044
|
+
st->lookup_out_cols = lookup_out_cols;
|
|
1045
|
+
st->n_lookup_out = n_lookup_out;
|
|
1046
|
+
st->map = map;
|
|
257
1047
|
return TF_OK;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
static int join_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
261
|
-
tf_side_channels *side) {
|
|
262
|
-
(void)side;
|
|
263
|
-
join_state *st = self->state;
|
|
264
|
-
*out = NULL;
|
|
265
1048
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
1049
|
+
fail:
|
|
1050
|
+
if (f) fclose(f);
|
|
1051
|
+
if (all_batches) {
|
|
1052
|
+
for (size_t i = 0; i < total_batches; i++) tf_batch_free(all_batches[i]);
|
|
1053
|
+
free(all_batches);
|
|
1054
|
+
if (batches) {
|
|
1055
|
+
for (size_t i = 0; i < n_batches; i++) batches[i] = NULL;
|
|
1056
|
+
}
|
|
1057
|
+
if (flush_batches) {
|
|
1058
|
+
for (size_t i = 0; i < n_flush; i++) flush_batches[i] = NULL;
|
|
1059
|
+
}
|
|
269
1060
|
}
|
|
1061
|
+
tf_batch_array_free(batches, n_batches);
|
|
1062
|
+
tf_batch_array_free(flush_batches, n_flush);
|
|
1063
|
+
if (dec) dec->destroy(dec);
|
|
1064
|
+
free(data);
|
|
1065
|
+
if (merged) tf_batch_free(merged);
|
|
1066
|
+
free(lookup_out_cols);
|
|
1067
|
+
map_free(&map);
|
|
1068
|
+
return TF_ERROR;
|
|
1069
|
+
}
|
|
270
1070
|
|
|
271
|
-
|
|
272
|
-
if (
|
|
1071
|
+
static int join_reserve_output_row(join_state *st, tf_side_channels *side) {
|
|
1072
|
+
if (st->max_output_rows > 0 && st->output_rows >= st->max_output_rows) {
|
|
1073
|
+
if (join_limit_error_context(side, "max_output_rows",
|
|
1074
|
+
st->max_output_rows, st->output_rows + 1,
|
|
1075
|
+
"while emitting joined output") != TF_OK)
|
|
1076
|
+
return 0;
|
|
1077
|
+
return 0;
|
|
1078
|
+
}
|
|
1079
|
+
st->output_rows++;
|
|
1080
|
+
return 1;
|
|
1081
|
+
}
|
|
273
1082
|
|
|
274
|
-
size_t n_out_cols = in->n_cols + st->n_lookup_out;
|
|
275
1083
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
size_t
|
|
279
|
-
|
|
280
|
-
|
|
1084
|
+
static int join_ensure_ordinals(uint64_t **ord, size_t *cap, size_t need) {
|
|
1085
|
+
if (*cap >= need) return TF_OK;
|
|
1086
|
+
size_t new_cap = 0;
|
|
1087
|
+
if (tf_size_grow_pow2(*cap, need, 16, &new_cap) != TF_OK) return TF_ERROR;
|
|
1088
|
+
uint64_t *tmp = tf_reallocarray_checked(*ord, new_cap, sizeof(uint64_t));
|
|
1089
|
+
if (!tmp) return TF_ERROR;
|
|
1090
|
+
*ord = tmp;
|
|
1091
|
+
*cap = new_cap;
|
|
1092
|
+
return TF_OK;
|
|
1093
|
+
}
|
|
281
1094
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
1095
|
+
static size_t join_spill_estimated_row_bytes(const join_state *st) {
|
|
1096
|
+
size_t bytes = 40;
|
|
1097
|
+
for (size_t c = 0; c < st->spill_n_cols; c++) {
|
|
1098
|
+
bytes += 1;
|
|
1099
|
+
switch (st->spill_schema_types[c]) {
|
|
1100
|
+
case TF_TYPE_BOOL: bytes += 1; break;
|
|
1101
|
+
case TF_TYPE_INT64: bytes += sizeof(int64_t); break;
|
|
1102
|
+
case TF_TYPE_FLOAT64: bytes += sizeof(double); break;
|
|
1103
|
+
case TF_TYPE_STRING: bytes += sizeof(char *) + 64; break;
|
|
1104
|
+
case TF_TYPE_DATE: bytes += sizeof(int32_t); break;
|
|
1105
|
+
case TF_TYPE_TIMESTAMP: bytes += sizeof(int64_t); break;
|
|
1106
|
+
default: break;
|
|
1107
|
+
}
|
|
290
1108
|
}
|
|
1109
|
+
return bytes < 64 ? 64 : bytes;
|
|
1110
|
+
}
|
|
291
1111
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
1112
|
+
static tf_batch *join_spill_create_left_batch(const join_state *st, size_t capacity) {
|
|
1113
|
+
tf_batch *b = tf_batch_create(st->spill_n_cols, capacity ? capacity : 16);
|
|
1114
|
+
if (!b) return NULL;
|
|
1115
|
+
for (size_t c = 0; c < st->spill_n_cols; c++) {
|
|
1116
|
+
if (tf_batch_set_schema(b, c, st->spill_schema_names[c], st->spill_schema_types[c]) != TF_OK) {
|
|
1117
|
+
tf_batch_free(b);
|
|
1118
|
+
return NULL;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return b;
|
|
1122
|
+
}
|
|
299
1123
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
for (size_t k = 0; k < st->n_lookup_out; k++)
|
|
310
|
-
copy_cell(ob, out_row, in->n_cols + k, st->lookup, lr, st->lookup_out_cols[k]);
|
|
311
|
-
ob->n_rows = ++out_row;
|
|
1124
|
+
static tf_batch *join_spill_create_lookup_batch(const join_state *st, size_t capacity) {
|
|
1125
|
+
if (st->how < 2) {
|
|
1126
|
+
tf_batch *b = tf_batch_create(st->spill_lookup_n_cols, capacity ? capacity : 16);
|
|
1127
|
+
if (!b) return NULL;
|
|
1128
|
+
for (size_t c = 0; c < st->spill_lookup_n_cols; c++) {
|
|
1129
|
+
if (tf_batch_set_schema(b, c, st->spill_lookup_schema_names[c],
|
|
1130
|
+
st->spill_lookup_schema_types[c]) != TF_OK) {
|
|
1131
|
+
tf_batch_free(b);
|
|
1132
|
+
return NULL;
|
|
312
1133
|
}
|
|
313
|
-
} else if (st->how == 1) {
|
|
314
|
-
/* Left join: emit main + nulls */
|
|
315
|
-
tf_batch_ensure_capacity(ob, out_row + 1);
|
|
316
|
-
for (size_t c = 0; c < in->n_cols; c++)
|
|
317
|
-
copy_cell(ob, out_row, c, in, r, (int)c);
|
|
318
|
-
for (size_t k = 0; k < st->n_lookup_out; k++)
|
|
319
|
-
tf_batch_set_null(ob, out_row, in->n_cols + k);
|
|
320
|
-
ob->n_rows = ++out_row;
|
|
321
1134
|
}
|
|
322
|
-
|
|
1135
|
+
return b;
|
|
323
1136
|
}
|
|
324
1137
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
tf_batch_free(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
static int join_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
334
|
-
(void)self; (void)side; *out = NULL; return TF_OK;
|
|
1138
|
+
tf_batch *b = tf_batch_create(1, capacity ? capacity : 16);
|
|
1139
|
+
if (!b) return NULL;
|
|
1140
|
+
if (tf_batch_set_schema(b, 0, st->right_col ? st->right_col : "key", st->lookup_key_type) != TF_OK) {
|
|
1141
|
+
tf_batch_free(b);
|
|
1142
|
+
return NULL;
|
|
1143
|
+
}
|
|
1144
|
+
return b;
|
|
335
1145
|
}
|
|
336
1146
|
|
|
337
|
-
static
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
1147
|
+
static tf_batch *join_spill_create_output_batch(const join_state *st, size_t capacity) {
|
|
1148
|
+
if (st->how < 2) {
|
|
1149
|
+
tf_batch *b = tf_batch_create(st->spill_output_n_cols, capacity ? capacity : 16);
|
|
1150
|
+
if (!b) return NULL;
|
|
1151
|
+
for (size_t c = 0; c < st->spill_output_n_cols; c++) {
|
|
1152
|
+
if (tf_batch_set_schema(b, c, st->spill_output_schema_names[c],
|
|
1153
|
+
st->spill_output_schema_types[c]) != TF_OK) {
|
|
1154
|
+
tf_batch_free(b);
|
|
1155
|
+
return NULL;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return b;
|
|
347
1159
|
}
|
|
348
|
-
|
|
1160
|
+
return join_spill_create_left_batch(st, capacity);
|
|
349
1161
|
}
|
|
350
1162
|
|
|
351
|
-
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
1163
|
+
static int join_spill_check_input_schema(join_state *st, const tf_batch *in, tf_side_channels *side) {
|
|
1164
|
+
if (in->n_cols != st->spill_n_cols) {
|
|
1165
|
+
if (join_write_error(side, "join spill: input schema changed across batches") != TF_OK)
|
|
1166
|
+
return TF_ERROR;
|
|
1167
|
+
return TF_ERROR;
|
|
1168
|
+
}
|
|
1169
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
1170
|
+
if (in->col_types[c] != st->spill_schema_types[c] ||
|
|
1171
|
+
strcmp(in->col_names[c] ? in->col_names[c] : "", st->spill_schema_names[c]) != 0) {
|
|
1172
|
+
if (join_write_error(side, "join spill: input schema changed across batches") != TF_OK)
|
|
1173
|
+
return TF_ERROR;
|
|
1174
|
+
return TF_ERROR;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
int left_ci = tf_batch_col_index(in, st->left_col);
|
|
1178
|
+
if (left_ci < 0 || left_ci != st->spill_left_join_col) {
|
|
1179
|
+
if (join_write_error(side, "join spill: left join column is missing or moved") != TF_OK)
|
|
1180
|
+
return TF_ERROR;
|
|
1181
|
+
return TF_ERROR;
|
|
1182
|
+
}
|
|
1183
|
+
return TF_OK;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
static int join_spill_init_schema(join_state *st, const tf_batch *in, tf_side_channels *side) {
|
|
1187
|
+
if (st->spill_has_schema) return join_spill_check_input_schema(st, in, side);
|
|
1188
|
+
int left_ci = tf_batch_col_index(in, st->left_col);
|
|
1189
|
+
if (left_ci < 0) {
|
|
1190
|
+
if (join_write_error(side, "join spill: left join column not found") != TF_OK)
|
|
1191
|
+
return TF_ERROR;
|
|
1192
|
+
return TF_ERROR;
|
|
1193
|
+
}
|
|
1194
|
+
st->spill_n_cols = in->n_cols;
|
|
1195
|
+
st->spill_schema_names = tf_callocarray_checked(in->n_cols ? in->n_cols : 1, sizeof(char *));
|
|
1196
|
+
st->spill_schema_types = tf_callocarray_checked(in->n_cols ? in->n_cols : 1, sizeof(tf_type));
|
|
1197
|
+
if (!st->spill_schema_names || !st->spill_schema_types) return TF_ERROR;
|
|
1198
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
1199
|
+
st->spill_schema_names[c] = strdup(in->col_names[c] ? in->col_names[c] : "");
|
|
1200
|
+
if (!st->spill_schema_names[c]) return TF_ERROR;
|
|
1201
|
+
st->spill_schema_types[c] = in->col_types[c];
|
|
1202
|
+
}
|
|
1203
|
+
st->spill_left_join_col = left_ci;
|
|
1204
|
+
if (st->configured_run_rows > 0) {
|
|
1205
|
+
st->run_rows = st->configured_run_rows;
|
|
1206
|
+
} else if (st->spill_memory_bytes > 0) {
|
|
1207
|
+
size_t row_bytes = join_spill_estimated_row_bytes(st);
|
|
1208
|
+
st->run_rows = st->spill_memory_bytes / (row_bytes * 4);
|
|
1209
|
+
if (st->run_rows < JOIN_MIN_RUN_ROWS) st->run_rows = JOIN_MIN_RUN_ROWS;
|
|
1210
|
+
} else {
|
|
1211
|
+
st->run_rows = JOIN_DEFAULT_RUN_ROWS;
|
|
1212
|
+
}
|
|
1213
|
+
st->spill_left_buf = join_spill_create_left_batch(st, st->run_rows);
|
|
1214
|
+
if (st->how >= 2)
|
|
1215
|
+
st->spill_out_buf = join_spill_create_output_batch(st, st->run_rows);
|
|
1216
|
+
if (!st->spill_left_buf || (st->how >= 2 && !st->spill_out_buf)) return TF_ERROR;
|
|
1217
|
+
st->lookup_key_type = in->col_types[left_ci];
|
|
1218
|
+
st->have_lookup_key_type = 0;
|
|
1219
|
+
st->spill_has_schema = 1;
|
|
1220
|
+
return TF_OK;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
static int join_spill_compare_batch_key(const tf_batch *b, size_t ca, size_t cb,
|
|
1224
|
+
size_t ra, size_t rb) {
|
|
1225
|
+
int null_a = tf_batch_is_null(b, ra, ca);
|
|
1226
|
+
int null_b = tf_batch_is_null(b, rb, cb);
|
|
1227
|
+
if (null_a && null_b) return 0;
|
|
1228
|
+
if (null_a) return 1;
|
|
1229
|
+
if (null_b) return -1;
|
|
1230
|
+
switch (b->col_types[ca]) {
|
|
1231
|
+
case TF_TYPE_BOOL:
|
|
1232
|
+
return (int)tf_batch_get_bool(b, ra, ca) - (int)tf_batch_get_bool(b, rb, cb);
|
|
1233
|
+
case TF_TYPE_INT64: {
|
|
1234
|
+
int64_t a = tf_batch_get_int64(b, ra, ca), v = tf_batch_get_int64(b, rb, cb);
|
|
1235
|
+
return (a > v) - (a < v);
|
|
1236
|
+
}
|
|
1237
|
+
case TF_TYPE_FLOAT64: {
|
|
1238
|
+
double a = tf_batch_get_float64(b, ra, ca), v = tf_batch_get_float64(b, rb, cb);
|
|
1239
|
+
return (a > v) - (a < v);
|
|
1240
|
+
}
|
|
1241
|
+
case TF_TYPE_STRING:
|
|
1242
|
+
return strcmp(tf_batch_get_string(b, ra, ca), tf_batch_get_string(b, rb, cb));
|
|
1243
|
+
case TF_TYPE_DATE: {
|
|
1244
|
+
int32_t a = tf_batch_get_date(b, ra, ca), v = tf_batch_get_date(b, rb, cb);
|
|
1245
|
+
return (a > v) - (a < v);
|
|
1246
|
+
}
|
|
1247
|
+
case TF_TYPE_TIMESTAMP: {
|
|
1248
|
+
int64_t a = tf_batch_get_timestamp(b, ra, ca), v = tf_batch_get_timestamp(b, rb, cb);
|
|
1249
|
+
return (a > v) - (a < v);
|
|
1250
|
+
}
|
|
1251
|
+
default:
|
|
1252
|
+
return 0;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
typedef struct {
|
|
1257
|
+
const join_state *st;
|
|
1258
|
+
const tf_batch *batch;
|
|
1259
|
+
const uint64_t *ordinals;
|
|
1260
|
+
int kind; /* 0=left by key, 1=lookup by key, 2=output by ordinal */
|
|
1261
|
+
} join_spill_sort_ctx;
|
|
1262
|
+
|
|
1263
|
+
static int join_spill_compare_indices(const void *ctx, size_t ra, size_t rb) {
|
|
1264
|
+
const join_spill_sort_ctx *sort = (const join_spill_sort_ctx *)ctx;
|
|
1265
|
+
if (sort->kind == 2) {
|
|
1266
|
+
uint64_t oa = sort->ordinals[ra], ob = sort->ordinals[rb];
|
|
1267
|
+
return (oa > ob) - (oa < ob);
|
|
1268
|
+
}
|
|
1269
|
+
size_t col;
|
|
1270
|
+
if (sort->kind == 1)
|
|
1271
|
+
col = (sort->st->how < 2) ? (size_t)sort->st->spill_lookup_join_col : 0u;
|
|
1272
|
+
else
|
|
1273
|
+
col = (size_t)sort->st->spill_left_join_col;
|
|
1274
|
+
int cmp = join_spill_compare_batch_key(sort->batch, col, col, ra, rb);
|
|
1275
|
+
if (cmp != 0) return cmp;
|
|
1276
|
+
uint64_t oa = sort->ordinals[ra], ob = sort->ordinals[rb];
|
|
1277
|
+
return (oa > ob) - (oa < ob);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
static size_t *join_spill_sorted_indices(const join_state *st, const tf_batch *b,
|
|
1281
|
+
const uint64_t *ordinals, int kind) {
|
|
1282
|
+
size_t n = b ? b->n_rows : 0;
|
|
1283
|
+
size_t *idx = tf_mallocarray_checked(n ? n : 1, sizeof(size_t));
|
|
1284
|
+
if (!idx) return NULL;
|
|
1285
|
+
for (size_t i = 0; i < n; i++) idx[i] = i;
|
|
1286
|
+
join_spill_sort_ctx ctx = { .st = st, .batch = b, .ordinals = ordinals, .kind = kind };
|
|
1287
|
+
tf_sort_indices(idx, n, join_spill_compare_indices, &ctx);
|
|
1288
|
+
return idx;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
static int join_spill_write_exact(FILE *f, const void *ptr, size_t len) {
|
|
1292
|
+
return fwrite(ptr, 1, len, f) == len ? TF_OK : TF_ERROR;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
static int join_spill_read_exact(FILE *f, void *ptr, size_t len) {
|
|
1296
|
+
return fread(ptr, 1, len, f) == len ? TF_OK : TF_ERROR;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
static int join_spill_write_cell(FILE *f, const tf_batch *b, size_t r, size_t c) {
|
|
1300
|
+
uint8_t is_null = tf_batch_is_null(b, r, c) ? 1 : 0;
|
|
1301
|
+
if (join_spill_write_exact(f, &is_null, sizeof(is_null)) != TF_OK) return TF_ERROR;
|
|
1302
|
+
if (is_null) return TF_OK;
|
|
1303
|
+
switch (b->col_types[c]) {
|
|
1304
|
+
case TF_TYPE_BOOL: { uint8_t v = tf_batch_get_bool(b, r, c) ? 1 : 0; return join_spill_write_exact(f, &v, sizeof(v)); }
|
|
1305
|
+
case TF_TYPE_INT64: { int64_t v = tf_batch_get_int64(b, r, c); return join_spill_write_exact(f, &v, sizeof(v)); }
|
|
1306
|
+
case TF_TYPE_FLOAT64: { double v = tf_batch_get_float64(b, r, c); return join_spill_write_exact(f, &v, sizeof(v)); }
|
|
1307
|
+
case TF_TYPE_STRING: {
|
|
1308
|
+
const char *str = tf_batch_get_string(b, r, c);
|
|
1309
|
+
uint64_t len = str ? (uint64_t)strlen(str) : 0;
|
|
1310
|
+
if (join_spill_write_exact(f, &len, sizeof(len)) != TF_OK) return TF_ERROR;
|
|
1311
|
+
return len ? join_spill_write_exact(f, str, (size_t)len) : TF_OK;
|
|
1312
|
+
}
|
|
1313
|
+
case TF_TYPE_DATE: { int32_t v = tf_batch_get_date(b, r, c); return join_spill_write_exact(f, &v, sizeof(v)); }
|
|
1314
|
+
case TF_TYPE_TIMESTAMP: { int64_t v = tf_batch_get_timestamp(b, r, c); return join_spill_write_exact(f, &v, sizeof(v)); }
|
|
1315
|
+
default: return TF_OK;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
static int join_spill_append_path(char ***paths, size_t *n, size_t *cap, char *path) {
|
|
1320
|
+
if (*n == *cap) {
|
|
1321
|
+
size_t need = 0;
|
|
1322
|
+
size_t new_cap = 0;
|
|
1323
|
+
if (tf_size_add(*n, 1, &need) != TF_OK ||
|
|
1324
|
+
tf_size_grow_pow2(*cap, need, 8, &new_cap) != TF_OK) {
|
|
1325
|
+
return TF_ERROR;
|
|
1326
|
+
}
|
|
1327
|
+
char **tmp = tf_reallocarray_checked(*paths, new_cap, sizeof(char *));
|
|
1328
|
+
if (!tmp) return TF_ERROR;
|
|
1329
|
+
*paths = tmp;
|
|
1330
|
+
*cap = new_cap;
|
|
1331
|
+
}
|
|
1332
|
+
(*paths)[(*n)++] = path;
|
|
1333
|
+
return TF_OK;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
static int join_spill_write_run(join_state *st, tf_batch *batch, const uint64_t *ordinals,
|
|
1337
|
+
const size_t *indices, size_t n, const char *kind) {
|
|
1338
|
+
if (n == 0) return TF_OK;
|
|
1339
|
+
char label[48];
|
|
1340
|
+
snprintf(label, sizeof(label), "join-%s", kind ? kind : "run");
|
|
1341
|
+
char *path = NULL;
|
|
1342
|
+
FILE *f = tf_spill_open_run_file(st->spill, label, &path);
|
|
1343
|
+
if (!f) return TF_ERROR;
|
|
1344
|
+
for (size_t i = 0; i < n; i++) {
|
|
1345
|
+
size_t r = indices[i];
|
|
1346
|
+
uint64_t ordinal = ordinals[r];
|
|
1347
|
+
if (join_spill_write_exact(f, &ordinal, sizeof(ordinal)) != TF_OK) goto fail;
|
|
1348
|
+
for (size_t c = 0; c < batch->n_cols; c++) {
|
|
1349
|
+
if (join_spill_write_cell(f, batch, r, c) != TF_OK) goto fail;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
long pos = ftell(f);
|
|
1353
|
+
if (pos > 0) st->spill_bytes += (size_t)pos;
|
|
1354
|
+
if (fclose(f) != 0) {
|
|
1355
|
+
tf_set_last_error("join spill: failed closing run file");
|
|
1356
|
+
remove(path);
|
|
1357
|
+
free(path);
|
|
1358
|
+
return TF_ERROR;
|
|
1359
|
+
}
|
|
1360
|
+
int rc;
|
|
1361
|
+
if (strcmp(kind, "left") == 0) {
|
|
1362
|
+
rc = join_spill_append_path(&st->spill_left_run_paths, &st->spill_n_left_runs, &st->spill_cap_left_runs, path);
|
|
1363
|
+
} else if (strcmp(kind, "lookup") == 0) {
|
|
1364
|
+
rc = join_spill_append_path(&st->spill_lookup_run_paths, &st->spill_n_lookup_runs, &st->spill_cap_lookup_runs, path);
|
|
1365
|
+
} else {
|
|
1366
|
+
rc = join_spill_append_path(&st->spill_out_run_paths, &st->spill_n_out_runs, &st->spill_cap_out_runs, path);
|
|
1367
|
+
}
|
|
1368
|
+
if (rc != TF_OK) { remove(path); free(path); return TF_ERROR; }
|
|
1369
|
+
st->spill_runs++;
|
|
1370
|
+
return TF_OK;
|
|
1371
|
+
fail:
|
|
1372
|
+
tf_set_last_error("join spill: failed writing run file");
|
|
1373
|
+
fclose(f);
|
|
1374
|
+
remove(path);
|
|
1375
|
+
free(path);
|
|
1376
|
+
return TF_ERROR;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
static int join_spill_write_left_run(join_state *st) {
|
|
1380
|
+
if (!st->spill_left_buf || st->spill_left_buf->n_rows == 0) return TF_OK;
|
|
1381
|
+
size_t *idx = join_spill_sorted_indices(st, st->spill_left_buf, st->spill_left_ordinals, 0);
|
|
1382
|
+
if (!idx) return TF_ERROR;
|
|
1383
|
+
int rc = join_spill_write_run(st, st->spill_left_buf, st->spill_left_ordinals, idx,
|
|
1384
|
+
st->spill_left_buf->n_rows, "left");
|
|
1385
|
+
free(idx);
|
|
1386
|
+
if (rc != TF_OK) return TF_ERROR;
|
|
1387
|
+
tf_batch_free(st->spill_left_buf);
|
|
1388
|
+
st->spill_left_buf = join_spill_create_left_batch(st, st->run_rows);
|
|
1389
|
+
free(st->spill_left_ordinals);
|
|
1390
|
+
st->spill_left_ordinals = NULL;
|
|
1391
|
+
st->spill_left_ordinal_cap = 0;
|
|
1392
|
+
return st->spill_left_buf ? TF_OK : TF_ERROR;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
static int join_spill_write_lookup_run(join_state *st) {
|
|
1396
|
+
if (!st->spill_lookup_buf || st->spill_lookup_buf->n_rows == 0) return TF_OK;
|
|
1397
|
+
size_t *idx = join_spill_sorted_indices(st, st->spill_lookup_buf, st->spill_lookup_ordinals, 1);
|
|
1398
|
+
if (!idx) return TF_ERROR;
|
|
1399
|
+
int rc = join_spill_write_run(st, st->spill_lookup_buf, st->spill_lookup_ordinals, idx,
|
|
1400
|
+
st->spill_lookup_buf->n_rows, "lookup");
|
|
1401
|
+
free(idx);
|
|
1402
|
+
if (rc != TF_OK) return TF_ERROR;
|
|
1403
|
+
tf_batch_free(st->spill_lookup_buf);
|
|
1404
|
+
st->spill_lookup_buf = join_spill_create_lookup_batch(st, st->run_rows);
|
|
1405
|
+
free(st->spill_lookup_ordinals);
|
|
1406
|
+
st->spill_lookup_ordinals = NULL;
|
|
1407
|
+
st->spill_lookup_ordinal_cap = 0;
|
|
1408
|
+
return st->spill_lookup_buf ? TF_OK : TF_ERROR;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
static int join_spill_write_output_run(join_state *st) {
|
|
1412
|
+
if (!st->spill_out_buf || st->spill_out_buf->n_rows == 0) return TF_OK;
|
|
1413
|
+
size_t *idx = join_spill_sorted_indices(st, st->spill_out_buf, st->spill_out_ordinals, 2);
|
|
1414
|
+
if (!idx) return TF_ERROR;
|
|
1415
|
+
int rc = join_spill_write_run(st, st->spill_out_buf, st->spill_out_ordinals, idx,
|
|
1416
|
+
st->spill_out_buf->n_rows, "out");
|
|
1417
|
+
free(idx);
|
|
1418
|
+
if (rc != TF_OK) return TF_ERROR;
|
|
1419
|
+
tf_batch_free(st->spill_out_buf);
|
|
1420
|
+
st->spill_out_buf = join_spill_create_output_batch(st, st->run_rows);
|
|
1421
|
+
free(st->spill_out_ordinals);
|
|
1422
|
+
st->spill_out_ordinals = NULL;
|
|
1423
|
+
st->spill_out_ordinal_cap = 0;
|
|
1424
|
+
return st->spill_out_buf ? TF_OK : TF_ERROR;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
static void join_spill_row_clear(join_spill_row *row, const tf_type *types, size_t n_cols) {
|
|
1428
|
+
if (!row || !row->cells || !row->nulls) return;
|
|
1429
|
+
for (size_t c = 0; c < n_cols; c++) {
|
|
1430
|
+
if (!row->nulls[c] && types[c] == TF_TYPE_STRING) free(row->cells[c].str);
|
|
1431
|
+
row->cells[c].str = NULL;
|
|
1432
|
+
row->nulls[c] = 1;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
static int join_spill_row_init(join_spill_row *row, size_t n_cols) {
|
|
1437
|
+
row->ordinal = 0;
|
|
1438
|
+
row->nulls = tf_callocarray_checked(n_cols ? n_cols : 1, sizeof(uint8_t));
|
|
1439
|
+
row->cells = tf_callocarray_checked(n_cols ? n_cols : 1, sizeof(join_key_data));
|
|
1440
|
+
if (!row->nulls || !row->cells) {
|
|
1441
|
+
free(row->nulls);
|
|
1442
|
+
free(row->cells);
|
|
1443
|
+
row->nulls = NULL;
|
|
1444
|
+
row->cells = NULL;
|
|
1445
|
+
return TF_ERROR;
|
|
1446
|
+
}
|
|
1447
|
+
for (size_t c = 0; c < n_cols; c++) row->nulls[c] = 1;
|
|
1448
|
+
return TF_OK;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
static void join_spill_row_free(join_spill_row *row, const tf_type *types, size_t n_cols) {
|
|
1452
|
+
if (!row) return;
|
|
1453
|
+
join_spill_row_clear(row, types, n_cols);
|
|
1454
|
+
free(row->nulls);
|
|
1455
|
+
free(row->cells);
|
|
1456
|
+
row->nulls = NULL;
|
|
1457
|
+
row->cells = NULL;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
static int join_spill_read_cell_value(FILE *f, join_spill_row *row, const tf_type *types, size_t c) {
|
|
1461
|
+
switch (types[c]) {
|
|
1462
|
+
case TF_TYPE_BOOL: { uint8_t v = 0; if (join_spill_read_exact(f, &v, sizeof(v)) != TF_OK) return TF_ERROR; row->cells[c].b = v; return TF_OK; }
|
|
1463
|
+
case TF_TYPE_INT64: { int64_t v = 0; if (join_spill_read_exact(f, &v, sizeof(v)) != TF_OK) return TF_ERROR; row->cells[c].i64 = v; return TF_OK; }
|
|
1464
|
+
case TF_TYPE_FLOAT64: { double v = 0; if (join_spill_read_exact(f, &v, sizeof(v)) != TF_OK) return TF_ERROR; row->cells[c].f64 = v; return TF_OK; }
|
|
1465
|
+
case TF_TYPE_STRING: {
|
|
1466
|
+
uint64_t len = 0;
|
|
1467
|
+
if (join_spill_read_exact(f, &len, sizeof(len)) != TF_OK) return TF_ERROR;
|
|
1468
|
+
if (len > (uint64_t)SIZE_MAX - 1) return TF_ERROR;
|
|
1469
|
+
char *str = malloc((size_t)len + 1);
|
|
1470
|
+
if (!str) return TF_ERROR;
|
|
1471
|
+
if (len && join_spill_read_exact(f, str, (size_t)len) != TF_OK) { free(str); return TF_ERROR; }
|
|
1472
|
+
str[len] = '\0';
|
|
1473
|
+
row->cells[c].str = str;
|
|
1474
|
+
return TF_OK;
|
|
1475
|
+
}
|
|
1476
|
+
case TF_TYPE_DATE: { int32_t v = 0; if (join_spill_read_exact(f, &v, sizeof(v)) != TF_OK) return TF_ERROR; row->cells[c].date = v; return TF_OK; }
|
|
1477
|
+
case TF_TYPE_TIMESTAMP: { int64_t v = 0; if (join_spill_read_exact(f, &v, sizeof(v)) != TF_OK) return TF_ERROR; row->cells[c].i64 = v; return TF_OK; }
|
|
1478
|
+
default: return TF_OK;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
static int join_spill_reader_advance(join_spill_reader *reader, const tf_type *types, size_t n_cols) {
|
|
1483
|
+
if (!reader || !reader->file || reader->done) return 0;
|
|
1484
|
+
join_spill_row_clear(&reader->row, types, n_cols);
|
|
1485
|
+
if (fread(&reader->row.ordinal, sizeof(reader->row.ordinal), 1, reader->file) != 1) {
|
|
1486
|
+
if (feof(reader->file)) {
|
|
1487
|
+
reader->done = 1;
|
|
1488
|
+
reader->has_row = 0;
|
|
1489
|
+
return 0;
|
|
1490
|
+
}
|
|
1491
|
+
tf_set_last_error("join spill: failed reading run file");
|
|
1492
|
+
return -1;
|
|
1493
|
+
}
|
|
1494
|
+
for (size_t c = 0; c < n_cols; c++) {
|
|
1495
|
+
uint8_t is_null = 1;
|
|
1496
|
+
if (join_spill_read_exact(reader->file, &is_null, sizeof(is_null)) != TF_OK) {
|
|
1497
|
+
tf_set_last_error("join spill: corrupt run file");
|
|
1498
|
+
return -1;
|
|
1499
|
+
}
|
|
1500
|
+
reader->row.nulls[c] = is_null ? 1 : 0;
|
|
1501
|
+
if (!reader->row.nulls[c] && join_spill_read_cell_value(reader->file, &reader->row, types, c) != TF_OK) {
|
|
1502
|
+
tf_set_last_error("join spill: corrupt run file");
|
|
1503
|
+
return -1;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
reader->has_row = 1;
|
|
1507
|
+
return 1;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
static const tf_type *join_spill_reader_types(const join_state *st, int kind, size_t *n_cols) {
|
|
1511
|
+
if (kind == 1) {
|
|
1512
|
+
if (st->how < 2) {
|
|
1513
|
+
*n_cols = st->spill_lookup_n_cols;
|
|
1514
|
+
return st->spill_lookup_schema_types;
|
|
1515
|
+
}
|
|
1516
|
+
*n_cols = 1;
|
|
1517
|
+
return &st->lookup_key_type;
|
|
1518
|
+
}
|
|
1519
|
+
if (kind == 2 && st->how < 2) {
|
|
1520
|
+
*n_cols = st->spill_output_n_cols;
|
|
1521
|
+
return st->spill_output_schema_types;
|
|
1522
|
+
}
|
|
1523
|
+
*n_cols = st->spill_n_cols;
|
|
1524
|
+
return st->spill_schema_types;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
static void join_spill_close_readers(join_state *st, int kind) {
|
|
1528
|
+
join_spill_reader **readers;
|
|
1529
|
+
size_t *n_readers;
|
|
1530
|
+
if (kind == 0) { readers = &st->spill_left_readers; n_readers = &st->spill_n_left_readers; }
|
|
1531
|
+
else if (kind == 1) { readers = &st->spill_lookup_readers; n_readers = &st->spill_n_lookup_readers; }
|
|
1532
|
+
else { readers = &st->spill_out_readers; n_readers = &st->spill_n_out_readers; }
|
|
1533
|
+
if (!*readers) return;
|
|
1534
|
+
size_t n_cols = 0;
|
|
1535
|
+
const tf_type *types = join_spill_reader_types(st, kind, &n_cols);
|
|
1536
|
+
for (size_t i = 0; i < *n_readers; i++) {
|
|
1537
|
+
if ((*readers)[i].file) fclose((*readers)[i].file);
|
|
1538
|
+
join_spill_row_free(&(*readers)[i].row, types, n_cols);
|
|
1539
|
+
}
|
|
1540
|
+
free(*readers);
|
|
1541
|
+
*readers = NULL;
|
|
1542
|
+
*n_readers = 0;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
static void join_spill_remove_paths(char ***paths, size_t *n, size_t *cap) {
|
|
1546
|
+
for (size_t i = 0; i < *n; i++) {
|
|
1547
|
+
if ((*paths)[i]) {
|
|
1548
|
+
remove((*paths)[i]);
|
|
1549
|
+
free((*paths)[i]);
|
|
1550
|
+
(*paths)[i] = NULL;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
free(*paths);
|
|
1554
|
+
*paths = NULL;
|
|
1555
|
+
*n = 0;
|
|
1556
|
+
*cap = 0;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
static int join_spill_open_readers(join_state *st, int kind) {
|
|
1560
|
+
char **paths;
|
|
1561
|
+
size_t n_paths;
|
|
1562
|
+
join_spill_reader **readers;
|
|
1563
|
+
size_t *n_readers;
|
|
1564
|
+
if (kind == 0) {
|
|
1565
|
+
paths = st->spill_left_run_paths; n_paths = st->spill_n_left_runs;
|
|
1566
|
+
readers = &st->spill_left_readers; n_readers = &st->spill_n_left_readers;
|
|
1567
|
+
} else if (kind == 1) {
|
|
1568
|
+
paths = st->spill_lookup_run_paths; n_paths = st->spill_n_lookup_runs;
|
|
1569
|
+
readers = &st->spill_lookup_readers; n_readers = &st->spill_n_lookup_readers;
|
|
1570
|
+
} else {
|
|
1571
|
+
paths = st->spill_out_run_paths; n_paths = st->spill_n_out_runs;
|
|
1572
|
+
readers = &st->spill_out_readers; n_readers = &st->spill_n_out_readers;
|
|
1573
|
+
}
|
|
1574
|
+
if (n_paths == 0) return TF_OK;
|
|
1575
|
+
*readers = tf_callocarray_checked(n_paths, sizeof(join_spill_reader));
|
|
1576
|
+
if (!*readers) return TF_ERROR;
|
|
1577
|
+
*n_readers = n_paths;
|
|
1578
|
+
size_t n_cols = 0;
|
|
1579
|
+
const tf_type *types = join_spill_reader_types(st, kind, &n_cols);
|
|
1580
|
+
for (size_t i = 0; i < n_paths; i++) {
|
|
1581
|
+
(*readers)[i].file = fopen(paths[i], "rb");
|
|
1582
|
+
if (!(*readers)[i].file) { tf_set_last_error("join spill: cannot reopen run file"); return TF_ERROR; }
|
|
1583
|
+
if (join_spill_row_init(&(*readers)[i].row, n_cols) != TF_OK) return TF_ERROR;
|
|
1584
|
+
int rc = join_spill_reader_advance(&(*readers)[i], types, n_cols);
|
|
1585
|
+
if (rc < 0) return TF_ERROR;
|
|
1586
|
+
}
|
|
1587
|
+
return TF_OK;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
static int join_spill_compare_row_cell(const join_spill_row *a, size_t ac, tf_type at,
|
|
1591
|
+
const join_spill_row *b, size_t bc, tf_type bt) {
|
|
1592
|
+
if (at != bt) return (int)at - (int)bt;
|
|
1593
|
+
int null_a = a->nulls[ac] != 0;
|
|
1594
|
+
int null_b = b->nulls[bc] != 0;
|
|
1595
|
+
if (null_a && null_b) return 0;
|
|
1596
|
+
if (null_a) return 1;
|
|
1597
|
+
if (null_b) return -1;
|
|
1598
|
+
switch (at) {
|
|
1599
|
+
case TF_TYPE_BOOL: return (int)a->cells[ac].b - (int)b->cells[bc].b;
|
|
1600
|
+
case TF_TYPE_INT64:
|
|
1601
|
+
case TF_TYPE_TIMESTAMP: return (a->cells[ac].i64 > b->cells[bc].i64) - (a->cells[ac].i64 < b->cells[bc].i64);
|
|
1602
|
+
case TF_TYPE_FLOAT64: return (a->cells[ac].f64 > b->cells[bc].f64) - (a->cells[ac].f64 < b->cells[bc].f64);
|
|
1603
|
+
case TF_TYPE_STRING: return strcmp(a->cells[ac].str ? a->cells[ac].str : "", b->cells[bc].str ? b->cells[bc].str : "");
|
|
1604
|
+
case TF_TYPE_DATE: return (a->cells[ac].date > b->cells[bc].date) - (a->cells[ac].date < b->cells[bc].date);
|
|
1605
|
+
default: return 0;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
static int join_spill_compare_left_rows(const join_state *st, const join_spill_row *a,
|
|
1610
|
+
const join_spill_row *b) {
|
|
1611
|
+
size_t c = (size_t)st->spill_left_join_col;
|
|
1612
|
+
int cmp = join_spill_compare_row_cell(a, c, st->spill_schema_types[c], b, c, st->spill_schema_types[c]);
|
|
1613
|
+
if (cmp != 0) return cmp;
|
|
1614
|
+
return (a->ordinal > b->ordinal) - (a->ordinal < b->ordinal);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
static int join_spill_compare_lookup_rows(const join_state *st, const join_spill_row *a,
|
|
1618
|
+
const join_spill_row *b) {
|
|
1619
|
+
size_t c = (st->how < 2) ? (size_t)st->spill_lookup_join_col : 0u;
|
|
1620
|
+
tf_type t = (st->how < 2) ? st->spill_lookup_schema_types[c] : st->lookup_key_type;
|
|
1621
|
+
int cmp = join_spill_compare_row_cell(a, c, t, b, c, t);
|
|
1622
|
+
if (cmp != 0) return cmp;
|
|
1623
|
+
return (a->ordinal > b->ordinal) - (a->ordinal < b->ordinal);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
static int join_spill_compare_lookup_to_left(const join_state *st, const join_spill_row *lookup,
|
|
1627
|
+
const join_spill_row *left) {
|
|
1628
|
+
size_t lc = (size_t)st->spill_left_join_col;
|
|
1629
|
+
size_t rc = (st->how < 2) ? (size_t)st->spill_lookup_join_col : 0u;
|
|
1630
|
+
tf_type rt = (st->how < 2) ? st->spill_lookup_schema_types[rc] : st->lookup_key_type;
|
|
1631
|
+
return join_spill_compare_row_cell(lookup, rc, rt, left, lc, st->spill_schema_types[lc]);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
static int join_spill_best_left_reader(const join_state *st) {
|
|
1635
|
+
int best = -1;
|
|
1636
|
+
for (size_t i = 0; i < st->spill_n_left_readers; i++) {
|
|
1637
|
+
const join_spill_reader *r = &st->spill_left_readers[i];
|
|
1638
|
+
if (!r->has_row || r->done) continue;
|
|
1639
|
+
if (best < 0) { best = (int)i; continue; }
|
|
1640
|
+
int cmp = join_spill_compare_left_rows(st, &r->row, &st->spill_left_readers[best].row);
|
|
1641
|
+
if (cmp < 0 || (cmp == 0 && i < (size_t)best)) best = (int)i;
|
|
1642
|
+
}
|
|
1643
|
+
return best;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
static int join_spill_best_lookup_reader(const join_state *st) {
|
|
1647
|
+
int best = -1;
|
|
1648
|
+
for (size_t i = 0; i < st->spill_n_lookup_readers; i++) {
|
|
1649
|
+
const join_spill_reader *r = &st->spill_lookup_readers[i];
|
|
1650
|
+
if (!r->has_row || r->done) continue;
|
|
1651
|
+
if (best < 0) { best = (int)i; continue; }
|
|
1652
|
+
int cmp = join_spill_compare_lookup_rows(st, &r->row, &st->spill_lookup_readers[best].row);
|
|
1653
|
+
if (cmp < 0 || (cmp == 0 && i < (size_t)best)) best = (int)i;
|
|
1654
|
+
}
|
|
1655
|
+
return best;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
static int join_spill_best_output_reader(const join_state *st) {
|
|
1659
|
+
int best = -1;
|
|
1660
|
+
for (size_t i = 0; i < st->spill_n_out_readers; i++) {
|
|
1661
|
+
const join_spill_reader *r = &st->spill_out_readers[i];
|
|
1662
|
+
if (!r->has_row || r->done) continue;
|
|
1663
|
+
if (best < 0) { best = (int)i; continue; }
|
|
1664
|
+
uint64_t a = r->row.ordinal;
|
|
1665
|
+
uint64_t b = st->spill_out_readers[best].row.ordinal;
|
|
1666
|
+
if (a < b || (a == b && i < (size_t)best)) best = (int)i;
|
|
1667
|
+
}
|
|
1668
|
+
return best;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
static int join_spill_append_key_bytes(char **buf, size_t *len, size_t *cap, const void *src, size_t n) {
|
|
1672
|
+
size_t need = 0;
|
|
1673
|
+
if (tf_size_add(*len, n, &need) != TF_OK ||
|
|
1674
|
+
tf_size_add(need, 1, &need) != TF_OK) {
|
|
1675
|
+
return TF_ERROR;
|
|
1676
|
+
}
|
|
1677
|
+
if (need > *cap) {
|
|
1678
|
+
size_t new_cap = 0;
|
|
1679
|
+
if (tf_size_grow_pow2(*cap, need, 64, &new_cap) != TF_OK) return TF_ERROR;
|
|
1680
|
+
char *tmp = tf_reallocarray_checked(*buf, new_cap, sizeof(char));
|
|
1681
|
+
if (!tmp) return TF_ERROR;
|
|
1682
|
+
*buf = tmp;
|
|
1683
|
+
*cap = new_cap;
|
|
1684
|
+
}
|
|
1685
|
+
memcpy(*buf + *len, src, n);
|
|
1686
|
+
*len += n;
|
|
1687
|
+
(*buf)[*len] = '\0';
|
|
1688
|
+
return TF_OK;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
static int join_spill_append_key_str(char **buf, size_t *len, size_t *cap, const char *str) {
|
|
1692
|
+
return join_spill_append_key_bytes(buf, len, cap, str, strlen(str));
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
static char *join_spill_build_key(const join_state *st, const join_spill_row *row, int lookup_row) {
|
|
1696
|
+
size_t c;
|
|
1697
|
+
tf_type type;
|
|
1698
|
+
if (lookup_row) {
|
|
1699
|
+
c = (st->how < 2) ? (size_t)st->spill_lookup_join_col : 0u;
|
|
1700
|
+
type = (st->how < 2) ? st->spill_lookup_schema_types[c] : st->lookup_key_type;
|
|
1701
|
+
} else {
|
|
1702
|
+
c = (size_t)st->spill_left_join_col;
|
|
1703
|
+
type = st->spill_schema_types[c];
|
|
1704
|
+
}
|
|
1705
|
+
char *buf = NULL;
|
|
1706
|
+
size_t len = 0, cap = 0;
|
|
1707
|
+
char tmp[96];
|
|
1708
|
+
if (row->nulls[c]) {
|
|
1709
|
+
snprintf(tmp, sizeof(tmp), "N:%d", (int)type);
|
|
1710
|
+
if (join_spill_append_key_str(&buf, &len, &cap, tmp) != TF_OK) goto fail;
|
|
1711
|
+
} else {
|
|
1712
|
+
switch (type) {
|
|
1713
|
+
case TF_TYPE_BOOL:
|
|
1714
|
+
if (join_spill_append_key_str(&buf, &len, &cap, row->cells[c].b ? "B:1" : "B:0") != TF_OK) goto fail;
|
|
1715
|
+
break;
|
|
1716
|
+
case TF_TYPE_INT64:
|
|
1717
|
+
snprintf(tmp, sizeof(tmp), "I:%lld", (long long)row->cells[c].i64);
|
|
1718
|
+
if (join_spill_append_key_str(&buf, &len, &cap, tmp) != TF_OK) goto fail;
|
|
1719
|
+
break;
|
|
1720
|
+
case TF_TYPE_FLOAT64: {
|
|
1721
|
+
int n = snprintf(tmp, sizeof(tmp), "F:%.17g", row->cells[c].f64);
|
|
1722
|
+
if (n < 0 || join_spill_append_key_bytes(&buf, &len, &cap, tmp, (size_t)n) != TF_OK) goto fail;
|
|
1723
|
+
break;
|
|
1724
|
+
}
|
|
1725
|
+
case TF_TYPE_STRING: {
|
|
1726
|
+
const char *str = row->cells[c].str ? row->cells[c].str : "";
|
|
1727
|
+
int n = snprintf(tmp, sizeof(tmp), "S:%zu:", strlen(str));
|
|
1728
|
+
if (n < 0 || join_spill_append_key_bytes(&buf, &len, &cap, tmp, (size_t)n) != TF_OK) goto fail;
|
|
1729
|
+
if (join_spill_append_key_str(&buf, &len, &cap, str) != TF_OK) goto fail;
|
|
1730
|
+
break;
|
|
1731
|
+
}
|
|
1732
|
+
case TF_TYPE_DATE:
|
|
1733
|
+
snprintf(tmp, sizeof(tmp), "D:%lld", (long long)row->cells[c].date);
|
|
1734
|
+
if (join_spill_append_key_str(&buf, &len, &cap, tmp) != TF_OK) goto fail;
|
|
1735
|
+
break;
|
|
1736
|
+
case TF_TYPE_TIMESTAMP:
|
|
1737
|
+
snprintf(tmp, sizeof(tmp), "T:%lld", (long long)row->cells[c].i64);
|
|
1738
|
+
if (join_spill_append_key_str(&buf, &len, &cap, tmp) != TF_OK) goto fail;
|
|
1739
|
+
break;
|
|
1740
|
+
default:
|
|
1741
|
+
if (join_spill_append_key_str(&buf, &len, &cap, "U") != TF_OK) goto fail;
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
if (!buf) buf = strdup("");
|
|
1746
|
+
return buf;
|
|
1747
|
+
fail:
|
|
1748
|
+
free(buf);
|
|
1749
|
+
return NULL;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
static int join_spill_count_lookup_key(join_state *st, const join_spill_row *row, tf_side_channels *side) {
|
|
1753
|
+
char *key = join_spill_build_key(st, row, 1);
|
|
1754
|
+
if (!key) return TF_ERROR;
|
|
1755
|
+
if (!st->spill_last_lookup_key || strcmp(st->spill_last_lookup_key, key) != 0) {
|
|
1756
|
+
if (st->max_lookup_keys > 0 && st->spill_lookup_keys >= st->max_lookup_keys) {
|
|
1757
|
+
int err_rc = join_limit_error(side, "max_lookup_keys", st->max_lookup_keys,
|
|
1758
|
+
st->spill_lookup_keys + 1);
|
|
1759
|
+
free(key);
|
|
1760
|
+
if (err_rc != TF_OK) return TF_ERROR;
|
|
1761
|
+
return TF_ERROR;
|
|
1762
|
+
}
|
|
1763
|
+
size_t key_bytes_delta = 0;
|
|
1764
|
+
size_t new_spill_lookup_key_bytes = 0;
|
|
1765
|
+
if (tf_size_add(strlen(key), 1, &key_bytes_delta) != TF_OK ||
|
|
1766
|
+
tf_size_add(st->spill_lookup_key_bytes, key_bytes_delta,
|
|
1767
|
+
&new_spill_lookup_key_bytes) != TF_OK) {
|
|
1768
|
+
free(key);
|
|
1769
|
+
return TF_ERROR;
|
|
1770
|
+
}
|
|
1771
|
+
free(st->spill_last_lookup_key);
|
|
1772
|
+
st->spill_last_lookup_key = key;
|
|
1773
|
+
key = NULL;
|
|
1774
|
+
st->spill_lookup_keys++;
|
|
1775
|
+
st->spill_lookup_key_bytes = new_spill_lookup_key_bytes;
|
|
1776
|
+
}
|
|
1777
|
+
free(key);
|
|
1778
|
+
return TF_OK;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
static int join_spill_set_batch_cell(tf_batch *out, size_t dst_row, size_t dst_col,
|
|
1782
|
+
const join_spill_row *row, size_t src_col,
|
|
1783
|
+
const tf_type *types) {
|
|
1784
|
+
tf_type type = types[src_col];
|
|
1785
|
+
return tf_batch_set_owned_cell_value(out, dst_row, dst_col, type,
|
|
1786
|
+
row->nulls[src_col], &row->cells[src_col]);
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
static int join_spill_row_to_batch_typed(tf_batch *out, size_t dst_row,
|
|
1790
|
+
const join_spill_row *row,
|
|
1791
|
+
const tf_type *types, size_t n_cols) {
|
|
1792
|
+
if (tf_batch_ensure_capacity(out, dst_row + 1) != TF_OK) return TF_ERROR;
|
|
1793
|
+
for (size_t c = 0; c < n_cols; c++) {
|
|
1794
|
+
if (join_spill_set_batch_cell(out, dst_row, c, row, c, types) != TF_OK) return TF_ERROR;
|
|
1795
|
+
}
|
|
1796
|
+
return TF_OK;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
static int join_spill_row_to_batch(const join_state *st, tf_batch *out, size_t dst_row,
|
|
1800
|
+
const join_spill_row *row) {
|
|
1801
|
+
return join_spill_row_to_batch_typed(out, dst_row, row,
|
|
1802
|
+
st->spill_schema_types, st->spill_n_cols);
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
static int join_spill_append_selected_row(join_state *st, const join_spill_row *row,
|
|
1806
|
+
tf_side_channels *side) {
|
|
1807
|
+
if (!join_reserve_output_row(st, side)) return TF_ERROR;
|
|
1808
|
+
size_t dst = st->spill_out_buf->n_rows;
|
|
1809
|
+
if (join_ensure_ordinals(&st->spill_out_ordinals,
|
|
1810
|
+
&st->spill_out_ordinal_cap, dst + 1) != TF_OK) {
|
|
1811
|
+
return TF_ERROR;
|
|
1812
|
+
}
|
|
1813
|
+
if (join_spill_row_to_batch(st, st->spill_out_buf, dst, row) != TF_OK) return TF_ERROR;
|
|
1814
|
+
st->spill_out_ordinals[dst] = row->ordinal;
|
|
1815
|
+
if (tf_batch_expose_row(st->spill_out_buf, dst) != TF_OK) return TF_ERROR;
|
|
1816
|
+
st->spill_kept_rows++;
|
|
1817
|
+
if (st->spill_out_buf->n_rows >= st->run_rows) return join_spill_write_output_run(st);
|
|
1818
|
+
return TF_OK;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
static int join_spill_capture_lookup_schema(join_state *st, const tf_batch *batch,
|
|
1822
|
+
int lookup_ci, tf_side_channels *side) {
|
|
1823
|
+
if (st->how >= 2) return TF_OK;
|
|
1824
|
+
if (lookup_ci < 0) return TF_ERROR;
|
|
1825
|
+
|
|
1826
|
+
if (st->spill_lookup_n_cols > 0) {
|
|
1827
|
+
if (batch->n_cols != st->spill_lookup_n_cols || lookup_ci != st->spill_lookup_join_col) {
|
|
1828
|
+
if (join_write_error(side, "join spill: lookup schema changed across batches") != TF_OK)
|
|
1829
|
+
return TF_ERROR;
|
|
1830
|
+
return TF_ERROR;
|
|
1831
|
+
}
|
|
1832
|
+
for (size_t c = 0; c < batch->n_cols; c++) {
|
|
1833
|
+
if (batch->col_types[c] != st->spill_lookup_schema_types[c] ||
|
|
1834
|
+
strcmp(batch->col_names[c] ? batch->col_names[c] : "",
|
|
1835
|
+
st->spill_lookup_schema_names[c] ? st->spill_lookup_schema_names[c] : "") != 0) {
|
|
1836
|
+
if (join_write_error(side, "join spill: lookup schema changed across batches") != TF_OK)
|
|
1837
|
+
return TF_ERROR;
|
|
1838
|
+
return TF_ERROR;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return TF_OK;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
st->spill_lookup_n_cols = batch->n_cols;
|
|
1845
|
+
st->spill_lookup_join_col = lookup_ci;
|
|
1846
|
+
st->spill_lookup_schema_names = tf_callocarray_checked(batch->n_cols ? batch->n_cols : 1, sizeof(char *));
|
|
1847
|
+
st->spill_lookup_schema_types = tf_callocarray_checked(batch->n_cols ? batch->n_cols : 1, sizeof(tf_type));
|
|
1848
|
+
if (!st->spill_lookup_schema_names || !st->spill_lookup_schema_types) return TF_ERROR;
|
|
1849
|
+
for (size_t c = 0; c < batch->n_cols; c++) {
|
|
1850
|
+
st->spill_lookup_schema_names[c] = strdup(batch->col_names[c] ? batch->col_names[c] : "");
|
|
1851
|
+
if (!st->spill_lookup_schema_names[c]) return TF_ERROR;
|
|
1852
|
+
st->spill_lookup_schema_types[c] = batch->col_types[c];
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
st->lookup_key_type = batch->col_types[lookup_ci];
|
|
1856
|
+
st->have_lookup_key_type = 1;
|
|
1857
|
+
|
|
1858
|
+
st->lookup_out_cols = tf_mallocarray_checked(batch->n_cols ? batch->n_cols : 1, sizeof(int));
|
|
1859
|
+
if (!st->lookup_out_cols) return TF_ERROR;
|
|
1860
|
+
st->n_lookup_out = 0;
|
|
1861
|
+
for (size_t c = 0; c < batch->n_cols; c++) {
|
|
1862
|
+
if ((int)c != lookup_ci) st->lookup_out_cols[st->n_lookup_out++] = (int)c;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
st->spill_output_n_cols = st->spill_n_cols + st->n_lookup_out;
|
|
1866
|
+
st->spill_output_schema_names = tf_callocarray_checked(st->spill_output_n_cols ? st->spill_output_n_cols : 1, sizeof(char *));
|
|
1867
|
+
st->spill_output_schema_types = tf_callocarray_checked(st->spill_output_n_cols ? st->spill_output_n_cols : 1, sizeof(tf_type));
|
|
1868
|
+
if (!st->spill_output_schema_names || !st->spill_output_schema_types) return TF_ERROR;
|
|
1869
|
+
for (size_t c = 0; c < st->spill_n_cols; c++) {
|
|
1870
|
+
st->spill_output_schema_names[c] = strdup(st->spill_schema_names[c] ? st->spill_schema_names[c] : "");
|
|
1871
|
+
if (!st->spill_output_schema_names[c]) return TF_ERROR;
|
|
1872
|
+
st->spill_output_schema_types[c] = st->spill_schema_types[c];
|
|
1873
|
+
}
|
|
1874
|
+
for (size_t k = 0; k < st->n_lookup_out; k++) {
|
|
1875
|
+
int lc = st->lookup_out_cols[k];
|
|
1876
|
+
size_t oc = st->spill_n_cols + k;
|
|
1877
|
+
st->spill_output_schema_names[oc] = strdup(st->spill_lookup_schema_names[lc] ? st->spill_lookup_schema_names[lc] : "");
|
|
1878
|
+
if (!st->spill_output_schema_names[oc]) return TF_ERROR;
|
|
1879
|
+
st->spill_output_schema_types[oc] = st->spill_lookup_schema_types[lc];
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
st->spill_lookup_buf = join_spill_create_lookup_batch(st, st->run_rows);
|
|
1883
|
+
st->spill_out_buf = join_spill_create_output_batch(st, st->run_rows);
|
|
1884
|
+
if (!st->spill_lookup_buf || !st->spill_out_buf) return TF_ERROR;
|
|
1885
|
+
return TF_OK;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
static int join_spill_process_lookup_batch(join_state *st, const tf_batch *batch,
|
|
1889
|
+
tf_side_channels *side) {
|
|
1890
|
+
int lookup_ci = tf_batch_col_index(batch, st->right_col);
|
|
1891
|
+
if (lookup_ci < 0) {
|
|
1892
|
+
if (join_write_error(side, "join spill: lookup file is missing join column") != TF_OK)
|
|
1893
|
+
return TF_ERROR;
|
|
1894
|
+
return TF_ERROR;
|
|
1895
|
+
}
|
|
1896
|
+
if (batch->col_types[lookup_ci] != st->spill_schema_types[st->spill_left_join_col]) {
|
|
1897
|
+
if (join_write_error(side, "join spill: left and lookup join key types differ") != TF_OK)
|
|
1898
|
+
return TF_ERROR;
|
|
1899
|
+
return TF_ERROR;
|
|
1900
|
+
}
|
|
1901
|
+
if (st->how < 2) {
|
|
1902
|
+
if (join_spill_capture_lookup_schema(st, batch, lookup_ci, side) != TF_OK) return TF_ERROR;
|
|
1903
|
+
} else if (!st->have_lookup_key_type) {
|
|
1904
|
+
st->lookup_key_type = batch->col_types[lookup_ci];
|
|
1905
|
+
st->have_lookup_key_type = 1;
|
|
1906
|
+
st->spill_lookup_buf = join_spill_create_lookup_batch(st, st->run_rows);
|
|
1907
|
+
if (!st->spill_lookup_buf) return TF_ERROR;
|
|
1908
|
+
}
|
|
1909
|
+
for (size_t r = 0; r < batch->n_rows; r++) {
|
|
1910
|
+
if (st->max_lookup_rows > 0 && st->lookup_rows >= st->max_lookup_rows) {
|
|
1911
|
+
if (join_limit_error(side, "max_lookup_rows", st->max_lookup_rows,
|
|
1912
|
+
st->lookup_rows + 1) != TF_OK)
|
|
1913
|
+
return TF_ERROR;
|
|
1914
|
+
return TF_ERROR;
|
|
1915
|
+
}
|
|
1916
|
+
size_t dst = st->spill_lookup_buf->n_rows;
|
|
1917
|
+
if (tf_batch_ensure_capacity(st->spill_lookup_buf, dst + 1) != TF_OK) return TF_ERROR;
|
|
1918
|
+
if (st->how < 2) {
|
|
1919
|
+
if (tf_batch_copy_row(st->spill_lookup_buf, dst, batch, r) != TF_OK) return TF_ERROR;
|
|
1920
|
+
} else {
|
|
1921
|
+
if (tf_batch_copy_cell_index(st->spill_lookup_buf, dst, 0, batch, r, lookup_ci) != TF_OK) return TF_ERROR;
|
|
1922
|
+
}
|
|
1923
|
+
if (join_ensure_ordinals(&st->spill_lookup_ordinals, &st->spill_lookup_ordinal_cap, dst + 1) != TF_OK) return TF_ERROR;
|
|
1924
|
+
st->spill_lookup_ordinals[dst] = st->spill_next_lookup_ordinal++;
|
|
1925
|
+
if (tf_batch_expose_row(st->spill_lookup_buf, dst) != TF_OK) return TF_ERROR;
|
|
1926
|
+
st->lookup_rows++;
|
|
1927
|
+
if (st->spill_lookup_buf->n_rows >= st->run_rows && join_spill_write_lookup_run(st) != TF_OK)
|
|
1928
|
+
return TF_ERROR;
|
|
1929
|
+
}
|
|
1930
|
+
return TF_OK;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
static int join_spill_load_lookup_runs(join_state *st, tf_side_channels *side) {
|
|
1934
|
+
if (st->spill_lookup_loaded) return TF_OK;
|
|
1935
|
+
st->spill_lookup_loaded = 1;
|
|
1936
|
+
if (!csv_file_header_has_column(st->file, st->validated_file, st->right_col)) {
|
|
1937
|
+
if (join_write_error(side, "join spill: lookup file is missing join column") != TF_OK)
|
|
1938
|
+
return TF_ERROR;
|
|
1939
|
+
return TF_ERROR;
|
|
1940
|
+
}
|
|
1941
|
+
FILE *f = tf_policy_fopen_read(st->file, st->validated_file);
|
|
1942
|
+
if (!f) {
|
|
1943
|
+
if (join_write_error(side, "join spill: cannot open lookup file") != TF_OK)
|
|
1944
|
+
return TF_ERROR;
|
|
1945
|
+
return TF_ERROR;
|
|
1946
|
+
}
|
|
1947
|
+
tf_decoder *dec = tf_csv_decoder_create(NULL);
|
|
1948
|
+
if (!dec) { fclose(f); return TF_ERROR; }
|
|
1949
|
+
size_t bytes_read = 0;
|
|
1950
|
+
int flushed = 0;
|
|
1951
|
+
int rc = TF_OK;
|
|
1952
|
+
while (!flushed) {
|
|
1953
|
+
uint8_t buf[64 * 1024];
|
|
1954
|
+
size_t n = fread(buf, 1, sizeof(buf), f);
|
|
1955
|
+
tf_batch **batches = NULL;
|
|
1956
|
+
size_t n_batches = 0;
|
|
1957
|
+
if (n > 0) {
|
|
1958
|
+
bytes_read += n;
|
|
1959
|
+
if (st->max_lookup_bytes > 0 && bytes_read > st->max_lookup_bytes) {
|
|
1960
|
+
if (join_limit_error(side, "max_lookup_bytes", st->max_lookup_bytes,
|
|
1961
|
+
bytes_read) != TF_OK)
|
|
1962
|
+
rc = TF_ERROR;
|
|
1963
|
+
rc = TF_ERROR;
|
|
1964
|
+
break;
|
|
1965
|
+
}
|
|
1966
|
+
rc = dec->decode(dec, buf, n, &batches, &n_batches, side);
|
|
1967
|
+
} else {
|
|
1968
|
+
if (ferror(f)) {
|
|
1969
|
+
if (join_write_error(side, "join spill: failed reading lookup file") != TF_OK)
|
|
1970
|
+
rc = TF_ERROR;
|
|
1971
|
+
rc = TF_ERROR;
|
|
1972
|
+
break;
|
|
1973
|
+
}
|
|
1974
|
+
flushed = 1;
|
|
1975
|
+
rc = dec->flush(dec, &batches, &n_batches, side);
|
|
1976
|
+
}
|
|
1977
|
+
if (rc != TF_OK) {
|
|
1978
|
+
tf_batch_array_free(batches, n_batches);
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
for (size_t i = 0; i < n_batches; i++) {
|
|
1982
|
+
if (join_spill_process_lookup_batch(st, batches[i], side) != TF_OK) rc = TF_ERROR;
|
|
1983
|
+
tf_batch_free(batches[i]);
|
|
1984
|
+
if (rc != TF_OK) {
|
|
1985
|
+
tf_batch_array_free_items(batches + i + 1, n_batches - i - 1);
|
|
1986
|
+
break;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
free(batches);
|
|
1990
|
+
if (rc != TF_OK) break;
|
|
1991
|
+
}
|
|
1992
|
+
dec->destroy(dec);
|
|
1993
|
+
fclose(f);
|
|
1994
|
+
if (rc != TF_OK) return TF_ERROR;
|
|
1995
|
+
if (st->spill_lookup_buf && st->spill_lookup_buf->n_rows > 0)
|
|
1996
|
+
return join_spill_write_lookup_run(st);
|
|
1997
|
+
if (st->spill_lookup_buf) {
|
|
1998
|
+
tf_batch_free(st->spill_lookup_buf);
|
|
1999
|
+
st->spill_lookup_buf = NULL;
|
|
2000
|
+
}
|
|
2001
|
+
return TF_OK;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
static int join_spill_output_ordinal(join_state *st, uint64_t left_ordinal,
|
|
2005
|
+
size_t match_idx, uint64_t *out,
|
|
2006
|
+
tf_side_channels *side) {
|
|
2007
|
+
if (st->how >= 2) {
|
|
2008
|
+
*out = left_ordinal;
|
|
2009
|
+
return TF_OK;
|
|
2010
|
+
}
|
|
2011
|
+
if (st->max_matches_per_row == 0) {
|
|
2012
|
+
if (join_write_error(side, "join spill: inner/left joins need max_matches_per_row to bound lookup key runs") != TF_OK)
|
|
2013
|
+
return TF_ERROR;
|
|
2014
|
+
return TF_ERROR;
|
|
2015
|
+
}
|
|
2016
|
+
if (match_idx >= st->max_matches_per_row) {
|
|
2017
|
+
if (join_limit_error_context(side, "max_matches_per_row", st->max_matches_per_row,
|
|
2018
|
+
match_idx + 1, "for spilled lookup key") != TF_OK)
|
|
2019
|
+
return TF_ERROR;
|
|
2020
|
+
return TF_ERROR;
|
|
2021
|
+
}
|
|
2022
|
+
uint64_t slots = (uint64_t)st->max_matches_per_row;
|
|
2023
|
+
if (slots == 0 || left_ordinal > (UINT64_MAX - (uint64_t)match_idx) / slots) {
|
|
2024
|
+
if (join_write_error(side, "join spill: output ordinal overflow") != TF_OK)
|
|
2025
|
+
return TF_ERROR;
|
|
2026
|
+
return TF_ERROR;
|
|
2027
|
+
}
|
|
2028
|
+
*out = left_ordinal * slots + (uint64_t)match_idx;
|
|
2029
|
+
return TF_OK;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
static int join_spill_append_joined_row(join_state *st, const join_spill_row *left,
|
|
2033
|
+
const tf_batch *lookup_run, size_t match_idx,
|
|
2034
|
+
tf_side_channels *side) {
|
|
2035
|
+
if (!st->spill_out_buf) {
|
|
2036
|
+
st->spill_out_buf = join_spill_create_output_batch(st, st->run_rows);
|
|
2037
|
+
if (!st->spill_out_buf) return TF_ERROR;
|
|
2038
|
+
}
|
|
2039
|
+
if (!join_reserve_output_row(st, side)) return TF_ERROR;
|
|
2040
|
+
size_t dst = st->spill_out_buf->n_rows;
|
|
2041
|
+
if (tf_batch_ensure_capacity(st->spill_out_buf, dst + 1) != TF_OK) return TF_ERROR;
|
|
2042
|
+
if (join_ensure_ordinals(&st->spill_out_ordinals, &st->spill_out_ordinal_cap, dst + 1) != TF_OK)
|
|
2043
|
+
return TF_ERROR;
|
|
2044
|
+
uint64_t output_ordinal = 0;
|
|
2045
|
+
if (join_spill_output_ordinal(st, left->ordinal, lookup_run ? match_idx : 0, &output_ordinal, side) != TF_OK)
|
|
2046
|
+
return TF_ERROR;
|
|
2047
|
+
|
|
2048
|
+
for (size_t c = 0; c < st->spill_n_cols; c++) {
|
|
2049
|
+
if (join_spill_set_batch_cell(st->spill_out_buf, dst, c, left, c,
|
|
2050
|
+
st->spill_schema_types) != TF_OK) return TF_ERROR;
|
|
2051
|
+
}
|
|
2052
|
+
if (lookup_run) {
|
|
2053
|
+
for (size_t k = 0; k < st->n_lookup_out; k++) {
|
|
2054
|
+
size_t src = (size_t)st->lookup_out_cols[k];
|
|
2055
|
+
if (tf_batch_is_null(lookup_run, match_idx, src)) {
|
|
2056
|
+
if (tf_batch_set_null(st->spill_out_buf, dst, st->spill_n_cols + k) != TF_OK) return TF_ERROR;
|
|
2057
|
+
} else {
|
|
2058
|
+
if (tf_batch_copy_cell_index(st->spill_out_buf, dst, st->spill_n_cols + k,
|
|
2059
|
+
lookup_run, match_idx, (int)src) != TF_OK) return TF_ERROR;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
} else {
|
|
2063
|
+
for (size_t k = 0; k < st->n_lookup_out; k++) {
|
|
2064
|
+
if (tf_batch_set_null(st->spill_out_buf, dst, st->spill_n_cols + k) != TF_OK) return TF_ERROR;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
st->spill_out_ordinals[dst] = output_ordinal;
|
|
2069
|
+
if (tf_batch_expose_row(st->spill_out_buf, dst) != TF_OK) return TF_ERROR;
|
|
2070
|
+
st->spill_kept_rows++;
|
|
2071
|
+
if (st->spill_out_buf->n_rows >= st->run_rows) return join_spill_write_output_run(st);
|
|
2072
|
+
return TF_OK;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
static int join_spill_drain_lookup_before_left(join_state *st, const join_spill_row *left,
|
|
2076
|
+
tf_side_channels *side) {
|
|
2077
|
+
for (;;) {
|
|
2078
|
+
int lookup_idx = join_spill_best_lookup_reader(st);
|
|
2079
|
+
if (lookup_idx < 0) return TF_OK;
|
|
2080
|
+
join_spill_reader *lookup = &st->spill_lookup_readers[lookup_idx];
|
|
2081
|
+
if (join_spill_count_lookup_key(st, &lookup->row, side) != TF_OK) return TF_ERROR;
|
|
2082
|
+
int cmp = join_spill_compare_lookup_to_left(st, &lookup->row, left);
|
|
2083
|
+
if (cmp >= 0) return TF_OK;
|
|
2084
|
+
int adv = join_spill_reader_advance(lookup, st->spill_lookup_schema_types, st->spill_lookup_n_cols);
|
|
2085
|
+
if (adv < 0) return TF_ERROR;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
static int join_spill_collect_lookup_run(join_state *st, const join_spill_row *left,
|
|
2090
|
+
tf_batch **run_out, tf_side_channels *side) {
|
|
2091
|
+
*run_out = NULL;
|
|
2092
|
+
if (join_spill_drain_lookup_before_left(st, left, side) != TF_OK) return TF_ERROR;
|
|
2093
|
+
int lookup_idx = join_spill_best_lookup_reader(st);
|
|
2094
|
+
if (lookup_idx < 0) return TF_OK;
|
|
2095
|
+
join_spill_reader *lookup = &st->spill_lookup_readers[lookup_idx];
|
|
2096
|
+
if (join_spill_count_lookup_key(st, &lookup->row, side) != TF_OK) return TF_ERROR;
|
|
2097
|
+
if (join_spill_compare_lookup_to_left(st, &lookup->row, left) != 0) return TF_OK;
|
|
2098
|
+
|
|
2099
|
+
size_t initial = st->max_matches_per_row < 16 ? st->max_matches_per_row : 16;
|
|
2100
|
+
tf_batch *run = join_spill_create_lookup_batch(st, initial ? initial : 16);
|
|
2101
|
+
if (!run) return TF_ERROR;
|
|
2102
|
+
|
|
2103
|
+
for (;;) {
|
|
2104
|
+
lookup_idx = join_spill_best_lookup_reader(st);
|
|
2105
|
+
if (lookup_idx < 0) break;
|
|
2106
|
+
lookup = &st->spill_lookup_readers[lookup_idx];
|
|
2107
|
+
int cmp = join_spill_compare_lookup_to_left(st, &lookup->row, left);
|
|
2108
|
+
if (cmp != 0) break;
|
|
2109
|
+
if (join_spill_count_lookup_key(st, &lookup->row, side) != TF_OK) { tf_batch_free(run); return TF_ERROR; }
|
|
2110
|
+
if (st->max_matches_per_row > 0 && run->n_rows >= st->max_matches_per_row) {
|
|
2111
|
+
int err_rc = join_limit_error_context(side, "max_matches_per_row",
|
|
2112
|
+
st->max_matches_per_row,
|
|
2113
|
+
run->n_rows + 1,
|
|
2114
|
+
"for spilled lookup key");
|
|
2115
|
+
tf_batch_free(run);
|
|
2116
|
+
if (err_rc != TF_OK) return TF_ERROR;
|
|
2117
|
+
return TF_ERROR;
|
|
2118
|
+
}
|
|
2119
|
+
size_t dst = run->n_rows;
|
|
2120
|
+
if (join_spill_row_to_batch_typed(run, dst, &lookup->row,
|
|
2121
|
+
st->spill_lookup_schema_types,
|
|
2122
|
+
st->spill_lookup_n_cols) != TF_OK) {
|
|
2123
|
+
tf_batch_free(run);
|
|
2124
|
+
return TF_ERROR;
|
|
2125
|
+
}
|
|
2126
|
+
if (tf_batch_expose_row(run, dst) != TF_OK) {
|
|
2127
|
+
tf_batch_free(run);
|
|
2128
|
+
return TF_ERROR;
|
|
2129
|
+
}
|
|
2130
|
+
int adv = join_spill_reader_advance(lookup, st->spill_lookup_schema_types, st->spill_lookup_n_cols);
|
|
2131
|
+
if (adv < 0) { tf_batch_free(run); return TF_ERROR; }
|
|
2132
|
+
}
|
|
2133
|
+
*run_out = run;
|
|
2134
|
+
return TF_OK;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
static int join_spill_produce_mutating_output_runs(join_state *st, tf_side_channels *side) {
|
|
2138
|
+
if (st->spill_left_buf && st->spill_left_buf->n_rows > 0 && join_spill_write_left_run(st) != TF_OK)
|
|
2139
|
+
return TF_ERROR;
|
|
2140
|
+
if (st->spill_left_buf) { tf_batch_free(st->spill_left_buf); st->spill_left_buf = NULL; }
|
|
2141
|
+
free(st->spill_left_ordinals); st->spill_left_ordinals = NULL; st->spill_left_ordinal_cap = 0;
|
|
2142
|
+
|
|
2143
|
+
if (join_spill_load_lookup_runs(st, side) != TF_OK) return TF_ERROR;
|
|
2144
|
+
if (st->spill_lookup_n_cols == 0) {
|
|
2145
|
+
if (join_write_error(side, "join spill: inner/left joins need a non-empty lookup to infer output schema") != TF_OK)
|
|
2146
|
+
return TF_ERROR;
|
|
2147
|
+
return TF_ERROR;
|
|
2148
|
+
}
|
|
2149
|
+
if (join_spill_open_readers(st, 0) != TF_OK) return TF_ERROR;
|
|
2150
|
+
if (join_spill_open_readers(st, 1) != TF_OK) return TF_ERROR;
|
|
2151
|
+
|
|
2152
|
+
for (;;) {
|
|
2153
|
+
int left_idx = join_spill_best_left_reader(st);
|
|
2154
|
+
if (left_idx < 0) break;
|
|
2155
|
+
join_spill_reader *left = &st->spill_left_readers[left_idx];
|
|
2156
|
+
char *left_key = join_spill_build_key(st, &left->row, 0);
|
|
2157
|
+
if (!left_key) return TF_ERROR;
|
|
2158
|
+
|
|
2159
|
+
tf_batch *lookup_run = NULL;
|
|
2160
|
+
if (join_spill_collect_lookup_run(st, &left->row, &lookup_run, side) != TF_OK) {
|
|
2161
|
+
free(left_key);
|
|
2162
|
+
return TF_ERROR;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
for (;;) {
|
|
2166
|
+
left_idx = join_spill_best_left_reader(st);
|
|
2167
|
+
if (left_idx < 0) break;
|
|
2168
|
+
left = &st->spill_left_readers[left_idx];
|
|
2169
|
+
char *cur_key = join_spill_build_key(st, &left->row, 0);
|
|
2170
|
+
if (!cur_key) { free(left_key); tf_batch_free(lookup_run); return TF_ERROR; }
|
|
2171
|
+
int same_key = strcmp(cur_key, left_key) == 0;
|
|
2172
|
+
free(cur_key);
|
|
2173
|
+
if (!same_key) break;
|
|
2174
|
+
|
|
2175
|
+
if (lookup_run && lookup_run->n_rows > 0) {
|
|
2176
|
+
for (size_t m = 0; m < lookup_run->n_rows; m++) {
|
|
2177
|
+
if (join_spill_append_joined_row(st, &left->row, lookup_run, m, side) != TF_OK) {
|
|
2178
|
+
free(left_key);
|
|
2179
|
+
tf_batch_free(lookup_run);
|
|
2180
|
+
return TF_ERROR;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
} else if (st->how == 1) {
|
|
2184
|
+
if (join_spill_append_joined_row(st, &left->row, NULL, 0, side) != TF_OK) {
|
|
2185
|
+
free(left_key);
|
|
2186
|
+
tf_batch_free(lookup_run);
|
|
2187
|
+
return TF_ERROR;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
int adv_left = join_spill_reader_advance(left, st->spill_schema_types, st->spill_n_cols);
|
|
2192
|
+
if (adv_left < 0) { free(left_key); tf_batch_free(lookup_run); return TF_ERROR; }
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
free(left_key);
|
|
2196
|
+
tf_batch_free(lookup_run);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
for (;;) {
|
|
2200
|
+
int lookup_idx = join_spill_best_lookup_reader(st);
|
|
2201
|
+
if (lookup_idx < 0) break;
|
|
2202
|
+
join_spill_reader *lookup = &st->spill_lookup_readers[lookup_idx];
|
|
2203
|
+
if (join_spill_count_lookup_key(st, &lookup->row, side) != TF_OK) return TF_ERROR;
|
|
2204
|
+
int adv = join_spill_reader_advance(lookup, st->spill_lookup_schema_types, st->spill_lookup_n_cols);
|
|
2205
|
+
if (adv < 0) return TF_ERROR;
|
|
2206
|
+
}
|
|
2207
|
+
if (st->spill_out_buf && st->spill_out_buf->n_rows > 0 && join_spill_write_output_run(st) != TF_OK)
|
|
2208
|
+
return TF_ERROR;
|
|
2209
|
+
join_spill_close_readers(st, 0);
|
|
2210
|
+
join_spill_close_readers(st, 1);
|
|
2211
|
+
join_spill_remove_paths(&st->spill_left_run_paths, &st->spill_n_left_runs, &st->spill_cap_left_runs);
|
|
2212
|
+
join_spill_remove_paths(&st->spill_lookup_run_paths, &st->spill_n_lookup_runs, &st->spill_cap_lookup_runs);
|
|
2213
|
+
return TF_OK;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
static int join_spill_produce_output_runs(join_state *st, tf_side_channels *side) {
|
|
2217
|
+
if (st->spill_key_merge_done) return TF_OK;
|
|
2218
|
+
if (!st->spill_has_schema) {
|
|
2219
|
+
st->spill_key_merge_done = 1;
|
|
2220
|
+
return TF_OK;
|
|
2221
|
+
}
|
|
2222
|
+
if (st->how < 2) {
|
|
2223
|
+
int rc = join_spill_produce_mutating_output_runs(st, side);
|
|
2224
|
+
if (rc != TF_OK) return rc;
|
|
2225
|
+
st->spill_key_merge_done = 1;
|
|
2226
|
+
return TF_OK;
|
|
2227
|
+
}
|
|
2228
|
+
if (st->spill_left_buf && st->spill_left_buf->n_rows > 0 && join_spill_write_left_run(st) != TF_OK)
|
|
2229
|
+
return TF_ERROR;
|
|
2230
|
+
if (st->spill_left_buf) { tf_batch_free(st->spill_left_buf); st->spill_left_buf = NULL; }
|
|
2231
|
+
free(st->spill_left_ordinals); st->spill_left_ordinals = NULL; st->spill_left_ordinal_cap = 0;
|
|
2232
|
+
|
|
2233
|
+
if (join_spill_load_lookup_runs(st, side) != TF_OK) return TF_ERROR;
|
|
2234
|
+
if (join_spill_open_readers(st, 0) != TF_OK) return TF_ERROR;
|
|
2235
|
+
if (join_spill_open_readers(st, 1) != TF_OK) return TF_ERROR;
|
|
2236
|
+
|
|
2237
|
+
for (;;) {
|
|
2238
|
+
int left_idx = join_spill_best_left_reader(st);
|
|
2239
|
+
if (left_idx < 0) break;
|
|
2240
|
+
join_spill_reader *left = &st->spill_left_readers[left_idx];
|
|
2241
|
+
int lookup_idx = join_spill_best_lookup_reader(st);
|
|
2242
|
+
while (lookup_idx >= 0) {
|
|
2243
|
+
join_spill_reader *lookup = &st->spill_lookup_readers[lookup_idx];
|
|
2244
|
+
if (join_spill_count_lookup_key(st, &lookup->row, side) != TF_OK) return TF_ERROR;
|
|
2245
|
+
int cmp = join_spill_compare_lookup_to_left(st, &lookup->row, &left->row);
|
|
2246
|
+
if (cmp >= 0) break;
|
|
2247
|
+
int adv = join_spill_reader_advance(lookup, &st->lookup_key_type, 1);
|
|
2248
|
+
if (adv < 0) return TF_ERROR;
|
|
2249
|
+
lookup_idx = join_spill_best_lookup_reader(st);
|
|
2250
|
+
}
|
|
2251
|
+
int has_match = 0;
|
|
2252
|
+
if (lookup_idx >= 0) {
|
|
2253
|
+
join_spill_reader *lookup = &st->spill_lookup_readers[lookup_idx];
|
|
2254
|
+
if (join_spill_count_lookup_key(st, &lookup->row, side) != TF_OK) return TF_ERROR;
|
|
2255
|
+
has_match = join_spill_compare_lookup_to_left(st, &lookup->row, &left->row) == 0;
|
|
2256
|
+
}
|
|
2257
|
+
int keep = (st->how == 2) ? has_match : !has_match;
|
|
2258
|
+
if (keep && join_spill_append_selected_row(st, &left->row, side) != TF_OK) return TF_ERROR;
|
|
2259
|
+
int adv_left = join_spill_reader_advance(left, st->spill_schema_types, st->spill_n_cols);
|
|
2260
|
+
if (adv_left < 0) return TF_ERROR;
|
|
2261
|
+
}
|
|
2262
|
+
for (;;) {
|
|
2263
|
+
int lookup_idx = join_spill_best_lookup_reader(st);
|
|
2264
|
+
if (lookup_idx < 0) break;
|
|
2265
|
+
join_spill_reader *lookup = &st->spill_lookup_readers[lookup_idx];
|
|
2266
|
+
if (join_spill_count_lookup_key(st, &lookup->row, side) != TF_OK) return TF_ERROR;
|
|
2267
|
+
int adv = join_spill_reader_advance(lookup, &st->lookup_key_type, 1);
|
|
2268
|
+
if (adv < 0) return TF_ERROR;
|
|
2269
|
+
}
|
|
2270
|
+
if (st->spill_out_buf && st->spill_out_buf->n_rows > 0 && join_spill_write_output_run(st) != TF_OK)
|
|
2271
|
+
return TF_ERROR;
|
|
2272
|
+
join_spill_close_readers(st, 0);
|
|
2273
|
+
join_spill_close_readers(st, 1);
|
|
2274
|
+
join_spill_remove_paths(&st->spill_left_run_paths, &st->spill_n_left_runs, &st->spill_cap_left_runs);
|
|
2275
|
+
join_spill_remove_paths(&st->spill_lookup_run_paths, &st->spill_n_lookup_runs, &st->spill_cap_lookup_runs);
|
|
2276
|
+
st->spill_key_merge_done = 1;
|
|
2277
|
+
return TF_OK;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
static int join_spill_begin_output_merge(join_state *st) {
|
|
2281
|
+
if (st->spill_output_merge_started) return TF_OK;
|
|
2282
|
+
st->spill_output_merge_started = 1;
|
|
2283
|
+
if (st->spill_out_buf) { tf_batch_free(st->spill_out_buf); st->spill_out_buf = NULL; }
|
|
2284
|
+
free(st->spill_out_ordinals); st->spill_out_ordinals = NULL; st->spill_out_ordinal_cap = 0;
|
|
2285
|
+
if (st->spill_n_out_runs == 0) {
|
|
2286
|
+
tf_spill_cleanup(st->spill);
|
|
2287
|
+
st->spill = NULL;
|
|
2288
|
+
st->spill_output_merge_done = 1;
|
|
2289
|
+
return TF_OK;
|
|
2290
|
+
}
|
|
2291
|
+
return join_spill_open_readers(st, 2);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
static int join_spill_output_next_batch(join_state *st, tf_batch **out, tf_side_channels *side) {
|
|
2295
|
+
*out = NULL;
|
|
2296
|
+
if (join_spill_produce_output_runs(st, side) != TF_OK) return TF_ERROR;
|
|
2297
|
+
if (join_spill_begin_output_merge(st) != TF_OK) return TF_ERROR;
|
|
2298
|
+
if (st->spill_output_merge_done) return TF_OK;
|
|
2299
|
+
|
|
2300
|
+
tf_batch *ob = join_spill_create_output_batch(st, st->output_batch_rows);
|
|
2301
|
+
if (!ob) return TF_ERROR;
|
|
2302
|
+
size_t out_n_cols = 0;
|
|
2303
|
+
const tf_type *out_types = join_spill_reader_types(st, 2, &out_n_cols);
|
|
2304
|
+
while (ob->n_rows < st->output_batch_rows) {
|
|
2305
|
+
int best = join_spill_best_output_reader(st);
|
|
2306
|
+
if (best < 0) break;
|
|
2307
|
+
join_spill_reader *reader = &st->spill_out_readers[best];
|
|
2308
|
+
size_t out_row = ob->n_rows;
|
|
2309
|
+
if (join_spill_row_to_batch_typed(ob, out_row, &reader->row,
|
|
2310
|
+
out_types, out_n_cols) != TF_OK) {
|
|
2311
|
+
tf_batch_free(ob);
|
|
2312
|
+
return TF_ERROR;
|
|
2313
|
+
}
|
|
2314
|
+
if (tf_batch_expose_row(ob, out_row) != TF_OK) {
|
|
2315
|
+
tf_batch_free(ob);
|
|
2316
|
+
return TF_ERROR;
|
|
2317
|
+
}
|
|
2318
|
+
int adv = join_spill_reader_advance(reader, out_types, out_n_cols);
|
|
2319
|
+
if (adv < 0) { tf_batch_free(ob); return TF_ERROR; }
|
|
2320
|
+
}
|
|
2321
|
+
if (ob->n_rows == 0) {
|
|
2322
|
+
tf_batch_free(ob);
|
|
2323
|
+
join_spill_close_readers(st, 2);
|
|
2324
|
+
join_spill_remove_paths(&st->spill_out_run_paths, &st->spill_n_out_runs, &st->spill_cap_out_runs);
|
|
2325
|
+
tf_spill_cleanup(st->spill);
|
|
2326
|
+
st->spill = NULL;
|
|
2327
|
+
st->spill_output_merge_done = 1;
|
|
2328
|
+
return TF_OK;
|
|
2329
|
+
}
|
|
2330
|
+
st->spill_output_batches++;
|
|
2331
|
+
st->spill_output_rows += ob->n_rows;
|
|
2332
|
+
*out = ob;
|
|
2333
|
+
return TF_OK;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
static int join_process_spill(join_state *st, tf_batch *in, tf_batch **out, tf_side_channels *side) {
|
|
2337
|
+
*out = NULL;
|
|
2338
|
+
if (join_spill_init_schema(st, in, side) != TF_OK) return TF_ERROR;
|
|
2339
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
2340
|
+
size_t dst = st->spill_left_buf->n_rows;
|
|
2341
|
+
if (tf_batch_copy_row(st->spill_left_buf, dst, in, r) != TF_OK) return TF_ERROR;
|
|
2342
|
+
if (join_ensure_ordinals(&st->spill_left_ordinals, &st->spill_left_ordinal_cap, dst + 1) != TF_OK) return TF_ERROR;
|
|
2343
|
+
st->spill_left_ordinals[dst] = st->spill_next_left_ordinal++;
|
|
2344
|
+
if (tf_batch_expose_row(st->spill_left_buf, dst) != TF_OK) return TF_ERROR;
|
|
2345
|
+
if (st->spill_left_buf->n_rows >= st->run_rows && join_spill_write_left_run(st) != TF_OK)
|
|
2346
|
+
return TF_ERROR;
|
|
2347
|
+
}
|
|
2348
|
+
return TF_OK;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
static size_t join_spill_retained_state_bytes(const join_state *st) {
|
|
2352
|
+
if (!st || !st->use_spill) return 0;
|
|
2353
|
+
size_t total = 0;
|
|
2354
|
+
total += join_batch_storage_bytes(st->spill_left_buf);
|
|
2355
|
+
total += join_batch_storage_bytes(st->spill_lookup_buf);
|
|
2356
|
+
total += join_batch_storage_bytes(st->spill_out_buf);
|
|
2357
|
+
total += st->spill_left_ordinal_cap * sizeof(uint64_t);
|
|
2358
|
+
total += st->spill_lookup_ordinal_cap * sizeof(uint64_t);
|
|
2359
|
+
total += st->spill_out_ordinal_cap * sizeof(uint64_t);
|
|
2360
|
+
total += st->spill_n_left_readers * sizeof(join_spill_reader);
|
|
2361
|
+
total += st->spill_n_lookup_readers * sizeof(join_spill_reader);
|
|
2362
|
+
total += st->spill_n_out_readers * sizeof(join_spill_reader);
|
|
2363
|
+
total += st->spill_last_lookup_key ? strlen(st->spill_last_lookup_key) + 1 : 0;
|
|
2364
|
+
return total;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
static int sorted_join_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
2368
|
+
tf_side_channels *side) {
|
|
2369
|
+
join_state *st = self->state;
|
|
2370
|
+
*out = NULL;
|
|
2371
|
+
|
|
2372
|
+
int left_ci = tf_batch_col_index(in, st->left_col);
|
|
2373
|
+
if (left_ci < 0) return TF_ERROR;
|
|
2374
|
+
|
|
2375
|
+
if (!st->loaded) {
|
|
2376
|
+
if (sorted_join_open(st, side) != TF_OK) return TF_ERROR;
|
|
2377
|
+
if (sorted_join_advance_lookup(st, side) != TF_OK) return TF_ERROR;
|
|
2378
|
+
if (st->how < 2) {
|
|
2379
|
+
if (!st->sorted_have_row) {
|
|
2380
|
+
if (join_write_error(side, "sorted join: mutating joins need a non-empty lookup to infer output schema") != TF_OK)
|
|
2381
|
+
return TF_ERROR;
|
|
2382
|
+
return TF_ERROR;
|
|
2383
|
+
}
|
|
2384
|
+
if (st->max_matches_per_row == 0) {
|
|
2385
|
+
if (join_write_error(side, "sorted join: inner/left joins need max_matches_per_row to bound the current lookup key run") != TF_OK)
|
|
2386
|
+
return TF_ERROR;
|
|
2387
|
+
return TF_ERROR;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
st->loaded = 1;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
size_t n_out_cols = (st->how >= 2) ? in->n_cols : in->n_cols + st->n_lookup_out;
|
|
2394
|
+
size_t out_cap = in->n_rows > 0 ? in->n_rows : 16;
|
|
2395
|
+
tf_batch *ob = tf_batch_create(n_out_cols, out_cap);
|
|
2396
|
+
if (!ob) return TF_ERROR;
|
|
2397
|
+
|
|
2398
|
+
for (size_t c = 0; c < in->n_cols; c++) {
|
|
2399
|
+
if (tf_batch_set_schema(ob, c, in->col_names[c], in->col_types[c]) != TF_OK) {
|
|
2400
|
+
tf_batch_free(ob);
|
|
2401
|
+
return TF_ERROR;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
if (st->how < 2) {
|
|
2405
|
+
for (size_t k = 0; k < st->n_lookup_out; k++) {
|
|
2406
|
+
int lc = st->lookup_out_cols[k];
|
|
2407
|
+
if (tf_batch_set_schema(ob, in->n_cols + k,
|
|
2408
|
+
st->lookup_schema_names[lc],
|
|
2409
|
+
st->lookup_schema_types[lc]) != TF_OK) {
|
|
2410
|
+
tf_batch_free(ob);
|
|
2411
|
+
return TF_ERROR;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
2417
|
+
if (sorted_join_check_left_order(st, in, r, left_ci, side) != TF_OK) {
|
|
2418
|
+
tf_batch_free(ob);
|
|
2419
|
+
return TF_ERROR;
|
|
2420
|
+
}
|
|
2421
|
+
if (sorted_join_ensure_run_at_least(st, in, r, left_ci, side) != TF_OK) {
|
|
2422
|
+
tf_batch_free(ob);
|
|
2423
|
+
return TF_ERROR;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
int has_match = 0;
|
|
2427
|
+
if (st->have_right_run) {
|
|
2428
|
+
int cmp = 0;
|
|
2429
|
+
if (join_key_compare_value_to_cell(&st->right_run_key, in, r, left_ci, &cmp) != TF_OK) {
|
|
2430
|
+
int err_rc = join_write_error(side, "sorted join: left and lookup join key types differ");
|
|
2431
|
+
tf_batch_free(ob);
|
|
2432
|
+
if (err_rc != TF_OK) return TF_ERROR;
|
|
2433
|
+
return TF_ERROR;
|
|
2434
|
+
}
|
|
2435
|
+
has_match = (cmp == 0);
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
if (st->how == 2 || st->how == 3) {
|
|
2439
|
+
int keep = (st->how == 2) ? has_match : !has_match;
|
|
2440
|
+
if (keep) {
|
|
2441
|
+
if (!join_reserve_output_row(st, side)) { tf_batch_free(ob); return TF_ERROR; }
|
|
2442
|
+
if (tf_batch_append_row(ob, in, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
2443
|
+
}
|
|
2444
|
+
} else if (has_match) {
|
|
2445
|
+
for (size_t m = 0; m < st->right_run->n_rows; m++) {
|
|
2446
|
+
if (!join_reserve_output_row(st, side)) { tf_batch_free(ob); return TF_ERROR; }
|
|
2447
|
+
if (tf_batch_append_row_with_selected_extra(ob, in, r, st->right_run, m,
|
|
2448
|
+
st->lookup_out_cols,
|
|
2449
|
+
st->n_lookup_out) != TF_OK) {
|
|
2450
|
+
tf_batch_free(ob);
|
|
2451
|
+
return TF_ERROR;
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
} else if (st->how == 1) {
|
|
2455
|
+
if (!join_reserve_output_row(st, side)) { tf_batch_free(ob); return TF_ERROR; }
|
|
2456
|
+
if (tf_batch_append_row_with_null_extra(ob, in, r, st->n_lookup_out) != TF_OK) {
|
|
2457
|
+
tf_batch_free(ob);
|
|
2458
|
+
return TF_ERROR;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
if (ob->n_rows > 0)
|
|
2464
|
+
*out = ob;
|
|
2465
|
+
else
|
|
2466
|
+
tf_batch_free(ob);
|
|
2467
|
+
|
|
2468
|
+
return TF_OK;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
static int join_process(tf_step *self, tf_batch *in, tf_batch **out,
|
|
2472
|
+
tf_side_channels *side) {
|
|
2473
|
+
join_state *st = self->state;
|
|
2474
|
+
*out = NULL;
|
|
2475
|
+
if (st->sorted) return sorted_join_process(self, in, out, side);
|
|
2476
|
+
if (st->use_spill) return join_process_spill(st, in, out, side);
|
|
2477
|
+
|
|
2478
|
+
if (!st->loaded) {
|
|
2479
|
+
if (load_lookup(st, side) != TF_OK) return TF_ERROR;
|
|
2480
|
+
st->loaded = 1;
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
int left_ci = tf_batch_col_index(in, st->left_col);
|
|
2484
|
+
if (left_ci < 0) return TF_ERROR;
|
|
2485
|
+
if (st->have_lookup_key_type && in->col_types[left_ci] != st->lookup_key_type) {
|
|
2486
|
+
if (join_write_error(side, "join: left and lookup join key types differ") != TF_OK)
|
|
2487
|
+
return TF_ERROR;
|
|
2488
|
+
return TF_ERROR;
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
size_t n_out_cols = (st->how >= 2) ? in->n_cols : in->n_cols + st->n_lookup_out;
|
|
2492
|
+
|
|
2493
|
+
/* Filtering joins retain at most one output row per input row. Mutating
|
|
2494
|
+
* joins may grow if lookup keys have duplicate matches. */
|
|
2495
|
+
size_t out_cap = in->n_rows > 0 ? in->n_rows : 16;
|
|
2496
|
+
tf_batch *ob = tf_batch_create(n_out_cols, out_cap);
|
|
2497
|
+
if (!ob) return TF_ERROR;
|
|
2498
|
+
|
|
2499
|
+
/* Set schema: main columns, plus lookup columns for mutating joins. */
|
|
2500
|
+
int schema_rc = (st->how >= 2)
|
|
2501
|
+
? tf_batch_clone_schema(ob, in)
|
|
2502
|
+
: tf_batch_clone_with_selected_extra_cols(ob, in, st->lookup,
|
|
2503
|
+
st->lookup_out_cols,
|
|
2504
|
+
st->n_lookup_out);
|
|
2505
|
+
if (schema_rc != TF_OK) {
|
|
2506
|
+
tf_batch_free(ob);
|
|
2507
|
+
return TF_ERROR;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
for (size_t r = 0; r < in->n_rows; r++) {
|
|
2511
|
+
char *key = format_join_key(in, r, left_ci);
|
|
2512
|
+
if (!key) {
|
|
2513
|
+
tf_batch_free(ob);
|
|
2514
|
+
return TF_ERROR;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
join_bucket *bucket = map_find(&st->map, key);
|
|
2518
|
+
free(key);
|
|
2519
|
+
|
|
2520
|
+
if (st->how == 2 || st->how == 3) {
|
|
2521
|
+
int keep = (st->how == 2) ? (bucket != NULL) : (bucket == NULL);
|
|
2522
|
+
if (keep) {
|
|
2523
|
+
if (!join_reserve_output_row(st, side)) { tf_batch_free(ob); return TF_ERROR; }
|
|
2524
|
+
if (tf_batch_append_row(ob, in, r) != TF_OK) { tf_batch_free(ob); return TF_ERROR; }
|
|
2525
|
+
}
|
|
2526
|
+
} else if (bucket) {
|
|
2527
|
+
if (st->max_matches_per_row > 0 && bucket->n_rows > st->max_matches_per_row) {
|
|
2528
|
+
int err_rc = join_limit_error_context(side, "max_matches_per_row",
|
|
2529
|
+
st->max_matches_per_row,
|
|
2530
|
+
bucket->n_rows,
|
|
2531
|
+
"for input row");
|
|
2532
|
+
tf_batch_free(ob);
|
|
2533
|
+
if (err_rc != TF_OK) return TF_ERROR;
|
|
2534
|
+
return TF_ERROR;
|
|
2535
|
+
}
|
|
2536
|
+
/* Emit one row per match */
|
|
2537
|
+
for (size_t m = 0; m < bucket->n_rows; m++) {
|
|
2538
|
+
size_t lr = bucket->rows[m];
|
|
2539
|
+
if (!join_reserve_output_row(st, side)) { tf_batch_free(ob); return TF_ERROR; }
|
|
2540
|
+
if (tf_batch_append_row_with_selected_extra(ob, in, r, st->lookup, lr,
|
|
2541
|
+
st->lookup_out_cols,
|
|
2542
|
+
st->n_lookup_out) != TF_OK) {
|
|
2543
|
+
tf_batch_free(ob);
|
|
2544
|
+
return TF_ERROR;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
} else if (st->how == 1) {
|
|
2548
|
+
/* Left join: emit main + nulls */
|
|
2549
|
+
if (!join_reserve_output_row(st, side)) { tf_batch_free(ob); return TF_ERROR; }
|
|
2550
|
+
if (tf_batch_append_row_with_null_extra(ob, in, r, st->n_lookup_out) != TF_OK) {
|
|
2551
|
+
tf_batch_free(ob);
|
|
2552
|
+
return TF_ERROR;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
/* Inner join + no match: skip */
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
if (ob->n_rows > 0)
|
|
2559
|
+
*out = ob;
|
|
2560
|
+
else
|
|
2561
|
+
tf_batch_free(ob);
|
|
2562
|
+
|
|
2563
|
+
return TF_OK;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
static int join_flush(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
2567
|
+
join_state *st = self ? self->state : NULL;
|
|
2568
|
+
*out = NULL;
|
|
2569
|
+
if (st && st->use_spill) return join_spill_output_next_batch(st, out, side);
|
|
2570
|
+
(void)side;
|
|
2571
|
+
return TF_OK;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
static int join_flush_next(tf_step *self, tf_batch **out, tf_side_channels *side) {
|
|
2575
|
+
join_state *st = self ? self->state : NULL;
|
|
2576
|
+
*out = NULL;
|
|
2577
|
+
if (st && st->use_spill) return join_spill_output_next_batch(st, out, side);
|
|
2578
|
+
(void)side;
|
|
2579
|
+
return TF_OK;
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
static size_t join_type_storage_size(tf_type type) {
|
|
2583
|
+
switch (type) {
|
|
2584
|
+
case TF_TYPE_BOOL: return sizeof(uint8_t);
|
|
2585
|
+
case TF_TYPE_INT64: return sizeof(int64_t);
|
|
2586
|
+
case TF_TYPE_FLOAT64: return sizeof(double);
|
|
2587
|
+
case TF_TYPE_STRING: return sizeof(char *);
|
|
2588
|
+
case TF_TYPE_DATE: return sizeof(int32_t);
|
|
2589
|
+
case TF_TYPE_TIMESTAMP: return sizeof(int64_t);
|
|
2590
|
+
default: return 0;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
static size_t join_batch_storage_bytes(const tf_batch *b) {
|
|
2595
|
+
if (!b) return 0;
|
|
2596
|
+
size_t total = sizeof(*b) +
|
|
2597
|
+
b->n_cols * (sizeof(char *) + sizeof(tf_type) +
|
|
2598
|
+
sizeof(void *) + sizeof(uint8_t *));
|
|
2599
|
+
for (size_t c = 0; c < b->n_cols; c++) {
|
|
2600
|
+
if (b->col_names && b->col_names[c]) total += strlen(b->col_names[c]) + 1;
|
|
2601
|
+
size_t cell_size = join_type_storage_size(b->col_types[c]);
|
|
2602
|
+
total += cell_size * b->capacity;
|
|
2603
|
+
total += b->capacity;
|
|
2604
|
+
if (b->col_types[c] == TF_TYPE_STRING) {
|
|
2605
|
+
for (size_t r = 0; r < b->n_rows; r++) {
|
|
2606
|
+
if (!tf_batch_is_null(b, r, c)) {
|
|
2607
|
+
const char *s = tf_batch_get_string(b, r, c);
|
|
2608
|
+
if (s) total += strlen(s) + 1;
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
return total;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
static size_t join_hash_map_retained_bytes(const join_hash_map *map) {
|
|
2617
|
+
if (!map) return 0;
|
|
2618
|
+
return map->n_buckets * sizeof(join_bucket) +
|
|
2619
|
+
map->key_bytes +
|
|
2620
|
+
map->row_ref_capacity * sizeof(size_t);
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
static size_t join_unsorted_retained_state_bytes(const join_hash_map *map,
|
|
2624
|
+
const tf_batch *lookup,
|
|
2625
|
+
size_t n_lookup_out) {
|
|
2626
|
+
return join_hash_map_retained_bytes(map) +
|
|
2627
|
+
join_batch_storage_bytes(lookup) +
|
|
2628
|
+
n_lookup_out * sizeof(int);
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
static int join_check_unsorted_state_bytes(const join_state *st,
|
|
2632
|
+
const join_hash_map *map,
|
|
2633
|
+
const tf_batch *lookup,
|
|
2634
|
+
size_t n_lookup_out,
|
|
2635
|
+
tf_side_channels *side) {
|
|
2636
|
+
if (!st || st->max_state_bytes == 0) return TF_OK;
|
|
2637
|
+
size_t retained = join_unsorted_retained_state_bytes(map, lookup, n_lookup_out);
|
|
2638
|
+
if (retained <= st->max_state_bytes) return TF_OK;
|
|
2639
|
+
if (join_limit_error_context(side, "max_state_bytes", st->max_state_bytes, retained,
|
|
2640
|
+
"while tracking lookup state") != TF_OK) {
|
|
2641
|
+
return TF_ERROR;
|
|
2642
|
+
}
|
|
2643
|
+
return TF_ERROR;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
static size_t join_key_value_retained_bytes(const join_key_value *v) {
|
|
2647
|
+
if (!v || !v->valid || v->is_null) return 0;
|
|
2648
|
+
if (v->type == TF_TYPE_STRING) return v->v.str ? strlen(v->v.str) + 1 : 0;
|
|
2649
|
+
switch (v->type) {
|
|
2650
|
+
case TF_TYPE_BOOL: return sizeof(uint8_t);
|
|
2651
|
+
case TF_TYPE_INT64: return sizeof(int64_t);
|
|
2652
|
+
case TF_TYPE_FLOAT64: return sizeof(double);
|
|
2653
|
+
case TF_TYPE_DATE: return sizeof(int32_t);
|
|
2654
|
+
case TF_TYPE_TIMESTAMP: return sizeof(int64_t);
|
|
2655
|
+
default: return 0;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
static size_t join_sorted_retained_state_bytes(const join_state *st) {
|
|
2660
|
+
if (!st) return 0;
|
|
2661
|
+
size_t total = join_batch_storage_bytes(st->right_run);
|
|
2662
|
+
total += join_key_value_retained_bytes(&st->right_run_key);
|
|
2663
|
+
total += join_key_value_retained_bytes(&st->prev_lookup_key);
|
|
2664
|
+
total += join_key_value_retained_bytes(&st->prev_left_key);
|
|
2665
|
+
total += st->n_lookup_out * sizeof(int);
|
|
2666
|
+
total += st->n_lookup_schema_cols * (sizeof(char *) + sizeof(tf_type));
|
|
2667
|
+
for (size_t i = 0; i < st->n_lookup_schema_cols; i++) {
|
|
2668
|
+
if (st->lookup_schema_names && st->lookup_schema_names[i]) {
|
|
2669
|
+
total += strlen(st->lookup_schema_names[i]) + 1;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
return total;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
static int join_append_stats(tf_step *self, tf_buffer *out) {
|
|
2676
|
+
if (!self || !self->state || !out) return TF_ERROR;
|
|
2677
|
+
join_state *st = self->state;
|
|
2678
|
+
size_t lookup_keys = 0;
|
|
2679
|
+
size_t lookup_key_bytes = 0;
|
|
2680
|
+
size_t lookup_row_refs = 0;
|
|
2681
|
+
size_t lookup_batch_bytes = 0;
|
|
2682
|
+
size_t retained = 0;
|
|
2683
|
+
if (st->use_spill) {
|
|
2684
|
+
lookup_keys = st->spill_lookup_keys;
|
|
2685
|
+
lookup_key_bytes = st->spill_lookup_key_bytes;
|
|
2686
|
+
retained = join_spill_retained_state_bytes(st);
|
|
2687
|
+
} else if (st->sorted) {
|
|
2688
|
+
lookup_keys = st->have_right_run ? 1u : 0u;
|
|
2689
|
+
lookup_key_bytes = join_key_value_retained_bytes(&st->right_run_key);
|
|
2690
|
+
lookup_row_refs = st->right_run ? st->right_run->capacity : 0u;
|
|
2691
|
+
lookup_batch_bytes = join_batch_storage_bytes(st->right_run);
|
|
2692
|
+
retained = join_sorted_retained_state_bytes(st);
|
|
2693
|
+
} else {
|
|
2694
|
+
lookup_keys = st->map.count;
|
|
2695
|
+
lookup_key_bytes = st->map.key_bytes;
|
|
2696
|
+
lookup_row_refs = st->map.row_ref_capacity;
|
|
2697
|
+
lookup_batch_bytes = join_batch_storage_bytes(st->lookup);
|
|
2698
|
+
retained = join_unsorted_retained_state_bytes(&st->map, st->lookup, st->n_lookup_out);
|
|
2699
|
+
}
|
|
2700
|
+
char buf[384];
|
|
2701
|
+
snprintf(buf, sizeof(buf),
|
|
2702
|
+
",\"lookup_rows\":%zu,\"lookup_keys\":%zu,"
|
|
2703
|
+
"\"lookup_key_bytes\":%zu,\"lookup_row_refs\":%zu,"
|
|
2704
|
+
"\"lookup_batch_bytes\":%zu,\"retained_state_bytes\":%zu,"
|
|
2705
|
+
"\"max_state_bytes\":%zu",
|
|
2706
|
+
st->lookup_rows, lookup_keys, lookup_key_bytes, lookup_row_refs,
|
|
2707
|
+
lookup_batch_bytes, retained, st->max_state_bytes);
|
|
2708
|
+
if (tf_buffer_write_str(out, buf) != TF_OK) return TF_ERROR;
|
|
2709
|
+
if (st->use_spill) {
|
|
2710
|
+
snprintf(buf, sizeof(buf),
|
|
2711
|
+
",\"spill_bytes\":%zu,\"spill_runs\":%zu,"
|
|
2712
|
+
"\"spill_output_batches\":%zu,\"spill_output_rows\":%zu,"
|
|
2713
|
+
"\"spill_kept_rows\":%zu",
|
|
2714
|
+
st->spill_bytes, st->spill_runs,
|
|
2715
|
+
st->spill_output_batches, st->spill_output_rows,
|
|
2716
|
+
st->spill_kept_rows);
|
|
2717
|
+
return tf_buffer_write_str(out, buf);
|
|
2718
|
+
}
|
|
2719
|
+
return TF_OK;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
static void join_state_free(join_state *st) {
|
|
2723
|
+
if (!st) return;
|
|
2724
|
+
free(st->file);
|
|
2725
|
+
free(st->validated_file);
|
|
2726
|
+
free(st->left_col);
|
|
2727
|
+
free(st->right_col);
|
|
2728
|
+
if (st->lookup) tf_batch_free(st->lookup);
|
|
2729
|
+
sorted_join_close(st);
|
|
2730
|
+
sorted_join_clear_right_run(st);
|
|
2731
|
+
join_key_value_clear(&st->prev_lookup_key);
|
|
2732
|
+
join_key_value_clear(&st->prev_left_key);
|
|
2733
|
+
sorted_join_free_schema(st);
|
|
2734
|
+
free(st->lookup_out_cols);
|
|
2735
|
+
map_free(&st->map);
|
|
2736
|
+
if (st->spill_left_buf) tf_batch_free(st->spill_left_buf);
|
|
2737
|
+
if (st->spill_lookup_buf) tf_batch_free(st->spill_lookup_buf);
|
|
2738
|
+
if (st->spill_out_buf) tf_batch_free(st->spill_out_buf);
|
|
2739
|
+
free(st->spill_left_ordinals);
|
|
2740
|
+
free(st->spill_lookup_ordinals);
|
|
2741
|
+
free(st->spill_out_ordinals);
|
|
2742
|
+
join_spill_close_readers(st, 0);
|
|
2743
|
+
join_spill_close_readers(st, 1);
|
|
2744
|
+
join_spill_close_readers(st, 2);
|
|
2745
|
+
join_spill_remove_paths(&st->spill_left_run_paths, &st->spill_n_left_runs, &st->spill_cap_left_runs);
|
|
2746
|
+
join_spill_remove_paths(&st->spill_lookup_run_paths, &st->spill_n_lookup_runs, &st->spill_cap_lookup_runs);
|
|
2747
|
+
join_spill_remove_paths(&st->spill_out_run_paths, &st->spill_n_out_runs, &st->spill_cap_out_runs);
|
|
2748
|
+
for (size_t i = 0; i < st->spill_n_cols; i++) free(st->spill_schema_names ? st->spill_schema_names[i] : NULL);
|
|
2749
|
+
free(st->spill_schema_names);
|
|
2750
|
+
free(st->spill_schema_types);
|
|
2751
|
+
for (size_t i = 0; i < st->spill_lookup_n_cols; i++) free(st->spill_lookup_schema_names ? st->spill_lookup_schema_names[i] : NULL);
|
|
2752
|
+
free(st->spill_lookup_schema_names);
|
|
2753
|
+
free(st->spill_lookup_schema_types);
|
|
2754
|
+
for (size_t i = 0; i < st->spill_output_n_cols; i++) free(st->spill_output_schema_names ? st->spill_output_schema_names[i] : NULL);
|
|
2755
|
+
free(st->spill_output_schema_names);
|
|
2756
|
+
free(st->spill_output_schema_types);
|
|
2757
|
+
free(st->spill_last_lookup_key);
|
|
2758
|
+
tf_spill_cleanup(st->spill);
|
|
2759
|
+
free(st->spill_dir);
|
|
2760
|
+
free(st);
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
static void join_destroy(tf_step *self) {
|
|
2764
|
+
if (self) {
|
|
2765
|
+
join_state_free(self->state);
|
|
2766
|
+
free(self);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
static int parse_positive_size_arg(const cJSON *args, const char *name,
|
|
2771
|
+
size_t *out, const char *op_name) {
|
|
2772
|
+
return tf_json_get_size_arg(args, name, 1, TF_MAX_COUNT_ARG, out, op_name);
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
tf_step *tf_join_create(const cJSON *args) {
|
|
2776
|
+
if (!args) return NULL;
|
|
2777
|
+
cJSON *file_j = cJSON_GetObjectItemCaseSensitive(args, "file");
|
|
2778
|
+
cJSON *on_j = cJSON_GetObjectItemCaseSensitive(args, "on");
|
|
355
2779
|
if (!cJSON_IsString(file_j) || !cJSON_IsString(on_j)) return NULL;
|
|
356
2780
|
|
|
357
2781
|
join_state *st = calloc(1, sizeof(join_state));
|
|
358
2782
|
if (!st) return NULL;
|
|
2783
|
+
st->output_batch_rows = JOIN_DEFAULT_OUTPUT_ROWS;
|
|
359
2784
|
|
|
360
2785
|
st->file = strdup(file_j->valuestring);
|
|
2786
|
+
if (!st->file) { join_state_free(st); return NULL; }
|
|
2787
|
+
const char *validated = tf_policy_validated_path_arg(args, "file");
|
|
2788
|
+
if (validated) {
|
|
2789
|
+
st->validated_file = strdup(validated);
|
|
2790
|
+
if (!st->validated_file) { join_state_free(st); return NULL; }
|
|
2791
|
+
}
|
|
361
2792
|
|
|
362
2793
|
/* Parse "on" field: "col" or "left_col=right_col" */
|
|
363
2794
|
const char *on = on_j->valuestring;
|
|
@@ -369,16 +2800,103 @@ tf_step *tf_join_create(const cJSON *args) {
|
|
|
369
2800
|
st->left_col = strdup(on);
|
|
370
2801
|
st->right_col = strdup(on);
|
|
371
2802
|
}
|
|
2803
|
+
if (!st->left_col || !st->right_col) { join_state_free(st); return NULL; }
|
|
2804
|
+
|
|
2805
|
+
cJSON *sorted_j = cJSON_GetObjectItemCaseSensitive(args, "sorted");
|
|
2806
|
+
if (sorted_j && !cJSON_IsBool(sorted_j)) {
|
|
2807
|
+
tf_set_last_error("join: sorted must be true or false");
|
|
2808
|
+
join_state_free(st);
|
|
2809
|
+
return NULL;
|
|
2810
|
+
}
|
|
2811
|
+
st->sorted = cJSON_IsTrue(sorted_j) ? 1 : 0;
|
|
372
2812
|
|
|
373
2813
|
cJSON *how_j = cJSON_GetObjectItemCaseSensitive(args, "how");
|
|
374
|
-
if (cJSON_IsString(how_j)
|
|
375
|
-
st->how =
|
|
2814
|
+
if (cJSON_IsString(how_j)) {
|
|
2815
|
+
if (strcmp(how_j->valuestring, "inner") == 0) st->how = 0;
|
|
2816
|
+
else if (strcmp(how_j->valuestring, "left") == 0) st->how = 1;
|
|
2817
|
+
else if (strcmp(how_j->valuestring, "semi") == 0) st->how = 2;
|
|
2818
|
+
else if (strcmp(how_j->valuestring, "anti") == 0) st->how = 3;
|
|
2819
|
+
else {
|
|
2820
|
+
tf_set_last_error("join: how must be inner, left, semi, or anti");
|
|
2821
|
+
join_state_free(st);
|
|
2822
|
+
return NULL;
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
if (parse_positive_size_arg(args, "max_lookup_rows", &st->max_lookup_rows, "join") < 0 ||
|
|
2827
|
+
parse_positive_size_arg(args, "max_lookup_keys", &st->max_lookup_keys, "join") < 0 ||
|
|
2828
|
+
parse_positive_size_arg(args, "max_lookup_bytes", &st->max_lookup_bytes, "join") < 0 ||
|
|
2829
|
+
parse_positive_size_arg(args, "max_state_bytes", &st->max_state_bytes, "join") < 0 ||
|
|
2830
|
+
parse_positive_size_arg(args, "max_matches_per_row", &st->max_matches_per_row, "join") < 0 ||
|
|
2831
|
+
parse_positive_size_arg(args, "max_output_rows", &st->max_output_rows, "join") < 0) {
|
|
2832
|
+
join_state_free(st);
|
|
2833
|
+
return NULL;
|
|
2834
|
+
}
|
|
376
2835
|
|
|
377
|
-
|
|
378
|
-
if (
|
|
2836
|
+
cJSON *spill_dir_j = cJSON_GetObjectItemCaseSensitive(args, "spill_dir");
|
|
2837
|
+
if (cJSON_IsString(spill_dir_j) && spill_dir_j->valuestring && spill_dir_j->valuestring[0]) {
|
|
2838
|
+
st->use_spill = 1;
|
|
2839
|
+
st->spill_dir = strdup(spill_dir_j->valuestring);
|
|
2840
|
+
if (!st->spill_dir) { join_state_free(st); return NULL; }
|
|
2841
|
+
if (tf_spill_session_create(st->spill_dir, &st->spill) != TF_OK) { join_state_free(st); return NULL; }
|
|
2842
|
+
size_t parsed_size = 0;
|
|
2843
|
+
int has_spill_memory = tf_json_get_size_arg(args, "spill_memory_bytes",
|
|
2844
|
+
1, TF_MAX_SPILL_MEMORY_BYTES,
|
|
2845
|
+
&parsed_size, "join");
|
|
2846
|
+
if (has_spill_memory < 0) { join_state_free(st); return NULL; }
|
|
2847
|
+
if (has_spill_memory > 0) st->spill_memory_bytes = parsed_size;
|
|
2848
|
+
int has_spill_rows = tf_json_get_size_arg(args, "spill_run_rows",
|
|
2849
|
+
1, TF_MAX_SPILL_RUN_ROWS,
|
|
2850
|
+
&parsed_size, "join");
|
|
2851
|
+
if (has_spill_rows < 0) { join_state_free(st); return NULL; }
|
|
2852
|
+
if (has_spill_rows > 0) st->configured_run_rows = parsed_size;
|
|
2853
|
+
int has_output_rows = tf_json_get_size_arg(args, "spill_output_rows",
|
|
2854
|
+
1, TF_MAX_SPILL_OUTPUT_ROWS,
|
|
2855
|
+
&parsed_size, "join");
|
|
2856
|
+
if (has_output_rows < 0) { join_state_free(st); return NULL; }
|
|
2857
|
+
if (has_output_rows > 0) st->output_batch_rows = parsed_size;
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
if (st->use_spill && st->sorted) {
|
|
2861
|
+
tf_set_last_error("join: spill_dir and sorted=true are mutually exclusive");
|
|
2862
|
+
join_state_free(st);
|
|
2863
|
+
return NULL;
|
|
2864
|
+
}
|
|
2865
|
+
if (st->use_spill && st->how < 2 && st->max_matches_per_row == 0) {
|
|
2866
|
+
tf_set_last_error("join: spill_dir for inner/left joins needs max_matches_per_row");
|
|
2867
|
+
join_state_free(st);
|
|
2868
|
+
return NULL;
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
tf_step *step = calloc(1, sizeof(tf_step));
|
|
2872
|
+
if (!step) { join_state_free(st); return NULL; }
|
|
379
2873
|
step->process = join_process;
|
|
380
2874
|
step->flush = join_flush;
|
|
2875
|
+
step->flush_next = st->use_spill ? join_flush_next : NULL;
|
|
2876
|
+
step->append_stats = join_append_stats;
|
|
381
2877
|
step->destroy = join_destroy;
|
|
382
2878
|
step->state = st;
|
|
383
2879
|
return step;
|
|
384
2880
|
}
|
|
2881
|
+
|
|
2882
|
+
static tf_step *tf_join_create_with_how(const cJSON *args, const char *how) {
|
|
2883
|
+
if (!args) return NULL;
|
|
2884
|
+
cJSON *copy = cJSON_Duplicate(args, 1);
|
|
2885
|
+
if (!copy) return NULL;
|
|
2886
|
+
cJSON_DeleteItemFromObjectCaseSensitive(copy, "how");
|
|
2887
|
+
if (tf_json_add_string(copy, "how", how) != TF_OK) {
|
|
2888
|
+
cJSON_Delete(copy);
|
|
2889
|
+
return NULL;
|
|
2890
|
+
}
|
|
2891
|
+
tf_step *step = tf_join_create(copy);
|
|
2892
|
+
cJSON_Delete(copy);
|
|
2893
|
+
return step;
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
tf_step *tf_semi_join_create(const cJSON *args) {
|
|
2897
|
+
return tf_join_create_with_how(args, "semi");
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
tf_step *tf_anti_join_create(const cJSON *args) {
|
|
2901
|
+
return tf_join_create_with_how(args, "anti");
|
|
2902
|
+
}
|