Skip to content

Commit

Permalink
resolve conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
melihaydogd committed Jun 27, 2024
2 parents 19e62c0 + 90251b3 commit 38c48d4
Show file tree
Hide file tree
Showing 65 changed files with 4,287 additions and 899 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class RuntimeSearchParam {
private final Map<String, String> myUpliftRefchains = new HashMap<>();
private final ComboSearchParamType myComboSearchParamType;
private final List<Component> myComponents;
private final IIdType myIdUnqualifiedVersionless;
private IPhoneticEncoder myPhoneticEncoder;

/**
Expand Down Expand Up @@ -127,6 +128,7 @@ public RuntimeSearchParam(
super();

myId = theId;
myIdUnqualifiedVersionless = theId != null ? theId.toUnqualifiedVersionless() : null;
myUri = theUri;
myName = theName;
myDescription = theDescription;
Expand Down Expand Up @@ -214,6 +216,10 @@ public IIdType getId() {
return myId;
}

public IIdType getIdUnqualifiedVersionless() {
return myIdUnqualifiedVersionless;
}

public String getUri() {
return myUri;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.api.Constants;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.commons.lang3.StringUtils.defaultString;

public class SpecialParam extends BaseParam /*implements IQueryParameterType*/ {
private static final Logger ourLog = LoggerFactory.getLogger(StringParam.class);

private String myValue;
private boolean myContains;

/**
* Constructor
Expand All @@ -40,7 +47,11 @@ public SpecialParam() {

@Override
String doGetQueryParameterQualifier() {
return null;
if (myContains) {
return Constants.PARAMQUALIFIER_STRING_CONTAINS;
} else {
return null;
}
}

/**
Expand All @@ -56,6 +67,15 @@ String doGetValueAsQueryToken(FhirContext theContext) {
*/
@Override
void doSetValueAsQueryToken(FhirContext theContext, String theParamName, String theQualifier, String theParameter) {
if (Constants.PARAMQUALIFIER_STRING_CONTAINS.equals(theQualifier)) {
if (theParamName.equalsIgnoreCase(Constants.PARAM_TEXT)
|| theParamName.equalsIgnoreCase(Constants.PARAM_CONTENT)) {
setContains(true);
} else {
ourLog.debug(
"Attempted to set the :contains modifier on a special search parameter that was not `_text` or `_content`. This is not supported.");
}
}
setValue(ParameterUtil.unescape(theParameter));
}

Expand Down Expand Up @@ -93,4 +113,52 @@ public String toString() {
private static String toSystemValue(UriDt theSystem) {
return theSystem.getValueAsString();
}
/**
* Special parameter modifier <code>:contains</code> for _text and _content
*/
public boolean isContains() {
return myContains;
}

/**
* Special parameter modifier <code>:contains</code> for _text and _content
*/
public SpecialParam setContains(boolean theContains) {
myContains = theContains;
if (myContains) {
setMissing(null);
}
return this;
}

@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(isContains())
.append(getValue())
.append(getMissing())
.toHashCode();
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof SpecialParam)) {
return false;
}

SpecialParam other = (SpecialParam) obj;

EqualsBuilder eb = new EqualsBuilder();
eb.append(myContains, other.myContains);
eb.append(myValue, other.myValue);
eb.append(getMissing(), other.getMissing());

return eb.isEquals();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ca.uhn.fhir.rest.param;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.LoggerFactory;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith(MockitoExtension.class)
public class SpecialParamTest {

private static final Logger ourLog = (Logger) LoggerFactory.getLogger(StringParam.class);
private ListAppender<ILoggingEvent> myListAppender = new ListAppender<>();

@Mock
private FhirContext myContext;

@BeforeEach
public void beforeEach(){
myListAppender = new ListAppender<>();
myListAppender.start();
ourLog.addAppender(myListAppender);
}

@AfterEach
public void afterEach(){
myListAppender.stop();
}

@Test
public void testEquals() {
SpecialParam specialParam = new SpecialParam();
specialParam.setValueAsQueryToken(myContext, Constants.PARAM_TEXT, Constants.PARAMQUALIFIER_STRING_CONTAINS, "my-test-value");

SpecialParam specialParam2 = new SpecialParam();
specialParam2.setValueAsQueryToken(myContext, Constants.PARAM_TEXT, Constants.PARAMQUALIFIER_STRING_CONTAINS, "my-test-value");
assertThat(specialParam).isEqualTo(specialParam2);
}

@Test
public void testContainsOnlyWorksForSpecificParams() {
SpecialParam specialParamText = new SpecialParam();
specialParamText.setValueAsQueryToken(myContext, Constants.PARAM_TEXT, Constants.PARAMQUALIFIER_STRING_CONTAINS, "my-test-value");
assertTrue(specialParamText.isContains());

SpecialParam specialParamContent = new SpecialParam();
specialParamContent.setValueAsQueryToken(myContext, Constants.PARAM_CONTENT, Constants.PARAMQUALIFIER_STRING_CONTAINS, "my-test-value");
assertTrue(specialParamContent.isContains());

SpecialParam nonTextSpecialParam = new SpecialParam();
nonTextSpecialParam.setValueAsQueryToken(myContext, "name", Constants.PARAMQUALIFIER_STRING_CONTAINS, "my-test-value");
assertFalse(nonTextSpecialParam.isContains());
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@

import static ca.uhn.fhir.rest.api.Constants.PARAMQUALIFIER_STRING_TEXT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
public class StringParamTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: perf
issue: 5885
title: "When unique and non-unique combo parameters are in use on a server, FHIR Transaction and Reindex Job
performance has been optimized by pre-fetching all existing combo index rows for a large batch of resources
in a single database operation. This should yield a meaningful performance improvement on such systems."
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: perf
issue: 5885
title: "Indexing for non-unique combo Search Parameters has been improved,
using a new hash-based index that should perform significantly better in
many circumstances."
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
type: perf
issue: 5885
title: "Indexing for unique combo Search Parameters has been modified so that a hash
value is now stored. This hash value is not yet used in searching or enforcing uniqueness,
but will be in the future in order to reduce the space required to store the indexes and the
current size limitation on unique indexes."
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
type: fix
issue: 6044
title: "Fixed an issue where doing a cache refresh with advanced Hibernate Search
enabled would result in an infinite loop of cache refresh -> search for
StructureDefinition -> cache refresh, etc
"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
type: fix
issue: 6046
title: "Previously, using `_text` and `_content` searches in Hibernate Search in R5 was not supported. This issue has been fixed."
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
type: add
issue: 6046
title: "Added support for `:contains` parameter qualifier on the `_text` and `_content` Search Parameters. When using Hibernate Search, this will cause
the search to perform an substring match on the provided value. Documentation can be found [here](/hapi-fhir/docs/server_jpa/elastic.html#performing-fulltext-search-in-luceneelasticsearch)."
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,55 @@
The HAPI JPA Server supports optional indexing via Hibernate Search when configured to use Lucene or Elasticsearch.
This is required to support the `_content`, or `_text` search parameters.

# Performing Fulltext Search in Lucene/Elasticsearch

When enabled, searches for `_text` and `_content` are forwarded to the underlying Hibernate Search engine, which can be backed by either Elasticsearch or Lucene.
By default, search is supported in the way indicated in the [FHIR Specification on _text/_content Search](https://www.hl7.org/fhir/search.html#_text). This means that
queries like the following can be evaluated:

```http request
GET [base]/Observation?_content=cancer OR metastases OR tumor
```
To understand how this works, look at the following example. During ingestion, the fields required for `_content` and `_text` searches are stored in the backing engine, after undergoing normalization and analysis. For example consider this Observation:

```json
{
"resourceType" : "Observation",
"code" : {
"coding" : [{
"system" : "http://loinc.org",
"code" : "15074-8",
"display" : "Glucose [Moles/volume] in Blood Found during patient's visit!"
}]
}
"valueQuantity" : {
"value" : 6.3,
"unit" : "mmol/l",
"system" : "http://unitsofmeasure.org",
"code" : "mmol/L"
}
}
```

In the display section, once parsed and analyzed, will result in the followings tokens being generated to be able to be searched on:

```json
["glucose", "mole", "volume", "blood", "found", "during", "patient", "visit"]
```

You will notice that plurality is removed, and the text has been normalized, and special characters removed. When searched for, the search terms will be normalized in the same fashion.

However, the default implementation will not allow you to search for an exact match over a long string that contains special characters or other characters which could be broken apart during tokenization. E.g. an exact match for `_content=[Moles/volume]` would not return this result.

In order to perform such an exact string match in Lucene/Elasticsearch, you should modify the `_text` or `_content` Search Parameter with the `:contains` modifier, as follows:

```http request
GET [base]/Observation?_content:contains=[Moles/volume]
```

Using `:contains` on the `_text` or `_content` modifies the search engine to perform a direct substring match anywhere within the field.


# Experimental Extended Lucene/Elasticsearch Indexing

Additional indexing is implemented for simple search parameters of type token, string, and reference.
Expand Down Expand Up @@ -68,19 +117,19 @@ The `:text` modifier provides the same [modified Simple Query Syntax](#modified-
See https://www.hl7.org/fhir/search.html#token.

## Supported Common and Special Search Parameters
| Parameter | Supported | type |
|--------------|-----------|--------|
| _id | no | |
| _lastUpdated | yes | date |
| _tag | yes | token |
| _profile | yes | URI |
| _security | yes | token |
| _text | yes | string |
| _content | yes | string |
| _list | no | |
| _has | no | |
| _type | no | |
| _source | yes | URI |
| Parameter | Supported | type |
|--------------|-----------|------------------------|
| _id | no | |
| _lastUpdated | yes | date |
| _tag | yes | token |
| _profile | yes | URI |
| _security | yes | token |
| _text | yes | string(R4) special(R5) |
| _content | yes | string(R4) special(R5) |
| _list | no | |
| _has | no | |
| _type | no | |
| _source | yes | URI |

## ValueSet autocomplete extension

Expand Down
Loading

0 comments on commit 38c48d4

Please sign in to comment.