@gscdump/engine 5.6.0 → 5.7.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.
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ComparisonOrderBy } from "./resolver/types.mjs";
|
|
1
2
|
import { BuilderState, SearchType } from "gscdump/query";
|
|
2
3
|
export type AnalysisTool = 'striking-distance' | 'opportunity' | 'movers' | 'decay' | 'zero-click' | 'brand' | 'cannibalization' | 'clustering' | 'concentration' | 'seasonality' | 'trends' | 'ctr-anomaly' | 'position-volatility' | 'long-tail' | 'intent-atlas' | 'query-migration' | 'bayesian-ctr' | 'stl-decompose' | 'change-point' | 'bipartite-pagerank' | 'survival' | 'position-distribution' | 'ctr-curve' | 'dark-traffic' | 'content-velocity' | 'keyword-breadth' | 'device-gap' | 'data-query' | 'data-detail' | 'trajectory';
|
|
3
4
|
export interface AnalysisParams {
|
|
@@ -55,6 +56,8 @@ export interface AnalysisParams {
|
|
|
55
56
|
qc?: BuilderState;
|
|
56
57
|
/** data-query comparison filter applied to joined current/previous rows. */
|
|
57
58
|
comparisonFilter?: 'new' | 'lost' | 'improving' | 'declining';
|
|
59
|
+
/** data-query comparison order applied to the joined rows before the row cap. */
|
|
60
|
+
comparisonOrderBy?: ComparisonOrderBy;
|
|
58
61
|
/** GSC slice the analysis is scoped to. Undefined = analyzer runs cross-type (today's behaviour for web-only sites). */
|
|
59
62
|
searchType?: SearchType;
|
|
60
63
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Row } from "../storage.mjs";
|
|
2
|
-
import { AnalysisParams } from "../analysis-types.mjs";
|
|
3
2
|
import { ResolverAdapter } from "../resolver/types.mjs";
|
|
3
|
+
import { AnalysisParams } from "../analysis-types.mjs";
|
|
4
4
|
import { FileSet, SourceCapabilities } from "../source/source-types.mjs";
|
|
5
5
|
import { BuilderState } from "gscdump/query";
|
|
6
6
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComparisonFilter, ExtraQuery, ResolvedComparisonSQL, ResolvedSQL, ResolvedSQLOptimized, ResolverOptions } from "./types.mjs";
|
|
1
|
+
import { ComparisonFilter, ComparisonOrderBy, ExtraQuery, ResolvedComparisonSQL, ResolvedSQL, ResolvedSQLOptimized, ResolverOptions } from "./types.mjs";
|
|
2
2
|
import { BuilderState } from "gscdump/query";
|
|
3
3
|
export declare function resolveToSQLOptimized<TK extends string>(state: BuilderState, options: ResolverOptions<TK>): ResolvedSQLOptimized;
|
|
4
4
|
export declare function resolveToSQL<TK extends string>(state: BuilderState, options: ResolverOptions<TK>): ResolvedSQL;
|
|
@@ -6,7 +6,7 @@ export declare function buildTotalsSql<TK extends string>(state: BuilderState, o
|
|
|
6
6
|
sql: string;
|
|
7
7
|
params: unknown[];
|
|
8
8
|
};
|
|
9
|
-
export declare function resolveComparisonSQL<TK extends string>(current: BuilderState, previous: BuilderState, options: ResolverOptions<TK>, comparisonFilter?: ComparisonFilter): ResolvedComparisonSQL;
|
|
9
|
+
export declare function resolveComparisonSQL<TK extends string>(current: BuilderState, previous: BuilderState, options: ResolverOptions<TK>, comparisonFilter?: ComparisonFilter, comparisonOrderBy?: ComparisonOrderBy): ResolvedComparisonSQL;
|
|
10
10
|
/**
|
|
11
11
|
* Enrich Canonical Query groups with their raw variants.
|
|
12
12
|
* Supply selected keys to limit aggregation to the current page.
|
|
@@ -35,6 +35,14 @@ function orderByClause(state, prefix = "", columnOverride) {
|
|
|
35
35
|
}
|
|
36
36
|
return sql.raw(`ORDER BY ${prefix}clicks DESC`);
|
|
37
37
|
}
|
|
38
|
+
function changeOrderClause(order) {
|
|
39
|
+
if (order.column !== "clicksChange") throw new Error(`[resolver] unknown comparison order column: ${JSON.stringify(order.column)}`);
|
|
40
|
+
if (order.dir !== "asc" && order.dir !== "desc") throw new Error(`[resolver] unknown comparison order direction: ${JSON.stringify(order.dir)}`);
|
|
41
|
+
return {
|
|
42
|
+
column: sql.raw("CAST(COALESCE(c.clicks, 0) AS DOUBLE) - CAST(COALESCE(p.clicks, 0) AS DOUBLE) as \"clicksChange\""),
|
|
43
|
+
orderBy: sql.raw(`ORDER BY clicksChange ${order.dir.toUpperCase()}`)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
38
46
|
function limitOffsetClause(state) {
|
|
39
47
|
const rowLimit = Math.max(0, Math.floor(Number(state.rowLimit ?? 100)));
|
|
40
48
|
const offset = state.startRow ? Math.max(0, Math.floor(Number(state.startRow))) : 0;
|
|
@@ -239,7 +247,7 @@ function buildTotalsSql(state, options) {
|
|
|
239
247
|
const selectExprs = metrics.map((m) => sql`${adapter.metricSql(m, tableKey)} as ${aliasRaw(m)}`);
|
|
240
248
|
return compileCollapsed(adapter, wherePredicates.length > 0 ? sql`SELECT ${joinComma(selectExprs)} FROM ${table} WHERE ${joinAnd(wherePredicates)}` : sql`SELECT ${joinComma(selectExprs)} FROM ${table}`);
|
|
241
249
|
}
|
|
242
|
-
function resolveComparisonSQL(current, previous, options, comparisonFilter) {
|
|
250
|
+
function resolveComparisonSQL(current, previous, options, comparisonFilter, comparisonOrderBy) {
|
|
243
251
|
const { adapter, siteId, searchType } = options;
|
|
244
252
|
const comparisonPlan = buildComparisonPlan(current, previous, adapter.capabilities);
|
|
245
253
|
const currentScope = buildScope(current, options);
|
|
@@ -271,7 +279,8 @@ function resolveComparisonSQL(current, previous, options, comparisonFilter) {
|
|
|
271
279
|
if (groupByExprs.length > 0) previousCte = sql`${previousCte} GROUP BY ${joinComma(groupByExprs)}`;
|
|
272
280
|
const joinOn = groupByDims.length > 0 ? sql.raw(groupByDims.map((d) => `c.${d.replace(/\W/g, "")} = p.${d.replace(/\W/g, "")}`).join(" AND ")) : sql.raw("1=1");
|
|
273
281
|
const filterClause = comparisonFilter ? COMPARISON_FILTER_SQL[comparisonFilter] : sql.raw("");
|
|
274
|
-
const
|
|
282
|
+
const changeOrder = comparisonOrderBy ? changeOrderClause(comparisonOrderBy) : void 0;
|
|
283
|
+
const orderSql = changeOrder?.orderBy ?? orderByClause(current, "");
|
|
275
284
|
const limitSql = limitOffsetClause(current);
|
|
276
285
|
const outerCurrentCols = [];
|
|
277
286
|
for (const d of groupByDims) {
|
|
@@ -282,6 +291,7 @@ function resolveComparisonSQL(current, previous, options, comparisonFilter) {
|
|
|
282
291
|
outerCurrentCols.push(sql.raw("CAST(COALESCE(c.impressions, 0) AS DOUBLE) as \"impressions\""));
|
|
283
292
|
outerCurrentCols.push(sql.raw("COALESCE(c.ctr, 0) as \"ctr\""));
|
|
284
293
|
outerCurrentCols.push(sql.raw("COALESCE(c.position, 0) as \"position\""));
|
|
294
|
+
if (changeOrder) outerCurrentCols.push(changeOrder.column);
|
|
285
295
|
const mainQuery = sql`WITH current AS (${currentCte}), previous AS (${previousCte}) SELECT ${joinComma(outerCurrentCols)}, COALESCE(CAST(p.clicks AS DOUBLE), 0) as "prevClicks", COALESCE(CAST(p.impressions AS DOUBLE), 0) as "prevImpressions", COALESCE(p.ctr, 0) as "prevCtr", COALESCE(p.position, 0) as "prevPosition" FROM current c FULL OUTER JOIN previous p ON ${joinOn} WHERE 1=1 ${filterClause} ${orderSql} ${limitSql}`;
|
|
286
296
|
const firstGroupBy = groupByDims[0] ? groupByDims[0].replace(/\W/g, "") : "clicks";
|
|
287
297
|
const countInnerSelect = groupByDims[0] ? sql.raw(`COALESCE(c.${firstGroupBy}, p.${firstGroupBy})`) : sql.raw(`c.${firstGroupBy}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComparisonFilter, ExtraQuery, ResolvedComparisonSQL, ResolvedSQL, ResolvedSQLOptimized, ResolverAdapter, ResolverOptions } from "./types.mjs";
|
|
1
|
+
import { ComparisonFilter, ComparisonOrderBy, ExtraQuery, ResolvedComparisonSQL, ResolvedSQL, ResolvedSQLOptimized, ResolverAdapter, ResolverOptions } from "./types.mjs";
|
|
2
2
|
import { DIMENSION_SURFACES, DimensionBinding, DimensionSurface, LOGICAL_DATASETS, LogicalDataset, LogicalDatasetDefinition, UnresolvableDatasetError, assertDimensionsSupported, dimensionColumn, inferLogicalDataset, isDatasetResolvable, supportsDimensionOnSurface } from "./datasets.mjs";
|
|
3
3
|
import { SqlFragments, SqlFragmentsConfig, createSqlFragments } from "./fragments.mjs";
|
|
4
4
|
import { CreateResolverAdapterConfig, createResolverAdapter } from "./adapter.mjs";
|
|
@@ -9,4 +9,4 @@ import { RollupRowsReader, createRollupExtrasOverlay } from "./extras-overlay.mj
|
|
|
9
9
|
import { dimensionValue, getDimensionFilters, getFilterDimensions, getInternalFilters, matchesDimensionFilter, matchesMetricFilter, matchesTopLevelPage, metricValue } from "./filter-utils.mjs";
|
|
10
10
|
import { PgTableKey, R2SqlResolverAdapterOptions, ResolverAdapterOptions, createIcebergResolverAdapter, createParquetResolverAdapter, createR2SqlResolverAdapter, pgResolverAdapter } from "./pg-adapter.mjs";
|
|
11
11
|
import { AssertSchemaInSyncOptions, assertSchemaInSync } from "./schema-drift.mjs";
|
|
12
|
-
export { type AssertSchemaInSyncOptions, type CanonicalQueryDimSource, type ComparisonFilter, type ComparisonQueryResult, type CreateResolverAdapterConfig, DIMENSION_SURFACES, type DimensionBinding, type DimensionSurface, type ExtraQuery, LOGICAL_DATASETS, type LogicalDataset, type LogicalDatasetDefinition, type OptimizedQueryResult, type PgTableKey, type PrimaryColumnarSource, QuerySourceCoverageError, type QuerySourceDecision, type QuerySourceFallback, type QuerySourceFallbackKind, type QuerySourceKind, type R2SqlResolverAdapterOptions, type ResolveExtraFn, type ResolvedComparisonSQL, type ResolvedSQL, type ResolvedSQLOptimized, type ResolverAdapter, type ResolverAdapterOptions, type ResolverOptions, type RollupRowsReader, type RunOptimizedQueryOptions, type RunQueryCtx, type RunSQLFn, type SqlFragments, type SqlFragmentsConfig, UnresolvableDatasetError, assertDimensionsSupported, assertSchemaInSync, buildExtrasQueries, buildTotalsSql, canonicalRollupCovers, createIcebergResolverAdapter, createParquetResolverAdapter, createR2SqlResolverAdapter, createResolverAdapter, createRollupExtrasOverlay, createSqlFragments, dimensionColumn, dimensionValue, getDimensionFilters, getFilterDimensions, getInternalFilters, inferLogicalDataset, isDatasetResolvable, matchesDimensionFilter, matchesMetricFilter, matchesTopLevelPage, mergeExtras, metricValue, pgResolverAdapter, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized, runComparisonQuery, runOptimizedQuery, supportsDimensionOnSurface };
|
|
12
|
+
export { type AssertSchemaInSyncOptions, type CanonicalQueryDimSource, type ComparisonFilter, type ComparisonOrderBy, type ComparisonQueryResult, type CreateResolverAdapterConfig, DIMENSION_SURFACES, type DimensionBinding, type DimensionSurface, type ExtraQuery, LOGICAL_DATASETS, type LogicalDataset, type LogicalDatasetDefinition, type OptimizedQueryResult, type PgTableKey, type PrimaryColumnarSource, QuerySourceCoverageError, type QuerySourceDecision, type QuerySourceFallback, type QuerySourceFallbackKind, type QuerySourceKind, type R2SqlResolverAdapterOptions, type ResolveExtraFn, type ResolvedComparisonSQL, type ResolvedSQL, type ResolvedSQLOptimized, type ResolverAdapter, type ResolverAdapterOptions, type ResolverOptions, type RollupRowsReader, type RunOptimizedQueryOptions, type RunQueryCtx, type RunSQLFn, type SqlFragments, type SqlFragmentsConfig, UnresolvableDatasetError, assertDimensionsSupported, assertSchemaInSync, buildExtrasQueries, buildTotalsSql, canonicalRollupCovers, createIcebergResolverAdapter, createParquetResolverAdapter, createR2SqlResolverAdapter, createResolverAdapter, createRollupExtrasOverlay, createSqlFragments, dimensionColumn, dimensionValue, getDimensionFilters, getFilterDimensions, getInternalFilters, inferLogicalDataset, isDatasetResolvable, matchesDimensionFilter, matchesMetricFilter, matchesTopLevelPage, mergeExtras, metricValue, pgResolverAdapter, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized, runComparisonQuery, runOptimizedQuery, supportsDimensionOnSurface };
|
|
@@ -29,6 +29,15 @@ export interface ResolverAdapter<TableKey extends string = string> {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
export type ComparisonFilter = 'new' | 'lost' | 'improving' | 'declining';
|
|
32
|
+
/**
|
|
33
|
+
* Order for a comparison read, applied to the joined rows before the row cap.
|
|
34
|
+
* `clicksChange` is current clicks minus previous clicks, so `asc` puts the
|
|
35
|
+
* biggest losers first and `desc` the biggest gainers.
|
|
36
|
+
*/
|
|
37
|
+
export interface ComparisonOrderBy {
|
|
38
|
+
column: 'clicksChange';
|
|
39
|
+
dir: 'asc' | 'desc';
|
|
40
|
+
}
|
|
32
41
|
export interface ResolverOptions<TableKey extends string = string> {
|
|
33
42
|
adapter: ResolverAdapter<TableKey>;
|
|
34
43
|
/** Optional site scope. Required for multi-tenant D1; omitted for parquet. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/engine",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.7.1",
|
|
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": "^5.
|
|
193
|
-
"@gscdump/lakehouse": "^5.
|
|
192
|
+
"@gscdump/contracts": "^5.7.1",
|
|
193
|
+
"@gscdump/lakehouse": "^5.7.1",
|
|
194
194
|
"drizzle-orm": "1.0.0-rc.4",
|
|
195
|
-
"gscdump": "^5.
|
|
195
|
+
"gscdump": "^5.7.1",
|
|
196
196
|
"pathe": "^2.0.3",
|
|
197
197
|
"proper-lockfile": "^4.1.2"
|
|
198
198
|
},
|