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}