I think I have a handle on this bug.
Let me try and walk you through the path where I see the problem.
We have defined our āItemā model this way:
conn.model('Item', require('./items').default, 'items'),
So, the Item
is the model name, and items
is the collection name.
Keep that in mind.
When the Forest client makes a request to load the collection it would issue this call:
/forest/Item
So the model name is the path to reach the data.
And within the HTTP GET query the fields
for this call look like this (shortened):
&fields[Item]=_id,name,maker&
Now on the server, directly on the route for this action - this code:
const { query, user } = request;
const recordsGetter = new RecordsGetter(Item, user, query);
Looks at a query
with this structure:
{
fields: {
Item: "_id,name,category,maker,createdAt,modifiedAt,product",
... more
},
... more
}
And RecordsGetter
(mongoose) initialized with this query:
constructor(model, opts, params, user) {
this._model = model;
this._opts = { Mongoose: this._model.base, connections: this._model.base.connections };
this._params = params;
this._user = user;
}
Now calls this query params
- keep that in mind.
Moving on on the call chain - those params are passed down (higlighting intersting parts only in the pipeline):
RecordsGetter.perform()
-> new QueryBuilder(this._model, params, this._opts); // Params goes into query builder under private member this._params
-> await queryBuilder.joinAllReferences(jsonQuery); // query builder called to join references
--> let fieldNames = await this.getFieldNamesRequested(); // getFieldNamesRequested called
---> if (!this._params.fields || !this._params.fields[this._model.collection.name]) { return null; } // Ensure the target collection has any fields defined on the query
Look at that last condition:
!this._params.fields[this._model.collection.name]
This turns out to be true since:
this._model.collection.name == 'items'
and:
!this._params.fields["items"] == undefined
If I debug and manually set right before that line:
this._params.fields.items = this._params.fields.Item
I am getting all the joins applied to the DB query and the reference fields are sent to the client correctly.
Tracing the code changes - seems like this is a pretty old condition. What has changed since Liana 7 that could cause this?
This is how the code for QueryBuilder.joinAllReferences
Looked at Liana 7.9.2:
async joinAllReferences(jsonQuery, alreadyJoinedQuery) {
const fieldNames = await this.getFieldNamesRequested();
this._schema.fields.forEach((field) => {
if ((fieldNames && !fieldNames.includes(field.field))
|| QueryBuilder._joinAlreadyExists(field, alreadyJoinedQuery)) {
return;
}
this.addJoinToQuery(field, jsonQuery);
});
return this;
}
And here it is for Liana 8.7.2:
async joinAllReferences(jsonQuery, alreadyJoinedQuery) {
let fieldNames = await this.getFieldNamesRequested();
const flattenReferenceNames = Flattener
.getFlattenedReferenceFieldsFromParams(this._model.collection.name, this._params.fields);
fieldNames = flattenReferenceNames.concat(fieldNames);
this._schema.fields.forEach((field) => {
if ((fieldNames && !fieldNames.includes(field.field))
|| QueryBuilder._joinAlreadyExists(field, alreadyJoinedQuery)) {
return;
}
this.addJoinToQuery(field, jsonQuery);
});
return this;
}
See how on the old version the null
coming back from getFieldNamesRequested()
is not preventing the joins to be added.
Since
(fieldNames && !fieldNames.includes(field.field))
is false
.
But on the new version - the fieldNames
is produced by this concat:
const flattenReferenceNames = Flattener
.getFlattenedReferenceFieldsFromParams(this._model.collection.name, this._params.fields);
fieldNames = flattenReferenceNames.concat(fieldNames);
And Flattener.getFlattenedReferenceFieldsFromParams()
will always return an array.
So in fact fieldNames will not be null
and thus no field join would be added since that short-circuit condition would not occur.
Patching 8.7.2 with something like this indeed solves the problem:
async joinAllReferences(jsonQuery, alreadyJoinedQuery) {
let fieldNames = await this.getFieldNamesRequested();
const flattenReferenceNames = Flattener
.getFlattenedReferenceFieldsFromParams(this._model.collection.name, this._params.fields);
fieldNames = fieldNames ? flattenReferenceNames.concat(fieldNames) : flattenReferenceNames;
this._schema.fields.forEach((field) => {
if ((fieldNames.length && !fieldNames.includes(field.field))
|| QueryBuilder._joinAlreadyExists(field, alreadyJoinedQuery)) {
return;
}
this.addJoinToQuery(field, jsonQuery);
});
return this;
}
Now, Is the problem for us the way the Model name is not the same as the collection name? Or the introduction of the flattener?