@gscdump/engine 0.32.12 → 0.33.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/_chunks/engine.mjs +54 -25
- package/dist/_chunks/entities.mjs +5 -2
- package/dist/_chunks/manifest-store-utils.mjs +56 -0
- package/dist/_chunks/pg-adapter.d.mts +30 -4
- package/dist/_chunks/resolver.mjs +201 -37
- package/dist/_chunks/schema.d.mts +0 -120
- package/dist/_chunks/schema.mjs +4 -8
- package/dist/_chunks/schema2.mjs +8 -6
- package/dist/_chunks/sink.d.mts +18 -12
- package/dist/_chunks/types.d.mts +3 -0
- package/dist/adapters/filesystem.mjs +11 -65
- package/dist/adapters/hyparquet.mjs +13 -10
- package/dist/adapters/r2-manifest.mjs +51 -50
- package/dist/entities.d.mts +2 -2
- package/dist/iceberg/index.d.mts +2 -2
- package/dist/iceberg/index.mjs +9 -9
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/ingest.d.mts +1 -6
- package/dist/ingest.mjs +0 -7
- package/dist/resolver/index.d.mts +91 -36
- package/dist/resolver/index.mjs +2 -2
- package/dist/rollups.d.mts +4 -4
- package/dist/rollups.mjs +28 -19
- package/package.json +3 -3
package/dist/_chunks/engine.mjs
CHANGED
|
@@ -6,6 +6,20 @@ import { compileLogicalQueryPlan, substituteNamedFiles } from "./parquet-plan.mj
|
|
|
6
6
|
import { sqlEscape } from "../sql-bind.mjs";
|
|
7
7
|
import { buildLogicalPlan } from "gscdump/query/plan";
|
|
8
8
|
import { normalizeUrl } from "gscdump/normalize";
|
|
9
|
+
const BUFFER_READ_BATCH_SIZE = 8;
|
|
10
|
+
async function registerBufferedFiles(db, files, dataSource, registered, signal) {
|
|
11
|
+
for (let i = 0; i < files.length; i += BUFFER_READ_BATCH_SIZE) {
|
|
12
|
+
signal?.throwIfAborted();
|
|
13
|
+
const batch = files.slice(i, i + BUFFER_READ_BATCH_SIZE);
|
|
14
|
+
const buffers = await Promise.all(batch.map((file) => dataSource.read(file.key, void 0, signal)));
|
|
15
|
+
for (let j = 0; j < batch.length; j++) {
|
|
16
|
+
signal?.throwIfAborted();
|
|
17
|
+
const file = batch[j];
|
|
18
|
+
await db.registerFileBuffer(file.name, buffers[j]);
|
|
19
|
+
registered.push(file.name);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
9
23
|
async function encodeBytes(db, table, rows) {
|
|
10
24
|
const inName = db.makeTempPath("json");
|
|
11
25
|
const outName = db.makeTempPath("parquet");
|
|
@@ -72,17 +86,19 @@ function createDuckDBCodec(factory) {
|
|
|
72
86
|
await db.dropFiles([outName]);
|
|
73
87
|
}
|
|
74
88
|
}
|
|
75
|
-
const inputs = await Promise.all(inputKeys.map((k) => dataSource.read(k)));
|
|
76
89
|
const inNames = [];
|
|
77
90
|
const outName = db.makeTempPath("parquet");
|
|
78
91
|
const registered = [];
|
|
79
|
-
|
|
92
|
+
const bufferedInputs = inputKeys.map((key) => {
|
|
80
93
|
const name = db.makeTempPath("parquet");
|
|
81
|
-
await db.registerFileBuffer(name, inputs[i]);
|
|
82
94
|
inNames.push(name);
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
return {
|
|
96
|
+
key,
|
|
97
|
+
name
|
|
98
|
+
};
|
|
99
|
+
});
|
|
85
100
|
try {
|
|
101
|
+
await registerBufferedFiles(db, bufferedInputs, dataSource, registered);
|
|
86
102
|
const fileList = inNames.map((n) => `'${sqlEscape(n)}'`).join(", ");
|
|
87
103
|
await db.query(`COPY (${dedupedMergeSql(ctx.table, fileList)}) TO '${sqlEscape(outName)}' (FORMAT PARQUET, COMPRESSION ZSTD)`);
|
|
88
104
|
registered.push(outName);
|
|
@@ -127,23 +143,26 @@ function createDuckDBExecutor(factory) {
|
|
|
127
143
|
const registered = [];
|
|
128
144
|
const totalFiles = Object.values(fileKeys).reduce((n, keys) => n + keys.length, 0);
|
|
129
145
|
const endRegister = profiler?.start("files.register", { files: totalFiles });
|
|
130
|
-
await Promise.all(Object.entries(fileKeys).map(async ([name, keys]) => {
|
|
131
|
-
const uris = keys.map((key) => dataSource.uri?.(key));
|
|
132
|
-
const buffers = await Promise.all(keys.map((key, i) => uris[i] !== void 0 ? Promise.resolve(void 0) : dataSource.read(key, void 0, signal)));
|
|
133
|
-
const resolved = [];
|
|
134
|
-
for (let i = 0; i < keys.length; i++) {
|
|
135
|
-
const uri = uris[i];
|
|
136
|
-
if (uri !== void 0) resolved.push(uri);
|
|
137
|
-
else {
|
|
138
|
-
await db.registerFileBuffer(keys[i], buffers[i]);
|
|
139
|
-
registered.push(keys[i]);
|
|
140
|
-
resolved.push(keys[i]);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
placeholders[name] = resolved;
|
|
144
|
-
}));
|
|
145
|
-
endRegister?.({ buffered: registered.length });
|
|
146
146
|
try {
|
|
147
|
+
await Promise.all(Object.entries(fileKeys).map(async ([name, keys]) => {
|
|
148
|
+
const resolved = [];
|
|
149
|
+
const buffered = [];
|
|
150
|
+
for (let i = 0; i < keys.length; i++) {
|
|
151
|
+
const key = keys[i];
|
|
152
|
+
const uri = dataSource.uri?.(key);
|
|
153
|
+
if (uri !== void 0) resolved.push(uri);
|
|
154
|
+
else {
|
|
155
|
+
buffered.push({
|
|
156
|
+
key,
|
|
157
|
+
name: key
|
|
158
|
+
});
|
|
159
|
+
resolved.push(key);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
await registerBufferedFiles(db, buffered, dataSource, registered, signal);
|
|
163
|
+
placeholders[name] = resolved;
|
|
164
|
+
}));
|
|
165
|
+
endRegister?.({ buffered: registered.length });
|
|
147
166
|
signal?.throwIfAborted();
|
|
148
167
|
const finalSql = substituteNamedFiles(rewriteEmptyFileSets(sql, placeholders, table, placeholderTables), placeholders);
|
|
149
168
|
const endQuery = profiler?.start("query.run");
|
|
@@ -274,13 +293,23 @@ async function gcOrphansImpl(deps, now, graceMs, opts = {}) {
|
|
|
274
293
|
}
|
|
275
294
|
return { deleted: retired.length + sweptOrphans + hourlyDeleted };
|
|
276
295
|
}
|
|
277
|
-
const PUSHABLE_COLUMN = {
|
|
296
|
+
const PUSHABLE_COLUMN = {
|
|
297
|
+
country: "country",
|
|
298
|
+
query: "query",
|
|
299
|
+
searchAppearance: "searchAppearance"
|
|
300
|
+
};
|
|
278
301
|
function txLeaf(leaf, columns) {
|
|
279
302
|
if (leaf.operator !== "equals") return null;
|
|
280
303
|
const column = PUSHABLE_COLUMN[leaf.dimension];
|
|
281
304
|
if (!column || !columns.has(column)) return null;
|
|
282
305
|
return { [column]: { $eq: leaf.expression } };
|
|
283
306
|
}
|
|
307
|
+
function combineFilters(parts, groupType) {
|
|
308
|
+
const first = parts[0];
|
|
309
|
+
if (!first) return null;
|
|
310
|
+
if (parts.length === 1) return first;
|
|
311
|
+
return groupType === "or" ? { $or: [...parts] } : { $and: [...parts] };
|
|
312
|
+
}
|
|
284
313
|
function txExact(node, columns) {
|
|
285
314
|
const groupType = node._groupType ?? "and";
|
|
286
315
|
const leafParts = [];
|
|
@@ -291,7 +320,7 @@ function txExact(node, columns) {
|
|
|
291
320
|
}
|
|
292
321
|
if (groupType === "or") {
|
|
293
322
|
if (node._nestedGroups?.length || leafParts.length === 0) return null;
|
|
294
|
-
return leafParts
|
|
323
|
+
return combineFilters(leafParts, "or");
|
|
295
324
|
}
|
|
296
325
|
const parts = leafParts;
|
|
297
326
|
for (const group of node._nestedGroups ?? []) {
|
|
@@ -300,7 +329,7 @@ function txExact(node, columns) {
|
|
|
300
329
|
parts.push(t);
|
|
301
330
|
}
|
|
302
331
|
if (parts.length === 0) return null;
|
|
303
|
-
return parts
|
|
332
|
+
return combineFilters(parts, "and");
|
|
304
333
|
}
|
|
305
334
|
function extractParquetPushdown(state, table) {
|
|
306
335
|
const filter = state?.filter;
|
|
@@ -318,7 +347,7 @@ function extractParquetPushdown(state, table) {
|
|
|
318
347
|
if (t) parts.push(t);
|
|
319
348
|
}
|
|
320
349
|
if (parts.length === 0) return void 0;
|
|
321
|
-
return parts
|
|
350
|
+
return combineFilters(parts, "and") ?? void 0;
|
|
322
351
|
}
|
|
323
352
|
const URL_PURGE_TABLES = ["pages", "page_queries"];
|
|
324
353
|
const MAX_DAY_BYTES = 100 * 1024 * 1024;
|
|
@@ -737,6 +737,7 @@ function createSitemapStore(opts) {
|
|
|
737
737
|
const m = SITEMAP_URLS_DELTA_PREFIX_RE.exec(key);
|
|
738
738
|
if (!m) continue;
|
|
739
739
|
const date = m[1];
|
|
740
|
+
if (!date) continue;
|
|
740
741
|
if (from && date < from) continue;
|
|
741
742
|
if (to && date > to) continue;
|
|
742
743
|
const bytes = await readOptional(ds, key);
|
|
@@ -763,9 +764,11 @@ function createSitemapStore(opts) {
|
|
|
763
764
|
for (const key of deltaKeys) {
|
|
764
765
|
const m = SITEMAP_URLS_DELTA_PREFIX_RE.exec(key);
|
|
765
766
|
if (!m) continue;
|
|
766
|
-
const
|
|
767
|
+
const feedpathHash = m[2];
|
|
768
|
+
if (!feedpathHash) continue;
|
|
769
|
+
const list = deltasByFeed.get(feedpathHash) ?? [];
|
|
767
770
|
list.push(key);
|
|
768
|
-
deltasByFeed.set(
|
|
771
|
+
deltasByFeed.set(feedpathHash, list);
|
|
769
772
|
}
|
|
770
773
|
for (const [fpHash, feedDeltaKeys] of deltasByFeed) {
|
|
771
774
|
const indexKey = sitemapUrlsIndexKey(ctx, fpHash);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { inferLegacyTier, inferSearchType } from "./layout.mjs";
|
|
2
|
+
function manifestEntryKey(entry) {
|
|
3
|
+
return entry.objectKey;
|
|
4
|
+
}
|
|
5
|
+
function watermarkKey(watermark) {
|
|
6
|
+
return `${watermark.userId}|${watermark.siteId ?? ""}|${watermark.table}`;
|
|
7
|
+
}
|
|
8
|
+
function syncStateKey(state) {
|
|
9
|
+
return `${state.userId}|${state.siteId ?? ""}|${state.table}|${state.date}|${inferSearchType(state)}`;
|
|
10
|
+
}
|
|
11
|
+
function matchesManifestEntryFilter(entry, filter, options = {}) {
|
|
12
|
+
if (!options.ignoreUserId && entry.userId !== filter.userId) return false;
|
|
13
|
+
if (filter.siteId !== void 0 && entry.siteId !== filter.siteId) return false;
|
|
14
|
+
if (filter.table !== void 0 && entry.table !== filter.table) return false;
|
|
15
|
+
if (filter.partitions && !filter.partitions.includes(entry.partition)) return false;
|
|
16
|
+
if (filter.tier !== void 0 && inferLegacyTier(entry) !== filter.tier) return false;
|
|
17
|
+
if (filter.searchType !== void 0 && inferSearchType(entry) !== filter.searchType) return false;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
function matchesWatermarkFilter(watermark, filter, options = {}) {
|
|
21
|
+
if (!options.ignoreUserId && watermark.userId !== filter.userId) return false;
|
|
22
|
+
if (filter.siteId !== void 0 && watermark.siteId !== filter.siteId) return false;
|
|
23
|
+
if (filter.table !== void 0 && watermark.table !== filter.table) return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function matchesSyncStateFilter(state, filter, options = {}) {
|
|
27
|
+
if (!options.ignoreUserId && state.userId !== filter.userId) return false;
|
|
28
|
+
if (filter.siteId !== void 0 && state.siteId !== filter.siteId) return false;
|
|
29
|
+
if (filter.table !== void 0 && state.table !== filter.table) return false;
|
|
30
|
+
if (filter.state !== void 0 && state.state !== filter.state) return false;
|
|
31
|
+
if (filter.searchType !== void 0 && inferSearchType(state) !== filter.searchType) return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function mergeSyncState(existing, scope, state, detail) {
|
|
35
|
+
const at = detail?.at ?? Date.now();
|
|
36
|
+
const attemptsBump = state === "inflight" ? 1 : 0;
|
|
37
|
+
if (!existing) return {
|
|
38
|
+
userId: scope.userId,
|
|
39
|
+
siteId: scope.siteId,
|
|
40
|
+
table: scope.table,
|
|
41
|
+
date: scope.date,
|
|
42
|
+
state,
|
|
43
|
+
updatedAt: at,
|
|
44
|
+
attempts: attemptsBump,
|
|
45
|
+
error: detail?.error,
|
|
46
|
+
...scope.searchType !== void 0 ? { searchType: scope.searchType } : {}
|
|
47
|
+
};
|
|
48
|
+
return {
|
|
49
|
+
...existing,
|
|
50
|
+
state,
|
|
51
|
+
updatedAt: at,
|
|
52
|
+
attempts: existing.attempts + attemptsBump,
|
|
53
|
+
error: state === "done" ? void 0 : detail?.error ?? existing.error
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export { manifestEntryKey, matchesManifestEntryFilter, matchesSyncStateFilter, matchesWatermarkFilter, mergeSyncState, syncStateKey, watermarkKey };
|
|
@@ -15,11 +15,25 @@ declare const pgResolverAdapter: ResolverAdapter<PgTableKey>;
|
|
|
15
15
|
*/
|
|
16
16
|
interface ResolverAdapterOptions {
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Deprecated compatibility flag. Fact tables no longer carry
|
|
19
|
+
* `query_canonical`; canonical reads derive from `query_dim`, or from a
|
|
20
|
+
* canonical rollup relation when `queryCanonicalSource: 'column'` is set.
|
|
21
21
|
*/
|
|
22
22
|
canonicalFallback?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* `queryDim` reads canonical from a joined query dimension. `column` is only
|
|
25
|
+
* for derived canonical rollup relations whose primary relation already
|
|
26
|
+
* carries a null-free `query_canonical` output column.
|
|
27
|
+
*/
|
|
28
|
+
queryCanonicalSource?: 'queryDim' | 'column';
|
|
29
|
+
}
|
|
30
|
+
interface R2SqlResolverAdapterOptions extends ResolverAdapterOptions {
|
|
31
|
+
/**
|
|
32
|
+
* R2 SQL string partition equality can undercount on identity partitions;
|
|
33
|
+
* string-encoded catalogs use CONCAT(col, '') in partition predicates. Int
|
|
34
|
+
* catalogs do not need the workaround and keep bare equality for pruning.
|
|
35
|
+
*/
|
|
36
|
+
partitionKeyEncoding?: 'string' | 'int';
|
|
23
37
|
}
|
|
24
38
|
declare function createParquetResolverAdapter(options?: ResolverAdapterOptions): ResolverAdapter<PgTableKey>;
|
|
25
39
|
/**
|
|
@@ -33,4 +47,16 @@ declare function createParquetResolverAdapter(options?: ResolverAdapterOptions):
|
|
|
33
47
|
* `${namespace}.pages`) before sending to R2 SQL.
|
|
34
48
|
*/
|
|
35
49
|
declare function createIcebergResolverAdapter(options?: ResolverAdapterOptions): ResolverAdapter<PgTableKey>;
|
|
36
|
-
|
|
50
|
+
/**
|
|
51
|
+
* R2 SQL adapter for the Iceberg fact tables.
|
|
52
|
+
*
|
|
53
|
+
* It shares the multi-tenant Iceberg schema with `createIcebergResolverAdapter`
|
|
54
|
+
* but models R2 SQL's narrower execution surface: no window-total plans and no
|
|
55
|
+
* comparison joins. Int-partition catalogs are the default and emit bare
|
|
56
|
+
* equality predicates for pruning. Legacy string-partition catalogs must pass
|
|
57
|
+
* `partitionKeyEncoding: 'string'` to emit `CONCAT(partition_col, '') = ?`,
|
|
58
|
+
* working around R2 SQL's partition-string equality undercount while preserving
|
|
59
|
+
* bound params.
|
|
60
|
+
*/
|
|
61
|
+
declare function createR2SqlResolverAdapter(options?: R2SqlResolverAdapterOptions): ResolverAdapter<PgTableKey>;
|
|
62
|
+
export { PgTableKey, R2SqlResolverAdapterOptions, ResolverAdapterOptions, createIcebergResolverAdapter, createParquetResolverAdapter, createR2SqlResolverAdapter, pgResolverAdapter };
|