-
-
Notifications
You must be signed in to change notification settings - Fork 219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(9586): implement freetext search in cht datasource #9625
base: master
Are you sure you want to change the base?
feat(9586): implement freetext search in cht datasource #9625
Conversation
eba7aac
to
43efbef
Compare
…text-search-in-cht-datasource
…text-search-in-cht-datasource
…text-search-in-cht-datasource
@jkuester the e2e tests related to cht-datasource are failing right now. I remember seeing something related to changes in how we deal with auth and its impact in cht-datasource. Do we have any workaround at the moment? |
Oh wow. I did not realize that the end-result of the changes was to just delete the existing remote cht-datasource tests with no alternative. This is very disappointing. We can do better here, though. The challenge is that in a browser context, the session cookie is automatically included when making the remote 1. Update cht-datasource to accept auth information when creating a remote DataContextThis would essentially be addressing #9701. We could pass username/password in the As I noted on the ticket, though, I am reluctant to make changes to the implementation code just for testing purposes. 2. Re-introduce the MITM for
|
@jkuester I did not go with the hook implementation as it would mean checking for filenames to run because this auth setup needs to run only for a few test suites, which would have resulted in a not-so-fruitful hook file with checks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright! We are getting very close here.
}; | ||
|
||
/** @internal */ | ||
export const getContactLineage = (medicDb: PouchDB.Database<Doc>) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: I guess now the getPrimaryContactIds, hydratePrimaryContact, hydrateLineage functions do not need to be exported since they are only getting used in this file.
data: pagedDocs.data.map((doc) => doc._id), | ||
cursor: pagedDocs.cursor | ||
}; | ||
return await getPaginatedDocs(getDocsFn, limit, skip); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: okay bad news 😞 I found an edge case that breaks this logic... We should never get null id values back from these freetext queries, but it is possible to get duplicate id values.
To test this, I created a contact with:
"name": "Alberto O'Kon Rivera",
"short_name": "River",
Then I did a search with freetext=river
. The list of ids returned contained two instances of the same id value for this contact. This is because the getByStartsWithFreetext
query will match the emissions for both River
and Rivera
(as intended). To make things even worse, even if we dupe-checked the ids returned for a given page, I don't think there is any reason that Couch could not give us more dupes of the same id later on different pages (because the view will return things ordered by key not by id).... 😬
@sugat009 definitely interested to hear your thoughts on the best way to proceed here. Pragmatically, my current inclination is to guarantee that each page will be free from duplicates, but then note in our documentation that different pages could contain the same ids. (I cannot come up with any feasible way to guarantee no dupes across pages).
The good news is that if we decide to just dupe-check on a page-by-page basis, then I think we can easily just reuse the fetch and filter logic here like this:
const uuidSet = new Set<string>();
const filterFn = (uuid: Nullable<string>): boolean => {
if (!uuid) {
return false;
}
const { size } = uuidSet;
uuidSet.add(uuid);
return uuidSet.size !== size;
};
return await fetchAndFilter(
getDocsFn,
filterFn,
limit
)(limit, skip);
(We just need to update the fetchAndFilter
signature and change <T extends Doc>
to just be <T>
. I don't think we actually need the extends Doc
for the current functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same thing is going to apply for the contact logic too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, after a bunch more investigation, I still believe the best approach is to ensure there are no duplicate entries on each page, but allow duplicate entries to be returned across pages. This is slightly better than in the current implementation of shared-libs/search
where, as far as I can tell, the default page limit was 50
and there was no dupe resolution at all for single-request search queries (so it would have been possible to get duplicate results back even on the same page).
For the record, here are the various approaches I considered to try and avoid returning duplicate entries across pages (and why each of them will not work):
- Edit the view code to only emit once for each contact.
- This would just break freetext searching since it depends on mapping multiple search terms to the same contact.
- Use a
reduce
function on the view to combine duplicate results.- To properly support freetext searches, we need to emit multiple keys for the same contact. The "duplicate" results are produced at runtime and are caused by doing a "starts-with" search across a range of keys. I do not see any way to use a
reduce
to join results for a contact that would be useful for a "starts-with" type search.
- To properly support freetext searches, we need to emit multiple keys for the same contact. The "duplicate" results are produced at runtime and are caused by doing a "starts-with" search across a range of keys. I do not see any way to use a
- Emit the contact
_id
value as part of the key. Then the query results would be sorted by contact id.- You cannot use the
_id
as the first value in a key array (unless you wanted to only search for entries for a particular id). Otherwise you would need to match all for the first element in the array and that would prevent filtering by any subsequent elements in the array. Thestart_key
/end_key
boundaries would essentially just include everything. - You cannot emit the
_id
value as part of the key (concatenated to the actual search key). If you put the_id
at the beginning of the key value, it would break the "starts-with" range search functionality. If you put the_id
at the end of the key value, there still would be no way to guarantee no duplicates because the results would be sorted by key and the same id value might be later in the results after a slightly different search term.
- You cannot use the
- Just pull back all the ids and filter them for duplicates.
- Technically this would work, but would totally defeat the purpose of having a paged API. It would also be much less performant than our current approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried this to learn and see if there's anything else that can be done. I did not find anything different than the above options. The technically feasible thing to do right now is as @jkuester suggested i.e. to filter duplicate ids on the same page but allow them on different pages.
When you think about it, it isn't that bad realistically, as the default page size is 10000
and the chances that the IDs will be on different pages are not that high, I think.
Co-authored-by: Joshua Kuestersteffen <[email protected]>
Co-authored-by: Joshua Kuestersteffen <[email protected]>
Co-authored-by: Joshua Kuestersteffen <[email protected]>
Co-authored-by: Joshua Kuestersteffen <[email protected]>
…thub.com:medic/cht-core into 9586-implement-freetext-search-in-cht-datasource
Co-authored-by: Joshua Kuestersteffen <[email protected]>
Co-authored-by: Joshua Kuestersteffen <[email protected]>
Co-authored-by: Joshua Kuestersteffen <[email protected]>
…thub.com:medic/cht-core into 9586-implement-freetext-search-in-cht-datasource
return isRecord(qualifier) && | ||
hasField(qualifier, { name: 'freetext', type: 'string' }) && | ||
qualifier.freetext.length >= 3 && | ||
!qualifier.freetext.includes(' '); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so once again I missed some important logic here. 😬 🤦 I was reviewing the view query code and realized that the keyed freetext values are actually emitted whole and un-split. So, these actually can include whitespace values (as opposed to the non-keyed values which do not include white-spaces). I did some testing to confirm. If you have a doc with the field: "name": "Auburn's Household"
the following keys get emitted for this field:
["auburn's"]
["household"]
["name:auburn's household"]
We should allow for searching for any of these. In fact, I found this issue when reviewing your shared-libs/validation changes. That code definitely relies on this behavior (and we need to avoid having to fall back to the Couch view in the shared-libs/validation
code...)
So, I think we need to update this logic to validate that either the freetext
value is keyed (it includes :
) OR is has no whitespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, our white-space check should probably match the view logic and use /\s+/
to check for white-space instead of just ' '
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, @sugat009, this is probably as good a time as any to pause and reconsider if we should include this kind of validation here in cht-datasource at all... 🤔 Originally I was thinking that it would be best to protect against running searches with keys that could not return any results. Throwing an error for an invalid freetext qualifier allows consumers to distinguish between a search that just did not return any results vs a search with an invalid qualifier.
The more I consider this, though, the less confident I get in that behavior. It seems like someone using cht-datasource to search with (e.g. someone calling the REST apis) might not care about the difference between an invalid qualifier and not finding any results. In both cases, just getting back an empty page might be reasonable/desired behavior??? 🤔
What are your thoughts here? Personally, I am still leaning to keeping the validation here if only to force consumers to be aware of some of the weird behavior that these search rules entail. Take the above "name": "Auburn's Household"
case. If you do a view query for ["name:auburn's household"]
, you will find that contact. However, if you do a view query for ["auburn's household"]
you will NOT find the contact (because of how things get indexed). This is just weird and unexpected if you are not intimately familiar with the internal logic of the freetext views. Seems better to surface this weirdness here... I think.
Description
Closes: #9586
Code review checklist
Compose URLs
If Build CI hasn't passed, these may 404:
License
The software is provided under AGPL-3.0. Contributions to this project are accepted under the same license.