@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.
@@ -2,6 +2,7 @@ import { SCHEMAS, drizzleSchema } from "./schema.mjs";
2
2
  import { enumeratePartitions } from "./compaction.mjs";
3
3
  import { escapeLike } from "../sql-fragments.mjs";
4
4
  import "../planner.mjs";
5
+ import "./schema2.mjs";
5
6
  import { UnresolvableDatasetError, buildLogicalComparisonPlan, buildLogicalPlan, inferDataset as inferLogicalDataset, isDatasetResolvable } from "gscdump/query/plan";
6
7
  import { PgDialect, pgTable, varchar } from "drizzle-orm/pg-core";
7
8
  import { normalizeUrl } from "gscdump/normalize";
@@ -9,7 +10,7 @@ import { sql } from "drizzle-orm";
9
10
  const DIMENSION_SURFACES = {
10
11
  page: ["api", "stored"],
11
12
  query: ["api", "stored"],
12
- queryCanonical: ["stored", "derived"],
13
+ queryCanonical: ["derived"],
13
14
  country: ["api", "stored"],
14
15
  device: ["api", "stored"],
15
16
  searchAppearance: ["api", "stored"],
@@ -34,7 +35,7 @@ const LOGICAL_DATASETS = {
34
35
  },
35
36
  queryCanonical: {
36
37
  column: "query_canonical",
37
- surfaces: ["stored", "derived"]
38
+ surfaces: ["derived"]
38
39
  },
39
40
  date: {
40
41
  column: "date",
@@ -52,7 +53,7 @@ const LOGICAL_DATASETS = {
52
53
  },
53
54
  queryCanonical: {
54
55
  column: "query_canonical",
55
- surfaces: ["stored", "derived"]
56
+ surfaces: ["derived"]
56
57
  },
57
58
  date: {
58
59
  column: "date",
@@ -108,7 +109,7 @@ const LOGICAL_DATASETS = {
108
109
  },
109
110
  queryCanonical: {
110
111
  column: "query_canonical",
111
- surfaces: ["stored", "derived"]
112
+ surfaces: ["derived"]
112
113
  },
113
114
  date: {
114
115
  column: "date",
@@ -130,7 +131,7 @@ const LOGICAL_DATASETS = {
130
131
  },
131
132
  queryCanonical: {
132
133
  column: "query_canonical",
133
- surfaces: ["stored", "derived"]
134
+ surfaces: ["derived"]
134
135
  },
135
136
  date: {
136
137
  column: "date",
@@ -169,6 +170,13 @@ const METRIC_NAMES = [
169
170
  "ctr",
170
171
  "position"
171
172
  ];
173
+ const QUERY_DIM_ALIAS = "query_dim";
174
+ function quoteIdent(id) {
175
+ return `"${id.replace(/"/g, "\"\"")}"`;
176
+ }
177
+ function qualifiedRaw(alias, column) {
178
+ return sql.raw(`${quoteIdent(alias)}.${quoteIdent(column)}`);
179
+ }
172
180
  function defaultSqliteUrlToPathExpr(col) {
173
181
  return `CASE WHEN ${col} LIKE 'http%' THEN CASE WHEN INSTR(SUBSTR(${col}, INSTR(${col}, '://') + 3), '/') > 0 THEN SUBSTR(${col}, INSTR(${col}, '://') + 2 + INSTR(SUBSTR(${col}, INSTR(${col}, '://') + 3), '/')) ELSE '/' END ELSE ${col} END`;
174
182
  }
@@ -180,7 +188,7 @@ function buildDimensionColumnMap(datasetToTableKey) {
180
188
  return Object.fromEntries(entries);
181
189
  }
182
190
  function createSqlFragments(config) {
183
- const { schema, datasetToTableKey, metricCast, regexPredicate, tableLabel, includeSiteId, includeSearchType, urlToPathExpr: urlToPathExprOverride, tableRef: tableRefOverride, canonicalFallback = false } = config;
191
+ const { schema, datasetToTableKey, metricCast, regexPredicate, tableLabel, includeSiteId, includeSearchType, urlToPathExpr: urlToPathExprOverride, tableRef: tableRefOverride, queryCanonicalSource = "queryDim", queryDimTableRef, queryDimSiteScoped = false } = config;
184
192
  const DIM_COLUMN_MAP = buildDimensionColumnMap(datasetToTableKey);
185
193
  function isMetricDimension(dim) {
186
194
  return METRIC_NAMES.includes(dim);
@@ -205,6 +213,11 @@ function createSqlFragments(config) {
205
213
  if (tableRefOverride) return tableRefOverride(tableKey);
206
214
  return sql`${schema[tableKey]}`;
207
215
  }
216
+ function fromSql(tableKey, options = {}) {
217
+ const base = tableRef(tableKey);
218
+ if (!options.queryCanonical || queryCanonicalSource !== "queryDim") return base;
219
+ return sql`${base} LEFT JOIN ${queryDimTableRef?.() ?? sql.raw(`${quoteIdent(QUERY_DIM_ALIAS)}`)} ON ${queryDimSiteScoped ? sql`${colRef(tableKey, "query")} = ${qualifiedRaw(QUERY_DIM_ALIAS, "query")} AND ${colRef(tableKey, "site_id")} = ${qualifiedRaw(QUERY_DIM_ALIAS, "site_id")}` : sql`${colRef(tableKey, "query")} = ${qualifiedRaw(QUERY_DIM_ALIAS, "query")}`}`;
220
+ }
208
221
  function dateColRef(tableKey) {
209
222
  return colRef(tableKey, "date");
210
223
  }
@@ -217,7 +230,10 @@ function createSqlFragments(config) {
217
230
  function dimExprSql(dim, tableKey) {
218
231
  const colName = dimColumn(dim, tableKey);
219
232
  if (dim === "page") return sql.raw(urlToPathExpr(colName));
220
- if (canonicalFallback && dim === "queryCanonical") return sql`COALESCE(NULLIF(${colRef(tableKey, colName)}, ''), ${colRef(tableKey, "query")})`;
233
+ if (dim === "queryCanonical") {
234
+ if (queryCanonicalSource === "queryDim") return sql`COALESCE(${qualifiedRaw(QUERY_DIM_ALIAS, "query_canonical")}, ${colRef(tableKey, "query")})`;
235
+ return qualifiedRaw(String(tableKey), "query_canonical");
236
+ }
221
237
  return colRef(tableKey, colName);
222
238
  }
223
239
  function metricSql(metric, tableKey) {
@@ -298,7 +314,7 @@ function createSqlFragments(config) {
298
314
  if (f.dimension === "date") continue;
299
315
  if (f.operator === "topLevel") continue;
300
316
  const dim = f.dimension;
301
- const cRef = colRef(tableKey, dimColumn(dim, tableKey));
317
+ const cRef = dim === "queryCanonical" ? void 0 : colRef(tableKey, dimColumn(dim, tableKey));
302
318
  const matchExpr = dim === "page" || dim === "queryCanonical" ? dimExprSql(dim, tableKey) : cRef;
303
319
  const patternExpr = dim === "queryCanonical" ? matchExpr : cRef;
304
320
  switch (f.operator) {
@@ -339,6 +355,7 @@ function createSqlFragments(config) {
339
355
  urlToPathExpr,
340
356
  colRef,
341
357
  tableRef,
358
+ fromSql,
342
359
  dateColRef,
343
360
  siteIdColRef: includeSiteId ? siteIdColRef : void 0,
344
361
  searchTypeColRef: includeSearchType ? searchTypeColRef : void 0,
@@ -361,6 +378,7 @@ function createResolverAdapter(config) {
361
378
  dimColumn: runtime.dimColumn,
362
379
  isMetricDimension: runtime.isMetricDimension,
363
380
  tableRef: runtime.tableRef,
381
+ fromSql: runtime.fromSql,
364
382
  dateColRef: runtime.dateColRef,
365
383
  urlToPathExpr: runtime.urlToPathExpr,
366
384
  siteIdColRef: runtime.siteIdColRef,
@@ -439,7 +457,9 @@ function createParquetResolverAdapter(options = {}) {
439
457
  ...PG_BASE_CONFIG,
440
458
  tableLabel: "parquet-resolver-adapter",
441
459
  canonicalFallback: options.canonicalFallback ?? false,
442
- tableRef: (tk) => sql.raw(`read_parquet({{FILES}}, union_by_name = true) AS "${tk}"`)
460
+ queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
461
+ tableRef: (tk) => sql.raw(`read_parquet({{FILES}}, union_by_name = true) AS "${tk}"`),
462
+ queryDimTableRef: () => sql.raw("read_parquet({{QUERY_DIM}}, union_by_name = true) AS \"query_dim\"")
443
463
  });
444
464
  }
445
465
  function createIcebergResolverAdapter(options = {}) {
@@ -450,9 +470,35 @@ function createIcebergResolverAdapter(options = {}) {
450
470
  includeSearchType: true,
451
471
  tableLabel: "iceberg-resolver-adapter",
452
472
  canonicalFallback: options.canonicalFallback ?? false,
453
- tableRef: (tk) => sql.raw(`"${tk}"`)
473
+ queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
474
+ tableRef: (tk) => sql.raw(`"${tk}"`),
475
+ queryDimTableRef: () => sql.raw("\"query_dim\"")
454
476
  });
455
477
  }
478
+ function createR2SqlResolverAdapter(options = {}) {
479
+ const adapter = createResolverAdapter({
480
+ ...PG_BASE_CONFIG,
481
+ schema: icebergSchema,
482
+ includeSiteId: true,
483
+ includeSearchType: true,
484
+ tableLabel: "r2-sql-resolver-adapter",
485
+ canonicalFallback: options.canonicalFallback ?? false,
486
+ queryCanonicalSource: options.queryCanonicalSource ?? "queryDim",
487
+ capabilities: {
488
+ regex: false,
489
+ comparisonJoin: false,
490
+ windowTotals: false
491
+ },
492
+ tableRef: (tk) => sql.raw(`"${tk}"`),
493
+ queryDimTableRef: () => sql.raw("\"query_dim\"")
494
+ });
495
+ if ((options.partitionKeyEncoding ?? "int") === "int") return adapter;
496
+ return {
497
+ ...adapter,
498
+ siteIdColRef: (tk) => sql`CONCAT(${adapter.siteIdColRef(tk)}, '')`,
499
+ searchTypeColRef: (tk) => sql`CONCAT(${adapter.searchTypeColRef(tk)}, '')`
500
+ };
501
+ }
456
502
  const ALLOWED_FILTER_DIMS = /* @__PURE__ */ new Set(["date", "queryCanonical"]);
457
503
  function planCoveredByCanonicalRollup(plan) {
458
504
  if (plan.dataset !== "queries") return false;
@@ -548,6 +594,7 @@ function buildScope(state, options) {
548
594
  const groupByDims = plan.groupByDimensions;
549
595
  const hasDate = plan.hasDate;
550
596
  const metrics = plan.metrics;
597
+ const queryCanonicalUsed = groupByDims.includes("queryCanonical") || plan.dimensionFilters.some((filter) => filter.dimension === "queryCanonical");
551
598
  const wherePredicates = [];
552
599
  if (adapter.siteIdColRef && siteId != null) wherePredicates.push(sql`${adapter.siteIdColRef(tableKey)} = ${siteId}`);
553
600
  if (adapter.searchTypeColRef && searchType != null) wherePredicates.push(sql`${adapter.searchTypeColRef(tableKey)} = ${searchType}`);
@@ -569,7 +616,8 @@ function buildScope(state, options) {
569
616
  having: adapter.havingPredicates(metricFilters, tableKey),
570
617
  dimFilters,
571
618
  startDate: plan.dateRange.startDate,
572
- endDate: plan.dateRange.endDate
619
+ endDate: plan.dateRange.endDate,
620
+ queryCanonicalUsed
573
621
  };
574
622
  }
575
623
  function buildComparisonPlan(current, previous, capabilities) {
@@ -584,8 +632,8 @@ function compileCollapsed(adapter, q) {
584
632
  }
585
633
  function resolveToSQLOptimized(state, options) {
586
634
  const { adapter } = options;
587
- const { tableKey, groupByDims, hasDate, metrics, wherePredicates, having } = buildScope(state, options);
588
- const table = adapter.tableRef(tableKey);
635
+ const { tableKey, groupByDims, hasDate, metrics, wherePredicates, having, queryCanonicalUsed } = buildScope(state, options);
636
+ const table = adapter.fromSql(tableKey, { queryCanonical: queryCanonicalUsed });
589
637
  const schema = adapter.schema;
590
638
  const cteSelect = [];
591
639
  for (const d of groupByDims) {
@@ -632,8 +680,8 @@ function resolveToSQLOptimized(state, options) {
632
680
  }
633
681
  function resolveToSQL(state, options) {
634
682
  const { adapter } = options;
635
- const { tableKey, groupByDims, hasDate, metrics, wherePredicates, having } = buildScope(state, options);
636
- const table = adapter.tableRef(tableKey);
683
+ const { tableKey, groupByDims, hasDate, metrics, wherePredicates, having, queryCanonicalUsed } = buildScope(state, options);
684
+ const table = adapter.fromSql(tableKey, { queryCanonical: queryCanonicalUsed });
637
685
  const selectExprs = [];
638
686
  for (const d of groupByDims) {
639
687
  const expr = adapter.dimExprSql(d, tableKey);
@@ -666,8 +714,8 @@ function resolveToSQL(state, options) {
666
714
  }
667
715
  function buildTotalsSql(state, options) {
668
716
  const { adapter } = options;
669
- const { tableKey, metrics, wherePredicates } = buildScope(state, options);
670
- const table = adapter.tableRef(tableKey);
717
+ const { tableKey, metrics, wherePredicates, queryCanonicalUsed } = buildScope(state, options);
718
+ const table = adapter.fromSql(tableKey, { queryCanonical: queryCanonicalUsed });
671
719
  const selectExprs = metrics.map((m) => sql`${adapter.metricSql(m, tableKey)} as ${aliasRaw(m)}`);
672
720
  return compileCollapsed(adapter, wherePredicates.length > 0 ? sql`SELECT ${joinComma(selectExprs)} FROM ${table} WHERE ${joinAnd(wherePredicates)}` : sql`SELECT ${joinComma(selectExprs)} FROM ${table}`);
673
721
  }
@@ -677,7 +725,7 @@ function resolveComparisonSQL(current, previous, options, comparisonFilter) {
677
725
  const currentScope = buildScope(current, options);
678
726
  const previousScope = buildScope(previous, options);
679
727
  const { tableKey, groupByDims, metrics, wherePredicates: currentWhere, having } = currentScope;
680
- const table = adapter.tableRef(tableKey);
728
+ const table = adapter.fromSql(tableKey, { queryCanonical: currentScope.queryCanonicalUsed || previousScope.queryCanonicalUsed });
681
729
  const dimSelectExprs = [];
682
730
  for (const d of groupByDims) {
683
731
  const expr = adapter.dimExprSql(d, tableKey);
@@ -735,7 +783,7 @@ function buildExtrasQueries(state, options) {
735
783
  if (!dims.includes("queryCanonical")) return extras;
736
784
  const queriesKey = adapter.tableKeyForDataset("queries");
737
785
  const t = adapter.schema[queriesKey];
738
- const table = adapter.tableRef(queriesKey);
786
+ const table = adapter.fromSql(queriesKey, { queryCanonical: true });
739
787
  const whereParts = [];
740
788
  if (adapter.siteIdColRef && siteId != null) whereParts.push(sql`${adapter.siteIdColRef(queriesKey)} = ${siteId}`);
741
789
  if (adapter.searchTypeColRef && searchType != null) whereParts.push(sql`${adapter.searchTypeColRef(queriesKey)} = ${searchType}`);
@@ -743,7 +791,7 @@ function buildExtrasQueries(state, options) {
743
791
  whereParts.push(sql`${adapter.dateColRef(queriesKey)} <= ${plan.dateRange.endDate}`);
744
792
  const whereExpr = whereParts.length > 0 ? sql`WHERE ${joinAnd(whereParts)}` : sql``;
745
793
  const outerQueryCol = sql.raw("query");
746
- const canonKey = sql`COALESCE(NULLIF(${t.query_canonical}, ''), ${t.query})`;
794
+ const canonKey = adapter.dimExprSql("queryCanonical", queriesKey);
747
795
  const compiled = compileCollapsed(adapter, sql`WITH per_variant AS (SELECT ${canonKey} as joinKey, ${t.query} as query, SUM(${t.clicks}) as clicks, SUM(${t.impressions}) as impressions, SUM(${t.sum_position}) as sum_pos, ROW_NUMBER() OVER (PARTITION BY ${canonKey} ORDER BY SUM(${t.clicks}) DESC) as rn, COUNT(*) OVER (PARTITION BY ${canonKey}) as variantCount FROM ${table} ${whereExpr} GROUP BY ${canonKey}, ${t.query}) SELECT joinKey, MAX(variantCount) as variantCount, MAX(CASE WHEN rn = 1 THEN ${outerQueryCol} END) as canonicalName, GROUP_CONCAT(CASE WHEN rn <= 10 THEN ${outerQueryCol} || ':::' || clicks || ':::' || impressions || ':::' || CAST(ROUND(CAST(sum_pos AS REAL) / NULLIF(impressions, 0) + 1, 1) AS TEXT) END, '||') as variants FROM per_variant GROUP BY joinKey`);
748
796
  extras.push({
749
797
  key: "canonicalExtras",
@@ -890,9 +938,80 @@ function matchesMetricFilter(row, filter) {
890
938
  function matchesTopLevelPage(row) {
891
939
  return (normalizeUrl(dimensionValue(row, "page")).match(/\//g)?.length ?? 0) <= 1;
892
940
  }
941
+ var QuerySourceCoverageError = class extends Error {
942
+ fallback;
943
+ name = "QuerySourceCoverageError";
944
+ constructor(fallback) {
945
+ super(fallback.message);
946
+ this.fallback = fallback;
947
+ }
948
+ };
893
949
  function canonicalSourceWithinCoverage(source, windowEnd) {
894
950
  return source.coversThrough === void 0 || windowEnd <= source.coversThrough;
895
951
  }
952
+ function querySourceDecision(kind, fallbacks = []) {
953
+ const first = fallbacks[0];
954
+ return {
955
+ kind,
956
+ ...first ? {
957
+ fallback: first,
958
+ fallbacks
959
+ } : {}
960
+ };
961
+ }
962
+ function canonicalFeatureRequested(state, capabilities) {
963
+ const plan = buildLogicalPlan(state, capabilities);
964
+ return plan.groupByDimensions.includes("queryCanonical") || plan.dimensionFilters.some((f) => f.dimension === "queryCanonical");
965
+ }
966
+ function fallback(kind, message) {
967
+ return {
968
+ kind,
969
+ message
970
+ };
971
+ }
972
+ function staleQueryDim(source, requirements) {
973
+ if (requirements?.normalizerVersion !== void 0 && source.normalizerVersion !== requirements.normalizerVersion) return fallback("query-dim-stale-version", `canonical query dimension normalizer v${source.normalizerVersion} does not match required v${requirements.normalizerVersion}`);
974
+ if (requirements?.intentVersion !== void 0 && source.intentVersion !== requirements.intentVersion) return fallback("query-dim-stale-version", `canonical query dimension intent v${source.intentVersion ?? "missing"} does not match required v${requirements.intentVersion}`);
975
+ }
976
+ function canonicalQueryDim(options) {
977
+ return options.queryDim ?? options.canonicalSource?.queryDim;
978
+ }
979
+ function queryDimMiss(source, requirements) {
980
+ if (!source) return fallback("query-dim-missing", "canonical query requires queryDim; fact tables do not carry query_canonical");
981
+ if (source.keys.length === 0) return fallback("query-dim-empty", "queryDim has no parquet keys; refusing to run canonical query from an empty dimension");
982
+ return staleQueryDim(source, requirements);
983
+ }
984
+ function decideCanonicalSource(state, capabilities, options, windowEnd) {
985
+ if (!canonicalFeatureRequested(state, capabilities)) return querySourceDecision("raw-partitions");
986
+ const dimMiss = queryDimMiss(canonicalQueryDim(options), options.canonicalRequirements);
987
+ if (dimMiss) throw new QuerySourceCoverageError(dimMiss);
988
+ const source = options.canonicalSource;
989
+ const miss = (() => {
990
+ if (!source) return fallback("canonical-source-missing", "canonical rollup source missing; falling back to primary facts joined through query_dim");
991
+ if (source.keys.length === 0) return fallback("canonical-source-empty", "canonicalSource has no parquet keys; falling back to primary facts joined through query_dim");
992
+ if (!canonicalSourceWithinCoverage(source, windowEnd)) return fallback("canonical-source-stale-coverage", `canonicalSource covers through ${source.coversThrough}, but query needs ${windowEnd}`);
993
+ if (!canonicalRollupCovers(state, capabilities)) return fallback("canonical-source-not-coverable", "canonicalSource only covers queryCanonical/date reads without raw-grain filters");
994
+ })();
995
+ if (!miss) return querySourceDecision("canonical-rollup");
996
+ return querySourceDecision("raw-partitions", [miss]);
997
+ }
998
+ function primarySourceMiss(source, dateRange) {
999
+ if (!source) return fallback("primary-source-missing", "primary columnar source is required; pass primarySourceFallback: \"raw\" to use raw daily partitions explicitly");
1000
+ if (source.keys.length === 0) return fallback("primary-source-empty", "primarySource has no parquet keys; refusing to return partial or empty data implicitly");
1001
+ if (source.coversFrom !== void 0 && dateRange.startDate < source.coversFrom) return fallback("primary-source-missing-coverage", `primarySource covers from ${source.coversFrom}, but query starts ${dateRange.startDate}`);
1002
+ if (source.coversThrough !== void 0 && dateRange.endDate > source.coversThrough) return fallback("primary-source-stale-coverage", `primarySource covers through ${source.coversThrough}, but query needs ${dateRange.endDate}`);
1003
+ }
1004
+ function decidePrimarySource(options, dateRange, priorFallbacks = []) {
1005
+ const miss = primarySourceMiss(options.primarySource, dateRange);
1006
+ if (!miss) return querySourceDecision("primary-columnar", priorFallbacks);
1007
+ const fallbacks = [...priorFallbacks, miss];
1008
+ if (options.primarySourceFallback === "raw") return querySourceDecision("raw-partitions", fallbacks);
1009
+ throw new QuerySourceCoverageError(miss);
1010
+ }
1011
+ function sourceFallbacks(source) {
1012
+ if (source.fallbacks) return source.fallbacks;
1013
+ return source.fallback ? [source.fallback] : [];
1014
+ }
896
1015
  function runArgs(ctx, partitions) {
897
1016
  return {
898
1017
  ctx: {
@@ -907,26 +1026,58 @@ function runArgs(ctx, partitions) {
907
1026
  ...ctx.searchType !== void 0 ? { searchType: ctx.searchType } : {}
908
1027
  };
909
1028
  }
1029
+ function primaryRunArgs(ctx, keys) {
1030
+ return {
1031
+ ctx: {
1032
+ userId: ctx.userId,
1033
+ siteId: ctx.siteId
1034
+ },
1035
+ table: ctx.table,
1036
+ fileSets: { FILES: {
1037
+ table: ctx.table,
1038
+ keys
1039
+ } },
1040
+ ...ctx.searchType !== void 0 ? { searchType: ctx.searchType } : {}
1041
+ };
1042
+ }
1043
+ function withQueryDimFileSet(args, queryDim, enabled) {
1044
+ if (!enabled || !queryDim) return args;
1045
+ return {
1046
+ ...args,
1047
+ fileSets: {
1048
+ ...args.fileSets,
1049
+ QUERY_DIM: {
1050
+ table: "queries",
1051
+ keys: queryDim.keys
1052
+ }
1053
+ }
1054
+ };
1055
+ }
910
1056
  async function runOptimizedQuery(runSQL, ctx, state, dateRange, options = {}) {
911
1057
  const base = runArgs(ctx, enumeratePartitions(dateRange.startDate, dateRange.endDate));
912
- const probe = createParquetResolverAdapter({ canonicalFallback: options.canonicalFallback ?? false });
913
- const useCanonicalSource = options.canonicalSource !== void 0 && (options.canonicalFallback ?? false) && canonicalSourceWithinCoverage(options.canonicalSource, dateRange.endDate) && canonicalRollupCovers(state, probe.capabilities);
914
- const adapter = useCanonicalSource ? createParquetResolverAdapter({ canonicalFallback: false }) : probe;
1058
+ const probe = createParquetResolverAdapter();
1059
+ const canonicalRequested = canonicalFeatureRequested(state, probe.capabilities);
1060
+ const canonicalDecision = decideCanonicalSource(state, probe.capabilities, options, dateRange.endDate);
1061
+ const useCanonicalSource = canonicalDecision.kind === "canonical-rollup";
1062
+ const source = useCanonicalSource ? canonicalDecision : decidePrimarySource(options, dateRange, sourceFallbacks(canonicalDecision));
915
1063
  const optimized = resolveToSQLOptimized(state, {
916
- adapter,
1064
+ adapter: useCanonicalSource ? createParquetResolverAdapter({ queryCanonicalSource: "column" }) : probe,
917
1065
  siteId: void 0
918
1066
  });
919
1067
  const extras = buildExtrasQueries(state, {
920
- adapter,
1068
+ adapter: probe,
921
1069
  siteId: void 0
922
1070
  });
923
- const mainArgs = useCanonicalSource ? {
1071
+ const extraSource = extras.length > 0 ? useCanonicalSource ? decidePrimarySource(options, dateRange) : source : void 0;
1072
+ const queryDim = canonicalQueryDim(options);
1073
+ const mainArgs = withQueryDimFileSet(useCanonicalSource ? {
924
1074
  ...base,
925
1075
  fileSets: { FILES: {
926
1076
  table: ctx.table,
927
1077
  keys: options.canonicalSource.keys
928
1078
  } }
929
- } : base;
1079
+ } : source.kind === "primary-columnar" ? primaryRunArgs(ctx, options.primarySource.keys) : base, queryDim, canonicalRequested && !useCanonicalSource);
1080
+ const extraArgs = withQueryDimFileSet(extraSource?.kind === "primary-columnar" ? primaryRunArgs(ctx, options.primarySource.keys) : base, queryDim, extras.length > 0);
930
1081
  const resolveExtra = options.resolveExtra;
931
1082
  const [optRes, ...extrasRows] = await Promise.all([runSQL({
932
1083
  ...mainArgs,
@@ -940,7 +1091,7 @@ async function runOptimizedQuery(runSQL, ctx, state, dateRange, options = {}) {
940
1091
  dateRange
941
1092
  }) : null;
942
1093
  return overlaid !== null ? { rows: overlaid } : runSQL({
943
- ...base,
1094
+ ...extraArgs,
944
1095
  sql: e.sql,
945
1096
  params: e.params
946
1097
  });
@@ -963,13 +1114,25 @@ async function runOptimizedQuery(runSQL, ctx, state, dateRange, options = {}) {
963
1114
  extras: extras.map((e, i) => ({
964
1115
  key: e.key,
965
1116
  rows: extrasRows[i].rows
966
- }))
1117
+ })),
1118
+ source,
1119
+ ...extraSource ? { extraSource } : {}
967
1120
  };
968
1121
  }
969
1122
  async function runComparisonQuery(runSQL, ctx, current, previous, windows, filter, options = {}) {
970
- const probe = createParquetResolverAdapter({ canonicalFallback: options.canonicalFallback ?? false });
971
- const useCanonicalSource = options.canonicalSource !== void 0 && (options.canonicalFallback ?? false) && canonicalSourceWithinCoverage(options.canonicalSource, windows.current.endDate > windows.previous.endDate ? windows.current.endDate : windows.previous.endDate) && canonicalRollupCovers(current, probe.capabilities) && canonicalRollupCovers(previous, probe.capabilities);
972
- const adapter = useCanonicalSource ? createParquetResolverAdapter({ canonicalFallback: false }) : probe;
1123
+ const probe = createParquetResolverAdapter();
1124
+ const maxWindowEnd = windows.current.endDate > windows.previous.endDate ? windows.current.endDate : windows.previous.endDate;
1125
+ const currentSource = decideCanonicalSource(current, probe.capabilities, options, maxWindowEnd);
1126
+ const previousSource = decideCanonicalSource(previous, probe.capabilities, options, maxWindowEnd);
1127
+ const startDate = windows.current.startDate < windows.previous.startDate ? windows.current.startDate : windows.previous.startDate;
1128
+ const endDate = windows.current.endDate > windows.previous.endDate ? windows.current.endDate : windows.previous.endDate;
1129
+ const comparisonRange = {
1130
+ startDate,
1131
+ endDate
1132
+ };
1133
+ const source = currentSource.kind === "canonical-rollup" && previousSource.kind === "canonical-rollup" ? querySourceDecision("canonical-rollup") : decidePrimarySource(options, comparisonRange, [...sourceFallbacks(currentSource), ...sourceFallbacks(previousSource)]);
1134
+ const useCanonicalSource = source.kind === "canonical-rollup";
1135
+ const adapter = useCanonicalSource ? createParquetResolverAdapter({ queryCanonicalSource: "column" }) : probe;
973
1136
  const comparison = resolveComparisonSQL(current, previous, {
974
1137
  adapter,
975
1138
  siteId: void 0
@@ -978,14 +1141,14 @@ async function runComparisonQuery(runSQL, ctx, current, previous, windows, filte
978
1141
  adapter,
979
1142
  siteId: void 0
980
1143
  });
981
- const partitions = enumeratePartitions(windows.current.startDate < windows.previous.startDate ? windows.current.startDate : windows.previous.startDate, windows.current.endDate > windows.previous.endDate ? windows.current.endDate : windows.previous.endDate);
982
- const base = useCanonicalSource ? {
1144
+ const partitions = enumeratePartitions(startDate, endDate);
1145
+ const base = withQueryDimFileSet(useCanonicalSource ? {
983
1146
  ...runArgs(ctx, partitions),
984
1147
  fileSets: { FILES: {
985
1148
  table: ctx.table,
986
1149
  keys: options.canonicalSource.keys
987
1150
  } }
988
- } : runArgs(ctx, partitions);
1151
+ } : source.kind === "primary-columnar" ? primaryRunArgs(ctx, options.primarySource.keys) : runArgs(ctx, partitions), canonicalQueryDim(options), !useCanonicalSource && (canonicalFeatureRequested(current, probe.capabilities) || canonicalFeatureRequested(previous, probe.capabilities)));
989
1152
  const main = await runSQL({
990
1153
  ...base,
991
1154
  sql: comparison.sql,
@@ -1004,7 +1167,8 @@ async function runComparisonQuery(runSQL, ctx, current, previous, windows, filte
1004
1167
  return {
1005
1168
  rows: main.rows,
1006
1169
  totalCount: Number(count.rows[0]?.total ?? 0),
1007
- totals: totalsRow.rows[0] ?? {}
1170
+ totals: totalsRow.rows[0] ?? {},
1171
+ source
1008
1172
  };
1009
1173
  }
1010
1174
  function assertSchemaInSync(options) {
@@ -1017,4 +1181,4 @@ function assertSchemaInSync(options) {
1017
1181
  if (missing.length > 0 || extra.length > 0) throw new Error(`${label} drizzle schema for '${key}' drifted from SCHEMAS. Missing: [${missing.join(", ")}]. Extra: [${extra.join(", ")}].`);
1018
1182
  }
1019
1183
  }
1020
- export { DIMENSION_SURFACES, LOGICAL_DATASETS, UnresolvableDatasetError, assertDimensionsSupported, assertSchemaInSync, buildExtrasQueries, buildTotalsSql, canonicalRollupCovers, createIcebergResolverAdapter, createParquetResolverAdapter, createResolverAdapter, createRollupExtrasOverlay, createSqlFragments, dimensionColumn, dimensionValue, getDimensionFilters, getFilterDimensions, getInternalFilters, inferLogicalDataset, isDatasetResolvable, matchesDimensionFilter, matchesMetricFilter, matchesTopLevelPage, mergeExtras, metricValue, pgResolverAdapter, planCoveredByCanonicalRollup, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized, runComparisonQuery, runOptimizedQuery, supportsDimensionOnSurface };
1184
+ export { DIMENSION_SURFACES, LOGICAL_DATASETS, QuerySourceCoverageError, 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, planCoveredByCanonicalRollup, resolveComparisonSQL, resolveToSQL, resolveToSQLOptimized, runComparisonQuery, runOptimizedQuery, supportsDimensionOnSurface };
@@ -145,21 +145,6 @@ declare const queries: import("drizzle-orm/pg-core").PgTableWithColumns<{
145
145
  identity: undefined;
146
146
  generated: undefined;
147
147
  }>;
148
- query_canonical: import("drizzle-orm/pg-core").PgBuildColumn<"queries", import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>, {
149
- name: string;
150
- tableName: "queries";
151
- dataType: "string";
152
- data: string;
153
- driverParam: string;
154
- notNull: false;
155
- hasDefault: false;
156
- isPrimaryKey: false;
157
- isAutoincrement: false;
158
- hasRuntimeDefault: false;
159
- enumValues: undefined;
160
- identity: undefined;
161
- generated: undefined;
162
- }>;
163
148
  date: import("drizzle-orm/pg-core").PgBuildColumn<"queries", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgDateStringBuilder>, {
164
149
  name: string;
165
150
  tableName: "queries";
@@ -339,21 +324,6 @@ declare const page_queries: import("drizzle-orm/pg-core").PgTableWithColumns<{
339
324
  identity: undefined;
340
325
  generated: undefined;
341
326
  }>;
342
- query_canonical: import("drizzle-orm/pg-core").PgBuildColumn<"page_queries", import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>, {
343
- name: string;
344
- tableName: "page_queries";
345
- dataType: "string";
346
- data: string;
347
- driverParam: string;
348
- notNull: false;
349
- hasDefault: false;
350
- isPrimaryKey: false;
351
- isAutoincrement: false;
352
- hasRuntimeDefault: false;
353
- enumValues: undefined;
354
- identity: undefined;
355
- generated: undefined;
356
- }>;
357
327
  date: import("drizzle-orm/pg-core").PgBuildColumn<"page_queries", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgDateStringBuilder>, {
358
328
  name: string;
359
329
  tableName: "page_queries";
@@ -863,21 +833,6 @@ declare const search_appearance_queries: import("drizzle-orm/pg-core").PgTableWi
863
833
  identity: undefined;
864
834
  generated: undefined;
865
835
  }>;
866
- query_canonical: import("drizzle-orm/pg-core").PgBuildColumn<"search_appearance_queries", import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>, {
867
- name: string;
868
- tableName: "search_appearance_queries";
869
- dataType: "string";
870
- data: string;
871
- driverParam: string;
872
- notNull: false;
873
- hasDefault: false;
874
- isPrimaryKey: false;
875
- isAutoincrement: false;
876
- hasRuntimeDefault: false;
877
- enumValues: undefined;
878
- identity: undefined;
879
- generated: undefined;
880
- }>;
881
836
  date: import("drizzle-orm/pg-core").PgBuildColumn<"search_appearance_queries", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgDateStringBuilder>, {
882
837
  name: string;
883
838
  tableName: "search_appearance_queries";
@@ -990,21 +945,6 @@ declare const search_appearance_page_queries: import("drizzle-orm/pg-core").PgTa
990
945
  identity: undefined;
991
946
  generated: undefined;
992
947
  }>;
993
- query_canonical: import("drizzle-orm/pg-core").PgBuildColumn<"search_appearance_page_queries", import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>, {
994
- name: string;
995
- tableName: "search_appearance_page_queries";
996
- dataType: "string";
997
- data: string;
998
- driverParam: string;
999
- notNull: false;
1000
- hasDefault: false;
1001
- isPrimaryKey: false;
1002
- isAutoincrement: false;
1003
- hasRuntimeDefault: false;
1004
- enumValues: undefined;
1005
- identity: undefined;
1006
- generated: undefined;
1007
- }>;
1008
948
  date: import("drizzle-orm/pg-core").PgBuildColumn<"search_appearance_page_queries", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgDateStringBuilder>, {
1009
949
  name: string;
1010
950
  tableName: "search_appearance_page_queries";
@@ -1267,21 +1207,6 @@ declare const drizzleSchema: {
1267
1207
  identity: undefined;
1268
1208
  generated: undefined;
1269
1209
  }>;
1270
- query_canonical: import("drizzle-orm/pg-core").PgBuildColumn<"queries", import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>, {
1271
- name: string;
1272
- tableName: "queries";
1273
- dataType: "string";
1274
- data: string;
1275
- driverParam: string;
1276
- notNull: false;
1277
- hasDefault: false;
1278
- isPrimaryKey: false;
1279
- isAutoincrement: false;
1280
- hasRuntimeDefault: false;
1281
- enumValues: undefined;
1282
- identity: undefined;
1283
- generated: undefined;
1284
- }>;
1285
1210
  date: import("drizzle-orm/pg-core").PgBuildColumn<"queries", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgDateStringBuilder>, {
1286
1211
  name: string;
1287
1212
  tableName: "queries";
@@ -1461,21 +1386,6 @@ declare const drizzleSchema: {
1461
1386
  identity: undefined;
1462
1387
  generated: undefined;
1463
1388
  }>;
1464
- query_canonical: import("drizzle-orm/pg-core").PgBuildColumn<"page_queries", import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>, {
1465
- name: string;
1466
- tableName: "page_queries";
1467
- dataType: "string";
1468
- data: string;
1469
- driverParam: string;
1470
- notNull: false;
1471
- hasDefault: false;
1472
- isPrimaryKey: false;
1473
- isAutoincrement: false;
1474
- hasRuntimeDefault: false;
1475
- enumValues: undefined;
1476
- identity: undefined;
1477
- generated: undefined;
1478
- }>;
1479
1389
  date: import("drizzle-orm/pg-core").PgBuildColumn<"page_queries", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgDateStringBuilder>, {
1480
1390
  name: string;
1481
1391
  tableName: "page_queries";
@@ -1969,21 +1879,6 @@ declare const drizzleSchema: {
1969
1879
  identity: undefined;
1970
1880
  generated: undefined;
1971
1881
  }>;
1972
- query_canonical: import("drizzle-orm/pg-core").PgBuildColumn<"search_appearance_queries", import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>, {
1973
- name: string;
1974
- tableName: "search_appearance_queries";
1975
- dataType: "string";
1976
- data: string;
1977
- driverParam: string;
1978
- notNull: false;
1979
- hasDefault: false;
1980
- isPrimaryKey: false;
1981
- isAutoincrement: false;
1982
- hasRuntimeDefault: false;
1983
- enumValues: undefined;
1984
- identity: undefined;
1985
- generated: undefined;
1986
- }>;
1987
1882
  date: import("drizzle-orm/pg-core").PgBuildColumn<"search_appearance_queries", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgDateStringBuilder>, {
1988
1883
  name: string;
1989
1884
  tableName: "search_appearance_queries";
@@ -2096,21 +1991,6 @@ declare const drizzleSchema: {
2096
1991
  identity: undefined;
2097
1992
  generated: undefined;
2098
1993
  }>;
2099
- query_canonical: import("drizzle-orm/pg-core").PgBuildColumn<"search_appearance_page_queries", import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>, {
2100
- name: string;
2101
- tableName: "search_appearance_page_queries";
2102
- dataType: "string";
2103
- data: string;
2104
- driverParam: string;
2105
- notNull: false;
2106
- hasDefault: false;
2107
- isPrimaryKey: false;
2108
- isAutoincrement: false;
2109
- hasRuntimeDefault: false;
2110
- enumValues: undefined;
2111
- identity: undefined;
2112
- generated: undefined;
2113
- }>;
2114
1994
  date: import("drizzle-orm/pg-core").PgBuildColumn<"search_appearance_page_queries", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgDateStringBuilder>, {
2115
1995
  name: string;
2116
1996
  tableName: "search_appearance_page_queries";