Skip to content

Commit 21cb24f

Browse files
fix(resources getter): retrieve only the requested fields if no smart fields are requested (#832)
1 parent 7fde76c commit 21cb24f

File tree

3 files changed

+22
-37
lines changed

3 files changed

+22
-37
lines changed

src/services/query-options.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,18 @@ class QueryOptions {
3131
options.limit = this._limit;
3232
}
3333

34-
if (this._restrictFieldsOnRootModel && this._requestedFields.size) {
34+
if (this._requestedFields.size && !this._hasRequestedSmartFields) {
3535
// Restricting loaded fields on the root model is opt-in with sequelize to avoid
3636
// side-effects as this was not supported historically and it would probably break
3737
// smart fields.
3838
// @see https://github.com/ForestAdmin/forest-express-sequelize/blob/7d7ad0/src/services/resources-getter.js#L142
3939

40-
options.attributes = [...this._requestedFields].filter((s) => !s.includes('.'));
40+
const simpleSchemaFields = this._schema.fields
41+
.filter((field) => !field.reference)
42+
.map((field) => field.field);
43+
options.attributes = [...this._requestedFields]
44+
.filter((field) => simpleSchemaFields.includes(field));
45+
options.attributes.push(...this._schema.primaryKeys);
4146
}
4247

4348
return SequelizeCompatibility.postProcess(this._model, options);
@@ -67,7 +72,7 @@ class QueryOptions {
6772

6873
/** Compute sequelize query `.include` property */
6974
get _sequelizeInclude() {
70-
const fields = [...this._requestedFields, ...this._neededFields];
75+
const fields = [...this._requestedFields, ...this._requestedRelations, ...this._neededFields];
7176
const include = [
7277
...new QueryBuilder().getIncludes(this._model, fields.length ? fields : null),
7378
...this._customerIncludes,
@@ -87,6 +92,11 @@ class QueryOptions {
8792
return this._order;
8893
}
8994

95+
get _hasRequestedSmartFields() {
96+
return this._schema.fields
97+
.some((field) => field.isVirtual && this._requestedFields.has(field.field));
98+
}
99+
90100
/**
91101
* @param {sequelize.model} model Sequelize model that should be targeted
92102
* @param {boolean} options.includeRelations Include BelongsTo and HasOne relations by default
@@ -102,8 +112,8 @@ class QueryOptions {
102112
this._options = options;
103113

104114
// Used to compute relations that will go in the final 'include'
105-
this._restrictFieldsOnRootModel = false;
106115
this._requestedFields = new Set();
116+
this._requestedRelations = new Set();
107117
this._neededFields = new Set();
108118
this._scopes = []; // @see sequelizeScopes getter
109119

@@ -117,7 +127,7 @@ class QueryOptions {
117127
if (this._options.includeRelations) {
118128
_.values(this._model.associations)
119129
.filter((association) => ['HasOne', 'BelongsTo'].includes(association.associationType))
120-
.forEach((association) => this._requestedFields.add(association.associationAccessor));
130+
.forEach((association) => this._requestedRelations.add(association.associationAccessor));
121131
}
122132
}
123133

@@ -128,12 +138,10 @@ class QueryOptions {
128138
* @param {string[]} fields the output of the extractRequestedFields() util function
129139
* @param {boolean} applyOnRootModel restrict fetched fields also on the root
130140
*/
131-
async requireFields(fields, applyOnRootModel = false) {
141+
async requireFields(fields) {
132142
if (fields) {
133143
fields.forEach((field) => this._requestedFields.add(field));
134144
}
135-
136-
this._restrictFieldsOnRootModel = Boolean(applyOnRootModel);
137145
}
138146

139147
/**

src/services/resources-getter.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ class ResourcesGetter {
5252
async _buildQueryOptions(buildOptions = {}) {
5353
const { forCount, tableAlias } = buildOptions;
5454
const {
55-
fields, filters, restrictFieldsOnRootModel,
55+
fields, filters,
5656
search, searchExtended, segment, segmentQuery, timezone,
5757
} = this._params;
5858

5959
const scopeFilters = await scopeManager.getScopeForUser(this._user, this._model.name, true);
6060

6161
const requestedFields = extractRequestedFields(fields, this._model, Schemas.schemas);
6262
const queryOptions = new QueryOptions(this._model, { tableAlias });
63-
await queryOptions.requireFields(requestedFields, restrictFieldsOnRootModel);
63+
if (!forCount) await queryOptions.requireFields(requestedFields);
6464
await queryOptions.search(search, searchExtended);
6565
await queryOptions.filterByConditionTree(filters, timezone);
6666
await queryOptions.filterByConditionTree(scopeFilters, timezone);

test/databases.test.js

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2856,6 +2856,7 @@ const user = { renderingId: 1 };
28562856
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
28572857
const params = {
28582858
...baseParams,
2859+
sort: 'id',
28592860
fields: { address: 'user', user: 'firstName' },
28602861
page: { number: '1' },
28612862
};
@@ -2879,6 +2880,7 @@ const user = { renderingId: 1 };
28792880
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
28802881
const params = {
28812882
...baseParams,
2883+
sort: 'id',
28822884
fields: { address: 'user', user: 'fullName' },
28832885
page: { number: '1' },
28842886
};
@@ -2897,16 +2899,15 @@ const user = { renderingId: 1 };
28972899
});
28982900
});
28992901

2900-
describe('request on the resources getter with restrictFieldsOnRootModel flag', () => {
2901-
it('should only retrieve requested fields with the flag', async () => {
2902+
describe('request on the resources getter with no smart fields', () => {
2903+
it('should only retrieve requested fields', async () => {
29022904
expect.assertions(6);
29032905
const { models } = initializeSequelize();
29042906
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
29052907
const params = {
29062908
...baseParams,
29072909
fields: { address: 'id,line,zipCode' },
29082910
page: { number: '1' },
2909-
restrictFieldsOnRootModel: true,
29102911
};
29112912

29122913
try {
@@ -2922,30 +2923,6 @@ const user = { renderingId: 1 };
29222923
connectionManager.closeConnection();
29232924
}
29242925
});
2925-
2926-
it('should ignore requested fields without the flag', async () => {
2927-
expect.assertions(6);
2928-
const { models } = initializeSequelize();
2929-
const spy = jest.spyOn(scopeManager, 'getScopeForUser').mockReturnValue(null);
2930-
const params = {
2931-
...baseParams,
2932-
fields: { address: 'id,line,zipCode' },
2933-
page: { number: '1' },
2934-
};
2935-
2936-
try {
2937-
const result = await new ResourcesGetter(models.address, null, params).perform();
2938-
expect(result[0]).not.toBeEmpty();
2939-
expect(result[0][0].dataValues).toHaveProperty('id');
2940-
expect(result[0][0].dataValues).toHaveProperty('line');
2941-
expect(result[0][0].dataValues).toHaveProperty('zipCode');
2942-
expect(result[0][0].dataValues).toHaveProperty('city');
2943-
expect(result[0][0].dataValues).toHaveProperty('country');
2944-
} finally {
2945-
spy.mockRestore();
2946-
connectionManager.closeConnection();
2947-
}
2948-
});
29492926
});
29502927
});
29512928

0 commit comments

Comments
 (0)