tranfi 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +177 -21
- package/NOTICE +8 -0
- package/README.md +627 -0
- package/app/assets/index-6quYZ5Ap.css +5 -0
- package/app/assets/index-BIAIKnrp.js +160 -0
- package/app/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/app/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/app/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/app/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/app/index.html +13 -0
- package/binding.gyp +121 -0
- package/csrc/arena.c +93 -0
- package/csrc/batch.c +976 -0
- package/csrc/buffer.c +154 -0
- package/csrc/cJSON.c +3386 -0
- package/csrc/cJSON.h +316 -0
- package/csrc/codec_csv.c +1951 -0
- package/csrc/codec_jsonl.c +1086 -0
- package/csrc/codec_table.c +248 -0
- package/csrc/codec_text.c +447 -0
- package/csrc/compiler.c +130 -0
- package/csrc/config.h +21 -0
- package/csrc/date_utils.h +94 -0
- package/csrc/dsl.c +5417 -0
- package/csrc/dsl.h +22 -0
- package/csrc/expr.c +1553 -0
- package/csrc/expr.h +58 -0
- package/csrc/internal.h +539 -0
- package/csrc/ir.c +166 -0
- package/csrc/ir.h +208 -0
- package/csrc/ir_schema.c +75 -0
- package/csrc/ir_serialize.c +166 -0
- package/csrc/ir_sql.c +1822 -0
- package/csrc/ir_validate.c +576 -0
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +1241 -0
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +283 -0
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +255 -0
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +248 -0
- package/csrc/op_cast.c +523 -0
- package/csrc/op_clip.c +99 -0
- package/csrc/op_date_trunc.c +355 -0
- package/csrc/op_datetime.c +394 -0
- package/csrc/op_derive.c +216 -0
- package/csrc/op_diff.c +250 -0
- package/csrc/op_ewma.c +222 -0
- package/csrc/op_explode.c +206 -0
- package/csrc/op_fill_down.c +235 -0
- package/csrc/op_fill_null.c +268 -0
- package/csrc/op_filter.c +181 -0
- package/csrc/op_frequency.c +721 -0
- package/csrc/op_grep.c +181 -0
- package/csrc/op_group_agg.c +1956 -0
- package/csrc/op_hash.c +159 -0
- package/csrc/op_head.c +84 -0
- package/csrc/op_interpolate.c +445 -0
- package/csrc/op_join.c +2902 -0
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +419 -0
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +242 -0
- package/csrc/op_normalize.c +510 -0
- package/csrc/op_onehot.c +457 -0
- package/csrc/op_pivot.c +1754 -0
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +3044 -0
- package/csrc/op_rename.c +129 -0
- package/csrc/op_replace.c +354 -0
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +158 -0
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +340 -0
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +95 -0
- package/csrc/op_sort.c +819 -0
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +151 -0
- package/csrc/op_split_data.c +119 -0
- package/csrc/op_stack.c +271 -0
- package/csrc/op_stats.c +875 -0
- package/csrc/op_step.c +333 -0
- package/csrc/op_tail.c +105 -0
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +357 -0
- package/csrc/op_trim.c +138 -0
- package/csrc/op_unique.c +1343 -0
- package/csrc/op_unpivot.c +193 -0
- package/csrc/op_validate.c +648 -0
- package/csrc/op_window.c +591 -0
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +1088 -0
- package/csrc/recipes.c +104 -0
- package/csrc/recipes.h +27 -0
- package/csrc/report.c +506 -0
- package/csrc/report.h +22 -0
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +291 -0
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +218 -0
- package/napi_api.c +534 -0
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +64 -59
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +190 -0
- package/src/engines/duckdb.js +142 -0
- package/src/index.js +925 -0
- package/src/memory_policy.js +411 -0
- package/src/native.js +18 -0
- package/src/pipeline.js +709 -0
- package/src/recipe_json.js +80 -0
- package/src/server.js +279 -0
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +21 -0
- package/wasm/index.js +732 -0
- package/wasm/package.json +1 -0
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/dist/bundle.js +0 -1
- package/index.html +0 -18
- package/src/app.css +0 -169
- package/src/app.js +0 -203
- package/src/app.vue +0 -250
- package/src/bulma-input.vue +0 -110
- package/src/common-inputs.js +0 -28
- package/src/main.js +0 -20
- package/src/transforms.js +0 -166
- package/webpack.config.js +0 -108
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/** Host-side memory policy checks for native Tranfi execution. */
|
|
2
|
+
|
|
3
|
+
const BLOCKING_OPS = new Set([
|
|
4
|
+
'sort',
|
|
5
|
+
'pivot',
|
|
6
|
+
'interpolate',
|
|
7
|
+
'normalize',
|
|
8
|
+
'acf',
|
|
9
|
+
'codec.table.encode'
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
const KEY_STATE_OPS = new Set([
|
|
13
|
+
'rowid',
|
|
14
|
+
'unique',
|
|
15
|
+
'dedup',
|
|
16
|
+
'group-agg',
|
|
17
|
+
'frequency',
|
|
18
|
+
'join',
|
|
19
|
+
'semi-join',
|
|
20
|
+
'anti-join',
|
|
21
|
+
'onehot',
|
|
22
|
+
'label-encode',
|
|
23
|
+
'intersect',
|
|
24
|
+
'setdiff',
|
|
25
|
+
'intersect-all',
|
|
26
|
+
'setdiff-all',
|
|
27
|
+
'union'
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
const SIZE_UNITS = {
|
|
31
|
+
'': 1,
|
|
32
|
+
b: 1,
|
|
33
|
+
k: 1024,
|
|
34
|
+
kb: 1024,
|
|
35
|
+
kib: 1024,
|
|
36
|
+
m: 1024 ** 2,
|
|
37
|
+
mb: 1024 ** 2,
|
|
38
|
+
mib: 1024 ** 2,
|
|
39
|
+
g: 1024 ** 3,
|
|
40
|
+
gb: 1024 ** 3,
|
|
41
|
+
gib: 1024 ** 3,
|
|
42
|
+
t: 1024 ** 4,
|
|
43
|
+
tb: 1024 ** 4,
|
|
44
|
+
tib: 1024 ** 4
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const UNIQUE_DEFAULT_BLOOM_BYTES = 1024 ** 2
|
|
48
|
+
|
|
49
|
+
function parseMemorySize(value) {
|
|
50
|
+
if (value === undefined || value === null) return null
|
|
51
|
+
if (typeof value === 'number') {
|
|
52
|
+
if (!Number.isFinite(value) || value <= 0 || Math.floor(value) !== value) {
|
|
53
|
+
throw new Error('memory must be a positive integer byte count')
|
|
54
|
+
}
|
|
55
|
+
return value
|
|
56
|
+
}
|
|
57
|
+
if (typeof value !== 'string') {
|
|
58
|
+
throw new TypeError('memory must be a positive byte count or size string')
|
|
59
|
+
}
|
|
60
|
+
let text = value.trim()
|
|
61
|
+
const lower = text.toLowerCase()
|
|
62
|
+
if (lower.startsWith('max:') || lower.startsWith('max=')) text = text.slice(4).trim()
|
|
63
|
+
const match = text.match(/^(\d+)\s*([a-zA-Z]*)$/)
|
|
64
|
+
if (!match) throw new Error('memory must look like 64MB or max:64MB')
|
|
65
|
+
const amount = Number(match[1])
|
|
66
|
+
const suffix = match[2].toLowerCase()
|
|
67
|
+
if (amount <= 0) throw new Error('memory must be positive')
|
|
68
|
+
if (!(suffix in SIZE_UNITS)) throw new Error(`unsupported memory size suffix '${suffix}'`)
|
|
69
|
+
return amount * SIZE_UNITS[suffix]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function memoryForEngine(value) {
|
|
73
|
+
const n = parseMemorySize(value)
|
|
74
|
+
return n === null ? null : `${n}B`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatBytes(n) {
|
|
78
|
+
if (n < 1024) return `${n}B`
|
|
79
|
+
if (n < 1024 ** 2) return `${(n / 1024).toFixed(1)}KB`
|
|
80
|
+
if (n < 1024 ** 3) return `${(n / (1024 ** 2)).toFixed(1)}MB`
|
|
81
|
+
return `${(n / (1024 ** 3)).toFixed(1)}GB`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function stepsFromPlan(planJson) {
|
|
85
|
+
const plan = typeof planJson === 'string' ? JSON.parse(planJson) : planJson
|
|
86
|
+
if (!plan || !Array.isArray(plan.steps)) throw new Error('plan must contain a steps array')
|
|
87
|
+
return plan.steps
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isFilteringJoin(step) {
|
|
91
|
+
const args = argsOf(step)
|
|
92
|
+
return step.op === 'semi-join' || step.op === 'anti-join' ||
|
|
93
|
+
(step.op === 'join' && (args.how === 'semi' || args.how === 'anti'))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function memoryClass(step) {
|
|
97
|
+
if (typeof step.memory_class === 'string') return step.memory_class
|
|
98
|
+
const args = argsOf(step)
|
|
99
|
+
if (step.op === 'pivot') {
|
|
100
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
|
|
101
|
+
if (args.sorted === true && Array.isArray(args.categories) && args.categories.length > 0) return 'bounded_state'
|
|
102
|
+
}
|
|
103
|
+
if (step.op === 'unique' || step.op === 'dedup') {
|
|
104
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
|
|
105
|
+
if (args.sorted === true) return 'bounded_state'
|
|
106
|
+
}
|
|
107
|
+
if (step.op === 'group-agg') {
|
|
108
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
|
|
109
|
+
if (args.sorted === true) return 'bounded_state'
|
|
110
|
+
}
|
|
111
|
+
if (step.op === 'join' || step.op === 'semi-join' || step.op === 'anti-join') {
|
|
112
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
|
|
113
|
+
if (args.sorted === true) return 'bounded_state'
|
|
114
|
+
}
|
|
115
|
+
if (step.op === 'intersect' || step.op === 'setdiff' || step.op === 'intersect-all' || step.op === 'setdiff-all') {
|
|
116
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
|
|
117
|
+
if (args.sorted === true) return 'bounded_state'
|
|
118
|
+
}
|
|
119
|
+
if (step.op === 'union') {
|
|
120
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) return 'external'
|
|
121
|
+
if (args.sorted === true) return 'bounded_state'
|
|
122
|
+
}
|
|
123
|
+
if (BLOCKING_OPS.has(step.op)) return 'blocking'
|
|
124
|
+
if (KEY_STATE_OPS.has(step.op)) return 'key_state'
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function argsOf(step) {
|
|
129
|
+
return step && step.args && typeof step.args === 'object' ? step.args : {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function positiveInt(value) {
|
|
133
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 && Math.floor(value) === value
|
|
134
|
+
? value
|
|
135
|
+
: null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function arrayLen(value) {
|
|
139
|
+
return Array.isArray(value) ? value.length : 0
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function stringArrayBytes(value) {
|
|
143
|
+
if (!Array.isArray(value)) return 0
|
|
144
|
+
let total = 0
|
|
145
|
+
for (const item of value) {
|
|
146
|
+
if (typeof item === 'string') total += Buffer.byteLength(item, 'utf-8') + 1
|
|
147
|
+
}
|
|
148
|
+
return total
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function categoryCap(step) {
|
|
152
|
+
const args = argsOf(step)
|
|
153
|
+
const maxCategories = positiveInt(args.max_categories)
|
|
154
|
+
let nCategories = arrayLen(args.categories)
|
|
155
|
+
let literalBytes = stringArrayBytes(args.categories)
|
|
156
|
+
if (args.unknown === 'other') {
|
|
157
|
+
nCategories += 1
|
|
158
|
+
literalBytes += 'other'.length + 1
|
|
159
|
+
}
|
|
160
|
+
if (maxCategories !== null) return [maxCategories, literalBytes]
|
|
161
|
+
if (nCategories > 0) return [nCategories, literalBytes]
|
|
162
|
+
return [null, literalBytes]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function estimateKeyStateStepBytes(step) {
|
|
166
|
+
const args = argsOf(step)
|
|
167
|
+
const op = step.op
|
|
168
|
+
|
|
169
|
+
if (op === 'rowid') {
|
|
170
|
+
const nCols = arrayLen(args.columns)
|
|
171
|
+
if (nCols === 0 || args.sorted === true) return 2048 + nCols * 128
|
|
172
|
+
const maxStateBytes = positiveInt(args.max_state_bytes)
|
|
173
|
+
if (maxStateBytes !== null) return maxStateBytes
|
|
174
|
+
const maxKeys = positiveInt(args.max_keys)
|
|
175
|
+
if (maxKeys === null) throw new Error("step 'rowid' needs max_keys, max_state_bytes, or sorted=true")
|
|
176
|
+
return 1024 + maxKeys * 224 + nCols * 128
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (op === 'unique' || op === 'dedup') {
|
|
180
|
+
const nCols = arrayLen(args.columns)
|
|
181
|
+
if (args.sorted === true) return 2048 + nCols * 128
|
|
182
|
+
if (args.approx === true || args.mode === 'approx') {
|
|
183
|
+
const bloomBytes = positiveInt(args.bloom_bytes)
|
|
184
|
+
return bloomBytes !== null ? bloomBytes : UNIQUE_DEFAULT_BLOOM_BYTES
|
|
185
|
+
}
|
|
186
|
+
const maxStateBytes = positiveInt(args.max_state_bytes)
|
|
187
|
+
if (maxStateBytes !== null) return maxStateBytes
|
|
188
|
+
const maxKeys = positiveInt(args.max_keys)
|
|
189
|
+
if (maxKeys === null) throw new Error(`step '${op}' needs max_keys, max_state_bytes, or sorted=true for byte-bounded native execution`)
|
|
190
|
+
return 1024 + maxKeys * (256 + nCols * 32)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (op === 'group-agg') {
|
|
194
|
+
const nGroup = arrayLen(args.group_by)
|
|
195
|
+
const nAggs = arrayLen(args.aggs)
|
|
196
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) {
|
|
197
|
+
const spillBytes = positiveInt(args.spill_memory_bytes)
|
|
198
|
+
if (spillBytes !== null) return spillBytes
|
|
199
|
+
throw new Error("step 'group-agg' spill mode needs spill_memory_bytes for a host byte estimate")
|
|
200
|
+
}
|
|
201
|
+
const maxStateBytes = positiveInt(args.max_state_bytes)
|
|
202
|
+
if (maxStateBytes !== null) return maxStateBytes
|
|
203
|
+
if (args.sorted === true) return 4096 + nGroup * 128 + nAggs * 128
|
|
204
|
+
const maxGroups = positiveInt(args.max_groups)
|
|
205
|
+
if (maxGroups === null) throw new Error("step 'group-agg' needs max_groups, max_state_bytes, or sorted=true for byte-bounded native execution")
|
|
206
|
+
return 2048 + maxGroups * (384 + nGroup * 64 + nAggs * 96)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (op === 'frequency') {
|
|
210
|
+
const approx = args.mode === 'approx' || args.approx === true
|
|
211
|
+
const maxValues = positiveInt(args.max_values)
|
|
212
|
+
if (approx && maxValues === null) throw new Error("step 'frequency' mode=approx needs max_values for byte-bounded native execution")
|
|
213
|
+
const maxStateBytes = positiveInt(args.max_state_bytes)
|
|
214
|
+
if (maxStateBytes !== null) return maxStateBytes
|
|
215
|
+
if (maxValues === null) throw new Error("step 'frequency' needs max_values or max_state_bytes for byte-bounded native execution")
|
|
216
|
+
const cappedValues = args.overflow === 'other' ? maxValues + 1 : maxValues
|
|
217
|
+
return 1024 + cappedValues * (224 + arrayLen(args.columns) * 32)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (op === 'onehot' || op === 'label-encode') {
|
|
221
|
+
const maxStateBytes = positiveInt(args.max_state_bytes)
|
|
222
|
+
if (maxStateBytes !== null) return maxStateBytes
|
|
223
|
+
const [cap, literalBytes] = categoryCap(step)
|
|
224
|
+
if (cap === null) throw new Error(`step '${op}' needs max_categories, max_state_bytes, or declared categories`)
|
|
225
|
+
return 1024 + literalBytes + cap * (op === 'onehot' ? 320 : 224)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (op === 'union-all') return 4096
|
|
229
|
+
|
|
230
|
+
if (op === 'intersect' || op === 'setdiff' || op === 'intersect-all' || op === 'setdiff-all') {
|
|
231
|
+
const nCols = arrayLen(args.columns)
|
|
232
|
+
const bagSetOp = op === 'intersect-all' || op === 'setdiff-all'
|
|
233
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) {
|
|
234
|
+
const spillBytes = positiveInt(args.spill_memory_bytes)
|
|
235
|
+
if (spillBytes !== null) return spillBytes
|
|
236
|
+
throw new Error(`step '${op}' spill mode needs spill_memory_bytes for a host byte estimate`)
|
|
237
|
+
}
|
|
238
|
+
if (args.sorted === true) {
|
|
239
|
+
return 4096 + nCols * 256
|
|
240
|
+
}
|
|
241
|
+
const maxLookupBytes = positiveInt(args.max_lookup_bytes)
|
|
242
|
+
if (maxLookupBytes === null) throw new Error(`step '${op}' needs max_lookup_bytes for byte-bounded native execution`)
|
|
243
|
+
const maxStateBytes = positiveInt(args.max_state_bytes)
|
|
244
|
+
if (maxStateBytes !== null) return 4096 + maxLookupBytes * 3 + maxStateBytes
|
|
245
|
+
let est = 4096 + maxLookupBytes * 3
|
|
246
|
+
const maxLookupKeys = positiveInt(args.max_lookup_keys)
|
|
247
|
+
if (bagSetOp) {
|
|
248
|
+
if (maxLookupKeys === null) throw new Error(`step '${op}' needs max_lookup_keys or max_state_bytes for bag-count set semantics`)
|
|
249
|
+
return est + maxLookupKeys * (224 + nCols * 32)
|
|
250
|
+
}
|
|
251
|
+
const maxOutputKeys = positiveInt(args.max_output_keys)
|
|
252
|
+
if (maxOutputKeys === null) throw new Error(`step '${op}' needs max_output_keys or max_state_bytes for duplicate-eliminating set semantics`)
|
|
253
|
+
if (maxLookupKeys !== null) est += maxLookupKeys * (192 + nCols * 32)
|
|
254
|
+
est += maxOutputKeys * (192 + nCols * 32)
|
|
255
|
+
return est
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (op === 'union') {
|
|
259
|
+
if (args.sorted === true) {
|
|
260
|
+
const nCols = arrayLen(args.columns)
|
|
261
|
+
return 4096 + nCols * 256
|
|
262
|
+
}
|
|
263
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) {
|
|
264
|
+
const spillMemoryBytes = positiveInt(args.spill_memory_bytes)
|
|
265
|
+
if (spillMemoryBytes === null) throw new Error("step 'union' spill needs spill_memory_bytes for byte-bounded native execution")
|
|
266
|
+
return spillMemoryBytes
|
|
267
|
+
}
|
|
268
|
+
const maxStateBytes = positiveInt(args.max_state_bytes)
|
|
269
|
+
if (maxStateBytes !== null) return maxStateBytes
|
|
270
|
+
const nCols = arrayLen(args.columns)
|
|
271
|
+
const maxOutputKeys = positiveInt(args.max_output_keys)
|
|
272
|
+
if (maxOutputKeys === null) throw new Error("step 'union' needs max_output_keys or max_state_bytes for duplicate-eliminating set semantics")
|
|
273
|
+
return 4096 + maxOutputKeys * (192 + nCols * 32)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (op === 'join' || op === 'semi-join' || op === 'anti-join') {
|
|
277
|
+
const filtering = isFilteringJoin(step)
|
|
278
|
+
if (typeof args.spill_dir === 'string' && args.spill_dir) {
|
|
279
|
+
if (!filtering) {
|
|
280
|
+
const maxMatches = positiveInt(args.max_matches_per_row)
|
|
281
|
+
if (maxMatches === null) throw new Error(`step '${op}' spill mode needs max_matches_per_row for byte-bounded mutating join`)
|
|
282
|
+
}
|
|
283
|
+
const spillBytes = positiveInt(args.spill_memory_bytes)
|
|
284
|
+
if (spillBytes !== null) return spillBytes
|
|
285
|
+
throw new Error(`step '${op}' spill mode needs spill_memory_bytes for a host byte estimate`)
|
|
286
|
+
}
|
|
287
|
+
if (args.sorted === true) {
|
|
288
|
+
if (filtering) return 4096
|
|
289
|
+
const maxMatches = positiveInt(args.max_matches_per_row)
|
|
290
|
+
if (maxMatches === null) throw new Error(`step '${op}' sorted=true needs max_matches_per_row for byte-bounded mutating join`)
|
|
291
|
+
return 4096 + maxMatches * 512
|
|
292
|
+
}
|
|
293
|
+
const maxLookupBytes = positiveInt(args.max_lookup_bytes)
|
|
294
|
+
if (maxLookupBytes === null) throw new Error(`step '${op}' needs max_lookup_bytes or sorted=true for byte-bounded native execution`)
|
|
295
|
+
let est = 4096 + maxLookupBytes * 4
|
|
296
|
+
const maxStateBytes = positiveInt(args.max_state_bytes)
|
|
297
|
+
if (maxStateBytes !== null) return est + maxStateBytes
|
|
298
|
+
const maxLookupRows = positiveInt(args.max_lookup_rows)
|
|
299
|
+
const maxLookupKeys = positiveInt(args.max_lookup_keys)
|
|
300
|
+
if (maxLookupRows !== null) est += maxLookupRows * 96
|
|
301
|
+
if (maxLookupKeys !== null) est += maxLookupKeys * 192
|
|
302
|
+
return est
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
throw new Error(`step '${op}' has no native byte estimator`)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function loadPlan(planJson) {
|
|
309
|
+
const plan = typeof planJson === 'string' ? JSON.parse(planJson) : planJson
|
|
310
|
+
if (!plan || !Array.isArray(plan.steps)) throw new Error('plan must contain a steps array')
|
|
311
|
+
return plan
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function blockingSteps(steps) {
|
|
315
|
+
return steps.filter(step => memoryClass(step) === 'blocking')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function keyStateSteps(steps) {
|
|
319
|
+
return steps.filter(step => memoryClass(step) === 'key_state')
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function pivotStepCanUseNativeSpill(step) {
|
|
323
|
+
const args = argsOf(step)
|
|
324
|
+
if (step.op !== 'pivot' || args.sorted === true) return false
|
|
325
|
+
return (Array.isArray(args.categories) && args.categories.length > 0) || positiveInt(args.max_categories) !== null
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function blockingStepCanUseNativeSpill(step) {
|
|
329
|
+
return step.op === 'sort' || pivotStepCanUseNativeSpill(step)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function blockingPlanCanUseNativeSpill(steps) {
|
|
333
|
+
return steps.length > 0 && steps.every(blockingStepCanUseNativeSpill)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function keyStateStepCanUseNativeSpill(step) {
|
|
337
|
+
const args = argsOf(step)
|
|
338
|
+
return (step.op === 'unique' || step.op === 'dedup' || step.op === 'group-agg' || step.op === 'join' || step.op === 'semi-join' || step.op === 'anti-join' || step.op === 'intersect' || step.op === 'setdiff' || step.op === 'intersect-all' || step.op === 'setdiff-all' || step.op === 'union') && args.sorted !== true
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function injectNativeSpill(plan, spillDir, memoryBytes) {
|
|
342
|
+
for (const step of plan.steps) {
|
|
343
|
+
if (!blockingStepCanUseNativeSpill(step) && !keyStateStepCanUseNativeSpill(step)) continue
|
|
344
|
+
if (!step.args || typeof step.args !== 'object') step.args = {}
|
|
345
|
+
step.args.spill_dir = spillDir
|
|
346
|
+
if (memoryBytes !== null) step.args.spill_memory_bytes = memoryBytes
|
|
347
|
+
}
|
|
348
|
+
return JSON.stringify(plan)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function prepareNativePlan(planJson, { allowBlocking = false, memory, spillDir } = {}) {
|
|
352
|
+
const plan = loadPlan(planJson)
|
|
353
|
+
const steps = plan.steps
|
|
354
|
+
const blocking = blockingSteps(steps)
|
|
355
|
+
const firstBlocking = blocking[0] || null
|
|
356
|
+
const keySteps = keyStateSteps(steps)
|
|
357
|
+
const memoryBytes = parseMemorySize(memory)
|
|
358
|
+
const canSpillBlocking = Boolean(spillDir) && blockingPlanCanUseNativeSpill(blocking)
|
|
359
|
+
const spillableKeySteps = spillDir ? keySteps.filter(keyStateStepCanUseNativeSpill) : []
|
|
360
|
+
const keyStepsForEstimate = keySteps.filter(step => !spillableKeySteps.includes(step))
|
|
361
|
+
|
|
362
|
+
if (spillDir) {
|
|
363
|
+
const unsupportedKey = keySteps.find(step => !keyStateStepCanUseNativeSpill(step))
|
|
364
|
+
if (unsupportedKey) {
|
|
365
|
+
throw new Error(`spillDir was requested, but native spill is not implemented yet for key-state step '${unsupportedKey.op}'`)
|
|
366
|
+
}
|
|
367
|
+
const unsupported = blocking.find(step => !blockingStepCanUseNativeSpill(step))
|
|
368
|
+
if (unsupported) {
|
|
369
|
+
throw new Error(`spillDir was requested, but native spill is not implemented yet for blocking step '${unsupported.op}'`)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (memoryBytes !== null && firstBlocking && !canSpillBlocking) {
|
|
374
|
+
throw new Error(`memory ${formatBytes(memoryBytes)} was requested, but native byte caps are not implemented for blocking step '${firstBlocking.op}'`)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (memoryBytes !== null && keyStepsForEstimate.length > 0) {
|
|
378
|
+
let total = 0
|
|
379
|
+
for (const step of keyStepsForEstimate) total += estimateKeyStateStepBytes(step)
|
|
380
|
+
if (total > memoryBytes) {
|
|
381
|
+
throw new Error(`estimated native key-state memory ${formatBytes(total)} exceeds memory ${formatBytes(memoryBytes)}`)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (firstBlocking && !allowBlocking && !canSpillBlocking) {
|
|
386
|
+
throw new Error(`blocking step '${firstBlocking.op}' requires full input in native mode; pass allowBlocking: true for known-small data or use an external engine`)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (canSpillBlocking || spillableKeySteps.length > 0) return injectNativeSpill(plan, spillDir, memoryBytes)
|
|
390
|
+
return typeof planJson === 'string' ? planJson : JSON.stringify(plan)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function validateNativeMemoryPolicy(planJson, { allowBlocking = false, memory, spillDir } = {}) {
|
|
394
|
+
const memoryBytes = parseMemorySize(memory)
|
|
395
|
+
const prepared = prepareNativePlan(planJson, { allowBlocking, memory, spillDir })
|
|
396
|
+
const plan = loadPlan(prepared)
|
|
397
|
+
const keySteps = keyStateSteps(plan.steps)
|
|
398
|
+
|
|
399
|
+
if (memoryBytes === null || keySteps.length === 0) return null
|
|
400
|
+
let estimated = 0
|
|
401
|
+
for (const step of keySteps) estimated += estimateKeyStateStepBytes(step)
|
|
402
|
+
return estimated
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
module.exports = {
|
|
406
|
+
parseMemorySize,
|
|
407
|
+
memoryForEngine,
|
|
408
|
+
prepareNativePlan,
|
|
409
|
+
validateNativeMemoryPolicy,
|
|
410
|
+
estimateKeyStateStepBytes
|
|
411
|
+
}
|
package/src/native.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* native.js — Load the N-API native addon.
|
|
3
|
+
* Returns the binding object or null if not available.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let binding = null
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
binding = require('../build/Release/tranfi_napi.node')
|
|
10
|
+
} catch {
|
|
11
|
+
try {
|
|
12
|
+
binding = require('../build/Debug/tranfi_napi.node')
|
|
13
|
+
} catch {
|
|
14
|
+
binding = null
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = binding
|