neoorm 0.4.7 → 0.5.0
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/README.md +6 -6
- package/dist/codegen/emit-includes.d.ts.map +1 -1
- package/dist/codegen/emit-includes.js +3 -1
- package/dist/codegen/emit-includes.js.map +1 -1
- package/dist/dialect/postgres.d.ts.map +1 -1
- package/dist/dialect/postgres.js +8 -4
- package/dist/dialect/postgres.js.map +1 -1
- package/dist/dialect/sqlite.d.ts.map +1 -1
- package/dist/dialect/sqlite.js +14 -4
- package/dist/dialect/sqlite.js.map +1 -1
- package/dist/dialect/types.d.ts +4 -1
- package/dist/dialect/types.d.ts.map +1 -1
- package/dist/plugins/builtin.d.ts +6 -2
- package/dist/plugins/builtin.d.ts.map +1 -1
- package/dist/plugins/builtin.js.map +1 -1
- package/dist/runtime/client.d.ts +44 -6
- package/dist/runtime/client.d.ts.map +1 -1
- package/dist/runtime/client.js +13 -10
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/query/aggregate.d.ts +3 -1
- package/dist/runtime/query/aggregate.d.ts.map +1 -1
- package/dist/runtime/query/aggregate.js +19 -5
- package/dist/runtime/query/aggregate.js.map +1 -1
- package/dist/runtime/query/compile.d.ts +49 -10
- package/dist/runtime/query/compile.d.ts.map +1 -1
- package/dist/runtime/query/compile.js +666 -68
- package/dist/runtime/query/compile.js.map +1 -1
- package/dist/runtime/query/count.d.ts +10 -2
- package/dist/runtime/query/count.d.ts.map +1 -1
- package/dist/runtime/query/count.js +39 -3
- package/dist/runtime/query/count.js.map +1 -1
- package/dist/runtime/query/create.d.ts +2 -0
- package/dist/runtime/query/create.d.ts.map +1 -1
- package/dist/runtime/query/create.js +7 -4
- package/dist/runtime/query/create.js.map +1 -1
- package/dist/runtime/query/cursor.d.ts +2 -1
- package/dist/runtime/query/cursor.d.ts.map +1 -1
- package/dist/runtime/query/cursor.js +21 -2
- package/dist/runtime/query/cursor.js.map +1 -1
- package/dist/runtime/query/delete.d.ts +3 -0
- package/dist/runtime/query/delete.d.ts.map +1 -1
- package/dist/runtime/query/delete.js +18 -3
- package/dist/runtime/query/delete.js.map +1 -1
- package/dist/runtime/query/find-or-create.d.ts.map +1 -1
- package/dist/runtime/query/find-or-create.js +3 -3
- package/dist/runtime/query/find-or-create.js.map +1 -1
- package/dist/runtime/query/find.d.ts +17 -9
- package/dist/runtime/query/find.d.ts.map +1 -1
- package/dist/runtime/query/find.js +118 -53
- package/dist/runtime/query/find.js.map +1 -1
- package/dist/runtime/query/group-by.d.ts +23 -0
- package/dist/runtime/query/group-by.d.ts.map +1 -0
- package/dist/runtime/query/group-by.js +54 -0
- package/dist/runtime/query/group-by.js.map +1 -0
- package/dist/runtime/query/paginate.d.ts +3 -0
- package/dist/runtime/query/paginate.d.ts.map +1 -1
- package/dist/runtime/query/paginate.js +34 -12
- package/dist/runtime/query/paginate.js.map +1 -1
- package/dist/runtime/query/projection.d.ts +19 -0
- package/dist/runtime/query/projection.d.ts.map +1 -0
- package/dist/runtime/query/projection.js +125 -0
- package/dist/runtime/query/projection.js.map +1 -0
- package/dist/runtime/query/relation-planner.d.ts +15 -5
- package/dist/runtime/query/relation-planner.d.ts.map +1 -1
- package/dist/runtime/query/relation-planner.js +103 -43
- package/dist/runtime/query/relation-planner.js.map +1 -1
- package/dist/runtime/query/table-index.d.ts +1 -0
- package/dist/runtime/query/table-index.d.ts.map +1 -1
- package/dist/runtime/query/table-index.js +1 -0
- package/dist/runtime/query/table-index.js.map +1 -1
- package/dist/runtime/query/update.d.ts +4 -0
- package/dist/runtime/query/update.d.ts.map +1 -1
- package/dist/runtime/query/update.js +55 -25
- package/dist/runtime/query/update.js.map +1 -1
- package/dist/runtime/query/upsert.d.ts.map +1 -1
- package/dist/runtime/query/upsert.js +8 -9
- package/dist/runtime/query/upsert.js.map +1 -1
- package/dist/runtime/types.d.ts +23 -11
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/schema/column-where.d.ts +3 -0
- package/dist/schema/column-where.d.ts.map +1 -1
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/relation-types.d.ts +17 -8
- package/dist/schema/relation-types.d.ts.map +1 -1
- package/dist/schema/types.d.ts +117 -16
- package/dist/schema/types.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { effectiveRelations } from "../../codegen/manifest-relations.js";
|
|
2
|
-
import { postgresDialect, quoteIdentifier, tableRef } from "../../dialect/postgres.js";
|
|
2
|
+
import { postgresDialect, quoteIdentifier, tableRef, } from "../../dialect/postgres.js";
|
|
3
3
|
import { getColumnType } from "../../plugins/registry.js";
|
|
4
4
|
import { rebaseParamRefs } from "../../sql/template.js";
|
|
5
5
|
import { findM2M } from "./manifest-lookup.js";
|
|
@@ -20,6 +20,35 @@ const operatorParamTransform = {
|
|
|
20
20
|
startsWith: (v) => `${String(v)}%`,
|
|
21
21
|
endsWith: (v) => `%${String(v)}`,
|
|
22
22
|
};
|
|
23
|
+
function parseQueryMode(value) {
|
|
24
|
+
if (value === undefined || value === "default")
|
|
25
|
+
return "default";
|
|
26
|
+
if (value === "insensitive")
|
|
27
|
+
return "insensitive";
|
|
28
|
+
throw new Error(`unsupported query mode: ${String(value)}`);
|
|
29
|
+
}
|
|
30
|
+
function isStringPatternOp(op) {
|
|
31
|
+
return (op === "contains" ||
|
|
32
|
+
op === "startsWith" ||
|
|
33
|
+
op === "endsWith" ||
|
|
34
|
+
op === "search");
|
|
35
|
+
}
|
|
36
|
+
function stringFilterSql(op, sqlCol, paramIndex, mode, dialect) {
|
|
37
|
+
switch (op) {
|
|
38
|
+
case "contains":
|
|
39
|
+
case "startsWith":
|
|
40
|
+
case "endsWith":
|
|
41
|
+
return mode === "insensitive"
|
|
42
|
+
? dialect.ilike(sqlCol, paramIndex)
|
|
43
|
+
: dialect.whereOperators[op](sqlCol, paramIndex);
|
|
44
|
+
case "search":
|
|
45
|
+
return dialect.regex(sqlCol, paramIndex, mode === "insensitive");
|
|
46
|
+
default: {
|
|
47
|
+
const _never = op;
|
|
48
|
+
throw new Error(`unsupported string filter: ${_never}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
23
52
|
function pluginWhereOperators(col) {
|
|
24
53
|
if (col.kind === "fk")
|
|
25
54
|
return {};
|
|
@@ -60,6 +89,7 @@ function compileColumnCondition(col, rawValue, dialect, paramIndex, columnRef) {
|
|
|
60
89
|
nextParamIndex++;
|
|
61
90
|
return { sql: conditions.join(" AND "), params, nextParamIndex };
|
|
62
91
|
}
|
|
92
|
+
const queryMode = parseQueryMode(rawValue.mode);
|
|
63
93
|
const hasOperator = Object.keys(rawValue).some((k) => k in dialect.whereOperators || k in spatialOps);
|
|
64
94
|
if (!hasOperator) {
|
|
65
95
|
conditions.push(dialect.whereOperators.equals(sqlCol, nextParamIndex));
|
|
@@ -68,6 +98,8 @@ function compileColumnCondition(col, rawValue, dialect, paramIndex, columnRef) {
|
|
|
68
98
|
return { sql: conditions.join(" AND "), params, nextParamIndex };
|
|
69
99
|
}
|
|
70
100
|
for (const [op, value] of Object.entries(rawValue)) {
|
|
101
|
+
if (op === "mode")
|
|
102
|
+
continue;
|
|
71
103
|
if (op in spatialOps) {
|
|
72
104
|
const operator = spatialOps[op];
|
|
73
105
|
if (!operator)
|
|
@@ -102,7 +134,9 @@ function compileColumnCondition(col, rawValue, dialect, paramIndex, columnRef) {
|
|
|
102
134
|
: transform
|
|
103
135
|
? transform(value)
|
|
104
136
|
: serializeColumnValue(col, value);
|
|
105
|
-
conditions.push(
|
|
137
|
+
conditions.push(isStringPatternOp(operator)
|
|
138
|
+
? stringFilterSql(operator, sqlCol, nextParamIndex, queryMode, dialect)
|
|
139
|
+
: dialect.whereOperators[operator](sqlCol, nextParamIndex));
|
|
106
140
|
params.push(paramValue);
|
|
107
141
|
nextParamIndex++;
|
|
108
142
|
}
|
|
@@ -282,13 +316,15 @@ function compileWhereNode(manifest, table, where, dialect, startParamIndex, colu
|
|
|
282
316
|
nextParamIndex: paramIndex,
|
|
283
317
|
};
|
|
284
318
|
}
|
|
285
|
-
export function compileWhere(manifest, table, where, dialect, startParamIndex = 1, manifestIndex, qualifyColumns = false) {
|
|
319
|
+
export function compileWhere(manifest, table, where, dialect, startParamIndex = 1, manifestIndex, qualifyColumns = false, tableAlias) {
|
|
286
320
|
if (!where || Object.keys(where).length === 0) {
|
|
287
321
|
return { sql: "", params: [] };
|
|
288
322
|
}
|
|
289
|
-
const columnRef =
|
|
290
|
-
?
|
|
291
|
-
:
|
|
323
|
+
const columnRef = tableAlias
|
|
324
|
+
? (col) => `${quoteIdentifier(tableAlias)}.${quoteIdentifier(col.sqlName)}`
|
|
325
|
+
: qualifyColumns
|
|
326
|
+
? qualifiedColumnRefForTable(table)
|
|
327
|
+
: defaultColumnRef;
|
|
292
328
|
const result = compileWhereNode(manifest, table, where, dialect, startParamIndex, columnRef, manifestIndex);
|
|
293
329
|
const impossible = isImpossibleWhereSql(result.sql);
|
|
294
330
|
return {
|
|
@@ -297,19 +333,23 @@ export function compileWhere(manifest, table, where, dialect, startParamIndex =
|
|
|
297
333
|
...(impossible ? { impossible: true } : {}),
|
|
298
334
|
};
|
|
299
335
|
}
|
|
300
|
-
function whereShapeKey(where) {
|
|
336
|
+
export function whereShapeKey(where) {
|
|
301
337
|
const parts = [];
|
|
302
338
|
for (const [key, value] of Object.entries(where)) {
|
|
303
339
|
if (key === "AND" && Array.isArray(value)) {
|
|
304
340
|
parts.push(`AND:${value
|
|
305
|
-
.filter((item) => !!item &&
|
|
341
|
+
.filter((item) => !!item &&
|
|
342
|
+
typeof item === "object" &&
|
|
343
|
+
!Array.isArray(item))
|
|
306
344
|
.map((item) => whereShapeKey(item))
|
|
307
345
|
.join(",")}`);
|
|
308
346
|
continue;
|
|
309
347
|
}
|
|
310
348
|
if (key === "OR" && Array.isArray(value)) {
|
|
311
349
|
parts.push(`OR:${value
|
|
312
|
-
.filter((item) => !!item &&
|
|
350
|
+
.filter((item) => !!item &&
|
|
351
|
+
typeof item === "object" &&
|
|
352
|
+
!Array.isArray(item))
|
|
313
353
|
.map((item) => whereShapeKey(item))
|
|
314
354
|
.join(",")}`);
|
|
315
355
|
continue;
|
|
@@ -332,7 +372,9 @@ function whereShapeKey(where) {
|
|
|
332
372
|
parts.push(`${key}:${op}:${len}`);
|
|
333
373
|
}
|
|
334
374
|
else {
|
|
335
|
-
|
|
375
|
+
const modePart = typeof value.mode === "string" ? `:mode:${value.mode}` : "";
|
|
376
|
+
const opsForKey = ops.filter((op) => op !== "mode");
|
|
377
|
+
parts.push(`${key}:${opsForKey.join(",")}${modePart}`);
|
|
336
378
|
}
|
|
337
379
|
continue;
|
|
338
380
|
}
|
|
@@ -345,12 +387,17 @@ function collectWhereParams(manifest, table, where, dialect, manifestIndex) {
|
|
|
345
387
|
function walk(node, columnRef) {
|
|
346
388
|
const tableIndex = getTableIndex(manifestIndex, table.accessor);
|
|
347
389
|
const relations = tableIndex?.effectiveRelationsByName ??
|
|
348
|
-
new Map(effectiveRelations(manifest, table).map((rel) => [
|
|
390
|
+
new Map(effectiveRelations(manifest, table).map((rel) => [
|
|
391
|
+
rel.name,
|
|
392
|
+
rel,
|
|
393
|
+
]));
|
|
349
394
|
for (const [key, value] of Object.entries(node)) {
|
|
350
395
|
if (key === "AND" || key === "OR") {
|
|
351
396
|
if (Array.isArray(value)) {
|
|
352
397
|
for (const item of value) {
|
|
353
|
-
if (item &&
|
|
398
|
+
if (item &&
|
|
399
|
+
typeof item === "object" &&
|
|
400
|
+
!Array.isArray(item)) {
|
|
354
401
|
walk(item, columnRef);
|
|
355
402
|
}
|
|
356
403
|
}
|
|
@@ -413,7 +460,9 @@ export function orderByShapeKey(orderBy, tableAlias) {
|
|
|
413
460
|
return "";
|
|
414
461
|
const entries = Object.entries(orderBy)
|
|
415
462
|
.filter(([key]) => key !== "_count")
|
|
416
|
-
.map(([key, direction]) => typeof direction === "string"
|
|
463
|
+
.map(([key, direction]) => typeof direction === "string"
|
|
464
|
+
? `${key}:${direction.toUpperCase()}`
|
|
465
|
+
: "")
|
|
417
466
|
.filter(Boolean)
|
|
418
467
|
.sort((a, b) => a.localeCompare(b));
|
|
419
468
|
if (entries.length === 0)
|
|
@@ -458,9 +507,108 @@ function buildValuePlaceholder(col, paramIndex) {
|
|
|
458
507
|
}
|
|
459
508
|
return `$${paramIndex}`;
|
|
460
509
|
}
|
|
461
|
-
function
|
|
510
|
+
function isBinaryValue(value) {
|
|
511
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value))
|
|
512
|
+
return true;
|
|
513
|
+
return value instanceof Uint8Array;
|
|
514
|
+
}
|
|
515
|
+
const ATOMIC_UPDATE_OPS = new Set([
|
|
516
|
+
"increment",
|
|
517
|
+
"decrement",
|
|
518
|
+
"multiply",
|
|
519
|
+
"set",
|
|
520
|
+
]);
|
|
521
|
+
const NUMERIC_UPDATE_KINDS = new Set(["int", "serial", "decimal", "bigint"]);
|
|
522
|
+
function isNumericUpdateKind(kind) {
|
|
523
|
+
return NUMERIC_UPDATE_KINDS.has(kind);
|
|
524
|
+
}
|
|
525
|
+
function needsNumericCast(col, dialect) {
|
|
526
|
+
return (col.kind === "decimal" ||
|
|
527
|
+
(dialect.name === "sqlite" && col.kind === "bigint"));
|
|
528
|
+
}
|
|
529
|
+
function isAtomicOp(op) {
|
|
530
|
+
return ATOMIC_UPDATE_OPS.has(op);
|
|
531
|
+
}
|
|
532
|
+
function arithmeticSql(op, left, right) {
|
|
533
|
+
switch (op) {
|
|
534
|
+
case "increment":
|
|
535
|
+
return `${left} + ${right}`;
|
|
536
|
+
case "decrement":
|
|
537
|
+
return `${left} - ${right}`;
|
|
538
|
+
case "multiply":
|
|
539
|
+
return `${left} * ${right}`;
|
|
540
|
+
default: {
|
|
541
|
+
const _never = op;
|
|
542
|
+
throw new Error(`unsupported atomic update: ${_never}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
export function parseAtomicUpdate(col, value) {
|
|
547
|
+
if (!isOperatorObject(value) || isBinaryValue(value)) {
|
|
548
|
+
return { op: "set", value };
|
|
549
|
+
}
|
|
550
|
+
const keys = Object.keys(value);
|
|
551
|
+
const opKeys = keys.filter(isAtomicOp);
|
|
552
|
+
if (opKeys.length === 0) {
|
|
553
|
+
if (isNumericUpdateKind(col.kind)) {
|
|
554
|
+
throw new Error(`update on ${col.tsName} requires increment, decrement, multiply, or set`);
|
|
555
|
+
}
|
|
556
|
+
return { op: "set", value };
|
|
557
|
+
}
|
|
558
|
+
if (opKeys.length !== keys.length) {
|
|
559
|
+
throw new Error(`update on ${col.tsName} cannot mix operators with other keys`);
|
|
560
|
+
}
|
|
561
|
+
if (opKeys.length !== 1) {
|
|
562
|
+
throw new Error(`update on ${col.tsName} allows only one of increment, decrement, multiply, set`);
|
|
563
|
+
}
|
|
564
|
+
const op = opKeys[0];
|
|
565
|
+
if (op === undefined) {
|
|
566
|
+
throw new Error(`update on ${col.tsName} requires an operator`);
|
|
567
|
+
}
|
|
568
|
+
if (value[op] === undefined) {
|
|
569
|
+
throw new Error(`update ${op} on ${col.tsName} requires a value`);
|
|
570
|
+
}
|
|
571
|
+
if (op !== "set" && !isNumericUpdateKind(col.kind)) {
|
|
572
|
+
throw new Error(`${op} is not supported on ${col.kind} column ${col.tsName}`);
|
|
573
|
+
}
|
|
574
|
+
return { op, value: value[op] };
|
|
575
|
+
}
|
|
576
|
+
function orderUpdateAssignments(dataKeys, ops) {
|
|
577
|
+
const pairs = dataKeys.map((key, i) => ({
|
|
578
|
+
key,
|
|
579
|
+
op: ops?.[i] ?? "set",
|
|
580
|
+
}));
|
|
581
|
+
pairs.sort((a, b) => a.key.localeCompare(b.key));
|
|
582
|
+
return {
|
|
583
|
+
keys: pairs.map((pair) => pair.key),
|
|
584
|
+
ops: pairs.map((pair) => pair.op),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function buildSetExpression(col, paramIndex, op = "set", dialect = postgresDialect) {
|
|
462
588
|
const sqlCol = quoteIdentifier(col?.sqlName ?? "");
|
|
463
|
-
|
|
589
|
+
const placeholder = buildValuePlaceholder(col, paramIndex);
|
|
590
|
+
switch (op) {
|
|
591
|
+
case "set":
|
|
592
|
+
return `${sqlCol} = ${placeholder}`;
|
|
593
|
+
case "increment":
|
|
594
|
+
case "decrement":
|
|
595
|
+
case "multiply": {
|
|
596
|
+
if (!col) {
|
|
597
|
+
throw new Error("atomic update requires a column");
|
|
598
|
+
}
|
|
599
|
+
const left = needsNumericCast(col, dialect)
|
|
600
|
+
? dialect.castToNumeric(sqlCol)
|
|
601
|
+
: sqlCol;
|
|
602
|
+
const right = needsNumericCast(col, dialect)
|
|
603
|
+
? dialect.castToNumeric(placeholder)
|
|
604
|
+
: placeholder;
|
|
605
|
+
return `${sqlCol} = ${arithmeticSql(op, left, right)}`;
|
|
606
|
+
}
|
|
607
|
+
default: {
|
|
608
|
+
const _never = op;
|
|
609
|
+
throw new Error(`unsupported atomic update: ${_never}`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
464
612
|
}
|
|
465
613
|
export function compileOrderBy(table, orderBy, tableAlias, manifestIndex) {
|
|
466
614
|
if (!orderBy || Object.keys(orderBy).length === 0)
|
|
@@ -518,10 +666,10 @@ export function buildQualifiedSelectColumns(table, select, manifestIndex) {
|
|
|
518
666
|
: table.columns;
|
|
519
667
|
return cols.map((c) => `${ref}.${selectExpression(c)}`).join(", ");
|
|
520
668
|
}
|
|
521
|
-
export function buildFindByIdQuery(table) {
|
|
669
|
+
export function buildFindByIdQuery(table, select, manifestIndex) {
|
|
522
670
|
const { sqlName } = requireScalarPrimaryKey(table);
|
|
523
671
|
const sqlCol = quoteIdentifier(sqlName);
|
|
524
|
-
const selectCols = buildSelectColumns(table);
|
|
672
|
+
const selectCols = buildSelectColumns(table, select, manifestIndex);
|
|
525
673
|
return `SELECT ${selectCols} FROM ${tableRef(table)} WHERE ${sqlCol} = $1`;
|
|
526
674
|
}
|
|
527
675
|
export function buildFindAllQuery(table) {
|
|
@@ -533,12 +681,12 @@ export function normalizeLimitOffset(value, label) {
|
|
|
533
681
|
}
|
|
534
682
|
return value;
|
|
535
683
|
}
|
|
536
|
-
export function buildFindManyQuery(table, whereSql, orderSql,
|
|
684
|
+
export function buildFindManyQuery(table, whereSql, orderSql, take, skip, distinctOn, extraSelectCols, joinClauses, manifestIndex, groupBySql, select) {
|
|
537
685
|
const hasJoins = Boolean(joinClauses && joinClauses.length > 0);
|
|
538
686
|
const tableIndex = getTableIndex(manifestIndex, table.accessor);
|
|
539
687
|
const selectCols = hasJoins
|
|
540
|
-
? buildQualifiedSelectColumns(table,
|
|
541
|
-
: buildSelectColumns(table,
|
|
688
|
+
? buildQualifiedSelectColumns(table, select, manifestIndex)
|
|
689
|
+
: buildSelectColumns(table, select, manifestIndex);
|
|
542
690
|
let sql = "SELECT ";
|
|
543
691
|
if (distinctOn && distinctOn.length > 0) {
|
|
544
692
|
const distinctCols = columnsByTsNames(tableIndex, table, distinctOn)
|
|
@@ -562,23 +710,99 @@ export function buildFindManyQuery(table, whereSql, orderSql, limit, offset, dis
|
|
|
562
710
|
sql += ` ${groupBySql}`;
|
|
563
711
|
if (orderSql)
|
|
564
712
|
sql += ` ${orderSql}`;
|
|
565
|
-
if (
|
|
566
|
-
sql += ` LIMIT ${normalizeLimitOffset(
|
|
713
|
+
if (take !== undefined) {
|
|
714
|
+
sql += ` LIMIT ${normalizeLimitOffset(take, "take")}`;
|
|
567
715
|
}
|
|
568
|
-
if (
|
|
569
|
-
sql += ` OFFSET ${normalizeLimitOffset(
|
|
716
|
+
if (skip !== undefined) {
|
|
717
|
+
sql += ` OFFSET ${normalizeLimitOffset(skip, "skip")}`;
|
|
570
718
|
}
|
|
571
719
|
return sql;
|
|
572
720
|
}
|
|
573
721
|
export function buildPaginateQuery(table, whereSql, orderSql, take, extraSelectCols, joinClauses, manifestIndex) {
|
|
574
722
|
return buildFindManyQuery(table, whereSql, orderSql, take + 1, undefined, undefined, extraSelectCols, joinClauses, manifestIndex);
|
|
575
723
|
}
|
|
576
|
-
export function buildCountQuery(table, whereSql, dialect = postgresDialect) {
|
|
577
|
-
|
|
724
|
+
export function buildCountQuery(table, whereSql, dialect = postgresDialect, distinct, select, manifestIndex) {
|
|
725
|
+
if (select !== undefined) {
|
|
726
|
+
if (distinct) {
|
|
727
|
+
throw new Error("count cannot combine distinct and select");
|
|
728
|
+
}
|
|
729
|
+
const parts = countSelectParts(table, select, dialect, manifestIndex);
|
|
730
|
+
if (parts.length === 0) {
|
|
731
|
+
throw new Error("count select requires at least one field");
|
|
732
|
+
}
|
|
733
|
+
let sql = `SELECT ${parts.join(", ")} FROM ${tableRef(table)}`;
|
|
734
|
+
if (whereSql)
|
|
735
|
+
sql += ` ${whereSql}`;
|
|
736
|
+
return sql;
|
|
737
|
+
}
|
|
738
|
+
let expr = "COUNT(*)";
|
|
739
|
+
if (distinct) {
|
|
740
|
+
const sqlCol = requireCountSqlCol(table, distinct, manifestIndex);
|
|
741
|
+
expr = `COUNT(DISTINCT ${sqlCol})`;
|
|
742
|
+
}
|
|
743
|
+
let sql = `SELECT ${dialect.castToInt(expr)} AS count FROM ${tableRef(table)}`;
|
|
744
|
+
if (whereSql)
|
|
745
|
+
sql += ` ${whereSql}`;
|
|
746
|
+
return sql;
|
|
747
|
+
}
|
|
748
|
+
export function buildExistsQuery(table, whereSql) {
|
|
749
|
+
let sql = `SELECT 1 FROM ${tableRef(table)}`;
|
|
578
750
|
if (whereSql)
|
|
579
751
|
sql += ` ${whereSql}`;
|
|
752
|
+
sql += " LIMIT 1";
|
|
580
753
|
return sql;
|
|
581
754
|
}
|
|
755
|
+
function countSqlCol(table, tsName, manifestIndex) {
|
|
756
|
+
const tableIndex = getTableIndex(manifestIndex, table.accessor);
|
|
757
|
+
const col = columnByTsName(tableIndex, table, tsName);
|
|
758
|
+
if (!col)
|
|
759
|
+
return undefined;
|
|
760
|
+
return quoteIdentifier(col.sqlName);
|
|
761
|
+
}
|
|
762
|
+
export function requireCountSqlCol(table, tsName, manifestIndex) {
|
|
763
|
+
const sqlCol = countSqlCol(table, tsName, manifestIndex);
|
|
764
|
+
if (!sqlCol) {
|
|
765
|
+
throw new Error(`Unknown count column: ${tsName}`);
|
|
766
|
+
}
|
|
767
|
+
return sqlCol;
|
|
768
|
+
}
|
|
769
|
+
export function normalizeCountMap(select) {
|
|
770
|
+
const map = {};
|
|
771
|
+
for (const [key, value] of Object.entries(select)) {
|
|
772
|
+
if (value === true)
|
|
773
|
+
map[key] = true;
|
|
774
|
+
}
|
|
775
|
+
return map;
|
|
776
|
+
}
|
|
777
|
+
export function toCountSelector(value) {
|
|
778
|
+
if (value === true)
|
|
779
|
+
return true;
|
|
780
|
+
const map = normalizeCountMap(value);
|
|
781
|
+
if (Object.keys(map).length === 0) {
|
|
782
|
+
throw new Error("_count requires at least one field");
|
|
783
|
+
}
|
|
784
|
+
return map;
|
|
785
|
+
}
|
|
786
|
+
export function hasStarCount(selectors) {
|
|
787
|
+
return (selectors._count === true ||
|
|
788
|
+
(typeof selectors._count === "object" && selectors._count._all === true));
|
|
789
|
+
}
|
|
790
|
+
export function hasCountField(selectors, field) {
|
|
791
|
+
return (typeof selectors._count === "object" && selectors._count[field] === true);
|
|
792
|
+
}
|
|
793
|
+
export function countSelectParts(table, select, dialect = postgresDialect, manifestIndex) {
|
|
794
|
+
const parts = [];
|
|
795
|
+
if (select._all) {
|
|
796
|
+
parts.push(`${dialect.castToInt("COUNT(*)")} AS "_all"`);
|
|
797
|
+
}
|
|
798
|
+
for (const key of Object.keys(select).sort()) {
|
|
799
|
+
if (key === "_all")
|
|
800
|
+
continue;
|
|
801
|
+
const sqlCol = requireCountSqlCol(table, key, manifestIndex);
|
|
802
|
+
parts.push(`${dialect.castToInt(`COUNT(${sqlCol})`)} AS ${quoteIdentifier(key)}`);
|
|
803
|
+
}
|
|
804
|
+
return parts;
|
|
805
|
+
}
|
|
582
806
|
function aggregateSqlCol(table, tsName, dialect, manifestIndex) {
|
|
583
807
|
const tableIndex = getTableIndex(manifestIndex, table.accessor);
|
|
584
808
|
const col = columnByTsName(tableIndex, table, tsName);
|
|
@@ -589,31 +813,58 @@ function aggregateSqlCol(table, tsName, dialect, manifestIndex) {
|
|
|
589
813
|
return dialect.castToNumeric(sqlCol);
|
|
590
814
|
return sqlCol;
|
|
591
815
|
}
|
|
592
|
-
|
|
816
|
+
const FIELD_AGG_KEYS = ["_avg", "_sum", "_min", "_max"];
|
|
817
|
+
function sqlFnForFieldAgg(key) {
|
|
818
|
+
switch (key) {
|
|
819
|
+
case "_avg":
|
|
820
|
+
return "AVG";
|
|
821
|
+
case "_sum":
|
|
822
|
+
return "SUM";
|
|
823
|
+
case "_min":
|
|
824
|
+
return "MIN";
|
|
825
|
+
case "_max":
|
|
826
|
+
return "MAX";
|
|
827
|
+
default: {
|
|
828
|
+
const _never = key;
|
|
829
|
+
throw new Error(`unsupported aggregate: ${_never}`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function fieldAggExpression(key, table, colName, dialect, manifestIndex) {
|
|
834
|
+
const sqlCol = aggregateSqlCol(table, colName, dialect, manifestIndex);
|
|
835
|
+
if (!sqlCol)
|
|
836
|
+
return undefined;
|
|
837
|
+
return `${sqlFnForFieldAgg(key)}(${sqlCol})`;
|
|
838
|
+
}
|
|
839
|
+
export function aggregateSelectParts(table, selectors, dialect = postgresDialect, manifestIndex) {
|
|
593
840
|
const parts = [];
|
|
594
|
-
if (selectors._count) {
|
|
841
|
+
if (selectors._count === true) {
|
|
595
842
|
parts.push(`${dialect.castToInt("COUNT(*)")} AS "__count"`);
|
|
596
843
|
}
|
|
597
|
-
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
for (const colName of Object.keys(selectors._min ?? {})) {
|
|
608
|
-
const sqlCol = aggregateSqlCol(table, colName, dialect, manifestIndex);
|
|
609
|
-
if (sqlCol)
|
|
610
|
-
parts.push(`MIN(${sqlCol}) AS "_min_${colName}"`);
|
|
844
|
+
else if (selectors._count) {
|
|
845
|
+
for (const key of Object.keys(selectors._count).sort()) {
|
|
846
|
+
if (key === "_all") {
|
|
847
|
+
parts.push(`${dialect.castToInt("COUNT(*)")} AS "__count_all"`);
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
const sqlCol = requireCountSqlCol(table, key, manifestIndex);
|
|
851
|
+
parts.push(`${dialect.castToInt(`COUNT(${sqlCol})`)} AS "__count_${key}"`);
|
|
852
|
+
}
|
|
611
853
|
}
|
|
612
|
-
for (const
|
|
613
|
-
const
|
|
614
|
-
if (
|
|
615
|
-
|
|
854
|
+
for (const key of FIELD_AGG_KEYS) {
|
|
855
|
+
const fieldMap = selectors[key];
|
|
856
|
+
if (!fieldMap)
|
|
857
|
+
continue;
|
|
858
|
+
for (const colName of Object.keys(fieldMap)) {
|
|
859
|
+
const expr = fieldAggExpression(key, table, colName, dialect, manifestIndex);
|
|
860
|
+
if (expr)
|
|
861
|
+
parts.push(`${expr} AS "${key}_${colName}"`);
|
|
862
|
+
}
|
|
616
863
|
}
|
|
864
|
+
return parts;
|
|
865
|
+
}
|
|
866
|
+
export function buildAggregateQuery(table, selectors, whereSql, manifestIndex, dialect = postgresDialect) {
|
|
867
|
+
const parts = aggregateSelectParts(table, selectors, dialect, manifestIndex);
|
|
617
868
|
if (parts.length === 0) {
|
|
618
869
|
throw new Error("aggregate requires at least one selector");
|
|
619
870
|
}
|
|
@@ -624,8 +875,12 @@ export function buildAggregateQuery(table, selectors, whereSql, manifestIndex, d
|
|
|
624
875
|
}
|
|
625
876
|
export function aggregateSelectorCacheKey(selectors) {
|
|
626
877
|
const parts = [];
|
|
627
|
-
if (selectors._count)
|
|
878
|
+
if (selectors._count === true) {
|
|
628
879
|
parts.push("_count");
|
|
880
|
+
}
|
|
881
|
+
else if (selectors._count) {
|
|
882
|
+
parts.push(`_count:${Object.keys(selectors._count).sort().join(",")}`);
|
|
883
|
+
}
|
|
629
884
|
for (const key of ["_avg", "_sum", "_min", "_max"]) {
|
|
630
885
|
const fieldMap = selectors[key];
|
|
631
886
|
if (!fieldMap)
|
|
@@ -641,7 +896,297 @@ export function getCachedAggregateQuery(tableIndex, table, selectors, whereSql,
|
|
|
641
896
|
}
|
|
642
897
|
return getOrSetSqlCache(tableIndex.aggregateSqlBySelector, cacheKey, () => buildAggregateQuery(table, selectors, whereSql, manifestIndex, dialect));
|
|
643
898
|
}
|
|
644
|
-
|
|
899
|
+
function isHavingOperator(op) {
|
|
900
|
+
return (op === "equals" ||
|
|
901
|
+
op === "gt" ||
|
|
902
|
+
op === "gte" ||
|
|
903
|
+
op === "lt" ||
|
|
904
|
+
op === "lte" ||
|
|
905
|
+
op === "in" ||
|
|
906
|
+
op === "notIn");
|
|
907
|
+
}
|
|
908
|
+
function requireStarCount(selectors, context) {
|
|
909
|
+
if (!hasStarCount(selectors)) {
|
|
910
|
+
throw new Error(`${context} requires _count: true or _count: { _all: true }`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function requireCountMapField(selectors, field, context) {
|
|
914
|
+
if (field === "_all") {
|
|
915
|
+
requireStarCount(selectors, context);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (!hasCountField(selectors, field)) {
|
|
919
|
+
throw new Error(`${context} requires _count: { ${field}: true }`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
function isStarHavingSpec(spec) {
|
|
923
|
+
if (typeof spec === "number")
|
|
924
|
+
return true;
|
|
925
|
+
const keys = Object.keys(spec);
|
|
926
|
+
return keys.every((key) => isHavingOperator(key));
|
|
927
|
+
}
|
|
928
|
+
function isMixedCountHaving(spec) {
|
|
929
|
+
const keys = Object.keys(spec);
|
|
930
|
+
const hasOps = keys.some((key) => isHavingOperator(key));
|
|
931
|
+
const hasFields = keys.some((key) => !isHavingOperator(key));
|
|
932
|
+
return hasOps && hasFields;
|
|
933
|
+
}
|
|
934
|
+
function countStarExpr() {
|
|
935
|
+
return "COUNT(*)";
|
|
936
|
+
}
|
|
937
|
+
function countFieldExpr(table, field, manifestIndex) {
|
|
938
|
+
if (field === "_all")
|
|
939
|
+
return countStarExpr();
|
|
940
|
+
return `COUNT(${requireCountSqlCol(table, field, manifestIndex)})`;
|
|
941
|
+
}
|
|
942
|
+
function requireSelectedFieldAgg(selectors, key, colName) {
|
|
943
|
+
if (!selectors[key]?.[colName]) {
|
|
944
|
+
throw new Error(`having.${key}.${colName} requires ${key}: { ${colName}: true }`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function compileHavingCompare(expr, spec, dialect, paramIndex) {
|
|
948
|
+
const conditions = [];
|
|
949
|
+
const params = [];
|
|
950
|
+
let nextParamIndex = paramIndex;
|
|
951
|
+
let impossible = false;
|
|
952
|
+
const ops = typeof spec === "number" ? { equals: spec } : spec;
|
|
953
|
+
for (const [op, value] of Object.entries(ops)) {
|
|
954
|
+
if (!isHavingOperator(op)) {
|
|
955
|
+
throw new Error(`unsupported having operator: ${op}`);
|
|
956
|
+
}
|
|
957
|
+
switch (op) {
|
|
958
|
+
case "in":
|
|
959
|
+
case "notIn": {
|
|
960
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
961
|
+
if (op === "in") {
|
|
962
|
+
conditions.push("1=0");
|
|
963
|
+
impossible = true;
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
conditions.push("1=1");
|
|
967
|
+
}
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
conditions.push(dialect.whereOperators[op](expr, nextParamIndex));
|
|
971
|
+
params.push(value);
|
|
972
|
+
nextParamIndex++;
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
case "equals":
|
|
976
|
+
case "gt":
|
|
977
|
+
case "gte":
|
|
978
|
+
case "lt":
|
|
979
|
+
case "lte": {
|
|
980
|
+
conditions.push(dialect.whereOperators[op](expr, nextParamIndex));
|
|
981
|
+
params.push(value);
|
|
982
|
+
nextParamIndex++;
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
default: {
|
|
986
|
+
const _never = op;
|
|
987
|
+
throw new Error(`unsupported having operator: ${_never}`);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
sql: conditions.join(" AND "),
|
|
993
|
+
params,
|
|
994
|
+
nextParamIndex,
|
|
995
|
+
...(impossible ? { impossible: true } : {}),
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
function requireFieldAggExpression(key, table, colName, dialect, manifestIndex) {
|
|
999
|
+
const expr = fieldAggExpression(key, table, colName, dialect, manifestIndex);
|
|
1000
|
+
if (!expr) {
|
|
1001
|
+
throw new Error(`Unknown aggregate column: ${colName}`);
|
|
1002
|
+
}
|
|
1003
|
+
return expr;
|
|
1004
|
+
}
|
|
1005
|
+
export function compileHaving(table, selectors, having, dialect, startParamIndex = 1, manifestIndex) {
|
|
1006
|
+
if (!having || Object.keys(having).length === 0) {
|
|
1007
|
+
return { sql: "", params: [] };
|
|
1008
|
+
}
|
|
1009
|
+
for (const key of Object.keys(having)) {
|
|
1010
|
+
if (key !== "_count" &&
|
|
1011
|
+
key !== "_avg" &&
|
|
1012
|
+
key !== "_sum" &&
|
|
1013
|
+
key !== "_min" &&
|
|
1014
|
+
key !== "_max") {
|
|
1015
|
+
throw new Error(`unsupported having key: ${key}`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
const conditions = [];
|
|
1019
|
+
const params = [];
|
|
1020
|
+
let paramIndex = startParamIndex;
|
|
1021
|
+
let impossible = false;
|
|
1022
|
+
const pushCompare = (expr, spec) => {
|
|
1023
|
+
const compiled = compileHavingCompare(expr, spec, dialect, paramIndex);
|
|
1024
|
+
if (compiled.sql)
|
|
1025
|
+
conditions.push(compiled.sql);
|
|
1026
|
+
params.push(...compiled.params);
|
|
1027
|
+
paramIndex = compiled.nextParamIndex;
|
|
1028
|
+
if (compiled.impossible)
|
|
1029
|
+
impossible = true;
|
|
1030
|
+
};
|
|
1031
|
+
if (having._count !== undefined) {
|
|
1032
|
+
const spec = having._count;
|
|
1033
|
+
if (typeof spec === "number") {
|
|
1034
|
+
requireStarCount(selectors, "having._count");
|
|
1035
|
+
pushCompare(countStarExpr(), spec);
|
|
1036
|
+
}
|
|
1037
|
+
else if (typeof spec === "object" && spec !== null) {
|
|
1038
|
+
if (isMixedCountHaving(spec)) {
|
|
1039
|
+
throw new Error("having._count cannot mix comparison operators with field keys");
|
|
1040
|
+
}
|
|
1041
|
+
if (isStarHavingSpec(spec)) {
|
|
1042
|
+
requireStarCount(selectors, "having._count");
|
|
1043
|
+
pushCompare(countStarExpr(), spec);
|
|
1044
|
+
}
|
|
1045
|
+
else {
|
|
1046
|
+
for (const [field, fieldSpec] of Object.entries(spec)) {
|
|
1047
|
+
if (typeof fieldSpec === "number") {
|
|
1048
|
+
requireCountMapField(selectors, field, `having._count.${field}`);
|
|
1049
|
+
pushCompare(countFieldExpr(table, field, manifestIndex), fieldSpec);
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
if (typeof fieldSpec !== "object" || fieldSpec === null) {
|
|
1053
|
+
throw new Error(`invalid having._count.${field} predicate`);
|
|
1054
|
+
}
|
|
1055
|
+
requireCountMapField(selectors, field, `having._count.${field}`);
|
|
1056
|
+
pushCompare(countFieldExpr(table, field, manifestIndex), fieldSpec);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
for (const key of FIELD_AGG_KEYS) {
|
|
1062
|
+
const fieldMap = having[key];
|
|
1063
|
+
if (!fieldMap)
|
|
1064
|
+
continue;
|
|
1065
|
+
for (const [colName, spec] of Object.entries(fieldMap)) {
|
|
1066
|
+
requireSelectedFieldAgg(selectors, key, colName);
|
|
1067
|
+
if (typeof spec !== "number" &&
|
|
1068
|
+
(typeof spec !== "object" || spec === null)) {
|
|
1069
|
+
throw new Error(`invalid having.${key}.${colName} predicate`);
|
|
1070
|
+
}
|
|
1071
|
+
const expr = requireFieldAggExpression(key, table, colName, dialect, manifestIndex);
|
|
1072
|
+
pushCompare(expr, spec);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (conditions.length === 0)
|
|
1076
|
+
return { sql: "", params: [] };
|
|
1077
|
+
return {
|
|
1078
|
+
sql: `HAVING ${conditions.join(" AND ")}`,
|
|
1079
|
+
params,
|
|
1080
|
+
...(impossible ? { impossible: true } : {}),
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
export function compileGroupByOrderBy(table, byKeys, selectors, orderBy, dialect = postgresDialect, manifestIndex) {
|
|
1084
|
+
if (!orderBy || Object.keys(orderBy).length === 0)
|
|
1085
|
+
return "";
|
|
1086
|
+
const tableIndex = getTableIndex(manifestIndex, table.accessor);
|
|
1087
|
+
const bySet = new Set(byKeys);
|
|
1088
|
+
const parts = [];
|
|
1089
|
+
for (const [tsKey, direction] of Object.entries(orderBy)) {
|
|
1090
|
+
if (tsKey === "_count") {
|
|
1091
|
+
if (typeof direction === "string") {
|
|
1092
|
+
requireStarCount(selectors, "orderBy._count");
|
|
1093
|
+
const dir = direction.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
1094
|
+
parts.push(`${countStarExpr()} ${dir}`);
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (typeof direction !== "object" || direction === null) {
|
|
1098
|
+
throw new Error('orderBy._count must be "asc" or "desc" or a field map');
|
|
1099
|
+
}
|
|
1100
|
+
for (const [field, colDir] of Object.entries(direction)) {
|
|
1101
|
+
if (typeof colDir !== "string")
|
|
1102
|
+
continue;
|
|
1103
|
+
requireCountMapField(selectors, field, `orderBy._count.${field}`);
|
|
1104
|
+
const dir = colDir.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
1105
|
+
parts.push(`${countFieldExpr(table, field, manifestIndex)} ${dir}`);
|
|
1106
|
+
}
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
if (tsKey === "_avg" ||
|
|
1110
|
+
tsKey === "_sum" ||
|
|
1111
|
+
tsKey === "_min" ||
|
|
1112
|
+
tsKey === "_max") {
|
|
1113
|
+
const key = tsKey;
|
|
1114
|
+
if (typeof direction !== "object" || direction === null) {
|
|
1115
|
+
throw new Error(`orderBy.${key} must be a column map`);
|
|
1116
|
+
}
|
|
1117
|
+
for (const [colName, colDir] of Object.entries(direction)) {
|
|
1118
|
+
if (typeof colDir !== "string")
|
|
1119
|
+
continue;
|
|
1120
|
+
if (!selectors[key]?.[colName]) {
|
|
1121
|
+
throw new Error(`orderBy.${key}.${colName} requires ${key}: { ${colName}: true }`);
|
|
1122
|
+
}
|
|
1123
|
+
const expr = requireFieldAggExpression(key, table, colName, dialect, manifestIndex);
|
|
1124
|
+
const dir = colDir.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
1125
|
+
parts.push(`${expr} ${dir}`);
|
|
1126
|
+
}
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (typeof direction !== "string")
|
|
1130
|
+
continue;
|
|
1131
|
+
if (!bySet.has(tsKey)) {
|
|
1132
|
+
throw new Error(`orderBy column "${tsKey}" is not in groupBy by`);
|
|
1133
|
+
}
|
|
1134
|
+
const col = columnByTsName(tableIndex, table, tsKey);
|
|
1135
|
+
if (!col) {
|
|
1136
|
+
throw new Error(`Unknown groupBy column: ${tsKey}`);
|
|
1137
|
+
}
|
|
1138
|
+
const dir = direction.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
1139
|
+
parts.push(`${quoteIdentifier(col.sqlName)} ${dir}`);
|
|
1140
|
+
}
|
|
1141
|
+
return parts.length > 0 ? `ORDER BY ${parts.join(", ")}` : "";
|
|
1142
|
+
}
|
|
1143
|
+
export function resolveGroupByColumns(table, byKeys, manifestIndex) {
|
|
1144
|
+
if (byKeys.length === 0) {
|
|
1145
|
+
throw new Error("groupBy requires at least one column");
|
|
1146
|
+
}
|
|
1147
|
+
const tableIndex = getTableIndex(manifestIndex, table.accessor);
|
|
1148
|
+
const cols = [];
|
|
1149
|
+
for (const key of byKeys) {
|
|
1150
|
+
const col = columnByTsName(tableIndex, table, key);
|
|
1151
|
+
if (!col) {
|
|
1152
|
+
throw new Error(`Unknown groupBy column: ${key}`);
|
|
1153
|
+
}
|
|
1154
|
+
cols.push(col);
|
|
1155
|
+
}
|
|
1156
|
+
return cols;
|
|
1157
|
+
}
|
|
1158
|
+
export function buildGroupByQuery(table, byKeys, selectors, whereSql, havingSql, orderSql, take, skip, manifestIndex, dialect = postgresDialect) {
|
|
1159
|
+
const byCols = resolveGroupByColumns(table, byKeys, manifestIndex);
|
|
1160
|
+
const selectBy = buildSelectColumns(table, byKeys, manifestIndex);
|
|
1161
|
+
const aggParts = aggregateSelectParts(table, selectors, dialect, manifestIndex);
|
|
1162
|
+
const selectList = aggParts.length > 0 ? `${selectBy}, ${aggParts.join(", ")}` : selectBy;
|
|
1163
|
+
const groupList = byCols
|
|
1164
|
+
.map((col) => quoteIdentifier(col.sqlName))
|
|
1165
|
+
.join(", ");
|
|
1166
|
+
let sql = `SELECT ${selectList} FROM ${tableRef(table)}`;
|
|
1167
|
+
if (whereSql)
|
|
1168
|
+
sql += ` ${whereSql}`;
|
|
1169
|
+
sql += ` GROUP BY ${groupList}`;
|
|
1170
|
+
if (havingSql)
|
|
1171
|
+
sql += ` ${havingSql}`;
|
|
1172
|
+
if (orderSql)
|
|
1173
|
+
sql += ` ${orderSql}`;
|
|
1174
|
+
if (take !== undefined) {
|
|
1175
|
+
sql += ` LIMIT ${normalizeLimitOffset(take, "take")}`;
|
|
1176
|
+
}
|
|
1177
|
+
if (skip !== undefined) {
|
|
1178
|
+
sql += ` OFFSET ${normalizeLimitOffset(skip, "skip")}`;
|
|
1179
|
+
}
|
|
1180
|
+
return sql;
|
|
1181
|
+
}
|
|
1182
|
+
export function getCachedGroupByQuery(tableIndex, table, byKeys, selectors, whereSql, havingSql, orderSql, take, skip, manifestIndex, dialect = postgresDialect) {
|
|
1183
|
+
const cacheKey = `${dialect.name}|${byKeys.join(",")}|${aggregateSelectorCacheKey(selectors)}|${whereSql}|${havingSql}|${orderSql}|${take ?? ""}|${skip ?? ""}`;
|
|
1184
|
+
if (!tableIndex) {
|
|
1185
|
+
return buildGroupByQuery(table, byKeys, selectors, whereSql, havingSql, orderSql, take, skip, manifestIndex, dialect);
|
|
1186
|
+
}
|
|
1187
|
+
return getOrSetSqlCache(tableIndex.groupBySqlBySignature, cacheKey, () => buildGroupByQuery(table, byKeys, selectors, whereSql, havingSql, orderSql, take, skip, manifestIndex, dialect));
|
|
1188
|
+
}
|
|
1189
|
+
export function buildUpsertQuery(table, insertKeys, updateKeys, conflictSqlColumns, exprSets = [], manifestIndex, dialect = postgresDialect, updateOps) {
|
|
645
1190
|
const insertCols = insertKeys.map((k) => {
|
|
646
1191
|
const col = colByTs(table, k, manifestIndex);
|
|
647
1192
|
return quoteIdentifier(col?.sqlName ?? k);
|
|
@@ -656,11 +1201,18 @@ export function buildUpsertQuery(table, insertKeys, updateKeys, conflictSqlColum
|
|
|
656
1201
|
const conflictCols = conflictSqlColumns
|
|
657
1202
|
.map((c) => quoteIdentifier(c))
|
|
658
1203
|
.join(", ");
|
|
1204
|
+
let nextParam = insertKeys.length + 1;
|
|
659
1205
|
const updateSets = updateKeys.length > 0
|
|
660
|
-
? updateKeys.map((k) => {
|
|
1206
|
+
? updateKeys.map((k, i) => {
|
|
661
1207
|
const col = colByTs(table, k, manifestIndex);
|
|
662
1208
|
const sqlCol = quoteIdentifier(col?.sqlName ?? k);
|
|
663
|
-
|
|
1209
|
+
const op = updateOps?.[i] ?? "set";
|
|
1210
|
+
if (op === "set") {
|
|
1211
|
+
return `${sqlCol} = excluded.${sqlCol}`;
|
|
1212
|
+
}
|
|
1213
|
+
const expr = buildSetExpression(col, nextParam, op, dialect);
|
|
1214
|
+
nextParam++;
|
|
1215
|
+
return expr;
|
|
664
1216
|
})
|
|
665
1217
|
: exprSets.length === 0
|
|
666
1218
|
? conflictSqlColumns.map((c) => {
|
|
@@ -728,7 +1280,7 @@ export function buildInsertQuery(table, dataKeys, manifestIndex, returning = "pk
|
|
|
728
1280
|
return buildValuePlaceholder(col, i + 1);
|
|
729
1281
|
})
|
|
730
1282
|
.join(", ");
|
|
731
|
-
|
|
1283
|
+
const sql = `INSERT INTO ${tableRef(table)} (${cols.join(", ")}) VALUES (${placeholders})`;
|
|
732
1284
|
if (returning === "none")
|
|
733
1285
|
return sql;
|
|
734
1286
|
const effectiveReturning = resolveReturning(table, returning);
|
|
@@ -774,7 +1326,7 @@ export function buildInsertManyValueRows(table, dataKeys, rows, manifestIndex) {
|
|
|
774
1326
|
}
|
|
775
1327
|
return { valueRows, values };
|
|
776
1328
|
}
|
|
777
|
-
export function buildInsertManyQuery(table, dataKeys, valueRows, manifestIndex) {
|
|
1329
|
+
export function buildInsertManyQuery(table, dataKeys, valueRows, manifestIndex, skipDuplicates = false, dialect = postgresDialect) {
|
|
778
1330
|
if (dataKeys.length === 0) {
|
|
779
1331
|
throw new Error("Cannot build INSERT many query with no columns");
|
|
780
1332
|
}
|
|
@@ -783,16 +1335,18 @@ export function buildInsertManyQuery(table, dataKeys, valueRows, manifestIndex)
|
|
|
783
1335
|
return quoteIdentifier(col?.sqlName ?? k);
|
|
784
1336
|
});
|
|
785
1337
|
const selectCols = buildSelectColumns(table, undefined, manifestIndex);
|
|
786
|
-
|
|
1338
|
+
const conflict = skipDuplicates ? ` ${dialect.onConflictDoNothing()}` : "";
|
|
1339
|
+
return `INSERT INTO ${tableRef(table)} (${cols.join(", ")}) VALUES ${valueRows.join(", ")}${conflict} RETURNING ${selectCols}`;
|
|
787
1340
|
}
|
|
788
|
-
export function buildUpdateQuery(table, dataKeys, whereSql, exprSets = [], manifestIndex, returning = "full") {
|
|
789
|
-
const
|
|
790
|
-
const paramSets =
|
|
1341
|
+
export function buildUpdateQuery(table, dataKeys, whereSql, exprSets = [], manifestIndex, returning = "full", dialect = postgresDialect, ops) {
|
|
1342
|
+
const ordered = orderUpdateAssignments(dataKeys, ops);
|
|
1343
|
+
const paramSets = ordered.keys.map((k, i) => {
|
|
791
1344
|
const col = colByTs(table, k, manifestIndex);
|
|
792
|
-
|
|
1345
|
+
const op = ordered.ops[i] ?? "set";
|
|
1346
|
+
return buildSetExpression(col, i + 1, op, dialect);
|
|
793
1347
|
});
|
|
794
1348
|
const sets = [...paramSets, ...exprSets];
|
|
795
|
-
const whereOffset =
|
|
1349
|
+
const whereOffset = ordered.keys.length;
|
|
796
1350
|
let sql = `UPDATE ${tableRef(table)} SET ${sets.join(", ")}`;
|
|
797
1351
|
if (whereSql) {
|
|
798
1352
|
const adjustedWhere = rebaseParamRefs(whereSql, whereOffset);
|
|
@@ -832,14 +1386,15 @@ export function buildDeleteManyQuery(table, whereSql) {
|
|
|
832
1386
|
sql += ` ${whereSql}`;
|
|
833
1387
|
return sql;
|
|
834
1388
|
}
|
|
835
|
-
export function buildUpdateManyQuery(table, dataKeys, whereSql, exprSets = [], manifestIndex) {
|
|
836
|
-
const
|
|
837
|
-
const paramSets =
|
|
1389
|
+
export function buildUpdateManyQuery(table, dataKeys, whereSql, exprSets = [], manifestIndex, dialect = postgresDialect, ops) {
|
|
1390
|
+
const ordered = orderUpdateAssignments(dataKeys, ops);
|
|
1391
|
+
const paramSets = ordered.keys.map((k, i) => {
|
|
838
1392
|
const col = colByTs(table, k, manifestIndex);
|
|
839
|
-
|
|
1393
|
+
const op = ordered.ops[i] ?? "set";
|
|
1394
|
+
return buildSetExpression(col, i + 1, op, dialect);
|
|
840
1395
|
});
|
|
841
1396
|
const sets = [...paramSets, ...exprSets];
|
|
842
|
-
const whereOffset =
|
|
1397
|
+
const whereOffset = ordered.keys.length;
|
|
843
1398
|
let sql = `UPDATE ${tableRef(table)} SET ${sets.join(", ")}`;
|
|
844
1399
|
if (whereSql) {
|
|
845
1400
|
const adjustedWhere = rebaseParamRefs(whereSql, whereOffset);
|
|
@@ -847,13 +1402,16 @@ export function buildUpdateManyQuery(table, dataKeys, whereSql, exprSets = [], m
|
|
|
847
1402
|
}
|
|
848
1403
|
return sql;
|
|
849
1404
|
}
|
|
850
|
-
export function getCachedUpdateManyQuery(tableIndex, table, dataKeys, whereSql, exprSets, manifestIndex) {
|
|
851
|
-
const
|
|
852
|
-
const
|
|
1405
|
+
export function getCachedUpdateManyQuery(tableIndex, table, dataKeys, whereSql, exprSets, manifestIndex, dialect = postgresDialect, ops) {
|
|
1406
|
+
const ordered = orderUpdateAssignments(dataKeys, ops);
|
|
1407
|
+
const opKey = ordered.keys
|
|
1408
|
+
.map((key, i) => `${key}:${ordered.ops[i] ?? "set"}`)
|
|
1409
|
+
.join(",");
|
|
1410
|
+
const cacheKey = `${dialect.name}|${opKey}|${exprSets.length}|${whereSql}`;
|
|
853
1411
|
if (!tableIndex) {
|
|
854
|
-
return buildUpdateManyQuery(table,
|
|
1412
|
+
return buildUpdateManyQuery(table, ordered.keys, whereSql, exprSets, manifestIndex, dialect, ordered.ops);
|
|
855
1413
|
}
|
|
856
|
-
return getOrSetSqlCache(tableIndex.updateManySqlByKeys, cacheKey, () => buildUpdateManyQuery(table,
|
|
1414
|
+
return getOrSetSqlCache(tableIndex.updateManySqlByKeys, cacheKey, () => buildUpdateManyQuery(table, ordered.keys, whereSql, exprSets, manifestIndex, dialect, ordered.ops));
|
|
857
1415
|
}
|
|
858
1416
|
export function getCachedFindManyQuery(tableIndex, signature, build) {
|
|
859
1417
|
if (!tableIndex)
|
|
@@ -877,6 +1435,46 @@ export function dataToSqlValues(table, data, options, manifestIndex, dialect = p
|
|
|
877
1435
|
}
|
|
878
1436
|
return reorderKeyValues(keys, values);
|
|
879
1437
|
}
|
|
1438
|
+
export function dataToUpdateAssignments(table, data, options, manifestIndex, dialect = postgresDialect) {
|
|
1439
|
+
const tableIndex = getTableIndex(manifestIndex, table.accessor);
|
|
1440
|
+
const keys = [];
|
|
1441
|
+
const ops = [];
|
|
1442
|
+
const values = [];
|
|
1443
|
+
for (const [key, raw] of Object.entries(data)) {
|
|
1444
|
+
const col = columnByTsName(tableIndex, table, key);
|
|
1445
|
+
if (!col)
|
|
1446
|
+
continue;
|
|
1447
|
+
if (options?.excludePrimary && col.primary)
|
|
1448
|
+
continue;
|
|
1449
|
+
if (raw === undefined)
|
|
1450
|
+
continue;
|
|
1451
|
+
const parsed = parseAtomicUpdate(col, raw);
|
|
1452
|
+
keys.push(key);
|
|
1453
|
+
ops.push(parsed.op);
|
|
1454
|
+
values.push(serializeColumnValue(col, parsed.value, dialect));
|
|
1455
|
+
}
|
|
1456
|
+
if (keys.length <= 1)
|
|
1457
|
+
return { keys, ops, values };
|
|
1458
|
+
const pairs = keys.map((key, index) => ({
|
|
1459
|
+
key,
|
|
1460
|
+
op: ops[index] ?? "set",
|
|
1461
|
+
value: values[index],
|
|
1462
|
+
}));
|
|
1463
|
+
pairs.sort((a, b) => a.key.localeCompare(b.key));
|
|
1464
|
+
return {
|
|
1465
|
+
keys: pairs.map((pair) => pair.key),
|
|
1466
|
+
ops: pairs.map((pair) => pair.op),
|
|
1467
|
+
values: pairs.map((pair) => pair.value),
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
export function upsertAtomicValues(ops, values) {
|
|
1471
|
+
const extra = [];
|
|
1472
|
+
for (let i = 0; i < ops.length; i++) {
|
|
1473
|
+
if (ops[i] !== "set")
|
|
1474
|
+
extra.push(values[i]);
|
|
1475
|
+
}
|
|
1476
|
+
return extra;
|
|
1477
|
+
}
|
|
880
1478
|
export function rowToTs(table, row) {
|
|
881
1479
|
const result = {};
|
|
882
1480
|
for (const col of table.columns) {
|