@gscdump/engine-duckdb-wasm 0.26.0 → 0.26.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.
Files changed (2) hide show
  1. package/dist/index.mjs +101 -2
  2. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -78,6 +78,55 @@ function rangePredicate(q) {
78
78
  ]
79
79
  };
80
80
  }
81
+ function compareRangePredicate(q) {
82
+ if (!q.compareRange) return null;
83
+ return {
84
+ sql: "date BETWEEN ? AND ? AND search_type = ?",
85
+ params: [
86
+ q.compareRange.start,
87
+ q.compareRange.end,
88
+ q.searchType
89
+ ]
90
+ };
91
+ }
92
+ const STD_METRICS = [
93
+ "clicks",
94
+ "impressions",
95
+ "ctr",
96
+ "position"
97
+ ];
98
+ function coalesceMetric(metric, src, alias) {
99
+ const ref = `${src}.${metric}`;
100
+ return metric === "clicks" || metric === "impressions" ? `CAST(COALESCE(${ref}, 0) AS DOUBLE) AS ${alias}` : `COALESCE(${ref}, 0) AS ${alias}`;
101
+ }
102
+ function prevAlias(metric) {
103
+ return `prev${metric.charAt(0).toUpperCase()}${metric.slice(1)}`;
104
+ }
105
+ function moverClause(movers) {
106
+ const curClicks = "COALESCE(c.clicks, 0)";
107
+ const prevClicks = "COALESCE(p.clicks, 0)";
108
+ const curImpr = "COALESCE(c.impressions, 0)";
109
+ const prevImpr = "COALESCE(p.impressions, 0)";
110
+ switch (movers) {
111
+ case "improving": return {
112
+ where: `${curClicks} > ${prevClicks}`,
113
+ order: `(${curClicks} - ${prevClicks}) DESC`
114
+ };
115
+ case "declining": return {
116
+ where: `${curClicks} < ${prevClicks}`,
117
+ order: `(${curClicks} - ${prevClicks}) ASC`
118
+ };
119
+ case "new": return {
120
+ where: `${prevImpr} = 0 AND ${curImpr} > 0`,
121
+ order: `${curClicks} DESC, ${curImpr} DESC`
122
+ };
123
+ case "lost": return {
124
+ where: `${curImpr} = 0 AND ${prevImpr} > 0`,
125
+ order: `${prevImpr} DESC`
126
+ };
127
+ default: throw new Error(`[archetype-sql] unknown movers mode: ${movers}`);
128
+ }
129
+ }
81
130
  function facetPredicate(query) {
82
131
  const facets = query.facets;
83
132
  if (!facets?.length) return {
@@ -145,9 +194,34 @@ function compileArchetypeSql(query) {
145
194
  case "top-n-breakdown": {
146
195
  const table = tableForDimensions([query.dimension]);
147
196
  const where = rangePredicate(query);
197
+ const cmp = compareRangePredicate(query);
148
198
  const dir = query.orderBy.dir === "asc" ? "ASC" : "DESC";
199
+ const metricList = query.metrics.includes(query.orderBy.metric) ? query.metrics : [...query.metrics, query.orderBy.metric];
200
+ const variantSel = query.dimension === "queryCanonical" ? ", COUNT(DISTINCT query) AS variantCount" : "";
149
201
  if (query.dimension === "device") {
150
- const metricList = query.metrics.includes(query.orderBy.metric) ? query.metrics : [...query.metrics, query.orderBy.metric];
202
+ if (cmp) {
203
+ const curCols = metricList.map((m) => coalesceMetric(m, "c", m)).join(", ");
204
+ const prevCols = STD_METRICS.map((m) => coalesceMetric(m, "p", prevAlias(m))).join(", ");
205
+ let sql = `WITH cur AS (${deviceUnpivotSql(metricList, where.sql, false)}), prev AS (${deviceUnpivotSql(STD_METRICS, cmp.sql, false)}) SELECT COALESCE(c.device, p.device) AS device, ${curCols}, ${prevCols} FROM cur c FULL OUTER JOIN prev p ON c.device = p.device ORDER BY ${query.orderBy.metric} ${dir} LIMIT ?`;
206
+ const params = [
207
+ ...where.params,
208
+ ...where.params,
209
+ ...where.params,
210
+ ...cmp.params,
211
+ ...cmp.params,
212
+ ...cmp.params,
213
+ query.limit
214
+ ];
215
+ if (query.offset && query.offset > 0) {
216
+ sql += " OFFSET ?";
217
+ params.push(query.offset);
218
+ }
219
+ return {
220
+ table,
221
+ sql,
222
+ params
223
+ };
224
+ }
151
225
  let sql = `SELECT device, ${metricList.join(", ")} FROM (${deviceUnpivotSql(metricList, where.sql, false)}) ORDER BY ${query.orderBy.metric} ${dir} LIMIT ?`;
152
226
  const params = [
153
227
  ...where.params,
@@ -168,7 +242,32 @@ function compileArchetypeSql(query) {
168
242
  const col = DIM_COLUMN[query.dimension];
169
243
  const facet = facetPredicate(query);
170
244
  const totalCol = query.includeTotal ? ", COUNT(*) OVER() AS __total" : "";
171
- let sql = `SELECT ${col} AS ${query.dimension}, ${metricSelectList(query.metrics)}${totalCol} FROM ${table} WHERE ${where.sql}${facet.sql} GROUP BY ${col} ORDER BY ${query.orderBy.metric} ${dir} LIMIT ?`;
245
+ if (cmp) {
246
+ const curCols = metricList.map((m) => coalesceMetric(m, "c", m)).join(", ");
247
+ const prevCols = STD_METRICS.map((m) => coalesceMetric(m, "p", prevAlias(m))).join(", ");
248
+ const variantOut = query.dimension === "queryCanonical" ? ", c.variantCount AS variantCount" : "";
249
+ const mover = query.movers ? moverClause(query.movers) : null;
250
+ const moverWhere = mover ? `WHERE ${mover.where} ` : "";
251
+ const orderSql = mover ? mover.order : `${query.orderBy.metric} ${dir}`;
252
+ let sql = `WITH cur AS (SELECT ${col} AS k, ${metricSelectList(metricList)}${variantSel} FROM ${table} WHERE ${where.sql}${facet.sql} GROUP BY ${col}), prev AS (SELECT ${col} AS k, ${metricSelectList(STD_METRICS)} FROM ${table} WHERE ${cmp.sql}${facet.sql} GROUP BY ${col}) SELECT COALESCE(c.k, p.k) AS ${query.dimension}, ${curCols}, ${prevCols}${variantOut}${totalCol} FROM cur c FULL OUTER JOIN prev p ON c.k = p.k ${moverWhere}ORDER BY ${orderSql} LIMIT ?`;
253
+ const params = [
254
+ ...where.params,
255
+ ...facet.params,
256
+ ...cmp.params,
257
+ ...facet.params,
258
+ query.limit
259
+ ];
260
+ if (query.offset && query.offset > 0) {
261
+ sql += " OFFSET ?";
262
+ params.push(query.offset);
263
+ }
264
+ return {
265
+ table,
266
+ sql,
267
+ params
268
+ };
269
+ }
270
+ let sql = `SELECT ${col} AS ${query.dimension}, ${metricSelectList(query.metrics)}${variantSel}${totalCol} FROM ${table} WHERE ${where.sql}${facet.sql} GROUP BY ${col} ORDER BY ${query.orderBy.metric} ${dir} LIMIT ?`;
172
271
  const params = [
173
272
  ...where.params,
174
273
  ...facet.params,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine-duckdb-wasm",
3
3
  "type": "module",
4
- "version": "0.26.0",
4
+ "version": "0.26.1",
5
5
  "description": "DuckDB-WASM engine adapter for @gscdump/analysis — typed browser analytics against parquet via R2.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -45,9 +45,9 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "drizzle-orm": "1.0.0-rc.3",
48
- "@gscdump/engine": "0.26.0",
49
- "@gscdump/sdk": "0.26.0",
50
- "gscdump": "0.26.0"
48
+ "@gscdump/engine": "0.26.1",
49
+ "@gscdump/sdk": "0.26.1",
50
+ "gscdump": "0.26.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@duckdb/duckdb-wasm": "^1.32.0",