objection 3.2.0 → 3.4.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 +13 -13
- package/lib/model/AjvValidator.js +220 -13
- package/lib/model/Model.js +3 -1
- package/lib/model/modelId.js +10 -2
- package/lib/model/modelQueryProps.js +7 -0
- package/lib/queryBuilder/QueryBuilder.js +191 -8
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
- package/lib/queryBuilder/ReferenceBuilder.js +5 -3
- package/lib/queryBuilder/RelationExpression.js +13 -1
- package/lib/queryBuilder/StaticHookArguments.js +12 -1
- package/lib/queryBuilder/graph/GraphFetcher.js +1 -3
- package/lib/queryBuilder/graph/GraphOptions.js +30 -1
- package/lib/queryBuilder/graph/GraphUpsert.js +38 -1
- package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +107 -8
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +8 -1
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +8 -2
- package/lib/queryBuilder/join/JoinResultParser.js +22 -0
- package/lib/queryBuilder/join/RelationJoiner.js +80 -27
- package/lib/queryBuilder/join/utils.js +20 -2
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
- package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
- package/lib/queryBuilder/operations/InsertOperation.js +129 -13
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
- package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
- package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +4 -0
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +2 -6
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +10 -33
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +41 -34
- package/lib/queryBuilder/operations/select/Selection.js +95 -4
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
- package/lib/queryBuilder/whereGroups.js +125 -0
- package/lib/relations/Relation.js +5 -2
- package/lib/relations/RelationFindOperation.js +46 -2
- package/lib/relations/RelationProperty.js +3 -2
- package/lib/relations/hasMany/HasManyRelateOperation.js +5 -2
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
- package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +104 -51
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +4 -2
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +12 -2
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
- package/lib/utils/assert.js +9 -0
- package/lib/utils/deprecate.js +6 -0
- package/lib/utils/identifierMapping.js +15 -1
- package/lib/utils/parseFieldExpression.js +28 -0
- package/lib/utils/wrapDbError.js +36 -0
- package/package.json +8 -7
- package/typings/objection/index.d.ts +281 -29
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { wrapDbError } = require('../utils/wrapDbError');
|
|
4
4
|
|
|
5
5
|
const { raw } = require('./RawBuilder');
|
|
6
6
|
const { createModifier } = require('../utils/createModifier');
|
|
7
7
|
const { ValidationErrorType } = require('../model/ValidationError');
|
|
8
8
|
const { isObject, isString, isFunction, last, flatten } = require('../utils/objectUtils');
|
|
9
9
|
const { RelationExpression, DuplicateRelationError } = require('./RelationExpression');
|
|
10
|
-
const { assertIdNotUndefined } = require('../utils/assert');
|
|
10
|
+
const { assertIdNotUndefined, assertHasIdColumn } = require('../utils/assert');
|
|
11
11
|
|
|
12
12
|
const { Selection } = require('./operations/select/Selection');
|
|
13
13
|
|
|
@@ -46,6 +46,7 @@ const { FirstOperation } = require('./operations/FirstOperation');
|
|
|
46
46
|
const { NoneOperation } = require('./operations/NoneOperation');
|
|
47
47
|
const { FromOperation } = require('./operations/FromOperation');
|
|
48
48
|
const { KnexOperation } = require('./operations/KnexOperation');
|
|
49
|
+
const { wrapWhereGroups } = require('./whereGroups');
|
|
49
50
|
|
|
50
51
|
class QueryBuilder extends QueryBuilderBase {
|
|
51
52
|
static init(self, modelClass) {
|
|
@@ -58,8 +59,12 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
58
59
|
|
|
59
60
|
self._allowedGraphExpression = null;
|
|
60
61
|
self._graphModifiersAtPath = [];
|
|
62
|
+
// The eager algorithm that was most recently used explicitly, see `withGraph()`.
|
|
63
|
+
self._graphAlgorithm = null;
|
|
64
|
+
self._isJoinChildQuery = false;
|
|
61
65
|
self._findOperationOptions = modelClass.defaultFindOptions;
|
|
62
66
|
self._relatedQueryFor = null;
|
|
67
|
+
self._internalSelections = new Set();
|
|
63
68
|
|
|
64
69
|
self._findOperationFactory = findOperationFactory;
|
|
65
70
|
self._insertOperationFactory = insertOperationFactory;
|
|
@@ -78,6 +83,27 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
78
83
|
return RelationExpression.create(expr).toPojo();
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
childQueryOf(query, options = {}) {
|
|
87
|
+
super.childQueryOf(query, options);
|
|
88
|
+
|
|
89
|
+
if (query) {
|
|
90
|
+
this._isJoinChildQuery = !!options.isJoinChildQuery;
|
|
91
|
+
|
|
92
|
+
if (query.isObjectionQueryBuilder) {
|
|
93
|
+
this._graphAlgorithm = query._graphAlgorithm;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Returns true if this query was created for a relation that is loaded using
|
|
101
|
+
// `withGraphJoined()`. Such queries are only used to build the join and are
|
|
102
|
+
// never executed themselves.
|
|
103
|
+
isJoinChildQuery() {
|
|
104
|
+
return this._isJoinChildQuery;
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
tableNameFor(modelClassOrTableName, newTableName) {
|
|
82
108
|
return super.tableNameFor(getTableName(modelClassOrTableName), newTableName);
|
|
83
109
|
}
|
|
@@ -99,6 +125,8 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
99
125
|
}
|
|
100
126
|
|
|
101
127
|
fullIdColumnFor(modelClass) {
|
|
128
|
+
assertHasIdColumn(modelClass);
|
|
129
|
+
|
|
102
130
|
const tableName = this.tableRefFor(modelClass);
|
|
103
131
|
const idColumn = modelClass.getIdColumn();
|
|
104
132
|
|
|
@@ -191,19 +219,50 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
191
219
|
}
|
|
192
220
|
|
|
193
221
|
withGraphFetched(exp, options = {}) {
|
|
194
|
-
return this.
|
|
222
|
+
return this._withGraphUsing(exp, options, getWhereInEagerAlgorithm(this));
|
|
195
223
|
}
|
|
196
224
|
|
|
197
225
|
withGraphJoined(exp, options = {}) {
|
|
198
|
-
return this.
|
|
226
|
+
return this._withGraphUsing(exp, options, getJoinEagerAlgorithm(this));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Like `withGraphFetched()` or `withGraphJoined()` with `options.algorithm`
|
|
230
|
+
// set to 'fetch' or 'join'. Without it, the top-level relations that are
|
|
231
|
+
// already loaded by one of the algorithms are merged into it, and new ones
|
|
232
|
+
// use the most recently used algorithm.
|
|
233
|
+
withGraph(exp, { algorithm, ...options } = {}) {
|
|
234
|
+
if (algorithm !== undefined) {
|
|
235
|
+
return this._withGraphUsing(exp, options, getEagerAlgorithmByName(this, algorithm));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const parsedExp = parseRelationExpression(this.modelClass(), exp);
|
|
239
|
+
|
|
240
|
+
for (const [algorithm, groupExp] of groupByEagerAlgorithm(this, parsedExp)) {
|
|
241
|
+
this._withGraph(groupExp, options, algorithm);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_withGraphUsing(exp, options, algorithm) {
|
|
248
|
+
this._graphAlgorithm = algorithm;
|
|
249
|
+
return this._withGraph(exp, options, algorithm);
|
|
199
250
|
}
|
|
200
251
|
|
|
201
252
|
_withGraph(exp, options, algorithm) {
|
|
202
253
|
const eagerOp = ensureEagerOperation(this, algorithm);
|
|
203
254
|
const parsedExp = parseRelationExpression(this.modelClass(), exp);
|
|
204
255
|
|
|
256
|
+
// The `joinOperation` option only applies to the relations of this call,
|
|
257
|
+
// see `RelationJoiner`. All other options are shared by all calls.
|
|
258
|
+
const { joinOperation, ...graphOptions } = options;
|
|
259
|
+
|
|
205
260
|
eagerOp.expression = eagerOp.expression.merge(parsedExp);
|
|
206
|
-
eagerOp.graphOptions = { ...eagerOp.graphOptions, ...
|
|
261
|
+
eagerOp.graphOptions = { ...eagerOp.graphOptions, ...graphOptions };
|
|
262
|
+
|
|
263
|
+
if (joinOperation) {
|
|
264
|
+
eagerOp.joinOperations.push({ expression: parsedExp, joinOperation });
|
|
265
|
+
}
|
|
207
266
|
|
|
208
267
|
checkMixedEager(this);
|
|
209
268
|
checkEager(this);
|
|
@@ -355,6 +414,8 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
355
414
|
|
|
356
415
|
builder._allowedGraphExpression = this._allowedGraphExpression;
|
|
357
416
|
builder._graphModifiersAtPath = this._graphModifiersAtPath.slice();
|
|
417
|
+
builder._graphAlgorithm = this._graphAlgorithm;
|
|
418
|
+
builder._isJoinChildQuery = this._isJoinChildQuery;
|
|
358
419
|
builder._findOperationOptions = this._findOperationOptions;
|
|
359
420
|
builder._relatedQueryFor = this._relatedQueryFor;
|
|
360
421
|
|
|
@@ -376,8 +437,17 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
376
437
|
return builder;
|
|
377
438
|
}
|
|
378
439
|
|
|
440
|
+
// Registers a property of the result models that was selected internally,
|
|
441
|
+
// e.g. by an eager operation, without the user asking for it. Such properties
|
|
442
|
+
// are omitted from the result models before `runAfter` hooks are called.
|
|
443
|
+
addInternalSelection(prop) {
|
|
444
|
+
this._internalSelections.add(prop);
|
|
445
|
+
return this;
|
|
446
|
+
}
|
|
447
|
+
|
|
379
448
|
clearWithGraph() {
|
|
380
449
|
this.clear(EagerOperation);
|
|
450
|
+
this._graphAlgorithm = null;
|
|
381
451
|
return this;
|
|
382
452
|
}
|
|
383
453
|
|
|
@@ -424,11 +494,27 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
424
494
|
async resultSize() {
|
|
425
495
|
const knex = this.knex();
|
|
426
496
|
const builder = this.clone().clear(/orderBy|offset|limit/);
|
|
497
|
+
const joinEagerOp = builder.findOperation(JoinEagerOperation);
|
|
498
|
+
|
|
499
|
+
if (joinEagerOp) {
|
|
500
|
+
// `withGraphJoined` needs the table metadata to build its selects.
|
|
501
|
+
await joinEagerOp.onBefore3(builder);
|
|
502
|
+
}
|
|
427
503
|
|
|
428
504
|
const countQuery = knex.count('* as count').from((knexBuilder) => {
|
|
429
505
|
// Also clear the order on the built knex query, since `orderBy` calls
|
|
430
506
|
// can be added at build time, e.g. by a relation's `modify`.
|
|
431
|
-
builder.toKnexQuery(knexBuilder).clearOrder()
|
|
507
|
+
knexBuilder = builder.toKnexQuery(knexBuilder).clearOrder();
|
|
508
|
+
|
|
509
|
+
if (joinEagerOp) {
|
|
510
|
+
// The joins of `withGraphJoined` return one row per joined child.
|
|
511
|
+
// Count the distinct root models instead.
|
|
512
|
+
const tableRef = builder.tableRef();
|
|
513
|
+
const idColumns = builder.modelClass().getIdColumnArray();
|
|
514
|
+
knexBuilder.clearSelect().distinct(idColumns.map((col) => `${tableRef}.${col}`));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
knexBuilder.as('temp');
|
|
432
518
|
});
|
|
433
519
|
|
|
434
520
|
if (this.internalOptions().debug) {
|
|
@@ -891,6 +977,26 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
891
977
|
return this;
|
|
892
978
|
}
|
|
893
979
|
|
|
980
|
+
updateById(id, modelOrObject) {
|
|
981
|
+
return this.findById(id)
|
|
982
|
+
.update(modelOrObject)
|
|
983
|
+
.runBefore((_, builder) => {
|
|
984
|
+
if (!builder.internalOptions().skipUndefined) {
|
|
985
|
+
assertIdNotUndefined(id, `undefined was passed to updateById`);
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
patchById(id, modelOrObject) {
|
|
991
|
+
return this.findById(id)
|
|
992
|
+
.patch(modelOrObject)
|
|
993
|
+
.runBefore((_, builder) => {
|
|
994
|
+
if (!builder.internalOptions().skipUndefined) {
|
|
995
|
+
assertIdNotUndefined(id, `undefined was passed to patchById`);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
|
|
894
1000
|
deleteById(id) {
|
|
895
1001
|
return this.findById(id)
|
|
896
1002
|
.delete()
|
|
@@ -1055,6 +1161,66 @@ function getGraphExpression(builder) {
|
|
|
1055
1161
|
);
|
|
1056
1162
|
}
|
|
1057
1163
|
|
|
1164
|
+
function getEagerAlgorithmByName(builder, name) {
|
|
1165
|
+
if (name === 'fetch') {
|
|
1166
|
+
return getWhereInEagerAlgorithm(builder);
|
|
1167
|
+
} else if (name === 'join') {
|
|
1168
|
+
return getJoinEagerAlgorithm(builder);
|
|
1169
|
+
} else {
|
|
1170
|
+
throw new Error(`unknown graph algorithm "${name}", expected "fetch" or "join"`);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function getEagerAlgorithmOfOperation(builder, eagerOp) {
|
|
1175
|
+
if (eagerOp instanceof JoinEagerOperation) {
|
|
1176
|
+
return getJoinEagerAlgorithm(builder);
|
|
1177
|
+
} else if (eagerOp instanceof NaiveEagerOperation) {
|
|
1178
|
+
return getNaiveEagerAlgorithm(builder);
|
|
1179
|
+
} else {
|
|
1180
|
+
return getWhereInEagerAlgorithm(builder);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Splits `expression` by its top-level relations into one expression per eager
|
|
1185
|
+
// algorithm. Relations that are already loaded by one of the eager operations
|
|
1186
|
+
// keep its algorithm, new ones use the most recently used one.
|
|
1187
|
+
function groupByEagerAlgorithm(builder, expression) {
|
|
1188
|
+
const defaultAlgorithm = builder._graphAlgorithm || getWhereInEagerAlgorithm(builder);
|
|
1189
|
+
const { node } = expression;
|
|
1190
|
+
const rootProps = {};
|
|
1191
|
+
const groups = new Map();
|
|
1192
|
+
|
|
1193
|
+
for (const key of Object.keys(node)) {
|
|
1194
|
+
if (key.startsWith('$')) {
|
|
1195
|
+
rootProps[key] = node[key];
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
for (const childName of node.$childNames) {
|
|
1200
|
+
const eagerOp = findEagerOperations(builder).find(
|
|
1201
|
+
(op) => op.expression.node.$allRecursive || op.expression.node[childName],
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
const algorithm = eagerOp ? getEagerAlgorithmOfOperation(builder, eagerOp) : defaultAlgorithm;
|
|
1205
|
+
let group = groups.get(algorithm);
|
|
1206
|
+
|
|
1207
|
+
if (!group) {
|
|
1208
|
+
group = { ...rootProps, $childNames: [] };
|
|
1209
|
+
groups.set(algorithm, group);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
group.$childNames.push(childName);
|
|
1213
|
+
group[childName] = node[childName];
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (groups.size === 0) {
|
|
1217
|
+
// No top-level relations, e.g. `*` or an empty expression.
|
|
1218
|
+
return [[defaultAlgorithm, expression]];
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
return [...groups].map(([algorithm, group]) => [algorithm, RelationExpression.create(group)]);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1058
1224
|
function getWhereInEagerAlgorithm(builder) {
|
|
1059
1225
|
return builder.modelClass().WhereInEagerAlgorithm;
|
|
1060
1226
|
}
|
|
@@ -1200,6 +1366,7 @@ function afterExecute(builder, result) {
|
|
|
1200
1366
|
|
|
1201
1367
|
promise = chainOperationHooks(promise, builder, 'onAfter1');
|
|
1202
1368
|
promise = chainOperationHooks(promise, builder, 'onAfter2');
|
|
1369
|
+
promise = promise.then((result) => omitInternalSelections(builder, result));
|
|
1203
1370
|
|
|
1204
1371
|
promise = chainHooks(promise, builder, builder.context().runAfter);
|
|
1205
1372
|
promise = chainHooks(promise, builder, builder.internalContext().runAfter);
|
|
@@ -1209,6 +1376,22 @@ function afterExecute(builder, result) {
|
|
|
1209
1376
|
return promise;
|
|
1210
1377
|
}
|
|
1211
1378
|
|
|
1379
|
+
function omitInternalSelections(builder, result) {
|
|
1380
|
+
const props = builder._internalSelections;
|
|
1381
|
+
|
|
1382
|
+
if (props.size && Array.isArray(result) && isObject(result[0])) {
|
|
1383
|
+
const modelClass = builder.resultModelClass();
|
|
1384
|
+
|
|
1385
|
+
for (const model of result) {
|
|
1386
|
+
for (const prop of props) {
|
|
1387
|
+
modelClass.omitImpl(model, prop);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
return result;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1212
1395
|
class ReturnImmediatelyException {
|
|
1213
1396
|
constructor(value) {
|
|
1214
1397
|
this.value = value;
|
|
@@ -1227,7 +1410,7 @@ function handleExecuteError(builder, err) {
|
|
|
1227
1410
|
return Promise.resolve(err.value);
|
|
1228
1411
|
}
|
|
1229
1412
|
|
|
1230
|
-
let promise = Promise.reject(
|
|
1413
|
+
let promise = Promise.reject(wrapDbError(err));
|
|
1231
1414
|
|
|
1232
1415
|
builder.forEachOperation(true, (op) => {
|
|
1233
1416
|
if (op.hasOnError()) {
|
|
@@ -1331,7 +1514,7 @@ function callOnBuildHooks(builder) {
|
|
|
1331
1514
|
callOnBuildFuncs(builder, builder.internalContext().onBuild);
|
|
1332
1515
|
|
|
1333
1516
|
builder.executeOnBuild();
|
|
1334
|
-
return builder;
|
|
1517
|
+
return wrapWhereGroups(builder);
|
|
1335
1518
|
}
|
|
1336
1519
|
|
|
1337
1520
|
function callOnBuildFuncs(builder, func) {
|
|
@@ -26,6 +26,7 @@ class QueryBuilderOperationSupport {
|
|
|
26
26
|
self._parentQuery = null;
|
|
27
27
|
self._isPartialQuery = false;
|
|
28
28
|
self._activeOperations = [];
|
|
29
|
+
self._whereGroup = null;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
static forClass(modelClass) {
|
|
@@ -413,6 +414,10 @@ class QueryBuilderOperationSupport {
|
|
|
413
414
|
const shouldAdd = this.callOperationMethod(operation, 'onAdd', [this, args]);
|
|
414
415
|
|
|
415
416
|
if (shouldAdd) {
|
|
417
|
+
if (this._whereGroup) {
|
|
418
|
+
operation.whereGroup = this._whereGroup;
|
|
419
|
+
}
|
|
420
|
+
|
|
416
421
|
if (this._activeOperations.length) {
|
|
417
422
|
const { operation: parentOperation, hookName } = last(this._activeOperations);
|
|
418
423
|
parentOperation.addChildOperation(hookName, operation);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
parseFieldExpression,
|
|
5
|
+
toPostgresJsonPathLiteral,
|
|
6
|
+
} = require('../utils/parseFieldExpression');
|
|
4
7
|
const { isObject } = require('../utils/objectUtils');
|
|
5
8
|
|
|
6
9
|
class ReferenceBuilder {
|
|
@@ -185,8 +188,7 @@ class ReferenceBuilder {
|
|
|
185
188
|
|
|
186
189
|
if (this._parsedExpr.access.length > 0) {
|
|
187
190
|
const extractor = this._cast ? '#>>' : '#>';
|
|
188
|
-
|
|
189
|
-
return `??${extractor}'{${jsonFieldRef}}'`;
|
|
191
|
+
return `??${extractor}${toPostgresJsonPathLiteral(this._parsedExpr.access)}`;
|
|
190
192
|
} else {
|
|
191
193
|
return '??';
|
|
192
194
|
}
|
|
@@ -355,7 +355,19 @@ function normalizeAllRecursive(node, allRecusive) {
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
function normalizeChildNames(node) {
|
|
358
|
-
|
|
358
|
+
const childNames = getChildNames(node);
|
|
359
|
+
|
|
360
|
+
if (node && node.$childNames) {
|
|
361
|
+
// The actual keys of the node are authoritative, since the node may be a
|
|
362
|
+
// modified copy of `graphExpressionObject()` with a stale `$childNames`.
|
|
363
|
+
// Keep the order of `$childNames` for the children that still exist.
|
|
364
|
+
return union(
|
|
365
|
+
node.$childNames.filter((childName) => childNames.includes(childName)),
|
|
366
|
+
childNames,
|
|
367
|
+
);
|
|
368
|
+
} else {
|
|
369
|
+
return childNames;
|
|
370
|
+
}
|
|
359
371
|
}
|
|
360
372
|
|
|
361
373
|
function findExpressionsAtPath(target, path, results) {
|
|
@@ -24,7 +24,13 @@ class StaticHookArguments {
|
|
|
24
24
|
|
|
25
25
|
get asFindQuery() {
|
|
26
26
|
return () => {
|
|
27
|
-
|
|
27
|
+
// The `runAfter()` callbacks of the query being executed (including the
|
|
28
|
+
// ones installed by `throwIfNotFound()` and `traverse()`) are meant for
|
|
29
|
+
// its result, not for the result of this find query.
|
|
30
|
+
return this[BUILDER_SYMBOL].toFindQuery()
|
|
31
|
+
.clear('runAfter')
|
|
32
|
+
.clearWithGraphFetched()
|
|
33
|
+
.runAfter(toArray);
|
|
28
34
|
};
|
|
29
35
|
}
|
|
30
36
|
|
|
@@ -97,6 +103,11 @@ class StaticHookArguments {
|
|
|
97
103
|
}
|
|
98
104
|
}
|
|
99
105
|
|
|
106
|
+
// Like `asArray()`, but an empty `first()` result becomes an empty array.
|
|
107
|
+
function toArray(result) {
|
|
108
|
+
return result === undefined ? [] : asArray(result);
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
function getRelation(op) {
|
|
101
112
|
return op.relation;
|
|
102
113
|
}
|
|
@@ -153,9 +153,7 @@ function createManyToManyExtraSelectionIfNeeded(builder, column, node) {
|
|
|
153
153
|
const extra = relation.joinTableExtras.find((extra) => extra.aliasCol === column);
|
|
154
154
|
|
|
155
155
|
if (extra) {
|
|
156
|
-
return `${
|
|
157
|
-
extra.aliasCol
|
|
158
|
-
}`;
|
|
156
|
+
return `${relation.joinTableAliasFor(builder)}.${extra.joinTableCol} as ${extra.aliasCol}`;
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
159
|
|
|
@@ -13,6 +13,20 @@ const INSERT_MISSING = 'insertMissing';
|
|
|
13
13
|
const FETCH_STRATEGY = 'fetchStrategy';
|
|
14
14
|
const ALLOW_REFS = 'allowRefs';
|
|
15
15
|
|
|
16
|
+
const OPTION_NAMES = [
|
|
17
|
+
NO_RELATE,
|
|
18
|
+
NO_UNRELATE,
|
|
19
|
+
NO_INSERT,
|
|
20
|
+
NO_UPDATE,
|
|
21
|
+
NO_DELETE,
|
|
22
|
+
UPDATE,
|
|
23
|
+
RELATE,
|
|
24
|
+
UNRELATE,
|
|
25
|
+
INSERT_MISSING,
|
|
26
|
+
FETCH_STRATEGY,
|
|
27
|
+
ALLOW_REFS,
|
|
28
|
+
];
|
|
29
|
+
|
|
16
30
|
const FetchStrategy = {
|
|
17
31
|
OnlyIdentifiers: 'OnlyIdentifiers',
|
|
18
32
|
Everything: 'Everything',
|
|
@@ -64,7 +78,7 @@ class GraphOptions {
|
|
|
64
78
|
!getCurrentNode(node, graphData) &&
|
|
65
79
|
!!node.parentEdge &&
|
|
66
80
|
!!node.parentEdge.relation &&
|
|
67
|
-
|
|
81
|
+
hasRelateProp(node) &&
|
|
68
82
|
graphData.nodeDbExistence.doesNodeExistInDb(node)
|
|
69
83
|
);
|
|
70
84
|
}
|
|
@@ -212,6 +226,20 @@ function getCurrentNode(node, graphData) {
|
|
|
212
226
|
return graphData.currentGraph.nodeForNode(node);
|
|
213
227
|
}
|
|
214
228
|
|
|
229
|
+
// For `HasMany` and `HasOne` relations, the relate prop is the id of the
|
|
230
|
+
// related model. Use the id state of the input graph, since the parent's
|
|
231
|
+
// insert copies the foreign key into the related model, which can complete
|
|
232
|
+
// a composite id that contains it and would turn an insert into a relate (#2544).
|
|
233
|
+
function hasRelateProp(node) {
|
|
234
|
+
const relation = node.parentEdge.relation;
|
|
235
|
+
|
|
236
|
+
if (relation.isObjectionHasManyRelation) {
|
|
237
|
+
return node.hasId;
|
|
238
|
+
} else {
|
|
239
|
+
return relation.hasRelateProp(node.obj);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
215
243
|
function getNode(currentNode, graph) {
|
|
216
244
|
if (!graph || !currentNode) {
|
|
217
245
|
return null;
|
|
@@ -222,5 +250,6 @@ function getNode(currentNode, graph) {
|
|
|
222
250
|
|
|
223
251
|
module.exports = {
|
|
224
252
|
GraphOptions,
|
|
253
|
+
OPTION_NAMES,
|
|
225
254
|
FetchStrategy,
|
|
226
255
|
};
|
|
@@ -8,16 +8,18 @@ const { GraphInsert } = require('./insert/GraphInsert');
|
|
|
8
8
|
const { GraphPatch } = require('./patch/GraphPatch');
|
|
9
9
|
const { GraphDelete } = require('./delete/GraphDelete');
|
|
10
10
|
const { GraphRecursiveUpsert } = require('./recursiveUpsert/GraphRecursiveUpsert');
|
|
11
|
-
const { GraphOptions } = require('./GraphOptions');
|
|
11
|
+
const { GraphOptions, OPTION_NAMES } = require('./GraphOptions');
|
|
12
12
|
const { ValidationErrorType } = require('../../model/ValidationError');
|
|
13
13
|
const { RelationExpression } = require('../RelationExpression');
|
|
14
14
|
const { GraphNodeDbExistence } = require('./GraphNodeDbExistence');
|
|
15
15
|
const { GraphData } = require('./GraphData');
|
|
16
16
|
const { uniqBy, asArray, isObject } = require('../../utils/objectUtils');
|
|
17
|
+
const { deprecate } = require('../../utils/deprecate');
|
|
17
18
|
|
|
18
19
|
class GraphUpsert {
|
|
19
20
|
constructor({ rootModelClass, objects, upsertOptions }) {
|
|
20
21
|
checkCanBeConvertedToModels(rootModelClass, objects);
|
|
22
|
+
warnOnUnknownOptions(upsertOptions);
|
|
21
23
|
|
|
22
24
|
this.objects = rootModelClass.ensureModelArray(objects, GraphUpsert.modelOptions);
|
|
23
25
|
this.isArray = Array.isArray(objects);
|
|
@@ -28,6 +30,23 @@ class GraphUpsert {
|
|
|
28
30
|
return { skipValidation: true };
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
// A graph is written using many insert queries (one per batch and table),
|
|
34
|
+
// so there's no single insert query an `onConflict` clause could be applied
|
|
35
|
+
// to. Even applying it to the root table only would be unsound: rows skipped
|
|
36
|
+
// by `ignore()` aren't returned, which leaves the root models without ids,
|
|
37
|
+
// so that related rows would be inserted with missing or wrong foreign keys.
|
|
38
|
+
// The clause is ignored, and a warning is logged once per method.
|
|
39
|
+
// TODO: Throw an error instead in objection 4.0.
|
|
40
|
+
static warnOnConflict(builder, method) {
|
|
41
|
+
if (builder.has(isOnConflictOperation)) {
|
|
42
|
+
deprecate(
|
|
43
|
+
`onConflict(), ignore() and merge() are not supported by ${method}(). ` +
|
|
44
|
+
'Insert the conflicting rows with a separate insert() query instead. ' +
|
|
45
|
+
'This will throw in objection 4.0.',
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
run(builder) {
|
|
32
51
|
const modelClass = builder.modelClass();
|
|
33
52
|
const graphOptions = new GraphOptions(this.upsertOpt);
|
|
@@ -43,6 +62,20 @@ class GraphUpsert {
|
|
|
43
62
|
}
|
|
44
63
|
}
|
|
45
64
|
|
|
65
|
+
// Misspelled options are silently ignored, so the operation runs with the
|
|
66
|
+
// default behaviour instead (#84). A warning is logged once per option.
|
|
67
|
+
// TODO: Throw an error instead in objection 4.0.
|
|
68
|
+
function warnOnUnknownOptions(upsertOptions) {
|
|
69
|
+
for (const name of Object.keys(upsertOptions)) {
|
|
70
|
+
if (!OPTION_NAMES.includes(name)) {
|
|
71
|
+
deprecate(
|
|
72
|
+
`Unknown graph option "${name}" is ignored. ` +
|
|
73
|
+
`Valid options are: ${OPTION_NAMES.join(', ')}. This will throw in objection 4.0.`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
46
79
|
function checkCanBeConvertedToModels(modelClass, objects) {
|
|
47
80
|
asArray(objects).forEach((obj) => {
|
|
48
81
|
if (!isObject(obj)) {
|
|
@@ -326,6 +359,10 @@ function executeActions(builder, actions) {
|
|
|
326
359
|
);
|
|
327
360
|
}
|
|
328
361
|
|
|
362
|
+
function isOnConflictOperation(op) {
|
|
363
|
+
return op.name === 'onConflict' || op.name === 'ignore' || op.name === 'merge';
|
|
364
|
+
}
|
|
365
|
+
|
|
329
366
|
function returnResult(objects, isArray) {
|
|
330
367
|
return () => (isArray ? objects : objects[0]);
|
|
331
368
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { GraphAction } = require('../GraphAction');
|
|
4
|
+
const { groupBy } = require('../../../utils/objectUtils');
|
|
5
|
+
const promiseUtils = require('../../../utils/promiseUtils');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Patches in the foreign keys of `BelongsToOneRelation` edges that `GraphInsert`
|
|
9
|
+
* had to defer to break a dependency cycle. The owner nodes of these edges were
|
|
10
|
+
* inserted without the foreign keys. `GraphInsertAction` already copied them to
|
|
11
|
+
* the owner objects once the related nodes were inserted, so all that's left to
|
|
12
|
+
* do is one patch query per owner.
|
|
13
|
+
*/
|
|
14
|
+
class DeferredRelationGraphInsertAction extends GraphAction {
|
|
15
|
+
constructor(graphData, { edges }) {
|
|
16
|
+
super(graphData);
|
|
17
|
+
|
|
18
|
+
// `BelongsToOneRelation` edges whose owner node needs to be patched.
|
|
19
|
+
this.edges = edges;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
run(builder) {
|
|
23
|
+
const edgesByOwner = groupBy(this.edges, (edge) => edge.ownerNode);
|
|
24
|
+
const owners = [...edgesByOwner.keys()];
|
|
25
|
+
const concurrency = this._getConcurrency(builder, owners);
|
|
26
|
+
|
|
27
|
+
return promiseUtils.map(
|
|
28
|
+
owners,
|
|
29
|
+
(owner) => this._patchOwner(builder, owner, edgesByOwner.get(owner)),
|
|
30
|
+
{ concurrency },
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_patchOwner(builder, ownerNode, edges) {
|
|
35
|
+
const { modelClass } = ownerNode;
|
|
36
|
+
const ownerIdProp = modelClass.getIdRelationProperty();
|
|
37
|
+
const patch = {};
|
|
38
|
+
|
|
39
|
+
for (const { relation } of edges) {
|
|
40
|
+
const { ownerProp } = relation;
|
|
41
|
+
|
|
42
|
+
ownerProp.forEach((i) => {
|
|
43
|
+
ownerProp.patch(patch, i, ownerProp.getProp(ownerNode.obj, i));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return modelClass
|
|
48
|
+
.query()
|
|
49
|
+
.childQueryOf(builder)
|
|
50
|
+
.patch(patch)
|
|
51
|
+
.whereInComposite(ownerIdProp.refs(builder), [ownerIdProp.getProps(ownerNode.obj)]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
DeferredRelationGraphInsertAction,
|
|
57
|
+
};
|