@atscript/db-sql-tools 0.1.128 → 0.1.129
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.cjs +33 -23
- package/dist/index.d.cts +4 -2
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +33 -23
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -339,19 +339,27 @@ function buildAggExpr(dialect, expr) {
|
|
|
339
339
|
return `${aggFnSql(dialect, expr)} AS ${dialect.quoteIdentifier((0, _atscript_db_agg.resolveAlias)(expr))}`;
|
|
340
340
|
}
|
|
341
341
|
/**
|
|
342
|
-
*
|
|
343
|
-
* `
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
342
|
+
* ` HAVING <predicate>` (leading space) + params for `controls.$having`, or
|
|
343
|
+
* `undefined` when there is nothing to render. Shared by the row and the
|
|
344
|
+
* count builders so both filter the same group set.
|
|
345
|
+
*
|
|
346
|
+
* A key that names an aggregate alias (`$as`, else `fn_field`) renders the
|
|
347
|
+
* aggregate expression itself — `SUM("amount") > ?` — because PostgreSQL does
|
|
348
|
+
* not allow a SELECT alias in HAVING (MySQL and SQLite tolerate it, so the
|
|
349
|
+
* expression form keeps all three identical). Other keys (grouped columns)
|
|
350
|
+
* render as plain columns.
|
|
347
351
|
*/
|
|
348
|
-
function
|
|
352
|
+
function havingClause(dialect, controls) {
|
|
349
353
|
const having = controls.$having;
|
|
350
|
-
|
|
351
|
-
if (!aggregates?.length) return buildWhere(dialect, having);
|
|
354
|
+
if (!having) return void 0;
|
|
352
355
|
const exprByAlias = /* @__PURE__ */ new Map();
|
|
353
|
-
for (const expr of aggregates) exprByAlias.set((0, _atscript_db_agg.resolveAlias)(expr), aggFnSql(dialect, expr));
|
|
354
|
-
|
|
356
|
+
for (const expr of controls.$select?.aggregates ?? []) exprByAlias.set((0, _atscript_db_agg.resolveAlias)(expr), aggFnSql(dialect, expr));
|
|
357
|
+
const fragment = (0, _uniqu_core.walkFilter)(having, createFilterVisitor(dialect, { columnRef: (field) => exprByAlias.get(field) ?? dialect.quoteIdentifier(field) }));
|
|
358
|
+
if (!fragment || fragment.sql === EMPTY_AND.sql) return void 0;
|
|
359
|
+
return {
|
|
360
|
+
sql: ` HAVING ${fragment.sql}`,
|
|
361
|
+
params: fragment.params
|
|
362
|
+
};
|
|
355
363
|
}
|
|
356
364
|
/**
|
|
357
365
|
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
@@ -369,12 +377,10 @@ function buildAggregateSelect(dialect, table, where, controls) {
|
|
|
369
377
|
const groupCols = groupBy.map((f) => dialect.quoteIdentifier(f)).join(", ");
|
|
370
378
|
sql += ` GROUP BY ${groupCols}`;
|
|
371
379
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
params.push(...havingFragment.params);
|
|
377
|
-
}
|
|
380
|
+
const having = havingClause(dialect, controls);
|
|
381
|
+
if (having) {
|
|
382
|
+
sql += having.sql;
|
|
383
|
+
params.push(...having.params);
|
|
378
384
|
}
|
|
379
385
|
if (controls.$sort) {
|
|
380
386
|
const orderParts = [];
|
|
@@ -396,19 +402,23 @@ function buildAggregateSelect(dialect, table, where, controls) {
|
|
|
396
402
|
});
|
|
397
403
|
}
|
|
398
404
|
/**
|
|
399
|
-
* Builds a COUNT query for the number of distinct groups
|
|
400
|
-
*
|
|
405
|
+
* Builds a COUNT query for the number of distinct groups — the groups that
|
|
406
|
+
* survive `$having` when one is given (the same predicate the row query
|
|
407
|
+
* renders, so `$count` agrees with the row set). Returns `{ count: N }` when
|
|
408
|
+
* executed.
|
|
401
409
|
*/
|
|
402
410
|
function buildAggregateCount(dialect, table, where, controls) {
|
|
403
411
|
const groupFields = controls.$groupBy;
|
|
404
|
-
|
|
405
|
-
|
|
412
|
+
const having = havingClause(dialect, controls);
|
|
413
|
+
const countCol = `COUNT(*) AS ${dialect.quoteIdentifier("count")}`;
|
|
414
|
+
if (!groupFields?.length && !having) return finalizeParams(dialect, {
|
|
415
|
+
sql: `SELECT ${countCol} FROM ${dialect.quoteTable(table)} WHERE ${where.sql}`,
|
|
406
416
|
params: where.params
|
|
407
417
|
});
|
|
408
|
-
const
|
|
418
|
+
const groupBy = groupFields?.length ? ` GROUP BY ${groupFields.map((f) => dialect.quoteIdentifier(f)).join(", ")}` : "";
|
|
409
419
|
return finalizeParams(dialect, {
|
|
410
|
-
sql: `SELECT
|
|
411
|
-
params: where.params
|
|
420
|
+
sql: `SELECT ${countCol} FROM (SELECT ${groupBy ? "1" : "COUNT(*)"} FROM ${dialect.quoteTable(table)} WHERE ${where.sql}${groupBy}${having?.sql ?? ""}) AS ${dialect.quoteIdentifier("_groups")}`,
|
|
421
|
+
params: [...where.params, ...having?.params ?? []]
|
|
412
422
|
});
|
|
413
423
|
}
|
|
414
424
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -215,8 +215,10 @@ declare const AGG_FN_SQL: Record<string, string>;
|
|
|
215
215
|
*/
|
|
216
216
|
declare function buildAggregateSelect(dialect: SqlDialect, table: string, where: TSqlFragment, controls: DbControls): TSqlFragment;
|
|
217
217
|
/**
|
|
218
|
-
* Builds a COUNT query for the number of distinct groups
|
|
219
|
-
*
|
|
218
|
+
* Builds a COUNT query for the number of distinct groups — the groups that
|
|
219
|
+
* survive `$having` when one is given (the same predicate the row query
|
|
220
|
+
* renders, so `$count` agrees with the row set). Returns `{ count: N }` when
|
|
221
|
+
* executed.
|
|
220
222
|
*/
|
|
221
223
|
declare function buildAggregateCount(dialect: SqlDialect, table: string, where: TSqlFragment, controls: DbControls): TSqlFragment;
|
|
222
224
|
//#endregion
|
package/dist/index.d.mts
CHANGED
|
@@ -215,8 +215,10 @@ declare const AGG_FN_SQL: Record<string, string>;
|
|
|
215
215
|
*/
|
|
216
216
|
declare function buildAggregateSelect(dialect: SqlDialect, table: string, where: TSqlFragment, controls: DbControls): TSqlFragment;
|
|
217
217
|
/**
|
|
218
|
-
* Builds a COUNT query for the number of distinct groups
|
|
219
|
-
*
|
|
218
|
+
* Builds a COUNT query for the number of distinct groups — the groups that
|
|
219
|
+
* survive `$having` when one is given (the same predicate the row query
|
|
220
|
+
* renders, so `$count` agrees with the row set). Returns `{ count: N }` when
|
|
221
|
+
* executed.
|
|
220
222
|
*/
|
|
221
223
|
declare function buildAggregateCount(dialect: SqlDialect, table: string, where: TSqlFragment, controls: DbControls): TSqlFragment;
|
|
222
224
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -338,19 +338,27 @@ function buildAggExpr(dialect, expr) {
|
|
|
338
338
|
return `${aggFnSql(dialect, expr)} AS ${dialect.quoteIdentifier(resolveAlias(expr))}`;
|
|
339
339
|
}
|
|
340
340
|
/**
|
|
341
|
-
*
|
|
342
|
-
* `
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
341
|
+
* ` HAVING <predicate>` (leading space) + params for `controls.$having`, or
|
|
342
|
+
* `undefined` when there is nothing to render. Shared by the row and the
|
|
343
|
+
* count builders so both filter the same group set.
|
|
344
|
+
*
|
|
345
|
+
* A key that names an aggregate alias (`$as`, else `fn_field`) renders the
|
|
346
|
+
* aggregate expression itself — `SUM("amount") > ?` — because PostgreSQL does
|
|
347
|
+
* not allow a SELECT alias in HAVING (MySQL and SQLite tolerate it, so the
|
|
348
|
+
* expression form keeps all three identical). Other keys (grouped columns)
|
|
349
|
+
* render as plain columns.
|
|
346
350
|
*/
|
|
347
|
-
function
|
|
351
|
+
function havingClause(dialect, controls) {
|
|
348
352
|
const having = controls.$having;
|
|
349
|
-
|
|
350
|
-
if (!aggregates?.length) return buildWhere(dialect, having);
|
|
353
|
+
if (!having) return void 0;
|
|
351
354
|
const exprByAlias = /* @__PURE__ */ new Map();
|
|
352
|
-
for (const expr of aggregates) exprByAlias.set(resolveAlias(expr), aggFnSql(dialect, expr));
|
|
353
|
-
|
|
355
|
+
for (const expr of controls.$select?.aggregates ?? []) exprByAlias.set(resolveAlias(expr), aggFnSql(dialect, expr));
|
|
356
|
+
const fragment = walkFilter(having, createFilterVisitor(dialect, { columnRef: (field) => exprByAlias.get(field) ?? dialect.quoteIdentifier(field) }));
|
|
357
|
+
if (!fragment || fragment.sql === EMPTY_AND.sql) return void 0;
|
|
358
|
+
return {
|
|
359
|
+
sql: ` HAVING ${fragment.sql}`,
|
|
360
|
+
params: fragment.params
|
|
361
|
+
};
|
|
354
362
|
}
|
|
355
363
|
/**
|
|
356
364
|
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
@@ -368,12 +376,10 @@ function buildAggregateSelect(dialect, table, where, controls) {
|
|
|
368
376
|
const groupCols = groupBy.map((f) => dialect.quoteIdentifier(f)).join(", ");
|
|
369
377
|
sql += ` GROUP BY ${groupCols}`;
|
|
370
378
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
params.push(...havingFragment.params);
|
|
376
|
-
}
|
|
379
|
+
const having = havingClause(dialect, controls);
|
|
380
|
+
if (having) {
|
|
381
|
+
sql += having.sql;
|
|
382
|
+
params.push(...having.params);
|
|
377
383
|
}
|
|
378
384
|
if (controls.$sort) {
|
|
379
385
|
const orderParts = [];
|
|
@@ -395,19 +401,23 @@ function buildAggregateSelect(dialect, table, where, controls) {
|
|
|
395
401
|
});
|
|
396
402
|
}
|
|
397
403
|
/**
|
|
398
|
-
* Builds a COUNT query for the number of distinct groups
|
|
399
|
-
*
|
|
404
|
+
* Builds a COUNT query for the number of distinct groups — the groups that
|
|
405
|
+
* survive `$having` when one is given (the same predicate the row query
|
|
406
|
+
* renders, so `$count` agrees with the row set). Returns `{ count: N }` when
|
|
407
|
+
* executed.
|
|
400
408
|
*/
|
|
401
409
|
function buildAggregateCount(dialect, table, where, controls) {
|
|
402
410
|
const groupFields = controls.$groupBy;
|
|
403
|
-
|
|
404
|
-
|
|
411
|
+
const having = havingClause(dialect, controls);
|
|
412
|
+
const countCol = `COUNT(*) AS ${dialect.quoteIdentifier("count")}`;
|
|
413
|
+
if (!groupFields?.length && !having) return finalizeParams(dialect, {
|
|
414
|
+
sql: `SELECT ${countCol} FROM ${dialect.quoteTable(table)} WHERE ${where.sql}`,
|
|
405
415
|
params: where.params
|
|
406
416
|
});
|
|
407
|
-
const
|
|
417
|
+
const groupBy = groupFields?.length ? ` GROUP BY ${groupFields.map((f) => dialect.quoteIdentifier(f)).join(", ")}` : "";
|
|
408
418
|
return finalizeParams(dialect, {
|
|
409
|
-
sql: `SELECT
|
|
410
|
-
params: where.params
|
|
419
|
+
sql: `SELECT ${countCol} FROM (SELECT ${groupBy ? "1" : "COUNT(*)"} FROM ${dialect.quoteTable(table)} WHERE ${where.sql}${groupBy}${having?.sql ?? ""}) AS ${dialect.quoteIdentifier("_groups")}`,
|
|
420
|
+
params: [...where.params, ...having?.params ?? []]
|
|
411
421
|
});
|
|
412
422
|
}
|
|
413
423
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-sql-tools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.129",
|
|
4
4
|
"description": "Shared SQL builder utilities for @atscript database adapters.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@uniqu/core": "^0.1.8",
|
|
45
|
-
"@atscript/db": "^0.1.
|
|
45
|
+
"@atscript/db": "^0.1.129"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "vp pack",
|