001package ca.uhn.fhir.jpa.dao.predicate.querystack;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.interceptor.model.RequestPartitionId;
024import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinEnum;
025import ca.uhn.fhir.jpa.dao.predicate.SearchBuilderJoinKey;
026import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
027import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
028import org.apache.commons.lang3.Validate;
029
030import javax.persistence.criteria.AbstractQuery;
031import javax.persistence.criteria.CriteriaBuilder;
032import javax.persistence.criteria.Expression;
033import javax.persistence.criteria.From;
034import javax.persistence.criteria.Join;
035import javax.persistence.criteria.Order;
036import javax.persistence.criteria.Path;
037import javax.persistence.criteria.Predicate;
038import javax.persistence.criteria.Root;
039import javax.persistence.criteria.Subquery;
040import java.util.Date;
041import java.util.List;
042import java.util.Map;
043import java.util.Optional;
044import java.util.Stack;
045
046import static org.apache.commons.lang3.StringUtils.isNotBlank;
047
048/**
049 * This class represents a SQL SELECT statement that is selecting for resource PIDs, ie.
050 * the <code>RES_ID</code> column on the <code>HFJ_RESOURCE</code> ({@link ca.uhn.fhir.jpa.model.entity.ResourceTable})
051 * table.
052 * <p>
053 * We add predicates (WHERE A=B) to it, and can join other tables to it as well. At the root of the query
054 * we are typically doing a <code>select RES_ID from HFJ_RESOURCE where (....)</code> and this class
055 * is used to build the <i>where</i> clause. In the case of subqueries though, we may be performing a
056 * select on a different table since many tables have a column with a FK dependency on RES_ID.
057 * </p>
058 */
059public class QueryStack {
060
061        private final Stack<QueryRootEntry> myQueryRootStack = new Stack<>();
062        private final CriteriaBuilder myCriteriaBuilder;
063        private final SearchParameterMap mySearchParameterMap;
064        private final RequestPartitionId myRequestPartitionId;
065        private final String myResourceType;
066
067        /**
068         * Constructor
069         */
070        public QueryStack(CriteriaBuilder theCriteriaBuilder, String theResourceType, SearchParameterMap theSearchParameterMap, RequestPartitionId theRequestPartitionId) {
071                assert theCriteriaBuilder != null;
072                assert isNotBlank(theResourceType);
073                assert theSearchParameterMap != null;
074                assert theRequestPartitionId != null;
075                myCriteriaBuilder = theCriteriaBuilder;
076                mySearchParameterMap = theSearchParameterMap;
077                myRequestPartitionId = theRequestPartitionId;
078                myResourceType = theResourceType;
079        }
080
081        /**
082         * Add a new <code>select RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
083         * will be added to this select clause until {@link #pop()} is called.
084         * <p>
085         * This method must only be called when the stack is empty.
086         * </p>
087         */
088        public void pushResourceTableQuery() {
089                assert myQueryRootStack.isEmpty();
090                myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, false, false, mySearchParameterMap, myResourceType, myRequestPartitionId));
091        }
092
093        /**
094         * Add a new <code>select DISTINCT RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
095         * will be added to this select clause until {@link #pop()} is called.
096         * <p>
097         * This method must only be called when the stack is empty.
098         * </p>
099         */
100        public void pushResourceTableDistinctQuery() {
101                assert myQueryRootStack.isEmpty();
102                myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, true, false, mySearchParameterMap, myResourceType, myRequestPartitionId));
103        }
104
105        /**
106         * Add a new <code>select count(RES_ID) from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
107         * will be added to this select clause until {@link #pop()} is called.
108         * <p>
109         * This method must only be called when the stack is empty.
110         * </p>
111         */
112        public void pushResourceTableCountQuery() {
113                assert myQueryRootStack.isEmpty();
114                myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, false, true, mySearchParameterMap, myResourceType, myRequestPartitionId));
115        }
116
117        /**
118         * Add a new <code>select RES_ID from HFJ_RESOURCE</code> to the stack. All predicates added to the {@literal QueryRootStack}
119         * will be added to this select clause until {@link #pop()} is called.
120         * <p>
121         * This method must only be called when the stack is NOT empty.
122         * </p>
123         */
124        public void pushResourceTableSubQuery(String theResourceType) {
125                assert !myQueryRootStack.isEmpty();
126                myQueryRootStack.push(new QueryRootEntryResourceTable(myCriteriaBuilder, top(), mySearchParameterMap, theResourceType, myRequestPartitionId));
127        }
128
129        /**
130         * Add a new <code>select RES_ID from (....)</code> to the stack, where the specific table being selected on will be
131         * determined based on the first call to {@link #createJoin(SearchBuilderJoinEnum, String)}. All predicates added
132         * to the {@literal QueryRootStack} will be added to this select clause until {@link #pop()} is called.
133         * <p>
134         * This method must only be called when the stack is NOT empty.
135         * </p>
136         */
137        public void pushIndexTableSubQuery() {
138                assert !myQueryRootStack.isEmpty();
139                myQueryRootStack.push(new QueryRootEntryIndexTable(myCriteriaBuilder, top()));
140        }
141
142        /**
143         * This method must be called once all predicates have been added
144         */
145        public AbstractQuery<Long> pop() {
146                QueryRootEntry element = myQueryRootStack.pop();
147                return element.pop();
148        }
149
150        /**
151         * Creates a new SQL join from the current select statement to another table, using the resource PID as the
152         * joining key
153         */
154        public <T> From<?, T> createJoin(SearchBuilderJoinEnum theType, String theSearchParameterName) {
155                return top().createJoin(theType, theSearchParameterName);
156        }
157
158        /**
159         * Returns a join that was previously created by a call to {@link #createJoin(SearchBuilderJoinEnum, String)},
160         * if one exists for the given key.
161         */
162        public Optional<Join<?, ?>> getExistingJoin(SearchBuilderJoinKey theKey) {
163                return top().getIndexJoin(theKey);
164        }
165
166        /**
167         * Gets an attribute (aka a column) from the current select statement.
168         *
169         * @param theAttributeName Must be the name of a java field for the entity/table being selected on
170         */
171        public <Y> Path<Y> get(String theAttributeName) {
172                return top().get(theAttributeName);
173        }
174
175        /**
176         * Adds a predicate to the current select statement
177         */
178        public void addPredicate(Predicate thePredicate) {
179                top().addPredicate(thePredicate);
180        }
181
182        /**
183         * Adds a predicate and marks it as having implicit type selection in it. In other words, call this method if a
184         * this predicate will ensure:
185         * <ul>
186         *    <li>Only Resource PIDs for the correct resource type will be selected</li>
187         *    <li>Only Resource PIDs for non-deleted resources will be selected</li>
188         * </ul>
189         * Setting this flag is a performance optimization, since it avoids the need for us to explicitly
190         * add predicates for the two conditions above.
191         */
192        public void addPredicateWithImplicitTypeSelection(Predicate thePredicate) {
193                setHasImplicitTypeSelection();
194                addPredicate(thePredicate);
195        }
196
197        /**
198         * Adds predicates and marks them as having implicit type selection in it. In other words, call this method if a
199         * this predicate will ensure:
200         * <ul>
201         *    <li>Only Resource PIDs for the correct resource type will be selected</li>
202         *    <li>Only Resource PIDs for non-deleted resources will be selected</li>
203         * </ul>
204         * Setting this flag is a performance optimization, since it avoids the need for us to explicitly
205         * add predicates for the two conditions above.
206         */
207        public void addPredicatesWithImplicitTypeSelection(List<Predicate> thePredicates) {
208                setHasImplicitTypeSelection();
209                addPredicates(thePredicates);
210        }
211
212        /**
213         * Adds predicate(s) to the current select statement
214         */
215        public void addPredicates(List<Predicate> thePredicates) {
216                top().addPredicates(thePredicates);
217        }
218
219        /**
220         * Clear all predicates from the current select statement
221         */
222        public void clearPredicates() {
223                top().clearPredicates();
224        }
225
226        /**
227         * Fetch all the current predicates
228         * <p>
229         * TODO This should really be package protected, but it is called externally in one spot - We need to clean that up
230         * at some point.
231         */
232        public List<Predicate> getPredicates() {
233                return top().getPredicates();
234        }
235
236        private void setHasImplicitTypeSelection() {
237                top().setHasImplicitTypeSelection(true);
238        }
239
240        /**
241         * @see #setHasImplicitTypeSelection()
242         */
243        public void clearHasImplicitTypeSelection() {
244                top().setHasImplicitTypeSelection(false);
245        }
246
247        public boolean isEmpty() {
248                return myQueryRootStack.isEmpty();
249        }
250
251        /**
252         * Add an SQL <code>order by</code> expression
253         */
254        public void orderBy(List<Order> theOrders) {
255                top().orderBy(theOrders);
256        }
257
258        /**
259         * Fetch the column for the current table root that corresponds to the resource's lastUpdated time
260         */
261        public Expression<Date> getLastUpdatedColumn() {
262                return top().getLastUpdatedColumn();
263        }
264
265        /**
266         * Fetch the column for the current table root that corresponds to the resource's PID
267         */
268        public Expression<Long> getResourcePidColumn() {
269                return top().getResourcePidColumn();
270        }
271
272        public Subquery<Long> subqueryForTagNegation() {
273                return top().subqueryForTagNegation();
274        }
275
276        private QueryRootEntry top() {
277                Validate.isTrue(!myQueryRootStack.empty());
278                return myQueryRootStack.peek();
279        }
280
281        /**
282         * TODO This class should avoid leaking the internal query root, but we need to do so for how composite search params are
283         * currently implemented. These only half work in the first place so I'm not going to worry about the fact that
284         * they rely on a leaky abstraction right now.. But when we get around to implementing composites properly,
285         * let's not continue this. JA 2020-05-12
286         */
287        public Root<?> getRootForComposite() {
288                return top().getRoot();
289        }
290
291
292        /**
293         * Add a predicate that will never match any resources
294         */
295        public Predicate addNeverMatchingPredicate() {
296                return top().addNeverMatchingPredicate();
297        }
298
299        public Map<String, From<?, ResourceIndexedSearchParamDate>> getJoinMap() {
300                return top().getJoinMap();
301        }
302
303}