Skip to content

Commit 444f4ca

Browse files
authored
fix(search): search extended on collection with array of numbers (#792)
1 parent edb956a commit 444f4ca

File tree

2 files changed

+226
-22
lines changed

2 files changed

+226
-22
lines changed

src/services/search-builder.js

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ function SearchBuilder(model, opts, params, searchFields) {
2020
}
2121

2222
_.each(model.schema.paths, (value, key) => {
23-
if (searchFields && !searchFields.includes(value.path)) {
23+
if ((searchFields && !searchFields.includes(value.path))
24+
|| value.path === model.schema.options.versionKey) {
2425
return;
2526
}
2627

@@ -44,28 +45,35 @@ function SearchBuilder(model, opts, params, searchFields) {
4445
pushCondition(condition, key);
4546
} else if (value.instance === 'Array') {
4647
const field = _.find(schema.fields, { field: key });
47-
if (field && _.isArray(field.type) && field.type[0] === 'String'
48-
&& !field.reference) {
49-
condition[key] = searchRegexp;
50-
pushCondition(condition, key);
51-
} else if (field && _.isArray(field.type)
52-
&& !field.reference && Number.parseInt(params.searchExtended, 10)) {
53-
const elemMatch = { $elemMatch: { $or: [] } };
54-
55-
field.type[0].fields.forEach((subField) => {
56-
const query = {};
57-
if (subField.type === 'String'
58-
&& !value.schema.obj[subField.field].ref) {
59-
query[subField.field] = searchRegexp;
60-
elemMatch.$elemMatch.$or.push(query);
61-
} else if (subField.type === 'Number'
62-
&& Number.parseInt(params.search, 10)) {
63-
query[subField.field] = Number.parseInt(params.search, 10);
64-
elemMatch.$elemMatch.$or.push(query);
48+
if (_.isArray(field?.type) && !field.reference) {
49+
if (field.type[0] === 'String') {
50+
condition[key] = searchRegexp;
51+
pushCondition(condition, key);
52+
} else if (field.type[0] === 'Number') {
53+
const searchNumber = Number(params.search);
54+
if (!Number.isNaN(searchNumber)) {
55+
condition[key] = searchNumber;
56+
pushCondition(condition, key);
6557
}
66-
});
67-
condition[key] = elemMatch;
68-
pushCondition(condition, key);
58+
} else if (field.type[0].fields && Number.parseInt(params.searchExtended, 10)) {
59+
const elemMatch = { $elemMatch: { $or: [] } };
60+
field.type[0].fields.forEach((subField) => {
61+
const query = {};
62+
if (subField.type === 'String'
63+
&& !value.schema.obj[subField.field].ref) {
64+
query[subField.field] = searchRegexp;
65+
elemMatch.$elemMatch.$or.push(query);
66+
} else if (subField.type === 'Number') {
67+
const searchNumber = Number(params.search);
68+
if (!Number.isNaN(searchNumber)) {
69+
query[subField.field] = searchNumber;
70+
elemMatch.$elemMatch.$or.push(query);
71+
}
72+
}
73+
});
74+
condition[key] = elemMatch;
75+
pushCondition(condition, key);
76+
}
6977
}
7078
}
7179
});
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import Interface from 'forest-express';
2+
import mongoose from 'mongoose';
3+
4+
import SearchBuilderClass from '../../../src/services/search-builder';
5+
6+
let AnimalModel;
7+
8+
describe('searchBuilder', () => {
9+
beforeAll(() => {
10+
const AnimalSchema = new mongoose.Schema({
11+
id: { type: 'ObjectId' },
12+
name: { type: String },
13+
arrayOfNumber: {
14+
type: ['Number'],
15+
},
16+
arrayOfString: {
17+
type: ['Number'],
18+
},
19+
embedded: {
20+
field: 'embedded',
21+
type: [
22+
{ species: { type: 'String' } },
23+
{ cousins: { type: 'Number' } },
24+
],
25+
},
26+
});
27+
AnimalModel = mongoose.model('Animal', AnimalSchema);
28+
29+
Interface.Schemas = {
30+
schemas: {
31+
Animal: {
32+
name: 'animals',
33+
nameOld: 'animals',
34+
idField: '_id',
35+
primaryKeys: ['_id'],
36+
isCompositePrimary: false,
37+
fields: [
38+
{
39+
field: 'name',
40+
type: 'String',
41+
},
42+
{
43+
field: 'arrayOfNumber',
44+
type: ['Number'],
45+
defaultValue: null,
46+
isRequired: false,
47+
isPrimaryKey: false,
48+
isReadOnly: false,
49+
isSortable: true,
50+
isFilterable: true,
51+
isVirtual: false,
52+
description: null,
53+
reference: null,
54+
inverseOf: null,
55+
relationships: null,
56+
enums: null,
57+
validations: [],
58+
integration: null,
59+
},
60+
{
61+
field: 'arrayOfString',
62+
type: ['String'],
63+
defaultValue: null,
64+
isRequired: false,
65+
isPrimaryKey: false,
66+
isReadOnly: false,
67+
isSortable: true,
68+
isFilterable: true,
69+
isVirtual: false,
70+
description: null,
71+
reference: null,
72+
inverseOf: null,
73+
relationships: null,
74+
enums: null,
75+
validations: [],
76+
integration: null,
77+
},
78+
{
79+
field: 'embedded',
80+
type: [
81+
{
82+
fields: [
83+
{ field: 'species', type: 'String' },
84+
{ field: 'cousins', type: 'Number' },
85+
],
86+
},
87+
],
88+
isRequired: false,
89+
isPrimaryKey: false,
90+
isReadOnly: false,
91+
isSortable: true,
92+
isFilterable: true,
93+
isVirtual: false,
94+
},
95+
{
96+
field: '_id',
97+
type: 'String',
98+
isPrimaryKey: true,
99+
defaultValue: null,
100+
isRequired: false,
101+
isReadOnly: false,
102+
isSortable: true,
103+
isFilterable: true,
104+
isVirtual: false,
105+
},
106+
],
107+
isSearchable: true,
108+
actions: [],
109+
segments: [],
110+
onlyForRelationships: false,
111+
isVirtual: false,
112+
isReadOnly: false,
113+
paginationType: 'page',
114+
},
115+
},
116+
};
117+
});
118+
describe('when searching a number', () => {
119+
it('should add the elemMatch for nested number', async () => {
120+
expect.assertions(1);
121+
122+
const params = {
123+
timezone: 'Europe/Paris',
124+
fields: { animals: '_id,name,nbAlive,deadNumber,arrayOfNumber,arrayOfString' },
125+
page: { number: '1', size: '15' },
126+
search: '23',
127+
searchExtended: '1',
128+
sort: '-_id',
129+
filters: undefined,
130+
};
131+
const searchFields = undefined;
132+
133+
const searchBuilder = new SearchBuilderClass(AnimalModel, undefined, params, searchFields);
134+
135+
expect(await searchBuilder.getConditions()).toStrictEqual({
136+
$or: [
137+
{ name: /.*23.*/i },
138+
{ arrayOfNumber: 23 },
139+
{ arrayOfString: /.*23.*/i },
140+
{
141+
embedded: {
142+
$elemMatch: {
143+
$or: [
144+
{
145+
species: /.*23.*/i,
146+
},
147+
{
148+
cousins: 23,
149+
},
150+
],
151+
},
152+
},
153+
},
154+
],
155+
});
156+
});
157+
});
158+
159+
describe('when searching a string', () => {
160+
it('should add the elemMatch for nested string', async () => {
161+
expect.assertions(1);
162+
163+
const params = {
164+
timezone: 'Europe/Paris',
165+
fields: { animals: '_id,name,nbAlive,deadNumber,arrayOfNumber,arrayOfString' },
166+
page: { number: '1', size: '15' },
167+
search: 'gorillas',
168+
searchExtended: '1',
169+
sort: '-_id',
170+
filters: undefined,
171+
};
172+
const searchFields = undefined;
173+
174+
const searchBuilder = new SearchBuilderClass(AnimalModel, undefined, params, searchFields);
175+
176+
expect(await searchBuilder.getConditions()).toStrictEqual({
177+
$or: [
178+
179+
{ name: /.*gorillas.*/i },
180+
{ arrayOfString: /.*gorillas.*/i },
181+
{
182+
embedded: {
183+
$elemMatch: {
184+
$or: [
185+
{
186+
species: /.*gorillas.*/i,
187+
},
188+
],
189+
},
190+
},
191+
},
192+
],
193+
});
194+
});
195+
});
196+
});

0 commit comments

Comments
 (0)