Skip to content

Commit

Permalink
feat: support chain search
Browse files Browse the repository at this point in the history
- The chain search can search parameter's type is
reference like `organization.name` in Patient
- This will join `Organization` resource and search
with name in Organization and return **orginal Patient resource**

Note: WIP
  • Loading branch information
Chinlinlee committed Nov 7, 2022
1 parent b767df5 commit 3cb4c6f
Show file tree
Hide file tree
Showing 5 changed files with 520 additions and 312 deletions.
70 changes: 70 additions & 0 deletions api/FHIR/Patient/PatientParametersHandler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
const _ = require('lodash');
const queryBuild = require('../../../models/FHIR/queryBuild.js');
const queryHandler = require('../../../models/FHIR/searchParameterQueryHandler');
const jp = require("jsonpath");
let resourceInclude = require("../../../api_generator/resource-reference/resourceInclude.json");
const {
chainSearch
} = require('../../../models/FHIR/queryBuild.js');
const path = require("path");

let paramsSearchFields = {};

Expand Down Expand Up @@ -478,5 +484,69 @@ paramsSearch["organization"] = (query) => {
};
//#endregion

// #region chain search
let patientRefItem = resourceInclude["Patient"];
for (let refItem of patientRefItem) {
let refSearch = Object.keys(paramsSearchFields).map(v => {
if (paramsSearchFields[v].find(field => field.includes(refItem.path))) {
return v;
}
});

refSearch = _.compact(refSearch);
if (refSearch.length > 0) {
let refSearchParam = refSearch[0];
let refSearchParamField = paramsSearchFields[refSearchParam][0];
let refResources = refItem.resourceList;
for (let refResource of refResources) {
if (refResource === "Resource") continue;
if (refResource === path.basename(__dirname)) {
parameterHandler = {
paramsSearch: paramsSearch,
paramsSearchFields: paramsSearchFields
};
} else {
parameterHandler = require(`../${refResource}/${refResource}ParametersHandler`);
}
if (_.isEmpty(parameterHandler)) continue;

let refParamSearch = parameterHandler.paramsSearch;
Object.keys(refParamSearch).forEach(v => {
paramsSearch[`${refSearchParam}.${v}`] = function(query) {
let targetSearchParamsFields = parameterHandler.paramsSearchFields[v];
query["isChain"] = true;
let queryForChain = {
$and: [],
[v]: query[`${refSearchParam}.${v}`]
};
refParamSearch[v](queryForChain);
for (let targetField of targetSearchParamsFields) {
let originalPath = jp.nodes(queryForChain, `$..${targetField}`);
let cleanPath = originalPath[0].path.slice(1);
cleanPath.pop();
let replacePath = [...cleanPath, `ref${refResource}.${targetField}`];
let searchValue = originalPath[0].value;
_.set(queryForChain, replacePath, searchValue);
queryForChain = _.omit(queryForChain, [`${cleanPath.join(".")}.${targetField}`]);
}

let chainAggregate = chainSearch(refResource, refSearchParamField);
chainAggregate.push({
"$match": {
...queryForChain
}
});

if (!_.get(query, "chain")) query["chain"] = [];
query["chain"] = [...query["chain"], chainAggregate];
delete query[`${refSearchParam}.${v}`]
}
});
}
}
}
//#endregion


module.exports.paramsSearch = paramsSearch;
module.exports.paramsSearchFields = paramsSearchFields;
53 changes: 42 additions & 11 deletions api/FHIRApiService/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,53 @@ module.exports = async function(req, res, resourceType, paramsSearch) {
delete queryParameter["$and"];
}
try {
let docs = await mongodb[resourceType].find(queryParameter).
limit(paginationLimit).
skip(paginationSkip).
sort({
_id: -1
}).
exec();
docs = docs.map(v => {
return v.getFHIRField();
});
let docs;
let isChain = _.get(queryParameter, "isChain", false);
let count = 0;

if (isChain) {
let aggregateQuery = [];
if (_.get(queryParameter, "$and", []).length > 0) {
let selfMatch = {
"$match" : {
...queryParameter.$and
}
}
aggregateQuery.push(selfMatch);
}
aggregateQuery.push(...queryParameter["chain"]);
docs = await mongodb[resourceType].aggregate(aggregateQuery).exec();

aggregateQuery[0].push({ "$count": "totalDocs" });
let totalDocs = count = await mongodb[resourceType].aggregate(aggregateQuery).exec();
count = _.get(totalDocs, "0.totalDocs", 0);
} else {
docs = await mongodb[resourceType].find(queryParameter).
limit(paginationLimit).
skip(paginationSkip).
sort({
_id: -1
}).
exec();
}


if (_.isEmpty(queryParameter)) {
count = await mongodb[resourceType].estimatedDocumentCount();
} else {
} else if (!isChain) {
count = await mongodb[resourceType].countDocuments(queryParameter);
}

if (isChain) {
docs = docs.map(v => {
return new mongodb[resourceType](v).getFHIRField();
});
} else {
docs = docs.map(v => {
return v.getFHIRField();
});
}

let includeDocs = await searchResultParametersHandler["_include"](req.query, docs);
let reincludeDocs = await searchResultParametersHandler["_revIncludes"](req.query, docs, resourceType);
docs = [...docs, ...includeDocs, ...reincludeDocs];
Expand Down
70 changes: 70 additions & 0 deletions api_generator/API_Generator_V2.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ function generateAPI(option) {
const _ = require('lodash');
const queryBuild = require('../../../models/FHIR/queryBuild.js');
const queryHandler = require('../../../models/FHIR/searchParameterQueryHandler');
const jp = require("jsonpath");
let resourceInclude = require("../../../api_generator/resource-reference/resourceInclude.json");
const { chainSearch } = require('../../../models/FHIR/queryBuild.js');
const path = require("path");
let paramsSearchFields = {};
Expand Down Expand Up @@ -136,6 +140,72 @@ function generateAPI(option) {
}
}
}
resourceParameterHandler +=`
// #region chain search
let ${res.toLocaleLowerCase()}RefItem = resourceInclude["${res}"];
for (let refItem of ${res.toLocaleLowerCase()}RefItem) {
let refSearch = Object.keys(paramsSearchFields).map( v => {
if (paramsSearchFields[v].find( field => field.includes(refItem.path))) {
return v;
}
});
refSearch = _.compact(refSearch);
if (refSearch.length > 0) {
let refSearchParam = refSearch[0];
let refSearchParamField = paramsSearchFields[refSearchParam][0];
let refResources = refItem.resourceList;
for (let refResource of refResources) {
if (refResource === "Resource") continue;
if (refResource === path.basename(__dirname)) {
parameterHandler = {
paramsSearch: paramsSearch,
paramsSearchFields: paramsSearchFields
};
}
else {
parameterHandler = require(\`../\${refResource}/\${refResource}ParametersHandler\`);
}
if (_.isEmpty(parameterHandler)) continue;
let refParamSearch = parameterHandler.paramsSearch;
Object.keys(refParamSearch).forEach( v => {
paramsSearch[\`\${refSearchParam}.\${v}\`] = function(query) {
let targetSearchParamsFields = parameterHandler.paramsSearchFields[v];
query["isChain"] = true;
let queryForChain = {
$and: [],
[v]: query[\`\${refSearchParam}.\${v}\`]
};
refParamSearch[v](queryForChain);
for (let targetField of targetSearchParamsFields) {
let originalPath = jp.nodes(queryForChain, \`\$..\${targetField}\`);
let cleanPath = originalPath[0].path.slice(1);
cleanPath.pop();
let replacePath = [...cleanPath, \`ref\${refResource}.\${targetField}\`];
let searchValue = originalPath[0].value;
_.set(queryForChain, replacePath, searchValue);
queryForChain = _.omit(queryForChain, [\`\${cleanPath.join(".")}.\${targetField}\`]);
}
let chainAggregate = chainSearch(refResource, refSearchParamField);
chainAggregate.push({
"$match" : {
...queryForChain
}
});
if (!_.get(query, "chain")) query["chain"] = [];
query["chain"] = [...query["chain"], chainAggregate];
delete query[\`\${refSearchParam}.\${v}\`]
}
});
}
}
}
//#endregion\r\n
`;

resourceParameterHandler += `
module.exports.paramsSearch = paramsSearch;
module.exports.paramsSearchFields = paramsSearchFields;
Expand Down
39 changes: 38 additions & 1 deletion models/FHIR/queryBuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,42 @@ function numberQuery (value, field) {
}
}

/**
*
* @param {string} targetResource
* @param {string} targetField
*/
function chainSearch(targetResource, targetField, queryValue) {
let aggregate = [
{
"$lookup": {
"from": targetResource,
"let": {
"refId": {
"$substr": [`$${targetField}`, targetResource.length+1, -1]
}
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$id", "$$refId"
]
}
}
}
],
"as": `ref${targetResource}`
}
},
{
"$unwind": `$ref${targetResource}`
}
];
return aggregate;
}

module.exports = {
stringQuery: stringQuery,
numberQuery : numberQuery ,
Expand All @@ -552,5 +588,6 @@ module.exports = {
quantityQuery : quantityQuery ,
referenceQuery : referenceQuery ,
arrayStringBuild : arrayStringBuild,
getCommaSplitArray: getCommaSplitArray
getCommaSplitArray: getCommaSplitArray,
chainSearch: chainSearch
};
Loading

0 comments on commit 3cb4c6f

Please sign in to comment.