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/dist/index.js
CHANGED
|
@@ -1086,8 +1086,12 @@ function fieldGeometry(bitOffset, bitWidth) {
|
|
|
1086
1086
|
byteCount: bitWidth === 0 ? 0 : shift + bitWidth + 7 >>> 3
|
|
1087
1087
|
};
|
|
1088
1088
|
}
|
|
1089
|
-
function decodeIndexColumn(buffer, recordSize, rowCount, bitOffset, bitWidth, bias, out) {
|
|
1089
|
+
function decodeIndexColumn(buffer, recordSize, rowCount, bitOffset, bitWidth, bias, out, bounds = null) {
|
|
1090
|
+
const symbolCount = bounds === null ? Infinity : bounds.symbolCount;
|
|
1090
1091
|
if (bitWidth === 0) {
|
|
1092
|
+
if (bounds !== null && rowCount > 0 && bias >= symbolCount) {
|
|
1093
|
+
refuse2(bounds, 0, bias, bias);
|
|
1094
|
+
}
|
|
1091
1095
|
out.fill(bias, 0, rowCount);
|
|
1092
1096
|
return out;
|
|
1093
1097
|
}
|
|
@@ -1101,10 +1105,25 @@ function decodeIndexColumn(buffer, recordSize, rowCount, bitOffset, bitWidth, bi
|
|
|
1101
1105
|
if (byteCount > 2) acc += buffer[base + 2] * 65536;
|
|
1102
1106
|
if (byteCount > 3) acc += buffer[base + 3] * 16777216;
|
|
1103
1107
|
if (byteCount > 4) acc += buffer[base + 4] * 4294967296;
|
|
1104
|
-
|
|
1108
|
+
const index = Math.floor(acc / divisor) % modulus + bias;
|
|
1109
|
+
if ((index >= symbolCount || index < 0 && index !== bias) && bounds !== null) {
|
|
1110
|
+
refuse2(bounds, row, index, bias);
|
|
1111
|
+
}
|
|
1112
|
+
out[row] = index;
|
|
1105
1113
|
}
|
|
1106
1114
|
return out;
|
|
1107
1115
|
}
|
|
1116
|
+
function refuse2(bounds, row, index, bias) {
|
|
1117
|
+
throw new QvdCorruptedError("Symbol index out of range", {
|
|
1118
|
+
field: bounds.field,
|
|
1119
|
+
row: bounds.firstRow + row,
|
|
1120
|
+
symbolIndex: index,
|
|
1121
|
+
symbolCount: bounds.symbolCount,
|
|
1122
|
+
bias,
|
|
1123
|
+
file: bounds.file,
|
|
1124
|
+
stage: "parseIndexTable"
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1108
1127
|
function writeBitField(buffer, recordBase, geometry, value) {
|
|
1109
1128
|
const { byteStart, shift, byteCount } = geometry;
|
|
1110
1129
|
if (byteCount === 0) {
|
|
@@ -1121,10 +1140,12 @@ function writeBitField(buffer, recordBase, geometry, value) {
|
|
|
1121
1140
|
var MAX_BIT_WIDTH, POW2;
|
|
1122
1141
|
var init_bitUtils = __esm({
|
|
1123
1142
|
"src/util/bitUtils.js"() {
|
|
1143
|
+
init_QvdErrors();
|
|
1124
1144
|
MAX_BIT_WIDTH = 31;
|
|
1125
1145
|
POW2 = Array.from({ length: 41 }, (_, exponent) => 2 ** exponent);
|
|
1126
1146
|
__name(fieldGeometry, "fieldGeometry");
|
|
1127
1147
|
__name(decodeIndexColumn, "decodeIndexColumn");
|
|
1148
|
+
__name(refuse2, "refuse");
|
|
1128
1149
|
__name(writeBitField, "writeBitField");
|
|
1129
1150
|
}
|
|
1130
1151
|
});
|
|
@@ -2558,6 +2579,15 @@ function validateFieldBitMetadata(field, recordSize, filePath) {
|
|
|
2558
2579
|
stage: "parseIndexTable"
|
|
2559
2580
|
});
|
|
2560
2581
|
}
|
|
2582
|
+
if (bias < -2147483648 || bias + 2 ** bitWidth - 1 > 2 ** 31 - 1) {
|
|
2583
|
+
throw new QvdCorruptedError("Bias out of range", {
|
|
2584
|
+
field: field["FieldName"],
|
|
2585
|
+
bias,
|
|
2586
|
+
bitWidth,
|
|
2587
|
+
file: filePath,
|
|
2588
|
+
stage: "parseIndexTable"
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2561
2591
|
const recordSizeInBits = recordSize * 8;
|
|
2562
2592
|
if (bitOffset + bitWidth > recordSizeInBits) {
|
|
2563
2593
|
throw new QvdCorruptedError("Bit field extends beyond record size", {
|
|
@@ -2685,7 +2715,10 @@ function parseFieldSymbols(symbolBuffer, start, end, keep, fieldName, filePath)
|
|
|
2685
2715
|
}
|
|
2686
2716
|
return { numbers, texts };
|
|
2687
2717
|
}
|
|
2688
|
-
|
|
2718
|
+
function countFieldSymbols(symbolBuffer, start, end, fieldName, filePath) {
|
|
2719
|
+
return parseFieldSymbols(symbolBuffer, start, end, DECODE_NOTHING, fieldName, filePath).numbers.length;
|
|
2720
|
+
}
|
|
2721
|
+
var MAX_TEXT_BYTES, DECODE_NOTHING;
|
|
2689
2722
|
var init_symbolParser = __esm({
|
|
2690
2723
|
"src/util/symbolParser.js"() {
|
|
2691
2724
|
init_QvdErrors();
|
|
@@ -2693,6 +2726,8 @@ var init_symbolParser = __esm({
|
|
|
2693
2726
|
__name(textEnd, "textEnd");
|
|
2694
2727
|
__name(overflow, "overflow");
|
|
2695
2728
|
__name(parseFieldSymbols, "parseFieldSymbols");
|
|
2729
|
+
DECODE_NOTHING = /* @__PURE__ */ new Set();
|
|
2730
|
+
__name(countFieldSymbols, "countFieldSymbols");
|
|
2696
2731
|
}
|
|
2697
2732
|
});
|
|
2698
2733
|
|
|
@@ -3206,7 +3241,7 @@ async function* chunksFrom(handle, chunkSize, failed) {
|
|
|
3206
3241
|
position += bytesRead;
|
|
3207
3242
|
}
|
|
3208
3243
|
}
|
|
3209
|
-
var MAX_HEADER_SIZE, READ_CHUNK_SIZE, ANALYSIS_SLICE_ROWS, QvdFileReader;
|
|
3244
|
+
var MAX_HEADER_SIZE, READ_CHUNK_SIZE, ANALYSIS_SLICE_ROWS, COUNT_SYMBOLS_PAST, QvdFileReader;
|
|
3210
3245
|
var init_QvdFileReader = __esm({
|
|
3211
3246
|
"src/QvdFileReader.js"() {
|
|
3212
3247
|
init_QvdDataFrame();
|
|
@@ -3224,6 +3259,7 @@ var init_QvdFileReader = __esm({
|
|
|
3224
3259
|
MAX_HEADER_SIZE = 16 * 1024 * 1024;
|
|
3225
3260
|
READ_CHUNK_SIZE = 512 * 1024 * 1024;
|
|
3226
3261
|
ANALYSIS_SLICE_ROWS = 65536;
|
|
3262
|
+
COUNT_SYMBOLS_PAST = 65536;
|
|
3227
3263
|
__name(chunksFrom, "chunksFrom");
|
|
3228
3264
|
QvdFileReader = class {
|
|
3229
3265
|
static {
|
|
@@ -3625,6 +3661,7 @@ var init_QvdFileReader = __esm({
|
|
|
3625
3661
|
}
|
|
3626
3662
|
const headerEndIndex = headerDelimiterIndex + HEADER_DELIMITER.length;
|
|
3627
3663
|
const headerBuffer = this._buffer.subarray(headerBeginIndex, headerEndIndex);
|
|
3664
|
+
this._fieldBitMetadataValidated = false;
|
|
3628
3665
|
this._header = await xml2.parseStringPromise(headerBuffer.toString(), { explicitArray: false });
|
|
3629
3666
|
if (!this._header) {
|
|
3630
3667
|
throw new QvdParseError("The XML header could not be parsed.", {
|
|
@@ -3670,8 +3707,8 @@ var init_QvdFileReader = __esm({
|
|
|
3670
3707
|
* @param {QvdRowWindow} window The rows of interest, as file row indices.
|
|
3671
3708
|
* @param {string} stage Stage name for any error raised here.
|
|
3672
3709
|
* @return {{fields: Array<any>, recordSize: number, totalRows: number, rowsToLoad: number,
|
|
3673
|
-
* indexBuffer: Buffer}} The record geometry. `indexBuffer` starts at the
|
|
3674
|
-
* record, so the decoder always counts from zero.
|
|
3710
|
+
* indexBuffer: Buffer, firstRow: number}} The record geometry. `indexBuffer` starts at the
|
|
3711
|
+
* window's first record, file row `firstRow`, so the decoder always counts from zero.
|
|
3675
3712
|
* @private
|
|
3676
3713
|
*/
|
|
3677
3714
|
_planIndexTable(window, stage) {
|
|
@@ -3717,7 +3754,7 @@ var init_QvdFileReader = __esm({
|
|
|
3717
3754
|
rowsToLoad === 0 || recordSize === 0 || Math.floor(indexBuffer.length / recordSize) >= rowsToLoad,
|
|
3718
3755
|
`The index table holds ${Math.floor(indexBuffer.length / (recordSize || 1))} whole records but ${rowsToLoad} were validated as present.`
|
|
3719
3756
|
);
|
|
3720
|
-
return { fields, recordSize, totalRows, rowsToLoad, indexBuffer };
|
|
3757
|
+
return { fields, recordSize, totalRows, rowsToLoad, indexBuffer, firstRow };
|
|
3721
3758
|
}
|
|
3722
3759
|
/**
|
|
3723
3760
|
* Analyzes the index table to determine which symbols are actually needed.
|
|
@@ -3744,6 +3781,9 @@ var init_QvdFileReader = __esm({
|
|
|
3744
3781
|
const bitOffset = parseInt(field["BitOffset"], 10);
|
|
3745
3782
|
const bitWidth = parseInt(field["BitWidth"], 10);
|
|
3746
3783
|
const bias = parseInt(field["Bias"], 10);
|
|
3784
|
+
const length = parseInt(field["Length"], 10);
|
|
3785
|
+
let indexLimit = Number.isSafeInteger(length) && length >= 0 ? Math.ceil(length / 2) : Infinity;
|
|
3786
|
+
let counted = false;
|
|
3747
3787
|
for (let first = 0; first < rowsToLoad; first += sliceRows) {
|
|
3748
3788
|
const count = Math.min(sliceRows, rowsToLoad - first);
|
|
3749
3789
|
decodeIndexColumn(
|
|
@@ -3756,15 +3796,53 @@ var init_QvdFileReader = __esm({
|
|
|
3756
3796
|
column
|
|
3757
3797
|
);
|
|
3758
3798
|
for (let row = 0; row < count; row++) {
|
|
3759
|
-
if (column[row] >= 0) {
|
|
3799
|
+
if (column[row] >= 0 && column[row] < indexLimit) {
|
|
3760
3800
|
needed.add(column[row]);
|
|
3761
3801
|
}
|
|
3762
3802
|
}
|
|
3803
|
+
if (!counted && needed.size > COUNT_SYMBOLS_PAST) {
|
|
3804
|
+
counted = true;
|
|
3805
|
+
indexLimit = Math.min(indexLimit, this._countFieldSymbols(field));
|
|
3806
|
+
for (const index of needed) {
|
|
3807
|
+
if (index >= indexLimit) {
|
|
3808
|
+
needed.delete(index);
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3763
3812
|
}
|
|
3764
3813
|
this._emitProgress("symbol-analysis", position + 1, fields.length);
|
|
3765
3814
|
});
|
|
3766
3815
|
return symbolUsage;
|
|
3767
3816
|
}
|
|
3817
|
+
/**
|
|
3818
|
+
* How many symbols a field holds, for the symbol-usage pass, which runs before the symbols are parsed.
|
|
3819
|
+
*
|
|
3820
|
+
* The count is `countFieldSymbols`, the parse itself told to decode nothing, so it is the count
|
|
3821
|
+
* `_parseSymbolTable` will produce and `_parseIndexTable` will check against. The field's area is
|
|
3822
|
+
* validated first, as `_parseSymbolTable` would, so a damaged `Offset` or `Length` is reported the
|
|
3823
|
+
* same way wherever it is met.
|
|
3824
|
+
*
|
|
3825
|
+
* @param {any} field The field's header.
|
|
3826
|
+
* @return {number} Its symbols.
|
|
3827
|
+
* @throws {QvdCorruptedError} If the area is not inside the symbol table, or a symbol runs past it.
|
|
3828
|
+
* @private
|
|
3829
|
+
*/
|
|
3830
|
+
_countFieldSymbols(field) {
|
|
3831
|
+
assert3(
|
|
3832
|
+
this._buffer && this._symbolTableOffset && this._indexTableOffset,
|
|
3833
|
+
"The QVD file has not been read before its symbols were counted."
|
|
3834
|
+
);
|
|
3835
|
+
const symbolBuffer = this._buffer.subarray(this._symbolTableOffset, this._indexTableOffset);
|
|
3836
|
+
validateFieldMetadata(field, symbolBuffer.length, this._path);
|
|
3837
|
+
const offset = parseInt(field["Offset"], 10);
|
|
3838
|
+
return countFieldSymbols(
|
|
3839
|
+
symbolBuffer,
|
|
3840
|
+
offset,
|
|
3841
|
+
offset + parseInt(field["Length"], 10),
|
|
3842
|
+
field["FieldName"],
|
|
3843
|
+
this._path
|
|
3844
|
+
);
|
|
3845
|
+
}
|
|
3768
3846
|
/**
|
|
3769
3847
|
* Parses the symbol table of the QVD file. This method is part of the parsing process
|
|
3770
3848
|
* and should not be called directly.
|
|
@@ -3850,12 +3928,19 @@ var init_QvdFileReader = __esm({
|
|
|
3850
3928
|
* `base += recordSize`, so decoding rows k to k+n is a question of where the buffer slice starts
|
|
3851
3929
|
* and how many iterations run. Nothing about the decoder changed to support it.
|
|
3852
3930
|
*
|
|
3931
|
+
* Every index is checked as it is decoded. One that addresses neither a symbol of its field nor
|
|
3932
|
+
* NULL throws a `QvdCorruptedError` naming the file row (#125). Here and not where rows or columns
|
|
3933
|
+
* are built, because every read decodes through this method and a columnar read hands its codes
|
|
3934
|
+
* straight to the caller. Rows outside the window are not decoded, so they are not checked.
|
|
3935
|
+
*
|
|
3853
3936
|
* @param {QvdRowWindow} window The rows to decode.
|
|
3937
|
+
* @throws {QvdCorruptedError} If an index in the window addresses neither a symbol nor NULL.
|
|
3854
3938
|
*/
|
|
3855
3939
|
async _parseIndexTable(window) {
|
|
3856
|
-
const { fields, recordSize, rowsToLoad, indexBuffer } = this._planIndexTable(window, "parseIndexTable");
|
|
3857
|
-
this.
|
|
3858
|
-
|
|
3940
|
+
const { fields, recordSize, rowsToLoad, indexBuffer, firstRow } = this._planIndexTable(window, "parseIndexTable");
|
|
3941
|
+
assert3(this._symbolTable, "The QVD file symbol table has not been parsed.");
|
|
3942
|
+
const symbolTable = this._symbolTable;
|
|
3943
|
+
const columns = fields.map((field, position) => {
|
|
3859
3944
|
this._throwIfAborted();
|
|
3860
3945
|
const column = decodeIndexColumn(
|
|
3861
3946
|
indexBuffer,
|
|
@@ -3864,11 +3949,14 @@ var init_QvdFileReader = __esm({
|
|
|
3864
3949
|
parseInt(field["BitOffset"], 10),
|
|
3865
3950
|
parseInt(field["BitWidth"], 10),
|
|
3866
3951
|
parseInt(field["Bias"], 10),
|
|
3867
|
-
new Int32Array(rowsToLoad)
|
|
3952
|
+
new Int32Array(rowsToLoad),
|
|
3953
|
+
{ symbolCount: symbolTable[position].numbers.length, field: field["FieldName"], file: this._path, firstRow }
|
|
3868
3954
|
);
|
|
3869
3955
|
this._emitProgress("index-table", position + 1, fields.length);
|
|
3870
3956
|
return column;
|
|
3871
3957
|
});
|
|
3958
|
+
this._indexColumns = columns;
|
|
3959
|
+
this._rowsDecoded = rowsToLoad;
|
|
3872
3960
|
}
|
|
3873
3961
|
/**
|
|
3874
3962
|
* Reads the file's schema and header metadata, without touching the symbol or index tables.
|
|
@@ -3997,7 +4085,8 @@ var init_QvdFileReader = __esm({
|
|
|
3997
4085
|
* `chunkSize * 2`, and why the heap it needs is twice what one chunk suggests.
|
|
3998
4086
|
*
|
|
3999
4087
|
* A window covering no rows yields nothing at all, rather than one empty frame - so
|
|
4000
|
-
* `for await` over an exhausted offset does nothing, which is what a paging loop wants.
|
|
4088
|
+
* `for await` over an exhausted offset does nothing, which is what a paging loop wants. Its header
|
|
4089
|
+
* is still checked, as every read of a file's rows checks it.
|
|
4001
4090
|
*
|
|
4002
4091
|
* @param {number|null|{offset?: number, limit?: number|null, maxRows?: number|null}} window
|
|
4003
4092
|
* The rows to cover, in the same spellings `load()` accepts.
|
|
@@ -4015,6 +4104,10 @@ var init_QvdFileReader = __esm({
|
|
|
4015
4104
|
const liveRows = { rows: chunkSize * 2, perChunk: 2 };
|
|
4016
4105
|
const rows = normaliseWindow(window, this._path);
|
|
4017
4106
|
const prepared = await this._prepare(rows, liveRows);
|
|
4107
|
+
if (prepared.rowsAvailable === 0) {
|
|
4108
|
+
this._planIndexTable({ offset: prepared.offset, limit: 0 }, "parseIndexTable");
|
|
4109
|
+
return;
|
|
4110
|
+
}
|
|
4018
4111
|
for (let done = 0; done < prepared.rowsAvailable; done += chunkSize) {
|
|
4019
4112
|
this._throwIfAborted();
|
|
4020
4113
|
const count = Math.min(chunkSize, prepared.rowsAvailable - done);
|