-
Notifications
You must be signed in to change notification settings - Fork 642
Description
Description
When used in conjunction with the JoinEagerAlgorithm setting, it appears that tree-like eager relations seem to be short-circuiting if they encounter an empty relation during conversion to internal structure (from database tuple).
For example, consider the following relation expression: Bs.[Cs, Ds]. I expect to receive the model instances for Ds, even if Cs is effectively empty. In the reproduction case that follows, you can see that Ds are never mapped to a Model instance, if relation Cs is empty.
The ouput of the following bug reproduction is as follows:
[
{
"id": 1,
"Bs": [
{
"id": 1,
"aId": 1,
"Cs": [],
"Ds": []
}
]
}
]Sadly, I do not receive my instances of D under Ds. Using the default eager algorithm (rather than JoinEagerAlgorithm), I receive the expected results:
[
{
"id": 1,
"Bs": [
{
"id": 1,
"aId": 1,
"Cs": [],
"Ds": [
{ "id": 1 },
{ "id": 2 }
]
}
]
}
]Finally, modifying the order of B's relations (whilst using JoinEagerAlgorithm) also impacts results. That is, swapping the order of the definitions of relations Cs and Ds, provides correct results. I expect that this observation may be helpful for identifying a root cause.
Reproduction Code
const Knex = require('knex');
const Objection = require('objection');
const { Model } = Objection;
const knexConfig = require('./knexfile');
const knex = Knex(knexConfig);
Model.knex(knex);
class A extends Model {
static get tableName() {
return 'a';
}
static get idColumn() {
return 'id';
}
static get relationMappings() {
return {
Bs: {
relation: Model.HasManyRelation,
modelClass: B,
join: {
from: 'a.id',
to: 'b.aId',
},
},
};
}
}
class B extends Model {
static get tableName() {
return 'b';
}
static get idColumn() {
return 'id';
}
static get relationMappings() {
return {
Cs: {
relation: Model.ManyToManyRelation,
modelClass: C,
join: {
from: 'b.id',
through: {
from: 'b_c.bId',
to: 'b_c.cId',
},
to: 'c.id',
},
},
Ds: {
relation: Model.ManyToManyRelation,
modelClass: D,
join: {
from: 'b.id',
through: {
from: 'b_d.bId',
to: 'b_d.dId',
},
to: 'd.id',
},
}
};
}
}
class C extends Model {
static get tableName() {
return 'c';
}
static get idColumn() {
return 'id';
}
}
class D extends Model {
static get tableName() {
return 'd';
}
static get idColumn() {
return 'id';
}
}
A.query()
.eagerAlgorithm(Model.JoinEagerAlgorithm)
.eager('Bs.[Cs, Ds]')
.then((results) => {
console.log(JSON.stringify(results, null, 2));
process.exit(0);
})
.catch((err) => {
console.error(err.message);
process.exit(1);
})begin;
drop table if exists b_d;
drop table if exists b_c;
drop table if exists d;
drop table if exists c;
drop table if exists b;
drop table if exists a;
create table a (
id int primary key
);
create table b (
id int primary key,
"aId" int references a (id)
);
create table c (
id int primary key
);
create table d (
id int primary key
);
create table b_c (
"bId" int references b (id),
"cId" int references c (id)
);
create table b_d (
"bId" int references b (id),
"dId" int references d (id)
);
insert into a values (1);
insert into b values (1, 1);
insert into d values (1);
insert into d values (2);
insert into b_d values (1, 1);
insert into b_d values (1, 2);
commit;Dependencies
"dependencies": {
"knex": "^0.12.6",
"objection": "^0.6.2",
"pg": "^6.1.2"
}
Knex Debug Output
knex:client acquired connection from pool: __knexUid1 +0ms
knex:query select "a"."id" as "id", "Bs"."id" as "Bs:id", "Bs"."aId" as "Bs:aId", "Bs:Cs"."id" as "Bs:Cs:id", "Bs:Ds"."id" as "Bs:Ds:id" from "a" as "a" left join (select * from "b") as "Bs" on "Bs"."aId" = "a"."id" left join "b_c" as "Bs:Cs_join" on "Bs:Cs_join"."bId" = "Bs"."id" left join (select * from "c") as "Bs:Cs" on "Bs:Cs_join"."cId" = "Bs:Cs"."id" left join "b_d" as "Bs:Ds_join" on "Bs:Ds_join"."bId" = "Bs"."id" left join (select * from "d") as "Bs:Ds" on "Bs:Ds_join"."dId" = "Bs:Ds"."id" +3ms
knex:bindings [] +1ms
knex:client releasing connection to pool: __knexUid1 +2ms
knex:pool INFO pool postgresql:pg:client0 - dispense() clients=0 available=1 +1msdavidgwking=# select "a"."id" as "id", "Bs"."id" as "Bs:id", "Bs"."aId" as "Bs:aId", "Bs:Cs"."id" as "Bs:Cs:id", "Bs:Ds"."id" as "Bs:Ds:id" from "a" as "a" left join (select * from "b") as "Bs" on "Bs"."aId" = "a"."id" left join "b_c" as "Bs:Cs_join" on "Bs:Cs_join"."bId" = "Bs"."id" left join (select * from "c") as "Bs:Cs" on "Bs:Cs_join"."cId" = "Bs:Cs"."id" left join "b_d" as "Bs:Ds_join" on "Bs:Ds_join"."bId" = "Bs"."id" left join (select * from "d") as "Bs:Ds" on "Bs:Ds_join"."dId" = "Bs:Ds"."id";
┌────┬───────┬────────┬──────────┬──────────┐
│ id │ Bs:id │ Bs:aId │ Bs:Cs:id │ Bs:Ds:id │
├────┼───────┼────────┼──────────┼──────────┤
│ 1 │ 1 │ 1 │ (NULL) │ 1 │
│ 1 │ 1 │ 1 │ (NULL) │ 2 │
└────┴───────┴────────┴──────────┴──────────┘
(2 rows)
Time: 1.489 ms