qvdjs 0.6.2 → 0.7.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/README.md +121 -6
- package/dist/index.cjs +617 -276
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +616 -276
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ var fs2 = require('fs');
|
|
|
5
5
|
var crypto = require('crypto');
|
|
6
6
|
var xml2 = require('xml2js');
|
|
7
7
|
var assert = require('assert');
|
|
8
|
+
var os = require('os');
|
|
8
9
|
|
|
9
10
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
11
|
|
|
@@ -13,6 +14,7 @@ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
|
|
|
13
14
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
14
15
|
var xml2__default = /*#__PURE__*/_interopDefault(xml2);
|
|
15
16
|
var assert__default = /*#__PURE__*/_interopDefault(assert);
|
|
17
|
+
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
16
18
|
|
|
17
19
|
var __defProp = Object.defineProperty;
|
|
18
20
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -645,6 +647,499 @@ var init_QvdFileWriter = __esm({
|
|
|
645
647
|
}
|
|
646
648
|
});
|
|
647
649
|
|
|
650
|
+
// src/util/bitUtils.js
|
|
651
|
+
function convertBitsToInt32(bits) {
|
|
652
|
+
if (bits.length === 0) {
|
|
653
|
+
return 0;
|
|
654
|
+
}
|
|
655
|
+
return bits.reduce((value, bit, index) => value += bit * Math.pow(2, index), 0);
|
|
656
|
+
}
|
|
657
|
+
var init_bitUtils = __esm({
|
|
658
|
+
"src/util/bitUtils.js"() {
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
function estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
|
|
662
|
+
const FULL_PARSE_OVERHEAD = 6;
|
|
663
|
+
const MINIMAL_OVERHEAD = 0.01;
|
|
664
|
+
if (maxRows === null || maxRows >= totalRows) {
|
|
665
|
+
return symbolTableSize * FULL_PARSE_OVERHEAD;
|
|
666
|
+
}
|
|
667
|
+
const rowPercentage = maxRows / totalRows;
|
|
668
|
+
const symbolPercentage = Math.sqrt(rowPercentage);
|
|
669
|
+
const keptSymbolsMemory = symbolTableSize * symbolPercentage * FULL_PARSE_OVERHEAD;
|
|
670
|
+
const skippedSymbolsMemory = symbolTableSize * (1 - symbolPercentage) * MINIMAL_OVERHEAD;
|
|
671
|
+
return keptSymbolsMemory + skippedSymbolsMemory;
|
|
672
|
+
}
|
|
673
|
+
function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePath) {
|
|
674
|
+
const availableMemory = os__default.default.freemem();
|
|
675
|
+
const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
|
|
676
|
+
const MEMORY_SAFETY_FACTOR = 0.3;
|
|
677
|
+
const maxAllowedMemory = availableMemory * MEMORY_SAFETY_FACTOR;
|
|
678
|
+
if (estimatedMemory > maxAllowedMemory) {
|
|
679
|
+
const safeSymbolPercentage = maxAllowedMemory / (symbolTableSize * 6);
|
|
680
|
+
const safeRowPercentage = Math.pow(safeSymbolPercentage, 2);
|
|
681
|
+
const recommendedMaxRows = Math.max(1, Math.floor(totalRows * safeRowPercentage));
|
|
682
|
+
const sizeMB = Math.round(symbolTableSize / 1024 / 1024);
|
|
683
|
+
const estimatedMB = Math.round(estimatedMemory / 1024 / 1024);
|
|
684
|
+
const availableMB = Math.round(maxAllowedMemory / 1024 / 1024);
|
|
685
|
+
throw new exports.QvdValidationError(
|
|
686
|
+
`Insufficient memory to load file safely. Symbol table: ${sizeMB}MB, Estimated memory needed: ${estimatedMB}MB, Available: ${availableMB}MB. Try loading fewer rows using the maxRows parameter (recommended: ${recommendedMaxRows.toLocaleString()} rows or less).`,
|
|
687
|
+
{
|
|
688
|
+
file: filePath,
|
|
689
|
+
symbolTableSize,
|
|
690
|
+
symbolTableSizeMB: sizeMB,
|
|
691
|
+
estimatedMemoryMB: estimatedMB,
|
|
692
|
+
availableMemoryMB: availableMB,
|
|
693
|
+
totalRows,
|
|
694
|
+
maxRows,
|
|
695
|
+
recommendedMaxRows
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function warnLargeSymbolTable(symbolTableSize, maxRows, totalRows) {
|
|
701
|
+
const LARGE_SYMBOL_TABLE_WARNING = 500 * 1024 * 1024;
|
|
702
|
+
if (symbolTableSize > LARGE_SYMBOL_TABLE_WARNING && (maxRows === null || maxRows >= totalRows)) {
|
|
703
|
+
const sizeMB = Math.round(symbolTableSize / 1024 / 1024);
|
|
704
|
+
const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
|
|
705
|
+
const estimatedMB = Math.round(estimatedMemory / 1024 / 1024);
|
|
706
|
+
console.warn(
|
|
707
|
+
`\u26A0\uFE0F Large symbol table detected (${sizeMB}MB). Loading all ${totalRows.toLocaleString()} rows will use ~${estimatedMB}MB RAM. Consider using the maxRows parameter for better performance and lower memory usage.`
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
var init_memoryUtils = __esm({
|
|
712
|
+
"src/util/memoryUtils.js"() {
|
|
713
|
+
init_QvdErrors();
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
// src/util/validationUtils.js
|
|
718
|
+
function validateSymbolTableSizeEarly(symbolTableLength, filePath) {
|
|
719
|
+
const MAX_SYMBOL_TABLE_SIZE = 500 * 1024 * 1024;
|
|
720
|
+
if (symbolTableLength > MAX_SYMBOL_TABLE_SIZE) {
|
|
721
|
+
const sizeMB = Math.round(symbolTableLength / 1024 / 1024);
|
|
722
|
+
const maxMB = Math.round(MAX_SYMBOL_TABLE_SIZE / 1024 / 1024);
|
|
723
|
+
throw new exports.QvdValidationError(
|
|
724
|
+
`Symbol table too large (${sizeMB}MB exceeds ${maxMB}MB limit). This QVD file contains extremely high-cardinality fields. Even with symbol filtering, this file cannot be loaded efficiently. Consider: (1) using a subset of the data, (2) aggregating high-cardinality fields, or (3) using a different data format.`,
|
|
725
|
+
{
|
|
726
|
+
file: filePath,
|
|
727
|
+
symbolTableSize: symbolTableLength,
|
|
728
|
+
symbolTableSizeMB: sizeMB,
|
|
729
|
+
maxAllowed: MAX_SYMBOL_TABLE_SIZE,
|
|
730
|
+
maxAllowedMB: maxMB
|
|
731
|
+
}
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
function validateSymbolTableSize(symbolTableLength, filePath, totalRows) {
|
|
736
|
+
const ABSOLUTE_MAX_SYMBOL_TABLE = 2 * 1024 * 1024 * 1024;
|
|
737
|
+
if (symbolTableLength > ABSOLUTE_MAX_SYMBOL_TABLE) {
|
|
738
|
+
const sizeMB = Math.round(symbolTableLength / 1024 / 1024);
|
|
739
|
+
const maxMB = Math.round(ABSOLUTE_MAX_SYMBOL_TABLE / 1024 / 1024);
|
|
740
|
+
throw new exports.QvdValidationError(
|
|
741
|
+
`Symbol table exceeds absolute maximum size (${sizeMB}MB > ${maxMB}MB). This QVD file has pathological cardinality (likely a data modeling issue). Even with Phase 2.5 optimization, files this large cannot be processed safely. Consider: (1) aggregating high-cardinality fields, (2) splitting the data, or (3) using a different format.`,
|
|
742
|
+
{
|
|
743
|
+
file: filePath,
|
|
744
|
+
symbolTableSize: symbolTableLength,
|
|
745
|
+
symbolTableSizeMB: sizeMB,
|
|
746
|
+
maxAllowed: ABSOLUTE_MAX_SYMBOL_TABLE,
|
|
747
|
+
maxAllowedMB: maxMB,
|
|
748
|
+
totalRows
|
|
749
|
+
}
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function validateFieldMetadata(field, symbolBufferLength, filePath) {
|
|
754
|
+
const symbolsOffset = parseInt(field["Offset"], 10);
|
|
755
|
+
const symbolsLength = parseInt(field["Length"], 10);
|
|
756
|
+
if (isNaN(symbolsOffset) || !Number.isSafeInteger(symbolsOffset) || symbolsOffset < 0) {
|
|
757
|
+
throw new exports.QvdCorruptedError("Invalid symbol offset", {
|
|
758
|
+
field: field["FieldName"],
|
|
759
|
+
offset: symbolsOffset,
|
|
760
|
+
file: filePath,
|
|
761
|
+
stage: "parseSymbolTable"
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
if (isNaN(symbolsLength) || !Number.isSafeInteger(symbolsLength) || symbolsLength < 0) {
|
|
765
|
+
throw new exports.QvdCorruptedError("Invalid symbol length", {
|
|
766
|
+
field: field["FieldName"],
|
|
767
|
+
length: symbolsLength,
|
|
768
|
+
file: filePath,
|
|
769
|
+
stage: "parseSymbolTable"
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
if (symbolsOffset + symbolsLength > symbolBufferLength) {
|
|
773
|
+
throw new exports.QvdCorruptedError("Symbol data extends beyond buffer", {
|
|
774
|
+
field: field["FieldName"],
|
|
775
|
+
offset: symbolsOffset,
|
|
776
|
+
length: symbolsLength,
|
|
777
|
+
bufferSize: symbolBufferLength,
|
|
778
|
+
file: filePath,
|
|
779
|
+
stage: "parseSymbolTable"
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function validateIndexTableMetadata(recordSize, totalRows, indexTableLength, indexTableOffset, bufferLength, rowsToLoad, filePath) {
|
|
784
|
+
if (isNaN(recordSize) || !Number.isSafeInteger(recordSize) || recordSize < 0) {
|
|
785
|
+
throw new exports.QvdCorruptedError("Invalid record byte size", {
|
|
786
|
+
recordSize,
|
|
787
|
+
file: filePath,
|
|
788
|
+
stage: "parseIndexTable"
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
if (isNaN(totalRows) || !Number.isSafeInteger(totalRows) || totalRows < 0) {
|
|
792
|
+
throw new exports.QvdCorruptedError("Invalid number of records", {
|
|
793
|
+
totalRows,
|
|
794
|
+
file: filePath,
|
|
795
|
+
stage: "parseIndexTable"
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
if (recordSize === 0 && totalRows > 0) {
|
|
799
|
+
throw new exports.QvdCorruptedError("Record byte size cannot be zero when records exist", {
|
|
800
|
+
recordSize,
|
|
801
|
+
totalRows,
|
|
802
|
+
file: filePath,
|
|
803
|
+
stage: "parseIndexTable"
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
if (recordSize > 1048576) {
|
|
807
|
+
throw new exports.QvdCorruptedError("Record byte size exceeds maximum", {
|
|
808
|
+
recordSize,
|
|
809
|
+
maxSize: 1048576,
|
|
810
|
+
file: filePath,
|
|
811
|
+
stage: "parseIndexTable"
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
if (isNaN(indexTableLength) || !Number.isSafeInteger(indexTableLength) || indexTableLength < 0) {
|
|
815
|
+
throw new exports.QvdCorruptedError("Invalid index table length", {
|
|
816
|
+
length: indexTableLength,
|
|
817
|
+
file: filePath,
|
|
818
|
+
stage: "parseIndexTable"
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
if (indexTableOffset > bufferLength) {
|
|
822
|
+
throw new exports.QvdCorruptedError("Index table offset beyond buffer", {
|
|
823
|
+
indexTableOffset,
|
|
824
|
+
bufferSize: bufferLength,
|
|
825
|
+
file: filePath,
|
|
826
|
+
stage: "parseIndexTable"
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
const maxReasonableOverage = 100 * 1024 * 1024;
|
|
830
|
+
if (indexTableLength > bufferLength + maxReasonableOverage) {
|
|
831
|
+
throw new exports.QvdCorruptedError("Index table length unreasonably large", {
|
|
832
|
+
indexTableLength,
|
|
833
|
+
bufferSize: bufferLength,
|
|
834
|
+
file: filePath,
|
|
835
|
+
stage: "parseIndexTable"
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
const requiredIndexBytes = rowsToLoad * recordSize;
|
|
839
|
+
if (indexTableLength < requiredIndexBytes) {
|
|
840
|
+
throw new exports.QvdCorruptedError("Index table length smaller than required", {
|
|
841
|
+
indexTableLength,
|
|
842
|
+
requiredBytes: requiredIndexBytes,
|
|
843
|
+
rowsToLoad,
|
|
844
|
+
recordSize,
|
|
845
|
+
file: filePath,
|
|
846
|
+
stage: "parseIndexTable"
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
function validateFieldBitMetadata(field, recordSize, filePath) {
|
|
851
|
+
const bitOffset = parseInt(field["BitOffset"], 10);
|
|
852
|
+
const bitWidth = parseInt(field["BitWidth"], 10);
|
|
853
|
+
if (isNaN(bitOffset) || !Number.isSafeInteger(bitOffset) || bitOffset < 0) {
|
|
854
|
+
throw new exports.QvdCorruptedError("Invalid bit offset", {
|
|
855
|
+
field: field["FieldName"],
|
|
856
|
+
bitOffset,
|
|
857
|
+
file: filePath,
|
|
858
|
+
stage: "parseIndexTable"
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
if (isNaN(bitWidth) || !Number.isSafeInteger(bitWidth) || bitWidth < 0) {
|
|
862
|
+
throw new exports.QvdCorruptedError("Invalid bit width", {
|
|
863
|
+
field: field["FieldName"],
|
|
864
|
+
bitWidth,
|
|
865
|
+
file: filePath,
|
|
866
|
+
stage: "parseIndexTable"
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
const recordSizeInBits = recordSize * 8;
|
|
870
|
+
if (bitOffset + bitWidth > recordSizeInBits) {
|
|
871
|
+
throw new exports.QvdCorruptedError("Bit field extends beyond record size", {
|
|
872
|
+
field: field["FieldName"],
|
|
873
|
+
bitOffset,
|
|
874
|
+
bitWidth,
|
|
875
|
+
recordSizeInBits,
|
|
876
|
+
file: filePath,
|
|
877
|
+
stage: "parseIndexTable"
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
var init_validationUtils = __esm({
|
|
882
|
+
"src/util/validationUtils.js"() {
|
|
883
|
+
init_QvdErrors();
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// src/util/symbolParser.js
|
|
888
|
+
function parseIntegerSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
|
|
889
|
+
if (pointer + 4 > bufferLength) {
|
|
890
|
+
throw new exports.QvdCorruptedError("Buffer overflow reading integer symbol", {
|
|
891
|
+
field: fieldName,
|
|
892
|
+
pointer,
|
|
893
|
+
bufferSize: bufferLength,
|
|
894
|
+
file: filePath,
|
|
895
|
+
stage: "parseSymbolTable"
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
const byteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 4));
|
|
899
|
+
const value = Buffer.from(byteData).readIntLE(0, byteData.length);
|
|
900
|
+
return { symbol: exports.QvdSymbol.fromIntValue(value), bytesRead: 4 };
|
|
901
|
+
}
|
|
902
|
+
function parseDoubleSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
|
|
903
|
+
if (pointer + 8 > bufferLength) {
|
|
904
|
+
throw new exports.QvdCorruptedError("Buffer overflow reading double symbol", {
|
|
905
|
+
field: fieldName,
|
|
906
|
+
pointer,
|
|
907
|
+
bufferSize: bufferLength,
|
|
908
|
+
file: filePath,
|
|
909
|
+
stage: "parseSymbolTable"
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
const byteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 8));
|
|
913
|
+
const value = Buffer.from(byteData).readDoubleLE(0);
|
|
914
|
+
return { symbol: exports.QvdSymbol.fromDoubleValue(value), bytesRead: 8 };
|
|
915
|
+
}
|
|
916
|
+
function parseStringSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
|
|
917
|
+
const startPointer = pointer;
|
|
918
|
+
const maxStringLength = 1048576;
|
|
919
|
+
let stringLength = 0;
|
|
920
|
+
while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
|
|
921
|
+
if (stringLength >= maxStringLength) {
|
|
922
|
+
throw new exports.QvdCorruptedError("String symbol exceeds maximum length", {
|
|
923
|
+
field: fieldName,
|
|
924
|
+
maxLength: maxStringLength,
|
|
925
|
+
file: filePath,
|
|
926
|
+
stage: "parseSymbolTable"
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
pointer++;
|
|
930
|
+
stringLength++;
|
|
931
|
+
}
|
|
932
|
+
if (pointer >= bufferLength) {
|
|
933
|
+
throw new exports.QvdCorruptedError("String symbol not null-terminated", {
|
|
934
|
+
field: fieldName,
|
|
935
|
+
pointer,
|
|
936
|
+
bufferSize: bufferLength,
|
|
937
|
+
file: filePath,
|
|
938
|
+
stage: "parseSymbolTable"
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
const value = symbolBuffer.subarray(startPointer, pointer).toString("utf-8");
|
|
942
|
+
return { symbol: exports.QvdSymbol.fromStringValue(value), bytesRead: pointer - startPointer + 1 };
|
|
943
|
+
}
|
|
944
|
+
function skipStringSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
|
|
945
|
+
const startPointer = pointer;
|
|
946
|
+
const maxStringLength = 1048576;
|
|
947
|
+
let stringLength = 0;
|
|
948
|
+
while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
|
|
949
|
+
if (stringLength >= maxStringLength) {
|
|
950
|
+
throw new exports.QvdCorruptedError("String symbol exceeds maximum length", {
|
|
951
|
+
field: fieldName,
|
|
952
|
+
maxLength: maxStringLength,
|
|
953
|
+
file: filePath,
|
|
954
|
+
stage: "parseSymbolTable"
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
pointer++;
|
|
958
|
+
stringLength++;
|
|
959
|
+
}
|
|
960
|
+
if (pointer >= bufferLength) {
|
|
961
|
+
throw new exports.QvdCorruptedError("String symbol not null-terminated", {
|
|
962
|
+
field: fieldName,
|
|
963
|
+
pointer,
|
|
964
|
+
bufferSize: bufferLength,
|
|
965
|
+
file: filePath,
|
|
966
|
+
stage: "parseSymbolTable"
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
return pointer - startPointer + 1;
|
|
970
|
+
}
|
|
971
|
+
function parseDualIntegerSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
|
|
972
|
+
if (pointer + 4 > bufferLength) {
|
|
973
|
+
throw new exports.QvdCorruptedError("Buffer overflow reading dual integer symbol", {
|
|
974
|
+
field: fieldName,
|
|
975
|
+
pointer,
|
|
976
|
+
bufferSize: bufferLength,
|
|
977
|
+
file: filePath,
|
|
978
|
+
stage: "parseSymbolTable"
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
const intByteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 4));
|
|
982
|
+
const intValue = Buffer.from(intByteData).readIntLE(0, intByteData.length);
|
|
983
|
+
pointer += 4;
|
|
984
|
+
const stringStart = pointer;
|
|
985
|
+
const maxStringLength = 1048576;
|
|
986
|
+
let stringLength = 0;
|
|
987
|
+
while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
|
|
988
|
+
if (stringLength >= maxStringLength) {
|
|
989
|
+
throw new exports.QvdCorruptedError("Dual string symbol exceeds maximum length", {
|
|
990
|
+
field: fieldName,
|
|
991
|
+
maxLength: maxStringLength,
|
|
992
|
+
file: filePath,
|
|
993
|
+
stage: "parseSymbolTable"
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
pointer++;
|
|
997
|
+
stringLength++;
|
|
998
|
+
}
|
|
999
|
+
if (pointer >= bufferLength) {
|
|
1000
|
+
throw new exports.QvdCorruptedError("Dual string symbol not null-terminated", {
|
|
1001
|
+
field: fieldName,
|
|
1002
|
+
pointer,
|
|
1003
|
+
bufferSize: bufferLength,
|
|
1004
|
+
file: filePath,
|
|
1005
|
+
stage: "parseSymbolTable"
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
const stringValue = symbolBuffer.subarray(stringStart, pointer).toString("utf-8");
|
|
1009
|
+
return { symbol: exports.QvdSymbol.fromDualIntValue(intValue, stringValue), bytesRead: pointer - (stringStart - 4) + 1 };
|
|
1010
|
+
}
|
|
1011
|
+
function parseDualDoubleSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
|
|
1012
|
+
if (pointer + 8 > bufferLength) {
|
|
1013
|
+
throw new exports.QvdCorruptedError("Buffer overflow reading dual double symbol", {
|
|
1014
|
+
field: fieldName,
|
|
1015
|
+
pointer,
|
|
1016
|
+
bufferSize: bufferLength,
|
|
1017
|
+
file: filePath,
|
|
1018
|
+
stage: "parseSymbolTable"
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
const doubleByteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 8));
|
|
1022
|
+
const doubleValue = Buffer.from(doubleByteData).readDoubleLE(0);
|
|
1023
|
+
pointer += 8;
|
|
1024
|
+
const stringStart = pointer;
|
|
1025
|
+
const maxStringLength = 1048576;
|
|
1026
|
+
let stringLength = 0;
|
|
1027
|
+
while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
|
|
1028
|
+
if (stringLength >= maxStringLength) {
|
|
1029
|
+
throw new exports.QvdCorruptedError("Dual string symbol exceeds maximum length", {
|
|
1030
|
+
field: fieldName,
|
|
1031
|
+
maxLength: maxStringLength,
|
|
1032
|
+
file: filePath,
|
|
1033
|
+
stage: "parseSymbolTable"
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
pointer++;
|
|
1037
|
+
stringLength++;
|
|
1038
|
+
}
|
|
1039
|
+
if (pointer >= bufferLength) {
|
|
1040
|
+
throw new exports.QvdCorruptedError("Dual string symbol not null-terminated", {
|
|
1041
|
+
field: fieldName,
|
|
1042
|
+
pointer,
|
|
1043
|
+
bufferSize: bufferLength,
|
|
1044
|
+
file: filePath,
|
|
1045
|
+
stage: "parseSymbolTable"
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
const stringValue = symbolBuffer.subarray(stringStart, pointer).toString("utf-8");
|
|
1049
|
+
return {
|
|
1050
|
+
symbol: exports.QvdSymbol.fromDualDoubleValue(doubleValue, stringValue),
|
|
1051
|
+
bytesRead: pointer - (stringStart - 8) + 1
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function skipDualSymbol(symbolBuffer, pointer, bufferLength, numericBytes, fieldName, filePath) {
|
|
1055
|
+
if (pointer + numericBytes > bufferLength) {
|
|
1056
|
+
throw new exports.QvdCorruptedError(`Buffer overflow reading dual symbol`, {
|
|
1057
|
+
field: fieldName,
|
|
1058
|
+
pointer,
|
|
1059
|
+
bufferSize: bufferLength,
|
|
1060
|
+
file: filePath,
|
|
1061
|
+
stage: "parseSymbolTable"
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
const startPointer = pointer;
|
|
1065
|
+
pointer += numericBytes;
|
|
1066
|
+
const maxStringLength = 1048576;
|
|
1067
|
+
let stringLength = 0;
|
|
1068
|
+
while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
|
|
1069
|
+
if (stringLength >= maxStringLength) {
|
|
1070
|
+
throw new exports.QvdCorruptedError("Dual string symbol exceeds maximum length", {
|
|
1071
|
+
field: fieldName,
|
|
1072
|
+
maxLength: maxStringLength,
|
|
1073
|
+
file: filePath,
|
|
1074
|
+
stage: "parseSymbolTable"
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
pointer++;
|
|
1078
|
+
stringLength++;
|
|
1079
|
+
}
|
|
1080
|
+
if (pointer >= bufferLength) {
|
|
1081
|
+
throw new exports.QvdCorruptedError("Dual string symbol not null-terminated", {
|
|
1082
|
+
field: fieldName,
|
|
1083
|
+
pointer,
|
|
1084
|
+
bufferSize: bufferLength,
|
|
1085
|
+
file: filePath,
|
|
1086
|
+
stage: "parseSymbolTable"
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
return pointer - startPointer + 1;
|
|
1090
|
+
}
|
|
1091
|
+
function parseSymbol(typeByte, symbolBuffer, pointer, bufferLength, fieldName, filePath, shouldParse) {
|
|
1092
|
+
switch (typeByte) {
|
|
1093
|
+
case 1: {
|
|
1094
|
+
if (shouldParse) {
|
|
1095
|
+
return parseIntegerSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
|
|
1096
|
+
}
|
|
1097
|
+
return { symbol: null, bytesRead: 4 };
|
|
1098
|
+
}
|
|
1099
|
+
case 2: {
|
|
1100
|
+
if (shouldParse) {
|
|
1101
|
+
return parseDoubleSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
|
|
1102
|
+
}
|
|
1103
|
+
return { symbol: null, bytesRead: 8 };
|
|
1104
|
+
}
|
|
1105
|
+
case 4: {
|
|
1106
|
+
if (shouldParse) {
|
|
1107
|
+
return parseStringSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
|
|
1108
|
+
}
|
|
1109
|
+
const bytesRead = skipStringSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
|
|
1110
|
+
return { symbol: null, bytesRead };
|
|
1111
|
+
}
|
|
1112
|
+
case 5: {
|
|
1113
|
+
if (shouldParse) {
|
|
1114
|
+
return parseDualIntegerSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
|
|
1115
|
+
}
|
|
1116
|
+
const bytesRead = skipDualSymbol(symbolBuffer, pointer, bufferLength, 4, fieldName, filePath);
|
|
1117
|
+
return { symbol: null, bytesRead };
|
|
1118
|
+
}
|
|
1119
|
+
case 6: {
|
|
1120
|
+
if (shouldParse) {
|
|
1121
|
+
return parseDualDoubleSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
|
|
1122
|
+
}
|
|
1123
|
+
const bytesRead = skipDualSymbol(symbolBuffer, pointer, bufferLength, 8, fieldName, filePath);
|
|
1124
|
+
return { symbol: null, bytesRead };
|
|
1125
|
+
}
|
|
1126
|
+
default: {
|
|
1127
|
+
throw new exports.QvdParseError("Unknown symbol type byte", {
|
|
1128
|
+
typeByte: typeByte.toString(16),
|
|
1129
|
+
offset: pointer - 1,
|
|
1130
|
+
file: filePath,
|
|
1131
|
+
stage: "parseSymbolTable"
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
var init_symbolParser = __esm({
|
|
1137
|
+
"src/util/symbolParser.js"() {
|
|
1138
|
+
init_QvdSymbol();
|
|
1139
|
+
init_QvdErrors();
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
|
|
648
1143
|
// src/QvdFileReader.js
|
|
649
1144
|
var QvdFileReader_exports = {};
|
|
650
1145
|
__export(QvdFileReader_exports, {
|
|
@@ -653,10 +1148,13 @@ __export(QvdFileReader_exports, {
|
|
|
653
1148
|
exports.QvdFileReader = void 0;
|
|
654
1149
|
var init_QvdFileReader = __esm({
|
|
655
1150
|
"src/QvdFileReader.js"() {
|
|
656
|
-
init_QvdSymbol();
|
|
657
1151
|
init_QvdDataFrame();
|
|
658
1152
|
init_QvdErrors();
|
|
659
1153
|
init_validatePath();
|
|
1154
|
+
init_bitUtils();
|
|
1155
|
+
init_memoryUtils();
|
|
1156
|
+
init_validationUtils();
|
|
1157
|
+
init_symbolParser();
|
|
660
1158
|
exports.QvdFileReader = class {
|
|
661
1159
|
/**
|
|
662
1160
|
* Constructs a new QVD file parser.
|
|
@@ -754,6 +1252,7 @@ var init_QvdFileReader = __esm({
|
|
|
754
1252
|
const recordSize = parseInt(headerObj["QvdTableHeader"]["RecordByteSize"], 10);
|
|
755
1253
|
const totalRows = parseInt(headerObj["QvdTableHeader"]["NoOfRecords"], 10);
|
|
756
1254
|
const rowsToLoad = Math.min(maxRows, totalRows);
|
|
1255
|
+
validateSymbolTableSizeEarly(symbolTableLength, this._path);
|
|
757
1256
|
const indexTableBytesToRead = rowsToLoad * recordSize;
|
|
758
1257
|
const totalBytesToRead = indexTableOffset + indexTableBytesToRead;
|
|
759
1258
|
const fd = await fs2__default.default.promises.open(this._path, "r");
|
|
@@ -803,11 +1302,80 @@ var init_QvdFileReader = __esm({
|
|
|
803
1302
|
this._symbolTableOffset = headerEndIndex;
|
|
804
1303
|
this._indexTableOffset = this._symbolTableOffset + parseInt(this._header["QvdTableHeader"]["Offset"], 10);
|
|
805
1304
|
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Estimates the memory usage for loading a QVD file with given parameters.
|
|
1307
|
+
* Takes into account Phase 2.5 optimization which skips parsing unused symbols.
|
|
1308
|
+
*
|
|
1309
|
+
* @param {number} symbolTableSize Size of symbol table in bytes
|
|
1310
|
+
* @param {number|null} maxRows Maximum number of rows to load (null = all rows)
|
|
1311
|
+
* @param {number} totalRows Total number of rows in the file
|
|
1312
|
+
* @returns {number} Estimated memory usage in bytes
|
|
1313
|
+
* @private
|
|
1314
|
+
*/
|
|
1315
|
+
_estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
|
|
1316
|
+
return estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Analyzes the index table to determine which symbols are actually needed.
|
|
1320
|
+
* This is used for two-pass symbol filtering optimization.
|
|
1321
|
+
*
|
|
1322
|
+
* @param {number} maxRows The maximum number of rows to analyze.
|
|
1323
|
+
* @returns {Promise<Map<string, Set<number>>>} Map of field names to Set of needed symbol indices.
|
|
1324
|
+
* @private
|
|
1325
|
+
*/
|
|
1326
|
+
async _analyzeIndexTableSymbolUsage(maxRows) {
|
|
1327
|
+
if (!this._buffer || !this._header || !this._indexTableOffset) {
|
|
1328
|
+
throw new exports.QvdCorruptedError(
|
|
1329
|
+
"The QVD file has not been loaded in the proper order or has not been loaded at all.",
|
|
1330
|
+
{
|
|
1331
|
+
file: this._path,
|
|
1332
|
+
stage: "analyzeIndexTableSymbolUsage"
|
|
1333
|
+
}
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
let fields = this._header["QvdTableHeader"]["Fields"]["QvdFieldHeader"];
|
|
1337
|
+
if (!Array.isArray(fields)) {
|
|
1338
|
+
fields = [fields];
|
|
1339
|
+
}
|
|
1340
|
+
const recordSize = parseInt(this._header["QvdTableHeader"]["RecordByteSize"], 10);
|
|
1341
|
+
const totalRows = parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10);
|
|
1342
|
+
const rowsToAnalyze = Math.min(maxRows, totalRows);
|
|
1343
|
+
const indexTableLength = parseInt(this._header["QvdTableHeader"]["Length"], 10);
|
|
1344
|
+
const indexBuffer = this._buffer.subarray(this._indexTableOffset, this._indexTableOffset + indexTableLength + 1);
|
|
1345
|
+
const symbolUsage = /* @__PURE__ */ new Map();
|
|
1346
|
+
fields.forEach((field) => {
|
|
1347
|
+
symbolUsage.set(field["FieldName"], /* @__PURE__ */ new Set());
|
|
1348
|
+
});
|
|
1349
|
+
for (let pointer = 0, rowCount = 0; pointer < indexBuffer.length && rowCount < rowsToAnalyze; pointer += recordSize, rowCount++) {
|
|
1350
|
+
const bytes = new Int32Array(indexBuffer.subarray(pointer, pointer + recordSize));
|
|
1351
|
+
bytes.reverse();
|
|
1352
|
+
const mask = bytes.reduce((bits, byte) => bits + ("00000000" + byte.toString(2)).slice(-8), "").split("").reverse().map((bit) => parseInt(bit));
|
|
1353
|
+
fields.forEach((field) => {
|
|
1354
|
+
const bitOffset = parseInt(field["BitOffset"], 10);
|
|
1355
|
+
const bitWidth = parseInt(field["BitWidth"], 10);
|
|
1356
|
+
const bias = parseInt(field["Bias"], 10);
|
|
1357
|
+
let symbolIndex;
|
|
1358
|
+
if (bitWidth === 0) {
|
|
1359
|
+
symbolIndex = 0;
|
|
1360
|
+
} else {
|
|
1361
|
+
symbolIndex = this._convertBitsToInt32(mask.slice(bitOffset, bitOffset + bitWidth));
|
|
1362
|
+
}
|
|
1363
|
+
symbolIndex -= bias;
|
|
1364
|
+
const fieldName = field["FieldName"];
|
|
1365
|
+
symbolUsage.get(fieldName)?.add(symbolIndex);
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
return symbolUsage;
|
|
1369
|
+
}
|
|
806
1370
|
/**
|
|
807
1371
|
* Parses the symbol table of the QVD file. This method is part of the parsing process
|
|
808
1372
|
* and should not be called directly.
|
|
1373
|
+
*
|
|
1374
|
+
* @param {Map<string, Set<number>>|null} symbolsToKeep Optional map of field names to symbol indices to keep.
|
|
1375
|
+
* If provided, only these symbols will be parsed (two-pass filtering optimization).
|
|
1376
|
+
* @param {number|null} maxRows Optional maximum number of rows being loaded (for memory estimation).
|
|
809
1377
|
*/
|
|
810
|
-
async _parseSymbolTable() {
|
|
1378
|
+
async _parseSymbolTable(symbolsToKeep = null, maxRows = null) {
|
|
811
1379
|
if (!this._buffer || !this._header || !this._symbolTableOffset || !this._indexTableOffset) {
|
|
812
1380
|
throw new exports.QvdCorruptedError(
|
|
813
1381
|
"The QVD file has not been loaded in the proper order or has not been loaded at all.",
|
|
@@ -819,192 +1387,40 @@ var init_QvdFileReader = __esm({
|
|
|
819
1387
|
}
|
|
820
1388
|
let fields = this._header["QvdTableHeader"]["Fields"]["QvdFieldHeader"];
|
|
821
1389
|
const symbolBuffer = this._buffer.subarray(this._symbolTableOffset, this._indexTableOffset);
|
|
1390
|
+
const symbolTableSize = symbolBuffer.length;
|
|
1391
|
+
const totalRows = parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10);
|
|
1392
|
+
validateSymbolTableSize(symbolTableSize, this._path, totalRows);
|
|
1393
|
+
validateMemoryAvailability(symbolTableSize, maxRows, totalRows, this._path);
|
|
1394
|
+
warnLargeSymbolTable(symbolTableSize, maxRows, totalRows);
|
|
822
1395
|
if (!Array.isArray(fields)) {
|
|
823
1396
|
fields = [fields];
|
|
824
1397
|
}
|
|
825
1398
|
for (const field of fields) {
|
|
826
|
-
|
|
827
|
-
const symbolsLength = parseInt(field["Length"], 10);
|
|
828
|
-
if (isNaN(symbolsOffset) || !Number.isSafeInteger(symbolsOffset) || symbolsOffset < 0) {
|
|
829
|
-
throw new exports.QvdCorruptedError("Invalid symbol offset", {
|
|
830
|
-
field: field["FieldName"],
|
|
831
|
-
offset: symbolsOffset,
|
|
832
|
-
file: this._path,
|
|
833
|
-
stage: "parseSymbolTable"
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
if (isNaN(symbolsLength) || !Number.isSafeInteger(symbolsLength) || symbolsLength < 0) {
|
|
837
|
-
throw new exports.QvdCorruptedError("Invalid symbol length", {
|
|
838
|
-
field: field["FieldName"],
|
|
839
|
-
length: symbolsLength,
|
|
840
|
-
file: this._path,
|
|
841
|
-
stage: "parseSymbolTable"
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
if (symbolsOffset + symbolsLength > symbolBuffer.length) {
|
|
845
|
-
throw new exports.QvdCorruptedError("Symbol data extends beyond buffer", {
|
|
846
|
-
field: field["FieldName"],
|
|
847
|
-
offset: symbolsOffset,
|
|
848
|
-
length: symbolsLength,
|
|
849
|
-
bufferSize: symbolBuffer.length,
|
|
850
|
-
file: this._path,
|
|
851
|
-
stage: "parseSymbolTable"
|
|
852
|
-
});
|
|
853
|
-
}
|
|
1399
|
+
validateFieldMetadata(field, symbolBuffer.length, this._path);
|
|
854
1400
|
}
|
|
855
1401
|
this._symbolTable = fields.map((field) => {
|
|
856
1402
|
const symbolsOffset = parseInt(field["Offset"], 10);
|
|
857
1403
|
const symbolsLength = parseInt(field["Length"], 10);
|
|
1404
|
+
const fieldName = field["FieldName"];
|
|
1405
|
+
const neededSymbols = symbolsToKeep ? symbolsToKeep.get(fieldName) : null;
|
|
1406
|
+
const filteringEnabled = neededSymbols !== null;
|
|
858
1407
|
const symbols = [];
|
|
1408
|
+
let symbolIndex = 0;
|
|
859
1409
|
for (let pointer = symbolsOffset; pointer < symbolsOffset + symbolsLength; pointer++) {
|
|
860
1410
|
const typeByte = symbolBuffer[pointer++];
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
pointer += 3;
|
|
875
|
-
symbols.push(exports.QvdSymbol.fromIntValue(value));
|
|
876
|
-
break;
|
|
877
|
-
}
|
|
878
|
-
case 2: {
|
|
879
|
-
if (pointer + 8 > symbolBuffer.length) {
|
|
880
|
-
throw new exports.QvdCorruptedError("Buffer overflow reading double symbol", {
|
|
881
|
-
field: field["FieldName"],
|
|
882
|
-
pointer,
|
|
883
|
-
bufferSize: symbolBuffer.length,
|
|
884
|
-
file: this._path,
|
|
885
|
-
stage: "parseSymbolTable"
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
const byteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 8));
|
|
889
|
-
const value = Buffer.from(byteData).readDoubleLE(0);
|
|
890
|
-
pointer += 7;
|
|
891
|
-
symbols.push(exports.QvdSymbol.fromDoubleValue(value));
|
|
892
|
-
break;
|
|
893
|
-
}
|
|
894
|
-
case 4: {
|
|
895
|
-
const byteData = [];
|
|
896
|
-
const maxStringLength = 1048576;
|
|
897
|
-
while (pointer < symbolBuffer.length && symbolBuffer[pointer] !== 0) {
|
|
898
|
-
if (byteData.length >= maxStringLength) {
|
|
899
|
-
throw new exports.QvdCorruptedError("String symbol exceeds maximum length", {
|
|
900
|
-
field: field["FieldName"],
|
|
901
|
-
maxLength: maxStringLength,
|
|
902
|
-
file: this._path,
|
|
903
|
-
stage: "parseSymbolTable"
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
byteData.push(symbolBuffer[pointer++]);
|
|
907
|
-
}
|
|
908
|
-
if (pointer >= symbolBuffer.length) {
|
|
909
|
-
throw new exports.QvdCorruptedError("String symbol not null-terminated", {
|
|
910
|
-
field: field["FieldName"],
|
|
911
|
-
pointer,
|
|
912
|
-
bufferSize: symbolBuffer.length,
|
|
913
|
-
file: this._path,
|
|
914
|
-
stage: "parseSymbolTable"
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
const value = Buffer.from(byteData).toString("utf-8");
|
|
918
|
-
symbols.push(exports.QvdSymbol.fromStringValue(value));
|
|
919
|
-
break;
|
|
920
|
-
}
|
|
921
|
-
case 5: {
|
|
922
|
-
if (pointer + 4 > symbolBuffer.length) {
|
|
923
|
-
throw new exports.QvdCorruptedError("Buffer overflow reading dual integer symbol", {
|
|
924
|
-
field: field["FieldName"],
|
|
925
|
-
pointer,
|
|
926
|
-
bufferSize: symbolBuffer.length,
|
|
927
|
-
file: this._path,
|
|
928
|
-
stage: "parseSymbolTable"
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
const intByteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 4));
|
|
932
|
-
const intValue = Buffer.from(intByteData).readIntLE(0, intByteData.length);
|
|
933
|
-
pointer += 4;
|
|
934
|
-
const stringByteData = [];
|
|
935
|
-
const maxStringLength = 1048576;
|
|
936
|
-
while (pointer < symbolBuffer.length && symbolBuffer[pointer] !== 0) {
|
|
937
|
-
if (stringByteData.length >= maxStringLength) {
|
|
938
|
-
throw new exports.QvdCorruptedError("Dual string symbol exceeds maximum length", {
|
|
939
|
-
field: field["FieldName"],
|
|
940
|
-
maxLength: maxStringLength,
|
|
941
|
-
file: this._path,
|
|
942
|
-
stage: "parseSymbolTable"
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
stringByteData.push(symbolBuffer[pointer++]);
|
|
946
|
-
}
|
|
947
|
-
if (pointer >= symbolBuffer.length) {
|
|
948
|
-
throw new exports.QvdCorruptedError("Dual string symbol not null-terminated", {
|
|
949
|
-
field: field["FieldName"],
|
|
950
|
-
pointer,
|
|
951
|
-
bufferSize: symbolBuffer.length,
|
|
952
|
-
file: this._path,
|
|
953
|
-
stage: "parseSymbolTable"
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
const stringValue = Buffer.from(stringByteData).toString("utf-8");
|
|
957
|
-
symbols.push(exports.QvdSymbol.fromDualIntValue(intValue, stringValue));
|
|
958
|
-
break;
|
|
959
|
-
}
|
|
960
|
-
case 6: {
|
|
961
|
-
if (pointer + 8 > symbolBuffer.length) {
|
|
962
|
-
throw new exports.QvdCorruptedError("Buffer overflow reading dual double symbol", {
|
|
963
|
-
field: field["FieldName"],
|
|
964
|
-
pointer,
|
|
965
|
-
bufferSize: symbolBuffer.length,
|
|
966
|
-
file: this._path,
|
|
967
|
-
stage: "parseSymbolTable"
|
|
968
|
-
});
|
|
969
|
-
}
|
|
970
|
-
const doubleByteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 8));
|
|
971
|
-
const doubleValue = Buffer.from(doubleByteData).readDoubleLE(0);
|
|
972
|
-
pointer += 8;
|
|
973
|
-
const stringByteData = [];
|
|
974
|
-
const maxStringLength = 1048576;
|
|
975
|
-
while (pointer < symbolBuffer.length && symbolBuffer[pointer] !== 0) {
|
|
976
|
-
if (stringByteData.length >= maxStringLength) {
|
|
977
|
-
throw new exports.QvdCorruptedError("Dual string symbol exceeds maximum length", {
|
|
978
|
-
field: field["FieldName"],
|
|
979
|
-
maxLength: maxStringLength,
|
|
980
|
-
file: this._path,
|
|
981
|
-
stage: "parseSymbolTable"
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
stringByteData.push(symbolBuffer[pointer++]);
|
|
985
|
-
}
|
|
986
|
-
if (pointer >= symbolBuffer.length) {
|
|
987
|
-
throw new exports.QvdCorruptedError("Dual string symbol not null-terminated", {
|
|
988
|
-
field: field["FieldName"],
|
|
989
|
-
pointer,
|
|
990
|
-
bufferSize: symbolBuffer.length,
|
|
991
|
-
file: this._path,
|
|
992
|
-
stage: "parseSymbolTable"
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
const stringValue = Buffer.from(stringByteData).toString("utf-8");
|
|
996
|
-
symbols.push(exports.QvdSymbol.fromDualDoubleValue(doubleValue, stringValue));
|
|
997
|
-
break;
|
|
998
|
-
}
|
|
999
|
-
default: {
|
|
1000
|
-
throw new exports.QvdParseError("Unknown symbol type byte", {
|
|
1001
|
-
typeByte: typeByte.toString(16),
|
|
1002
|
-
offset: pointer,
|
|
1003
|
-
file: this._path,
|
|
1004
|
-
stage: "parseSymbolTable"
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1411
|
+
const shouldKeepSymbol = !filteringEnabled || !!(neededSymbols && neededSymbols.has(symbolIndex));
|
|
1412
|
+
const { symbol, bytesRead } = parseSymbol(
|
|
1413
|
+
typeByte,
|
|
1414
|
+
symbolBuffer,
|
|
1415
|
+
pointer,
|
|
1416
|
+
symbolBuffer.length,
|
|
1417
|
+
fieldName,
|
|
1418
|
+
this._path,
|
|
1419
|
+
shouldKeepSymbol
|
|
1420
|
+
);
|
|
1421
|
+
symbols.push(symbol);
|
|
1422
|
+
pointer += bytesRead - 1;
|
|
1423
|
+
symbolIndex++;
|
|
1008
1424
|
}
|
|
1009
1425
|
return symbols;
|
|
1010
1426
|
});
|
|
@@ -1016,10 +1432,7 @@ var init_QvdFileReader = __esm({
|
|
|
1016
1432
|
* @return {Number} The integer value
|
|
1017
1433
|
*/
|
|
1018
1434
|
_convertBitsToInt32(bits) {
|
|
1019
|
-
|
|
1020
|
-
return 0;
|
|
1021
|
-
}
|
|
1022
|
-
return bits.reduce((value, bit, index) => value += bit * Math.pow(2, index), 0);
|
|
1435
|
+
return convertBitsToInt32(bits);
|
|
1023
1436
|
}
|
|
1024
1437
|
/**
|
|
1025
1438
|
* Parses the bit stuffed index table of the QVD file. This method is part of the parsing process
|
|
@@ -1042,105 +1455,21 @@ var init_QvdFileReader = __esm({
|
|
|
1042
1455
|
fields = [fields];
|
|
1043
1456
|
}
|
|
1044
1457
|
const recordSize = parseInt(this._header["QvdTableHeader"]["RecordByteSize"], 10);
|
|
1045
|
-
if (isNaN(recordSize) || !Number.isSafeInteger(recordSize) || recordSize < 0) {
|
|
1046
|
-
throw new exports.QvdCorruptedError("Invalid record byte size", {
|
|
1047
|
-
recordSize,
|
|
1048
|
-
file: this._path,
|
|
1049
|
-
stage: "parseIndexTable"
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
1458
|
const totalRows = parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10);
|
|
1053
|
-
if (isNaN(totalRows) || !Number.isSafeInteger(totalRows) || totalRows < 0) {
|
|
1054
|
-
throw new exports.QvdCorruptedError("Invalid number of records", {
|
|
1055
|
-
totalRows,
|
|
1056
|
-
file: this._path,
|
|
1057
|
-
stage: "parseIndexTable"
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
if (recordSize === 0 && totalRows > 0) {
|
|
1061
|
-
throw new exports.QvdCorruptedError("Record byte size cannot be zero when records exist", {
|
|
1062
|
-
recordSize,
|
|
1063
|
-
totalRows,
|
|
1064
|
-
file: this._path,
|
|
1065
|
-
stage: "parseIndexTable"
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
if (recordSize > 1048576) {
|
|
1069
|
-
throw new exports.QvdCorruptedError("Record byte size exceeds maximum", {
|
|
1070
|
-
recordSize,
|
|
1071
|
-
maxSize: 1048576,
|
|
1072
|
-
file: this._path,
|
|
1073
|
-
stage: "parseIndexTable"
|
|
1074
|
-
});
|
|
1075
|
-
}
|
|
1076
1459
|
const rowsToLoad = maxRows !== null ? Math.min(maxRows, totalRows) : totalRows;
|
|
1077
1460
|
const indexTableLength = parseInt(this._header["QvdTableHeader"]["Length"], 10);
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
indexTableOffset: this._indexTableOffset,
|
|
1088
|
-
bufferSize: this._buffer.length,
|
|
1089
|
-
file: this._path,
|
|
1090
|
-
stage: "parseIndexTable"
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
const maxReasonableOverage = 100 * 1024 * 1024;
|
|
1094
|
-
if (indexTableLength > this._buffer.length + maxReasonableOverage) {
|
|
1095
|
-
throw new exports.QvdCorruptedError("Index table length unreasonably large", {
|
|
1096
|
-
indexTableLength,
|
|
1097
|
-
bufferSize: this._buffer.length,
|
|
1098
|
-
file: this._path,
|
|
1099
|
-
stage: "parseIndexTable"
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
const requiredIndexBytes = rowsToLoad * recordSize;
|
|
1103
|
-
if (indexTableLength < requiredIndexBytes) {
|
|
1104
|
-
throw new exports.QvdCorruptedError("Index table length smaller than required", {
|
|
1105
|
-
indexTableLength,
|
|
1106
|
-
requiredBytes: requiredIndexBytes,
|
|
1107
|
-
rowsToLoad,
|
|
1108
|
-
recordSize,
|
|
1109
|
-
file: this._path,
|
|
1110
|
-
stage: "parseIndexTable"
|
|
1111
|
-
});
|
|
1112
|
-
}
|
|
1461
|
+
validateIndexTableMetadata(
|
|
1462
|
+
recordSize,
|
|
1463
|
+
totalRows,
|
|
1464
|
+
indexTableLength,
|
|
1465
|
+
this._indexTableOffset,
|
|
1466
|
+
this._buffer.length,
|
|
1467
|
+
rowsToLoad,
|
|
1468
|
+
this._path
|
|
1469
|
+
);
|
|
1113
1470
|
const indexBuffer = this._buffer.subarray(this._indexTableOffset, this._indexTableOffset + indexTableLength + 1);
|
|
1114
1471
|
for (const field of fields) {
|
|
1115
|
-
|
|
1116
|
-
const bitWidth = parseInt(field["BitWidth"], 10);
|
|
1117
|
-
if (isNaN(bitOffset) || !Number.isSafeInteger(bitOffset) || bitOffset < 0) {
|
|
1118
|
-
throw new exports.QvdCorruptedError("Invalid bit offset", {
|
|
1119
|
-
field: field["FieldName"],
|
|
1120
|
-
bitOffset,
|
|
1121
|
-
file: this._path,
|
|
1122
|
-
stage: "parseIndexTable"
|
|
1123
|
-
});
|
|
1124
|
-
}
|
|
1125
|
-
if (isNaN(bitWidth) || !Number.isSafeInteger(bitWidth) || bitWidth < 0) {
|
|
1126
|
-
throw new exports.QvdCorruptedError("Invalid bit width", {
|
|
1127
|
-
field: field["FieldName"],
|
|
1128
|
-
bitWidth,
|
|
1129
|
-
file: this._path,
|
|
1130
|
-
stage: "parseIndexTable"
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
const recordSizeInBits = recordSize * 8;
|
|
1134
|
-
if (bitOffset + bitWidth > recordSizeInBits) {
|
|
1135
|
-
throw new exports.QvdCorruptedError("Bit field extends beyond record size", {
|
|
1136
|
-
field: field["FieldName"],
|
|
1137
|
-
bitOffset,
|
|
1138
|
-
bitWidth,
|
|
1139
|
-
recordSizeInBits,
|
|
1140
|
-
file: this._path,
|
|
1141
|
-
stage: "parseIndexTable"
|
|
1142
|
-
});
|
|
1143
|
-
}
|
|
1472
|
+
validateFieldBitMetadata(field, recordSize, this._path);
|
|
1144
1473
|
}
|
|
1145
1474
|
this._indexTable = [];
|
|
1146
1475
|
for (let pointer = 0, rowCount = 0; pointer < indexBuffer.length && rowCount < rowsToLoad; pointer += recordSize, rowCount++) {
|
|
@@ -1182,7 +1511,19 @@ var init_QvdFileReader = __esm({
|
|
|
1182
1511
|
async load(maxRows = null) {
|
|
1183
1512
|
await this._readData(maxRows);
|
|
1184
1513
|
await this._parseHeader();
|
|
1185
|
-
|
|
1514
|
+
let symbolsToKeep = null;
|
|
1515
|
+
if (maxRows !== null && this._header) {
|
|
1516
|
+
const symbolTableLength = parseInt(this._header["QvdTableHeader"]["Offset"], 10);
|
|
1517
|
+
const SYMBOL_FILTERING_THRESHOLD = 50 * 1024 * 1024;
|
|
1518
|
+
if (symbolTableLength > SYMBOL_FILTERING_THRESHOLD) {
|
|
1519
|
+
symbolsToKeep = await this._analyzeIndexTableSymbolUsage(maxRows);
|
|
1520
|
+
const totalSymbols = Array.from(symbolsToKeep.values()).reduce((sum, set) => sum + set.size, 0);
|
|
1521
|
+
console.log(
|
|
1522
|
+
`[Phase 2.5 Optimization] Using stream-and-skip parsing: keeping ${totalSymbols} symbols from ${(symbolTableLength / 1024 / 1024).toFixed(1)}MB symbol table`
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
await this._parseSymbolTable(symbolsToKeep, maxRows);
|
|
1186
1527
|
await this._parseIndexTable(maxRows);
|
|
1187
1528
|
assert__default.default(this._header, "The QVD file header has not been parsed.");
|
|
1188
1529
|
assert__default.default(this._symbolTable, "The QVD file symbol table has not been parsed.");
|