@atscript/db-sql-tools 0.1.147 → 0.1.149
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 +261 -66
- package/dist/index.d.cts +93 -5
- package/dist/index.d.mts +93 -5
- package/dist/index.mjs +256 -67
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
let _uniqu_core = require("@uniqu/core");
|
|
3
2
|
let _atscript_db = require("@atscript/db");
|
|
3
|
+
let _uniqu_core = require("@uniqu/core");
|
|
4
4
|
let _atscript_db_agg = require("@atscript/db/agg");
|
|
5
5
|
//#region src/dialect.ts
|
|
6
6
|
/**
|
|
@@ -24,6 +24,32 @@ function havingGroupRef(dialect, expr, alias) {
|
|
|
24
24
|
return dialect.bucketAliasInHaving ? dialect.quoteIdentifier(alias) : expr;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
+
* Runs `fn`, rethrowing a driver error as the `DbError`
|
|
28
|
+
* {@link SqlDialect.mapQueryError} maps it to (any other error unchanged).
|
|
29
|
+
* `controls` of the query tell whether it has arithmetic expressions.
|
|
30
|
+
* @since 0.1.148
|
|
31
|
+
*/
|
|
32
|
+
async function mapQueryErrors(dialect, fn, controls) {
|
|
33
|
+
try {
|
|
34
|
+
return await fn();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const select = controls?.$select;
|
|
37
|
+
const arithmetic = !!(select?.exprAggregates?.length || select?.exprs?.length);
|
|
38
|
+
throw dialect.mapQueryError?.(error, arithmetic) ?? error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* One `ORDER BY` key with the uniform "NULL is the smallest value" ordering:
|
|
43
|
+
* `<expr> ASC` / `<expr> DESC`, plus `NULLS FIRST` / `NULLS LAST` on a
|
|
44
|
+
* dialect where NULL sorts largest ({@link SqlDialect.nullsSortLargest}).
|
|
45
|
+
* Shared by first-row joins and `first` / `last` aggregates.
|
|
46
|
+
* @since 0.1.148
|
|
47
|
+
*/
|
|
48
|
+
function orderKeySql(dialect, expr, desc) {
|
|
49
|
+
const nulls = dialect.nullsSortLargest ? desc ? " NULLS LAST" : " NULLS FIRST" : "";
|
|
50
|
+
return `${expr} ${desc ? "DESC" : "ASC"}${nulls}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
27
53
|
* Each JSON path segment wrapped in double quotes (`"a"`), for the dialects'
|
|
28
54
|
* {@link SqlDialect.jsonExtract} path literals (`'$."a"."b"'`, `'{"a","b"}'`).
|
|
29
55
|
*
|
|
@@ -55,6 +81,67 @@ const EMPTY_OR = {
|
|
|
55
81
|
params: []
|
|
56
82
|
};
|
|
57
83
|
//#endregion
|
|
84
|
+
//#region src/arith.ts
|
|
85
|
+
/**
|
|
86
|
+
* Renders a computed expression tree (`@db.compute` on a view, or a query-time
|
|
87
|
+
* arithmetic `$select` entry) as SQL, in IEEE double on every dialect: each
|
|
88
|
+
* literal is cast with {@link SqlDialect.castDouble}; `+ - *` render as
|
|
89
|
+
* `(l op r)`, `/` as `(l / NULLIF(r, 0))` (division by zero is NULL), unary
|
|
90
|
+
* minus as `(-x)` and `coalesce` as `COALESCE(…)`. `leaf` renders a field
|
|
91
|
+
* reference as raw SQL, which is cast to double here — or, as `{ double }`,
|
|
92
|
+
* SQL that already is a double (a nested computed column) and stays as is.
|
|
93
|
+
*
|
|
94
|
+
* The one renderer of both paths, so declared and query-time arithmetic
|
|
95
|
+
* cannot diverge.
|
|
96
|
+
*
|
|
97
|
+
* @param fail - the error to throw for a failure (default `DbError`:
|
|
98
|
+
* `AGG_EXPR_NOT_SUPPORTED` without `castDouble`, `INVALID_QUERY` for a
|
|
99
|
+
* non-finite literal).
|
|
100
|
+
* @since 0.1.148
|
|
101
|
+
*/
|
|
102
|
+
function renderArith(dialect, node, leaf, fail = defaultFailure) {
|
|
103
|
+
const cast = dialect.castDouble?.bind(dialect);
|
|
104
|
+
if (!cast) throw fail("no-cast");
|
|
105
|
+
const render = (e) => {
|
|
106
|
+
if (typeof e === "number") {
|
|
107
|
+
if (!Number.isFinite(e)) throw fail("non-finite");
|
|
108
|
+
return cast(String(e));
|
|
109
|
+
}
|
|
110
|
+
if ("field" in e) {
|
|
111
|
+
const sql = leaf(e.field);
|
|
112
|
+
return typeof sql === "string" ? cast(sql) : sql.double;
|
|
113
|
+
}
|
|
114
|
+
const args = e.args.map(render);
|
|
115
|
+
switch (e.op) {
|
|
116
|
+
case "neg": return `(-${args[0]})`;
|
|
117
|
+
case "coalesce": return `COALESCE(${args.join(", ")})`;
|
|
118
|
+
case "/": return `(${args[0]} / NULLIF(${args[1]}, 0))`;
|
|
119
|
+
default: return `(${args[0]} ${e.op} ${args[1]})`;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
return render(node);
|
|
123
|
+
}
|
|
124
|
+
function defaultFailure(reason) {
|
|
125
|
+
return reason === "no-cast" ? (0, _atscript_db.aggregateExpressionsNotSupported)() : new _atscript_db.DbError("INVALID_QUERY", [{
|
|
126
|
+
path: "$select",
|
|
127
|
+
message: "Expression literal is not finite"
|
|
128
|
+
}]);
|
|
129
|
+
}
|
|
130
|
+
/** The error of a numeric overflow that is no arithmetic expression's (`INVALID_QUERY`, `path` `""`). */
|
|
131
|
+
function numericOutOfRangeError() {
|
|
132
|
+
return new _atscript_db.DbError("INVALID_QUERY", [{
|
|
133
|
+
path: "",
|
|
134
|
+
message: "Numeric value out of range"
|
|
135
|
+
}]);
|
|
136
|
+
}
|
|
137
|
+
/** The error of a double overflow in aggregate arithmetic (`INVALID_QUERY`, `path` `$select`). */
|
|
138
|
+
function arithOverflowError() {
|
|
139
|
+
return new _atscript_db.DbError("INVALID_QUERY", [{
|
|
140
|
+
path: "$select",
|
|
141
|
+
message: "Arithmetic overflow"
|
|
142
|
+
}]);
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
58
145
|
//#region src/filter-builder.ts
|
|
59
146
|
/**
|
|
60
147
|
* Creates a dialect-specific filter visitor for `walkFilter`.
|
|
@@ -428,15 +515,23 @@ const AGG_FN_SQL = {
|
|
|
428
515
|
*/
|
|
429
516
|
function renderAggCall(fn, arg, path) {
|
|
430
517
|
(0, _atscript_db_agg.assertAggregateFn)(fn, path);
|
|
431
|
-
|
|
518
|
+
if (fn === "countDistinct") return `COUNT(DISTINCT ${arg})`;
|
|
519
|
+
return `${AGG_FN_SQL[fn]}(${arg})`;
|
|
432
520
|
}
|
|
433
|
-
/**
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
521
|
+
/**
|
|
522
|
+
* `MIN` / `MAX` of a column the engine may not aggregate directly: a boolean
|
|
523
|
+
* on a dialect with {@link SqlDialect.booleanAggregates} (PostgreSQL has no
|
|
524
|
+
* `MIN(boolean)`) renders its stand-in.
|
|
525
|
+
*/
|
|
526
|
+
function pickSql(dialect, select, fn, column, sql) {
|
|
527
|
+
const bool = select?.sources.get(column)?.designType === "boolean" ? dialect.booleanAggregates?.[fn] : void 0;
|
|
528
|
+
return bool ? `${bool}(${sql})` : renderAggCall(fn, sql);
|
|
437
529
|
}
|
|
438
|
-
|
|
439
|
-
|
|
530
|
+
/** The bare aggregate call, e.g. `SUM("amount")` / `COUNT(*)` / `COUNT(DISTINCT "region")`. */
|
|
531
|
+
function aggFnSql(dialect, select, expr) {
|
|
532
|
+
if (expr.$field === "*") return renderAggCall(expr.$fn, "*");
|
|
533
|
+
const field = dialect.quoteIdentifier(expr.$field);
|
|
534
|
+
return expr.$fn === "min" || expr.$fn === "max" ? pickSql(dialect, select, expr.$fn, expr.$field, field) : renderAggCall(expr.$fn, field);
|
|
440
535
|
}
|
|
441
536
|
const BUCKET_UNIT_SET = new Set(_uniqu_core.BUCKET_UNITS);
|
|
442
537
|
const WEEK_START_SET = new Set(_uniqu_core.WEEK_STARTS);
|
|
@@ -484,25 +579,52 @@ function groupKeySql(dialect, controls, key) {
|
|
|
484
579
|
const bucket = controls.$select?.bucketByAlias(key);
|
|
485
580
|
return bucket ? bucketSql(dialect, bucket) : dialect.quoteIdentifier(key);
|
|
486
581
|
}
|
|
582
|
+
/** Alias of the derived table `first` / `last` aggregates read from. */
|
|
583
|
+
const ROWS_ALIAS = "__as_rows";
|
|
584
|
+
/** The derived-table column of the `i`-th `first` / `last` entry. */
|
|
585
|
+
const firstLastColumn = (i) => `__as_fl${i}`;
|
|
586
|
+
/**
|
|
587
|
+
* The SQL each computed alias stands for where an alias is not usable
|
|
588
|
+
* (HAVING; other expressions): an aggregate's call, a `first` / `last`
|
|
589
|
+
* derived column (aggregated: constant within its group), a row-level
|
|
590
|
+
* expression aggregate's call, and every group-level expression rendered over
|
|
591
|
+
* those (in dependency order, operands cast to double). A grouped column
|
|
592
|
+
* renders as its quoted name.
|
|
593
|
+
*/
|
|
594
|
+
function aliasSqlMap(dialect, controls) {
|
|
595
|
+
const map = /* @__PURE__ */ new Map();
|
|
596
|
+
const select = controls.$select;
|
|
597
|
+
const quote = (name) => dialect.quoteIdentifier(name);
|
|
598
|
+
for (const expr of select?.aggregates ?? []) map.set((0, _atscript_db_agg.resolveAlias)(expr), aggFnSql(dialect, select, expr));
|
|
599
|
+
for (const e of select?.exprAggregates ?? []) map.set(e.alias, renderAggCall(e.fn, renderArith(dialect, e.expr, quote)));
|
|
600
|
+
(select?.firstLast ?? []).forEach((fl, i) => {
|
|
601
|
+
const col = quote(firstLastColumn(i));
|
|
602
|
+
map.set(fl.alias, dialect.anyValue ? dialect.anyValue(col, select?.sources.get(fl.column)) : renderAggCall("min", col));
|
|
603
|
+
});
|
|
604
|
+
for (const e of select?.exprs ?? []) map.set(e.alias, renderArith(dialect, e.expr, (name) => map.get(name) ?? quote(name)));
|
|
605
|
+
return map;
|
|
606
|
+
}
|
|
487
607
|
/**
|
|
488
608
|
* ` HAVING <predicate>` (leading space) + params for `controls.$having`, or
|
|
489
609
|
* `undefined` when there is nothing to render. Shared by the row and the
|
|
490
610
|
* count builders so both filter the same group set.
|
|
491
611
|
*
|
|
492
|
-
* A key that names
|
|
493
|
-
*
|
|
494
|
-
* not allow a SELECT alias in HAVING (MySQL and SQLite
|
|
495
|
-
* expression form keeps all three identical). A
|
|
496
|
-
* its bucket expression ({@link groupKeySql}),
|
|
497
|
-
* dialect sets `SqlDialect.bucketAliasInHaving`
|
|
498
|
-
* (grouped columns) render as plain columns.
|
|
612
|
+
* A key that names a computed alias (`$as`, else `fn_field`) renders what the
|
|
613
|
+
* alias stands for — `SUM("amount") > ?`, an expression's arithmetic — because
|
|
614
|
+
* PostgreSQL does not allow a SELECT alias in HAVING (MySQL and SQLite
|
|
615
|
+
* tolerate it, so the expression form keeps all three identical). A
|
|
616
|
+
* calendar-bucket alias renders its bucket expression ({@link groupKeySql}),
|
|
617
|
+
* or its quoted alias when the dialect sets `SqlDialect.bucketAliasInHaving`
|
|
618
|
+
* (`havingGroupRef`). Other keys (grouped columns) render as plain columns.
|
|
619
|
+
* `aliasSql` is the query's {@link aliasSqlMap} when the caller has built it.
|
|
499
620
|
*/
|
|
500
|
-
function havingClause(dialect, controls) {
|
|
621
|
+
function havingClause(dialect, controls, aliasSql) {
|
|
501
622
|
const having = controls.$having;
|
|
502
623
|
if (!having) return void 0;
|
|
503
|
-
const exprByAlias =
|
|
504
|
-
|
|
624
|
+
const exprByAlias = aliasSql ?? aliasSqlMap(dialect, controls);
|
|
625
|
+
const refs = /* @__PURE__ */ new Set();
|
|
505
626
|
const fragment = (0, _uniqu_core.walkFilter)(having, createFilterVisitor(dialect, { columnRef: (field) => {
|
|
627
|
+
refs.add(field);
|
|
506
628
|
const aggExpr = exprByAlias.get(field);
|
|
507
629
|
if (aggExpr) return aggExpr;
|
|
508
630
|
const bucket = controls.$select?.bucketByAlias(field);
|
|
@@ -511,43 +633,103 @@ function havingClause(dialect, controls) {
|
|
|
511
633
|
if (!fragment || fragment.sql === EMPTY_AND.sql) return void 0;
|
|
512
634
|
return {
|
|
513
635
|
sql: ` HAVING ${fragment.sql}`,
|
|
514
|
-
params: fragment.params
|
|
636
|
+
params: fragment.params,
|
|
637
|
+
refs
|
|
515
638
|
};
|
|
516
639
|
}
|
|
517
640
|
/** `<bucket expr> AS "alias"` for every calendar bucket in `$select`. */
|
|
518
641
|
function bucketSelectParts(dialect, controls) {
|
|
519
642
|
return (controls.$select?.buckets ?? []).map((bucket) => `${bucketSql(dialect, bucket)} AS ${dialect.quoteIdentifier(bucket.alias)}`);
|
|
520
643
|
}
|
|
644
|
+
/** ` GROUP BY <keys>` (leading space), or `""` for the whole table as one group. */
|
|
645
|
+
function groupByClause(dialect, controls) {
|
|
646
|
+
const keys = controls.$groupBy ?? [];
|
|
647
|
+
return keys.length ? ` GROUP BY ${keys.map((key) => groupKeySql(dialect, controls, key)).join(", ")}` : "";
|
|
648
|
+
}
|
|
649
|
+
/** Whether any of the HAVING keys is a `first` / `last` alias, or an expression over one. */
|
|
650
|
+
function readsFirstLast(select, refs) {
|
|
651
|
+
if (!select?.firstLast) return false;
|
|
652
|
+
const firstLast = new Set(select.firstLast.map((fl) => fl.alias));
|
|
653
|
+
const names = new Map((select.exprs ?? []).map((e) => [e.alias, e.names]));
|
|
654
|
+
const visit = (name, seen) => {
|
|
655
|
+
if (firstLast.has(name)) return true;
|
|
656
|
+
const deps = names.get(name);
|
|
657
|
+
if (!deps || seen.has(name)) return false;
|
|
658
|
+
seen.add(name);
|
|
659
|
+
return deps.some((dep) => visit(dep, seen));
|
|
660
|
+
};
|
|
661
|
+
return [...refs].some((key) => visit(key, /* @__PURE__ */ new Set()));
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* The columns the outer query reads from the derived table of a `first` /
|
|
665
|
+
* `last` aggregate: group keys (a bucket's source for its alias), plain
|
|
666
|
+
* `$select` fields, aggregate fields and the leaves of row-level expressions.
|
|
667
|
+
* The `$rowOrder` columns and the `first` / `last` sources stay inside the
|
|
668
|
+
* window; a group-level expression, `$having` and `$sort` read aliases or
|
|
669
|
+
* group keys only.
|
|
670
|
+
*/
|
|
671
|
+
function rowColumns(controls) {
|
|
672
|
+
const select = controls.$select;
|
|
673
|
+
const columns = /* @__PURE__ */ new Set();
|
|
674
|
+
for (const key of controls.$groupBy ?? []) if (!select?.bucketByAlias(key)) columns.add(key);
|
|
675
|
+
for (const bucket of select?.buckets ?? []) columns.add(bucket.field);
|
|
676
|
+
for (const field of select?.asArray ?? []) columns.add(field);
|
|
677
|
+
for (const expr of select?.aggregates ?? []) if (expr.$field !== "*") columns.add(expr.$field);
|
|
678
|
+
for (const e of select?.exprAggregates ?? []) for (const name of e.names) columns.add(name);
|
|
679
|
+
return [...columns];
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* The row source of an aggregate: `FROM <table> WHERE <where>`; with `first` /
|
|
683
|
+
* `last` (and `withRows`) a derived table instead — the table's rows that
|
|
684
|
+
* pass the WHERE, only the columns {@link rowColumns} lists, plus one
|
|
685
|
+
* `FIRST_VALUE(col) OVER (PARTITION BY <group keys> ORDER BY <rowOrder>)`
|
|
686
|
+
* column per entry (`last` over the reversed order). Each group then reads its
|
|
687
|
+
* representative row's value as an aggregate (constant within the group). The
|
|
688
|
+
* WHERE moves inside unchanged, so the bind parameters keep their order.
|
|
689
|
+
*/
|
|
690
|
+
function aggSource(dialect, table, where, controls, withRows) {
|
|
691
|
+
const quotedTable = dialect.quoteTable(table);
|
|
692
|
+
const firstLast = controls.$select?.firstLast;
|
|
693
|
+
const rowOrder = controls.$select?.rowOrder;
|
|
694
|
+
if (!withRows || !firstLast?.length || !rowOrder?.length) return `FROM ${quotedTable} WHERE ${where.sql}`;
|
|
695
|
+
const groupBy = controls.$groupBy;
|
|
696
|
+
const partition = groupBy?.length ? `PARTITION BY ${groupBy.map((key) => groupKeySql(dialect, controls, key)).join(", ")} ` : "";
|
|
697
|
+
const order = (reverse) => rowOrder.map((k) => orderKeySql(dialect, dialect.quoteIdentifier(k.column), k.desc !== reverse)).join(", ");
|
|
698
|
+
return `FROM (SELECT ${[...rowColumns(controls).map((column) => dialect.quoteIdentifier(column)), ...firstLast.map((fl, i) => {
|
|
699
|
+
const col = dialect.quoteIdentifier(firstLastColumn(i));
|
|
700
|
+
return `FIRST_VALUE(${dialect.quoteIdentifier(fl.column)}) OVER (${partition}ORDER BY ${order(fl.fn === "last")}) AS ${col}`;
|
|
701
|
+
})].join(", ")} FROM ${quotedTable} WHERE ${where.sql}) AS ${dialect.quoteIdentifier(ROWS_ALIAS)}`;
|
|
702
|
+
}
|
|
521
703
|
/**
|
|
522
704
|
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
523
705
|
*
|
|
524
706
|
* SELECT lists the plain grouped columns, then `<bucket expr> AS "alias"`
|
|
525
|
-
* per calendar bucket, then
|
|
707
|
+
* per calendar bucket, then every computed alias (aggregates, row-level
|
|
708
|
+
* expression aggregates, `first` / `last`, group-level expressions — the
|
|
709
|
+
* order of `UniquSelect.computedAliases`). Bucket expressions are
|
|
526
710
|
* parameter-free, so the bind parameters are exactly those of the same query
|
|
527
711
|
* without buckets (WHERE, HAVING, LIMIT, OFFSET).
|
|
528
712
|
*/
|
|
529
713
|
function buildAggregateSelect(dialect, table, where, controls) {
|
|
714
|
+
const select = controls.$select;
|
|
530
715
|
const selectParts = [];
|
|
531
|
-
const
|
|
532
|
-
if (plainFields) for (const f of plainFields) selectParts.push(dialect.quoteIdentifier(f));
|
|
716
|
+
for (const f of select?.asArray ?? []) selectParts.push(dialect.quoteIdentifier(f));
|
|
533
717
|
selectParts.push(...bucketSelectParts(dialect, controls));
|
|
534
|
-
|
|
535
|
-
if (
|
|
536
|
-
|
|
718
|
+
let aliasSql;
|
|
719
|
+
if (select?.exprAggregates || select?.firstLast || select?.exprs) {
|
|
720
|
+
aliasSql = aliasSqlMap(dialect, controls);
|
|
721
|
+
for (const alias of select.computedAliases) selectParts.push(`${aliasSql.get(alias)} AS ${dialect.quoteIdentifier(alias)}`);
|
|
722
|
+
} else for (const expr of select?.aggregates ?? []) selectParts.push(`${aggFnSql(dialect, select, expr)} AS ${dialect.quoteIdentifier((0, _atscript_db_agg.resolveAlias)(expr))}`);
|
|
723
|
+
let sql = `SELECT ${selectParts.length > 0 ? selectParts.join(", ") : "*"} ${aggSource(dialect, table, where, controls, true)}${groupByClause(dialect, controls)}`;
|
|
537
724
|
const params = [...where.params];
|
|
538
|
-
const
|
|
539
|
-
if (groupBy?.length) {
|
|
540
|
-
const groupCols = groupBy.map((key) => groupKeySql(dialect, controls, key)).join(", ");
|
|
541
|
-
sql += ` GROUP BY ${groupCols}`;
|
|
542
|
-
}
|
|
543
|
-
const having = havingClause(dialect, controls);
|
|
725
|
+
const having = havingClause(dialect, controls, aliasSql);
|
|
544
726
|
if (having) {
|
|
545
727
|
sql += having.sql;
|
|
546
728
|
params.push(...having.params);
|
|
547
729
|
}
|
|
548
730
|
if (controls.$sort) {
|
|
549
731
|
const orderParts = [];
|
|
550
|
-
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(
|
|
732
|
+
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(orderKeySql(dialect, dialect.quoteIdentifier(col), dir === -1));
|
|
551
733
|
if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
552
734
|
}
|
|
553
735
|
if (controls.$limit !== void 0) {
|
|
@@ -568,21 +750,30 @@ function buildAggregateSelect(dialect, table, where, controls) {
|
|
|
568
750
|
* Builds a COUNT query for the number of distinct groups — the groups that
|
|
569
751
|
* survive `$having` when one is given (the same predicate the row query
|
|
570
752
|
* renders, so `$count` agrees with the row set). Returns `{ count: N }` when
|
|
571
|
-
* executed.
|
|
753
|
+
* executed. The rows come straight from the table, unless `$having` reads a
|
|
754
|
+
* `first` / `last` value (or an expression over one): only then the window
|
|
755
|
+
* derived table is built.
|
|
572
756
|
*/
|
|
573
757
|
function buildAggregateCount(dialect, table, where, controls) {
|
|
574
758
|
const groupFields = controls.$groupBy;
|
|
575
759
|
const having = havingClause(dialect, controls);
|
|
576
760
|
const countCol = `COUNT(*) AS ${dialect.quoteIdentifier("count")}`;
|
|
577
|
-
if (!groupFields?.length && !having)
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
761
|
+
if (!groupFields?.length && !having) {
|
|
762
|
+
if ((controls.$select?.computedAliases.length ?? 0) > 0) return finalizeParams(dialect, {
|
|
763
|
+
sql: `SELECT 1 AS ${dialect.quoteIdentifier("count")}`,
|
|
764
|
+
params: []
|
|
765
|
+
});
|
|
766
|
+
return finalizeParams(dialect, {
|
|
767
|
+
sql: `SELECT ${countCol} FROM ${dialect.quoteTable(table)} WHERE ${where.sql}`,
|
|
768
|
+
params: where.params
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
const from = aggSource(dialect, table, where, controls, !!having && readsFirstLast(controls.$select, having.refs));
|
|
772
|
+
const groupBy = groupByClause(dialect, controls);
|
|
582
773
|
let inner = "COUNT(*)";
|
|
583
774
|
if (groupBy) inner = bucketSelectParts(dialect, controls).join(", ") || "1";
|
|
584
775
|
return finalizeParams(dialect, {
|
|
585
|
-
sql: `SELECT ${countCol} FROM (SELECT ${inner}
|
|
776
|
+
sql: `SELECT ${countCol} FROM (SELECT ${inner} ${from}${groupBy}${having?.sql ?? ""}) AS ${dialect.quoteIdentifier("_groups")}`,
|
|
586
777
|
params: [...where.params, ...having?.params ?? []]
|
|
587
778
|
});
|
|
588
779
|
}
|
|
@@ -648,30 +839,16 @@ function viewAggExpr(dialect, c, resolveFieldRef) {
|
|
|
648
839
|
function viewComputeExpr(dialect, c, byPath, resolveFieldRef, grouped = false, cache = /* @__PURE__ */ new Map()) {
|
|
649
840
|
const cached = cache.get(c.viewPath);
|
|
650
841
|
if (cached !== void 0) return cached;
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
const leaf = byPath.get(e.field);
|
|
660
|
-
if (!leaf) throw new Error(`View column "${c.viewColumn}": "${e.field}" is not a column of the view`);
|
|
661
|
-
if (leaf.expr !== void 0) return viewComputeExpr(dialect, leaf, byPath, resolveFieldRef, grouped, cache);
|
|
662
|
-
if (leaf.aggFn) return cast(viewAggExpr(dialect, leaf, resolveFieldRef));
|
|
663
|
-
const source = viewSourceExpr(dialect, leaf);
|
|
664
|
-
return cast(grouped && leaf.json ? `MIN(${source})` : source);
|
|
665
|
-
}
|
|
666
|
-
const args = e.args.map(render);
|
|
667
|
-
switch (e.op) {
|
|
668
|
-
case "neg": return `(-${args[0]})`;
|
|
669
|
-
case "coalesce": return `COALESCE(${args.join(", ")})`;
|
|
670
|
-
case "/": return `(${args[0]} / NULLIF(${args[1]}, 0))`;
|
|
671
|
-
default: return `(${args[0]} ${e.op} ${args[1]})`;
|
|
672
|
-
}
|
|
842
|
+
const fail = (reason) => /* @__PURE__ */ new Error(reason === "no-cast" ? `View column "${c.viewColumn}": computed view columns are not supported by this adapter` : `View column "${c.viewColumn}": non-finite literal in @db.compute`);
|
|
843
|
+
const leaf = (field) => {
|
|
844
|
+
const mapping = byPath.get(field);
|
|
845
|
+
if (!mapping) throw new Error(`View column "${c.viewColumn}": "${field}" is not a column of the view`);
|
|
846
|
+
if (mapping.expr !== void 0) return { double: viewComputeExpr(dialect, mapping, byPath, resolveFieldRef, grouped, cache) };
|
|
847
|
+
if (mapping.aggFn) return viewAggExpr(dialect, mapping, resolveFieldRef);
|
|
848
|
+
const source = viewSourceExpr(dialect, mapping);
|
|
849
|
+
return grouped && mapping.json ? `MIN(${source})` : source;
|
|
673
850
|
};
|
|
674
|
-
const sql =
|
|
851
|
+
const sql = renderArith(dialect, c.expr, leaf, fail);
|
|
675
852
|
cache.set(c.viewPath, sql);
|
|
676
853
|
return sql;
|
|
677
854
|
}
|
|
@@ -695,10 +872,7 @@ function firstRowOn(dialect, join, target, condition, resolveFieldRef) {
|
|
|
695
872
|
type: join.targetType,
|
|
696
873
|
field: first.key
|
|
697
874
|
});
|
|
698
|
-
return `${key} = (SELECT ${key} FROM ${target} WHERE ${condition} ORDER BY ${first.order.map(({ ref, desc }) =>
|
|
699
|
-
const nulls = dialect.nullsSortLargest ? desc ? " NULLS LAST" : " NULLS FIRST" : "";
|
|
700
|
-
return `${resolveFieldRef(ref)} ${desc ? "DESC" : "ASC"}${nulls}`;
|
|
701
|
-
}).join(", ")} LIMIT 1)`;
|
|
875
|
+
return `${key} = (SELECT ${key} FROM ${target} WHERE ${condition} ORDER BY ${first.order.map(({ ref, desc }) => orderKeySql(dialect, resolveFieldRef(ref), desc)).join(", ")} LIMIT 1)`;
|
|
702
876
|
}
|
|
703
877
|
/**
|
|
704
878
|
* Builds a CREATE VIEW statement from a view plan and column mappings.
|
|
@@ -779,6 +953,21 @@ function insertManyColumns(rows) {
|
|
|
779
953
|
return [...columns];
|
|
780
954
|
}
|
|
781
955
|
/**
|
|
956
|
+
* Splits `rows` into batches that stay under the driver's bind-parameter limit
|
|
957
|
+
* (PostgreSQL ~65535, MySQL packet size): `maxParams` (default 60000) divided
|
|
958
|
+
* by the column count. Returns the shared column union and the batches.
|
|
959
|
+
*/
|
|
960
|
+
function chunkInsertRows(rows, maxParams = 6e4) {
|
|
961
|
+
const columns = insertManyColumns(rows);
|
|
962
|
+
const size = columns.length > 0 ? Math.max(1, Math.floor(maxParams / columns.length)) : rows.length;
|
|
963
|
+
const batches = [];
|
|
964
|
+
for (let offset = 0; offset < rows.length; offset += size) batches.push(rows.slice(offset, offset + size));
|
|
965
|
+
return {
|
|
966
|
+
columns,
|
|
967
|
+
batches
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
782
971
|
* Builds a multi-row `INSERT … VALUES (…), (…)` statement over `columns`
|
|
783
972
|
* (default: {@link insertManyColumns} of `rows`). A row lacking a column gets
|
|
784
973
|
* `DEFAULT` — exactly what a single-row INSERT omitting it stores. Callers
|
|
@@ -1262,6 +1451,7 @@ exports.PARTITION_ROW_NUMBER_ALIAS = PARTITION_ROW_NUMBER_ALIAS;
|
|
|
1262
1451
|
exports.SEARCH_SOURCE_ALIAS = SEARCH_SOURCE_ALIAS;
|
|
1263
1452
|
exports.SQL_DEFAULT = SQL_DEFAULT;
|
|
1264
1453
|
exports.VECTOR_DISTANCE_ALIAS = VECTOR_DISTANCE_ALIAS;
|
|
1454
|
+
exports.arithOverflowError = arithOverflowError;
|
|
1265
1455
|
exports.buildAggregateCount = buildAggregateCount;
|
|
1266
1456
|
exports.buildAggregateSelect = buildAggregateSelect;
|
|
1267
1457
|
exports.buildCreateView = buildCreateView;
|
|
@@ -1277,6 +1467,7 @@ exports.buildUpdate = buildUpdate;
|
|
|
1277
1467
|
exports.buildVectorSearchCount = buildVectorSearchCount;
|
|
1278
1468
|
exports.buildVectorSearchSelect = buildVectorSearchSelect;
|
|
1279
1469
|
exports.buildWhere = buildWhere;
|
|
1470
|
+
exports.chunkInsertRows = chunkInsertRows;
|
|
1280
1471
|
exports.createFilterVisitor = createFilterVisitor;
|
|
1281
1472
|
exports.defaultValueForType = defaultValueForType;
|
|
1282
1473
|
exports.defaultValueToSqlLiteral = defaultValueToSqlLiteral;
|
|
@@ -1288,13 +1479,17 @@ exports.geoWindowFromControls = geoWindowFromControls;
|
|
|
1288
1479
|
exports.groupKeySql = groupKeySql;
|
|
1289
1480
|
exports.insertManyColumns = insertManyColumns;
|
|
1290
1481
|
exports.jsonDollarPath = jsonDollarPath;
|
|
1482
|
+
exports.mapQueryErrors = mapQueryErrors;
|
|
1291
1483
|
exports.normalizeGeoPointValue = normalizeGeoPointValue;
|
|
1484
|
+
exports.numericOutOfRangeError = numericOutOfRangeError;
|
|
1485
|
+
exports.orderKeySql = orderKeySql;
|
|
1292
1486
|
exports.parseRegexString = parseRegexString;
|
|
1293
1487
|
exports.queryNodeToSql = queryNodeToSql;
|
|
1294
1488
|
exports.queryOpToSql = queryOpToSql;
|
|
1295
1489
|
exports.quotedJsonPathSegments = quotedJsonPathSegments;
|
|
1296
1490
|
exports.refActionToSql = refActionToSql;
|
|
1297
1491
|
exports.renameGeoDistance = renameGeoDistance;
|
|
1492
|
+
exports.renderArith = renderArith;
|
|
1298
1493
|
exports.replaceColumnsFor = replaceColumnsFor;
|
|
1299
1494
|
exports.sqlStringLiteral = sqlStringLiteral;
|
|
1300
1495
|
exports.sqlTimeZoneLiteral = sqlTimeZoneLiteral;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AtscriptQueryFieldRef, AtscriptQueryNode, DbControls, TDbDefaultFn, TDbFieldMeta, TDbForeignKey, TDbReferentialAction, TFieldOps, TResolvedBucket, TViewColumnMapping, TViewJsonType, TViewPlan, UniquSelect } from "@atscript/db";
|
|
1
|
+
import { AtscriptExprNode, AtscriptQueryFieldRef, AtscriptQueryNode, DbControls, DbError, TDbDefaultFn, TDbFieldMeta, TDbForeignKey, TDbReferentialAction, TFieldOps, TResolvedBucket, TViewColumnMapping, TViewJsonType, TViewPlan, UniquSelect } from "@atscript/db";
|
|
2
2
|
import { FilterExpr, FilterVisitor } from "@uniqu/core";
|
|
3
3
|
import { TDbAggregateFn } from "@atscript/db/agg";
|
|
4
4
|
|
|
@@ -95,6 +95,38 @@ interface SqlDialect {
|
|
|
95
95
|
* @since 0.1.147
|
|
96
96
|
*/
|
|
97
97
|
castDouble?(expr: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* The aggregate functions that stand in for `MIN` / `MAX` over a BOOLEAN
|
|
100
|
+
* column on an engine that has no `MIN(boolean)` (PostgreSQL: `BOOL_AND` /
|
|
101
|
+
* `BOOL_OR`). Absent: `MIN` / `MAX` apply to booleans as to any column.
|
|
102
|
+
* @since 0.1.148
|
|
103
|
+
*/
|
|
104
|
+
booleanAggregates?: {
|
|
105
|
+
min: string;
|
|
106
|
+
max: string;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Renders the "any value of the group" pick over an already rendered column
|
|
110
|
+
* — what a `first` / `last` derived column (constant within its group)
|
|
111
|
+
* aggregates through — for the column's source `field` (`undefined` when
|
|
112
|
+
* unknown). The dialect picks the cheapest aggregate its engine has for the
|
|
113
|
+
* column's type: a streaming `MIN` where one exists, a type-agnostic form
|
|
114
|
+
* only where it does not (PostgreSQL: `MIN` for ordered types, `BOOL_AND`
|
|
115
|
+
* for a boolean, `(ARRAY_AGG(x))[1]` for uuid, bytea, point, json, …).
|
|
116
|
+
* Absent: `MIN(x)`.
|
|
117
|
+
* @since 0.1.148
|
|
118
|
+
*/
|
|
119
|
+
anyValue?(expr: string, field: TDbFieldMeta | undefined): string;
|
|
120
|
+
/**
|
|
121
|
+
* Maps a driver error of a grouped query to the `DbError` it means — a
|
|
122
|
+
* numeric overflow (`arithOverflowError` when the query has arithmetic
|
|
123
|
+
* expressions, else `numericOutOfRangeError`), an unknown calendar-bucket
|
|
124
|
+
* zone — or `undefined` to let it propagate unchanged. `arithmetic` is
|
|
125
|
+
* whether the query selects any arithmetic expression. Run by
|
|
126
|
+
* {@link mapQueryErrors}.
|
|
127
|
+
* @since 0.1.148
|
|
128
|
+
*/
|
|
129
|
+
mapQueryError?(error: unknown, arithmetic: boolean): Error | undefined;
|
|
98
130
|
/**
|
|
99
131
|
* `true` when the database sorts NULL as the LARGEST value (PostgreSQL):
|
|
100
132
|
* first-row join order keys then render `ASC NULLS FIRST` /
|
|
@@ -113,6 +145,21 @@ interface SqlDialect {
|
|
|
113
145
|
* (e.g. `$1, $2, ...` for PostgreSQL). No-op when `dialect.paramPlaceholder` is not set.
|
|
114
146
|
*/
|
|
115
147
|
declare function finalizeParams(dialect: SqlDialect, fragment: TSqlFragment): TSqlFragment;
|
|
148
|
+
/**
|
|
149
|
+
* Runs `fn`, rethrowing a driver error as the `DbError`
|
|
150
|
+
* {@link SqlDialect.mapQueryError} maps it to (any other error unchanged).
|
|
151
|
+
* `controls` of the query tell whether it has arithmetic expressions.
|
|
152
|
+
* @since 0.1.148
|
|
153
|
+
*/
|
|
154
|
+
declare function mapQueryErrors<R>(dialect: SqlDialect, fn: () => Promise<R>, controls?: DbControls): Promise<R>;
|
|
155
|
+
/**
|
|
156
|
+
* One `ORDER BY` key with the uniform "NULL is the smallest value" ordering:
|
|
157
|
+
* `<expr> ASC` / `<expr> DESC`, plus `NULLS FIRST` / `NULLS LAST` on a
|
|
158
|
+
* dialect where NULL sorts largest ({@link SqlDialect.nullsSortLargest}).
|
|
159
|
+
* Shared by first-row joins and `first` / `last` aggregates.
|
|
160
|
+
* @since 0.1.148
|
|
161
|
+
*/
|
|
162
|
+
declare function orderKeySql(dialect: SqlDialect, expr: string, desc: boolean): string;
|
|
116
163
|
/**
|
|
117
164
|
* Each JSON path segment wrapped in double quotes (`"a"`), for the dialects'
|
|
118
165
|
* {@link SqlDialect.jsonExtract} path literals (`'$."a"."b"'`, `'{"a","b"}'`).
|
|
@@ -128,6 +175,34 @@ declare function quotedJsonPathSegments(path: readonly string[]): string[];
|
|
|
128
175
|
declare const EMPTY_AND: TSqlFragment;
|
|
129
176
|
declare const EMPTY_OR: TSqlFragment;
|
|
130
177
|
//#endregion
|
|
178
|
+
//#region src/arith.d.ts
|
|
179
|
+
/** Why {@link renderArith} cannot render an expression. */
|
|
180
|
+
type TArithFailure = "no-cast" | "non-finite";
|
|
181
|
+
/**
|
|
182
|
+
* Renders a computed expression tree (`@db.compute` on a view, or a query-time
|
|
183
|
+
* arithmetic `$select` entry) as SQL, in IEEE double on every dialect: each
|
|
184
|
+
* literal is cast with {@link SqlDialect.castDouble}; `+ - *` render as
|
|
185
|
+
* `(l op r)`, `/` as `(l / NULLIF(r, 0))` (division by zero is NULL), unary
|
|
186
|
+
* minus as `(-x)` and `coalesce` as `COALESCE(…)`. `leaf` renders a field
|
|
187
|
+
* reference as raw SQL, which is cast to double here — or, as `{ double }`,
|
|
188
|
+
* SQL that already is a double (a nested computed column) and stays as is.
|
|
189
|
+
*
|
|
190
|
+
* The one renderer of both paths, so declared and query-time arithmetic
|
|
191
|
+
* cannot diverge.
|
|
192
|
+
*
|
|
193
|
+
* @param fail - the error to throw for a failure (default `DbError`:
|
|
194
|
+
* `AGG_EXPR_NOT_SUPPORTED` without `castDouble`, `INVALID_QUERY` for a
|
|
195
|
+
* non-finite literal).
|
|
196
|
+
* @since 0.1.148
|
|
197
|
+
*/
|
|
198
|
+
declare function renderArith(dialect: SqlDialect, node: AtscriptExprNode, leaf: (field: string) => string | {
|
|
199
|
+
double: string;
|
|
200
|
+
}, fail?: (reason: TArithFailure) => Error): string;
|
|
201
|
+
/** The error of a numeric overflow that is no arithmetic expression's (`INVALID_QUERY`, `path` `""`). */
|
|
202
|
+
declare function numericOutOfRangeError(): DbError;
|
|
203
|
+
/** The error of a double overflow in aggregate arithmetic (`INVALID_QUERY`, `path` `$select`). */
|
|
204
|
+
declare function arithOverflowError(): DbError;
|
|
205
|
+
//#endregion
|
|
131
206
|
//#region src/filter-builder.d.ts
|
|
132
207
|
interface TFilterVisitorOptions {
|
|
133
208
|
/**
|
|
@@ -326,6 +401,15 @@ declare function buildInsert(dialect: SqlDialect, table: string, data: Record<st
|
|
|
326
401
|
* some).
|
|
327
402
|
*/
|
|
328
403
|
declare function insertManyColumns(rows: readonly Record<string, unknown>[]): string[];
|
|
404
|
+
/**
|
|
405
|
+
* Splits `rows` into batches that stay under the driver's bind-parameter limit
|
|
406
|
+
* (PostgreSQL ~65535, MySQL packet size): `maxParams` (default 60000) divided
|
|
407
|
+
* by the column count. Returns the shared column union and the batches.
|
|
408
|
+
*/
|
|
409
|
+
declare function chunkInsertRows(rows: readonly Record<string, unknown>[], maxParams?: number): {
|
|
410
|
+
columns: string[];
|
|
411
|
+
batches: Record<string, unknown>[][];
|
|
412
|
+
};
|
|
329
413
|
/**
|
|
330
414
|
* Builds a multi-row `INSERT … VALUES (…), (…)` statement over `columns`
|
|
331
415
|
* (default: {@link insertManyColumns} of `rows`). A row lacking a column gets
|
|
@@ -495,7 +579,7 @@ declare function queryNodeToSql(node: AtscriptQueryNode, resolveFieldRef: (ref:
|
|
|
495
579
|
* SQL function name of each single-name aggregate. `countDistinct` is not a
|
|
496
580
|
* name but a form (`COUNT(DISTINCT x)`) — see {@link renderAggCall}.
|
|
497
581
|
*/
|
|
498
|
-
declare const AGG_FN_SQL: Readonly<Record<Exclude<TDbAggregateFn, "countDistinct">, string>>;
|
|
582
|
+
declare const AGG_FN_SQL: Readonly<Record<Exclude<TDbAggregateFn, "countDistinct" | "first" | "last">, string>>;
|
|
499
583
|
/**
|
|
500
584
|
* The SQL a `$groupBy` key renders as: a calendar-bucket alias renders the
|
|
501
585
|
* bucket EXPRESSION (`dialect.calendarBucket`), anything else the quoted
|
|
@@ -512,7 +596,9 @@ declare function groupKeySql(dialect: SqlDialect, controls: DbControls, key: str
|
|
|
512
596
|
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
513
597
|
*
|
|
514
598
|
* SELECT lists the plain grouped columns, then `<bucket expr> AS "alias"`
|
|
515
|
-
* per calendar bucket, then
|
|
599
|
+
* per calendar bucket, then every computed alias (aggregates, row-level
|
|
600
|
+
* expression aggregates, `first` / `last`, group-level expressions — the
|
|
601
|
+
* order of `UniquSelect.computedAliases`). Bucket expressions are
|
|
516
602
|
* parameter-free, so the bind parameters are exactly those of the same query
|
|
517
603
|
* without buckets (WHERE, HAVING, LIMIT, OFFSET).
|
|
518
604
|
*/
|
|
@@ -521,7 +607,9 @@ declare function buildAggregateSelect(dialect: SqlDialect, table: string, where:
|
|
|
521
607
|
* Builds a COUNT query for the number of distinct groups — the groups that
|
|
522
608
|
* survive `$having` when one is given (the same predicate the row query
|
|
523
609
|
* renders, so `$count` agrees with the row set). Returns `{ count: N }` when
|
|
524
|
-
* executed.
|
|
610
|
+
* executed. The rows come straight from the table, unless `$having` reads a
|
|
611
|
+
* `first` / `last` value (or an expression over one): only then the window
|
|
612
|
+
* derived table is built.
|
|
525
613
|
*/
|
|
526
614
|
declare function buildAggregateCount(dialect: SqlDialect, table: string, where: TSqlFragment, controls: DbControls): TSqlFragment;
|
|
527
615
|
//#endregion
|
|
@@ -538,4 +626,4 @@ declare function parseRegexString(value: unknown): {
|
|
|
538
626
|
flags: string;
|
|
539
627
|
};
|
|
540
628
|
//#endregion
|
|
541
|
-
export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TFilterVisitorOptions, type TGeoCircle, type TGeoSearchControls, type TGeoWindow, type TReplaceColumn, type TSqlFragment, VECTOR_DISTANCE_ALIAS, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource };
|
|
629
|
+
export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TArithFailure, type TFilterVisitorOptions, type TGeoCircle, type TGeoSearchControls, type TGeoWindow, type TReplaceColumn, type TSqlFragment, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, numericOutOfRangeError, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { AtscriptExprNode, AtscriptQueryFieldRef, AtscriptQueryNode, DbControls, DbError, TDbDefaultFn, TDbFieldMeta, TDbForeignKey, TDbReferentialAction, TFieldOps, TResolvedBucket, TViewColumnMapping, TViewJsonType, TViewPlan, UniquSelect } from "@atscript/db";
|
|
1
2
|
import { FilterExpr, FilterVisitor } from "@uniqu/core";
|
|
2
|
-
import { AtscriptQueryFieldRef, AtscriptQueryNode, DbControls, TDbDefaultFn, TDbFieldMeta, TDbForeignKey, TDbReferentialAction, TFieldOps, TResolvedBucket, TViewColumnMapping, TViewJsonType, TViewPlan, UniquSelect } from "@atscript/db";
|
|
3
3
|
import { TDbAggregateFn } from "@atscript/db/agg";
|
|
4
4
|
|
|
5
5
|
//#region src/dialect.d.ts
|
|
@@ -95,6 +95,38 @@ interface SqlDialect {
|
|
|
95
95
|
* @since 0.1.147
|
|
96
96
|
*/
|
|
97
97
|
castDouble?(expr: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* The aggregate functions that stand in for `MIN` / `MAX` over a BOOLEAN
|
|
100
|
+
* column on an engine that has no `MIN(boolean)` (PostgreSQL: `BOOL_AND` /
|
|
101
|
+
* `BOOL_OR`). Absent: `MIN` / `MAX` apply to booleans as to any column.
|
|
102
|
+
* @since 0.1.148
|
|
103
|
+
*/
|
|
104
|
+
booleanAggregates?: {
|
|
105
|
+
min: string;
|
|
106
|
+
max: string;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Renders the "any value of the group" pick over an already rendered column
|
|
110
|
+
* — what a `first` / `last` derived column (constant within its group)
|
|
111
|
+
* aggregates through — for the column's source `field` (`undefined` when
|
|
112
|
+
* unknown). The dialect picks the cheapest aggregate its engine has for the
|
|
113
|
+
* column's type: a streaming `MIN` where one exists, a type-agnostic form
|
|
114
|
+
* only where it does not (PostgreSQL: `MIN` for ordered types, `BOOL_AND`
|
|
115
|
+
* for a boolean, `(ARRAY_AGG(x))[1]` for uuid, bytea, point, json, …).
|
|
116
|
+
* Absent: `MIN(x)`.
|
|
117
|
+
* @since 0.1.148
|
|
118
|
+
*/
|
|
119
|
+
anyValue?(expr: string, field: TDbFieldMeta | undefined): string;
|
|
120
|
+
/**
|
|
121
|
+
* Maps a driver error of a grouped query to the `DbError` it means — a
|
|
122
|
+
* numeric overflow (`arithOverflowError` when the query has arithmetic
|
|
123
|
+
* expressions, else `numericOutOfRangeError`), an unknown calendar-bucket
|
|
124
|
+
* zone — or `undefined` to let it propagate unchanged. `arithmetic` is
|
|
125
|
+
* whether the query selects any arithmetic expression. Run by
|
|
126
|
+
* {@link mapQueryErrors}.
|
|
127
|
+
* @since 0.1.148
|
|
128
|
+
*/
|
|
129
|
+
mapQueryError?(error: unknown, arithmetic: boolean): Error | undefined;
|
|
98
130
|
/**
|
|
99
131
|
* `true` when the database sorts NULL as the LARGEST value (PostgreSQL):
|
|
100
132
|
* first-row join order keys then render `ASC NULLS FIRST` /
|
|
@@ -113,6 +145,21 @@ interface SqlDialect {
|
|
|
113
145
|
* (e.g. `$1, $2, ...` for PostgreSQL). No-op when `dialect.paramPlaceholder` is not set.
|
|
114
146
|
*/
|
|
115
147
|
declare function finalizeParams(dialect: SqlDialect, fragment: TSqlFragment): TSqlFragment;
|
|
148
|
+
/**
|
|
149
|
+
* Runs `fn`, rethrowing a driver error as the `DbError`
|
|
150
|
+
* {@link SqlDialect.mapQueryError} maps it to (any other error unchanged).
|
|
151
|
+
* `controls` of the query tell whether it has arithmetic expressions.
|
|
152
|
+
* @since 0.1.148
|
|
153
|
+
*/
|
|
154
|
+
declare function mapQueryErrors<R>(dialect: SqlDialect, fn: () => Promise<R>, controls?: DbControls): Promise<R>;
|
|
155
|
+
/**
|
|
156
|
+
* One `ORDER BY` key with the uniform "NULL is the smallest value" ordering:
|
|
157
|
+
* `<expr> ASC` / `<expr> DESC`, plus `NULLS FIRST` / `NULLS LAST` on a
|
|
158
|
+
* dialect where NULL sorts largest ({@link SqlDialect.nullsSortLargest}).
|
|
159
|
+
* Shared by first-row joins and `first` / `last` aggregates.
|
|
160
|
+
* @since 0.1.148
|
|
161
|
+
*/
|
|
162
|
+
declare function orderKeySql(dialect: SqlDialect, expr: string, desc: boolean): string;
|
|
116
163
|
/**
|
|
117
164
|
* Each JSON path segment wrapped in double quotes (`"a"`), for the dialects'
|
|
118
165
|
* {@link SqlDialect.jsonExtract} path literals (`'$."a"."b"'`, `'{"a","b"}'`).
|
|
@@ -128,6 +175,34 @@ declare function quotedJsonPathSegments(path: readonly string[]): string[];
|
|
|
128
175
|
declare const EMPTY_AND: TSqlFragment;
|
|
129
176
|
declare const EMPTY_OR: TSqlFragment;
|
|
130
177
|
//#endregion
|
|
178
|
+
//#region src/arith.d.ts
|
|
179
|
+
/** Why {@link renderArith} cannot render an expression. */
|
|
180
|
+
type TArithFailure = "no-cast" | "non-finite";
|
|
181
|
+
/**
|
|
182
|
+
* Renders a computed expression tree (`@db.compute` on a view, or a query-time
|
|
183
|
+
* arithmetic `$select` entry) as SQL, in IEEE double on every dialect: each
|
|
184
|
+
* literal is cast with {@link SqlDialect.castDouble}; `+ - *` render as
|
|
185
|
+
* `(l op r)`, `/` as `(l / NULLIF(r, 0))` (division by zero is NULL), unary
|
|
186
|
+
* minus as `(-x)` and `coalesce` as `COALESCE(…)`. `leaf` renders a field
|
|
187
|
+
* reference as raw SQL, which is cast to double here — or, as `{ double }`,
|
|
188
|
+
* SQL that already is a double (a nested computed column) and stays as is.
|
|
189
|
+
*
|
|
190
|
+
* The one renderer of both paths, so declared and query-time arithmetic
|
|
191
|
+
* cannot diverge.
|
|
192
|
+
*
|
|
193
|
+
* @param fail - the error to throw for a failure (default `DbError`:
|
|
194
|
+
* `AGG_EXPR_NOT_SUPPORTED` without `castDouble`, `INVALID_QUERY` for a
|
|
195
|
+
* non-finite literal).
|
|
196
|
+
* @since 0.1.148
|
|
197
|
+
*/
|
|
198
|
+
declare function renderArith(dialect: SqlDialect, node: AtscriptExprNode, leaf: (field: string) => string | {
|
|
199
|
+
double: string;
|
|
200
|
+
}, fail?: (reason: TArithFailure) => Error): string;
|
|
201
|
+
/** The error of a numeric overflow that is no arithmetic expression's (`INVALID_QUERY`, `path` `""`). */
|
|
202
|
+
declare function numericOutOfRangeError(): DbError;
|
|
203
|
+
/** The error of a double overflow in aggregate arithmetic (`INVALID_QUERY`, `path` `$select`). */
|
|
204
|
+
declare function arithOverflowError(): DbError;
|
|
205
|
+
//#endregion
|
|
131
206
|
//#region src/filter-builder.d.ts
|
|
132
207
|
interface TFilterVisitorOptions {
|
|
133
208
|
/**
|
|
@@ -326,6 +401,15 @@ declare function buildInsert(dialect: SqlDialect, table: string, data: Record<st
|
|
|
326
401
|
* some).
|
|
327
402
|
*/
|
|
328
403
|
declare function insertManyColumns(rows: readonly Record<string, unknown>[]): string[];
|
|
404
|
+
/**
|
|
405
|
+
* Splits `rows` into batches that stay under the driver's bind-parameter limit
|
|
406
|
+
* (PostgreSQL ~65535, MySQL packet size): `maxParams` (default 60000) divided
|
|
407
|
+
* by the column count. Returns the shared column union and the batches.
|
|
408
|
+
*/
|
|
409
|
+
declare function chunkInsertRows(rows: readonly Record<string, unknown>[], maxParams?: number): {
|
|
410
|
+
columns: string[];
|
|
411
|
+
batches: Record<string, unknown>[][];
|
|
412
|
+
};
|
|
329
413
|
/**
|
|
330
414
|
* Builds a multi-row `INSERT … VALUES (…), (…)` statement over `columns`
|
|
331
415
|
* (default: {@link insertManyColumns} of `rows`). A row lacking a column gets
|
|
@@ -495,7 +579,7 @@ declare function queryNodeToSql(node: AtscriptQueryNode, resolveFieldRef: (ref:
|
|
|
495
579
|
* SQL function name of each single-name aggregate. `countDistinct` is not a
|
|
496
580
|
* name but a form (`COUNT(DISTINCT x)`) — see {@link renderAggCall}.
|
|
497
581
|
*/
|
|
498
|
-
declare const AGG_FN_SQL: Readonly<Record<Exclude<TDbAggregateFn, "countDistinct">, string>>;
|
|
582
|
+
declare const AGG_FN_SQL: Readonly<Record<Exclude<TDbAggregateFn, "countDistinct" | "first" | "last">, string>>;
|
|
499
583
|
/**
|
|
500
584
|
* The SQL a `$groupBy` key renders as: a calendar-bucket alias renders the
|
|
501
585
|
* bucket EXPRESSION (`dialect.calendarBucket`), anything else the quoted
|
|
@@ -512,7 +596,9 @@ declare function groupKeySql(dialect: SqlDialect, controls: DbControls, key: str
|
|
|
512
596
|
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
513
597
|
*
|
|
514
598
|
* SELECT lists the plain grouped columns, then `<bucket expr> AS "alias"`
|
|
515
|
-
* per calendar bucket, then
|
|
599
|
+
* per calendar bucket, then every computed alias (aggregates, row-level
|
|
600
|
+
* expression aggregates, `first` / `last`, group-level expressions — the
|
|
601
|
+
* order of `UniquSelect.computedAliases`). Bucket expressions are
|
|
516
602
|
* parameter-free, so the bind parameters are exactly those of the same query
|
|
517
603
|
* without buckets (WHERE, HAVING, LIMIT, OFFSET).
|
|
518
604
|
*/
|
|
@@ -521,7 +607,9 @@ declare function buildAggregateSelect(dialect: SqlDialect, table: string, where:
|
|
|
521
607
|
* Builds a COUNT query for the number of distinct groups — the groups that
|
|
522
608
|
* survive `$having` when one is given (the same predicate the row query
|
|
523
609
|
* renders, so `$count` agrees with the row set). Returns `{ count: N }` when
|
|
524
|
-
* executed.
|
|
610
|
+
* executed. The rows come straight from the table, unless `$having` reads a
|
|
611
|
+
* `first` / `last` value (or an expression over one): only then the window
|
|
612
|
+
* derived table is built.
|
|
525
613
|
*/
|
|
526
614
|
declare function buildAggregateCount(dialect: SqlDialect, table: string, where: TSqlFragment, controls: DbControls): TSqlFragment;
|
|
527
615
|
//#endregion
|
|
@@ -538,4 +626,4 @@ declare function parseRegexString(value: unknown): {
|
|
|
538
626
|
flags: string;
|
|
539
627
|
};
|
|
540
628
|
//#endregion
|
|
541
|
-
export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TFilterVisitorOptions, type TGeoCircle, type TGeoSearchControls, type TGeoWindow, type TReplaceColumn, type TSqlFragment, VECTOR_DISTANCE_ALIAS, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource };
|
|
629
|
+
export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, type SqlDialect, type TArithFailure, type TFilterVisitorOptions, type TGeoCircle, type TGeoSearchControls, type TGeoWindow, type TReplaceColumn, type TSqlFragment, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, numericOutOfRangeError, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { DbError, aggregateExpressionsNotSupported, containsRelationFilter, fkColumns, isFieldRef, isResolvedRelationFilter } from "@atscript/db";
|
|
1
2
|
import { BUCKET_UNITS, TIME_ZONE_NAME_RE, WEEK_STARTS, walkFilter } from "@uniqu/core";
|
|
2
|
-
import { DbError, containsRelationFilter, fkColumns, isFieldRef, isResolvedRelationFilter } from "@atscript/db";
|
|
3
3
|
import { assertAggregateFn, resolveAlias } from "@atscript/db/agg";
|
|
4
4
|
//#region src/dialect.ts
|
|
5
5
|
/**
|
|
@@ -23,6 +23,32 @@ function havingGroupRef(dialect, expr, alias) {
|
|
|
23
23
|
return dialect.bucketAliasInHaving ? dialect.quoteIdentifier(alias) : expr;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
+
* Runs `fn`, rethrowing a driver error as the `DbError`
|
|
27
|
+
* {@link SqlDialect.mapQueryError} maps it to (any other error unchanged).
|
|
28
|
+
* `controls` of the query tell whether it has arithmetic expressions.
|
|
29
|
+
* @since 0.1.148
|
|
30
|
+
*/
|
|
31
|
+
async function mapQueryErrors(dialect, fn, controls) {
|
|
32
|
+
try {
|
|
33
|
+
return await fn();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
const select = controls?.$select;
|
|
36
|
+
const arithmetic = !!(select?.exprAggregates?.length || select?.exprs?.length);
|
|
37
|
+
throw dialect.mapQueryError?.(error, arithmetic) ?? error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* One `ORDER BY` key with the uniform "NULL is the smallest value" ordering:
|
|
42
|
+
* `<expr> ASC` / `<expr> DESC`, plus `NULLS FIRST` / `NULLS LAST` on a
|
|
43
|
+
* dialect where NULL sorts largest ({@link SqlDialect.nullsSortLargest}).
|
|
44
|
+
* Shared by first-row joins and `first` / `last` aggregates.
|
|
45
|
+
* @since 0.1.148
|
|
46
|
+
*/
|
|
47
|
+
function orderKeySql(dialect, expr, desc) {
|
|
48
|
+
const nulls = dialect.nullsSortLargest ? desc ? " NULLS LAST" : " NULLS FIRST" : "";
|
|
49
|
+
return `${expr} ${desc ? "DESC" : "ASC"}${nulls}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
26
52
|
* Each JSON path segment wrapped in double quotes (`"a"`), for the dialects'
|
|
27
53
|
* {@link SqlDialect.jsonExtract} path literals (`'$."a"."b"'`, `'{"a","b"}'`).
|
|
28
54
|
*
|
|
@@ -54,6 +80,67 @@ const EMPTY_OR = {
|
|
|
54
80
|
params: []
|
|
55
81
|
};
|
|
56
82
|
//#endregion
|
|
83
|
+
//#region src/arith.ts
|
|
84
|
+
/**
|
|
85
|
+
* Renders a computed expression tree (`@db.compute` on a view, or a query-time
|
|
86
|
+
* arithmetic `$select` entry) as SQL, in IEEE double on every dialect: each
|
|
87
|
+
* literal is cast with {@link SqlDialect.castDouble}; `+ - *` render as
|
|
88
|
+
* `(l op r)`, `/` as `(l / NULLIF(r, 0))` (division by zero is NULL), unary
|
|
89
|
+
* minus as `(-x)` and `coalesce` as `COALESCE(…)`. `leaf` renders a field
|
|
90
|
+
* reference as raw SQL, which is cast to double here — or, as `{ double }`,
|
|
91
|
+
* SQL that already is a double (a nested computed column) and stays as is.
|
|
92
|
+
*
|
|
93
|
+
* The one renderer of both paths, so declared and query-time arithmetic
|
|
94
|
+
* cannot diverge.
|
|
95
|
+
*
|
|
96
|
+
* @param fail - the error to throw for a failure (default `DbError`:
|
|
97
|
+
* `AGG_EXPR_NOT_SUPPORTED` without `castDouble`, `INVALID_QUERY` for a
|
|
98
|
+
* non-finite literal).
|
|
99
|
+
* @since 0.1.148
|
|
100
|
+
*/
|
|
101
|
+
function renderArith(dialect, node, leaf, fail = defaultFailure) {
|
|
102
|
+
const cast = dialect.castDouble?.bind(dialect);
|
|
103
|
+
if (!cast) throw fail("no-cast");
|
|
104
|
+
const render = (e) => {
|
|
105
|
+
if (typeof e === "number") {
|
|
106
|
+
if (!Number.isFinite(e)) throw fail("non-finite");
|
|
107
|
+
return cast(String(e));
|
|
108
|
+
}
|
|
109
|
+
if ("field" in e) {
|
|
110
|
+
const sql = leaf(e.field);
|
|
111
|
+
return typeof sql === "string" ? cast(sql) : sql.double;
|
|
112
|
+
}
|
|
113
|
+
const args = e.args.map(render);
|
|
114
|
+
switch (e.op) {
|
|
115
|
+
case "neg": return `(-${args[0]})`;
|
|
116
|
+
case "coalesce": return `COALESCE(${args.join(", ")})`;
|
|
117
|
+
case "/": return `(${args[0]} / NULLIF(${args[1]}, 0))`;
|
|
118
|
+
default: return `(${args[0]} ${e.op} ${args[1]})`;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
return render(node);
|
|
122
|
+
}
|
|
123
|
+
function defaultFailure(reason) {
|
|
124
|
+
return reason === "no-cast" ? aggregateExpressionsNotSupported() : new DbError("INVALID_QUERY", [{
|
|
125
|
+
path: "$select",
|
|
126
|
+
message: "Expression literal is not finite"
|
|
127
|
+
}]);
|
|
128
|
+
}
|
|
129
|
+
/** The error of a numeric overflow that is no arithmetic expression's (`INVALID_QUERY`, `path` `""`). */
|
|
130
|
+
function numericOutOfRangeError() {
|
|
131
|
+
return new DbError("INVALID_QUERY", [{
|
|
132
|
+
path: "",
|
|
133
|
+
message: "Numeric value out of range"
|
|
134
|
+
}]);
|
|
135
|
+
}
|
|
136
|
+
/** The error of a double overflow in aggregate arithmetic (`INVALID_QUERY`, `path` `$select`). */
|
|
137
|
+
function arithOverflowError() {
|
|
138
|
+
return new DbError("INVALID_QUERY", [{
|
|
139
|
+
path: "$select",
|
|
140
|
+
message: "Arithmetic overflow"
|
|
141
|
+
}]);
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
57
144
|
//#region src/filter-builder.ts
|
|
58
145
|
/**
|
|
59
146
|
* Creates a dialect-specific filter visitor for `walkFilter`.
|
|
@@ -427,15 +514,23 @@ const AGG_FN_SQL = {
|
|
|
427
514
|
*/
|
|
428
515
|
function renderAggCall(fn, arg, path) {
|
|
429
516
|
assertAggregateFn(fn, path);
|
|
430
|
-
|
|
517
|
+
if (fn === "countDistinct") return `COUNT(DISTINCT ${arg})`;
|
|
518
|
+
return `${AGG_FN_SQL[fn]}(${arg})`;
|
|
431
519
|
}
|
|
432
|
-
/**
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
520
|
+
/**
|
|
521
|
+
* `MIN` / `MAX` of a column the engine may not aggregate directly: a boolean
|
|
522
|
+
* on a dialect with {@link SqlDialect.booleanAggregates} (PostgreSQL has no
|
|
523
|
+
* `MIN(boolean)`) renders its stand-in.
|
|
524
|
+
*/
|
|
525
|
+
function pickSql(dialect, select, fn, column, sql) {
|
|
526
|
+
const bool = select?.sources.get(column)?.designType === "boolean" ? dialect.booleanAggregates?.[fn] : void 0;
|
|
527
|
+
return bool ? `${bool}(${sql})` : renderAggCall(fn, sql);
|
|
436
528
|
}
|
|
437
|
-
|
|
438
|
-
|
|
529
|
+
/** The bare aggregate call, e.g. `SUM("amount")` / `COUNT(*)` / `COUNT(DISTINCT "region")`. */
|
|
530
|
+
function aggFnSql(dialect, select, expr) {
|
|
531
|
+
if (expr.$field === "*") return renderAggCall(expr.$fn, "*");
|
|
532
|
+
const field = dialect.quoteIdentifier(expr.$field);
|
|
533
|
+
return expr.$fn === "min" || expr.$fn === "max" ? pickSql(dialect, select, expr.$fn, expr.$field, field) : renderAggCall(expr.$fn, field);
|
|
439
534
|
}
|
|
440
535
|
const BUCKET_UNIT_SET = new Set(BUCKET_UNITS);
|
|
441
536
|
const WEEK_START_SET = new Set(WEEK_STARTS);
|
|
@@ -483,25 +578,52 @@ function groupKeySql(dialect, controls, key) {
|
|
|
483
578
|
const bucket = controls.$select?.bucketByAlias(key);
|
|
484
579
|
return bucket ? bucketSql(dialect, bucket) : dialect.quoteIdentifier(key);
|
|
485
580
|
}
|
|
581
|
+
/** Alias of the derived table `first` / `last` aggregates read from. */
|
|
582
|
+
const ROWS_ALIAS = "__as_rows";
|
|
583
|
+
/** The derived-table column of the `i`-th `first` / `last` entry. */
|
|
584
|
+
const firstLastColumn = (i) => `__as_fl${i}`;
|
|
585
|
+
/**
|
|
586
|
+
* The SQL each computed alias stands for where an alias is not usable
|
|
587
|
+
* (HAVING; other expressions): an aggregate's call, a `first` / `last`
|
|
588
|
+
* derived column (aggregated: constant within its group), a row-level
|
|
589
|
+
* expression aggregate's call, and every group-level expression rendered over
|
|
590
|
+
* those (in dependency order, operands cast to double). A grouped column
|
|
591
|
+
* renders as its quoted name.
|
|
592
|
+
*/
|
|
593
|
+
function aliasSqlMap(dialect, controls) {
|
|
594
|
+
const map = /* @__PURE__ */ new Map();
|
|
595
|
+
const select = controls.$select;
|
|
596
|
+
const quote = (name) => dialect.quoteIdentifier(name);
|
|
597
|
+
for (const expr of select?.aggregates ?? []) map.set(resolveAlias(expr), aggFnSql(dialect, select, expr));
|
|
598
|
+
for (const e of select?.exprAggregates ?? []) map.set(e.alias, renderAggCall(e.fn, renderArith(dialect, e.expr, quote)));
|
|
599
|
+
(select?.firstLast ?? []).forEach((fl, i) => {
|
|
600
|
+
const col = quote(firstLastColumn(i));
|
|
601
|
+
map.set(fl.alias, dialect.anyValue ? dialect.anyValue(col, select?.sources.get(fl.column)) : renderAggCall("min", col));
|
|
602
|
+
});
|
|
603
|
+
for (const e of select?.exprs ?? []) map.set(e.alias, renderArith(dialect, e.expr, (name) => map.get(name) ?? quote(name)));
|
|
604
|
+
return map;
|
|
605
|
+
}
|
|
486
606
|
/**
|
|
487
607
|
* ` HAVING <predicate>` (leading space) + params for `controls.$having`, or
|
|
488
608
|
* `undefined` when there is nothing to render. Shared by the row and the
|
|
489
609
|
* count builders so both filter the same group set.
|
|
490
610
|
*
|
|
491
|
-
* A key that names
|
|
492
|
-
*
|
|
493
|
-
* not allow a SELECT alias in HAVING (MySQL and SQLite
|
|
494
|
-
* expression form keeps all three identical). A
|
|
495
|
-
* its bucket expression ({@link groupKeySql}),
|
|
496
|
-
* dialect sets `SqlDialect.bucketAliasInHaving`
|
|
497
|
-
* (grouped columns) render as plain columns.
|
|
611
|
+
* A key that names a computed alias (`$as`, else `fn_field`) renders what the
|
|
612
|
+
* alias stands for — `SUM("amount") > ?`, an expression's arithmetic — because
|
|
613
|
+
* PostgreSQL does not allow a SELECT alias in HAVING (MySQL and SQLite
|
|
614
|
+
* tolerate it, so the expression form keeps all three identical). A
|
|
615
|
+
* calendar-bucket alias renders its bucket expression ({@link groupKeySql}),
|
|
616
|
+
* or its quoted alias when the dialect sets `SqlDialect.bucketAliasInHaving`
|
|
617
|
+
* (`havingGroupRef`). Other keys (grouped columns) render as plain columns.
|
|
618
|
+
* `aliasSql` is the query's {@link aliasSqlMap} when the caller has built it.
|
|
498
619
|
*/
|
|
499
|
-
function havingClause(dialect, controls) {
|
|
620
|
+
function havingClause(dialect, controls, aliasSql) {
|
|
500
621
|
const having = controls.$having;
|
|
501
622
|
if (!having) return void 0;
|
|
502
|
-
const exprByAlias =
|
|
503
|
-
|
|
623
|
+
const exprByAlias = aliasSql ?? aliasSqlMap(dialect, controls);
|
|
624
|
+
const refs = /* @__PURE__ */ new Set();
|
|
504
625
|
const fragment = walkFilter(having, createFilterVisitor(dialect, { columnRef: (field) => {
|
|
626
|
+
refs.add(field);
|
|
505
627
|
const aggExpr = exprByAlias.get(field);
|
|
506
628
|
if (aggExpr) return aggExpr;
|
|
507
629
|
const bucket = controls.$select?.bucketByAlias(field);
|
|
@@ -510,43 +632,103 @@ function havingClause(dialect, controls) {
|
|
|
510
632
|
if (!fragment || fragment.sql === EMPTY_AND.sql) return void 0;
|
|
511
633
|
return {
|
|
512
634
|
sql: ` HAVING ${fragment.sql}`,
|
|
513
|
-
params: fragment.params
|
|
635
|
+
params: fragment.params,
|
|
636
|
+
refs
|
|
514
637
|
};
|
|
515
638
|
}
|
|
516
639
|
/** `<bucket expr> AS "alias"` for every calendar bucket in `$select`. */
|
|
517
640
|
function bucketSelectParts(dialect, controls) {
|
|
518
641
|
return (controls.$select?.buckets ?? []).map((bucket) => `${bucketSql(dialect, bucket)} AS ${dialect.quoteIdentifier(bucket.alias)}`);
|
|
519
642
|
}
|
|
643
|
+
/** ` GROUP BY <keys>` (leading space), or `""` for the whole table as one group. */
|
|
644
|
+
function groupByClause(dialect, controls) {
|
|
645
|
+
const keys = controls.$groupBy ?? [];
|
|
646
|
+
return keys.length ? ` GROUP BY ${keys.map((key) => groupKeySql(dialect, controls, key)).join(", ")}` : "";
|
|
647
|
+
}
|
|
648
|
+
/** Whether any of the HAVING keys is a `first` / `last` alias, or an expression over one. */
|
|
649
|
+
function readsFirstLast(select, refs) {
|
|
650
|
+
if (!select?.firstLast) return false;
|
|
651
|
+
const firstLast = new Set(select.firstLast.map((fl) => fl.alias));
|
|
652
|
+
const names = new Map((select.exprs ?? []).map((e) => [e.alias, e.names]));
|
|
653
|
+
const visit = (name, seen) => {
|
|
654
|
+
if (firstLast.has(name)) return true;
|
|
655
|
+
const deps = names.get(name);
|
|
656
|
+
if (!deps || seen.has(name)) return false;
|
|
657
|
+
seen.add(name);
|
|
658
|
+
return deps.some((dep) => visit(dep, seen));
|
|
659
|
+
};
|
|
660
|
+
return [...refs].some((key) => visit(key, /* @__PURE__ */ new Set()));
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* The columns the outer query reads from the derived table of a `first` /
|
|
664
|
+
* `last` aggregate: group keys (a bucket's source for its alias), plain
|
|
665
|
+
* `$select` fields, aggregate fields and the leaves of row-level expressions.
|
|
666
|
+
* The `$rowOrder` columns and the `first` / `last` sources stay inside the
|
|
667
|
+
* window; a group-level expression, `$having` and `$sort` read aliases or
|
|
668
|
+
* group keys only.
|
|
669
|
+
*/
|
|
670
|
+
function rowColumns(controls) {
|
|
671
|
+
const select = controls.$select;
|
|
672
|
+
const columns = /* @__PURE__ */ new Set();
|
|
673
|
+
for (const key of controls.$groupBy ?? []) if (!select?.bucketByAlias(key)) columns.add(key);
|
|
674
|
+
for (const bucket of select?.buckets ?? []) columns.add(bucket.field);
|
|
675
|
+
for (const field of select?.asArray ?? []) columns.add(field);
|
|
676
|
+
for (const expr of select?.aggregates ?? []) if (expr.$field !== "*") columns.add(expr.$field);
|
|
677
|
+
for (const e of select?.exprAggregates ?? []) for (const name of e.names) columns.add(name);
|
|
678
|
+
return [...columns];
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* The row source of an aggregate: `FROM <table> WHERE <where>`; with `first` /
|
|
682
|
+
* `last` (and `withRows`) a derived table instead — the table's rows that
|
|
683
|
+
* pass the WHERE, only the columns {@link rowColumns} lists, plus one
|
|
684
|
+
* `FIRST_VALUE(col) OVER (PARTITION BY <group keys> ORDER BY <rowOrder>)`
|
|
685
|
+
* column per entry (`last` over the reversed order). Each group then reads its
|
|
686
|
+
* representative row's value as an aggregate (constant within the group). The
|
|
687
|
+
* WHERE moves inside unchanged, so the bind parameters keep their order.
|
|
688
|
+
*/
|
|
689
|
+
function aggSource(dialect, table, where, controls, withRows) {
|
|
690
|
+
const quotedTable = dialect.quoteTable(table);
|
|
691
|
+
const firstLast = controls.$select?.firstLast;
|
|
692
|
+
const rowOrder = controls.$select?.rowOrder;
|
|
693
|
+
if (!withRows || !firstLast?.length || !rowOrder?.length) return `FROM ${quotedTable} WHERE ${where.sql}`;
|
|
694
|
+
const groupBy = controls.$groupBy;
|
|
695
|
+
const partition = groupBy?.length ? `PARTITION BY ${groupBy.map((key) => groupKeySql(dialect, controls, key)).join(", ")} ` : "";
|
|
696
|
+
const order = (reverse) => rowOrder.map((k) => orderKeySql(dialect, dialect.quoteIdentifier(k.column), k.desc !== reverse)).join(", ");
|
|
697
|
+
return `FROM (SELECT ${[...rowColumns(controls).map((column) => dialect.quoteIdentifier(column)), ...firstLast.map((fl, i) => {
|
|
698
|
+
const col = dialect.quoteIdentifier(firstLastColumn(i));
|
|
699
|
+
return `FIRST_VALUE(${dialect.quoteIdentifier(fl.column)}) OVER (${partition}ORDER BY ${order(fl.fn === "last")}) AS ${col}`;
|
|
700
|
+
})].join(", ")} FROM ${quotedTable} WHERE ${where.sql}) AS ${dialect.quoteIdentifier(ROWS_ALIAS)}`;
|
|
701
|
+
}
|
|
520
702
|
/**
|
|
521
703
|
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
522
704
|
*
|
|
523
705
|
* SELECT lists the plain grouped columns, then `<bucket expr> AS "alias"`
|
|
524
|
-
* per calendar bucket, then
|
|
706
|
+
* per calendar bucket, then every computed alias (aggregates, row-level
|
|
707
|
+
* expression aggregates, `first` / `last`, group-level expressions — the
|
|
708
|
+
* order of `UniquSelect.computedAliases`). Bucket expressions are
|
|
525
709
|
* parameter-free, so the bind parameters are exactly those of the same query
|
|
526
710
|
* without buckets (WHERE, HAVING, LIMIT, OFFSET).
|
|
527
711
|
*/
|
|
528
712
|
function buildAggregateSelect(dialect, table, where, controls) {
|
|
713
|
+
const select = controls.$select;
|
|
529
714
|
const selectParts = [];
|
|
530
|
-
const
|
|
531
|
-
if (plainFields) for (const f of plainFields) selectParts.push(dialect.quoteIdentifier(f));
|
|
715
|
+
for (const f of select?.asArray ?? []) selectParts.push(dialect.quoteIdentifier(f));
|
|
532
716
|
selectParts.push(...bucketSelectParts(dialect, controls));
|
|
533
|
-
|
|
534
|
-
if (
|
|
535
|
-
|
|
717
|
+
let aliasSql;
|
|
718
|
+
if (select?.exprAggregates || select?.firstLast || select?.exprs) {
|
|
719
|
+
aliasSql = aliasSqlMap(dialect, controls);
|
|
720
|
+
for (const alias of select.computedAliases) selectParts.push(`${aliasSql.get(alias)} AS ${dialect.quoteIdentifier(alias)}`);
|
|
721
|
+
} else for (const expr of select?.aggregates ?? []) selectParts.push(`${aggFnSql(dialect, select, expr)} AS ${dialect.quoteIdentifier(resolveAlias(expr))}`);
|
|
722
|
+
let sql = `SELECT ${selectParts.length > 0 ? selectParts.join(", ") : "*"} ${aggSource(dialect, table, where, controls, true)}${groupByClause(dialect, controls)}`;
|
|
536
723
|
const params = [...where.params];
|
|
537
|
-
const
|
|
538
|
-
if (groupBy?.length) {
|
|
539
|
-
const groupCols = groupBy.map((key) => groupKeySql(dialect, controls, key)).join(", ");
|
|
540
|
-
sql += ` GROUP BY ${groupCols}`;
|
|
541
|
-
}
|
|
542
|
-
const having = havingClause(dialect, controls);
|
|
724
|
+
const having = havingClause(dialect, controls, aliasSql);
|
|
543
725
|
if (having) {
|
|
544
726
|
sql += having.sql;
|
|
545
727
|
params.push(...having.params);
|
|
546
728
|
}
|
|
547
729
|
if (controls.$sort) {
|
|
548
730
|
const orderParts = [];
|
|
549
|
-
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(
|
|
731
|
+
for (const [col, dir] of Object.entries(controls.$sort)) orderParts.push(orderKeySql(dialect, dialect.quoteIdentifier(col), dir === -1));
|
|
550
732
|
if (orderParts.length > 0) sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
551
733
|
}
|
|
552
734
|
if (controls.$limit !== void 0) {
|
|
@@ -567,21 +749,30 @@ function buildAggregateSelect(dialect, table, where, controls) {
|
|
|
567
749
|
* Builds a COUNT query for the number of distinct groups — the groups that
|
|
568
750
|
* survive `$having` when one is given (the same predicate the row query
|
|
569
751
|
* renders, so `$count` agrees with the row set). Returns `{ count: N }` when
|
|
570
|
-
* executed.
|
|
752
|
+
* executed. The rows come straight from the table, unless `$having` reads a
|
|
753
|
+
* `first` / `last` value (or an expression over one): only then the window
|
|
754
|
+
* derived table is built.
|
|
571
755
|
*/
|
|
572
756
|
function buildAggregateCount(dialect, table, where, controls) {
|
|
573
757
|
const groupFields = controls.$groupBy;
|
|
574
758
|
const having = havingClause(dialect, controls);
|
|
575
759
|
const countCol = `COUNT(*) AS ${dialect.quoteIdentifier("count")}`;
|
|
576
|
-
if (!groupFields?.length && !having)
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
760
|
+
if (!groupFields?.length && !having) {
|
|
761
|
+
if ((controls.$select?.computedAliases.length ?? 0) > 0) return finalizeParams(dialect, {
|
|
762
|
+
sql: `SELECT 1 AS ${dialect.quoteIdentifier("count")}`,
|
|
763
|
+
params: []
|
|
764
|
+
});
|
|
765
|
+
return finalizeParams(dialect, {
|
|
766
|
+
sql: `SELECT ${countCol} FROM ${dialect.quoteTable(table)} WHERE ${where.sql}`,
|
|
767
|
+
params: where.params
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
const from = aggSource(dialect, table, where, controls, !!having && readsFirstLast(controls.$select, having.refs));
|
|
771
|
+
const groupBy = groupByClause(dialect, controls);
|
|
581
772
|
let inner = "COUNT(*)";
|
|
582
773
|
if (groupBy) inner = bucketSelectParts(dialect, controls).join(", ") || "1";
|
|
583
774
|
return finalizeParams(dialect, {
|
|
584
|
-
sql: `SELECT ${countCol} FROM (SELECT ${inner}
|
|
775
|
+
sql: `SELECT ${countCol} FROM (SELECT ${inner} ${from}${groupBy}${having?.sql ?? ""}) AS ${dialect.quoteIdentifier("_groups")}`,
|
|
585
776
|
params: [...where.params, ...having?.params ?? []]
|
|
586
777
|
});
|
|
587
778
|
}
|
|
@@ -647,30 +838,16 @@ function viewAggExpr(dialect, c, resolveFieldRef) {
|
|
|
647
838
|
function viewComputeExpr(dialect, c, byPath, resolveFieldRef, grouped = false, cache = /* @__PURE__ */ new Map()) {
|
|
648
839
|
const cached = cache.get(c.viewPath);
|
|
649
840
|
if (cached !== void 0) return cached;
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const leaf = byPath.get(e.field);
|
|
659
|
-
if (!leaf) throw new Error(`View column "${c.viewColumn}": "${e.field}" is not a column of the view`);
|
|
660
|
-
if (leaf.expr !== void 0) return viewComputeExpr(dialect, leaf, byPath, resolveFieldRef, grouped, cache);
|
|
661
|
-
if (leaf.aggFn) return cast(viewAggExpr(dialect, leaf, resolveFieldRef));
|
|
662
|
-
const source = viewSourceExpr(dialect, leaf);
|
|
663
|
-
return cast(grouped && leaf.json ? `MIN(${source})` : source);
|
|
664
|
-
}
|
|
665
|
-
const args = e.args.map(render);
|
|
666
|
-
switch (e.op) {
|
|
667
|
-
case "neg": return `(-${args[0]})`;
|
|
668
|
-
case "coalesce": return `COALESCE(${args.join(", ")})`;
|
|
669
|
-
case "/": return `(${args[0]} / NULLIF(${args[1]}, 0))`;
|
|
670
|
-
default: return `(${args[0]} ${e.op} ${args[1]})`;
|
|
671
|
-
}
|
|
841
|
+
const fail = (reason) => /* @__PURE__ */ new Error(reason === "no-cast" ? `View column "${c.viewColumn}": computed view columns are not supported by this adapter` : `View column "${c.viewColumn}": non-finite literal in @db.compute`);
|
|
842
|
+
const leaf = (field) => {
|
|
843
|
+
const mapping = byPath.get(field);
|
|
844
|
+
if (!mapping) throw new Error(`View column "${c.viewColumn}": "${field}" is not a column of the view`);
|
|
845
|
+
if (mapping.expr !== void 0) return { double: viewComputeExpr(dialect, mapping, byPath, resolveFieldRef, grouped, cache) };
|
|
846
|
+
if (mapping.aggFn) return viewAggExpr(dialect, mapping, resolveFieldRef);
|
|
847
|
+
const source = viewSourceExpr(dialect, mapping);
|
|
848
|
+
return grouped && mapping.json ? `MIN(${source})` : source;
|
|
672
849
|
};
|
|
673
|
-
const sql =
|
|
850
|
+
const sql = renderArith(dialect, c.expr, leaf, fail);
|
|
674
851
|
cache.set(c.viewPath, sql);
|
|
675
852
|
return sql;
|
|
676
853
|
}
|
|
@@ -694,10 +871,7 @@ function firstRowOn(dialect, join, target, condition, resolveFieldRef) {
|
|
|
694
871
|
type: join.targetType,
|
|
695
872
|
field: first.key
|
|
696
873
|
});
|
|
697
|
-
return `${key} = (SELECT ${key} FROM ${target} WHERE ${condition} ORDER BY ${first.order.map(({ ref, desc }) =>
|
|
698
|
-
const nulls = dialect.nullsSortLargest ? desc ? " NULLS LAST" : " NULLS FIRST" : "";
|
|
699
|
-
return `${resolveFieldRef(ref)} ${desc ? "DESC" : "ASC"}${nulls}`;
|
|
700
|
-
}).join(", ")} LIMIT 1)`;
|
|
874
|
+
return `${key} = (SELECT ${key} FROM ${target} WHERE ${condition} ORDER BY ${first.order.map(({ ref, desc }) => orderKeySql(dialect, resolveFieldRef(ref), desc)).join(", ")} LIMIT 1)`;
|
|
701
875
|
}
|
|
702
876
|
/**
|
|
703
877
|
* Builds a CREATE VIEW statement from a view plan and column mappings.
|
|
@@ -778,6 +952,21 @@ function insertManyColumns(rows) {
|
|
|
778
952
|
return [...columns];
|
|
779
953
|
}
|
|
780
954
|
/**
|
|
955
|
+
* Splits `rows` into batches that stay under the driver's bind-parameter limit
|
|
956
|
+
* (PostgreSQL ~65535, MySQL packet size): `maxParams` (default 60000) divided
|
|
957
|
+
* by the column count. Returns the shared column union and the batches.
|
|
958
|
+
*/
|
|
959
|
+
function chunkInsertRows(rows, maxParams = 6e4) {
|
|
960
|
+
const columns = insertManyColumns(rows);
|
|
961
|
+
const size = columns.length > 0 ? Math.max(1, Math.floor(maxParams / columns.length)) : rows.length;
|
|
962
|
+
const batches = [];
|
|
963
|
+
for (let offset = 0; offset < rows.length; offset += size) batches.push(rows.slice(offset, offset + size));
|
|
964
|
+
return {
|
|
965
|
+
columns,
|
|
966
|
+
batches
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
781
970
|
* Builds a multi-row `INSERT … VALUES (…), (…)` statement over `columns`
|
|
782
971
|
* (default: {@link insertManyColumns} of `rows`). A row lacking a column gets
|
|
783
972
|
* `DEFAULT` — exactly what a single-row INSERT omitting it stores. Callers
|
|
@@ -1253,4 +1442,4 @@ function parseRegexString(value) {
|
|
|
1253
1442
|
};
|
|
1254
1443
|
}
|
|
1255
1444
|
//#endregion
|
|
1256
|
-
export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, VECTOR_DISTANCE_ALIAS, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource };
|
|
1445
|
+
export { AGG_FN_SQL, EMPTY_AND, EMPTY_OR, GEO_DISTANCE_ALIAS, PARTITION_ROW_NUMBER_ALIAS, SEARCH_SOURCE_ALIAS, SQL_DEFAULT, VECTOR_DISTANCE_ALIAS, arithOverflowError, buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildPartitionedSelect, buildProjection, buildSelect, buildUpdate, buildVectorSearchCount, buildVectorSearchSelect, buildWhere, chunkInsertRows, createFilterVisitor, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, finalizeParams, foreignKeySql, geoWindowFromControls, groupKeySql, insertManyColumns, jsonDollarPath, mapQueryErrors, normalizeGeoPointValue, numericOutOfRangeError, orderKeySql, parseRegexString, queryNodeToSql, queryOpToSql, quotedJsonPathSegments, refActionToSql, renameGeoDistance, renderArith, replaceColumnsFor, sqlStringLiteral, sqlTimeZoneLiteral, stripPartitionRowNumber, toSqlValue, vectorDistanceSource };
|
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.149",
|
|
4
4
|
"description": "Shared SQL builder utilities for @atscript database adapters.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@uniqu/core": "^0.1.
|
|
41
|
-
"unplugin-atscript": "^0.1.
|
|
40
|
+
"@uniqu/core": "^0.1.13",
|
|
41
|
+
"unplugin-atscript": "^0.1.100"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@uniqu/core": "^0.1.
|
|
45
|
-
"@atscript/db": "^0.1.
|
|
44
|
+
"@uniqu/core": "^0.1.13",
|
|
45
|
+
"@atscript/db": "^0.1.149"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "vp pack",
|