qvdjs 0.10.0 → 0.10.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 +37 -0
- package/dist/index.cjs +33 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +33 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,6 +135,43 @@ console.log(df.head(5));
|
|
|
135
135
|
The above example loads the _qvdjs_ library and parses an example QVD file. A QVD file is typically loaded using the static
|
|
136
136
|
`QvdDataFrame.fromQvd` function of the `QvdDataFrame` class itself. After loading the file's content, numerous methods and properties are available to work with the parsed data.
|
|
137
137
|
|
|
138
|
+
### Three ways to open a file
|
|
139
|
+
|
|
140
|
+
`fromQvd` is the general one, and often not the one you want. A QVD is an XML header, then a
|
|
141
|
+
symbol table of every distinct value, then a bit-packed index table of one code per cell — so how
|
|
142
|
+
far into the file a read has to go is what separates these, and all three take the same options.
|
|
143
|
+
|
|
144
|
+
| You want | Call | How far it reads |
|
|
145
|
+
| --------------------------------------- | --------------------------------- | ---------------------------------------------------------------------- |
|
|
146
|
+
| Rows, to index, iterate or write back | `QvdDataFrame.fromQvd(path)` | Everything, and materialises every row |
|
|
147
|
+
| A few columns of a large file | `QvdColumnTable.fromQvd(path)` | Everything, but stops before building rows — 141 MiB against 385 MiB |
|
|
148
|
+
| Only the schema: names, row count, types | `QvdDataFrame.readMetadata(path)` | The header alone. Constant cost, whatever the file's size |
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
import {QvdDataFrame, QvdColumnTable} from 'qvdjs';
|
|
152
|
+
|
|
153
|
+
// What is in this file? Costs the same whether it is 20 KB or 20 GB.
|
|
154
|
+
const {columns, rowCount} = await QvdDataFrame.readMetadata('sales.qvd');
|
|
155
|
+
console.log(`${rowCount} rows x ${columns.length} columns`);
|
|
156
|
+
|
|
157
|
+
// Sum one column without ever building a row.
|
|
158
|
+
const table = await QvdColumnTable.fromQvd('sales.qvd');
|
|
159
|
+
let total = 0;
|
|
160
|
+
for (const value of table.column('amount')) {
|
|
161
|
+
if (typeof value === 'number') total += value;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Rows, when you want rows.
|
|
165
|
+
const df = await QvdDataFrame.fromQvd('sales.qvd');
|
|
166
|
+
console.log(df.head(5));
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Reaching for `fromQvd` when you wanted one of the other two is the common mistake, and
|
|
170
|
+
`fromQvd(path, {maxRows: 0})` is not a substitute for `readMetadata`: it loads no rows but still
|
|
171
|
+
reads and parses the whole symbol table, which grows with the data. See
|
|
172
|
+
[QvdColumnTable](#qvdcolumntable) and the [API Documentation](#api-documentation) for the
|
|
173
|
+
details and the measurements behind the table above.
|
|
174
|
+
|
|
138
175
|
### Lazy Loading
|
|
139
176
|
|
|
140
177
|
For large QVD files, you can load only a specific number of rows to improve performance and reduce memory usage. The library implements **lazy loading** - it reads only the necessary portions of the file from disk, not the entire file.
|
package/dist/index.cjs
CHANGED
|
@@ -957,6 +957,24 @@ var init_memoryUtils = __esm({
|
|
|
957
957
|
});
|
|
958
958
|
|
|
959
959
|
// src/util/validationUtils.js
|
|
960
|
+
function validateHeaderStructure(headerObj, filePath, stage) {
|
|
961
|
+
const tableHeader = headerObj?.["QvdTableHeader"];
|
|
962
|
+
if (tableHeader === null || typeof tableHeader !== "object" || Array.isArray(tableHeader)) {
|
|
963
|
+
throw new exports.QvdCorruptedError("The XML header contains no usable QvdTableHeader element", {
|
|
964
|
+
rootElements: headerObj && typeof headerObj === "object" ? Object.keys(headerObj) : [],
|
|
965
|
+
file: filePath,
|
|
966
|
+
stage
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
const symbolTableLength = parseInt(tableHeader["Offset"], 10);
|
|
970
|
+
if (isNaN(symbolTableLength) || !Number.isSafeInteger(symbolTableLength) || symbolTableLength < 0) {
|
|
971
|
+
throw new exports.QvdCorruptedError("Invalid symbol table offset", {
|
|
972
|
+
offset: tableHeader["Offset"],
|
|
973
|
+
file: filePath,
|
|
974
|
+
stage
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}
|
|
960
978
|
function validateSymbolTableSizeEarly(symbolTableLength, filePath) {
|
|
961
979
|
const heapLimit = getHeapLimit();
|
|
962
980
|
const MAX_SYMBOL_TABLE_SIZE = heapLimit * 0.125;
|
|
@@ -1861,6 +1879,7 @@ var init_QvdFileReader = __esm({
|
|
|
1861
1879
|
stage: "readData"
|
|
1862
1880
|
});
|
|
1863
1881
|
}
|
|
1882
|
+
validateHeaderStructure(headerObj, this._path, "readData");
|
|
1864
1883
|
const symbolTableOffset = headerEndIndex;
|
|
1865
1884
|
const symbolTableLength = parseInt(headerObj["QvdTableHeader"]["Offset"], 10);
|
|
1866
1885
|
const indexTableOffset = symbolTableOffset + symbolTableLength;
|
|
@@ -1984,14 +2003,26 @@ var init_QvdFileReader = __esm({
|
|
|
1984
2003
|
stage: "parseHeader"
|
|
1985
2004
|
});
|
|
1986
2005
|
}
|
|
2006
|
+
validateHeaderStructure(this._header, this._path, "parseHeader");
|
|
1987
2007
|
const fields = this._header["QvdTableHeader"]?.["Fields"]?.["QvdFieldHeader"];
|
|
1988
|
-
const
|
|
1989
|
-
if (
|
|
2008
|
+
const fieldList = fields === void 0 || fields === null ? [] : Array.isArray(fields) ? fields : [fields];
|
|
2009
|
+
if (fieldList.length === 0) {
|
|
1990
2010
|
throw new exports.QvdCorruptedError("The QVD file header declares no fields", {
|
|
1991
2011
|
file: this._path,
|
|
1992
2012
|
stage: "parseHeader"
|
|
1993
2013
|
});
|
|
1994
2014
|
}
|
|
2015
|
+
const malformedIndex = fieldList.findIndex(
|
|
2016
|
+
(field) => field === null || typeof field !== "object" || Array.isArray(field)
|
|
2017
|
+
);
|
|
2018
|
+
if (malformedIndex !== -1) {
|
|
2019
|
+
throw new exports.QvdCorruptedError("The QVD file header declares a field with no properties", {
|
|
2020
|
+
fieldIndex: malformedIndex,
|
|
2021
|
+
fieldCount: fieldList.length,
|
|
2022
|
+
file: this._path,
|
|
2023
|
+
stage: "parseHeader"
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
1995
2026
|
this._headerOffset = headerBeginIndex;
|
|
1996
2027
|
this._symbolTableOffset = headerEndIndex;
|
|
1997
2028
|
this._indexTableOffset = this._symbolTableOffset + parseInt(this._header["QvdTableHeader"]["Offset"], 10);
|