From 977fb6d8cbe18c16d5eb771d382ec3714659a5c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Primo=C5=BE=20Delopst?= Date: Mon, 22 Sep 2025 16:04:44 +0200 Subject: [PATCH 1/2] Fix issue that not logical operator did not work in FHIR filter --- .../jpa/dao/predicate/SearchFilterParser.java | 16 ++++++++++------ .../uhn/fhir/jpa/search/builder/QueryStack.java | 8 +++++++- .../uhn/fhir/jpa/dao/SearchFilterSyntaxTest.java | 10 ++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/SearchFilterParser.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/SearchFilterParser.java index 124c5adbfe0d..ae7145eddce3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/SearchFilterParser.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/SearchFilterParser.java @@ -237,14 +237,14 @@ private BaseFilter parseOpen() throws FilterSyntaxException { } private BaseFilter parseLogical(BaseFilter filter) throws FilterSyntaxException { - BaseFilter result = null; - String s; FilterLogical logical; if (filter == null) { - s = "not"; + logical = new FilterLogical(); + logical.setOperation(FilterLogicalOperation.not); + logical.setFilter1(parseOpen()); } else { - s = consumeName(); + String s = consumeName(); if ((!s.equals("or")) && (!s.equals("and")) && (!s.equals("not"))) { throw new FilterSyntaxException(Msg.code(1059) + String.format("Unexpected Name %s at %d", s, cursor)); } @@ -260,8 +260,8 @@ private BaseFilter parseLogical(BaseFilter filter) throws FilterSyntaxException } logical.setFilter2(parseOpen()); - result = logical; } + result = logical; return result; } @@ -594,8 +594,12 @@ void setFilter2(BaseFilter FFilter2) { @Override public String toString() { - return FFilter1.toString() + " " + if ("not".equals(CODES_LogicalOperation.get(getOperation().ordinal()))) { + return CODES_LogicalOperation.get(getOperation().ordinal()) + " " + FFilter1.toString(); + } else { + return FFilter1.toString() + " " + CODES_LogicalOperation.get(getOperation().ordinal()) + " " + FFilter2.toString(); + } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java index 19ba187c114a..26b6d3b8f325 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java @@ -98,6 +98,7 @@ import com.healthmarketscience.sqlbuilder.Condition; import com.healthmarketscience.sqlbuilder.Expression; import com.healthmarketscience.sqlbuilder.InCondition; +import com.healthmarketscience.sqlbuilder.NotCondition; import com.healthmarketscience.sqlbuilder.SelectQuery; import com.healthmarketscience.sqlbuilder.SetOperationQuery; import com.healthmarketscience.sqlbuilder.Subquery; @@ -962,7 +963,9 @@ private Condition createPredicateFilter( theRequestPartitionId); // Right side - Condition yPredicate = createPredicateFilter( + Condition yPredicate = ((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.not ? + null : + createPredicateFilter( theQueryStack3, ((SearchFilterParser.FilterLogical) theFilter).getFilter2(), theResourceName, @@ -974,6 +977,9 @@ private Condition createPredicateFilter( } else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.or) { return ComboCondition.or(xPredicate, yPredicate); + } else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() + == SearchFilterParser.FilterLogicalOperation.not) { + return new NotCondition(xPredicate); } else { // Shouldn't happen throw new InvalidRequestException(Msg.code(1205) + "Don't know how to handle operation " diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/SearchFilterSyntaxTest.java b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/SearchFilterSyntaxTest.java index 7a1f95a8a3fe..cf54ed9332bb 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/SearchFilterSyntaxTest.java +++ b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/SearchFilterSyntaxTest.java @@ -26,6 +26,11 @@ public void testToken() throws SearchFilterParser.FilterSyntaxException { testParse("name eq loinc|1234"); } + @Test + public void testTokenNot() throws SearchFilterParser.FilterSyntaxException { + testParse("not name eq loinc|1234"); + } + @Test public void testUrl() throws SearchFilterParser.FilterSyntaxException { testParse("name in http://loinc.org/vs/LP234"); @@ -56,6 +61,11 @@ public void testFilter2() throws SearchFilterParser.FilterSyntaxException { testParse("related[type eq comp and this lt that].target pr false"); } + @Test + public void testFilter2WithNot() throws SearchFilterParser.FilterSyntaxException { + testParse("related[type eq comp and not this lt that].target pr false"); + } + @Test public void testParentheses() throws SearchFilterParser.FilterSyntaxException { testParse("((userName eq \"bjensen\") or (userName eq \"jdoe\")) and (code sb snomed|diabetes)"); From 8252539608d3eadf1193fa1fbf5894c75ca74d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Primo=C5=BE=20Delopst?= Date: Mon, 22 Sep 2025 16:15:30 +0200 Subject: [PATCH 2/2] Fix issue that not logical operator did not work in FHIR filter (formatting) --- .../jpa/dao/predicate/SearchFilterParser.java | 2 +- .../fhir/jpa/search/builder/QueryStack.java | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/SearchFilterParser.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/SearchFilterParser.java index ae7145eddce3..eac5b7a06e20 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/SearchFilterParser.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/SearchFilterParser.java @@ -598,7 +598,7 @@ public String toString() { return CODES_LogicalOperation.get(getOperation().ordinal()) + " " + FFilter1.toString(); } else { return FFilter1.toString() + " " - + CODES_LogicalOperation.get(getOperation().ordinal()) + " " + FFilter2.toString(); + + CODES_LogicalOperation.get(getOperation().ordinal()) + " " + FFilter2.toString(); } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java index 26b6d3b8f325..b0af13edec3b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java @@ -963,13 +963,14 @@ private Condition createPredicateFilter( theRequestPartitionId); // Right side - Condition yPredicate = ((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.not ? - null : - createPredicateFilter( - theQueryStack3, - ((SearchFilterParser.FilterLogical) theFilter).getFilter2(), - theResourceName, - theRequestPartitionId); + Condition yPredicate = ((SearchFilterParser.FilterLogical) theFilter).getOperation() + == SearchFilterParser.FilterLogicalOperation.not + ? null + : createPredicateFilter( + theQueryStack3, + ((SearchFilterParser.FilterLogical) theFilter).getFilter2(), + theResourceName, + theRequestPartitionId); if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.and) { @@ -977,8 +978,8 @@ private Condition createPredicateFilter( } else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() == SearchFilterParser.FilterLogicalOperation.or) { return ComboCondition.or(xPredicate, yPredicate); - } else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() - == SearchFilterParser.FilterLogicalOperation.not) { + } else if (((SearchFilterParser.FilterLogical) theFilter).getOperation() + == SearchFilterParser.FilterLogicalOperation.not) { return new NotCondition(xPredicate); } else { // Shouldn't happen