qvdjs 2.0.0 → 2.0.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/README.md +16 -16
- package/dist/index.cjs +106 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +106 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ files, and validate output in your own Qlik environment** before relying on it.
|
|
|
38
38
|
Qlik file and writing it back reproduces its symbol table byte for byte, whichever way it was read; a
|
|
39
39
|
value you change is written as Qlik stores that kind of value. Where that boundary lies is the first
|
|
40
40
|
thing worth reading:
|
|
41
|
-
[What a round trip preserves](https://qvdjs.ptarmiganlabs.com/
|
|
41
|
+
[What a round trip preserves](https://qvdjs.ptarmiganlabs.com/v2.0/overview/what-a-round-trip-preserves/).
|
|
42
42
|
|
|
43
43
|
## Checked against Qlik Sense
|
|
44
44
|
|
|
@@ -65,7 +65,7 @@ Two differences are by design: Qlik often gives a field more bits in the index t
|
|
|
65
65
|
where qvdjs uses the fewest, and qvdjs adds no tags or number formats of its own - Qlik works tags out
|
|
66
66
|
when it loads a file. QVD is also QlikView's format, but QlikView is not part of the comparison.
|
|
67
67
|
|
|
68
|
-
→ [Checked against Qlik Sense](https://qvdjs.ptarmiganlabs.com/
|
|
68
|
+
→ [Checked against Qlik Sense](https://qvdjs.ptarmiganlabs.com/v2.0/overview/checked-against-qlik-sense/)
|
|
69
69
|
|
|
70
70
|
## Four ways to open a file
|
|
71
71
|
|
|
@@ -107,7 +107,7 @@ Reaching for `fromQvd` when you wanted one of the other three is the common mist
|
|
|
107
107
|
`fromQvd(path, {maxRows: 0})` is **not** a substitute for `readMetadata`: it loads no rows but still
|
|
108
108
|
parses the whole symbol table, which grows with the data.
|
|
109
109
|
|
|
110
|
-
→ [Choosing an entry point](https://qvdjs.ptarmiganlabs.com/
|
|
110
|
+
→ [Choosing an entry point](https://qvdjs.ptarmiganlabs.com/v2.0/getting-started/)
|
|
111
111
|
|
|
112
112
|
## What a cell holds
|
|
113
113
|
|
|
@@ -171,7 +171,7 @@ NULL. A dual value - a `QvdDual`, or an object with exactly the keys `number` an
|
|
|
171
171
|
as a dual, which is how a number gets the text Qlik displays. As in Qlik, a field holds one text per
|
|
172
172
|
number, the first written. Anything else is refused with a `QvdValidationError`.
|
|
173
173
|
|
|
174
|
-
→ [Writing a QVD](https://qvdjs.ptarmiganlabs.com/
|
|
174
|
+
→ [Writing a QVD](https://qvdjs.ptarmiganlabs.com/v2.0/guides/)
|
|
175
175
|
|
|
176
176
|
## Reading part of a file
|
|
177
177
|
|
|
@@ -211,7 +211,7 @@ try {
|
|
|
211
211
|
When it is `iterate()` that overflowed, the context carries `recommendedChunkSize` as well, so a
|
|
212
212
|
caller cannot mistake one recommendation for the other.
|
|
213
213
|
|
|
214
|
-
→ [Memory and file size limits](https://qvdjs.ptarmiganlabs.com/
|
|
214
|
+
→ [Memory and file size limits](https://qvdjs.ptarmiganlabs.com/v2.0/overview/memory-and-file-size-limits/)
|
|
215
215
|
|
|
216
216
|
## Metadata
|
|
217
217
|
|
|
@@ -228,7 +228,7 @@ df.setFileMetadata({tableName: 'UpdatedProducts'});
|
|
|
228
228
|
await df.toQvd('products-v2.qvd');
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
-
→ [Metadata reference](https://qvdjs.ptarmiganlabs.com/
|
|
231
|
+
→ [Metadata reference](https://qvdjs.ptarmiganlabs.com/v2.0/reference/)
|
|
232
232
|
|
|
233
233
|
## File paths are sandboxed
|
|
234
234
|
|
|
@@ -252,7 +252,7 @@ is a different file at that name. Either is a `QvdSecurityError` whose `context.
|
|
|
252
252
|
swapped for a symlink in the moment between the check and the open — is the one gap left, so keep
|
|
253
253
|
the allowed directory's own subdirectories out of untrusted hands.
|
|
254
254
|
|
|
255
|
-
→ [Path security](https://qvdjs.ptarmiganlabs.com/
|
|
255
|
+
→ [Path security](https://qvdjs.ptarmiganlabs.com/v2.0/overview/path-security/)
|
|
256
256
|
|
|
257
257
|
## Errors
|
|
258
258
|
|
|
@@ -273,7 +273,7 @@ without reporting an error; `error.context.filePosition` says where the file sto
|
|
|
273
273
|
`error.name` is always the class name, in the CommonJS and ES module builds alike, so it works across
|
|
274
274
|
a module boundary where `instanceof` may not.
|
|
275
275
|
|
|
276
|
-
→ [Troubleshooting](https://qvdjs.ptarmiganlabs.com/
|
|
276
|
+
→ [Troubleshooting](https://qvdjs.ptarmiganlabs.com/v2.0/troubleshooting/)
|
|
277
277
|
|
|
278
278
|
## Known limitations
|
|
279
279
|
|
|
@@ -286,7 +286,7 @@ Honest boundaries rather than an issue list — these are the ones that change w
|
|
|
286
286
|
| **A killed process can leave a temporary file** | A write builds `<name>.qvdjs-<hex>.tmp` beside the QVD and removes it however the write ends — unless the process is killed outright, or the removal is itself refused, in which case the error names the file in `context.temporaryFile`. The QVD is untouched either way, and such a file can be deleted. `{atomic: false}` writes none when it rewrites a QVD that exists, and damages the QVD instead; a QVD that does not exist yet is renamed into place in either mode. |
|
|
287
287
|
| **The writer takes numbers, strings, dual values and `null`** | Anything else - a `Date`, a boolean, an array, a `QvdSymbol` - is refused with a `QvdValidationError` naming the field and the row. Convert first: a `Date` to `new QvdDual(dateToQlikSerial(date), text)`, the serial and the text Qlik shows, which is how Qlik stores a date; a boolean to `-1` and `0`, as a Qlik comparison stores it. |
|
|
288
288
|
|
|
289
|
-
→ [What a round trip preserves](https://qvdjs.ptarmiganlabs.com/
|
|
289
|
+
→ [What a round trip preserves](https://qvdjs.ptarmiganlabs.com/v2.0/overview/what-a-round-trip-preserves/)
|
|
290
290
|
|
|
291
291
|
## Documentation
|
|
292
292
|
|
|
@@ -295,13 +295,13 @@ where everything above is covered properly:
|
|
|
295
295
|
|
|
296
296
|
| | |
|
|
297
297
|
| ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
298
|
-
| [Getting started](https://qvdjs.ptarmiganlabs.com/
|
|
299
|
-
| [Guides](https://qvdjs.ptarmiganlabs.com/
|
|
300
|
-
| [Concepts](https://qvdjs.ptarmiganlabs.com/
|
|
301
|
-
| [Reference](https://qvdjs.ptarmiganlabs.com/
|
|
302
|
-
| [Performance](https://qvdjs.ptarmiganlabs.com/
|
|
303
|
-
| [Troubleshooting](https://qvdjs.ptarmiganlabs.com/
|
|
304
|
-
| [Checked against Qlik Sense](https://qvdjs.ptarmiganlabs.com/
|
|
298
|
+
| [Getting started](https://qvdjs.ptarmiganlabs.com/v2.0/getting-started/) | Install, choose an entry point, first read and write |
|
|
299
|
+
| [Guides](https://qvdjs.ptarmiganlabs.com/v2.0/guides/) | One page per task, runnable sample code |
|
|
300
|
+
| [Concepts](https://qvdjs.ptarmiganlabs.com/v2.0/concepts/) | The QVD format, symbols and duals, bit stuffing, the memory model |
|
|
301
|
+
| [Reference](https://qvdjs.ptarmiganlabs.com/v2.0/reference/) | Every class, method, option and error |
|
|
302
|
+
| [Performance](https://qvdjs.ptarmiganlabs.com/v2.0/performance/) | Measured baselines, and how to read them |
|
|
303
|
+
| [Troubleshooting](https://qvdjs.ptarmiganlabs.com/v2.0/troubleshooting/) | What each failure means, and what to do about it |
|
|
304
|
+
| [Checked against Qlik Sense](https://qvdjs.ptarmiganlabs.com/v2.0/overview/checked-against-qlik-sense/) | How qvdjs's output is compared with Qlik Sense's |
|
|
305
305
|
|
|
306
306
|
Benchmarks run weekly and publish to
|
|
307
307
|
[ptarmiganlabs.github.io/qvdjs](https://ptarmiganlabs.github.io/qvdjs/).
|
package/dist/index.cjs
CHANGED
|
@@ -1098,8 +1098,12 @@ function fieldGeometry(bitOffset, bitWidth) {
|
|
|
1098
1098
|
byteCount: bitWidth === 0 ? 0 : shift + bitWidth + 7 >>> 3
|
|
1099
1099
|
};
|
|
1100
1100
|
}
|
|
1101
|
-
function decodeIndexColumn(buffer, recordSize, rowCount, bitOffset, bitWidth, bias, out) {
|
|
1101
|
+
function decodeIndexColumn(buffer, recordSize, rowCount, bitOffset, bitWidth, bias, out, bounds = null) {
|
|
1102
|
+
const symbolCount = bounds === null ? Infinity : bounds.symbolCount;
|
|
1102
1103
|
if (bitWidth === 0) {
|
|
1104
|
+
if (bounds !== null && rowCount > 0 && bias >= symbolCount) {
|
|
1105
|
+
refuse2(bounds, 0, bias, bias);
|
|
1106
|
+
}
|
|
1103
1107
|
out.fill(bias, 0, rowCount);
|
|
1104
1108
|
return out;
|
|
1105
1109
|
}
|
|
@@ -1113,10 +1117,25 @@ function decodeIndexColumn(buffer, recordSize, rowCount, bitOffset, bitWidth, bi
|
|
|
1113
1117
|
if (byteCount > 2) acc += buffer[base + 2] * 65536;
|
|
1114
1118
|
if (byteCount > 3) acc += buffer[base + 3] * 16777216;
|
|
1115
1119
|
if (byteCount > 4) acc += buffer[base + 4] * 4294967296;
|
|
1116
|
-
|
|
1120
|
+
const index = Math.floor(acc / divisor) % modulus + bias;
|
|
1121
|
+
if ((index >= symbolCount || index < 0 && index !== bias) && bounds !== null) {
|
|
1122
|
+
refuse2(bounds, row, index, bias);
|
|
1123
|
+
}
|
|
1124
|
+
out[row] = index;
|
|
1117
1125
|
}
|
|
1118
1126
|
return out;
|
|
1119
1127
|
}
|
|
1128
|
+
function refuse2(bounds, row, index, bias) {
|
|
1129
|
+
throw new exports.QvdCorruptedError("Symbol index out of range", {
|
|
1130
|
+
field: bounds.field,
|
|
1131
|
+
row: bounds.firstRow + row,
|
|
1132
|
+
symbolIndex: index,
|
|
1133
|
+
symbolCount: bounds.symbolCount,
|
|
1134
|
+
bias,
|
|
1135
|
+
file: bounds.file,
|
|
1136
|
+
stage: "parseIndexTable"
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1120
1139
|
function writeBitField(buffer, recordBase, geometry, value) {
|
|
1121
1140
|
const { byteStart, shift, byteCount } = geometry;
|
|
1122
1141
|
if (byteCount === 0) {
|
|
@@ -1133,10 +1152,12 @@ function writeBitField(buffer, recordBase, geometry, value) {
|
|
|
1133
1152
|
var MAX_BIT_WIDTH, POW2;
|
|
1134
1153
|
var init_bitUtils = __esm({
|
|
1135
1154
|
"src/util/bitUtils.js"() {
|
|
1155
|
+
init_QvdErrors();
|
|
1136
1156
|
MAX_BIT_WIDTH = 31;
|
|
1137
1157
|
POW2 = Array.from({ length: 41 }, (_, exponent) => 2 ** exponent);
|
|
1138
1158
|
__name(fieldGeometry, "fieldGeometry");
|
|
1139
1159
|
__name(decodeIndexColumn, "decodeIndexColumn");
|
|
1160
|
+
__name(refuse2, "refuse");
|
|
1140
1161
|
__name(writeBitField, "writeBitField");
|
|
1141
1162
|
}
|
|
1142
1163
|
});
|
|
@@ -2570,6 +2591,15 @@ function validateFieldBitMetadata(field, recordSize, filePath) {
|
|
|
2570
2591
|
stage: "parseIndexTable"
|
|
2571
2592
|
});
|
|
2572
2593
|
}
|
|
2594
|
+
if (bias < -2147483648 || bias + 2 ** bitWidth - 1 > 2 ** 31 - 1) {
|
|
2595
|
+
throw new exports.QvdCorruptedError("Bias out of range", {
|
|
2596
|
+
field: field["FieldName"],
|
|
2597
|
+
bias,
|
|
2598
|
+
bitWidth,
|
|
2599
|
+
file: filePath,
|
|
2600
|
+
stage: "parseIndexTable"
|
|
2601
|
+
});
|
|
2602
|
+
}
|
|
2573
2603
|
const recordSizeInBits = recordSize * 8;
|
|
2574
2604
|
if (bitOffset + bitWidth > recordSizeInBits) {
|
|
2575
2605
|
throw new exports.QvdCorruptedError("Bit field extends beyond record size", {
|
|
@@ -2697,7 +2727,10 @@ function parseFieldSymbols(symbolBuffer, start, end, keep, fieldName, filePath)
|
|
|
2697
2727
|
}
|
|
2698
2728
|
return { numbers, texts };
|
|
2699
2729
|
}
|
|
2700
|
-
|
|
2730
|
+
function countFieldSymbols(symbolBuffer, start, end, fieldName, filePath) {
|
|
2731
|
+
return parseFieldSymbols(symbolBuffer, start, end, DECODE_NOTHING, fieldName, filePath).numbers.length;
|
|
2732
|
+
}
|
|
2733
|
+
var MAX_TEXT_BYTES, DECODE_NOTHING;
|
|
2701
2734
|
var init_symbolParser = __esm({
|
|
2702
2735
|
"src/util/symbolParser.js"() {
|
|
2703
2736
|
init_QvdErrors();
|
|
@@ -2705,6 +2738,8 @@ var init_symbolParser = __esm({
|
|
|
2705
2738
|
__name(textEnd, "textEnd");
|
|
2706
2739
|
__name(overflow, "overflow");
|
|
2707
2740
|
__name(parseFieldSymbols, "parseFieldSymbols");
|
|
2741
|
+
DECODE_NOTHING = /* @__PURE__ */ new Set();
|
|
2742
|
+
__name(countFieldSymbols, "countFieldSymbols");
|
|
2708
2743
|
}
|
|
2709
2744
|
});
|
|
2710
2745
|
|
|
@@ -3218,7 +3253,7 @@ async function* chunksFrom(handle, chunkSize, failed) {
|
|
|
3218
3253
|
position += bytesRead;
|
|
3219
3254
|
}
|
|
3220
3255
|
}
|
|
3221
|
-
var MAX_HEADER_SIZE, READ_CHUNK_SIZE, ANALYSIS_SLICE_ROWS; exports.QvdFileReader = void 0;
|
|
3256
|
+
var MAX_HEADER_SIZE, READ_CHUNK_SIZE, ANALYSIS_SLICE_ROWS, COUNT_SYMBOLS_PAST; exports.QvdFileReader = void 0;
|
|
3222
3257
|
var init_QvdFileReader = __esm({
|
|
3223
3258
|
"src/QvdFileReader.js"() {
|
|
3224
3259
|
init_QvdDataFrame();
|
|
@@ -3236,6 +3271,7 @@ var init_QvdFileReader = __esm({
|
|
|
3236
3271
|
MAX_HEADER_SIZE = 16 * 1024 * 1024;
|
|
3237
3272
|
READ_CHUNK_SIZE = 512 * 1024 * 1024;
|
|
3238
3273
|
ANALYSIS_SLICE_ROWS = 65536;
|
|
3274
|
+
COUNT_SYMBOLS_PAST = 65536;
|
|
3239
3275
|
__name(chunksFrom, "chunksFrom");
|
|
3240
3276
|
exports.QvdFileReader = class {
|
|
3241
3277
|
static {
|
|
@@ -3637,6 +3673,7 @@ var init_QvdFileReader = __esm({
|
|
|
3637
3673
|
}
|
|
3638
3674
|
const headerEndIndex = headerDelimiterIndex + HEADER_DELIMITER.length;
|
|
3639
3675
|
const headerBuffer = this._buffer.subarray(headerBeginIndex, headerEndIndex);
|
|
3676
|
+
this._fieldBitMetadataValidated = false;
|
|
3640
3677
|
this._header = await xml2__default.default.parseStringPromise(headerBuffer.toString(), { explicitArray: false });
|
|
3641
3678
|
if (!this._header) {
|
|
3642
3679
|
throw new exports.QvdParseError("The XML header could not be parsed.", {
|
|
@@ -3682,8 +3719,8 @@ var init_QvdFileReader = __esm({
|
|
|
3682
3719
|
* @param {QvdRowWindow} window The rows of interest, as file row indices.
|
|
3683
3720
|
* @param {string} stage Stage name for any error raised here.
|
|
3684
3721
|
* @return {{fields: Array<any>, recordSize: number, totalRows: number, rowsToLoad: number,
|
|
3685
|
-
* indexBuffer: Buffer}} The record geometry. `indexBuffer` starts at the
|
|
3686
|
-
* record, so the decoder always counts from zero.
|
|
3722
|
+
* indexBuffer: Buffer, firstRow: number}} The record geometry. `indexBuffer` starts at the
|
|
3723
|
+
* window's first record, file row `firstRow`, so the decoder always counts from zero.
|
|
3687
3724
|
* @private
|
|
3688
3725
|
*/
|
|
3689
3726
|
_planIndexTable(window, stage) {
|
|
@@ -3729,7 +3766,7 @@ var init_QvdFileReader = __esm({
|
|
|
3729
3766
|
rowsToLoad === 0 || recordSize === 0 || Math.floor(indexBuffer.length / recordSize) >= rowsToLoad,
|
|
3730
3767
|
`The index table holds ${Math.floor(indexBuffer.length / (recordSize || 1))} whole records but ${rowsToLoad} were validated as present.`
|
|
3731
3768
|
);
|
|
3732
|
-
return { fields, recordSize, totalRows, rowsToLoad, indexBuffer };
|
|
3769
|
+
return { fields, recordSize, totalRows, rowsToLoad, indexBuffer, firstRow };
|
|
3733
3770
|
}
|
|
3734
3771
|
/**
|
|
3735
3772
|
* Analyzes the index table to determine which symbols are actually needed.
|
|
@@ -3756,6 +3793,9 @@ var init_QvdFileReader = __esm({
|
|
|
3756
3793
|
const bitOffset = parseInt(field["BitOffset"], 10);
|
|
3757
3794
|
const bitWidth = parseInt(field["BitWidth"], 10);
|
|
3758
3795
|
const bias = parseInt(field["Bias"], 10);
|
|
3796
|
+
const length = parseInt(field["Length"], 10);
|
|
3797
|
+
let indexLimit = Number.isSafeInteger(length) && length >= 0 ? Math.ceil(length / 2) : Infinity;
|
|
3798
|
+
let counted = false;
|
|
3759
3799
|
for (let first = 0; first < rowsToLoad; first += sliceRows) {
|
|
3760
3800
|
const count = Math.min(sliceRows, rowsToLoad - first);
|
|
3761
3801
|
decodeIndexColumn(
|
|
@@ -3768,15 +3808,53 @@ var init_QvdFileReader = __esm({
|
|
|
3768
3808
|
column
|
|
3769
3809
|
);
|
|
3770
3810
|
for (let row = 0; row < count; row++) {
|
|
3771
|
-
if (column[row] >= 0) {
|
|
3811
|
+
if (column[row] >= 0 && column[row] < indexLimit) {
|
|
3772
3812
|
needed.add(column[row]);
|
|
3773
3813
|
}
|
|
3774
3814
|
}
|
|
3815
|
+
if (!counted && needed.size > COUNT_SYMBOLS_PAST) {
|
|
3816
|
+
counted = true;
|
|
3817
|
+
indexLimit = Math.min(indexLimit, this._countFieldSymbols(field));
|
|
3818
|
+
for (const index of needed) {
|
|
3819
|
+
if (index >= indexLimit) {
|
|
3820
|
+
needed.delete(index);
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3775
3824
|
}
|
|
3776
3825
|
this._emitProgress("symbol-analysis", position + 1, fields.length);
|
|
3777
3826
|
});
|
|
3778
3827
|
return symbolUsage;
|
|
3779
3828
|
}
|
|
3829
|
+
/**
|
|
3830
|
+
* How many symbols a field holds, for the symbol-usage pass, which runs before the symbols are parsed.
|
|
3831
|
+
*
|
|
3832
|
+
* The count is `countFieldSymbols`, the parse itself told to decode nothing, so it is the count
|
|
3833
|
+
* `_parseSymbolTable` will produce and `_parseIndexTable` will check against. The field's area is
|
|
3834
|
+
* validated first, as `_parseSymbolTable` would, so a damaged `Offset` or `Length` is reported the
|
|
3835
|
+
* same way wherever it is met.
|
|
3836
|
+
*
|
|
3837
|
+
* @param {any} field The field's header.
|
|
3838
|
+
* @return {number} Its symbols.
|
|
3839
|
+
* @throws {QvdCorruptedError} If the area is not inside the symbol table, or a symbol runs past it.
|
|
3840
|
+
* @private
|
|
3841
|
+
*/
|
|
3842
|
+
_countFieldSymbols(field) {
|
|
3843
|
+
assert3__default.default(
|
|
3844
|
+
this._buffer && this._symbolTableOffset && this._indexTableOffset,
|
|
3845
|
+
"The QVD file has not been read before its symbols were counted."
|
|
3846
|
+
);
|
|
3847
|
+
const symbolBuffer = this._buffer.subarray(this._symbolTableOffset, this._indexTableOffset);
|
|
3848
|
+
validateFieldMetadata(field, symbolBuffer.length, this._path);
|
|
3849
|
+
const offset = parseInt(field["Offset"], 10);
|
|
3850
|
+
return countFieldSymbols(
|
|
3851
|
+
symbolBuffer,
|
|
3852
|
+
offset,
|
|
3853
|
+
offset + parseInt(field["Length"], 10),
|
|
3854
|
+
field["FieldName"],
|
|
3855
|
+
this._path
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3780
3858
|
/**
|
|
3781
3859
|
* Parses the symbol table of the QVD file. This method is part of the parsing process
|
|
3782
3860
|
* and should not be called directly.
|
|
@@ -3862,12 +3940,19 @@ var init_QvdFileReader = __esm({
|
|
|
3862
3940
|
* `base += recordSize`, so decoding rows k to k+n is a question of where the buffer slice starts
|
|
3863
3941
|
* and how many iterations run. Nothing about the decoder changed to support it.
|
|
3864
3942
|
*
|
|
3943
|
+
* Every index is checked as it is decoded. One that addresses neither a symbol of its field nor
|
|
3944
|
+
* NULL throws a `QvdCorruptedError` naming the file row (#125). Here and not where rows or columns
|
|
3945
|
+
* are built, because every read decodes through this method and a columnar read hands its codes
|
|
3946
|
+
* straight to the caller. Rows outside the window are not decoded, so they are not checked.
|
|
3947
|
+
*
|
|
3865
3948
|
* @param {QvdRowWindow} window The rows to decode.
|
|
3949
|
+
* @throws {QvdCorruptedError} If an index in the window addresses neither a symbol nor NULL.
|
|
3866
3950
|
*/
|
|
3867
3951
|
async _parseIndexTable(window) {
|
|
3868
|
-
const { fields, recordSize, rowsToLoad, indexBuffer } = this._planIndexTable(window, "parseIndexTable");
|
|
3869
|
-
this.
|
|
3870
|
-
|
|
3952
|
+
const { fields, recordSize, rowsToLoad, indexBuffer, firstRow } = this._planIndexTable(window, "parseIndexTable");
|
|
3953
|
+
assert3__default.default(this._symbolTable, "The QVD file symbol table has not been parsed.");
|
|
3954
|
+
const symbolTable = this._symbolTable;
|
|
3955
|
+
const columns = fields.map((field, position) => {
|
|
3871
3956
|
this._throwIfAborted();
|
|
3872
3957
|
const column = decodeIndexColumn(
|
|
3873
3958
|
indexBuffer,
|
|
@@ -3876,11 +3961,14 @@ var init_QvdFileReader = __esm({
|
|
|
3876
3961
|
parseInt(field["BitOffset"], 10),
|
|
3877
3962
|
parseInt(field["BitWidth"], 10),
|
|
3878
3963
|
parseInt(field["Bias"], 10),
|
|
3879
|
-
new Int32Array(rowsToLoad)
|
|
3964
|
+
new Int32Array(rowsToLoad),
|
|
3965
|
+
{ symbolCount: symbolTable[position].numbers.length, field: field["FieldName"], file: this._path, firstRow }
|
|
3880
3966
|
);
|
|
3881
3967
|
this._emitProgress("index-table", position + 1, fields.length);
|
|
3882
3968
|
return column;
|
|
3883
3969
|
});
|
|
3970
|
+
this._indexColumns = columns;
|
|
3971
|
+
this._rowsDecoded = rowsToLoad;
|
|
3884
3972
|
}
|
|
3885
3973
|
/**
|
|
3886
3974
|
* Reads the file's schema and header metadata, without touching the symbol or index tables.
|
|
@@ -4009,7 +4097,8 @@ var init_QvdFileReader = __esm({
|
|
|
4009
4097
|
* `chunkSize * 2`, and why the heap it needs is twice what one chunk suggests.
|
|
4010
4098
|
*
|
|
4011
4099
|
* A window covering no rows yields nothing at all, rather than one empty frame - so
|
|
4012
|
-
* `for await` over an exhausted offset does nothing, which is what a paging loop wants.
|
|
4100
|
+
* `for await` over an exhausted offset does nothing, which is what a paging loop wants. Its header
|
|
4101
|
+
* is still checked, as every read of a file's rows checks it.
|
|
4013
4102
|
*
|
|
4014
4103
|
* @param {number|null|{offset?: number, limit?: number|null, maxRows?: number|null}} window
|
|
4015
4104
|
* The rows to cover, in the same spellings `load()` accepts.
|
|
@@ -4027,6 +4116,10 @@ var init_QvdFileReader = __esm({
|
|
|
4027
4116
|
const liveRows = { rows: chunkSize * 2, perChunk: 2 };
|
|
4028
4117
|
const rows = normaliseWindow(window, this._path);
|
|
4029
4118
|
const prepared = await this._prepare(rows, liveRows);
|
|
4119
|
+
if (prepared.rowsAvailable === 0) {
|
|
4120
|
+
this._planIndexTable({ offset: prepared.offset, limit: 0 }, "parseIndexTable");
|
|
4121
|
+
return;
|
|
4122
|
+
}
|
|
4030
4123
|
for (let done = 0; done < prepared.rowsAvailable; done += chunkSize) {
|
|
4031
4124
|
this._throwIfAborted();
|
|
4032
4125
|
const count = Math.min(chunkSize, prepared.rowsAvailable - done);
|