Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 24002c5

Browse files
committed
fixes Vincit#1455
1 parent 9e9fe22 commit 24002c5

File tree

4 files changed

+203
-11
lines changed

4 files changed

+203
-11
lines changed

doc/changelog/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
* Only the first argument of [modify](/api/query-builder/other-methods.html#modify) query builder method is interpreted as a modifier name. Rest of the arguments are passed as arguments to the modifier. The first argument can be an array of modifier names.
1818

19+
## 1.6.10
20+
21+
* Fixes [#1455](https://github.com/Vincit/objection.js/issues/1455)
22+
1923
## 1.6.9
2024

2125
* Revert fix for [#1089](https://github.com/Vincit/objection.js/issues/1089). It was causing more bugs than it fixed. #1089 will be addressed in 2.0.

lib/queryBuilder/graph/GraphUpsert.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,28 @@ function pruneRelatedBranches(graph, currentGraph, graphOptions) {
9393

9494
function pruneDeletedBranches(graph, currentGraph) {
9595
const deleteNodes = currentGraph.nodes.filter(currentNode => !graph.nodeForNode(currentNode));
96+
const roots = findRoots(deleteNodes);
97+
98+
// Don't delete relations the current graph doesn't even mention.
99+
// So if the parent node doesn't even have the relation, it's not
100+
// supposed to be deleted.
101+
const rootsNotInRelation = roots.filter(deleteRoot => {
102+
if (!deleteRoot.parentNode) {
103+
return false;
104+
}
105+
106+
const { relation } = deleteRoot.parentEdge;
107+
const parentNode = graph.nodeForNode(deleteRoot.parentNode);
108+
109+
if (!parentNode) {
110+
return false;
111+
}
96112

97-
removeBranchesFromGraph(findRoots(deleteNodes), currentGraph);
113+
return parentNode.obj[relation.name] === undefined;
114+
});
115+
116+
removeBranchesFromGraph(roots, currentGraph);
117+
removeNodesFromGraph(new Set(rootsNotInRelation), currentGraph);
98118
}
99119

100120
function findRoots(nodes) {
@@ -125,6 +145,10 @@ function removeBranchesFromGraph(branchRoots, graph) {
125145
)
126146
);
127147

148+
removeNodesFromGraph(nodesToRemove, graph);
149+
}
150+
151+
function removeNodesFromGraph(nodesToRemove, graph) {
128152
const edgesToRemove = new Set();
129153

130154
for (const node of nodesToRemove) {

tests/integration/misc/#1455.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
const { Model, transaction } = require('../../../');
2+
const { expect } = require('chai');
3+
4+
module.exports = session => {
5+
describe('UpsertGraph deletes rows for relation which is not mentioned in graph #1455', () => {
6+
let knex = session.knex;
7+
let Role;
8+
9+
beforeEach(() => {
10+
const { knex } = session;
11+
12+
return knex.schema
13+
.dropTableIfExists('roles')
14+
.then(() => knex.schema.dropTableIfExists('sets'))
15+
.then(() => knex.schema.dropTableIfExists('setsAttributes'))
16+
.then(() => {
17+
return knex.schema.createTable('roles', table => {
18+
table.increments();
19+
table.string('name').notNullable();
20+
});
21+
})
22+
.then(() => {
23+
return knex.schema.createTable('sets', table => {
24+
table.increments();
25+
table.string('name').notNullable();
26+
table
27+
.integer('roleId')
28+
.unsigned()
29+
.notNullable();
30+
});
31+
})
32+
.then(() => {
33+
return knex.schema.createTable('setsAttributes', table => {
34+
table.increments();
35+
table.string('name').notNullable();
36+
table
37+
.integer('setId')
38+
.unsigned()
39+
.notNullable();
40+
});
41+
});
42+
});
43+
44+
afterEach(() => {
45+
return knex.schema
46+
.dropTableIfExists('roles')
47+
.then(() => knex.schema.dropTableIfExists('sets'))
48+
.then(() => knex.schema.dropTableIfExists('setsAttributes'));
49+
});
50+
51+
beforeEach(() => {
52+
const { knex } = session;
53+
54+
class BaseModel extends Model {
55+
static get useLimitInFirst() {
56+
return true;
57+
}
58+
}
59+
60+
class SetAttribute extends BaseModel {
61+
static get tableName() {
62+
return 'setsAttributes';
63+
}
64+
}
65+
66+
class Set extends BaseModel {
67+
static get tableName() {
68+
return 'sets';
69+
}
70+
71+
static get relationMappings() {
72+
return {
73+
setAttributes: {
74+
relation: BaseModel.HasManyRelation,
75+
modelClass: SetAttribute,
76+
join: { from: 'sets.id', to: 'setsAttributes.setId' }
77+
}
78+
};
79+
}
80+
}
81+
82+
Role = class Role extends BaseModel {
83+
static get tableName() {
84+
return 'roles';
85+
}
86+
87+
static get relationMappings() {
88+
return {
89+
sets: {
90+
relation: BaseModel.HasManyRelation,
91+
modelClass: Set,
92+
join: { from: 'roles.id', to: 'sets.roleId' }
93+
}
94+
};
95+
}
96+
};
97+
98+
BaseModel.knex(knex);
99+
});
100+
101+
it('test', () => {
102+
return transaction(Role.knex(), trx =>
103+
Role.query(trx).insertGraph({
104+
name: 'First Role',
105+
sets: [
106+
{
107+
name: 'First Set',
108+
setAttributes: [{ name: 'First SetAttribute' }, { name: 'Second SetAttribute' }]
109+
}
110+
]
111+
})
112+
)
113+
.then(role => {
114+
return transaction(Role.knex(), trx =>
115+
Role.query(trx).upsertGraph({
116+
id: role.id,
117+
sets: [
118+
{ id: role.sets[0].id },
119+
{
120+
name: 'Second Set',
121+
setAttributes: [{ name: 'First SetAttribute' }, { name: 'Second SetAttribute' }]
122+
}
123+
]
124+
})
125+
);
126+
})
127+
.then(() => {
128+
return Role.query()
129+
.first()
130+
.eager('sets(orderById).setAttributes(orderById)', {
131+
orderById(query) {
132+
query.orderBy('id');
133+
}
134+
});
135+
})
136+
.then(setsAfterUpsertGraph => {
137+
expect(setsAfterUpsertGraph).to.eql({
138+
id: 1,
139+
name: 'First Role',
140+
sets: [
141+
{
142+
id: 1,
143+
name: 'First Set',
144+
roleId: 1,
145+
146+
setAttributes: [
147+
{ id: 1, name: 'First SetAttribute', setId: 1 },
148+
{ id: 2, name: 'Second SetAttribute', setId: 1 }
149+
]
150+
},
151+
{
152+
id: 2,
153+
name: 'Second Set',
154+
roleId: 1,
155+
156+
setAttributes: [
157+
{ id: 3, name: 'First SetAttribute', setId: 2 },
158+
{ id: 4, name: 'Second SetAttribute', setId: 2 }
159+
]
160+
}
161+
]
162+
});
163+
});
164+
});
165+
});
166+
};

tests/unit/relations/ManyToManyRelation.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,9 @@ describe('ManyToManyRelation', () => {
253253
}
254254
});
255255
}).to.throwException(err => {
256-
expect(
257-
err.message.startsWith(
258-
"OwnerModel.relationMappings.testRelation: Cannot find module '/not/a/path/to/a/model'"
259-
)
260-
).to.equal(true);
256+
expect(err.message).to.contain(
257+
"OwnerModel.relationMappings.testRelation: Cannot find module '/not/a/path/to/a/model'"
258+
);
261259
});
262260
});
263261

@@ -277,7 +275,7 @@ describe('ManyToManyRelation', () => {
277275
}
278276
});
279277
}).to.throwException(err => {
280-
expect(err.message).to.equal(
278+
expect(err.message).to.contain(
281279
'OwnerModel.relationMappings.testRelation: join.through must be an object that describes the join table. For example: {from: "JoinTable.someId", to: "JoinTable.someOtherId"}'
282280
);
283281
});
@@ -299,7 +297,7 @@ describe('ManyToManyRelation', () => {
299297
}
300298
});
301299
}).to.throwException(err => {
302-
expect(err.message).to.equal(
300+
expect(err.message).to.contain(
303301
'OwnerModel.relationMappings.testRelation: join.through must be an object that describes the join table. For example: {from: "JoinTable.someId", to: "JoinTable.someOtherId"}'
304302
);
305303
});
@@ -322,7 +320,7 @@ describe('ManyToManyRelation', () => {
322320
}
323321
});
324322
}).to.throwException(err => {
325-
expect(err.message).to.equal(
323+
expect(err.message).to.contain(
326324
'OwnerModel.relationMappings.testRelation: join.through.from must have format JoinTable.columnName. For example "JoinTable.someId" or in case of composite key ["JoinTable.a", "JoinTable.b"].'
327325
);
328326
});
@@ -345,7 +343,7 @@ describe('ManyToManyRelation', () => {
345343
}
346344
});
347345
}).to.throwException(err => {
348-
expect(err.message).to.equal(
346+
expect(err.message).to.contain(
349347
'OwnerModel.relationMappings.testRelation: join.through.to must have format JoinTable.columnName. For example "JoinTable.someId" or in case of composite key ["JoinTable.a", "JoinTable.b"].'
350348
);
351349
});
@@ -368,7 +366,7 @@ describe('ManyToManyRelation', () => {
368366
}
369367
});
370368
}).to.throwException(err => {
371-
expect(err.message).to.equal(
369+
expect(err.message).to.contain(
372370
'OwnerModel.relationMappings.testRelation: join.through `from` and `to` must point to the same join table.'
373371
);
374372
});

0 commit comments

Comments
 (0)