@gscdump/engine 0.38.2 → 0.40.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 +4 -6
- package/dist/_chunks/compaction.mjs +14 -10
- package/dist/_chunks/duckdb.d.mts +10 -2
- package/dist/_chunks/engine.mjs +17 -12
- package/dist/_chunks/entities.mjs +113 -85
- package/dist/_chunks/errors.d.mts +1 -3
- package/dist/_chunks/index.d.mts +2 -11
- package/dist/_chunks/index2.d.mts +1 -4
- package/dist/_chunks/pg-adapter.d.mts +0 -6
- package/dist/_chunks/resolver.mjs +44 -42
- package/dist/_chunks/schema.d.mts +1 -2
- package/dist/_chunks/schema.mjs +1 -4
- package/dist/_chunks/source.mjs +2 -16
- package/dist/adapters/filesystem.mjs +103 -34
- package/dist/adapters/hyparquet.d.mts +5 -0
- package/dist/adapters/hyparquet.mjs +22 -7
- package/dist/adapters/r2-manifest.mjs +12 -13
- package/dist/adapters/r2.mjs +13 -2
- package/dist/errors.d.mts +2 -2
- package/dist/errors.mjs +1 -4
- package/dist/iceberg/index.mjs +6 -2
- package/dist/index.d.mts +2 -3
- package/dist/index.mjs +2 -18
- package/dist/ingest.d.mts +0 -2
- package/dist/period/index.d.mts +2 -2
- package/dist/period/index.mjs +1 -20
- package/dist/resolver/index.d.mts +0 -12
- package/dist/rollups.mjs +23 -7
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +2 -2
- package/dist/sink-node.d.mts +2 -3
- package/dist/source/index.d.mts +2 -2
- package/dist/source/index.mjs +2 -2
- package/package.json +4 -4
|
@@ -455,7 +455,6 @@ function createParquetResolverAdapter(options = {}) {
|
|
|
455
455
|
return createResolverAdapter({
|
|
456
456
|
...PG_BASE_CONFIG,
|
|
457
457
|
tableLabel: "parquet-resolver-adapter",
|
|
458
|
-
canonicalFallback: options.canonicalFallback ?? false,
|
|
459
458
|
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
460
459
|
tableRef: (tk) => sql.raw(`read_parquet({{FILES}}, union_by_name = true) AS "${tk}"`),
|
|
461
460
|
queryDimTableRef: () => sql.raw("read_parquet({{QUERY_DIM}}, union_by_name = true) AS \"query_dim\"")
|
|
@@ -468,7 +467,6 @@ function createIcebergResolverAdapter(options = {}) {
|
|
|
468
467
|
includeSiteId: true,
|
|
469
468
|
includeSearchType: true,
|
|
470
469
|
tableLabel: "iceberg-resolver-adapter",
|
|
471
|
-
canonicalFallback: options.canonicalFallback ?? false,
|
|
472
470
|
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
473
471
|
tableRef: (tk) => sql.raw(`"${tk}"`),
|
|
474
472
|
queryDimTableRef: () => sql.raw("\"query_dim\"")
|
|
@@ -481,7 +479,6 @@ function createR2SqlResolverAdapter(options = {}) {
|
|
|
481
479
|
includeSiteId: true,
|
|
482
480
|
includeSearchType: true,
|
|
483
481
|
tableLabel: "r2-sql-resolver-adapter",
|
|
484
|
-
canonicalFallback: options.canonicalFallback ?? false,
|
|
485
482
|
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
486
483
|
capabilities: {
|
|
487
484
|
regex: false,
|
|
@@ -822,65 +819,67 @@ function buildExtrasQueries(state, options) {
|
|
|
822
819
|
function mergeExtras(rows, extrasResults) {
|
|
823
820
|
if (extrasResults.length === 0) return rows;
|
|
824
821
|
const lookups = [];
|
|
822
|
+
const parseVariants = (raw) => {
|
|
823
|
+
if (raw.length === 0) return [];
|
|
824
|
+
const encoded = raw.split("||");
|
|
825
|
+
const variants = [];
|
|
826
|
+
for (const value of encoded) {
|
|
827
|
+
if (value.length === 0) continue;
|
|
828
|
+
const parts = value.split(":::");
|
|
829
|
+
variants.push({
|
|
830
|
+
query: parts[0],
|
|
831
|
+
clicks: Number(parts[1] || 0),
|
|
832
|
+
impressions: Number(parts[2] || 0),
|
|
833
|
+
position: Number(parts[3] || 0)
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
return variants;
|
|
837
|
+
};
|
|
825
838
|
for (const { key, results } of extrasResults) {
|
|
826
839
|
if (key === "canonicalExtras") {
|
|
827
|
-
const
|
|
828
|
-
const variantsMap = /* @__PURE__ */ new Map();
|
|
829
|
-
const canonicalNameMap = /* @__PURE__ */ new Map();
|
|
840
|
+
const map = /* @__PURE__ */ new Map();
|
|
830
841
|
for (const r of results) {
|
|
831
842
|
const jk = String(r.joinKey);
|
|
832
|
-
variantCountMap.set(jk, r.variantCount);
|
|
833
|
-
canonicalNameMap.set(jk, r.canonicalName);
|
|
834
843
|
const raw = r.variants;
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
impressions: Number(parts[2] || 0),
|
|
841
|
-
position: Number(parts[3] || 0)
|
|
842
|
-
};
|
|
843
|
-
}) : []);
|
|
844
|
+
map.set(jk, {
|
|
845
|
+
variantCount: r.variantCount,
|
|
846
|
+
variants: typeof raw === "string" ? parseVariants(raw) : [],
|
|
847
|
+
canonicalName: r.canonicalName
|
|
848
|
+
});
|
|
844
849
|
}
|
|
845
850
|
lookups.push({
|
|
846
|
-
|
|
847
|
-
map
|
|
848
|
-
});
|
|
849
|
-
lookups.push({
|
|
850
|
-
key: "variants",
|
|
851
|
-
map: variantsMap
|
|
852
|
-
});
|
|
853
|
-
lookups.push({
|
|
854
|
-
key: "canonicalName",
|
|
855
|
-
map: canonicalNameMap
|
|
851
|
+
kind: "canonical",
|
|
852
|
+
map
|
|
856
853
|
});
|
|
857
854
|
continue;
|
|
858
855
|
}
|
|
859
|
-
const filtered = results.filter((r) => r.rn === void 0 || r.rn === 1);
|
|
860
856
|
const map = /* @__PURE__ */ new Map();
|
|
861
|
-
for (const r of
|
|
857
|
+
for (const r of results) {
|
|
858
|
+
if (r.rn !== void 0 && r.rn !== 1) continue;
|
|
862
859
|
let val = r[key];
|
|
863
|
-
if (key === "variants" && typeof val === "string") val = val
|
|
864
|
-
const parts = v.split(":::");
|
|
865
|
-
return {
|
|
866
|
-
query: parts[0],
|
|
867
|
-
clicks: Number(parts[1] || 0),
|
|
868
|
-
impressions: Number(parts[2] || 0),
|
|
869
|
-
position: Number(parts[3] || 0)
|
|
870
|
-
};
|
|
871
|
-
});
|
|
860
|
+
if (key === "variants" && typeof val === "string") val = parseVariants(val);
|
|
872
861
|
map.set(String(r.joinKey), val);
|
|
873
862
|
}
|
|
874
863
|
lookups.push({
|
|
864
|
+
kind: "generic",
|
|
875
865
|
key,
|
|
876
866
|
map
|
|
877
867
|
});
|
|
878
868
|
}
|
|
879
869
|
return rows.map((row) => {
|
|
880
870
|
const enriched = { ...row };
|
|
881
|
-
for (const
|
|
882
|
-
|
|
883
|
-
|
|
871
|
+
for (const lookup of lookups) {
|
|
872
|
+
if (lookup.kind === "canonical") {
|
|
873
|
+
const joinValue = String(row.queryCanonical ?? row.query_canonical ?? "");
|
|
874
|
+
const extra = joinValue ? lookup.map.get(joinValue) : void 0;
|
|
875
|
+
enriched.variantCount = extra?.variantCount ?? null;
|
|
876
|
+
enriched.variants = extra?.variants ?? [];
|
|
877
|
+
enriched.canonicalName = extra?.canonicalName ?? null;
|
|
878
|
+
if (enriched.canonicalName) enriched.queryCanonical = enriched.canonicalName;
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
const { key, map } = lookup;
|
|
882
|
+
const joinValue = key === "variantCount" || key === "variants" || key === "canonicalName" ? String(row.queryCanonical ?? row.query_canonical ?? "") : void 0;
|
|
884
883
|
enriched[key] = (joinValue && map.get(joinValue)) ?? (key === "variants" ? [] : null);
|
|
885
884
|
if (key === "canonicalName" && enriched[key]) enriched.queryCanonical = enriched[key];
|
|
886
885
|
}
|
|
@@ -955,7 +954,10 @@ function matchesMetricFilter(row, filter) {
|
|
|
955
954
|
}
|
|
956
955
|
}
|
|
957
956
|
function matchesTopLevelPage(row) {
|
|
958
|
-
|
|
957
|
+
const path = normalizeUrl(dimensionValue(row, "page"));
|
|
958
|
+
let slashes = 0;
|
|
959
|
+
for (let index = 0; index < path.length; index++) if (path.charCodeAt(index) === 47 && ++slashes > 1) return false;
|
|
960
|
+
return true;
|
|
959
961
|
}
|
|
960
962
|
var QuerySourceCoverageError = class extends Error {
|
|
961
963
|
fallback;
|
|
@@ -2115,7 +2115,6 @@ declare const TABLE_METADATA: Record<TableName, {
|
|
|
2115
2115
|
}>;
|
|
2116
2116
|
declare const SCHEMAS: Record<TableName, TableSchema>;
|
|
2117
2117
|
declare function currentSchemaVersion(table: TableName): number;
|
|
2118
|
-
declare function schemaFor(table: TableName): TableSchema;
|
|
2119
2118
|
/**
|
|
2120
2119
|
* DATE column names for a table. The single schema-derived source every read
|
|
2121
2120
|
* path uses to build the legacy-VARCHAR date canonicalization (see
|
|
@@ -2146,4 +2145,4 @@ declare function naturalKeyColumns(table: TableName): readonly string[];
|
|
|
2146
2145
|
*/
|
|
2147
2146
|
declare function dedupeByNaturalKey(table: TableName, rows: readonly Row[]): Row[];
|
|
2148
2147
|
declare function dimensionToColumn(dim: string, _table: TableName): string;
|
|
2149
|
-
export { type ColumnDef$1 as ColumnDef, type ColumnType, DrizzleSchema, SCHEMAS, TABLE_METADATA, type TableSchema$1 as TableSchema, allTables, countries, currentSchemaVersion, dateColumnsFor, dates, dedupeByNaturalKey, dimensionToColumn, drizzleSchema, hourly_pages, inferTable, naturalKeyColumns, page_queries, pages, queries,
|
|
2148
|
+
export { type ColumnDef$1 as ColumnDef, type ColumnType, DrizzleSchema, SCHEMAS, TABLE_METADATA, type TableSchema$1 as TableSchema, allTables, countries, currentSchemaVersion, dateColumnsFor, dates, dedupeByNaturalKey, dimensionToColumn, drizzleSchema, hourly_pages, inferTable, naturalKeyColumns, page_queries, pages, queries, search_appearance, search_appearance_page_queries, search_appearance_pages, search_appearance_queries };
|
package/dist/_chunks/schema.mjs
CHANGED
|
@@ -219,9 +219,6 @@ const SCHEMAS = Object.fromEntries(METRIC_TABLES.map((t) => [t, tableSchemaFrom(
|
|
|
219
219
|
function currentSchemaVersion(table) {
|
|
220
220
|
return SCHEMAS[table].version;
|
|
221
221
|
}
|
|
222
|
-
function schemaFor(table) {
|
|
223
|
-
return SCHEMAS[table];
|
|
224
|
-
}
|
|
225
222
|
function dateColumnsFor(table) {
|
|
226
223
|
return SCHEMAS[table].columns.filter((c) => c.type === "DATE").map((c) => c.name);
|
|
227
224
|
}
|
|
@@ -259,4 +256,4 @@ function dimensionToColumn(dim, _table) {
|
|
|
259
256
|
if (dim === "queryCanonical") return "query_canonical";
|
|
260
257
|
return dim;
|
|
261
258
|
}
|
|
262
|
-
export { SCHEMAS, TABLE_METADATA, allTables, countries, currentSchemaVersion, dateColumnsFor, dates, dedupeByNaturalKey, dimensionToColumn, drizzleSchema, hourly_pages, inferTable, naturalKeyColumns, page_queries, pages, queries,
|
|
259
|
+
export { SCHEMAS, TABLE_METADATA, allTables, countries, currentSchemaVersion, dateColumnsFor, dates, dedupeByNaturalKey, dimensionToColumn, drizzleSchema, hourly_pages, inferTable, naturalKeyColumns, page_queries, pages, queries, search_appearance, search_appearance_page_queries, search_appearance_pages, search_appearance_queries };
|
package/dist/_chunks/source.mjs
CHANGED
|
@@ -137,21 +137,7 @@ async function runAnalyzerWithEngine(deps, ctx, params, registry) {
|
|
|
137
137
|
searchType: params.searchType ?? "web"
|
|
138
138
|
}), params, registry);
|
|
139
139
|
}
|
|
140
|
-
function
|
|
141
|
-
return { state };
|
|
142
|
-
}
|
|
143
|
-
function isTypedQuery(value) {
|
|
144
|
-
return "state" in value;
|
|
145
|
-
}
|
|
146
|
-
async function queryRows(source, query) {
|
|
147
|
-
const state = isTypedQuery(query) ? query.state : query;
|
|
140
|
+
async function queryRows(source, state) {
|
|
148
141
|
return await source.queryRows(state);
|
|
149
142
|
}
|
|
150
|
-
|
|
151
|
-
const [currentRows, previousRows] = await Promise.all([queryRows(source, current), queryRows(source, previous)]);
|
|
152
|
-
return {
|
|
153
|
-
current: currentRows,
|
|
154
|
-
previous: previousRows
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
export { AttachedTableMissingError, ENGINE_QUERY_CAPABILITIES, createAttachedTableSource, createEngineQuerySource, createSqlQuerySource, queryComparisonRows, queryRows, rewriteForTableSource, runAnalyzerWithEngine, typedQuery };
|
|
143
|
+
export { AttachedTableMissingError, ENGINE_QUERY_CAPABILITIES, createAttachedTableSource, createEngineQuerySource, createSqlQuerySource, queryRows, rewriteForTableSource, runAnalyzerWithEngine };
|
|
@@ -2,31 +2,71 @@ import { manifestEntryKey, matchesManifestEntryFilter, matchesSyncStateFilter, m
|
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
|
-
import { mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
|
+
import { mkdir, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
6
|
import { lock } from "proper-lockfile";
|
|
7
7
|
function createFilesystemDataSource(opts) {
|
|
8
8
|
const root = resolve(opts.rootDir);
|
|
9
|
+
const readyDirectories = /* @__PURE__ */ new Map();
|
|
9
10
|
function pathFor(key) {
|
|
10
11
|
const resolved = resolve(root, key);
|
|
11
12
|
if (!resolved.startsWith(`${root}/`) && resolved !== root) throw new Error(`path escapes root: ${key}`);
|
|
12
13
|
return resolved;
|
|
13
14
|
}
|
|
15
|
+
async function ensureDirectory(path) {
|
|
16
|
+
let pending = readyDirectories.get(path);
|
|
17
|
+
if (!pending) {
|
|
18
|
+
pending = mkdir(path, { recursive: true }).then(() => void 0);
|
|
19
|
+
readyDirectories.set(path, pending);
|
|
20
|
+
pending.catch(() => readyDirectories.delete(path));
|
|
21
|
+
}
|
|
22
|
+
await pending;
|
|
23
|
+
}
|
|
14
24
|
return {
|
|
15
25
|
async read(key, range, signal) {
|
|
16
|
-
const
|
|
17
|
-
if (!range)
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
const path = pathFor(key);
|
|
27
|
+
if (!range) {
|
|
28
|
+
const bytes = await readFile(path, { signal });
|
|
29
|
+
return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
30
|
+
}
|
|
31
|
+
signal?.throwIfAborted();
|
|
32
|
+
const handle = await open(path, "r");
|
|
33
|
+
try {
|
|
34
|
+
const bytes = Buffer.allocUnsafe(range.length);
|
|
35
|
+
let totalRead = 0;
|
|
36
|
+
while (totalRead < range.length) {
|
|
37
|
+
signal?.throwIfAborted();
|
|
38
|
+
const { bytesRead } = await handle.read(bytes, totalRead, range.length - totalRead, range.offset + totalRead);
|
|
39
|
+
if (bytesRead === 0) break;
|
|
40
|
+
totalRead += bytesRead;
|
|
41
|
+
}
|
|
42
|
+
return new Uint8Array(bytes.buffer, bytes.byteOffset, totalRead);
|
|
43
|
+
} finally {
|
|
44
|
+
await handle.close();
|
|
45
|
+
}
|
|
20
46
|
},
|
|
21
47
|
async write(key, bytes) {
|
|
22
48
|
const path = pathFor(key);
|
|
23
|
-
|
|
24
|
-
await
|
|
49
|
+
const dir = dirname(path);
|
|
50
|
+
await ensureDirectory(dir);
|
|
51
|
+
try {
|
|
52
|
+
await writeFile(path, bytes);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.code !== "ENOENT") throw error;
|
|
55
|
+
readyDirectories.delete(dir);
|
|
56
|
+
await ensureDirectory(dir);
|
|
57
|
+
await writeFile(path, bytes);
|
|
58
|
+
}
|
|
25
59
|
},
|
|
26
60
|
async delete(keys) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
61
|
+
let next = 0;
|
|
62
|
+
async function worker() {
|
|
63
|
+
while (true) {
|
|
64
|
+
const index = next++;
|
|
65
|
+
if (index >= keys.length) return;
|
|
66
|
+
await rm(pathFor(keys[index]), { force: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
await Promise.all(Array.from({ length: Math.min(32, keys.length) }, worker));
|
|
30
70
|
},
|
|
31
71
|
async list(prefix) {
|
|
32
72
|
const full = pathFor(prefix);
|
|
@@ -61,14 +101,23 @@ async function* walkStream(dir) {
|
|
|
61
101
|
}
|
|
62
102
|
}
|
|
63
103
|
async function walk(dir, out) {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
104
|
+
const pending = [dir];
|
|
105
|
+
let next = 0;
|
|
106
|
+
while (next < pending.length) {
|
|
107
|
+
const batch = pending.slice(next, next + 32);
|
|
108
|
+
next += batch.length;
|
|
109
|
+
const listings = await Promise.all(batch.map(async (current) => ({
|
|
110
|
+
current,
|
|
111
|
+
entries: await readdir(current, { withFileTypes: true }).catch((err) => {
|
|
112
|
+
if (err.code === "ENOENT") return [];
|
|
113
|
+
throw err;
|
|
114
|
+
})
|
|
115
|
+
})));
|
|
116
|
+
for (const { current, entries } of listings) for (const entry of entries) {
|
|
117
|
+
const p = join(current, String(entry.name));
|
|
118
|
+
if (entry.isDirectory()) pending.push(p);
|
|
119
|
+
else out.push(p);
|
|
120
|
+
}
|
|
72
121
|
}
|
|
73
122
|
}
|
|
74
123
|
function lockFileFor(locksDir, scope) {
|
|
@@ -77,6 +126,16 @@ function lockFileFor(locksDir, scope) {
|
|
|
77
126
|
function createFilesystemManifestStore(opts) {
|
|
78
127
|
const manifestPath = resolve(opts.path);
|
|
79
128
|
const locksDir = join(dirname(manifestPath), "locks");
|
|
129
|
+
const readyDirectories = /* @__PURE__ */ new Map();
|
|
130
|
+
async function ensureDirectory(path) {
|
|
131
|
+
let pending = readyDirectories.get(path);
|
|
132
|
+
if (!pending) {
|
|
133
|
+
pending = mkdir(path, { recursive: true }).then(() => void 0);
|
|
134
|
+
readyDirectories.set(path, pending);
|
|
135
|
+
pending.catch(() => readyDirectories.delete(path));
|
|
136
|
+
}
|
|
137
|
+
await pending;
|
|
138
|
+
}
|
|
80
139
|
async function load() {
|
|
81
140
|
const content = await readFile(manifestPath, "utf8").catch((err) => {
|
|
82
141
|
if (err.code === "ENOENT") return null;
|
|
@@ -91,9 +150,16 @@ function createFilesystemManifestStore(opts) {
|
|
|
91
150
|
return parsed;
|
|
92
151
|
}
|
|
93
152
|
async function save(data) {
|
|
94
|
-
|
|
153
|
+
const manifestDir = dirname(manifestPath);
|
|
154
|
+
await ensureDirectory(manifestDir);
|
|
95
155
|
const tmp = `${manifestPath}.${randomBytes(6).toString("hex")}.tmp`;
|
|
96
|
-
|
|
156
|
+
const content = JSON.stringify(data);
|
|
157
|
+
await writeFile(tmp, content, "utf8").catch(async (error) => {
|
|
158
|
+
if (error.code !== "ENOENT") throw error;
|
|
159
|
+
readyDirectories.delete(manifestDir);
|
|
160
|
+
await ensureDirectory(manifestDir);
|
|
161
|
+
await writeFile(tmp, content, "utf8");
|
|
162
|
+
});
|
|
97
163
|
await rename(tmp, manifestPath).catch(async (err) => {
|
|
98
164
|
try {
|
|
99
165
|
await unlink(tmp);
|
|
@@ -175,9 +241,14 @@ function createFilesystemManifestStore(opts) {
|
|
|
175
241
|
});
|
|
176
242
|
},
|
|
177
243
|
async withLock(scope, fn) {
|
|
178
|
-
await
|
|
244
|
+
await ensureDirectory(locksDir);
|
|
179
245
|
const path = lockFileFor(locksDir, scope);
|
|
180
|
-
await writeFile(path, "", { flag: "a" })
|
|
246
|
+
await writeFile(path, "", { flag: "a" }).catch(async (error) => {
|
|
247
|
+
if (error.code !== "ENOENT") throw error;
|
|
248
|
+
readyDirectories.delete(locksDir);
|
|
249
|
+
await ensureDirectory(locksDir);
|
|
250
|
+
await writeFile(path, "", { flag: "a" });
|
|
251
|
+
});
|
|
181
252
|
const release = await lock(path, {
|
|
182
253
|
realpath: false,
|
|
183
254
|
stale: 3e4,
|
|
@@ -244,24 +315,22 @@ async function filesystemStats(rootDir) {
|
|
|
244
315
|
const keys = [];
|
|
245
316
|
await walkForStats(resolve(rootDir), keys);
|
|
246
317
|
let bytes = 0;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
318
|
+
let next = 0;
|
|
319
|
+
async function worker() {
|
|
320
|
+
while (true) {
|
|
321
|
+
const index = next++;
|
|
322
|
+
if (index >= keys.length) return;
|
|
323
|
+
const s = await stat(keys[index]);
|
|
324
|
+
bytes += s.size;
|
|
325
|
+
}
|
|
250
326
|
}
|
|
327
|
+
await Promise.all(Array.from({ length: Math.min(32, keys.length) }, worker));
|
|
251
328
|
return {
|
|
252
329
|
files: keys.length,
|
|
253
330
|
bytes
|
|
254
331
|
};
|
|
255
332
|
}
|
|
256
333
|
async function walkForStats(dir, out) {
|
|
257
|
-
|
|
258
|
-
if (err.code === "ENOENT") return [];
|
|
259
|
-
throw err;
|
|
260
|
-
});
|
|
261
|
-
for (const entry of entries) {
|
|
262
|
-
const p = join(dir, String(entry.name));
|
|
263
|
-
if (entry.isDirectory()) await walkForStats(p, out);
|
|
264
|
-
else out.push(p);
|
|
265
|
-
}
|
|
334
|
+
await walk(dir, out);
|
|
266
335
|
}
|
|
267
336
|
export { createFilesystemDataSource, createFilesystemManifestStore, filesystemStats };
|
|
@@ -45,6 +45,11 @@ interface HyparquetCodecOptions {
|
|
|
45
45
|
* hyparquet to avoid WASM linear-memory growth. Defaults to hyparquet.
|
|
46
46
|
*/
|
|
47
47
|
readRows?: (ctx: CodecCtx, key: string, dataSource: DataSource) => Promise<Row[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Maximum input-file reads held in flight during compaction. Defaults to 8;
|
|
50
|
+
* lower it for unusually large files to trade latency for peak JS memory.
|
|
51
|
+
*/
|
|
52
|
+
compactionReadConcurrency?: number;
|
|
48
53
|
}
|
|
49
54
|
declare function createHyparquetCodec(options?: HyparquetCodecOptions): ParquetCodec;
|
|
50
55
|
export { DecodeParquetOptions, EncodeFlexOptions, HyparquetCodecOptions, createHyparquetCodec, decodeParquetToRows, encodeRowsToParquet, encodeRowsToParquetFlex };
|
|
@@ -2,6 +2,8 @@ import { SCHEMAS, TABLE_METADATA } from "../_chunks/schema.mjs";
|
|
|
2
2
|
import { parquetReadObjects } from "../_chunks/libs/hyparquet.mjs";
|
|
3
3
|
import { ByteWriter, parquetWriteRows } from "../_chunks/libs/hyparquet-writer.mjs";
|
|
4
4
|
const ROW_GROUP_SIZE = 25e3;
|
|
5
|
+
const DEFAULT_COMPACTION_READ_CONCURRENCY = 8;
|
|
6
|
+
const MAX_COMPACTION_READ_CONCURRENCY = 32;
|
|
5
7
|
function basicTypeFor(colType) {
|
|
6
8
|
if (colType === "VARCHAR") return "STRING";
|
|
7
9
|
if (colType === "BIGINT") return "INT64";
|
|
@@ -120,7 +122,13 @@ function sortRowsByClusterKey(table, rows) {
|
|
|
120
122
|
return copy;
|
|
121
123
|
}
|
|
122
124
|
function naturalKeyFor(table, row) {
|
|
123
|
-
|
|
125
|
+
const key = TABLE_METADATA[table].sortKey;
|
|
126
|
+
let value = "";
|
|
127
|
+
for (let index = 0; index < key.length; index++) {
|
|
128
|
+
if (index > 0) value += "\0";
|
|
129
|
+
value += `${row[key[index]] ?? ""}`;
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
124
132
|
}
|
|
125
133
|
function encodeOrderedRows(rows, columns, rowGroupSize) {
|
|
126
134
|
const schema = buildWriteSchema(columns);
|
|
@@ -204,6 +212,11 @@ function normalizeDecodedDates(rows) {
|
|
|
204
212
|
return rows;
|
|
205
213
|
}
|
|
206
214
|
function createHyparquetCodec(options = {}) {
|
|
215
|
+
const readRows = options.readRows ?? (async (_ctx, key, dataSource) => {
|
|
216
|
+
return decodeParquetToRows(await dataSource.read(key));
|
|
217
|
+
});
|
|
218
|
+
const requestedConcurrency = options.compactionReadConcurrency;
|
|
219
|
+
const compactionReadConcurrency = typeof requestedConcurrency === "number" && Number.isFinite(requestedConcurrency) ? Math.max(1, Math.min(MAX_COMPACTION_READ_CONCURRENCY, Math.floor(requestedConcurrency))) : DEFAULT_COMPACTION_READ_CONCURRENCY;
|
|
207
220
|
return {
|
|
208
221
|
async writeRows(ctx, rows, key, dataSource) {
|
|
209
222
|
const bytes = encodeRowsToParquet(ctx.table, rows);
|
|
@@ -213,9 +226,7 @@ function createHyparquetCodec(options = {}) {
|
|
|
213
226
|
rowCount: rows.length
|
|
214
227
|
};
|
|
215
228
|
},
|
|
216
|
-
readRows
|
|
217
|
-
return decodeParquetToRows(await dataSource.read(key));
|
|
218
|
-
}),
|
|
229
|
+
readRows,
|
|
219
230
|
async compactRows(ctx, inputKeys, outputKey, dataSource) {
|
|
220
231
|
if (inputKeys.length === 0) {
|
|
221
232
|
const bytes = encodeRowsToParquet(ctx.table, []);
|
|
@@ -226,9 +237,13 @@ function createHyparquetCodec(options = {}) {
|
|
|
226
237
|
};
|
|
227
238
|
}
|
|
228
239
|
const byNaturalKey = /* @__PURE__ */ new Map();
|
|
229
|
-
for (
|
|
230
|
-
const
|
|
231
|
-
|
|
240
|
+
for (let offset = 0; offset < inputKeys.length; offset += compactionReadConcurrency) {
|
|
241
|
+
const batch = inputKeys.slice(offset, offset + compactionReadConcurrency);
|
|
242
|
+
const inputs = await Promise.all(batch.map((key) => dataSource.read(key)));
|
|
243
|
+
for (const input of inputs) {
|
|
244
|
+
const rows = await decodeParquetToRows(input);
|
|
245
|
+
for (let i = 0; i < rows.length; i++) byNaturalKey.set(naturalKeyFor(ctx.table, rows[i]), rows[i]);
|
|
246
|
+
}
|
|
232
247
|
}
|
|
233
248
|
const rows = [...byNaturalKey.values()];
|
|
234
249
|
const bytes = encodeRowsToParquet(ctx.table, rows);
|
|
@@ -6,6 +6,7 @@ const SHARD_RE = /^u_[^/]+\/manifest\/(?<siteId>[^/]+)\/(?<table>[^/]+)\/HEAD$/;
|
|
|
6
6
|
const CAS_BACKOFF_BASE_MS = 5;
|
|
7
7
|
const CAS_BACKOFF_CAP_MS = 250;
|
|
8
8
|
const SHARD_IO_CONCURRENCY = 8;
|
|
9
|
+
const R2_DELETE_BATCH_SIZE = 1e3;
|
|
9
10
|
async function casBackoff(attempt) {
|
|
10
11
|
const ceil = Math.min(CAS_BACKOFF_CAP_MS, CAS_BACKOFF_BASE_MS * 2 ** attempt);
|
|
11
12
|
await new Promise((resolve) => setTimeout(resolve, Math.random() * ceil));
|
|
@@ -331,15 +332,8 @@ function createR2ManifestStore(opts) {
|
|
|
331
332
|
},
|
|
332
333
|
async purgeTenant(filter) {
|
|
333
334
|
if (filter.userId !== userId) throw new Error(`purgeTenant: store is scoped to userId=${userId}, got ${filter.userId}`);
|
|
334
|
-
const
|
|
335
|
-
let entriesRemoved = 0;
|
|
336
|
-
let watermarksRemoved = 0;
|
|
337
|
-
let syncStatesRemoved = 0;
|
|
338
|
-
for (const { siteId, table } of shards) {
|
|
335
|
+
const purged = await mapWithConcurrency(await shardsForFilter({ siteId: filter.siteId }), SHARD_IO_CONCURRENCY, async ({ siteId, table }) => {
|
|
339
336
|
const { snapshot } = await readShard(siteId, table);
|
|
340
|
-
entriesRemoved += snapshot.entries.length;
|
|
341
|
-
watermarksRemoved += snapshot.watermarks.length;
|
|
342
|
-
syncStatesRemoved += snapshot.syncStates.length;
|
|
343
337
|
const prefix = shardPrefix(userId, siteId, table);
|
|
344
338
|
const keys = [];
|
|
345
339
|
let cursor;
|
|
@@ -352,12 +346,17 @@ function createR2ManifestStore(opts) {
|
|
|
352
346
|
for (const obj of res.objects) keys.push(obj.key);
|
|
353
347
|
cursor = res.truncated ? res.cursor : void 0;
|
|
354
348
|
} while (cursor);
|
|
355
|
-
|
|
356
|
-
|
|
349
|
+
for (let offset = 0; offset < keys.length; offset += R2_DELETE_BATCH_SIZE) await bucket.delete(keys.slice(offset, offset + R2_DELETE_BATCH_SIZE));
|
|
350
|
+
return {
|
|
351
|
+
entriesRemoved: snapshot.entries.length,
|
|
352
|
+
watermarksRemoved: snapshot.watermarks.length,
|
|
353
|
+
syncStatesRemoved: snapshot.syncStates.length
|
|
354
|
+
};
|
|
355
|
+
});
|
|
357
356
|
return {
|
|
358
|
-
entriesRemoved,
|
|
359
|
-
watermarksRemoved,
|
|
360
|
-
syncStatesRemoved
|
|
357
|
+
entriesRemoved: purged.reduce((sum, result) => sum + result.entriesRemoved, 0),
|
|
358
|
+
watermarksRemoved: purged.reduce((sum, result) => sum + result.watermarksRemoved, 0),
|
|
359
|
+
syncStatesRemoved: purged.reduce((sum, result) => sum + result.syncStatesRemoved, 0)
|
|
361
360
|
};
|
|
362
361
|
}
|
|
363
362
|
};
|
package/dist/adapters/r2.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const BUCKET_NAME_RE = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
|
|
2
2
|
const KEY_RE = /^[\w./=-]+$/;
|
|
3
|
+
const DELETE_CHUNK_SIZE = 1e3;
|
|
4
|
+
const DELETE_CONCURRENCY = 4;
|
|
3
5
|
function assertKey(key) {
|
|
4
6
|
if (!KEY_RE.test(key)) throw new Error(`createR2DataSource: refusing unsafe key ${JSON.stringify(key)}`);
|
|
5
7
|
}
|
|
@@ -22,8 +24,17 @@ function createR2DataSource(options) {
|
|
|
22
24
|
},
|
|
23
25
|
async delete(keys) {
|
|
24
26
|
if (keys.length === 0) return;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
let next = 0;
|
|
28
|
+
async function worker() {
|
|
29
|
+
while (true) {
|
|
30
|
+
const offset = next;
|
|
31
|
+
next += DELETE_CHUNK_SIZE;
|
|
32
|
+
if (offset >= keys.length) return;
|
|
33
|
+
await bucket.delete(keys.slice(offset, offset + DELETE_CHUNK_SIZE));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const chunkCount = Math.ceil(keys.length / DELETE_CHUNK_SIZE);
|
|
37
|
+
await Promise.all(Array.from({ length: Math.min(DELETE_CONCURRENCY, chunkCount) }, worker));
|
|
27
38
|
},
|
|
28
39
|
async list(prefix) {
|
|
29
40
|
const out = [];
|
package/dist/errors.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { EngineError, EngineErrorKind, engineErrorToException, engineErrors,
|
|
2
|
-
export { EngineError, EngineErrorKind, engineErrorToException, engineErrors,
|
|
1
|
+
import { EngineError, EngineErrorKind, engineErrorToException, engineErrors, isEngineError } from "./_chunks/errors.mjs";
|
|
2
|
+
export { EngineError, EngineErrorKind, engineErrorToException, engineErrors, isEngineError };
|
package/dist/errors.mjs
CHANGED
|
@@ -190,13 +190,10 @@ const ENGINE_ERROR_KINDS = /* @__PURE__ */ new Set([
|
|
|
190
190
|
function isEngineError(value) {
|
|
191
191
|
return typeof value === "object" && value !== null && ENGINE_ERROR_KINDS.has(value.kind) && typeof value.message === "string";
|
|
192
192
|
}
|
|
193
|
-
function formatEngineError(error) {
|
|
194
|
-
return error.message;
|
|
195
|
-
}
|
|
196
193
|
function engineErrorToException(error) {
|
|
197
194
|
const exception = new Error(error.message);
|
|
198
195
|
if ("cause" in error && error.cause !== void 0) exception.cause = error.cause;
|
|
199
196
|
exception.engineError = error;
|
|
200
197
|
return exception;
|
|
201
198
|
}
|
|
202
|
-
export { engineErrorToException, engineErrors,
|
|
199
|
+
export { engineErrorToException, engineErrors, isEngineError };
|
package/dist/iceberg/index.mjs
CHANGED
|
@@ -137,8 +137,12 @@ function dedupeByIdentity(table, records) {
|
|
|
137
137
|
const key = gscDataset(table, "int").tableSpec.identityColumns;
|
|
138
138
|
const seen = /* @__PURE__ */ new Map();
|
|
139
139
|
for (const rec of records) {
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
let identity = "";
|
|
141
|
+
for (let index = 0; index < key.length; index++) {
|
|
142
|
+
if (index > 0) identity += "\0";
|
|
143
|
+
identity += `${rec[key[index]] ?? ""}`;
|
|
144
|
+
}
|
|
145
|
+
seen.set(identity, rec);
|
|
142
146
|
}
|
|
143
147
|
return seen.size === records.length ? records : [...seen.values()];
|
|
144
148
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CodecCtx, CompactionThresholds, CompactionTier, DataSource, EngineOptions, FileSetRef, GcCtx, Grain, ListLiveFilter, LockScope, ManifestEntry, ManifestPurgeResult, ManifestStore, ParquetCodec, PurgeFilter, PurgeResult, PurgeUrlsResult, QueryCtx, QueryExecuteOptions, QueryExecuteResult, QueryExecutor, QueryProfiler, QueryResult, QuerySpan, Row, RunSQLOptions, SearchType, StorageEngine, SyncState, SyncStateDetail, SyncStateFilter, SyncStateKind, SyncStateScope, TableName, TenantCtx, Watermark, WatermarkFilter, WatermarkScope, WriteCtx, WriteResult, enumeratePartitions } from "./_chunks/storage.mjs";
|
|
2
2
|
import { DuckDBFactory, DuckDBHandle, canonicalEmptyParquetSchema, createDuckDBCodec, createDuckDBExecutor } from "./_chunks/duckdb.mjs";
|
|
3
3
|
import { ColumnDef, ColumnType, DrizzleSchema, SCHEMAS, TABLE_METADATA, TableSchema, allTables, countries, currentSchemaVersion, dates, dimensionToColumn, drizzleSchema, hourly_pages, inferTable, page_queries, pages, queries } from "./_chunks/schema.mjs";
|
|
4
|
-
import { EngineError, EngineErrorKind, engineErrorToException, engineErrors,
|
|
4
|
+
import { EngineError, EngineErrorKind, engineErrorToException, engineErrors, isEngineError } from "./_chunks/errors.mjs";
|
|
5
5
|
import { InspectionVerdict, SchedulePolicy, ScheduleState, fixedPolicy, inspectionPolicy, sitemapPolicy } from "./schedule.mjs";
|
|
6
6
|
import { IcebergTableName, Sink, SinkCapabilities, SinkCloseResult, SinkOptions, SinkSlice, SinkWriteResult } from "./_chunks/sink.mjs";
|
|
7
7
|
import { GscApiRow, IngestOptions, RowAccumulator, RowAccumulatorOptions, assembleDatesRow, createRowAccumulator, toPath, toSumPosition, transformGscRow } from "./ingest.mjs";
|
|
@@ -109,7 +109,6 @@ interface CreateIngestAccumulatorOptions extends RowAccumulatorOptions {
|
|
|
109
109
|
ctx: IngestAccumulatorCtx;
|
|
110
110
|
hooks: IngestAccumulatorHooks;
|
|
111
111
|
}
|
|
112
|
-
declare function createNoopIngestAccumulator(): IngestAccumulator;
|
|
113
112
|
declare function createIngestAccumulator(opts: CreateIngestAccumulatorOptions): IngestAccumulator;
|
|
114
113
|
declare function dayPartition(date: string): string;
|
|
115
114
|
/**
|
|
@@ -209,4 +208,4 @@ declare const MIN_SYNC_IMPRESSIONS = 1;
|
|
|
209
208
|
declare const MIN_COUNTRY_IMPRESSIONS = 10;
|
|
210
209
|
declare const MAX_SITEMAP_URLS_PER_SITE = 50000;
|
|
211
210
|
declare const MAX_TRACKED_URLS_PER_SITE = 200000;
|
|
212
|
-
export { type CodecCtx, type ColumnDef, type ColumnType, type CompactionThresholds, type CompactionTier, type CreateIngestAccumulatorOptions, DEFAULT_SEARCH_TYPE, type DataSource, type DateWeight, type DrizzleSchema, type DuckDBFactory, type DuckDBHandle, ENGINE_QUERY_CAPABILITIES, EngineError, EngineErrorKind, type EngineOptions, FILES_PLACEHOLDER, type FileSetRef, type FinalizeOptions, type FinalizeResult, type GcCtx, type Grain, type GscApiRow, type InMemorySink, type IngestAccumulator, type IngestAccumulatorCtx, type IngestAccumulatorEngine, type IngestAccumulatorHooks, type IngestOptions, type InspectionVerdict, type ListLiveFilter, type LockScope, MAX_DAY_BYTES, MAX_GSC_PAGES_R2, MAX_SITEMAP_URLS_PER_SITE, MAX_TRACKED_URLS_PER_SITE, MIN_COUNTRY_IMPRESSIONS, MIN_SYNC_IMPRESSIONS, type ManifestEntry, type ManifestPurgeResult, type ManifestStore, type ParquetCodec, type PurgeFilter, type PurgeResult, type PurgeUrlsResult, type QueryCtx, type QueryExecuteOptions, type QueryExecuteResult, type QueryExecutor, type QueryProfiler, type QueryResult, type QuerySpan, ROW_LIMIT_R2, type ResolvedQuery, type Row, type RowAccumulator, type RowAccumulatorOptions, type RunSQLOptions, SCHEMAS, type SchedulePolicy, type ScheduleState, type SearchType, type Sink, type SinkCapabilities, type SinkCloseResult, type SinkOptions, type SinkSlice, type SinkWriteResult, type StorageEngine, type StoredRow, type SyncState, type SyncStateDetail, type SyncStateFilter, type SyncStateKind, type SyncStateScope, type SyncTableName, TABLES_BY_SEARCH_TYPE, TABLE_METADATA, TABLE_TIERS, TIER_PRIORITY, type TableName, type TableSchema, type TableTier, type TenantCtx, type TieredTableName, WEIGHT_PRIORITY, type Watermark, type WatermarkFilter, type WatermarkScope, type WriteCtx, type WriteResult, allTables, assembleDatesRow, bindLiterals, canonicalEmptyParquetSchema, coerceRow, coerceRows, collectSpans, countries, createDuckDBCodec, createDuckDBExecutor, createIcebergResolverAdapter, createInMemorySink, createIngestAccumulator,
|
|
211
|
+
export { type CodecCtx, type ColumnDef, type ColumnType, type CompactionThresholds, type CompactionTier, type CreateIngestAccumulatorOptions, DEFAULT_SEARCH_TYPE, type DataSource, type DateWeight, type DrizzleSchema, type DuckDBFactory, type DuckDBHandle, ENGINE_QUERY_CAPABILITIES, EngineError, EngineErrorKind, type EngineOptions, FILES_PLACEHOLDER, type FileSetRef, type FinalizeOptions, type FinalizeResult, type GcCtx, type Grain, type GscApiRow, type InMemorySink, type IngestAccumulator, type IngestAccumulatorCtx, type IngestAccumulatorEngine, type IngestAccumulatorHooks, type IngestOptions, type InspectionVerdict, type ListLiveFilter, type LockScope, MAX_DAY_BYTES, MAX_GSC_PAGES_R2, MAX_SITEMAP_URLS_PER_SITE, MAX_TRACKED_URLS_PER_SITE, MIN_COUNTRY_IMPRESSIONS, MIN_SYNC_IMPRESSIONS, type ManifestEntry, type ManifestPurgeResult, type ManifestStore, type ParquetCodec, type PurgeFilter, type PurgeResult, type PurgeUrlsResult, type QueryCtx, type QueryExecuteOptions, type QueryExecuteResult, type QueryExecutor, type QueryProfiler, type QueryResult, type QuerySpan, ROW_LIMIT_R2, type ResolvedQuery, type Row, type RowAccumulator, type RowAccumulatorOptions, type RunSQLOptions, SCHEMAS, type SchedulePolicy, type ScheduleState, type SearchType, type Sink, type SinkCapabilities, type SinkCloseResult, type SinkOptions, type SinkSlice, type SinkWriteResult, type StorageEngine, type StoredRow, type SyncState, type SyncStateDetail, type SyncStateFilter, type SyncStateKind, type SyncStateScope, type SyncTableName, TABLES_BY_SEARCH_TYPE, TABLE_METADATA, TABLE_TIERS, TIER_PRIORITY, type TableName, type TableSchema, type TableTier, type TenantCtx, type TieredTableName, WEIGHT_PRIORITY, type Watermark, type WatermarkFilter, type WatermarkScope, type WriteCtx, type WriteResult, allTables, assembleDatesRow, bindLiterals, canonicalEmptyParquetSchema, coerceRow, coerceRows, collectSpans, countries, createDuckDBCodec, createDuckDBExecutor, createIcebergResolverAdapter, createInMemorySink, createIngestAccumulator, createParquetResolverAdapter, createQueryProfiler, createR2SqlResolverAdapter, createRowAccumulator, createSqlQuerySource, createStorageEngine, currentSchemaVersion, dates, dayPartition, dimensionToColumn, drizzleSchema, engineErrorToException, engineErrors, enumeratePartitions, fixedPolicy, formatLiteral, getDateWeight, getTableTier, getTablesForTier, hourPartition, hourly_pages, inferLegacyTier, inferSearchType, inferTable, inspectionPolicy, isEngineError, objectKey, page_queries, pages, parseEnabledSearchTypes, pgResolverAdapter, queries, rebuildDailyFromHourly, resolveParquetSQL, sitemapPolicy, substituteNamedFiles, toPath, toSumPosition, transformGscRow, validateEnabledSearchTypes, validateEnabledSearchTypesResult };
|