Skip to content

Commit

Permalink
Clean up mongo query and fix aggregation
Browse files Browse the repository at this point in the history
  • Loading branch information
lmd59 committed Aug 29, 2024
1 parent c521114 commit 9631438
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 41 deletions.
16 changes: 9 additions & 7 deletions service/src/db/dbOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function findResourcesWithQuery<T extends fhir4.FhirResource[]>(
resourceType: FhirResourceType
) {
const collection = Connection.db.collection(resourceType);
const projection = { 'data._id': 0, 'data._dataRequirements': 0 };
const projection = { _id: 0, _dataRequirements: 0 };
const pagination: any = query.skip ? [{ $skip: query.skip }, { $limit: query.limit }] : [];
query._dataRequirements = { $exists: false };
query._summary = { $exists: false };
Expand All @@ -31,13 +31,13 @@ export async function findResourcesWithQuery<T extends fhir4.FhirResource[]>(
return collection
.aggregate<{ metadata: { total: number }[]; data: T }>([
{ $match: query },
{ $project: projection },
{
$facet: {
metadata: [{ $count: 'total' }],
data: pagination
}
},
{ $project: projection }
}
])
.toArray();
}
Expand All @@ -46,7 +46,7 @@ export async function findResourcesWithQuery<T extends fhir4.FhirResource[]>(
* searches the database and returns an array objects that contain metadata and each respective resource of the given type that match the given query
* but the resources only include the elements specified by the _elements parameter
*/
export async function findResourceElementsWithQuery<T extends fhir4.FhirResource>(
export async function findResourceElementsWithQuery<T extends fhir4.FhirResource[]>(
query: Filter<any>,
resourceType: FhirResourceType
) {
Expand All @@ -57,7 +57,7 @@ export async function findResourceElementsWithQuery<T extends fhir4.FhirResource
resourceType === 'Library' ? { status: 1, resourceType: 1, type: 1 } : { status: 1, resourceType: 1 };

(query._elements as string[]).forEach(elem => {
projection[elem] = 1;
projection[`${elem}`] = 1;
});
projection['_id'] = 0;

Expand All @@ -72,13 +72,13 @@ export async function findResourceElementsWithQuery<T extends fhir4.FhirResource
return collection
.aggregate<{ metadata: { total: number }[]; data: T }>([
{ $match: query },
{ $project: projection },
{
$facet: {
metadata: [{ $count: 'total' }],
data: pagination
}
},
{ $project: projection }
}
])
.toArray();
}
Expand All @@ -93,6 +93,8 @@ export async function findResourceCountWithQuery(query: Filter<any>, resourceTyp
query._dataRequirements = { $exists: false };
query._summary = { $exists: false };
query._elements = { $exists: false };
query.limit = { $exists: false };
query.skip = { $exists: false };
return collection.countDocuments(query);
}

Expand Down
4 changes: 2 additions & 2 deletions service/src/services/LibraryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export class LibraryService implements Service<fhir4.Library> {
// then return a searchset bundle that includes only those elements
// on those resource entries
else if (parsedQuery._elements) {
const result = await findResourceElementsWithQuery<fhir4.Library>(mongoQuery, 'Library');
const entries = result.map(r => r.data);
const result = await findResourceElementsWithQuery<fhir4.Library[]>(mongoQuery, 'Library');
const entries = result[0].data;
// add the SUBSETTED tag to the resources returned by the _elements parameter
entries.map(e => {
if (e.meta) {
Expand Down
4 changes: 2 additions & 2 deletions service/src/services/MeasureService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export class MeasureService implements Service<fhir4.Measure> {
// then return a searchset bundle that includes only those elements
// on those resource entries
else if (parsedQuery._elements) {
const result = await findResourceElementsWithQuery<fhir4.Measure>(mongoQuery, 'Measure');
const entries = result.map(r => r.data);
const result = await findResourceElementsWithQuery<fhir4.Measure[]>(mongoQuery, 'Measure');
const entries = result[0].data;
// add the SUBSETTED tag to the resources returned by the _elements parameter
entries.map(e => {
if (e.meta) {
Expand Down
37 changes: 16 additions & 21 deletions service/src/util/queryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,38 @@ const STRING_TYPE_PARAMS = ['name', 'title', 'description', 'version'];
* a usable mongo query
*/
export function getMongoQueryFromRequest(query: RequestQuery): Filter<any> {
const pageSize = (query['_count'] && parseInt(query['_count'] as string)) || 100; // set a base limit of 100
if (!query['page']) query['page'] = '1'; // default to first page

//TODO: Handle potential for query value to be array
return Object.keys(query).reduce((mf: Filter<any>, key: string) => {
const pageSize = parseInt((query['_count'] || '50') as string); //default to limit 50
const filter: Filter<any> = { limit: pageSize, skip: (parseInt((query['page'] || '1') as string) - 1) * pageSize }; //default to first page
Object.keys(query).forEach((key: string) => {
//TODO: Handle potential for query value to be array
if (!query[key] || Array.isArray(query[key])) {
throw new NotImplementedError(
`Retrieved undefined or multiple arguments for query param: ${key}. Multiple arguments for the same query param and undefined arguments are not supported.`
);
} else if (STRING_TYPE_PARAMS.includes(key)) {
// For string arguments in query they may match just the start of a string and are case insensitive
mf[key] = { $regex: `^${query[key]}`, $options: 'i' };
filter[key] = { $regex: `^${query[key]}`, $options: 'i' };
} else if (key === 'identifier') {
// Identifier can check against the identifier.system, identifier.value, or both on a resource
const iden = query.identifier as string;
const splitIden = iden.split('|');
if (splitIden.length === 1) {
mf['identifier.value'] = splitIden[0];
filter['identifier.value'] = splitIden[0];
} else if (splitIden[0] === '') {
mf['identifier.value'] = splitIden[1];
filter['identifier.value'] = splitIden[1];
} else if (splitIden[1] === '') {
mf['identifier.system'] = splitIden[0];
filter['identifier.system'] = splitIden[0];
} else {
mf['identifier.system'] = splitIden[0];
mf['identifier.value'] = splitIden[1];
filter['identifier.system'] = splitIden[0];
filter['identifier.value'] = splitIden[1];
}
} else if (key === '_elements') {
const elements = query[key] as string;
mf[key] = elements.split(',');
} else if (key === '_count') {
mf['limit'] = pageSize;
} else if (key === 'page') {
mf['skip'] = (parseInt((query[key] || '1') as string) - 1) * pageSize; //default to first page
} else {
// Otherwise no parsing necessary
mf[key] = query[key];
filter[key] = elements.split(',');
} else if (key !== '_count' && key !== 'page') {
// Skip _count and page, otherwise no parsing necessary
filter[key] = query[key];
}
return mf;
}, {});
});
return filter;
}
6 changes: 3 additions & 3 deletions service/test/services/LibraryService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ describe('LibraryService', () => {
.then(response => {
expect(response.body.issue[0].code).toEqual('invalid');
expect(response.body.issue[0].details.text).toEqual(
'Multiple resources found in collection: Library, with identifier: http://example.com/libraryWithSameSystem| and page: 1. /Library/$cqfm.package operation must specify a single Library'
'Multiple resources found in collection: Library, with identifier: http://example.com/libraryWithSameSystem|. /Library/$cqfm.package operation must specify a single Library'
);
});
});
Expand Down Expand Up @@ -496,7 +496,7 @@ describe('LibraryService', () => {
.then(response => {
expect(response.body.issue[0].code).toEqual('not-found');
expect(response.body.issue[0].details.text).toEqual(
'No resource found in collection: Library, with id: testLibraryWithDeps and url: http://example.com/invalid and page: 1'
'No resource found in collection: Library, with id: testLibraryWithDeps and url: http://example.com/invalid'
);
});
});
Expand All @@ -516,7 +516,7 @@ describe('LibraryService', () => {
.then(response => {
expect(response.body.issue[0].code).toEqual('not-found');
expect(response.body.issue[0].details.text).toEqual(
'No resource found in collection: Library, with id: invalid and url: http://example.com/invalid and page: 1'
'No resource found in collection: Library, with id: invalid and url: http://example.com/invalid'
);
});
});
Expand Down
4 changes: 2 additions & 2 deletions service/test/services/MeasureService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ describe('MeasureService', () => {
.then(response => {
expect(response.body.issue[0].code).toEqual('not-found');
expect(response.body.issue[0].details.text).toEqual(
'No resource found in collection: Measure, with id: testWithUrl and url: http://example.com/invalid and page: 1'
'No resource found in collection: Measure, with id: testWithUrl and url: http://example.com/invalid'
);
});
});
Expand Down Expand Up @@ -499,7 +499,7 @@ describe('MeasureService', () => {
.then(response => {
expect(response.body.issue[0].code).toEqual('not-found');
expect(response.body.issue[0].details.text).toEqual(
'No resource found in collection: Measure, with id: invalid and url: http://example.com/invalid and page: 1'
'No resource found in collection: Measure, with id: invalid and url: http://example.com/invalid'
);
});
});
Expand Down
9 changes: 5 additions & 4 deletions service/test/util/queryUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ const QUERY_WITH_SYSTEM_AND_CODE_IDEN = { url: 'http://example.com', identifier:
const QUERY_WITH_STRING_PARAM = { url: 'http://example.com', version: 'test' };
const QUERY_WITH_MULTIPLE_STRING_PARAM = { url: 'http://example.com', version: ['test', '...anotherTest?'] };

const EXPECTED_QUERY_WITH_NO_IDEN = { skip: 0, url: 'http://example.com' };
const EXPECTED_QUERY_WITH_CODE_IDEN = { skip: 0, url: 'http://example.com', 'identifier.value': 'testCode' };
const EXPECTED_QUERY_WITH_SYSTEM_IDEN = { skip: 0, url: 'http://example.com', 'identifier.system': 'testSystem' };
const EXPECTED_QUERY_WITH_NO_IDEN = { limit: 50, skip: 0, url: 'http://example.com' };
const EXPECTED_QUERY_WITH_CODE_IDEN = { limit: 50, skip: 0, url: 'http://example.com', 'identifier.value': 'testCode' };
const EXPECTED_QUERY_WITH_SYSTEM_IDEN = { limit: 50, skip: 0, url: 'http://example.com', 'identifier.system': 'testSystem' };
const EXPECTED_QUERY_WITH_SYSTEM_AND_CODE_IDEN = {
limit: 50,
skip: 0,
url: 'http://example.com',
'identifier.system': 'testSystem',
'identifier.value': 'testCode'
};
const EXPECTED_QUERY_WITH_STRING_PARAM = { skip: 0, url: 'http://example.com', version: { $regex: '^test', $options: 'i' } };
const EXPECTED_QUERY_WITH_STRING_PARAM = { limit: 50, skip: 0, url: 'http://example.com', version: { $regex: '^test', $options: 'i' } };

describe('getMongoQueryFromRequest', () => {
it('correctly parses a query with no identifier field', () => {
Expand Down

0 comments on commit 9631438

Please sign in to comment.