@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.
- package/dist/index.mjs +101 -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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
49
|
-
"@gscdump/sdk": "0.26.
|
|
50
|
-
"gscdump": "0.26.
|
|
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",
|