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.model.config.PartitionSettings;
024import ca.uhn.fhir.jpa.model.dao.JpaPid;
025import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
026import ca.uhn.fhir.jpa.util.QueryParameterUtils;
027import com.healthmarketscience.sqlbuilder.Condition;
028import com.healthmarketscience.sqlbuilder.NotCondition;
029import com.healthmarketscience.sqlbuilder.UnaryCondition;
030import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
031import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
032import jakarta.annotation.Nullable;
033import org.apache.commons.lang3.Validate;
034
035import java.util.Collection;
036import java.util.List;
037import java.util.stream.Collectors;
038
039public abstract class BaseJoiningPredicateBuilder extends BasePredicateBuilder {
040
041        private final DbTable myTable;
042        private final DbColumn myColumnPartitionId;
043        private final DbColumn myColumnResType;
044
045        BaseJoiningPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder, DbTable theTable) {
046                super(theSearchSqlBuilder);
047                myTable = theTable;
048                myColumnPartitionId = theTable.addColumn("PARTITION_ID");
049                myColumnResType = theTable.addColumn("RES_TYPE");
050        }
051
052        public DbTable getTable() {
053                return myTable;
054        }
055
056        public abstract DbColumn getResourceIdColumn();
057
058        public DbColumn getPartitionIdColumn() {
059                return myColumnPartitionId;
060        }
061
062        public DbColumn getResourceTypeColumn() {
063                return myColumnResType;
064        }
065
066        public DbColumn[] getJoinColumns() {
067                return getSearchQueryBuilder().toJoinColumns(getPartitionIdColumn(), getResourceIdColumn());
068        }
069
070        public Condition combineWithRequestPartitionIdPredicate(
071                        RequestPartitionId theRequestPartitionId, Condition theCondition) {
072                Condition partitionIdPredicate = createPartitionIdPredicate(theRequestPartitionId);
073                if (partitionIdPredicate == null) {
074                        return theCondition;
075                }
076                return QueryParameterUtils.toAndPredicate(partitionIdPredicate, theCondition);
077        }
078
079        @Nullable
080        public Condition createPartitionIdPredicate(RequestPartitionId theRequestPartitionId) {
081                if (theRequestPartitionId != null && !theRequestPartitionId.isAllPartitions()) {
082                        Condition condition;
083
084                        Integer defaultPartitionId = getPartitionSettings().getDefaultPartitionId();
085                        boolean defaultPartitionIsNull = defaultPartitionId == null;
086                        if (theRequestPartitionId.isPartition(defaultPartitionId) && defaultPartitionIsNull) {
087                                condition = UnaryCondition.isNull(getPartitionIdColumn());
088                        } else if (theRequestPartitionId.hasDefaultPartitionId(defaultPartitionId) && defaultPartitionIsNull) {
089                                List<String> placeholders = generatePlaceholders(theRequestPartitionId.getPartitionIdsWithoutDefault());
090                                UnaryCondition partitionNullPredicate = UnaryCondition.isNull(getPartitionIdColumn());
091                                Condition partitionIdsPredicate =
092                                                QueryParameterUtils.toEqualToOrInPredicate(getPartitionIdColumn(), placeholders);
093                                condition = QueryParameterUtils.toOrPredicate(partitionNullPredicate, partitionIdsPredicate);
094                        } else {
095                                List<Integer> partitionIds = theRequestPartitionId.getPartitionIds();
096                                partitionIds = replaceDefaultPartitionIdIfNonNull(getPartitionSettings(), partitionIds);
097
098                                List<String> placeholders = generatePlaceholders(partitionIds);
099                                condition = QueryParameterUtils.toEqualToOrInPredicate(getPartitionIdColumn(), placeholders);
100                        }
101                        return condition;
102                } else {
103                        return null;
104                }
105        }
106
107        public Condition createPredicateResourceIds(boolean theInverse, Collection<JpaPid> theResourceIds) {
108                Validate.notNull(theResourceIds, "theResourceIds must not be null");
109
110                Condition inResourceIds = QueryParameterUtils.toEqualToOrInPredicate(
111                                getResourceIdColumn(), generatePlaceholders(JpaPid.toLongList(theResourceIds)));
112                if (theInverse) {
113                        inResourceIds = new NotCondition(inResourceIds);
114                }
115
116                // Handle the _id parameter by adding it to the tail
117                return inResourceIds;
118        }
119
120        public static List<Integer> replaceDefaultPartitionIdIfNonNull(
121                        PartitionSettings thePartitionSettings, List<Integer> thePartitionIds) {
122                List<Integer> partitionIds = thePartitionIds;
123                if (thePartitionSettings.getDefaultPartitionId() != null) {
124                        partitionIds = partitionIds.stream()
125                                        .map(t -> t == null ? thePartitionSettings.getDefaultPartitionId() : t)
126                                        .collect(Collectors.toList());
127                }
128                return partitionIds;
129        }
130}