neoorm 0.4.6 → 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/driver.d.ts.map +1 -1
- package/dist/runtime/driver.js +32 -14
- package/dist/runtime/driver.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 +55 -10
- package/dist/runtime/query/compile.d.ts.map +1 -1
- package/dist/runtime/query/compile.js +683 -71
- 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 +18 -5
- 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)}`;
|
|
578
744
|
if (whereSql)
|
|
579
745
|
sql += ` ${whereSql}`;
|
|
580
746
|
return sql;
|
|
581
747
|
}
|
|
748
|
+
export function buildExistsQuery(table, whereSql) {
|
|
749
|
+
let sql = `SELECT 1 FROM ${tableRef(table)}`;
|
|
750
|
+
if (whereSql)
|
|
751
|
+
sql += ` ${whereSql}`;
|
|
752
|
+
sql += " LIMIT 1";
|
|
753
|
+
return sql;
|
|
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) => {
|
|
@@ -702,6 +1254,17 @@ SELECT ${selectCols}, false AS "${FIND_OR_CREATE_FLAG}" FROM ${tableSql} t
|
|
|
702
1254
|
WHERE NOT EXISTS (SELECT 1 FROM ins)${fallbackClause}
|
|
703
1255
|
LIMIT 1`;
|
|
704
1256
|
}
|
|
1257
|
+
/**
|
|
1258
|
+
* "pk" returning is impossible for tables without a primary key: the column
|
|
1259
|
+
* list would be empty, producing a dangling `RETURNING` clause that databases
|
|
1260
|
+
* reject. Fall back to returning the full row instead.
|
|
1261
|
+
*/
|
|
1262
|
+
export function resolveReturning(table, returning) {
|
|
1263
|
+
if (returning === "pk" && table.primaryKey.length === 0) {
|
|
1264
|
+
return "full";
|
|
1265
|
+
}
|
|
1266
|
+
return returning;
|
|
1267
|
+
}
|
|
705
1268
|
export function buildInsertQuery(table, dataKeys, manifestIndex, returning = "pk") {
|
|
706
1269
|
if (dataKeys.length === 0) {
|
|
707
1270
|
throw new Error("Cannot build INSERT query with no columns");
|
|
@@ -717,10 +1280,11 @@ export function buildInsertQuery(table, dataKeys, manifestIndex, returning = "pk
|
|
|
717
1280
|
return buildValuePlaceholder(col, i + 1);
|
|
718
1281
|
})
|
|
719
1282
|
.join(", ");
|
|
720
|
-
|
|
1283
|
+
const sql = `INSERT INTO ${tableRef(table)} (${cols.join(", ")}) VALUES (${placeholders})`;
|
|
721
1284
|
if (returning === "none")
|
|
722
1285
|
return sql;
|
|
723
|
-
const
|
|
1286
|
+
const effectiveReturning = resolveReturning(table, returning);
|
|
1287
|
+
const returningCols = effectiveReturning === "full"
|
|
724
1288
|
? buildSelectColumns(table, undefined, manifestIndex)
|
|
725
1289
|
: buildReturningPkColumns(table, manifestIndex);
|
|
726
1290
|
return `${sql} RETURNING ${returningCols}`;
|
|
@@ -762,7 +1326,7 @@ export function buildInsertManyValueRows(table, dataKeys, rows, manifestIndex) {
|
|
|
762
1326
|
}
|
|
763
1327
|
return { valueRows, values };
|
|
764
1328
|
}
|
|
765
|
-
export function buildInsertManyQuery(table, dataKeys, valueRows, manifestIndex) {
|
|
1329
|
+
export function buildInsertManyQuery(table, dataKeys, valueRows, manifestIndex, skipDuplicates = false, dialect = postgresDialect) {
|
|
766
1330
|
if (dataKeys.length === 0) {
|
|
767
1331
|
throw new Error("Cannot build INSERT many query with no columns");
|
|
768
1332
|
}
|
|
@@ -771,16 +1335,18 @@ export function buildInsertManyQuery(table, dataKeys, valueRows, manifestIndex)
|
|
|
771
1335
|
return quoteIdentifier(col?.sqlName ?? k);
|
|
772
1336
|
});
|
|
773
1337
|
const selectCols = buildSelectColumns(table, undefined, manifestIndex);
|
|
774
|
-
|
|
1338
|
+
const conflict = skipDuplicates ? ` ${dialect.onConflictDoNothing()}` : "";
|
|
1339
|
+
return `INSERT INTO ${tableRef(table)} (${cols.join(", ")}) VALUES ${valueRows.join(", ")}${conflict} RETURNING ${selectCols}`;
|
|
775
1340
|
}
|
|
776
|
-
export function buildUpdateQuery(table, dataKeys, whereSql, exprSets = [], manifestIndex, returning = "full") {
|
|
777
|
-
const
|
|
778
|
-
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) => {
|
|
779
1344
|
const col = colByTs(table, k, manifestIndex);
|
|
780
|
-
|
|
1345
|
+
const op = ordered.ops[i] ?? "set";
|
|
1346
|
+
return buildSetExpression(col, i + 1, op, dialect);
|
|
781
1347
|
});
|
|
782
1348
|
const sets = [...paramSets, ...exprSets];
|
|
783
|
-
const whereOffset =
|
|
1349
|
+
const whereOffset = ordered.keys.length;
|
|
784
1350
|
let sql = `UPDATE ${tableRef(table)} SET ${sets.join(", ")}`;
|
|
785
1351
|
if (whereSql) {
|
|
786
1352
|
const adjustedWhere = rebaseParamRefs(whereSql, whereOffset);
|
|
@@ -788,7 +1354,8 @@ export function buildUpdateQuery(table, dataKeys, whereSql, exprSets = [], manif
|
|
|
788
1354
|
}
|
|
789
1355
|
if (returning === "none")
|
|
790
1356
|
return sql;
|
|
791
|
-
const
|
|
1357
|
+
const effectiveReturning = resolveReturning(table, returning);
|
|
1358
|
+
const returningCols = effectiveReturning === "full"
|
|
792
1359
|
? buildSelectColumns(table, undefined, manifestIndex)
|
|
793
1360
|
: buildReturningPkColumns(table, manifestIndex);
|
|
794
1361
|
return `${sql} RETURNING ${returningCols}`;
|
|
@@ -803,7 +1370,8 @@ export function buildReturningPkColumns(table, manifestIndex) {
|
|
|
803
1370
|
.join(", ");
|
|
804
1371
|
}
|
|
805
1372
|
export function buildDeleteQuery(table, whereSql, returning, manifestIndex) {
|
|
806
|
-
const
|
|
1373
|
+
const effectiveReturning = resolveReturning(table, returning);
|
|
1374
|
+
const selectCols = effectiveReturning === "full"
|
|
807
1375
|
? buildSelectColumns(table, undefined, manifestIndex)
|
|
808
1376
|
: buildReturningPkColumns(table, manifestIndex);
|
|
809
1377
|
let sql = `DELETE FROM ${tableRef(table)}`;
|
|
@@ -818,14 +1386,15 @@ export function buildDeleteManyQuery(table, whereSql) {
|
|
|
818
1386
|
sql += ` ${whereSql}`;
|
|
819
1387
|
return sql;
|
|
820
1388
|
}
|
|
821
|
-
export function buildUpdateManyQuery(table, dataKeys, whereSql, exprSets = [], manifestIndex) {
|
|
822
|
-
const
|
|
823
|
-
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) => {
|
|
824
1392
|
const col = colByTs(table, k, manifestIndex);
|
|
825
|
-
|
|
1393
|
+
const op = ordered.ops[i] ?? "set";
|
|
1394
|
+
return buildSetExpression(col, i + 1, op, dialect);
|
|
826
1395
|
});
|
|
827
1396
|
const sets = [...paramSets, ...exprSets];
|
|
828
|
-
const whereOffset =
|
|
1397
|
+
const whereOffset = ordered.keys.length;
|
|
829
1398
|
let sql = `UPDATE ${tableRef(table)} SET ${sets.join(", ")}`;
|
|
830
1399
|
if (whereSql) {
|
|
831
1400
|
const adjustedWhere = rebaseParamRefs(whereSql, whereOffset);
|
|
@@ -833,13 +1402,16 @@ export function buildUpdateManyQuery(table, dataKeys, whereSql, exprSets = [], m
|
|
|
833
1402
|
}
|
|
834
1403
|
return sql;
|
|
835
1404
|
}
|
|
836
|
-
export function getCachedUpdateManyQuery(tableIndex, table, dataKeys, whereSql, exprSets, manifestIndex) {
|
|
837
|
-
const
|
|
838
|
-
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}`;
|
|
839
1411
|
if (!tableIndex) {
|
|
840
|
-
return buildUpdateManyQuery(table,
|
|
1412
|
+
return buildUpdateManyQuery(table, ordered.keys, whereSql, exprSets, manifestIndex, dialect, ordered.ops);
|
|
841
1413
|
}
|
|
842
|
-
return getOrSetSqlCache(tableIndex.updateManySqlByKeys, cacheKey, () => buildUpdateManyQuery(table,
|
|
1414
|
+
return getOrSetSqlCache(tableIndex.updateManySqlByKeys, cacheKey, () => buildUpdateManyQuery(table, ordered.keys, whereSql, exprSets, manifestIndex, dialect, ordered.ops));
|
|
843
1415
|
}
|
|
844
1416
|
export function getCachedFindManyQuery(tableIndex, signature, build) {
|
|
845
1417
|
if (!tableIndex)
|
|
@@ -863,6 +1435,46 @@ export function dataToSqlValues(table, data, options, manifestIndex, dialect = p
|
|
|
863
1435
|
}
|
|
864
1436
|
return reorderKeyValues(keys, values);
|
|
865
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
|
+
}
|
|
866
1478
|
export function rowToTs(table, row) {
|
|
867
1479
|
const result = {};
|
|
868
1480
|
for (const col of table.columns) {
|