objection 3.6.0 → 3.7.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/lib/model/AjvValidator.js +33 -10
- package/lib/model/modelClone.js +0 -2
- package/lib/queryBuilder/QueryBuilder.js +56 -10
- package/lib/queryBuilder/RelationExpression.js +6 -4
- package/lib/queryBuilder/graph/GraphOptions.js +27 -55
- package/lib/queryBuilder/graph/GraphUpsert.js +22 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +2 -0
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +6 -1
- package/lib/queryBuilder/operations/UpdateOperation.js +11 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +3 -1
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +4 -9
- package/lib/queryBuilder/parsers/relationExpressionParser.js +1 -1
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +1 -1
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +4 -7
- package/lib/relations/Relation.js +1 -0
- package/lib/relations/RelationFindOperation.js +21 -39
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +4 -2
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +39 -15
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +31 -42
- package/lib/relations/manyToMany/ManyToManyRelation.js +8 -16
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +0 -13
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +2 -3
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +1 -5
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +1 -5
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +9 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +1 -5
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +7 -1
- package/lib/utils/identifierMapping.js +8 -17
- package/lib/utils/objectUtils.js +38 -6
- package/lib/utils/parseFieldExpression.js +10 -3
- package/package.json +1 -1
- package/typings/objection/index.d.ts +46 -26
- package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +0 -27
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +0 -16
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +0 -12
|
@@ -215,7 +215,22 @@ class AjvValidator extends Validator {
|
|
|
215
215
|
this.setupAjv(this.ajvQueryProps);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
for (;;) {
|
|
219
|
+
try {
|
|
220
|
+
return this.ajvQueryProps.compile(jsonSchema);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
// The schemas of other model classes that `$ref`s point to aren't added
|
|
223
|
+
// to this instance. Copy them over from the normal instance when needed.
|
|
224
|
+
const validator =
|
|
225
|
+
err instanceof Ajv.MissingRefError && this.ajv.getSchema(err.missingSchema);
|
|
226
|
+
|
|
227
|
+
if (!validator) {
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
this.ajvQueryProps.addSchema(validator.schema, err.missingSchema);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
219
234
|
}
|
|
220
235
|
}
|
|
221
236
|
|
|
@@ -357,7 +372,9 @@ function fieldExpressionPath(key) {
|
|
|
357
372
|
return [key];
|
|
358
373
|
}
|
|
359
374
|
|
|
360
|
-
|
|
375
|
+
// Numeric keys like the `0` in `tags:0` also access array elements when the
|
|
376
|
+
// json value is an array, so treat them like `[0]`.
|
|
377
|
+
if (parsed.access.some((access) => access.type !== 'object' || /^-?\d+$/.test(access.ref))) {
|
|
361
378
|
return null;
|
|
362
379
|
}
|
|
363
380
|
|
|
@@ -470,6 +487,11 @@ function jsonSchemaWithoutRequiredKeys(jsonSchema, keys) {
|
|
|
470
487
|
}
|
|
471
488
|
|
|
472
489
|
function jsonSchemaWithoutRequired(jsonSchema) {
|
|
490
|
+
// Boolean schemas (`true` / `false`) have no `required` to strip.
|
|
491
|
+
if (!isObject(jsonSchema)) {
|
|
492
|
+
return jsonSchema;
|
|
493
|
+
}
|
|
494
|
+
|
|
473
495
|
// `not` is intentionally left untouched: Stripping `required` inside it
|
|
474
496
|
// would turn it into a schema that rejects everything (e.g.
|
|
475
497
|
// `not: { required: ['a'] }` -> `not: {}`). Kept as is, it still holds for
|
|
@@ -506,17 +528,18 @@ function definitionsWithoutRequired(jsonSchema, prop) {
|
|
|
506
528
|
}
|
|
507
529
|
|
|
508
530
|
function subSchemaWithoutRequired(jsonSchema, prop) {
|
|
509
|
-
if (jsonSchema[prop]) {
|
|
531
|
+
if (jsonSchema[prop] !== undefined) {
|
|
510
532
|
if (Array.isArray(jsonSchema[prop])) {
|
|
511
533
|
const schemaArray = jsonSchema[prop].map(jsonSchemaWithoutRequired);
|
|
512
|
-
const nonEmptySchemaArray = schemaArray.filter(
|
|
534
|
+
const nonEmptySchemaArray = schemaArray.filter((schema) => !matchesAnything(schema));
|
|
513
535
|
|
|
514
536
|
// An `anyOf` or `oneOf` option that only consisted of `required` is
|
|
515
537
|
// empty once `required` is stripped, and an empty schema matches any
|
|
516
|
-
// patch, just like the stripped `required` keywords elsewhere
|
|
517
|
-
// whole keyword then: requiring the patch to
|
|
518
|
-
// options would reject valid patches, and
|
|
519
|
-
// option would fail every patch that also
|
|
538
|
+
// patch, just like the stripped `required` keywords elsewhere and the
|
|
539
|
+
// `true` schema. Drop the whole keyword then: requiring the patch to
|
|
540
|
+
// match one of the remaining options would reject valid patches, and
|
|
541
|
+
// with `oneOf` keeping the empty option would fail every patch that also
|
|
542
|
+
// matches another option.
|
|
520
543
|
if (prop !== 'allOf' && nonEmptySchemaArray.length !== schemaArray.length) {
|
|
521
544
|
return {};
|
|
522
545
|
} else if (nonEmptySchemaArray.length !== 0) {
|
|
@@ -548,8 +571,8 @@ function subSchemaWithoutRequired(jsonSchema, prop) {
|
|
|
548
571
|
}
|
|
549
572
|
}
|
|
550
573
|
|
|
551
|
-
function
|
|
552
|
-
return Object.keys(
|
|
574
|
+
function matchesAnything(schema) {
|
|
575
|
+
return schema === true || (isObject(schema) && Object.keys(schema).length === 0);
|
|
553
576
|
}
|
|
554
577
|
|
|
555
578
|
module.exports = {
|
package/lib/model/modelClone.js
CHANGED
|
@@ -8,6 +8,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
10
|
const { assertIdNotUndefined, assertHasIdColumn } = require('../utils/assert');
|
|
11
|
+
const { deprecate } = require('../utils/deprecate');
|
|
11
12
|
|
|
12
13
|
const { Selection } = require('./operations/select/Selection');
|
|
13
14
|
|
|
@@ -176,7 +177,7 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
isExplicitlyResolvedOrRejected() {
|
|
179
|
-
return
|
|
180
|
+
return this._explicitRejectValue !== null || this._explicitResolveValue !== null;
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
isExecutable() {
|
|
@@ -261,7 +262,14 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
261
262
|
eagerOp.graphOptions = { ...eagerOp.graphOptions, ...graphOptions };
|
|
262
263
|
|
|
263
264
|
if (joinOperation) {
|
|
264
|
-
eagerOp
|
|
265
|
+
if (eagerOp instanceof JoinEagerOperation) {
|
|
266
|
+
eagerOp.joinOperations.push({ expression: parsedExp, joinOperation });
|
|
267
|
+
} else {
|
|
268
|
+
deprecate(
|
|
269
|
+
'The joinOperation option only applies to withGraphJoined() and is ignored for the ' +
|
|
270
|
+
`fetched relations "${parsedExp.toString()}".`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
265
273
|
}
|
|
266
274
|
|
|
267
275
|
checkMixedEager(this);
|
|
@@ -501,17 +509,31 @@ class QueryBuilder extends QueryBuilderBase {
|
|
|
501
509
|
await joinEagerOp.onBefore3(builder);
|
|
502
510
|
}
|
|
503
511
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
knexBuilder = builder.toKnexQuery(knexBuilder).clearOrder();
|
|
512
|
+
// Also clear the order on the built knex query, since `orderBy` calls
|
|
513
|
+
// can be added at build time, e.g. by a relation's `modify`.
|
|
514
|
+
const buildQuery = (knexBuilder) => builder.toKnexQuery(knexBuilder).clearOrder();
|
|
508
515
|
|
|
509
|
-
|
|
516
|
+
const countQuery = knex.count('* as count').from((knexBuilder) => {
|
|
517
|
+
if (!joinEagerOp) {
|
|
518
|
+
buildQuery(knexBuilder);
|
|
519
|
+
} else {
|
|
510
520
|
// The joins of `withGraphJoined` return one row per joined child.
|
|
511
521
|
// Count the distinct root models instead.
|
|
512
|
-
const tableRef = builder.tableRef();
|
|
513
522
|
const idColumns = builder.modelClass().getIdColumnArray();
|
|
514
|
-
|
|
523
|
+
|
|
524
|
+
if (refersToSelections(builder)) {
|
|
525
|
+
// `having`, and `groupBy` with an alias, may refer to aliased
|
|
526
|
+
// selections, so keep them and select the distinct ids from the
|
|
527
|
+
// result instead.
|
|
528
|
+
knexBuilder.distinct(idColumns).from((knexBuilder) => {
|
|
529
|
+
buildQuery(knexBuilder).as('inner');
|
|
530
|
+
});
|
|
531
|
+
} else {
|
|
532
|
+
const tableRef = builder.tableRef();
|
|
533
|
+
buildQuery(knexBuilder)
|
|
534
|
+
.clearSelect()
|
|
535
|
+
.distinct(idColumns.map((col) => `${tableRef}.${col}`));
|
|
536
|
+
}
|
|
515
537
|
}
|
|
516
538
|
|
|
517
539
|
knexBuilder.as('temp');
|
|
@@ -1171,6 +1193,29 @@ function getEagerAlgorithmByName(builder, name) {
|
|
|
1171
1193
|
}
|
|
1172
1194
|
}
|
|
1173
1195
|
|
|
1196
|
+
// Returns true if the `having` or `groupBy` clauses of `builder` may refer to
|
|
1197
|
+
// aliased selections (#2329).
|
|
1198
|
+
function refersToSelections(builder) {
|
|
1199
|
+
if (builder.has(/having/)) {
|
|
1200
|
+
return true;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const aliases = builder
|
|
1204
|
+
.findAllSelections()
|
|
1205
|
+
.map((selection) => selection.alias)
|
|
1206
|
+
.filter((alias) => !!alias);
|
|
1207
|
+
|
|
1208
|
+
let refersToAlias = false;
|
|
1209
|
+
|
|
1210
|
+
builder.forEachOperation(/^groupBy$/, (op) => {
|
|
1211
|
+
if (flatten(op.args).some((arg) => aliases.includes(arg))) {
|
|
1212
|
+
refersToAlias = true;
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
return refersToAlias;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1174
1219
|
function getEagerAlgorithmOfOperation(builder, eagerOp) {
|
|
1175
1220
|
if (eagerOp instanceof JoinEagerOperation) {
|
|
1176
1221
|
return getJoinEagerAlgorithm(builder);
|
|
@@ -1198,7 +1243,8 @@ function groupByEagerAlgorithm(builder, expression) {
|
|
|
1198
1243
|
|
|
1199
1244
|
for (const childName of node.$childNames) {
|
|
1200
1245
|
const eagerOp = findEagerOperations(builder).find(
|
|
1201
|
-
(op) =>
|
|
1246
|
+
(op) =>
|
|
1247
|
+
op.expression.node.$allRecursive || op.expression.node.$childNames.includes(childName),
|
|
1202
1248
|
);
|
|
1203
1249
|
|
|
1204
1250
|
const algorithm = eagerOp ? getEagerAlgorithmOfOperation(builder, eagerOp) : defaultAlgorithm;
|
|
@@ -129,7 +129,8 @@ class RelationExpression {
|
|
|
129
129
|
return new RelationExpression(this.node, this.recursionDepth + 1);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
// Don't mistake inherited properties like `constructor` for children.
|
|
133
|
+
const child = this.node.$childNames.includes(childName) ? this.node[childName] : null;
|
|
133
134
|
|
|
134
135
|
if (child) {
|
|
135
136
|
return new RelationExpression(child);
|
|
@@ -292,7 +293,7 @@ function modelToNode(model, node) {
|
|
|
292
293
|
const relName = relationNames[r];
|
|
293
294
|
|
|
294
295
|
if (model[relName] !== undefined) {
|
|
295
|
-
let childNode = node[relName];
|
|
296
|
+
let childNode = node.$childNames.includes(relName) ? node[relName] : null;
|
|
296
297
|
|
|
297
298
|
if (!childNode) {
|
|
298
299
|
childNode = newNode(relName);
|
|
@@ -402,8 +403,9 @@ function mergeNodes(node1, node2) {
|
|
|
402
403
|
node.$childNames = union(node1.$childNames, node2.$childNames);
|
|
403
404
|
|
|
404
405
|
for (const childName of node.$childNames) {
|
|
405
|
-
|
|
406
|
-
const
|
|
406
|
+
// Don't mistake inherited properties like `constructor` for children.
|
|
407
|
+
const child1 = node1.$childNames.includes(childName) ? node1[childName] : null;
|
|
408
|
+
const child2 = node2.$childNames.includes(childName) ? node2[childName] : null;
|
|
407
409
|
|
|
408
410
|
if (child1 && child2) {
|
|
409
411
|
node[childName] = mergeNodes(child1, child2);
|
|
@@ -15,6 +15,7 @@ const UNRELATE = 'unrelate';
|
|
|
15
15
|
const INSERT_MISSING = 'insertMissing';
|
|
16
16
|
const FETCH_STRATEGY = 'fetchStrategy';
|
|
17
17
|
const ALLOW_REFS = 'allowRefs';
|
|
18
|
+
const ALLOW_MARKERS = 'allowMarkers';
|
|
18
19
|
|
|
19
20
|
const OPTION_NAMES = [
|
|
20
21
|
NO_RELATE,
|
|
@@ -28,6 +29,7 @@ const OPTION_NAMES = [
|
|
|
28
29
|
INSERT_MISSING,
|
|
29
30
|
FETCH_STRATEGY,
|
|
30
31
|
ALLOW_REFS,
|
|
32
|
+
ALLOW_MARKERS,
|
|
31
33
|
];
|
|
32
34
|
|
|
33
35
|
// The options that are matched against the relation path of each node, see
|
|
@@ -52,7 +54,8 @@ const FetchStrategy = {
|
|
|
52
54
|
|
|
53
55
|
class GraphOptions {
|
|
54
56
|
// `rootRelationPath` is the relation path of the root node of a recursive
|
|
55
|
-
// upsert in the original graph, against which relation
|
|
57
|
+
// upsert in the original graph, against which the relation paths and
|
|
58
|
+
// expressions of the options match.
|
|
56
59
|
constructor(options, rootRelationPath = []) {
|
|
57
60
|
if (options instanceof GraphOptions) {
|
|
58
61
|
this.options = options.options;
|
|
@@ -61,10 +64,6 @@ class GraphOptions {
|
|
|
61
64
|
this.options = parseRelationExpressions(options);
|
|
62
65
|
this.rootRelationPath = rootRelationPath;
|
|
63
66
|
}
|
|
64
|
-
|
|
65
|
-
// Caches the result of matching relation expressions against relation
|
|
66
|
-
// paths, by option name and relation path key.
|
|
67
|
-
this.expressionMatches = new Map();
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
isFetchStrategy(strategy) {
|
|
@@ -206,33 +205,23 @@ class GraphOptions {
|
|
|
206
205
|
return !!this.options[ALLOW_REFS];
|
|
207
206
|
}
|
|
208
207
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
for (const name of Object.keys(this.options)) {
|
|
214
|
-
const value = this.options[name];
|
|
215
|
-
|
|
216
|
-
if (Array.isArray(value)) {
|
|
217
|
-
newOpt[name] = value
|
|
218
|
-
.filter((it) => it === newRootRelationPath || it.startsWith(`${newRootRelationPath}.`))
|
|
219
|
-
.map((it) => it.slice(newRootRelationPath.length + 1))
|
|
220
|
-
.filter((it) => !!it);
|
|
221
|
-
} else {
|
|
222
|
-
newOpt[name] = value;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
208
|
+
allowMarkers() {
|
|
209
|
+
return !!this.options[ALLOW_MARKERS];
|
|
210
|
+
}
|
|
225
211
|
|
|
226
|
-
|
|
212
|
+
// The options of recursive upserts keep matching against the relation paths
|
|
213
|
+
// of the original graph, which the relation paths of the new root lead to.
|
|
214
|
+
rebasedOptions(newRoot) {
|
|
215
|
+
return new GraphOptions(this.options, [...this.rootRelationPath, ...newRoot.relationPath]);
|
|
227
216
|
}
|
|
228
217
|
|
|
229
218
|
_hasOption(node, optionName) {
|
|
230
219
|
const option = this.options[optionName];
|
|
231
220
|
|
|
232
221
|
if (isRelationExpression(option)) {
|
|
233
|
-
return this.
|
|
222
|
+
return matchesRelationExpression(option, this._relationPath(node));
|
|
234
223
|
} else if (Array.isArray(option)) {
|
|
235
|
-
return option.
|
|
224
|
+
return option.includes(this._relationPath(node).join('.'));
|
|
236
225
|
} else if (typeof option === 'boolean') {
|
|
237
226
|
return option;
|
|
238
227
|
} else if (option === undefined) {
|
|
@@ -244,36 +233,9 @@ class GraphOptions {
|
|
|
244
233
|
}
|
|
245
234
|
}
|
|
246
235
|
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// `'*'` matches all relations below it and recursive expressions like
|
|
251
|
-
// `'parent.^'` match every level of the recursion. The root has no relation
|
|
252
|
-
// path and never matches.
|
|
253
|
-
_matchesRelationExpression(node, optionName, expression) {
|
|
254
|
-
const relationPath = [...this.rootRelationPath, ...node.relationPath];
|
|
255
|
-
|
|
256
|
-
if (relationPath.length === 0) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const relationPathKey = relationPath.join('.');
|
|
261
|
-
|
|
262
|
-
let matches = this.expressionMatches.get(optionName);
|
|
263
|
-
|
|
264
|
-
if (!matches) {
|
|
265
|
-
matches = new Map();
|
|
266
|
-
this.expressionMatches.set(optionName, matches);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
let isMatch = matches.get(relationPathKey);
|
|
270
|
-
|
|
271
|
-
if (isMatch === undefined) {
|
|
272
|
-
isMatch = matchesRelationPath(expression, relationPath);
|
|
273
|
-
matches.set(relationPathKey, isMatch);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return isMatch;
|
|
236
|
+
// The relation path of the node in the original graph, see `rebasedOptions()`.
|
|
237
|
+
_relationPath(node) {
|
|
238
|
+
return [...this.rootRelationPath, ...node.relationPath];
|
|
277
239
|
}
|
|
278
240
|
}
|
|
279
241
|
|
|
@@ -344,7 +306,17 @@ function findAlias(node) {
|
|
|
344
306
|
return null;
|
|
345
307
|
}
|
|
346
308
|
|
|
347
|
-
|
|
309
|
+
// A relation path matches a relation expression when it leads to a leaf of
|
|
310
|
+
// the expression, like it would match the same relation paths in an array:
|
|
311
|
+
// `'movies.reviews'` matches `movies.reviews` but not `movies`. `'*'` matches
|
|
312
|
+
// all relations below it and recursive expressions like `'parent.^'` match
|
|
313
|
+
// every level of the recursion. The root has no relation path and never
|
|
314
|
+
// matches.
|
|
315
|
+
function matchesRelationExpression(expression, relationPath) {
|
|
316
|
+
if (relationPath.length === 0) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
348
320
|
for (const relationName of relationPath) {
|
|
349
321
|
expression = expression.childExpression(relationName);
|
|
350
322
|
|
|
@@ -245,6 +245,7 @@ function checkForErrors(builder) {
|
|
|
245
245
|
checkForNotFoundErrors(graphData, builder);
|
|
246
246
|
checkForUnallowedRelationErrors(graphData, builder);
|
|
247
247
|
checkForUnallowedReferenceErrors(graphData, builder);
|
|
248
|
+
checkForUnallowedMarkerErrors(graphData, builder);
|
|
248
249
|
checkForRootUnrelateOrDeleteErrors(graphData, builder);
|
|
249
250
|
|
|
250
251
|
if (graphData.graphOptions.isInsertOnly()) {
|
|
@@ -344,6 +345,27 @@ function checkForUnallowedReferenceErrors(graphData, builder) {
|
|
|
344
345
|
}
|
|
345
346
|
}
|
|
346
347
|
|
|
348
|
+
// `#unrelate` and `#delete` markers come from the graph data, which may be
|
|
349
|
+
// untrusted input. Like `#ref`, they have to be allowed explicitly (#2410).
|
|
350
|
+
function checkForUnallowedMarkerErrors(graphData, builder) {
|
|
351
|
+
const { graph, graphOptions } = graphData;
|
|
352
|
+
|
|
353
|
+
if (graphOptions.allowMarkers()) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const node = graph.nodes.find((node) => node.toBeUnrelatedOrDeleted);
|
|
358
|
+
|
|
359
|
+
if (node) {
|
|
360
|
+
const { graphUnrelateProp, graphDeleteProp } = node.modelClass;
|
|
361
|
+
|
|
362
|
+
throw builder.modelClass().createValidationError({
|
|
363
|
+
type: ValidationErrorType.InvalidGraph,
|
|
364
|
+
message: `${graphUnrelateProp} and ${graphDeleteProp} markers are not allowed in a graph by default. see the allowMarkers insert/upsert graph option`,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
347
369
|
function checkForRootUnrelateOrDeleteErrors(graphData, builder) {
|
|
348
370
|
for (const node of graphData.graph.nodes) {
|
|
349
371
|
if (!node.parentEdge && node.toBeUnrelatedOrDeleted) {
|
|
@@ -140,6 +140,8 @@ class GraphInsert extends GraphOperation {
|
|
|
140
140
|
edge.relation.isObjectionBelongsToOneRelation &&
|
|
141
141
|
edge.isOwnerNode(node) &&
|
|
142
142
|
edge.relatedNode.isReference &&
|
|
143
|
+
// The owner is patched by its id, which models without one don't have (#2305).
|
|
144
|
+
node.modelClass.getIdColumnArray().length > 0 &&
|
|
143
145
|
// Both ends need to be inserted for the foreign key to be patched in later.
|
|
144
146
|
this.graphOptions.shouldInsert(node, this.graphData) &&
|
|
145
147
|
this.graphOptions.shouldInsert(edge.relatedNode.referencedNode, this.graphData) &&
|
|
@@ -37,7 +37,12 @@ class InstanceUpdateOperation extends UpdateOperation {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
result = await super.onAfter2(builder, result);
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
// A query resolved without updating anything, e.g. with `none()`, leaves
|
|
42
|
+
// the instance untouched.
|
|
43
|
+
if (!this.isResolvedWithoutUpdate(builder)) {
|
|
44
|
+
this.instance.$set(this.model);
|
|
45
|
+
}
|
|
41
46
|
|
|
42
47
|
if (isObject(result)) {
|
|
43
48
|
this.instance.$set(result);
|
|
@@ -14,6 +14,7 @@ class UpdateOperation extends QueryBuilderOperation {
|
|
|
14
14
|
|
|
15
15
|
this.model = null;
|
|
16
16
|
this.modelOptions = Object.assign({}, this.opt.modelOptions || {});
|
|
17
|
+
this.hasNothingToUpdate = false;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
onAdd(builder, args) {
|
|
@@ -34,10 +35,19 @@ class UpdateOperation extends QueryBuilderOperation {
|
|
|
34
35
|
|
|
35
36
|
if (isEmpty(row)) {
|
|
36
37
|
// Resolve the query if there is nothing to update.
|
|
38
|
+
this.hasNothingToUpdate = !builder.isExplicitlyResolvedOrRejected();
|
|
37
39
|
builder.resolve(0);
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
// Returns true if the query was resolved without updating anything, e.g.
|
|
44
|
+
// with `none()` or `resolve()` in a hook, but not if it was only resolved
|
|
45
|
+
// because there is nothing to update in the table, as the update still
|
|
46
|
+
// applies to everything else (#2184).
|
|
47
|
+
isResolvedWithoutUpdate(builder) {
|
|
48
|
+
return builder.isExplicitlyResolvedOrRejected() && !this.hasNothingToUpdate;
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
onBuildKnex(knexBuilder, builder) {
|
|
42
52
|
const json = this.model.$toDatabaseJson(builder);
|
|
43
53
|
const convertedJson = convertFieldExpressionsToRaw(builder, this.model, json);
|
|
@@ -56,6 +66,7 @@ class UpdateOperation extends QueryBuilderOperation {
|
|
|
56
66
|
clone() {
|
|
57
67
|
const clone = super.clone();
|
|
58
68
|
clone.model = this.model;
|
|
69
|
+
clone.hasNothingToUpdate = this.hasNothingToUpdate;
|
|
59
70
|
return clone;
|
|
60
71
|
}
|
|
61
72
|
}
|
|
@@ -42,7 +42,9 @@ class WhereInCompositeMsSqlOperation extends ObjectionToKnexConvertingOperation
|
|
|
42
42
|
knexBuilder,
|
|
43
43
|
columns,
|
|
44
44
|
helperColumns,
|
|
45
|
-
|
|
45
|
+
// knex only accepts strings as aliases, so the subquery is aliased in
|
|
46
|
+
// a raw query.
|
|
47
|
+
knex.raw(`? AS V(${helperColumns.map((_) => '??')})`, [values, ...helperColumns]),
|
|
46
48
|
);
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -48,18 +48,13 @@ function isCompositeKey(columns) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function buildCompositeArgs(columns, values) {
|
|
51
|
-
if (!Array.isArray(values)) {
|
|
52
|
-
// If the `values` is not an array of values but a function or a subquery
|
|
53
|
-
// we have no way to implement this method.
|
|
54
|
-
throw new Error(`sqlite doesn't support multi-column where in clauses`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (values.length === 0) {
|
|
51
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
58
52
|
// knex turns an empty `whereIn` into `1 = 0` and an empty `whereNotIn`
|
|
59
|
-
// into `1 = 1`.
|
|
53
|
+
// into `1 = 1`. Subqueries are compared as row values, which sqlite
|
|
54
|
+
// supports since version 3.15.
|
|
60
55
|
return {
|
|
61
56
|
method: 'whereIn',
|
|
62
|
-
args: [columns,
|
|
57
|
+
args: [columns, values],
|
|
63
58
|
};
|
|
64
59
|
}
|
|
65
60
|
|
|
@@ -943,7 +943,7 @@ function peg$parse(input, options) {
|
|
|
943
943
|
}
|
|
944
944
|
|
|
945
945
|
function assertDuplicateRelation(node, expr) {
|
|
946
|
-
if (expr.$name
|
|
946
|
+
if (Object.prototype.hasOwnProperty.call(node, expr.$name)) {
|
|
947
947
|
console.warn(
|
|
948
948
|
`Duplicate relation "${expr.$name}" in a relation expression. You should use "a.[b, c]" instead of "[a.b, a.c]". This will cause an error in objection 2.0`,
|
|
949
949
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
function assertDuplicateRelation(node, expr) {
|
|
3
|
-
if (expr.$name
|
|
3
|
+
if (Object.prototype.hasOwnProperty.call(node, expr.$name)) {
|
|
4
4
|
console.warn(`Duplicate relation "${expr.$name}" in a relation expression. You should use "a.[b, c]" instead of "[a.b, a.c]". This will cause an error in objection 2.0`);
|
|
5
5
|
|
|
6
6
|
// TODO: enable for v2.0.
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { QueryTransformation } = require('./QueryTransformation');
|
|
4
4
|
const { isMySql } = require('../../utils/knexUtils');
|
|
5
|
-
const { once } = require('../../utils/objectUtils');
|
|
6
|
-
const getJoinBuilder = once(() => require('../JoinBuilder').JoinBuilder);
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Mysql doesn't allow queries like this:
|
|
@@ -32,11 +30,10 @@ class WrapMysqlModifySubqueryTransformation extends QueryTransformation {
|
|
|
32
30
|
return query;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return query;
|
|
33
|
+
// Subqueries in where groups and join clauses are converted with a
|
|
34
|
+
// partial builder as their parent. Use the query that they belong to.
|
|
35
|
+
while (parentQuery.isPartial() && parentQuery.parentQuery()) {
|
|
36
|
+
parentQuery = parentQuery.parentQuery();
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
// This transformation only applies to update and delete queries.
|
|
@@ -103,6 +103,7 @@ class Relation {
|
|
|
103
103
|
relation.joinTableRelatedProp = this.joinTableRelatedProp;
|
|
104
104
|
relation.joinTableBeforeInsert = this.joinTableBeforeInsert;
|
|
105
105
|
relation.joinTableExtras = this.joinTableExtras;
|
|
106
|
+
relation.joinTableModify = this.joinTableModify;
|
|
106
107
|
|
|
107
108
|
return relation;
|
|
108
109
|
}
|