001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2024 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
044        BaseJoiningPredicateBuilder(SearchQueryBuilder theSearchSqlBuilder, DbTable theTable) {
045                super(theSearchSqlBuilder);
046                myTable = theTable;
047                myColumnPartitionId = theTable.addColumn("PARTITION_ID");
048        }
049
050        public DbTable getTable() {
051                return myTable;
052        }
053
054        public abstract DbColumn getResourceIdColumn();
055
056        public DbColumn getPartitionIdColumn() {
057                return myColumnPartitionId;
058        }
059
060        public DbColumn[] getJoinColumns() {
061                return getSearchQueryBuilder().toJoinColumns(getPartitionIdColumn(), getResourceIdColumn());
062        }
063
064        public Condition combineWithRequestPartitionIdPredicate(
065                        RequestPartitionId theRequestPartitionId, Condition theCondition) {
066                Condition partitionIdPredicate = createPartitionIdPredicate(theRequestPartitionId);
067                if (partitionIdPredicate == null) {
068                        return theCondition;
069                }
070                return QueryParameterUtils.toAndPredicate(partitionIdPredicate, theCondition);
071        }
072
073        @Nullable
074        public Condition createPartitionIdPredicate(RequestPartitionId theRequestPartitionId) {
075
076                if (theRequestPartitionId != null && !theRequestPartitionId.isAllPartitions()) {
077                        Condition condition;
078
079                        boolean defaultPartitionIsNull = getPartitionSettings().getDefaultPartitionId() == null;
080                        if (theRequestPartitionId.isDefaultPartition() && defaultPartitionIsNull) {
081                                condition = UnaryCondition.isNull(getPartitionIdColumn());
082                        } else if (theRequestPartitionId.hasDefaultPartitionId() && defaultPartitionIsNull) {
083                                List<String> placeholders = generatePlaceholders(theRequestPartitionId.getPartitionIdsWithoutDefault());
084                                UnaryCondition partitionNullPredicate = UnaryCondition.isNull(getPartitionIdColumn());
085                                Condition partitionIdsPredicate =
086                                                QueryParameterUtils.toEqualToOrInPredicate(getPartitionIdColumn(), placeholders);
087                                condition = QueryParameterUtils.toOrPredicate(partitionNullPredicate, partitionIdsPredicate);
088                        } else {
089                                List<Integer> partitionIds = theRequestPartitionId.getPartitionIds();
090                                partitionIds = replaceDefaultPartitionIdIfNonNull(getPartitionSettings(), partitionIds);
091
092                                List<String> placeholders = generatePlaceholders(partitionIds);
093                                condition = QueryParameterUtils.toEqualToOrInPredicate(getPartitionIdColumn(), placeholders);
094                        }
095                        return condition;
096                } else {
097                        return null;
098                }
099        }
100
101        public Condition createPredicateResourceIds(boolean theInverse, Collection<JpaPid> theResourceIds) {
102                Validate.notNull(theResourceIds, "theResourceIds must not be null");
103
104                Condition inResourceIds = QueryParameterUtils.toEqualToOrInPredicate(
105                                getResourceIdColumn(), generatePlaceholders(JpaPid.toLongList(theResourceIds)));
106                if (theInverse) {
107                        inResourceIds = new NotCondition(inResourceIds);
108                }
109
110                // Handle the _id parameter by adding it to the tail
111                return inResourceIds;
112        }
113
114        public static List<Integer> replaceDefaultPartitionIdIfNonNull(
115                        PartitionSettings thePartitionSettings, List<Integer> thePartitionIds) {
116                List<Integer> partitionIds = thePartitionIds;
117                if (thePartitionSettings.getDefaultPartitionId() != null) {
118                        partitionIds = partitionIds.stream()
119                                        .map(t -> t == null ? thePartitionSettings.getDefaultPartitionId() : t)
120                                        .collect(Collectors.toList());
121                }
122                return partitionIds;
123        }
124}