@gscdump/engine 1.0.1 → 1.0.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/README.md +4 -2
- package/dist/_chunks/coerce.mjs +1 -1
- package/dist/_chunks/engine.mjs +1 -1
- package/dist/_chunks/entities.mjs +3 -91
- package/dist/_chunks/index.d.mts +44 -95
- package/dist/_chunks/resolver.mjs +105 -105
- package/dist/_chunks/schema2.mjs +2 -1
- package/dist/_chunks/sink.d.mts +2 -1
- package/dist/_chunks/snapshot.d.mts +27 -1
- package/dist/adapters/node.d.mts +6 -2
- package/dist/adapters/node.mjs +93 -2
- package/dist/entities.d.mts +3 -36
- package/dist/entities.mjs +4 -2
- package/dist/entity-keys.d.mts +27 -0
- package/dist/entity-keys.mjs +93 -0
- package/dist/iceberg/index.mjs +7 -7
- package/dist/index.d.mts +6 -107
- package/dist/index.mjs +101 -132
- package/dist/ingest-accumulator.d.mts +97 -0
- package/dist/ingest-accumulator.mjs +123 -0
- package/dist/period/index.d.mts +1 -1
- package/dist/planner.d.mts +14 -2
- package/dist/report/index.d.mts +1 -1
- package/dist/resolver/index.d.mts +53 -1
- package/dist/rollups.mjs +3 -2
- package/dist/source/index.d.mts +103 -2
- package/dist/source/index.mjs +142 -1
- package/package.json +14 -4
- package/dist/_chunks/duckdb.d.mts +0 -27
- package/dist/_chunks/index2.d.mts +0 -53
- package/dist/_chunks/pg-adapter.d.mts +0 -56
- package/dist/_chunks/planner.d.mts +0 -15
- package/dist/_chunks/source.mjs +0 -143
- package/dist/adapters/duckdb-node.d.mts +0 -7
- package/dist/adapters/duckdb-node.mjs +0 -94
- package/dist/schedule.mjs +0 -100
- /package/dist/{schedule.d.mts → _chunks/schedule.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -23,15 +23,17 @@ Optional peers (install only what your runtime needs):
|
|
|
23
23
|
|
|
24
24
|
| Subpath | Purpose |
|
|
25
25
|
|---|---|
|
|
26
|
-
| `@gscdump/engine` |
|
|
26
|
+
| `@gscdump/engine` | Storage runtime: `createStorageEngine`, codec/executor factories, storage contracts. |
|
|
27
27
|
| `@gscdump/engine/contracts` | `StorageEngine`, `Row`, `TableName`, `WriteCtx`, `ManifestEntry`, ... |
|
|
28
28
|
| `@gscdump/engine/schema` | `SCHEMAS`, `allTables`, `inferTable`, column metadata. |
|
|
29
29
|
| `@gscdump/engine/planner` | `resolveToSQL`, `enumeratePartitions`, partition planning. |
|
|
30
30
|
| `@gscdump/engine/ingest` | GSC row → storage row helpers (`createRowAccumulator`, `transformGscRow`). |
|
|
31
|
+
| `@gscdump/engine/ingest-accumulator` | Stateful ingest flushing behind injected storage and recovery hooks. |
|
|
31
32
|
| `@gscdump/engine/sql` | SQL literal binding helpers (`bindLiterals`, `formatLiteral`). |
|
|
32
33
|
| `@gscdump/engine/sql-fragments` | Reusable SQL fragments shared across analyzers. |
|
|
33
34
|
| `@gscdump/engine/rollups` | Pre-aggregated rollup contracts + helpers. |
|
|
34
|
-
| `@gscdump/engine/entities` |
|
|
35
|
+
| `@gscdump/engine/entities` | Parquet-backed entity stores and their record contracts. |
|
|
36
|
+
| `@gscdump/engine/entity-keys` | Pure entity object-key and URL-hash helpers, with no Parquet runtime. |
|
|
35
37
|
| `@gscdump/engine/resolver` | Dialect-neutral SQL composition: `ResolverAdapter`, `pgResolverAdapter`, `resolveToSQL`. |
|
|
36
38
|
| `@gscdump/engine/scope` | Multi-tenant scope predicates. |
|
|
37
39
|
| `@gscdump/engine/arrow` | Apache Arrow utilities for engine result conversion. |
|
package/dist/_chunks/coerce.mjs
CHANGED
package/dist/_chunks/engine.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { SCHEMAS, TABLE_METADATA, currentSchemaVersion, dateColumnsFor, dedupeBy
|
|
|
4
4
|
import { compactTieredImpl, compileLogicalQueryPlan, dedupeOverlappingTiers, splitOverlappingTiers, substituteNamedFiles } from "./parquet-plan.mjs";
|
|
5
5
|
import { dateReplaceClause as dateReplaceClause$1 } from "../sql-fragments.mjs";
|
|
6
6
|
import { sqlEscape } from "../sql-bind.mjs";
|
|
7
|
-
import { encodeJsonBigintSafe } from "@gscdump/lakehouse";
|
|
7
|
+
import { encodeJsonBigintSafe } from "@gscdump/lakehouse/bigint";
|
|
8
8
|
import { buildLogicalPlan } from "gscdump/query/plan";
|
|
9
9
|
import { normalizeUrl } from "gscdump/normalize";
|
|
10
10
|
const DEFAULT_BUFFER_READ_CONCURRENCY = 16;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { emptyTypesKey, hashSortedUrlList, hashUrl, hashUrlList, indexingMetadataIndexKey, inspectionBaseKey, inspectionEventKey, inspectionEventsPrefix, inspectionHistoryPrefix, inspectionHistoryShardKey, inspectionParquetKey, queryDimMetaKey, queryDimParquetKey, sitemapHistoryKey, sitemapIndexKey, sitemapUrlsDeltaKey, sitemapUrlsIndexKey, sitemapUrlsIndexPrefix, sitemapUrlsPrefix } from "../entity-keys.mjs";
|
|
1
2
|
import { decodeParquetToRows, encodeRowsToParquetFlex } from "../adapters/hyparquet.mjs";
|
|
2
|
-
import { encodeJsonBigintSafe } from "@gscdump/lakehouse";
|
|
3
|
+
import { encodeJsonBigintSafe } from "@gscdump/lakehouse/bigint";
|
|
3
4
|
function isMissingKeyError(e) {
|
|
4
5
|
if (typeof e !== "object" || e === null) return false;
|
|
5
6
|
if (e.code === "ENOENT") return true;
|
|
@@ -41,15 +42,6 @@ const QUERY_DIM_COLUMNS = [
|
|
|
41
42
|
nullable: false
|
|
42
43
|
}
|
|
43
44
|
];
|
|
44
|
-
function queryDimPrefix(ctx) {
|
|
45
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/query_dim` : `u_${ctx.userId}/entities/query_dim`;
|
|
46
|
-
}
|
|
47
|
-
function queryDimParquetKey(ctx) {
|
|
48
|
-
return `${queryDimPrefix(ctx)}/index.parquet`;
|
|
49
|
-
}
|
|
50
|
-
function queryDimMetaKey(ctx) {
|
|
51
|
-
return `${queryDimPrefix(ctx)}/index.json`;
|
|
52
|
-
}
|
|
53
45
|
function buildQueryDimRecords(queries, deps) {
|
|
54
46
|
const seen = /* @__PURE__ */ new Set();
|
|
55
47
|
const out = [];
|
|
@@ -125,45 +117,7 @@ async function mapEntityIo(items, fn) {
|
|
|
125
117
|
await Promise.all(Array.from({ length: Math.min(ENTITY_IO_CONCURRENCY, items.length) }, worker));
|
|
126
118
|
return results;
|
|
127
119
|
}
|
|
128
|
-
function inspectionIndexKey(ctx) {
|
|
129
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/inspections/index.json` : `u_${ctx.userId}/entities/inspections/index.json`;
|
|
130
|
-
}
|
|
131
|
-
function emptyTypesKey(ctx) {
|
|
132
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/empty-types.json` : `u_${ctx.userId}/entities/empty-types.json`;
|
|
133
|
-
}
|
|
134
|
-
function inspectionParquetKey(ctx) {
|
|
135
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/inspections/index.parquet` : `u_${ctx.userId}/entities/inspections/index.parquet`;
|
|
136
|
-
}
|
|
137
|
-
function inspectionEventsPrefix(ctx) {
|
|
138
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/inspections/events` : `u_${ctx.userId}/entities/inspections/events`;
|
|
139
|
-
}
|
|
140
|
-
function inspectionEventKey(ctx, yearMonth, batchId) {
|
|
141
|
-
return `${inspectionEventsPrefix(ctx)}/${yearMonth}/${batchId}.parquet`;
|
|
142
|
-
}
|
|
143
|
-
function inspectionBaseKey(ctx) {
|
|
144
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/inspections/base.parquet` : `u_${ctx.userId}/entities/inspections/base.parquet`;
|
|
145
|
-
}
|
|
146
120
|
const INSPECTION_EVENT_KEY_RE = /\/inspections\/events\/\d{4}-\d{2}\/[^/]+\.parquet$/;
|
|
147
|
-
function inspectionHistoryPrefix(ctx, yearMonth) {
|
|
148
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/inspections/history/${yearMonth}` : `u_${ctx.userId}/entities/inspections/history/${yearMonth}`;
|
|
149
|
-
}
|
|
150
|
-
function inspectionHistoryShardKey(ctx, yearMonth, batchId) {
|
|
151
|
-
return `${inspectionHistoryPrefix(ctx, yearMonth)}/${batchId}.json`;
|
|
152
|
-
}
|
|
153
|
-
function hashUrl(url) {
|
|
154
|
-
let hi = 2166136261;
|
|
155
|
-
let lo = 3421674724;
|
|
156
|
-
for (let i = 0; i < url.length; i++) {
|
|
157
|
-
const c = url.charCodeAt(i);
|
|
158
|
-
lo ^= c;
|
|
159
|
-
const loMul = Math.imul(lo, 435) >>> 0;
|
|
160
|
-
const carry = Math.floor(lo * 435 / 4294967296);
|
|
161
|
-
const hiMul = Math.imul(hi, 435) + Math.imul(lo, 1) + carry >>> 0;
|
|
162
|
-
lo = loMul;
|
|
163
|
-
hi = hiMul;
|
|
164
|
-
}
|
|
165
|
-
return (hi >>> 0).toString(16).padStart(8, "0") + (lo >>> 0).toString(16).padStart(8, "0");
|
|
166
|
-
}
|
|
167
121
|
const INSPECTION_HISTORY_MAX_BYTES = 5 * 1024 * 1024;
|
|
168
122
|
const INSPECTION_PARQUET_COLUMNS = [
|
|
169
123
|
{
|
|
@@ -459,24 +413,6 @@ function createInspectionStore(opts) {
|
|
|
459
413
|
}
|
|
460
414
|
};
|
|
461
415
|
}
|
|
462
|
-
function sitemapIndexKey(ctx) {
|
|
463
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/sitemaps/index.json` : `u_${ctx.userId}/entities/sitemaps/index.json`;
|
|
464
|
-
}
|
|
465
|
-
function sitemapHistoryKey(ctx, feedpathHash, capturedAtMs) {
|
|
466
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/sitemaps/history/${feedpathHash}__${capturedAtMs}.json` : `u_${ctx.userId}/entities/sitemaps/history/${feedpathHash}__${capturedAtMs}.json`;
|
|
467
|
-
}
|
|
468
|
-
function sitemapUrlsPrefix(ctx) {
|
|
469
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/sitemaps/urls` : `u_${ctx.userId}/entities/sitemaps/urls`;
|
|
470
|
-
}
|
|
471
|
-
function sitemapUrlsIndexPrefix(ctx) {
|
|
472
|
-
return `${sitemapUrlsPrefix(ctx)}/by-feed`;
|
|
473
|
-
}
|
|
474
|
-
function sitemapUrlsIndexKey(ctx, feedpathHash) {
|
|
475
|
-
return `${sitemapUrlsIndexPrefix(ctx)}/${feedpathHash}/index.parquet`;
|
|
476
|
-
}
|
|
477
|
-
function sitemapUrlsDeltaKey(ctx, feedpathHash, date) {
|
|
478
|
-
return `${sitemapUrlsPrefix(ctx)}/deltas/${date}__${feedpathHash}.parquet`;
|
|
479
|
-
}
|
|
480
416
|
const SITEMAP_URLS_DELTA_PREFIX_RE = /\/urls\/deltas\/(\d{4}-\d{2}-\d{2})__([0-9a-f]+)\.parquet$/;
|
|
481
417
|
const URLS_INDEX_COLUMNS = [
|
|
482
418
|
{
|
|
@@ -584,27 +520,6 @@ function urlRecordToRow(r) {
|
|
|
584
520
|
function isoDate(ms) {
|
|
585
521
|
return new Date(ms).toISOString().slice(0, 10);
|
|
586
522
|
}
|
|
587
|
-
function hashUrlList(urls) {
|
|
588
|
-
return hashSortedUrlList(urls.map((u) => u.loc).sort());
|
|
589
|
-
}
|
|
590
|
-
function hashSortedUrlList(locs) {
|
|
591
|
-
let hi = 2166136261;
|
|
592
|
-
let lo = 3421674724;
|
|
593
|
-
for (let locIndex = 0; locIndex < locs.length; locIndex++) {
|
|
594
|
-
const loc = locs[locIndex];
|
|
595
|
-
const length = loc.length + (locIndex < locs.length - 1 ? 1 : 0);
|
|
596
|
-
for (let i = 0; i < length; i++) {
|
|
597
|
-
const c = i < loc.length ? loc.charCodeAt(i) : 10;
|
|
598
|
-
lo ^= c;
|
|
599
|
-
const loMul = Math.imul(lo, 435) >>> 0;
|
|
600
|
-
const carry = Math.floor(lo * 435 / 4294967296);
|
|
601
|
-
const hiMul = Math.imul(hi, 435) + Math.imul(lo, 1) + carry >>> 0;
|
|
602
|
-
lo = loMul;
|
|
603
|
-
hi = hiMul;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
return (hi >>> 0).toString(16).padStart(8, "0") + (lo >>> 0).toString(16).padStart(8, "0");
|
|
607
|
-
}
|
|
608
523
|
function createSitemapStore(opts) {
|
|
609
524
|
const ds = opts.dataSource;
|
|
610
525
|
const hash = opts.hash ?? hashUrl;
|
|
@@ -968,9 +883,6 @@ function createSitemapStore(opts) {
|
|
|
968
883
|
}
|
|
969
884
|
};
|
|
970
885
|
}
|
|
971
|
-
function indexingMetadataIndexKey(ctx) {
|
|
972
|
-
return ctx.siteId ? `u_${ctx.userId}/${ctx.siteId}/entities/indexing/index.json` : `u_${ctx.userId}/entities/indexing/index.json`;
|
|
973
|
-
}
|
|
974
886
|
function createIndexingMetadataStore(opts) {
|
|
975
887
|
const ds = opts.dataSource;
|
|
976
888
|
const hash = opts.hash ?? hashUrl;
|
|
@@ -1052,4 +964,4 @@ function createEmptyTypesStore(opts) {
|
|
|
1052
964
|
}
|
|
1053
965
|
};
|
|
1054
966
|
}
|
|
1055
|
-
export { INSPECTION_HISTORY_MAX_BYTES, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapStore
|
|
967
|
+
export { INSPECTION_HISTORY_MAX_BYTES, buildQueryDimRecords, createEmptyTypesStore, createIndexingMetadataStore, createInspectionStore, createQueryDimStore, createSitemapStore };
|
package/dist/_chunks/index.d.mts
CHANGED
|
@@ -1,104 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Run a query with positional (`?`) bound parameters. Return objects keyed
|
|
12
|
-
* by column name. BIGINT → number coercion is applied by the source factory
|
|
13
|
-
* (see `coerceRows`); runners only need to handle DATE → ISO string (or
|
|
14
|
-
* let the analyzer reducer normalize via `num(v)`/`str(v)`).
|
|
15
|
-
*/
|
|
16
|
-
query: (sql: string, params?: unknown[], signal?: AbortSignal) => Promise<Row[]>;
|
|
1
|
+
import { AnalysisParams } from "./analysis-types.mjs";
|
|
2
|
+
type WindowPreset = 'last-7d' | 'last-28d' | 'last-30d' | 'last-90d' | 'last-180d' | 'last-365d' | 'mtd' | 'ytd' | 'custom';
|
|
3
|
+
type ComparisonMode = 'none' | 'prev-period' | 'yoy';
|
|
4
|
+
interface ResolveWindowOptions {
|
|
5
|
+
preset: WindowPreset;
|
|
6
|
+
comparison?: ComparisonMode;
|
|
7
|
+
anchor?: string;
|
|
8
|
+
start?: string;
|
|
9
|
+
end?: string;
|
|
17
10
|
}
|
|
18
|
-
interface
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* List of table names actually attached to this connection. When provided,
|
|
28
|
-
* `executeSql` short-circuits with a specific "table not attached" error
|
|
29
|
-
* if the SQL plan references a table that isn't in this list — letting
|
|
30
|
-
* callers (e.g. the analytics layer) route to cloud fallback without
|
|
31
|
-
* paying the SQL execution cost. Omit to disable the check.
|
|
32
|
-
*/
|
|
33
|
-
attachedTables?: readonly string[];
|
|
34
|
-
/**
|
|
35
|
-
* Dialect adapter surfaced on the source for analyzers that compose SQL
|
|
36
|
-
* from a `BuilderState` at plan-build time (e.g. `data-query`,
|
|
37
|
-
* `data-detail`). Attached-table sources execute pg-flavored DuckDB SQL,
|
|
38
|
-
* so callers should pass `pgResolverAdapter` here.
|
|
39
|
-
*/
|
|
40
|
-
adapter?: ResolverAdapter<any>;
|
|
11
|
+
interface ResolvedWindow {
|
|
12
|
+
start: string;
|
|
13
|
+
end: string;
|
|
14
|
+
days: number;
|
|
15
|
+
comparison?: {
|
|
16
|
+
start: string;
|
|
17
|
+
end: string;
|
|
18
|
+
};
|
|
41
19
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
constructor(missing: readonly string[]);
|
|
20
|
+
interface AnalysisPeriod {
|
|
21
|
+
startDate: string;
|
|
22
|
+
endDate: string;
|
|
46
23
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
name: string;
|
|
51
|
-
/** Telemetry tag stamped onto analyzer result meta. */
|
|
52
|
-
kind?: AnalysisSourceKind;
|
|
53
|
-
/** Dialect-specific adapter; compiles `BuilderState` → `{ sql, params }`. */
|
|
54
|
-
adapter: ResolverAdapter<TKey>;
|
|
55
|
-
/** Drives the underlying DB. Called for both typed queries and raw SQL. */
|
|
56
|
-
execute: (sql: string, params: unknown[]) => Promise<QueryRow[]>;
|
|
57
|
-
/** Tenant id for multi-tenant dialects; forwarded to `resolveToSQL`. */
|
|
58
|
-
siteId?: string | number;
|
|
59
|
-
/**
|
|
60
|
-
* Search-type scope for multi-tenant dialects; forwarded to `resolveToSQL`.
|
|
61
|
-
* `number` = int-encoded code (`SEARCH_TYPE_INT`) for INT `search_type`
|
|
62
|
-
* catalogs (bound bare so the int partition prunes); `string` otherwise.
|
|
63
|
-
*/
|
|
64
|
-
searchType?: string | number;
|
|
65
|
-
/** Additional capability flags merged on top of `adapter.capabilities`. */
|
|
66
|
-
extraCapabilities?: Partial<SourceCapabilities>;
|
|
24
|
+
interface ComparisonPeriod {
|
|
25
|
+
current: AnalysisPeriod;
|
|
26
|
+
previous: AnalysisPeriod;
|
|
67
27
|
}
|
|
68
|
-
declare function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
engine: StorageEngine;
|
|
78
|
-
ctx: TenantCtx;
|
|
28
|
+
declare function defaultEndDate(): string;
|
|
29
|
+
declare function periodOf(params: AnalysisParams): AnalysisPeriod;
|
|
30
|
+
declare function comparisonOf(params: AnalysisParams): ComparisonPeriod;
|
|
31
|
+
declare function resolveWindow(opts: ResolveWindowOptions): ResolvedWindow;
|
|
32
|
+
interface PadTimeseriesOptions<T> {
|
|
33
|
+
/** ISO date (YYYY-MM-DD), inclusive lower bound. */
|
|
34
|
+
startDate: string;
|
|
35
|
+
/** ISO date (YYYY-MM-DD), inclusive upper bound. */
|
|
36
|
+
endDate: string;
|
|
79
37
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* source returns rows from one cohort instead of unioning web + non-web
|
|
83
|
-
* parquet. Undefined preserves legacy cross-type behaviour for web-only
|
|
84
|
-
* tenants and admin paths.
|
|
38
|
+
* Row to insert for missing dates. Defaults to `{ clicks: 0, impressions: 0, ctr: 0, position: 0 }`.
|
|
39
|
+
* The `date` field is set automatically.
|
|
85
40
|
*/
|
|
86
|
-
|
|
41
|
+
fill?: Omit<T, 'date'>;
|
|
42
|
+
/** Row-field that carries the ISO date. Defaults to `date`. */
|
|
43
|
+
dateKey?: string;
|
|
87
44
|
}
|
|
45
|
+
type DateRowShape = Record<string, unknown> & {
|
|
46
|
+
date?: unknown;
|
|
47
|
+
};
|
|
88
48
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* `engine.runSQL` and requires `opts.fileSets` (with a `FILES` entry so the
|
|
92
|
-
* target table can be resolved for partition lookup).
|
|
93
|
-
*/
|
|
94
|
-
declare function createEngineQuerySource(options: EngineQuerySourceOptions): AnalysisQuerySource;
|
|
95
|
-
/**
|
|
96
|
-
* Convenience: wrap a storage engine + tenant ctx in a source and dispatch.
|
|
97
|
-
* Equivalent to wrapping `createEngineQuerySource`, with omitted searchType
|
|
98
|
-
* defaulted to web at this public helper boundary.
|
|
49
|
+
* Pad rows so every calendar day in `[startDate, endDate]` appears at least
|
|
50
|
+
* once. Existing dates keep all their rows (grouped timeseries safe).
|
|
99
51
|
*/
|
|
100
|
-
declare function
|
|
101
|
-
|
|
102
|
-
}, ctx: TenantCtx, params: AnalysisParams, registry: AnalyzerRegistry): Promise<AnalysisResult>;
|
|
103
|
-
declare function queryRows<TRow = QueryRow>(source: AnalysisQuerySource, state: BuilderState): Promise<TRow[]>;
|
|
104
|
-
export { AttachedTableMissingError, AttachedTableRunner, AttachedTableSourceOptions, CreateSqlQuerySourceOptions, ENGINE_QUERY_CAPABILITIES, EngineQuerySourceOptions, createAttachedTableSource, createEngineQuerySource, createSqlQuerySource, queryRows, runAnalyzerWithEngine };
|
|
52
|
+
declare function padTimeseries<T extends DateRowShape = DateRowShape>(rows: readonly T[], options: PadTimeseriesOptions<T>): T[];
|
|
53
|
+
export { AnalysisPeriod, ComparisonMode, ComparisonPeriod, PadTimeseriesOptions, ResolveWindowOptions, ResolvedWindow, WindowPreset, comparisonOf, defaultEndDate, padTimeseries, periodOf, resolveWindow };
|
|
@@ -2,10 +2,10 @@ import { SCHEMAS, drizzleSchema } from "./schema.mjs";
|
|
|
2
2
|
import { enumeratePartitions } from "./parquet-plan.mjs";
|
|
3
3
|
import { escapeLike } from "../sql-fragments.mjs";
|
|
4
4
|
import "../planner.mjs";
|
|
5
|
-
import { DEFAULT_PARTITION_KEY_ENCODING } from "./schema2.mjs";
|
|
6
5
|
import { UnresolvableDatasetError, buildLogicalComparisonPlan, buildLogicalPlan, inferDataset as inferLogicalDataset, isDatasetResolvable } from "gscdump/query/plan";
|
|
7
6
|
import { PgDialect, pgTable, varchar } from "drizzle-orm/pg-core";
|
|
8
7
|
import { normalizeUrl } from "gscdump/normalize";
|
|
8
|
+
import { DEFAULT_PARTITION_KEY_ENCODING } from "@gscdump/lakehouse/schema";
|
|
9
9
|
import { sql } from "drizzle-orm";
|
|
10
10
|
import { and as and$1, gte as gte$1, impressions } from "gscdump/query";
|
|
11
11
|
const DIMENSION_SURFACES = {
|
|
@@ -392,110 +392,6 @@ function createResolverAdapter(config) {
|
|
|
392
392
|
compile: config.compile
|
|
393
393
|
};
|
|
394
394
|
}
|
|
395
|
-
const pgDialect = new PgDialect();
|
|
396
|
-
function withTenantCols(tableName, baseTable) {
|
|
397
|
-
const t = pgTable(tableName, {
|
|
398
|
-
site_id: varchar("site_id").notNull(),
|
|
399
|
-
search_type: varchar("search_type").notNull()
|
|
400
|
-
});
|
|
401
|
-
return {
|
|
402
|
-
...baseTable,
|
|
403
|
-
site_id: t.site_id,
|
|
404
|
-
search_type: t.search_type
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
const icebergSchema = {
|
|
408
|
-
pages: withTenantCols("pages", drizzleSchema.pages),
|
|
409
|
-
queries: withTenantCols("queries", drizzleSchema.queries),
|
|
410
|
-
countries: withTenantCols("countries", drizzleSchema.countries),
|
|
411
|
-
page_queries: withTenantCols("page_queries", drizzleSchema.page_queries),
|
|
412
|
-
dates: withTenantCols("dates", drizzleSchema.dates),
|
|
413
|
-
search_appearance: withTenantCols("search_appearance", drizzleSchema.search_appearance),
|
|
414
|
-
search_appearance_pages: withTenantCols("search_appearance_pages", drizzleSchema.search_appearance_pages),
|
|
415
|
-
search_appearance_queries: withTenantCols("search_appearance_queries", drizzleSchema.search_appearance_queries),
|
|
416
|
-
search_appearance_page_queries: withTenantCols("search_appearance_page_queries", drizzleSchema.search_appearance_page_queries),
|
|
417
|
-
hourly_pages: withTenantCols("hourly_pages", drizzleSchema.hourly_pages)
|
|
418
|
-
};
|
|
419
|
-
function compilePg(query) {
|
|
420
|
-
const compiled = pgDialect.sqlToQuery(query);
|
|
421
|
-
return {
|
|
422
|
-
sql: compiled.sql,
|
|
423
|
-
params: compiled.params
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
const PG_BASE_CONFIG = {
|
|
427
|
-
schema: drizzleSchema,
|
|
428
|
-
datasetToTableKey: {
|
|
429
|
-
pages: "pages",
|
|
430
|
-
queries: "queries",
|
|
431
|
-
page_queries: "page_queries",
|
|
432
|
-
countries: "countries",
|
|
433
|
-
dates: "dates",
|
|
434
|
-
search_appearance: "search_appearance",
|
|
435
|
-
search_appearance_pages: "search_appearance_pages",
|
|
436
|
-
search_appearance_queries: "search_appearance_queries",
|
|
437
|
-
search_appearance_page_queries: "search_appearance_page_queries",
|
|
438
|
-
hourly_pages: "hourly_pages"
|
|
439
|
-
},
|
|
440
|
-
metricCast: "DOUBLE",
|
|
441
|
-
regexPredicate: (expr, pattern, negate) => negate ? sql`NOT regexp_matches(${expr}, ${pattern})` : sql`regexp_matches(${expr}, ${pattern})`,
|
|
442
|
-
urlToPathExpr: (col) => `CASE WHEN ${col} LIKE 'http%' THEN COALESCE(NULLIF(regexp_replace(${col}, '^https?://[^/]+', ''), ''), '/') ELSE ${col} END`,
|
|
443
|
-
includeSiteId: false,
|
|
444
|
-
compile: compilePg,
|
|
445
|
-
capabilities: {
|
|
446
|
-
regex: true,
|
|
447
|
-
comparisonJoin: true,
|
|
448
|
-
windowTotals: true
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
const pgResolverAdapter = createResolverAdapter({
|
|
452
|
-
...PG_BASE_CONFIG,
|
|
453
|
-
tableLabel: "pg-resolver-adapter"
|
|
454
|
-
});
|
|
455
|
-
function createParquetResolverAdapter(options = {}) {
|
|
456
|
-
return createResolverAdapter({
|
|
457
|
-
...PG_BASE_CONFIG,
|
|
458
|
-
tableLabel: "parquet-resolver-adapter",
|
|
459
|
-
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
460
|
-
tableRef: (tk) => sql.raw(`read_parquet({{FILES}}, union_by_name = true) AS "${tk}"`),
|
|
461
|
-
queryDimTableRef: () => sql.raw("read_parquet({{QUERY_DIM}}, union_by_name = true) AS \"query_dim\"")
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
function createIcebergResolverAdapter(options = {}) {
|
|
465
|
-
return createResolverAdapter({
|
|
466
|
-
...PG_BASE_CONFIG,
|
|
467
|
-
schema: icebergSchema,
|
|
468
|
-
includeSiteId: true,
|
|
469
|
-
includeSearchType: true,
|
|
470
|
-
tableLabel: "iceberg-resolver-adapter",
|
|
471
|
-
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
472
|
-
tableRef: (tk) => sql.raw(`"${tk}"`),
|
|
473
|
-
queryDimTableRef: () => sql.raw("\"query_dim\"")
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
function createR2SqlResolverAdapter(options = {}) {
|
|
477
|
-
const adapter = createResolverAdapter({
|
|
478
|
-
...PG_BASE_CONFIG,
|
|
479
|
-
schema: icebergSchema,
|
|
480
|
-
includeSiteId: true,
|
|
481
|
-
includeSearchType: true,
|
|
482
|
-
tableLabel: "r2-sql-resolver-adapter",
|
|
483
|
-
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
484
|
-
capabilities: {
|
|
485
|
-
regex: false,
|
|
486
|
-
comparisonJoin: false,
|
|
487
|
-
windowTotals: false
|
|
488
|
-
},
|
|
489
|
-
tableRef: (tk) => sql.raw(`"${tk}"`),
|
|
490
|
-
queryDimTableRef: () => sql.raw("\"query_dim\"")
|
|
491
|
-
});
|
|
492
|
-
if ((options.partitionKeyEncoding ?? DEFAULT_PARTITION_KEY_ENCODING) === "int") return adapter;
|
|
493
|
-
return {
|
|
494
|
-
...adapter,
|
|
495
|
-
siteIdColRef: (tk) => sql`CONCAT(${adapter.siteIdColRef(tk)}, '')`,
|
|
496
|
-
searchTypeColRef: (tk) => sql`CONCAT(${adapter.searchTypeColRef(tk)}, '')`
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
395
|
const ALLOWED_FILTER_DIMS = /* @__PURE__ */ new Set(["date", "queryCanonical"]);
|
|
500
396
|
function planCoveredByCanonicalRollup(plan) {
|
|
501
397
|
if (plan.dataset !== "queries") return false;
|
|
@@ -973,6 +869,110 @@ function matchesTopLevelPage(row) {
|
|
|
973
869
|
for (let index = 0; index < path.length; index++) if (path.charCodeAt(index) === 47 && ++slashes > 1) return false;
|
|
974
870
|
return true;
|
|
975
871
|
}
|
|
872
|
+
const pgDialect = new PgDialect();
|
|
873
|
+
function withTenantCols(tableName, baseTable) {
|
|
874
|
+
const t = pgTable(tableName, {
|
|
875
|
+
site_id: varchar("site_id").notNull(),
|
|
876
|
+
search_type: varchar("search_type").notNull()
|
|
877
|
+
});
|
|
878
|
+
return {
|
|
879
|
+
...baseTable,
|
|
880
|
+
site_id: t.site_id,
|
|
881
|
+
search_type: t.search_type
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
const icebergSchema = {
|
|
885
|
+
pages: withTenantCols("pages", drizzleSchema.pages),
|
|
886
|
+
queries: withTenantCols("queries", drizzleSchema.queries),
|
|
887
|
+
countries: withTenantCols("countries", drizzleSchema.countries),
|
|
888
|
+
page_queries: withTenantCols("page_queries", drizzleSchema.page_queries),
|
|
889
|
+
dates: withTenantCols("dates", drizzleSchema.dates),
|
|
890
|
+
search_appearance: withTenantCols("search_appearance", drizzleSchema.search_appearance),
|
|
891
|
+
search_appearance_pages: withTenantCols("search_appearance_pages", drizzleSchema.search_appearance_pages),
|
|
892
|
+
search_appearance_queries: withTenantCols("search_appearance_queries", drizzleSchema.search_appearance_queries),
|
|
893
|
+
search_appearance_page_queries: withTenantCols("search_appearance_page_queries", drizzleSchema.search_appearance_page_queries),
|
|
894
|
+
hourly_pages: withTenantCols("hourly_pages", drizzleSchema.hourly_pages)
|
|
895
|
+
};
|
|
896
|
+
function compilePg(query) {
|
|
897
|
+
const compiled = pgDialect.sqlToQuery(query);
|
|
898
|
+
return {
|
|
899
|
+
sql: compiled.sql,
|
|
900
|
+
params: compiled.params
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
const PG_BASE_CONFIG = {
|
|
904
|
+
schema: drizzleSchema,
|
|
905
|
+
datasetToTableKey: {
|
|
906
|
+
pages: "pages",
|
|
907
|
+
queries: "queries",
|
|
908
|
+
page_queries: "page_queries",
|
|
909
|
+
countries: "countries",
|
|
910
|
+
dates: "dates",
|
|
911
|
+
search_appearance: "search_appearance",
|
|
912
|
+
search_appearance_pages: "search_appearance_pages",
|
|
913
|
+
search_appearance_queries: "search_appearance_queries",
|
|
914
|
+
search_appearance_page_queries: "search_appearance_page_queries",
|
|
915
|
+
hourly_pages: "hourly_pages"
|
|
916
|
+
},
|
|
917
|
+
metricCast: "DOUBLE",
|
|
918
|
+
regexPredicate: (expr, pattern, negate) => negate ? sql`NOT regexp_matches(${expr}, ${pattern})` : sql`regexp_matches(${expr}, ${pattern})`,
|
|
919
|
+
urlToPathExpr: (col) => `CASE WHEN ${col} LIKE 'http%' THEN COALESCE(NULLIF(regexp_replace(${col}, '^https?://[^/]+', ''), ''), '/') ELSE ${col} END`,
|
|
920
|
+
includeSiteId: false,
|
|
921
|
+
compile: compilePg,
|
|
922
|
+
capabilities: {
|
|
923
|
+
regex: true,
|
|
924
|
+
comparisonJoin: true,
|
|
925
|
+
windowTotals: true
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
const pgResolverAdapter = createResolverAdapter({
|
|
929
|
+
...PG_BASE_CONFIG,
|
|
930
|
+
tableLabel: "pg-resolver-adapter"
|
|
931
|
+
});
|
|
932
|
+
function createParquetResolverAdapter(options = {}) {
|
|
933
|
+
return createResolverAdapter({
|
|
934
|
+
...PG_BASE_CONFIG,
|
|
935
|
+
tableLabel: "parquet-resolver-adapter",
|
|
936
|
+
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
937
|
+
tableRef: (tk) => sql.raw(`read_parquet({{FILES}}, union_by_name = true) AS "${tk}"`),
|
|
938
|
+
queryDimTableRef: () => sql.raw("read_parquet({{QUERY_DIM}}, union_by_name = true) AS \"query_dim\"")
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
function createIcebergResolverAdapter(options = {}) {
|
|
942
|
+
return createResolverAdapter({
|
|
943
|
+
...PG_BASE_CONFIG,
|
|
944
|
+
schema: icebergSchema,
|
|
945
|
+
includeSiteId: true,
|
|
946
|
+
includeSearchType: true,
|
|
947
|
+
tableLabel: "iceberg-resolver-adapter",
|
|
948
|
+
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
949
|
+
tableRef: (tk) => sql.raw(`"${tk}"`),
|
|
950
|
+
queryDimTableRef: () => sql.raw("\"query_dim\"")
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
function createR2SqlResolverAdapter(options = {}) {
|
|
954
|
+
const adapter = createResolverAdapter({
|
|
955
|
+
...PG_BASE_CONFIG,
|
|
956
|
+
schema: icebergSchema,
|
|
957
|
+
includeSiteId: true,
|
|
958
|
+
includeSearchType: true,
|
|
959
|
+
tableLabel: "r2-sql-resolver-adapter",
|
|
960
|
+
queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
|
|
961
|
+
capabilities: {
|
|
962
|
+
regex: false,
|
|
963
|
+
comparisonJoin: false,
|
|
964
|
+
windowTotals: false
|
|
965
|
+
},
|
|
966
|
+
tableRef: (tk) => sql.raw(`"${tk}"`),
|
|
967
|
+
queryDimTableRef: () => sql.raw("\"query_dim\"")
|
|
968
|
+
});
|
|
969
|
+
if ((options.partitionKeyEncoding ?? DEFAULT_PARTITION_KEY_ENCODING) === "int") return adapter;
|
|
970
|
+
return {
|
|
971
|
+
...adapter,
|
|
972
|
+
siteIdColRef: (tk) => sql`CONCAT(${adapter.siteIdColRef(tk)}, '')`,
|
|
973
|
+
searchTypeColRef: (tk) => sql`CONCAT(${adapter.searchTypeColRef(tk)}, '')`
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
976
|
var QuerySourceCoverageError = class extends Error {
|
|
977
977
|
fallback;
|
|
978
978
|
name = "QuerySourceCoverageError";
|
package/dist/_chunks/schema2.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SCHEMAS, TABLE_METADATA } from "./schema.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { defineIcebergDataset } from "@gscdump/lakehouse";
|
|
3
|
+
import { DEFAULT_PARTITION_KEY_ENCODING, DEFAULT_PARTITION_KEY_ENCODING as DEFAULT_PARTITION_KEY_ENCODING$1 } from "@gscdump/lakehouse/schema";
|
|
3
4
|
function mapColumnType(t) {
|
|
4
5
|
switch (t) {
|
|
5
6
|
case "VARCHAR": return "STRING";
|
package/dist/_chunks/sink.d.mts
CHANGED
|
@@ -2,7 +2,8 @@ import { Row as Row$1, SearchType, TenantCtx as TenantCtx$1 } from "./storage.mj
|
|
|
2
2
|
import { EngineError } from "./errors.mjs";
|
|
3
3
|
import { icebergCreateTable } from "./libs/icebird.mjs";
|
|
4
4
|
import { Result } from "gscdump/result";
|
|
5
|
-
import { CommitRetryOptions, ConnectIcebergOptions, IcebergCatalogConfig,
|
|
5
|
+
import { CommitRetryOptions, ConnectIcebergOptions, IcebergCatalogConfig, IcebergConnection, IcebergListedDataFile, QueryProfiler } from "@gscdump/lakehouse";
|
|
6
|
+
import { IcebergColumn, IcebergPartitionField, IcebergS3Config, IcebergTableSpec, PartitionKeyEncoding, PartitionKeyEncoding as PartitionKeyEncoding$1 } from "@gscdump/lakehouse/schema";
|
|
6
7
|
import { TableName } from "@gscdump/contracts";
|
|
7
8
|
/** The 9 fact tables that exist as global Iceberg tables. */
|
|
8
9
|
type IcebergTableName = Extract<TableName, 'pages' | 'queries' | 'countries' | 'page_queries' | 'dates' | 'search_appearance' | 'search_appearance_pages' | 'search_appearance_queries' | 'search_appearance_page_queries'>;
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
import { ParquetCodec, QueryExecutor, Row } from "./storage.mjs";
|
|
2
|
+
interface DuckDBHandle {
|
|
3
|
+
query: (sql: string, params?: unknown[]) => Promise<Row[]>;
|
|
4
|
+
registerFileBuffer: (name: string, bytes: Uint8Array) => Promise<void>;
|
|
5
|
+
copyFileToBuffer: (name: string) => Promise<Uint8Array>;
|
|
6
|
+
dropFiles: (names: string[]) => Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Returns a unique path suitable for `COPY TO '…'` + `copyFileToBuffer`.
|
|
9
|
+
* In Node this is an absolute path under `os.tmpdir()` so DuckDB doesn't
|
|
10
|
+
* litter the CWD; in browsers/Workers it's a plain virtual-FS name.
|
|
11
|
+
*/
|
|
12
|
+
makeTempPath: (ext: string) => string;
|
|
13
|
+
}
|
|
14
|
+
interface DuckDBFactory {
|
|
15
|
+
getDuckDB: () => Promise<DuckDBHandle>;
|
|
16
|
+
}
|
|
17
|
+
interface DuckDBReadOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Maximum non-URI parquet reads held in flight while registering DuckDB
|
|
20
|
+
* buffers. Defaults to 16; callers handling unusually large files can lower
|
|
21
|
+
* it to trade latency for peak JS memory.
|
|
22
|
+
*/
|
|
23
|
+
bufferReadConcurrency?: number;
|
|
24
|
+
}
|
|
25
|
+
declare function createDuckDBCodec(factory: DuckDBFactory, options?: DuckDBReadOptions): ParquetCodec;
|
|
26
|
+
declare function createDuckDBExecutor(factory: DuckDBFactory, options?: DuckDBReadOptions): QueryExecutor;
|
|
1
27
|
/**
|
|
2
28
|
* Describes a hot/cold snapshot set. Produced by the snapshot builder,
|
|
3
29
|
* consumed by `attachSnapshotIndex`. Filenames are derived from `cold`
|
|
@@ -11,4 +37,4 @@ interface SnapshotIndex {
|
|
|
11
37
|
hot: boolean;
|
|
12
38
|
hotDays: number;
|
|
13
39
|
}
|
|
14
|
-
export { SnapshotIndex };
|
|
40
|
+
export { DuckDBFactory, DuckDBHandle, SnapshotIndex, createDuckDBCodec, createDuckDBExecutor };
|
package/dist/adapters/node.d.mts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { DataSource, StorageEngine } from "../_chunks/storage.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { SnapshotIndex } from "../_chunks/snapshot.mjs";
|
|
2
|
+
import { DuckDBHandle, SnapshotIndex } from "../_chunks/snapshot.mjs";
|
|
4
3
|
import { SearchType } from "gscdump/query";
|
|
5
4
|
import { Row, TableName } from "@gscdump/contracts";
|
|
5
|
+
interface NodeDuckDBOptions {
|
|
6
|
+
verbose?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare function createNodeDuckDBHandle(opts?: NodeDuckDBOptions): DuckDBHandle;
|
|
9
|
+
declare function resetNodeDuckDB(): void;
|
|
6
10
|
interface NodeHarnessOptions {
|
|
7
11
|
dataDir: string;
|
|
8
12
|
/** Tenant user id. Defaults to `'local'` for single-user CLI installs. */
|