objection 3.5.0 → 3.6.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 +3 -3
- package/lib/queryBuilder/InternalOptions.js +2 -0
- package/lib/queryBuilder/graph/GraphOptions.js +153 -6
- package/lib/queryBuilder/graph/GraphUpsert.js +35 -0
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +3 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +434 -364
- package/lib/queryBuilder/parsers/relationExpressionParser.js +512 -547
- package/lib/relations/Relation.js +6 -0
- package/lib/relations/RelationFindOperation.js +6 -1
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +9 -0
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +5 -1
- package/lib/relations/manyToMany/ManyToManyRelation.js +7 -2
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +21 -1
- package/lib/utils/objectUtils.js +77 -2
- package/package.json +5 -2
- package/typings/objection/index.d.ts +10 -10
- package/lib/utils/clone.js +0 -2329
|
@@ -83,6 +83,12 @@ class Relation {
|
|
|
83
83
|
return false;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Whether owner models may legitimately lack the owner join property at
|
|
87
|
+
// `index`, so that it isn't reported as missing (#132).
|
|
88
|
+
isOptionalOwnerProp(_index) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
clone() {
|
|
87
93
|
const relation = new this.constructor(this.name, this.ownerModelClass);
|
|
88
94
|
|
|
@@ -85,12 +85,17 @@ class RelationFindOperation extends FindOperation {
|
|
|
85
85
|
|
|
86
86
|
// Owner models without the join columns, e.g. loaded with a partial
|
|
87
87
|
// `select()`, silently get no related models (#1832). Only `undefined`
|
|
88
|
-
// is reported, as `null` is a legit value for a missing relation
|
|
88
|
+
// is reported, as `null` is a legit value for a missing relation, and
|
|
89
|
+
// owner props that may legitimately be unset are skipped (#132).
|
|
89
90
|
// TODO: Consider throwing an error instead in objection 4.0.
|
|
90
91
|
warnOnMissingOwnerProps() {
|
|
91
92
|
const { ownerProp } = this.relation;
|
|
92
93
|
|
|
93
94
|
for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
|
|
95
|
+
if (this.relation.isOptionalOwnerProp(c)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
94
99
|
if (this.owner.modelArray.some((owner) => ownerProp.getProp(owner, c) === undefined)) {
|
|
95
100
|
deprecate(
|
|
96
101
|
`Fetching relation "${this.relation.name}" of ${this.relation.ownerModelClass.name}: ` +
|
|
@@ -11,6 +11,15 @@ class BelongsToOneRelation extends Relation {
|
|
|
11
11
|
return true;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// The foreign key is unset when no related model was ever assigned, e.g. on
|
|
15
|
+
// models returned by `insert()` or created with `fromJson()`, where no
|
|
16
|
+
// related model is the right result. This can't be told apart from a
|
|
17
|
+
// foreign key left out of a partial `select()`. Foreign keys that are part
|
|
18
|
+
// of the id are always expected to be set (#132).
|
|
19
|
+
isOptionalOwnerProp(index) {
|
|
20
|
+
return !this.ownerModelClass.getIdPropertyArray().includes(this.ownerProp.props[index]);
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
insert(_, owner) {
|
|
15
24
|
return new BelongsToOneInsertOperation('insert', {
|
|
16
25
|
relation: this,
|
|
@@ -16,10 +16,13 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
16
16
|
constructor(...args) {
|
|
17
17
|
super(...args);
|
|
18
18
|
this.modifyFilterSubquery = null;
|
|
19
|
+
this.skipRelationFilter = false;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
// Updates of rows that are known to be related, like the patches of
|
|
23
|
+
// `upsertGraph()`, keep their own filters (#135).
|
|
21
24
|
get modifyMainQuery() {
|
|
22
|
-
return
|
|
25
|
+
return !this.skipRelationFilter;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
// At this point `builder` should only have the user's own wheres and joins. There can
|
|
@@ -108,6 +111,7 @@ const ManyToManyModifyMixin = (Operation) => {
|
|
|
108
111
|
clone() {
|
|
109
112
|
const clone = super.clone();
|
|
110
113
|
clone.modifyFilterSubquery = this.modifyFilterSubquery;
|
|
114
|
+
clone.skipRelationFilter = this.skipRelationFilter;
|
|
111
115
|
return clone;
|
|
112
116
|
}
|
|
113
117
|
};
|
|
@@ -93,7 +93,8 @@ class ManyToManyRelation extends Relation {
|
|
|
93
93
|
|
|
94
94
|
// The modify operations can't tell the join rows apart from the related rows
|
|
95
95
|
// if the join table is also the related table, so they would modify the
|
|
96
|
-
// wrong rows (#1803).
|
|
96
|
+
// wrong rows (#1803). Inserting and relating would insert the join rows as
|
|
97
|
+
// new related rows (#133).
|
|
97
98
|
assertCanModifyJoinTable(builder, operationName) {
|
|
98
99
|
if (this.isJoinTableRelatedTable(builder)) {
|
|
99
100
|
throw this.createError(
|
|
@@ -188,7 +189,9 @@ class ManyToManyRelation extends Relation {
|
|
|
188
189
|
});
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
insert(
|
|
192
|
+
insert(builder, owner) {
|
|
193
|
+
this.assertCanModifyJoinTable(builder, 'insert');
|
|
194
|
+
|
|
192
195
|
return new ManyToManyInsertOperation('insert', {
|
|
193
196
|
relation: this,
|
|
194
197
|
owner,
|
|
@@ -218,6 +221,8 @@ class ManyToManyRelation extends Relation {
|
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
relate(builder, owner) {
|
|
224
|
+
this.assertCanModifyJoinTable(builder, 'relate');
|
|
225
|
+
|
|
221
226
|
return new ManyToManyRelateOperation('relate', {
|
|
222
227
|
relation: this,
|
|
223
228
|
owner,
|
|
@@ -18,6 +18,11 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
18
18
|
onAdd(builder, args) {
|
|
19
19
|
const obj = args[0];
|
|
20
20
|
|
|
21
|
+
// The modified rows are known to be related to the owner, so they are
|
|
22
|
+
// filtered by their own ids instead of a subquery that joins the join
|
|
23
|
+
// table. On MySQL, concurrent updates with that subquery deadlock (#135).
|
|
24
|
+
this.skipRelationFilter = builder.internalOptions().skipRelationFilter;
|
|
25
|
+
|
|
21
26
|
// Copy all extra properties to the `joinTablePatch` object.
|
|
22
27
|
for (const extra of this.relation.joinTableExtras) {
|
|
23
28
|
if (extra.aliasProp in obj) {
|
|
@@ -50,7 +55,9 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
50
55
|
.patch(this.joinTablePatch);
|
|
51
56
|
|
|
52
57
|
addWhereGroup(joinTableUpdateQuery, () =>
|
|
53
|
-
this.
|
|
58
|
+
this.skipRelationFilter
|
|
59
|
+
? this.applyIdFilterForJoinTable(joinTableUpdateQuery)
|
|
60
|
+
: this.applyModifyFilterForJoinTable(joinTableUpdateQuery),
|
|
54
61
|
);
|
|
55
62
|
addWhereGroup(joinTableUpdateQuery, () =>
|
|
56
63
|
joinTableUpdateQuery.modify(this.relation.joinTableModify),
|
|
@@ -63,6 +70,19 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
// Filters the join rows by the ids of the owner and the updated model.
|
|
74
|
+
applyIdFilterForJoinTable(builder) {
|
|
75
|
+
return builder
|
|
76
|
+
.whereInComposite(
|
|
77
|
+
this.relation.joinTableOwnerProp.refs(builder),
|
|
78
|
+
this.owner.getProps(this.relation),
|
|
79
|
+
)
|
|
80
|
+
.whereComposite(
|
|
81
|
+
this.relation.joinTableRelatedProp.refs(builder),
|
|
82
|
+
this.relation.relatedProp.getProps(this.model),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
/* istanbul ignore next */
|
|
67
87
|
applyModifyFilterForRelatedTable(builder) {
|
|
68
88
|
throw new Error('not implemented');
|
package/lib/utils/objectUtils.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { clone, cloneDeep } = require('./clone');
|
|
4
3
|
const SMALL_ARRAY_SIZE = 10;
|
|
5
4
|
|
|
6
5
|
function isEmpty(item) {
|
|
@@ -389,6 +388,83 @@ function mergeMaps(map1, map2) {
|
|
|
389
388
|
return map;
|
|
390
389
|
}
|
|
391
390
|
|
|
391
|
+
// Clones `value` deeply, like lodash's `cloneDeep()`: objects keep their
|
|
392
|
+
// prototype and get copies of their own enumerable properties, symbols
|
|
393
|
+
// included. Arrays, dates, regular expressions, maps, sets, buffers, typed
|
|
394
|
+
// arrays and array buffers are copied, circular references are kept.
|
|
395
|
+
// Anything else, like functions and errors, is kept as it is.
|
|
396
|
+
function cloneDeep(value, clones = new Map()) {
|
|
397
|
+
if (!isObject(value)) {
|
|
398
|
+
return value;
|
|
399
|
+
} else if (clones.has(value)) {
|
|
400
|
+
return clones.get(value);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const Ctor = value.constructor;
|
|
404
|
+
const tag = Object.prototype.toString.call(value);
|
|
405
|
+
let copy;
|
|
406
|
+
|
|
407
|
+
if (tag === '[object Object]') {
|
|
408
|
+
copy = Object.create(Object.getPrototypeOf(value));
|
|
409
|
+
clones.set(value, copy);
|
|
410
|
+
|
|
411
|
+
for (const key of Object.keys(value)) {
|
|
412
|
+
const item = cloneDeep(value[key], clones);
|
|
413
|
+
|
|
414
|
+
if (key === '__proto__') {
|
|
415
|
+
// Assigning it would set the prototype instead of the property.
|
|
416
|
+
Object.defineProperty(copy, key, {
|
|
417
|
+
value: item,
|
|
418
|
+
writable: true,
|
|
419
|
+
enumerable: true,
|
|
420
|
+
configurable: true,
|
|
421
|
+
});
|
|
422
|
+
} else {
|
|
423
|
+
copy[key] = item;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
for (const symbol of Object.getOwnPropertySymbols(value)) {
|
|
428
|
+
if (Object.prototype.propertyIsEnumerable.call(value, symbol)) {
|
|
429
|
+
copy[symbol] = cloneDeep(value[symbol], clones);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} else if (tag === '[object Array]') {
|
|
433
|
+
copy = new Ctor(value.length);
|
|
434
|
+
clones.set(value, copy);
|
|
435
|
+
|
|
436
|
+
for (let i = 0, l = value.length; i < l; ++i) {
|
|
437
|
+
copy[i] = cloneDeep(value[i], clones);
|
|
438
|
+
}
|
|
439
|
+
} else if (Buffer.isBuffer(value)) {
|
|
440
|
+
copy = Buffer.from(value);
|
|
441
|
+
} else if (tag === '[object DataView]') {
|
|
442
|
+
copy = new Ctor(value.buffer.slice(0), value.byteOffset, value.byteLength);
|
|
443
|
+
} else if (ArrayBuffer.isView(value)) {
|
|
444
|
+
copy = value.slice();
|
|
445
|
+
} else if (tag === '[object ArrayBuffer]') {
|
|
446
|
+
copy = value.slice(0);
|
|
447
|
+
} else if (tag === '[object Date]') {
|
|
448
|
+
copy = new Ctor(+value);
|
|
449
|
+
} else if (tag === '[object RegExp]') {
|
|
450
|
+
copy = new Ctor(value.source, value.flags);
|
|
451
|
+
copy.lastIndex = value.lastIndex;
|
|
452
|
+
} else if (tag === '[object Map]') {
|
|
453
|
+
copy = new Ctor();
|
|
454
|
+
clones.set(value, copy);
|
|
455
|
+
value.forEach((item, key) => copy.set(key, cloneDeep(item, clones)));
|
|
456
|
+
} else if (tag === '[object Set]') {
|
|
457
|
+
copy = new Ctor();
|
|
458
|
+
clones.set(value, copy);
|
|
459
|
+
value.forEach((item) => copy.add(cloneDeep(item, clones)));
|
|
460
|
+
} else {
|
|
461
|
+
return value;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
clones.set(value, copy);
|
|
465
|
+
return copy;
|
|
466
|
+
}
|
|
467
|
+
|
|
392
468
|
module.exports = {
|
|
393
469
|
isEmpty,
|
|
394
470
|
isString,
|
|
@@ -411,7 +487,6 @@ module.exports = {
|
|
|
411
487
|
values,
|
|
412
488
|
union,
|
|
413
489
|
chunk,
|
|
414
|
-
clone,
|
|
415
490
|
omit,
|
|
416
491
|
once,
|
|
417
492
|
last,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "objection",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "An SQL-friendly ORM for Node.js",
|
|
5
5
|
"main": "lib/objection.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
"test": "npm run eslint && mocha --slow 10 --timeout 15000 --reporter spec --recursive tests --exclude \"tests/unit/relations/files/**\" && npm run test:typings",
|
|
9
9
|
"test:fast": "mocha --slow 10 --timeout 15000 --reporter spec --recursive tests --bail --exclude \"tests/unit/relations/files/**\"",
|
|
10
10
|
"test:typings": "tsc && prettier --check \"typings/**/*.ts\" \"tests/ts/**/*.ts\"",
|
|
11
|
+
"db:up": "docker compose up -d --wait",
|
|
12
|
+
"db:down": "docker compose down -v",
|
|
13
|
+
"build:parsers": "npx --yes peggy@5.1.0 --format commonjs -o lib/queryBuilder/parsers/jsonFieldExpressionParser.js lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs && npx --yes peggy@5.1.0 --format commonjs -o lib/queryBuilder/parsers/relationExpressionParser.js lib/queryBuilder/parsers/relationExpressionParser.pegjs && prettier --write \"lib/queryBuilder/parsers/*Parser.js\"",
|
|
11
14
|
"prettier": "prettier --write \"{examples,lib,tests,testUtils,typings,doc}/**/*.{js,ts}\"",
|
|
12
|
-
"eslint": "eslint --format codeframe \"examples/**/*.js\" \"lib/**/*.js\" \"tests/**/*.js\"",
|
|
15
|
+
"eslint": "eslint --format codeframe \"examples/**/*.js\" \"lib/**/*.js\" \"tests/**/*.js\" \"testUtils/**/*.js\" \"*.js\"",
|
|
13
16
|
"docs:dev": "vitepress dev doc",
|
|
14
17
|
"docs:build": "vitepress build doc",
|
|
15
18
|
"docs:preview": "vitepress preview doc"
|
|
@@ -1058,7 +1058,7 @@ declare namespace Objection {
|
|
|
1058
1058
|
}
|
|
1059
1059
|
|
|
1060
1060
|
export interface InsertGraphOptions {
|
|
1061
|
-
relate?: boolean | string[];
|
|
1061
|
+
relate?: boolean | string | string[];
|
|
1062
1062
|
allowRefs?: boolean;
|
|
1063
1063
|
}
|
|
1064
1064
|
|
|
@@ -1077,15 +1077,15 @@ declare namespace Objection {
|
|
|
1077
1077
|
}
|
|
1078
1078
|
|
|
1079
1079
|
export interface UpsertGraphOptions {
|
|
1080
|
-
relate?: boolean | string[];
|
|
1081
|
-
unrelate?: boolean | string[];
|
|
1082
|
-
insertMissing?: boolean | string[];
|
|
1083
|
-
update?: boolean | string[];
|
|
1084
|
-
noInsert?: boolean | string[];
|
|
1085
|
-
noUpdate?: boolean | string[];
|
|
1086
|
-
noDelete?: boolean | string[];
|
|
1087
|
-
noRelate?: boolean | string[];
|
|
1088
|
-
noUnrelate?: boolean | string[];
|
|
1080
|
+
relate?: boolean | string | string[];
|
|
1081
|
+
unrelate?: boolean | string | string[];
|
|
1082
|
+
insertMissing?: boolean | string | string[];
|
|
1083
|
+
update?: boolean | string | string[];
|
|
1084
|
+
noInsert?: boolean | string | string[];
|
|
1085
|
+
noUpdate?: boolean | string | string[];
|
|
1086
|
+
noDelete?: boolean | string | string[];
|
|
1087
|
+
noRelate?: boolean | string | string[];
|
|
1088
|
+
noUnrelate?: boolean | string | string[];
|
|
1089
1089
|
allowRefs?: boolean;
|
|
1090
1090
|
}
|
|
1091
1091
|
|