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.interceptor.model.RequestPartitionId;
023import ca.uhn.fhir.jpa.cache.ISearchParamIdentityCacheSvc;
024import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
025import ca.uhn.fhir.jpa.search.builder.models.MissingQueryParameterPredicateParams;
026import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
027import ca.uhn.fhir.jpa.util.QueryParameterUtils;
028import com.google.common.annotations.VisibleForTesting;
029import com.healthmarketscience.sqlbuilder.BinaryCondition;
030import com.healthmarketscience.sqlbuilder.ComboCondition;
031import com.healthmarketscience.sqlbuilder.Condition;
032import com.healthmarketscience.sqlbuilder.NotCondition;
033import com.healthmarketscience.sqlbuilder.SelectQuery;
034import com.healthmarketscience.sqlbuilder.UnaryCondition;
035import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
036import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
037import jakarta.annotation.Nonnull;
038import org.springframework.beans.factory.annotation.Autowired;
039
040import java.util.ArrayList;
041import java.util.List;
042
043public abstract class BaseSearchParamPredicateBuilder extends BaseJoiningPredicateBuilder
044                implements ICanMakeMissingParamPredicate {
045
046        private final DbColumn myColumnMissing;
047        private final DbColumn myColumnResType;
048        private final DbColumn myColumnParamName;
049        private final DbColumn myColumnResId;
050        private final DbColumn myColumnHashIdentity;
051
052        @Autowired
053        private ISearchParamIdentityCacheSvc mySearchParamIdentityCacheSvc;
054
055        @VisibleForTesting
056        public void setSearchParamIdentityCacheSvcForUnitTest(ISearchParamIdentityCacheSvc theSearchParamIdentityCacheSvc) {
057                mySearchParamIdentityCacheSvc = theSearchParamIdentityCacheSvc;
058        }
059
060        public BaseSearchParamPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder, DbTable theTable) {
061                super(theSearchSqlBuilder, theTable);
062
063                myColumnResId = getTable().addColumn("RES_ID");
064                myColumnMissing = theTable.addColumn("SP_MISSING");
065                myColumnResType = theTable.addColumn("RES_TYPE");
066                myColumnParamName = theTable.addColumn("SP_NAME");
067                myColumnHashIdentity = theTable.addColumn("HASH_IDENTITY");
068        }
069
070        public DbColumn getColumnHashIdentity() {
071                return myColumnHashIdentity;
072        }
073
074        public DbColumn getResourceTypeColumn() {
075                return myColumnResType;
076        }
077
078        public DbColumn getColumnParamName() {
079                return myColumnParamName;
080        }
081
082        public DbColumn getMissingColumn() {
083                return myColumnMissing;
084        }
085
086        @Override
087        public DbColumn getResourceIdColumn() {
088                return myColumnResId;
089        }
090
091        public Condition combineWithHashIdentityPredicate(
092                        String theResourceName, String theParamName, Condition thePredicate) {
093                List<Condition> andPredicates = new ArrayList<>();
094
095                Condition hashIdentityPredicate = createHashIdentityPredicate(theResourceName, theParamName);
096                andPredicates.add(hashIdentityPredicate);
097                andPredicates.add(thePredicate);
098
099                return QueryParameterUtils.toAndPredicate(andPredicates);
100        }
101
102        @Nonnull
103        public Condition createHashIdentityPredicate(String theResourceType, String theParamName) {
104                return createHashIdentityPredicate(getRequestPartitionId(), theResourceType, theParamName);
105        }
106
107        public Condition createHashIdentityPredicate(
108                        RequestPartitionId theRequestPartitionId, String theResourceType, String theParamName) {
109                long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(
110                                getPartitionSettings(), theRequestPartitionId, theResourceType, theParamName);
111                mySearchParamIdentityCacheSvc.findOrCreateSearchParamIdentity(hashIdentity, theResourceType, theParamName);
112                return BinaryCondition.equalTo(getColumnHashIdentity(), generatePlaceholder(hashIdentity));
113        }
114
115        public Condition createPredicateParamMissingForNonReference(
116                        String theResourceName, String theParamName, Boolean theMissing, RequestPartitionId theRequestPartitionId) {
117
118                List<Condition> conditions = new ArrayList<>();
119                if (getStorageSettings().isIndexStorageOptimized()) {
120                        conditions.add(createHashIdentityPredicate(theResourceName, theParamName));
121                } else {
122                        conditions.add(BinaryCondition.equalTo(getResourceTypeColumn(), generatePlaceholder(theResourceName)));
123                        conditions.add(BinaryCondition.equalTo(getColumnParamName(), generatePlaceholder(theParamName)));
124                }
125                conditions.add(BinaryCondition.equalTo(getMissingColumn(), generatePlaceholder(theMissing)));
126
127                ComboCondition condition = ComboCondition.and(conditions.toArray());
128                return combineWithRequestPartitionIdPredicate(theRequestPartitionId, condition);
129        }
130
131        @Override
132        public Condition createPredicateParamMissingValue(MissingQueryParameterPredicateParams theParams) {
133                SelectQuery subquery = new SelectQuery();
134                subquery.addCustomColumns(1);
135                subquery.addFromTable(getTable());
136
137                Condition hashIdentityPredicate = createHashIdentityPredicate(
138                                theParams.getRequestPartitionId(),
139                                theParams.getResourceTablePredicateBuilder().getResourceType(),
140                                theParams.getParamName());
141
142                Condition subQueryCondition = ComboCondition.and(
143                                BinaryCondition.equalTo(
144                                                getResourceIdColumn(),
145                                                theParams.getResourceTablePredicateBuilder().getResourceIdColumn()),
146                                hashIdentityPredicate);
147
148                subquery.addCondition(subQueryCondition);
149
150                Condition unaryCondition = UnaryCondition.exists(subquery);
151                if (theParams.isMissing()) {
152                        unaryCondition = new NotCondition(unaryCondition);
153                }
154
155                return combineWithRequestPartitionIdPredicate(theParams.getRequestPartitionId(), unaryCondition);
156        }
157}