diff --git a/server/src/main/java/au/org/aodn/ogcapi/server/core/service/ElasticSearch.java b/server/src/main/java/au/org/aodn/ogcapi/server/core/service/ElasticSearch.java index bcbf35bc..3e8af415 100644 --- a/server/src/main/java/au/org/aodn/ogcapi/server/core/service/ElasticSearch.java +++ b/server/src/main/java/au/org/aodn/ogcapi/server/core/service/ElasticSearch.java @@ -267,17 +267,35 @@ public ElasticSearchBase.SearchResult searchByParameters(Li should = new ArrayList<>(); for (String t : keywords) { - should.add(CQLFields.fuzzy_title.getPropertyEqualToQuery(t)); - should.add(CQLFields.fuzzy_desc.getPropertyEqualToQuery(t)); - should.add(CQLFields.parameter_vocabs.getPropertyEqualToQuery(t)); - should.add(CQLFields.organisation_vocabs.getPropertyEqualToQuery(t)); - should.add(CQLFields.platform_vocabs.getPropertyEqualToQuery(t)); - should.add(CQLFields.id.getPropertyEqualToQuery(t)); + // If user's input (keywords) starts and ends with quote ", and the text is not empty + // treat the user intend to search with the exact term, + // instead of searching in fuzzy fields i.e., fuzzy_title and fuzzy_desc, + // search in the original title and description fields + // other fields are searched with the same term regardless of exact match or not, as they do not use fuzzy matching. + boolean isExact = t.startsWith("\"") && t.endsWith("\"") && t.length() > 2; + // If search text with double quote, remove quotes, + // otherwise keeps same + String term = isExact ? t.substring(1, t.length() - 1) : t; + + if (isExact) { + // Match phrase in original title and description, not use fuzzy fields + should.add(CQLFields.title.getPropertyEqualToQuery(term)); + should.add(CQLFields.description.getPropertyEqualToQuery(term)); + } + else { + should.add(CQLFields.fuzzy_title.getPropertyEqualToQuery(term)); + should.add(CQLFields.fuzzy_desc.getPropertyEqualToQuery(term)); + } + should.add(CQLFields.parameter_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.organisation_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.platform_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.id.getPropertyEqualToQuery(term)); // A request to not using acronym in title and description in metadata, hence these // acronym moved to links, for example NRMN record is mentioned in the link title. // This is a work-around to the requirement but still allow use of NRMN - should.add(CQLFields.links_title_contains.getPropertyEqualToQuery(t)); - should.add(CQLFields.credit_contains.getPropertyEqualToQuery(t)); + // links_title_contains and credit_contains use match query by default, exact match is not applied here + should.add(CQLFields.links_title_contains.getPropertyEqualToQuery(term)); + should.add(CQLFields.credit_contains.getPropertyEqualToQuery(term)); } } diff --git a/server/src/test/java/au/org/aodn/ogcapi/server/service/ElasticSearchTest.java b/server/src/test/java/au/org/aodn/ogcapi/server/service/ElasticSearchTest.java index 624182f5..340536bc 100644 --- a/server/src/test/java/au/org/aodn/ogcapi/server/service/ElasticSearchTest.java +++ b/server/src/test/java/au/org/aodn/ogcapi/server/service/ElasticSearchTest.java @@ -4,6 +4,7 @@ import au.org.aodn.ogcapi.server.core.model.EsFeatureCollectionModel; import au.org.aodn.ogcapi.server.core.model.EsFeatureModel; import au.org.aodn.ogcapi.server.core.model.EsPolygonModel; +import au.org.aodn.ogcapi.server.core.model.enumeration.CQLFields; import au.org.aodn.ogcapi.server.core.model.ogc.FeatureRequest; import au.org.aodn.ogcapi.server.core.service.ElasticSearch; import au.org.aodn.ogcapi.server.core.service.ElasticSearchBase; @@ -13,6 +14,7 @@ import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.elasticsearch.core.search.HitsMetadata; import co.elastic.clients.elasticsearch.core.search.TotalHits; +import co.elastic.clients.elasticsearch._types.query_dsl.*; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -116,5 +118,56 @@ public void searchFeatureSummaryTest() throws IOException { featureProps.get("key")); } + @Test + public void searchByParametersWithDoubleQuote() throws Exception { + String keyword = "\"ocean temperature\""; + List keywords = List.of(keyword); + List should = new ArrayList<>(); + for (String t : keywords) { + boolean isExact = t.startsWith("\"") && t.endsWith("\"") && t.length() > 2; + String term = isExact ? t.substring(1, t.length() - 1) : t; + if (isExact) { + should.add(CQLFields.title.getPropertyEqualToQuery(term)); + should.add(CQLFields.description.getPropertyEqualToQuery(term)); + } else { + should.add(CQLFields.fuzzy_title.getPropertyEqualToQuery(term)); + should.add(CQLFields.fuzzy_desc.getPropertyEqualToQuery(term)); + } + should.add(CQLFields.parameter_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.organisation_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.platform_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.id.getPropertyEqualToQuery(term)); + should.add(CQLFields.links_title_contains.getPropertyEqualToQuery(term)); + should.add(CQLFields.credit_contains.getPropertyEqualToQuery(term)); + } + assertEquals(8, should.size(), "Exact match should produce 8 queries (title + description + other fields)"); + assertTrue(should.get(0).isMatchPhrase(), "Title query should be MatchPhraseQuery"); + assertTrue(should.get(1).isMatchPhrase(), "Description query should be MatchPhraseQuery"); + } + @Test + public void searchByParametersWithoutDoubleQuote() throws Exception { + String keyword = "ocean temperature"; + List keywords = List.of(keyword); + List should = new ArrayList<>(); + for (String t : keywords) { + boolean isExact = t.startsWith("\"") && t.endsWith("\"") && t.length() > 2; + String term = isExact ? t.substring(1, t.length() - 1) : t; + if (isExact) { + should.add(CQLFields.title.getPropertyEqualToQuery(term)); + should.add(CQLFields.description.getPropertyEqualToQuery(term)); + } else { + should.add(CQLFields.fuzzy_title.getPropertyEqualToQuery(term)); + should.add(CQLFields.fuzzy_desc.getPropertyEqualToQuery(term)); + } + should.add(CQLFields.parameter_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.organisation_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.platform_vocabs.getPropertyEqualToQuery(term)); + should.add(CQLFields.id.getPropertyEqualToQuery(term)); + should.add(CQLFields.links_title_contains.getPropertyEqualToQuery(term)); + should.add(CQLFields.credit_contains.getPropertyEqualToQuery(term)); + } + assertEquals(8, should.size(), "Fuzzy match should produce 8 queries"); + assertTrue(should.get(0).isMatch(), "fuzzy_title should be MatchQuery"); + } }