@spinajs/orm 2.0.481 → 2.0.484
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 +72 -3
- package/lib/cjs/bootstrap.js +10 -0
- package/lib/cjs/bootstrap.js.map +1 -1
- package/lib/cjs/builders.d.ts +127 -11
- package/lib/cjs/builders.d.ts.map +1 -1
- package/lib/cjs/builders.js +368 -283
- package/lib/cjs/builders.js.map +1 -1
- package/lib/cjs/converters.d.ts +9 -2
- package/lib/cjs/converters.d.ts.map +1 -1
- package/lib/cjs/converters.js +48 -8
- package/lib/cjs/converters.js.map +1 -1
- package/lib/cjs/decorators.d.ts +26 -3
- package/lib/cjs/decorators.d.ts.map +1 -1
- package/lib/cjs/decorators.js +95 -42
- package/lib/cjs/decorators.js.map +1 -1
- package/lib/cjs/dehydrators.js +1 -1
- package/lib/cjs/dehydrators.js.map +1 -1
- package/lib/cjs/descriptor.d.ts +1 -0
- package/lib/cjs/descriptor.d.ts.map +1 -1
- package/lib/cjs/descriptor.js +16 -40
- package/lib/cjs/descriptor.js.map +1 -1
- package/lib/cjs/driver.d.ts +136 -7
- package/lib/cjs/driver.d.ts.map +1 -1
- package/lib/cjs/driver.js +279 -0
- package/lib/cjs/driver.js.map +1 -1
- package/lib/cjs/existsRelationHandlers.d.ts +29 -0
- package/lib/cjs/existsRelationHandlers.d.ts.map +1 -0
- package/lib/cjs/existsRelationHandlers.js +138 -0
- package/lib/cjs/existsRelationHandlers.js.map +1 -0
- package/lib/cjs/fp.d.ts.map +1 -1
- package/lib/cjs/fp.js +10 -15
- package/lib/cjs/fp.js.map +1 -1
- package/lib/cjs/hydrators.d.ts.map +1 -1
- package/lib/cjs/hydrators.js +29 -2
- package/lib/cjs/hydrators.js.map +1 -1
- package/lib/cjs/identity-map.d.ts +60 -0
- package/lib/cjs/identity-map.d.ts.map +1 -0
- package/lib/cjs/identity-map.js +135 -0
- package/lib/cjs/identity-map.js.map +1 -0
- package/lib/cjs/index.d.ts +12 -0
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +12 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/interfaces.d.ts +300 -10
- package/lib/cjs/interfaces.d.ts.map +1 -1
- package/lib/cjs/interfaces.js +54 -1
- package/lib/cjs/interfaces.js.map +1 -1
- package/lib/cjs/metadata.d.ts.map +1 -1
- package/lib/cjs/metadata.js +4 -1
- package/lib/cjs/metadata.js.map +1 -1
- package/lib/cjs/metrics.d.ts +53 -0
- package/lib/cjs/metrics.d.ts.map +1 -0
- package/lib/cjs/metrics.js +82 -0
- package/lib/cjs/metrics.js.map +1 -0
- package/lib/cjs/middlewares.d.ts +13 -1
- package/lib/cjs/middlewares.d.ts.map +1 -1
- package/lib/cjs/middlewares.js +55 -15
- package/lib/cjs/middlewares.js.map +1 -1
- package/lib/cjs/model.d.ts +129 -13
- package/lib/cjs/model.d.ts.map +1 -1
- package/lib/cjs/model.js +449 -93
- package/lib/cjs/model.js.map +1 -1
- package/lib/cjs/orm.d.ts.map +1 -1
- package/lib/cjs/orm.js +12 -3
- package/lib/cjs/orm.js.map +1 -1
- package/lib/cjs/orphan.d.ts +27 -0
- package/lib/cjs/orphan.d.ts.map +1 -0
- package/lib/cjs/orphan.js +55 -0
- package/lib/cjs/orphan.js.map +1 -0
- package/lib/cjs/primary-keys.d.ts +60 -0
- package/lib/cjs/primary-keys.d.ts.map +1 -0
- package/lib/cjs/primary-keys.js +243 -0
- package/lib/cjs/primary-keys.js.map +1 -0
- package/lib/cjs/relation-objects.d.ts +101 -9
- package/lib/cjs/relation-objects.d.ts.map +1 -1
- package/lib/cjs/relation-objects.js +194 -43
- package/lib/cjs/relation-objects.js.map +1 -1
- package/lib/cjs/relations.d.ts +7 -1
- package/lib/cjs/relations.d.ts.map +1 -1
- package/lib/cjs/relations.js +16 -1
- package/lib/cjs/relations.js.map +1 -1
- package/lib/cjs/resilience.d.ts +50 -0
- package/lib/cjs/resilience.d.ts.map +1 -0
- package/lib/cjs/resilience.js +70 -0
- package/lib/cjs/resilience.js.map +1 -0
- package/lib/cjs/snapshot.d.ts +58 -0
- package/lib/cjs/snapshot.d.ts.map +1 -0
- package/lib/cjs/snapshot.js +125 -0
- package/lib/cjs/snapshot.js.map +1 -0
- package/lib/cjs/statements.d.ts +11 -1
- package/lib/cjs/statements.d.ts.map +1 -1
- package/lib/cjs/statements.js +13 -2
- package/lib/cjs/statements.js.map +1 -1
- package/lib/cjs/subject-builder.d.ts +79 -0
- package/lib/cjs/subject-builder.d.ts.map +1 -0
- package/lib/cjs/subject-builder.js +296 -0
- package/lib/cjs/subject-builder.js.map +1 -0
- package/lib/cjs/subject-executor.d.ts +122 -0
- package/lib/cjs/subject-executor.d.ts.map +1 -0
- package/lib/cjs/subject-executor.js +319 -0
- package/lib/cjs/subject-executor.js.map +1 -0
- package/lib/cjs/subject-sorter.d.ts +70 -0
- package/lib/cjs/subject-sorter.d.ts.map +1 -0
- package/lib/cjs/subject-sorter.js +173 -0
- package/lib/cjs/subject-sorter.js.map +1 -0
- package/lib/cjs/subject.d.ts +100 -0
- package/lib/cjs/subject.d.ts.map +1 -0
- package/lib/cjs/subject.js +89 -0
- package/lib/cjs/subject.js.map +1 -0
- package/lib/cjs/unit-of-work.d.ts +52 -0
- package/lib/cjs/unit-of-work.d.ts.map +1 -0
- package/lib/cjs/unit-of-work.js +170 -0
- package/lib/cjs/unit-of-work.js.map +1 -0
- package/lib/mjs/bootstrap.js +10 -0
- package/lib/mjs/bootstrap.js.map +1 -1
- package/lib/mjs/builders.d.ts +127 -11
- package/lib/mjs/builders.d.ts.map +1 -1
- package/lib/mjs/builders.js +370 -285
- package/lib/mjs/builders.js.map +1 -1
- package/lib/mjs/converters.d.ts +9 -2
- package/lib/mjs/converters.d.ts.map +1 -1
- package/lib/mjs/converters.js +48 -8
- package/lib/mjs/converters.js.map +1 -1
- package/lib/mjs/decorators.d.ts +26 -3
- package/lib/mjs/decorators.d.ts.map +1 -1
- package/lib/mjs/decorators.js +96 -44
- package/lib/mjs/decorators.js.map +1 -1
- package/lib/mjs/dehydrators.js +1 -1
- package/lib/mjs/dehydrators.js.map +1 -1
- package/lib/mjs/descriptor.d.ts +1 -0
- package/lib/mjs/descriptor.d.ts.map +1 -1
- package/lib/mjs/descriptor.js +17 -39
- package/lib/mjs/descriptor.js.map +1 -1
- package/lib/mjs/driver.d.ts +136 -7
- package/lib/mjs/driver.d.ts.map +1 -1
- package/lib/mjs/driver.js +279 -0
- package/lib/mjs/driver.js.map +1 -1
- package/lib/mjs/existsRelationHandlers.d.ts +29 -0
- package/lib/mjs/existsRelationHandlers.d.ts.map +1 -0
- package/lib/mjs/existsRelationHandlers.js +134 -0
- package/lib/mjs/existsRelationHandlers.js.map +1 -0
- package/lib/mjs/fp.d.ts.map +1 -1
- package/lib/mjs/fp.js +10 -15
- package/lib/mjs/fp.js.map +1 -1
- package/lib/mjs/hydrators.d.ts.map +1 -1
- package/lib/mjs/hydrators.js +29 -2
- package/lib/mjs/hydrators.js.map +1 -1
- package/lib/mjs/identity-map.d.ts +60 -0
- package/lib/mjs/identity-map.d.ts.map +1 -0
- package/lib/mjs/identity-map.js +130 -0
- package/lib/mjs/identity-map.js.map +1 -0
- package/lib/mjs/index.d.ts +12 -0
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +12 -0
- package/lib/mjs/index.js.map +1 -1
- package/lib/mjs/interfaces.d.ts +300 -10
- package/lib/mjs/interfaces.d.ts.map +1 -1
- package/lib/mjs/interfaces.js +53 -0
- package/lib/mjs/interfaces.js.map +1 -1
- package/lib/mjs/metadata.d.ts.map +1 -1
- package/lib/mjs/metadata.js +4 -1
- package/lib/mjs/metadata.js.map +1 -1
- package/lib/mjs/metrics.d.ts +53 -0
- package/lib/mjs/metrics.d.ts.map +1 -0
- package/lib/mjs/metrics.js +76 -0
- package/lib/mjs/metrics.js.map +1 -0
- package/lib/mjs/middlewares.d.ts +13 -1
- package/lib/mjs/middlewares.d.ts.map +1 -1
- package/lib/mjs/middlewares.js +55 -15
- package/lib/mjs/middlewares.js.map +1 -1
- package/lib/mjs/model.d.ts +129 -13
- package/lib/mjs/model.d.ts.map +1 -1
- package/lib/mjs/model.js +451 -95
- package/lib/mjs/model.js.map +1 -1
- package/lib/mjs/orm.d.ts.map +1 -1
- package/lib/mjs/orm.js +12 -3
- package/lib/mjs/orm.js.map +1 -1
- package/lib/mjs/orphan.d.ts +27 -0
- package/lib/mjs/orphan.d.ts.map +1 -0
- package/lib/mjs/orphan.js +52 -0
- package/lib/mjs/orphan.js.map +1 -0
- package/lib/mjs/primary-keys.d.ts +60 -0
- package/lib/mjs/primary-keys.d.ts.map +1 -0
- package/lib/mjs/primary-keys.js +223 -0
- package/lib/mjs/primary-keys.js.map +1 -0
- package/lib/mjs/relation-objects.d.ts +101 -9
- package/lib/mjs/relation-objects.d.ts.map +1 -1
- package/lib/mjs/relation-objects.js +194 -43
- package/lib/mjs/relation-objects.js.map +1 -1
- package/lib/mjs/relations.d.ts +7 -1
- package/lib/mjs/relations.d.ts.map +1 -1
- package/lib/mjs/relations.js +16 -1
- package/lib/mjs/relations.js.map +1 -1
- package/lib/mjs/resilience.d.ts +50 -0
- package/lib/mjs/resilience.d.ts.map +1 -0
- package/lib/mjs/resilience.js +64 -0
- package/lib/mjs/resilience.js.map +1 -0
- package/lib/mjs/snapshot.d.ts +58 -0
- package/lib/mjs/snapshot.d.ts.map +1 -0
- package/lib/mjs/snapshot.js +115 -0
- package/lib/mjs/snapshot.js.map +1 -0
- package/lib/mjs/statements.d.ts +11 -1
- package/lib/mjs/statements.d.ts.map +1 -1
- package/lib/mjs/statements.js +14 -3
- package/lib/mjs/statements.js.map +1 -1
- package/lib/mjs/subject-builder.d.ts +79 -0
- package/lib/mjs/subject-builder.d.ts.map +1 -0
- package/lib/mjs/subject-builder.js +292 -0
- package/lib/mjs/subject-builder.js.map +1 -0
- package/lib/mjs/subject-executor.d.ts +122 -0
- package/lib/mjs/subject-executor.d.ts.map +1 -0
- package/lib/mjs/subject-executor.js +312 -0
- package/lib/mjs/subject-executor.js.map +1 -0
- package/lib/mjs/subject-sorter.d.ts +70 -0
- package/lib/mjs/subject-sorter.d.ts.map +1 -0
- package/lib/mjs/subject-sorter.js +168 -0
- package/lib/mjs/subject-sorter.js.map +1 -0
- package/lib/mjs/subject.d.ts +100 -0
- package/lib/mjs/subject.d.ts.map +1 -0
- package/lib/mjs/subject.js +84 -0
- package/lib/mjs/subject.js.map +1 -0
- package/lib/mjs/unit-of-work.d.ts +52 -0
- package/lib/mjs/unit-of-work.d.ts.map +1 -0
- package/lib/mjs/unit-of-work.js +166 -0
- package/lib/mjs/unit-of-work.js.map +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +10 -7
package/lib/mjs/builders.js
CHANGED
|
@@ -10,7 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
var RawQuery_1, WhereBuilder_1, SelectQueryBuilder_1;
|
|
11
11
|
/* eslint-disable prettier/prettier */
|
|
12
12
|
import { Container, Inject, NewInstance, DI, Injectable, isConstructor } from '@spinajs/di';
|
|
13
|
-
import { InvalidArgument, MethodNotImplemented, InvalidOperation } from '@spinajs/exceptions';
|
|
13
|
+
import { InvalidArgument, MethodNotImplemented, InvalidOperation, NotSupported } from '@spinajs/exceptions';
|
|
14
14
|
import { OrmException, OrmNotFoundException } from './exceptions.js';
|
|
15
15
|
import _ from 'lodash';
|
|
16
16
|
import { use } from 'typescript-mix';
|
|
@@ -18,9 +18,10 @@ import { ColumnMethods, ColumnType, QueryMethod, SortOrder, WhereBoolean, SqlOpe
|
|
|
18
18
|
import { DeleteQueryCompiler, InsertQueryCompiler, SelectQueryCompiler, TruncateTableQueryCompiler, TableQueryCompiler, AlterTableQueryCompiler, UpdateQueryCompiler, QueryContext, IndexQueryCompiler, RelationType, ReferentialAction, DefaultValueBuilder, ColumnAlterationType, TableExistsCompiler, DropViewCompiler, DropTableCompiler, TableCloneQueryCompiler, QueryMiddleware, DropEventQueryCompiler, EventQueryCompiler, RawSchemaQueryCompiler } from './interfaces.js';
|
|
19
19
|
import { BetweenStatement, ColumnMethodStatement, ColumnStatement, ExistsQueryStatement, InSetStatement, InStatement, RawQueryStatement, WhereQueryStatement, WhereStatement, ColumnRawStatement, JoinStatement, WithRecursiveStatement, GroupByStatement, Wrap, LazyQueryStatement } from './statements.js';
|
|
20
20
|
import { BelongsToRelation, OneToManyRelation, ManyToManyRelation, BelongsToRecursiveRelation, QueryRelation, VirtualRelation } from './relations.js';
|
|
21
|
-
import { Lazy } from '@spinajs/util';
|
|
21
|
+
import { Lazy, isNullOrWhitespace, _check_arg, _positive } from '@spinajs/util';
|
|
22
22
|
import { DiscriminationMapMiddleware } from './discrimination-middleware.js';
|
|
23
23
|
import { extractModelDescriptor } from './descriptor.js';
|
|
24
|
+
import { ExistsRelationHandler } from './existsRelationHandlers.js';
|
|
24
25
|
function isWhereOperator(val) {
|
|
25
26
|
return _.isString(val) && Object.values(SqlOperator).includes(val.toLowerCase());
|
|
26
27
|
}
|
|
@@ -37,6 +38,12 @@ let Builder = class Builder {
|
|
|
37
38
|
constructor(container, driver, model) {
|
|
38
39
|
this._middlewares = [];
|
|
39
40
|
this._queryMiddlewares = [];
|
|
41
|
+
/**
|
|
42
|
+
* Memoized result of {@link execute}. A builder executes at most once; awaiting
|
|
43
|
+
* it again resolves with the same value instead of re-running the query.
|
|
44
|
+
* Call `clone()` first if you genuinely need a second round-trip.
|
|
45
|
+
*/
|
|
46
|
+
this._executionPromise = null;
|
|
40
47
|
this._driver = driver;
|
|
41
48
|
this._container = container;
|
|
42
49
|
this._model = model;
|
|
@@ -44,82 +51,104 @@ let Builder = class Builder {
|
|
|
44
51
|
this._asRaw = false;
|
|
45
52
|
this._queryMiddlewares = DI.resolve(Array.ofType(QueryMiddleware));
|
|
46
53
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}, onrejected);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
onfulfilled?.(models);
|
|
54
|
+
/**
|
|
55
|
+
* The execution engine. Sends the compiled query to the driver, applies the
|
|
56
|
+
* result middlewares, hydrates models and awaits the post-hydration middlewares.
|
|
57
|
+
*
|
|
58
|
+
* Always *returns* its value — the caller's promise chain is what propagates it.
|
|
59
|
+
* Subclasses override this (not `execute()`) so that their extra work also lands
|
|
60
|
+
* inside the memo.
|
|
61
|
+
*/
|
|
62
|
+
async _run() {
|
|
63
|
+
// Fires for EVERY builder type, not just SELECT. This used to live on
|
|
64
|
+
// `SelectQueryBuilder._run`, so an INSERT/UPDATE/DELETE never reached a
|
|
65
|
+
// `beforeQueryExecution` hook at all — which silently disabled any middleware that
|
|
66
|
+
// needs the finished query (rbac's insert-ownership check is exactly that: the payload
|
|
67
|
+
// does not exist until `values()` has been called, long after construction).
|
|
68
|
+
//
|
|
69
|
+
// Awaited in sequence, not `forEach`ed: a hook that needs a database round-trip to
|
|
70
|
+
// decide — an rbac rule whose ownership lives in another table — could otherwise only
|
|
71
|
+
// return a promise into the void, and the query would execute anyway. For a security
|
|
72
|
+
// check that is the difference between enforcing and pretending to.
|
|
73
|
+
for (const middleware of this._queryMiddlewares) {
|
|
74
|
+
await middleware.beforeQueryExecution(this);
|
|
75
|
+
}
|
|
76
|
+
const result = (await this._driver.execute(this));
|
|
77
|
+
// Snapshot the pipeline once, *after* the driver call: compiling the query is what
|
|
78
|
+
// registers the relation middlewares (the driver calls `toDB()`), so this is the first
|
|
79
|
+
// point at which the list is complete. Everything below runs against this immutable copy,
|
|
80
|
+
// so a middleware registered later cannot change the pipeline mid-flight, and — crucially —
|
|
81
|
+
// we never call `Array.prototype.reverse()` on `this._middlewares`, which mutates in place
|
|
82
|
+
// and used to flip `modelCreation` resolution order on every execution ( B8 ).
|
|
83
|
+
const middlewares = [...this._middlewares];
|
|
84
|
+
const creationOrder = [...middlewares].reverse();
|
|
85
|
+
if (this._asRaw) {
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
const transformedResult = result;
|
|
89
|
+
// if we have something to transform ...
|
|
90
|
+
if (transformedResult) {
|
|
91
|
+
middlewares.forEach((m) => {
|
|
92
|
+
Object.assign(transformedResult, m.afterQuery(transformedResult));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (this._model && !this._nonSelect) {
|
|
96
|
+
// TODO: rething this casting
|
|
97
|
+
const models = transformedResult.map((r) => {
|
|
98
|
+
let model = null;
|
|
99
|
+
for (const middleware of creationOrder) {
|
|
100
|
+
model = middleware.modelCreation(r);
|
|
101
|
+
if (model !== null) {
|
|
102
|
+
break;
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
if (model === null) {
|
|
106
|
+
model = DI.resolve('__orm_model_factory__', [this._model]);
|
|
104
107
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
model.hydrate(r);
|
|
109
|
+
model.IsDirty = false;
|
|
110
|
+
// This is the one point at which the instance's columns hold exactly what the
|
|
111
|
+
// database returned, so it is the diff baseline for `save()`. Relation members
|
|
112
|
+
// are attached later by the afterHydration middlewares below, which record their
|
|
113
|
+
// own member keys into this same snapshot.
|
|
114
|
+
model.takeSnapshot();
|
|
115
|
+
// Nested relation data that arrived on the row itself ( belongsTo LEFT JOIN,
|
|
116
|
+
// and hasMany arrays passed straight to hydrate ) was attached by the hydrators
|
|
117
|
+
// inside `hydrate()` above — i.e. before the snapshot existed, so their own
|
|
118
|
+
// `snapshotRelation` calls no-opped. Record those relations now.
|
|
119
|
+
for (const name of (model.ModelDescriptor?.Relations ?? new Map()).keys()) {
|
|
120
|
+
if (model[name]?.Populated) {
|
|
121
|
+
model.snapshotRelation(name);
|
|
122
|
+
}
|
|
112
123
|
}
|
|
124
|
+
return model;
|
|
125
|
+
});
|
|
126
|
+
if (middlewares.length > 0) {
|
|
127
|
+
await Promise.all(middlewares.map((m) => m.afterHydration(models)));
|
|
113
128
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
return models;
|
|
130
|
+
}
|
|
131
|
+
return transformedResult;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Executes the query. The single entry point for execution — `then()` delegates here.
|
|
135
|
+
* The underlying work runs exactly once per builder instance; subsequent calls resolve
|
|
136
|
+
* with the memoized result.
|
|
137
|
+
*/
|
|
138
|
+
execute() {
|
|
139
|
+
if (!this._executionPromise) {
|
|
140
|
+
this._executionPromise = this._run();
|
|
141
|
+
}
|
|
142
|
+
return this._executionPromise;
|
|
143
|
+
}
|
|
144
|
+
then(onfulfilled, onrejected) {
|
|
145
|
+
return this.execute().then(onfulfilled, onrejected);
|
|
146
|
+
}
|
|
147
|
+
catch(onrejected) {
|
|
148
|
+
return this.execute().catch(onrejected);
|
|
149
|
+
}
|
|
150
|
+
finally(onfinally) {
|
|
151
|
+
return this.execute().finally(onfinally);
|
|
123
152
|
}
|
|
124
153
|
middleware(middleware) {
|
|
125
154
|
this._middlewares.push(middleware);
|
|
@@ -191,7 +220,7 @@ let QueryBuilder = class QueryBuilder extends Builder {
|
|
|
191
220
|
*
|
|
192
221
|
*/
|
|
193
222
|
setTable(table, alias) {
|
|
194
|
-
if (
|
|
223
|
+
if (isNullOrWhitespace(table)) {
|
|
195
224
|
throw new InvalidArgument('table name is empty');
|
|
196
225
|
}
|
|
197
226
|
this._table = table;
|
|
@@ -228,9 +257,7 @@ let LimitBuilder = class LimitBuilder {
|
|
|
228
257
|
};
|
|
229
258
|
}
|
|
230
259
|
take(count) {
|
|
231
|
-
|
|
232
|
-
throw new InvalidArgument(`take count cannot be negative number`);
|
|
233
|
-
}
|
|
260
|
+
_check_arg(_positive())(count, 'take count');
|
|
234
261
|
this._limit.limit = count;
|
|
235
262
|
return this;
|
|
236
263
|
}
|
|
@@ -256,7 +283,7 @@ let LimitBuilder = class LimitBuilder {
|
|
|
256
283
|
const result = (await this);
|
|
257
284
|
if (result === undefined || (Array.isArray(result) && result.length === 0)) {
|
|
258
285
|
if (typeof error === 'function') {
|
|
259
|
-
|
|
286
|
+
throw error(this.toDB());
|
|
260
287
|
}
|
|
261
288
|
else
|
|
262
289
|
throw error;
|
|
@@ -285,10 +312,7 @@ LimitBuilder = __decorate([
|
|
|
285
312
|
export { LimitBuilder };
|
|
286
313
|
let OrderByBuilder = class OrderByBuilder {
|
|
287
314
|
constructor() {
|
|
288
|
-
this.
|
|
289
|
-
column: '',
|
|
290
|
-
order: SortOrder.ASC,
|
|
291
|
-
};
|
|
315
|
+
this._sorts = [];
|
|
292
316
|
}
|
|
293
317
|
order(column, direction) {
|
|
294
318
|
if (!column) {
|
|
@@ -301,10 +325,10 @@ let OrderByBuilder = class OrderByBuilder {
|
|
|
301
325
|
});
|
|
302
326
|
return this;
|
|
303
327
|
}
|
|
304
|
-
this.
|
|
328
|
+
this._sorts.push({
|
|
305
329
|
column,
|
|
306
330
|
order: direction,
|
|
307
|
-
};
|
|
331
|
+
});
|
|
308
332
|
return this;
|
|
309
333
|
}
|
|
310
334
|
orderBy(column) {
|
|
@@ -315,10 +339,10 @@ let OrderByBuilder = class OrderByBuilder {
|
|
|
315
339
|
});
|
|
316
340
|
return this;
|
|
317
341
|
}
|
|
318
|
-
this.
|
|
342
|
+
this._sorts.push({
|
|
319
343
|
column,
|
|
320
344
|
order: SortOrder.ASC,
|
|
321
|
-
};
|
|
345
|
+
});
|
|
322
346
|
return this;
|
|
323
347
|
}
|
|
324
348
|
orderByDescending(column) {
|
|
@@ -329,14 +353,25 @@ let OrderByBuilder = class OrderByBuilder {
|
|
|
329
353
|
});
|
|
330
354
|
return this;
|
|
331
355
|
}
|
|
332
|
-
this.
|
|
356
|
+
this._sorts.push({
|
|
333
357
|
column,
|
|
334
358
|
order: SortOrder.DESC,
|
|
335
|
-
};
|
|
359
|
+
});
|
|
336
360
|
return this;
|
|
337
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Returns the FIRST sort entry (or null) for backward compat with dialect
|
|
364
|
+
* packages that emit a single ORDER BY column. Use getSorts() for all entries.
|
|
365
|
+
*/
|
|
338
366
|
getSort() {
|
|
339
|
-
|
|
367
|
+
const sort = this._sorts.find((s) => s.column.trim() !== '');
|
|
368
|
+
return sort ?? null;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Returns all sort entries (multi-column ORDER BY), skipping empty columns.
|
|
372
|
+
*/
|
|
373
|
+
getSorts() {
|
|
374
|
+
return this._sorts.filter((s) => s.column.trim() !== '');
|
|
340
375
|
}
|
|
341
376
|
};
|
|
342
377
|
OrderByBuilder = __decorate([
|
|
@@ -548,6 +583,23 @@ let WithRecursiveBuilder = class WithRecursiveBuilder {
|
|
|
548
583
|
this._cteStatement = this._container.resolve(WithRecursiveStatement, ['cte', this, rcKeyName, pkName]);
|
|
549
584
|
return this;
|
|
550
585
|
}
|
|
586
|
+
/**
|
|
587
|
+
* Drops the recursive CTE from this builder.
|
|
588
|
+
*
|
|
589
|
+
* `WithRecursiveStatement.build()` compiles two CLONES of the owning query — the anchor
|
|
590
|
+
* member and the recursive member of the CTE. `clone()` copies `_cteStatement`, so each
|
|
591
|
+
* clone was still marked recursive and compiling it re-entered `build()`, which cloned
|
|
592
|
+
* again: an unbounded mutual recursion between `toDB()` and the recursive compiler that
|
|
593
|
+
* ended in a stack overflow rather than a query. The two member queries are by definition
|
|
594
|
+
* not themselves recursive, so the statement clears the flag on its clones.
|
|
595
|
+
*
|
|
596
|
+
* Named alongside `clearJoins()` / `clearWhere()`, which `build()` already uses to strip the
|
|
597
|
+
* parts of the parent query each member must not inherit.
|
|
598
|
+
*/
|
|
599
|
+
clearRecursive() {
|
|
600
|
+
this._cteStatement = undefined;
|
|
601
|
+
return this;
|
|
602
|
+
}
|
|
551
603
|
};
|
|
552
604
|
WithRecursiveBuilder = __decorate([
|
|
553
605
|
NewInstance()
|
|
@@ -582,12 +634,31 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
|
|
|
582
634
|
clone(_parent) {
|
|
583
635
|
// TODO: fix this cast
|
|
584
636
|
const builder = new WhereBuilder_1(_parent);
|
|
585
|
-
builder._statements = this._statements.map((s) =>
|
|
637
|
+
builder._statements = this._statements.map((s) => {
|
|
638
|
+
const cloned = s.clone(builder);
|
|
639
|
+
// preserve the per-statement boolean connector (clone() rebuilds the
|
|
640
|
+
// statement from scratch and would otherwise reset it to the AND default)
|
|
641
|
+
cloned.Boolean = s.Boolean;
|
|
642
|
+
return cloned;
|
|
643
|
+
});
|
|
586
644
|
builder._boolean = this._boolean;
|
|
587
645
|
builder._model = this._model;
|
|
588
646
|
builder._tableAlias = this.TableAlias;
|
|
589
647
|
return builder;
|
|
590
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Pushes a statement onto this builder, stamping it with the currently pending
|
|
651
|
+
* boolean connector (set by {@link orWhere}/{@link andWhere}). The pending
|
|
652
|
+
* connector applies to the NEXT pushed statement only and resets to AND
|
|
653
|
+
* afterwards, so `where(a).where(b).orWhere(c).where(d)` compiles to
|
|
654
|
+
* `a AND b OR c AND d` rather than rewriting the whole clause.
|
|
655
|
+
*/
|
|
656
|
+
pushStatement(statement) {
|
|
657
|
+
statement.Boolean = this._boolean;
|
|
658
|
+
this._statements.push(statement);
|
|
659
|
+
this._boolean = WhereBoolean.AND;
|
|
660
|
+
return this;
|
|
661
|
+
}
|
|
591
662
|
when(condition, callback, callbackElse) {
|
|
592
663
|
if (condition) {
|
|
593
664
|
if (callback)
|
|
@@ -632,18 +703,18 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
|
|
|
632
703
|
return this.where(RawQuery.create(column ? 'TRUE' : 'FALSE'));
|
|
633
704
|
}
|
|
634
705
|
if (column instanceof RawQuery) {
|
|
635
|
-
this.
|
|
706
|
+
this.pushStatement(this._container.resolve(RawQueryStatement, [column.Query, column.Bindings, self.TableAlias]));
|
|
636
707
|
return this;
|
|
637
708
|
}
|
|
638
709
|
// handle nested where's
|
|
639
710
|
if (_.isFunction(column)) {
|
|
640
711
|
const builder = new WhereBuilder_1(this);
|
|
641
712
|
column.call(builder);
|
|
642
|
-
self.
|
|
713
|
+
self.pushStatement(this._container.resolve(WhereQueryStatement, [builder, self.TableAlias]));
|
|
643
714
|
return this;
|
|
644
715
|
}
|
|
645
716
|
if (column instanceof Lazy) {
|
|
646
|
-
this.
|
|
717
|
+
this.pushStatement(this._container.resolve(LazyQueryStatement, [column, this]));
|
|
647
718
|
return this;
|
|
648
719
|
}
|
|
649
720
|
// handle simple key = object[key] AND ....
|
|
@@ -672,7 +743,7 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
|
|
|
672
743
|
if (sVal === null) {
|
|
673
744
|
return this.whereNull(c);
|
|
674
745
|
}
|
|
675
|
-
self.
|
|
746
|
+
self.pushStatement(self._container.resolve(WhereStatement, [c, SqlOperator.EQ, sVal, this]));
|
|
676
747
|
return self;
|
|
677
748
|
}
|
|
678
749
|
/**
|
|
@@ -691,12 +762,17 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
|
|
|
691
762
|
throw new InvalidArgument(`value cannot be NaN`);
|
|
692
763
|
}
|
|
693
764
|
if (sVal === null) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
765
|
+
const op = String(o).toLowerCase();
|
|
766
|
+
// where(col, '=', null) => IS NULL ; where(col, '!=' / '<>', null) => IS NOT NULL
|
|
767
|
+
if (op === SqlOperator.EQ) {
|
|
768
|
+
return this.whereNull(c);
|
|
769
|
+
}
|
|
770
|
+
if (op === SqlOperator.NOT || op === SqlOperator.NOT_2) {
|
|
771
|
+
return this.whereNotNull(c);
|
|
772
|
+
}
|
|
773
|
+
throw new InvalidArgument(`operator ${o} cannot be used with null value ( only =, !=, <> are allowed )`);
|
|
698
774
|
}
|
|
699
|
-
self.
|
|
775
|
+
self.pushStatement(self._container.resolve(WhereStatement, [c, o, sVal, self]));
|
|
700
776
|
return this;
|
|
701
777
|
}
|
|
702
778
|
}
|
|
@@ -713,9 +789,9 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
|
|
|
713
789
|
for (const key of Object.keys(obj).filter((x) => obj[x] !== undefined)) {
|
|
714
790
|
const val = obj[key];
|
|
715
791
|
if (Array.isArray(val)) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
792
|
+
// empty array => SQL `IN ()` semantics => match nothing (FALSE),
|
|
793
|
+
// never "no condition" (which would match everything)
|
|
794
|
+
this.whereIn(key, val);
|
|
719
795
|
}
|
|
720
796
|
else if (val === null) {
|
|
721
797
|
this.whereNull(key);
|
|
@@ -726,165 +802,82 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
|
|
|
726
802
|
return this;
|
|
727
803
|
}
|
|
728
804
|
whereNotNull(column) {
|
|
729
|
-
this.
|
|
805
|
+
this.pushStatement(this._container.resolve(WhereStatement, [column, SqlOperator.NOT_NULL, null, this]));
|
|
730
806
|
return this;
|
|
731
807
|
}
|
|
732
808
|
whereNull(column) {
|
|
733
|
-
this.
|
|
809
|
+
this.pushStatement(this._container.resolve(WhereStatement, [column, SqlOperator.NULL, null, this]));
|
|
734
810
|
return this;
|
|
735
811
|
}
|
|
736
812
|
whereNot(column, val) {
|
|
737
813
|
return this.where(column, SqlOperator.NOT, val);
|
|
738
814
|
}
|
|
739
815
|
whereIn(column, val) {
|
|
740
|
-
|
|
816
|
+
// `IN ()` matches nothing in SQL; compile an empty set to FALSE rather than
|
|
817
|
+
// emitting no condition (which would silently match every row).
|
|
818
|
+
if (Array.isArray(val) && val.length === 0) {
|
|
819
|
+
this.where(false);
|
|
820
|
+
return this;
|
|
821
|
+
}
|
|
822
|
+
this.pushStatement(this._container.resolve(InStatement, [column, val, false, this]));
|
|
741
823
|
return this;
|
|
742
824
|
}
|
|
743
825
|
whereNotIn(column, val) {
|
|
744
|
-
this.
|
|
826
|
+
this.pushStatement(this._container.resolve(InStatement, [column, val, true, this]));
|
|
745
827
|
return this;
|
|
746
828
|
}
|
|
747
829
|
whereExist(query, callback) {
|
|
748
|
-
|
|
749
|
-
// TODO: move relation handling to separate DI service for every exists relation type
|
|
750
|
-
let relQuery;
|
|
751
|
-
let sourcePKey = '';
|
|
752
|
-
const self = this;
|
|
753
|
-
let tableName = '';
|
|
754
|
-
let tDesc;
|
|
755
|
-
if (typeof query === 'string') {
|
|
756
|
-
const rel = this._model.getRelationDescriptor(query);
|
|
757
|
-
if (!rel) {
|
|
758
|
-
throw new OrmException(`relation ${query} not found in model ${this.constructor.name}`);
|
|
759
|
-
}
|
|
760
|
-
switch (rel.Type) {
|
|
761
|
-
case RelationType.One:
|
|
762
|
-
this.whereNotNull(rel.ForeignKey);
|
|
763
|
-
// simply use right join for condition check
|
|
764
|
-
if (callback) {
|
|
765
|
-
// TODO: cast fix
|
|
766
|
-
this.rightJoin(rel.TargetModel, callback.bind(query));
|
|
767
|
-
}
|
|
768
|
-
break;
|
|
769
|
-
case RelationType.Many:
|
|
770
|
-
tableName = rel.TargetModel.getModelDescriptor().TableName;
|
|
771
|
-
tDesc = self._model.getModelDescriptor();
|
|
772
|
-
// set alias to avoid conflicts in case of multiple relations to same model and to make sure that relation query is correct even if source query has alias
|
|
773
|
-
relQuery = rel.TargetModel.query().setAlias(`${tableName}_exists`);
|
|
774
|
-
relQuery.where(Lazy.oF(function () {
|
|
775
|
-
const sourceAlias = self._tableAlias ?? (self._parent ? self._parent.TableAlias : tDesc.TableName);
|
|
776
|
-
sourcePKey = `\`${sourceAlias}\`.\`${self._model.getModelDescriptor().PrimaryKey}\``;
|
|
777
|
-
// relQuery is guaranteed assigned above before this lazy callback executes
|
|
778
|
-
relQuery.where(new RawQuery(`${rel.ForeignKey} = ${sourcePKey}`));
|
|
779
|
-
}));
|
|
780
|
-
if (callback) {
|
|
781
|
-
callback.apply(relQuery);
|
|
782
|
-
}
|
|
783
|
-
this.whereExist(relQuery);
|
|
784
|
-
break;
|
|
785
|
-
case RelationType.ManyToMany:
|
|
786
|
-
relQuery = rel.JunctionModel.query();
|
|
787
|
-
tableName = rel.TargetModel.getModelDescriptor().TableName;
|
|
788
|
-
tDesc = self._model.getModelDescriptor();
|
|
789
|
-
relQuery = rel.TargetModel.query().setAlias(`${tableName}_exists`);
|
|
790
|
-
relQuery.where(Lazy.oF(function () {
|
|
791
|
-
const sourceAlias = self._tableAlias ?? (self._parent ? self._parent.TableAlias : tDesc.TableName);
|
|
792
|
-
sourcePKey = `\`${sourceAlias}\`.\`${self._model.getModelDescriptor().PrimaryKey}\``;
|
|
793
|
-
// relQuery is guaranteed assigned above before this lazy callback executes
|
|
794
|
-
relQuery.where(new RawQuery(`${rel.JunctionModelSourceModelFKey_Name} = ${sourcePKey}`));
|
|
795
|
-
}));
|
|
796
|
-
this.setAlias();
|
|
797
|
-
relQuery.rightJoin({
|
|
798
|
-
joinModel: rel.TargetModel,
|
|
799
|
-
joinTableForeignKey: rel.ForeignKey,
|
|
800
|
-
sourceTablePrimaryKey: rel.JunctionModelTargetModelFKey_Name,
|
|
801
|
-
callback: callback,
|
|
802
|
-
});
|
|
803
|
-
this.whereExist(relQuery);
|
|
804
|
-
break;
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
else {
|
|
808
|
-
this._statements.push(this._container.resolve(ExistsQueryStatement, [query, false]));
|
|
809
|
-
}
|
|
810
|
-
return this;
|
|
830
|
+
return this.buildExistsClause(query, false, callback);
|
|
811
831
|
}
|
|
812
832
|
whereNotExists(query, callback) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
break;
|
|
832
|
-
case RelationType.Many:
|
|
833
|
-
tableName = rel.TargetModel.getModelDescriptor().TableName;
|
|
834
|
-
tDesc = self._model.getModelDescriptor();
|
|
835
|
-
// set alias to avoid conflicts in case of multiple relations to same model and to make sure that relation query is correct even if source query has alias
|
|
836
|
-
relQuery = rel.TargetModel.query().setAlias(`${tableName}_exists`);
|
|
837
|
-
relQuery.where(Lazy.oF(function () {
|
|
838
|
-
const sourceAlias = self._tableAlias || (self._parent ? self._parent.TableAlias : tDesc.TableName);
|
|
839
|
-
sourcePKey = `\`${sourceAlias}\`.\`${self._model.getModelDescriptor().PrimaryKey}\``;
|
|
840
|
-
// relQuery is guaranteed assigned above before this lazy callback executes
|
|
841
|
-
relQuery.where(new RawQuery(`${rel.ForeignKey} = ${sourcePKey}`));
|
|
842
|
-
}));
|
|
843
|
-
if (callback) {
|
|
844
|
-
callback.apply(relQuery);
|
|
845
|
-
}
|
|
846
|
-
this.whereNotExists(relQuery);
|
|
847
|
-
break;
|
|
848
|
-
case RelationType.ManyToMany:
|
|
849
|
-
relQuery = rel.JunctionModel.query();
|
|
850
|
-
tableName = rel.TargetModel.getModelDescriptor().TableName;
|
|
851
|
-
tDesc = self._model.getModelDescriptor();
|
|
852
|
-
relQuery = rel.TargetModel.query().setAlias(`${tableName}_exists`);
|
|
853
|
-
relQuery.where(Lazy.oF(function () {
|
|
854
|
-
const sourceAlias = self._tableAlias || (self._parent ? self._parent.TableAlias : tDesc.TableName);
|
|
855
|
-
sourcePKey = `\`${sourceAlias}\`.\`${self._model.getModelDescriptor().PrimaryKey}\``;
|
|
856
|
-
// relQuery is guaranteed assigned above before this lazy callback executes
|
|
857
|
-
relQuery.where(new RawQuery(`${rel.JunctionModelSourceModelFKey_Name} = ${sourcePKey}`));
|
|
858
|
-
}));
|
|
859
|
-
relQuery.rightJoin({
|
|
860
|
-
joinModel: rel.TargetModel,
|
|
861
|
-
joinTableForeignKey: rel.PrimaryKey,
|
|
862
|
-
sourceTablePrimaryKey: rel.ForeignKey,
|
|
863
|
-
callback: callback,
|
|
864
|
-
});
|
|
865
|
-
this.whereNotExists(relQuery);
|
|
866
|
-
break;
|
|
867
|
-
}
|
|
833
|
+
return this.buildExistsClause(query, true, callback);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Shared implementation for {@link whereExist} / {@link whereNotExists}.
|
|
837
|
+
*
|
|
838
|
+
* For a ready sub-query it pushes an {@link ExistsQueryStatement} directly. For a relation
|
|
839
|
+
* name it resolves the matching {@link ExistsRelationHandler} from the container (one per
|
|
840
|
+
* {@link RelationType}) and lets it either mutate this builder or return a correlated
|
|
841
|
+
* sub-query that we then wrap in EXISTS / NOT EXISTS.
|
|
842
|
+
*
|
|
843
|
+
* @param query relation name or a ready sub-query
|
|
844
|
+
* @param negated `true` for NOT EXISTS, `false` for EXISTS
|
|
845
|
+
* @param callback optional where-callback applied to the relation sub-query
|
|
846
|
+
*/
|
|
847
|
+
buildExistsClause(query, negated, callback) {
|
|
848
|
+
if (typeof query !== 'string') {
|
|
849
|
+
this.pushStatement(this._container.resolve(ExistsQueryStatement, [query, negated]));
|
|
850
|
+
return this;
|
|
868
851
|
}
|
|
869
|
-
|
|
870
|
-
|
|
852
|
+
const rel = this._model.getRelationDescriptor(query);
|
|
853
|
+
if (!rel) {
|
|
854
|
+
throw new OrmException(`relation ${query} not found in model ${this.constructor.name}`);
|
|
855
|
+
}
|
|
856
|
+
const handlers = this._container.resolve(Array.ofType(ExistsRelationHandler));
|
|
857
|
+
const handler = handlers.find((h) => h.Type === rel.Type);
|
|
858
|
+
if (!handler) {
|
|
859
|
+
throw new OrmException(`no ExistsRelationHandler registered for relation type ${rel.Type} (relation ${query} on ${this.constructor.name})`);
|
|
860
|
+
}
|
|
861
|
+
const subquery = handler.apply(this, rel, query, callback);
|
|
862
|
+
if (subquery) {
|
|
863
|
+
this.pushStatement(this._container.resolve(ExistsQueryStatement, [subquery, negated]));
|
|
871
864
|
}
|
|
872
865
|
return this;
|
|
873
866
|
}
|
|
874
867
|
whereBetween(column, val) {
|
|
875
|
-
this.
|
|
868
|
+
this.pushStatement(this._container.resolve(BetweenStatement, [column, val, false, this.TableAlias]));
|
|
876
869
|
return this;
|
|
877
870
|
}
|
|
878
871
|
whereNotBetween(column, val) {
|
|
879
|
-
this.
|
|
872
|
+
this.pushStatement(this._container.resolve(BetweenStatement, [column, val, true, this.TableAlias]));
|
|
880
873
|
return this;
|
|
881
874
|
}
|
|
882
875
|
whereInSet(column, val) {
|
|
883
|
-
this.
|
|
876
|
+
this.pushStatement(this._container.resolve(InSetStatement, [column, val, false, this.TableAlias]));
|
|
884
877
|
return this;
|
|
885
878
|
}
|
|
886
879
|
whereNotInSet(column, val) {
|
|
887
|
-
this.
|
|
880
|
+
this.pushStatement(this._container.resolve(InSetStatement, [column, val, true, this.TableAlias]));
|
|
888
881
|
return this;
|
|
889
882
|
}
|
|
890
883
|
clearWhere() {
|
|
@@ -924,10 +917,7 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
|
|
|
924
917
|
this._distinct = false;
|
|
925
918
|
this._method = QueryMethod.SELECT;
|
|
926
919
|
this._boolean = WhereBoolean.AND;
|
|
927
|
-
this.
|
|
928
|
-
column: '',
|
|
929
|
-
order: SortOrder.NONE,
|
|
930
|
-
};
|
|
920
|
+
this._sorts = [];
|
|
931
921
|
this._first = false;
|
|
932
922
|
this._limit = {
|
|
933
923
|
limit: -1,
|
|
@@ -942,13 +932,12 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
|
|
|
942
932
|
return (await this);
|
|
943
933
|
}
|
|
944
934
|
setAlias(alias) {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
this.
|
|
949
|
-
this.
|
|
950
|
-
this.
|
|
951
|
-
this._statements.forEach((c) => (c.TableAlias = alias));
|
|
935
|
+
// isNullOrWhitespace is not a type predicate; a false result guarantees a non-blank string
|
|
936
|
+
const resolvedAlias = isNullOrWhitespace(alias) ? `${this._driver.Options.AliasSeparator}${this._table}${this._driver.Options.AliasSeparator}` : alias;
|
|
937
|
+
this._tableAlias = resolvedAlias;
|
|
938
|
+
this._columns.forEach((c) => (c.TableAlias = resolvedAlias));
|
|
939
|
+
this._joinStatements.forEach((c) => (c.TableAlias = resolvedAlias));
|
|
940
|
+
this._statements.forEach((c) => (c.TableAlias = resolvedAlias));
|
|
952
941
|
return this;
|
|
953
942
|
}
|
|
954
943
|
clone() {
|
|
@@ -959,11 +948,18 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
|
|
|
959
948
|
builder._joinStatements = this._joinStatements.map(c => c.clone(builder));
|
|
960
949
|
// Clone statements with mapped WhereBuilder references
|
|
961
950
|
builder._statements = this._statements.map(c => c.clone(builder));
|
|
951
|
+
// Clone group-by statements (previously dropped, silently losing GROUP BY
|
|
952
|
+
// on a cloned query — eg. orm-api count-pagination clones the builder).
|
|
953
|
+
builder._groupStatements = this._groupStatements.map(c => c.clone(builder));
|
|
954
|
+
// Carry relations and result middlewares over. These hold live objects and
|
|
955
|
+
// are shared the same way mergeRelations()/mergeBuilder() already share them.
|
|
956
|
+
builder._relations = [...this._relations];
|
|
957
|
+
builder._middlewares = [...this._middlewares];
|
|
962
958
|
/**
|
|
963
959
|
* ------------------------------------------------------------------
|
|
964
960
|
*/
|
|
965
961
|
builder._limit = { ...this._limit };
|
|
966
|
-
builder.
|
|
962
|
+
builder._sorts = this._sorts.map((s) => ({ ...s }));
|
|
967
963
|
builder._boolean = this._boolean;
|
|
968
964
|
builder._distinct = this._distinct;
|
|
969
965
|
builder._table = this._table;
|
|
@@ -1062,15 +1058,21 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
|
|
|
1062
1058
|
this._getRelationInstance(relation).execute(callback);
|
|
1063
1059
|
return this;
|
|
1064
1060
|
}
|
|
1065
|
-
mergeBuilder(builder) {
|
|
1061
|
+
mergeBuilder(builder, includeStatements = true) {
|
|
1066
1062
|
this._columns = this._columns.concat(builder._columns);
|
|
1067
1063
|
this._cteStatement = builder._cteStatement;
|
|
1068
1064
|
this._distinct = builder._distinct;
|
|
1069
|
-
this
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1065
|
+
// Fold the merged builder's sorts into this query's sorts (multi-column
|
|
1066
|
+
// ORDER BY). If the merged builder has no sorts, this keeps our own.
|
|
1067
|
+
this._sorts = this._sorts.concat(builder._sorts.map((s) => ({ ...s })));
|
|
1068
|
+
// `includeStatements: false` is used by JoinStatement so that a join
|
|
1069
|
+
// callback's WHERE conditions are NOT folded into the main query's WHERE
|
|
1070
|
+
// (which silently turns a LEFT JOIN into an inner filter). The join emits
|
|
1071
|
+
// those conditions in its own ON clause instead. Columns/sort are still
|
|
1072
|
+
// merged so join-callback selects (e.g. extra joined columns) keep working.
|
|
1073
|
+
if (includeStatements) {
|
|
1074
|
+
this.mergeStatements(builder);
|
|
1075
|
+
}
|
|
1074
1076
|
}
|
|
1075
1077
|
mergeRelations(builder) {
|
|
1076
1078
|
this._relations = this._relations.concat(builder._relations);
|
|
@@ -1081,6 +1083,21 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
|
|
|
1081
1083
|
this._joinStatements = this._joinStatements.concat(builder._joinStatements);
|
|
1082
1084
|
this._statements = this._statements.concat(stms);
|
|
1083
1085
|
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Includes soft-deleted rows in the result set by removing the default
|
|
1088
|
+
* `DeletedAt IS NULL` filter added by createQuery for @SoftDelete models.
|
|
1089
|
+
*/
|
|
1090
|
+
withDeleted() {
|
|
1091
|
+
const descriptor = extractModelDescriptor(this._model);
|
|
1092
|
+
const deletedAt = descriptor?.SoftDelete?.DeletedAt;
|
|
1093
|
+
if (!deletedAt) {
|
|
1094
|
+
return this;
|
|
1095
|
+
}
|
|
1096
|
+
this._statements = this._statements.filter((s) => {
|
|
1097
|
+
return !(s instanceof WhereStatement && s.Column === deletedAt && s.Operator === SqlOperator.NULL);
|
|
1098
|
+
});
|
|
1099
|
+
return this;
|
|
1100
|
+
}
|
|
1084
1101
|
min(column, as) {
|
|
1085
1102
|
this._columns.push(this._container.resolve(ColumnMethodStatement, [column, ColumnMethods.MIN, as, this._tableAlias]));
|
|
1086
1103
|
return this;
|
|
@@ -1122,44 +1139,26 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
|
|
|
1122
1139
|
return compiler.compile();
|
|
1123
1140
|
}
|
|
1124
1141
|
async all() {
|
|
1125
|
-
return await this;
|
|
1142
|
+
return await this.execute();
|
|
1126
1143
|
}
|
|
1127
1144
|
async resultExists() {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
then(onfulfilled, onrejected) {
|
|
1136
|
-
this._queryMiddlewares.forEach((x) => x.beforeQueryExecution(this));
|
|
1137
|
-
return super.then((result) => {
|
|
1138
|
-
if (this._first) {
|
|
1139
|
-
if (Array.isArray(result)) {
|
|
1140
|
-
if (result.length !== 0) {
|
|
1141
|
-
return onfulfilled?.(result[0]);
|
|
1142
|
-
}
|
|
1143
|
-
else {
|
|
1144
|
-
try {
|
|
1145
|
-
return onfulfilled?.(undefined);
|
|
1146
|
-
}
|
|
1147
|
-
catch (err) {
|
|
1148
|
-
onrejected?.(err);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
else {
|
|
1153
|
-
return onfulfilled?.(result);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
else {
|
|
1157
|
-
return onfulfilled?.(result);
|
|
1158
|
-
}
|
|
1159
|
-
}, onrejected);
|
|
1145
|
+
const res = await this.execute();
|
|
1146
|
+
if (Array.isArray(res)) {
|
|
1147
|
+
return res.length > 0;
|
|
1148
|
+
}
|
|
1149
|
+
return res !== undefined && res !== null;
|
|
1160
1150
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1151
|
+
/**
|
|
1152
|
+
* Overrides the engine rather than `execute()` so that the `takeFirst()` unwrapping
|
|
1153
|
+
* happens *inside* the memo. `beforeQueryExecution` is dispatched by `Builder._run()`
|
|
1154
|
+
* for every builder type, so it still fires once per builder rather than once per await.
|
|
1155
|
+
*/
|
|
1156
|
+
async _run() {
|
|
1157
|
+
const result = await super._run();
|
|
1158
|
+
if (this._first && Array.isArray(result)) {
|
|
1159
|
+
return (result.length !== 0 ? result[0] : undefined);
|
|
1160
|
+
}
|
|
1161
|
+
return result;
|
|
1163
1162
|
}
|
|
1164
1163
|
};
|
|
1165
1164
|
__decorate([
|
|
@@ -1184,6 +1183,10 @@ let DeleteQueryBuilder = class DeleteQueryBuilder extends QueryBuilder {
|
|
|
1184
1183
|
offset: -1,
|
|
1185
1184
|
};
|
|
1186
1185
|
this.QueryContext = QueryContext.Delete;
|
|
1186
|
+
// Query middlewares (e.g. rbac ownership enforcement) must run for deletes
|
|
1187
|
+
// too, not only selects — otherwise :own permission constraints are never
|
|
1188
|
+
// applied and any row can be deleted.
|
|
1189
|
+
this._queryMiddlewares.forEach((x) => x.afterQueryCreation(this));
|
|
1187
1190
|
}
|
|
1188
1191
|
toDB() {
|
|
1189
1192
|
return this._container.resolve(DeleteQueryCompiler, [this]).compile();
|
|
@@ -1223,6 +1226,9 @@ let OnDuplicateQueryBuilder = class OnDuplicateQueryBuilder {
|
|
|
1223
1226
|
this._columnsToUpdate = columns;
|
|
1224
1227
|
return this;
|
|
1225
1228
|
}
|
|
1229
|
+
execute() {
|
|
1230
|
+
return this._parent.execute();
|
|
1231
|
+
}
|
|
1226
1232
|
then(onfulfilled, onrejected) {
|
|
1227
1233
|
return this._parent.then(onfulfilled, onrejected);
|
|
1228
1234
|
}
|
|
@@ -1246,6 +1252,10 @@ let UpdateQueryBuilder = class UpdateQueryBuilder extends QueryBuilder {
|
|
|
1246
1252
|
this._boolean = WhereBoolean.AND;
|
|
1247
1253
|
this._statements = [];
|
|
1248
1254
|
this.QueryContext = QueryContext.Update;
|
|
1255
|
+
// Query middlewares (e.g. rbac ownership enforcement) must run for updates
|
|
1256
|
+
// too, not only selects — otherwise :own permission constraints are never
|
|
1257
|
+
// applied and any row can be updated.
|
|
1258
|
+
this._queryMiddlewares.forEach((x) => x.afterQueryCreation(this));
|
|
1249
1259
|
}
|
|
1250
1260
|
in(name) {
|
|
1251
1261
|
this.setTable(name);
|
|
@@ -1281,6 +1291,10 @@ let InsertQueryBuilder = class InsertQueryBuilder extends QueryBuilder {
|
|
|
1281
1291
|
get Replace() {
|
|
1282
1292
|
return this._replace;
|
|
1283
1293
|
}
|
|
1294
|
+
/** Columns requested via {@link returning}. Empty when no RETURNING clause was asked for. */
|
|
1295
|
+
get Returning() {
|
|
1296
|
+
return this._returning ?? [];
|
|
1297
|
+
}
|
|
1284
1298
|
constructor(container, driver, model) {
|
|
1285
1299
|
super(container, driver, model);
|
|
1286
1300
|
this._update = false;
|
|
@@ -1289,6 +1303,52 @@ let InsertQueryBuilder = class InsertQueryBuilder extends QueryBuilder {
|
|
|
1289
1303
|
this._columns = [];
|
|
1290
1304
|
this._values = [];
|
|
1291
1305
|
this.QueryContext = QueryContext.Insert;
|
|
1306
|
+
// Inserts get the same middleware pass as the other three builders. Note that a
|
|
1307
|
+
// middleware which needs to see or amend the row payload must use
|
|
1308
|
+
// `beforeQueryExecution` instead — at construction time `values()` has not been called.
|
|
1309
|
+
this._queryMiddlewares.forEach((x) => x.afterQueryCreation(this));
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* The value of `column` on every row about to be inserted, in row order. Empty when the
|
|
1313
|
+
* payload does not carry that column at all.
|
|
1314
|
+
*
|
|
1315
|
+
* The read counterpart of {@link forceColumn}, and the only sane way for a
|
|
1316
|
+
* `beforeQueryExecution` hook to inspect what is being written: the payload lives in two
|
|
1317
|
+
* parallel structures ( `_columns` and one array per row ), so every caller would
|
|
1318
|
+
* otherwise repeat the same index lookup.
|
|
1319
|
+
*
|
|
1320
|
+
* @param column - column to read off each row
|
|
1321
|
+
*/
|
|
1322
|
+
getColumnValues(column) {
|
|
1323
|
+
const index = this._columns.findIndex((c) => !(c.Column instanceof RawQuery) && c.Column === column);
|
|
1324
|
+
if (index === -1) {
|
|
1325
|
+
return [];
|
|
1326
|
+
}
|
|
1327
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
1328
|
+
return this._values.map((row) => row[index]);
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Forces `column` to `value` on every row of the payload, overwriting whatever the caller
|
|
1332
|
+
* supplied.
|
|
1333
|
+
*
|
|
1334
|
+
* This is the write path for a policy that must not be negotiable — rbac's `createOwn`
|
|
1335
|
+
* ownership stamp. `values()` cannot serve: it appends rows and takes the column list from
|
|
1336
|
+
* its own argument, so calling it a second time adds a row rather than amending the
|
|
1337
|
+
* existing ones.
|
|
1338
|
+
*
|
|
1339
|
+
* @param column - column to overwrite on every row
|
|
1340
|
+
* @param value - value to force
|
|
1341
|
+
*/
|
|
1342
|
+
forceColumn(column, value) {
|
|
1343
|
+
const existing = this._columns.findIndex((c) => !(c.Column instanceof RawQuery) && c.Column === column);
|
|
1344
|
+
if (existing === -1) {
|
|
1345
|
+
this.columns([...this._columns.filter((c) => !(c.Column instanceof RawQuery)).map((c) => c.Column), column]);
|
|
1346
|
+
this._values.forEach((row) => row.push(value));
|
|
1347
|
+
return this;
|
|
1348
|
+
}
|
|
1349
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
1350
|
+
this._values.forEach((row) => (row[existing] = value));
|
|
1351
|
+
return this;
|
|
1292
1352
|
}
|
|
1293
1353
|
/**
|
|
1294
1354
|
* Sets insert to ignore on duplicate
|
|
@@ -1301,8 +1361,21 @@ let InsertQueryBuilder = class InsertQueryBuilder extends QueryBuilder {
|
|
|
1301
1361
|
this._replace = true;
|
|
1302
1362
|
return this;
|
|
1303
1363
|
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Asks the dialect to echo the given columns of every inserted row back.
|
|
1366
|
+
*
|
|
1367
|
+
* @throws NotSupported on drivers whose `supportedFeatures().insertReturning` is false —
|
|
1368
|
+
* silently doing nothing is how this API was a no-op on MySQL and MSSQL for years.
|
|
1369
|
+
*/
|
|
1304
1370
|
returning(columns) {
|
|
1371
|
+
if (!this.Driver.supportedFeatures().insertReturning) {
|
|
1372
|
+
throw new NotSupported(`driver ${this.Driver.Options.Driver} does not support RETURNING on INSERT`);
|
|
1373
|
+
}
|
|
1305
1374
|
this._returning = columns;
|
|
1375
|
+
// onDuplicate() sets Upsert unconditionally and wins if it runs afterwards.
|
|
1376
|
+
if (this.QueryContext === QueryContext.Insert) {
|
|
1377
|
+
this.QueryContext = QueryContext.InsertReturning;
|
|
1378
|
+
}
|
|
1306
1379
|
return this;
|
|
1307
1380
|
}
|
|
1308
1381
|
values(data) {
|
|
@@ -1464,6 +1537,7 @@ let ColumnQueryBuilder = class ColumnQueryBuilder {
|
|
|
1464
1537
|
this.Comment = '';
|
|
1465
1538
|
this.Unique = false;
|
|
1466
1539
|
this.Unsigned = false;
|
|
1540
|
+
this.InlinePrimaryKey = true;
|
|
1467
1541
|
this.Args.push(...args);
|
|
1468
1542
|
}
|
|
1469
1543
|
notNull() {
|
|
@@ -1634,10 +1708,11 @@ AlterTableQueryBuilder = __decorate([
|
|
|
1634
1708
|
export { AlterTableQueryBuilder };
|
|
1635
1709
|
let TableQueryBuilder = class TableQueryBuilder extends QueryBuilder {
|
|
1636
1710
|
/**
|
|
1637
|
-
* Alias for
|
|
1711
|
+
* Alias for binary(name, 16 ) - uuids are stored as 16-byte BINARY to match
|
|
1712
|
+
* the UuidConverter ( which writes a dashed uuid as a 16-byte buffer ).
|
|
1638
1713
|
*/
|
|
1639
1714
|
uuid(name) {
|
|
1640
|
-
return this.
|
|
1715
|
+
return this.binary(name, 16);
|
|
1641
1716
|
}
|
|
1642
1717
|
ifExists() {
|
|
1643
1718
|
this._checkExists = true;
|
|
@@ -2043,6 +2118,16 @@ export function createQuery(model, query, injectModel = true) {
|
|
|
2043
2118
|
}
|
|
2044
2119
|
qr.middleware(new DiscriminationMapMiddleware(dsc));
|
|
2045
2120
|
qr.setTable(dsc.TableName);
|
|
2121
|
+
// Soft-delete read filtering: by default exclude rows that have been soft
|
|
2122
|
+
// deleted (DeletedAt IS NOT NULL). SelectQueryBuilder.withDeleted() removes
|
|
2123
|
+
// this default statement to include soft-deleted rows again.
|
|
2124
|
+
// Guarded on the DeletedAt column actually being present in the model's
|
|
2125
|
+
// reflected columns — a filter on a column the schema does not expose is
|
|
2126
|
+
// impossible anyway, and the guard keeps queries working when table info
|
|
2127
|
+
// has not (yet) surfaced the column.
|
|
2128
|
+
if (qr instanceof SelectQueryBuilder && dsc.SoftDelete?.DeletedAt && dsc.Columns?.some((c) => c.Name === dsc.SoftDelete.DeletedAt)) {
|
|
2129
|
+
qr.whereNull(dsc.SoftDelete.DeletedAt);
|
|
2130
|
+
}
|
|
2046
2131
|
if (driver.Options.Database) {
|
|
2047
2132
|
qr.database(driver.Options.Database);
|
|
2048
2133
|
}
|