@gscdump/engine 4.2.1 → 4.2.3
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/dist/iceberg/append-sink.mjs +37 -29
- package/dist/ingest.mjs +16 -3
- package/package.json +4 -4
|
@@ -29,37 +29,36 @@ function toIntPartitionSiteId(value) {
|
|
|
29
29
|
if (siteId < INT32_MIN || siteId > INT32_MAX) throw new TypeError(`toRecords: int partition site_id must fit Iceberg INT, got '${String(value)}'`);
|
|
30
30
|
return siteId;
|
|
31
31
|
}
|
|
32
|
-
function
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (index > 0) identity += "\0";
|
|
40
|
-
identity += `${rec[key[index]] ?? ""}`;
|
|
41
|
-
}
|
|
42
|
-
seen.set(identity, rec);
|
|
43
|
-
}
|
|
44
|
-
return seen.size === records.length ? records : [...seen.values()];
|
|
32
|
+
function compareIdentityValue(av, bv) {
|
|
33
|
+
if (av === bv) return 0;
|
|
34
|
+
if (typeof av === "number" && typeof bv === "number") return av - bv;
|
|
35
|
+
const as = `${av ?? ""}`;
|
|
36
|
+
const bs = `${bv ?? ""}`;
|
|
37
|
+
if (as === bs) return 0;
|
|
38
|
+
return as < bs ? -1 : 1;
|
|
45
39
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
if (typeof av === "number" && typeof bv === "number") return av - bv;
|
|
57
|
-
const as = String(av);
|
|
58
|
-
const bs = String(bv);
|
|
59
|
-
if (as !== bs) return as < bs ? -1 : 1;
|
|
40
|
+
function clusterAndDedupe(table, records) {
|
|
41
|
+
if (records.length < 2) return records;
|
|
42
|
+
const spec = gscDataset(table, "int").tableSpec;
|
|
43
|
+
const cluster = spec.clusterKey ?? [];
|
|
44
|
+
for (const col of cluster) if (!spec.identityColumns.includes(col)) throw new TypeError(`clusterAndDedupe: clusterKey column '${col}' is not an identity column of '${table}'. Dedupe would silently key on cluster plus identity.`);
|
|
45
|
+
const order = [...cluster, ...spec.identityColumns.filter((col) => !cluster.includes(col))];
|
|
46
|
+
const compare = (a, b) => {
|
|
47
|
+
for (const col of order) {
|
|
48
|
+
const c = compareIdentityValue(a[col], b[col]);
|
|
49
|
+
if (c !== 0) return c;
|
|
60
50
|
}
|
|
61
51
|
return 0;
|
|
62
|
-
}
|
|
52
|
+
};
|
|
53
|
+
records.sort(compare);
|
|
54
|
+
let write = 0;
|
|
55
|
+
for (let read = 0; read < records.length; read++) {
|
|
56
|
+
const rec = records[read];
|
|
57
|
+
if (read + 1 < records.length && compare(rec, records[read + 1]) === 0) continue;
|
|
58
|
+
records[write++] = rec;
|
|
59
|
+
}
|
|
60
|
+
records.length = write;
|
|
61
|
+
return records;
|
|
63
62
|
}
|
|
64
63
|
function toRecords(slice, rows, encoding) {
|
|
65
64
|
const siteVal = encoding === "int" ? toIntPartitionSiteId(slice.ctx.siteId) : slice.ctx.siteId ?? "";
|
|
@@ -115,7 +114,16 @@ function createIcebergAppendSink(options) {
|
|
|
115
114
|
}
|
|
116
115
|
for (const [table, records] of buffers) {
|
|
117
116
|
if (records.length === 0) continue;
|
|
118
|
-
|
|
117
|
+
let deduped;
|
|
118
|
+
try {
|
|
119
|
+
deduped = clusterAndDedupe(table, records);
|
|
120
|
+
} catch (cause) {
|
|
121
|
+
failed.push({
|
|
122
|
+
table,
|
|
123
|
+
error: engineErrors.sinkTableFlushFailed(table, cause)
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
119
127
|
await icebergAppendRetrying({
|
|
120
128
|
catalog: conn.catalog,
|
|
121
129
|
namespace: conn.namespace,
|
package/dist/ingest.mjs
CHANGED
|
@@ -242,14 +242,20 @@ const STORED_KEY_COLUMNS = Object.fromEntries(Object.keys(TABLE_DIMS).map((table
|
|
|
242
242
|
function sumByStoredKey(table, rows) {
|
|
243
243
|
const keyColumns = STORED_KEY_COLUMNS[table];
|
|
244
244
|
const grouped = /* @__PURE__ */ new Map();
|
|
245
|
+
const owned = /* @__PURE__ */ new Set();
|
|
245
246
|
for (const row of rows) {
|
|
246
247
|
const r = row;
|
|
247
248
|
const key = JSON.stringify(keyColumns.map((col) => r[col] ?? null));
|
|
248
|
-
|
|
249
|
+
let prior = grouped.get(key);
|
|
249
250
|
if (!prior) {
|
|
250
|
-
grouped.set(key,
|
|
251
|
+
grouped.set(key, r);
|
|
251
252
|
continue;
|
|
252
253
|
}
|
|
254
|
+
if (!owned.has(prior)) {
|
|
255
|
+
prior = { ...prior };
|
|
256
|
+
owned.add(prior);
|
|
257
|
+
grouped.set(key, prior);
|
|
258
|
+
}
|
|
253
259
|
for (const col of Object.keys(r)) if (ADDITIVE_COLUMN_RE.test(col)) prior[col] = Number(prior[col] ?? 0) + Number(r[col] ?? 0);
|
|
254
260
|
}
|
|
255
261
|
return [...grouped.values()];
|
|
@@ -299,7 +305,14 @@ function createRowAccumulator(options = {}) {
|
|
|
299
305
|
},
|
|
300
306
|
drain() {
|
|
301
307
|
const out = /* @__PURE__ */ new Map();
|
|
302
|
-
for (const [table, byDate] of buckets)
|
|
308
|
+
for (const [table, byDate] of buckets) {
|
|
309
|
+
const summed = /* @__PURE__ */ new Map();
|
|
310
|
+
for (const [date, sourceRows] of byDate) {
|
|
311
|
+
summed.set(date, sumByStoredKey(table, sourceRows.values()));
|
|
312
|
+
byDate.delete(date);
|
|
313
|
+
}
|
|
314
|
+
out.set(table, summed);
|
|
315
|
+
}
|
|
303
316
|
buckets = /* @__PURE__ */ new Map();
|
|
304
317
|
latestDate.clear();
|
|
305
318
|
total = 0;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/engine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.2.
|
|
4
|
+
"version": "4.2.3",
|
|
5
5
|
"description": "Append-only Parquet/DuckDB storage engine + planner + adapters for the gscdump pipeline. Node + edge runtimes; opt-in heavy peers.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -189,10 +189,10 @@
|
|
|
189
189
|
}
|
|
190
190
|
},
|
|
191
191
|
"dependencies": {
|
|
192
|
-
"@gscdump/contracts": "^4.2.
|
|
193
|
-
"@gscdump/lakehouse": "^4.2.
|
|
192
|
+
"@gscdump/contracts": "^4.2.3",
|
|
193
|
+
"@gscdump/lakehouse": "^4.2.3",
|
|
194
194
|
"drizzle-orm": "1.0.0-rc.4",
|
|
195
|
-
"gscdump": "^4.2.
|
|
195
|
+
"gscdump": "^4.2.3",
|
|
196
196
|
"pathe": "^2.0.3",
|
|
197
197
|
"proper-lockfile": "^4.1.2"
|
|
198
198
|
},
|