
001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.search.builder.predicate; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; 024import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder; 025import ca.uhn.fhir.jpa.util.QueryParameterUtils; 026import ca.uhn.fhir.model.api.IQueryParameterType; 027import ca.uhn.fhir.rest.param.UriParam; 028import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; 029import ca.uhn.fhir.util.StringUtil; 030import ca.uhn.fhir.util.UrlUtil; 031import com.healthmarketscience.sqlbuilder.BinaryCondition; 032import com.healthmarketscience.sqlbuilder.Condition; 033import com.healthmarketscience.sqlbuilder.FunctionCall; 034import com.healthmarketscience.sqlbuilder.UnaryCondition; 035import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn; 036import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable; 037 038import java.util.List; 039 040import static ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder.createLeftAndRightMatchLikeExpression; 041import static ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder.createLeftMatchLikeExpression; 042 043public abstract class BaseResourceHistoryPredicateBuilder extends BaseJoiningPredicateBuilder 044 implements ISourcePredicateBuilder { 045 protected DbColumn myColumnSourceUri; 046 protected DbColumn myColumnRequestId; 047 protected DbColumn myResourceIdColumn; 048 049 public BaseResourceHistoryPredicateBuilder( 050 SearchQueryBuilder theSearchSqlBuilder, DbTable theTable, String theResourceIdColumn) { 051 super(theSearchSqlBuilder, theTable); 052 myColumnSourceUri = getTable().addColumn("SOURCE_URI"); 053 myColumnRequestId = getTable().addColumn("REQUEST_ID"); 054 myResourceIdColumn = getTable().addColumn(theResourceIdColumn); 055 } 056 057 @Override 058 public DbColumn getResourceIdColumn() { 059 return myResourceIdColumn; 060 } 061 062 @Override 063 public Condition createPredicateSourceUri(String theSourceUri) { 064 return combineWithRequestPartitionIdPredicate( 065 getRequestPartitionId(), BinaryCondition.equalTo(myColumnSourceUri, generatePlaceholder(theSourceUri))); 066 } 067 068 @Override 069 public Condition createPredicateMissingSourceUri() { 070 return combineWithRequestPartitionIdPredicate( 071 getRequestPartitionId(), UnaryCondition.isNull(myColumnSourceUri)); 072 } 073 074 @Override 075 public Condition createPredicateSourceUriWithModifiers( 076 IQueryParameterType theQueryParameter, JpaStorageSettings theStorageSetting, String theSourceUri) { 077 078 if (theQueryParameter.getMissing() != null && !theQueryParameter.getMissing()) { 079 UnaryCondition condition = UnaryCondition.isNotNull(myColumnSourceUri); 080 return combineWithRequestPartitionIdPredicate(getRequestPartitionId(), condition); 081 } 082 083 if (theQueryParameter instanceof UriParam uriParam && theQueryParameter.getQueryParameterQualifier() != null) { 084 Condition condition = 085 switch (uriParam.getQualifier()) { 086 case ABOVE -> createPredicateSourceAbove(theSourceUri); 087 case BELOW -> createPredicateSourceBelow(theSourceUri); 088 case CONTAINS -> createPredicateSourceContains(theStorageSetting, theSourceUri); 089 }; 090 return combineWithRequestPartitionIdPredicate(getRequestPartitionId(), condition); 091 } 092 093 return createPredicateSourceUri(theSourceUri); 094 } 095 096 private Condition createPredicateSourceAbove(String theSourceUri) { 097 List<String> aboveUriCandidates = UrlUtil.getAboveUriCandidates(theSourceUri); 098 List<String> aboveUriPlaceholders = generatePlaceholders(aboveUriCandidates); 099 return QueryParameterUtils.toEqualToOrInPredicate(myColumnSourceUri, aboveUriPlaceholders); 100 } 101 102 private Condition createPredicateSourceBelow(String theSourceUri) { 103 String belowLikeExpression = createLeftMatchLikeExpression(theSourceUri); 104 return BinaryCondition.like(myColumnSourceUri, generatePlaceholder(belowLikeExpression)); 105 } 106 107 private Condition createPredicateSourceContains(JpaStorageSettings theStorageSetting, String theSourceUri) { 108 if (theStorageSetting.isAllowContainsSearches()) { 109 FunctionCall upperFunction = new FunctionCall("UPPER"); 110 upperFunction.addCustomParams(myColumnSourceUri); 111 String normalizedString = StringUtil.normalizeStringForSearchIndexing(theSourceUri); 112 String containsLikeExpression = createLeftAndRightMatchLikeExpression(normalizedString); 113 return BinaryCondition.like(upperFunction, generatePlaceholder(containsLikeExpression)); 114 } else { 115 throw new MethodNotAllowedException( 116 Msg.code(getContainsModifierDisabledCode()) + ":contains modifier is disabled on this server"); 117 } 118 } 119 120 protected abstract int getContainsModifierDisabledCode(); 121 122 @Override 123 public Condition createPredicateRequestId(String theRequestId) { 124 return combineWithRequestPartitionIdPredicate( 125 getRequestPartitionId(), BinaryCondition.equalTo(myColumnRequestId, generatePlaceholder(theRequestId))); 126 } 127}