Skip to content

Commit b8b3851

Browse files
Merge pull request #308 from IBM/Albert-Master-New
issue #262 modify SQLQueryBuilder to improve performance.
2 parents 8b07ca8 + 4c3614e commit b8b3851

5 files changed

Lines changed: 169 additions & 116 deletions

File tree

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/api/ResourceDAO.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ List<Resource> history(String resourceType, String logicalId, Timestamp fromDate
7575
*/
7676
int historyCount(String resourceType, String logicalId, Timestamp fromDateTime)
7777
throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException;
78-
78+
7979
/**
8080
* Executes the search contained in the passed SqlQueryData, using it's encapsulated search string and bind variables.
8181
* @param queryData - Contains a search string and (optionally) bind variables.
@@ -84,6 +84,19 @@ int historyCount(String resourceType, String logicalId, Timestamp fromDateTime)
8484
* @throws FHIRPersistenceDBConnectException
8585
*/
8686
List<Resource> search(SqlQueryData queryData) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException;
87+
88+
/**
89+
* Executes the search contained in the passed SqlQueryData, using it's encapsulated search string and bind variables.
90+
* @param queryData - Contains a search string and (optionally) bind variables.
91+
* @return List<String> A list of strings satisfying the passed search.
92+
* @throws FHIRPersistenceDataAccessException
93+
* @throws FHIRPersistenceDBConnectException
94+
* @implNote This method is used within searches which have _include or _revinclude parameters
95+
* in order to return a list of Reference values (e.g. {@code "Patient/<UUID>"})
96+
* to use for filtering the list of resources to be included with the response.
97+
*/
98+
List<String> searchStringValues(SqlQueryData queryData) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException;
99+
87100

88101
/**
89102
* Executes the passed fully-formed SQL Select statement and returns the results

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/FHIRDbDAOImpl.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,4 +525,59 @@ protected FHIRPersistenceDataAccessException buildExceptionWithIssue(String msg,
525525
Issue ooi = FHIRUtil.buildOperationOutcomeIssue(msg, issueType);
526526
return new FHIRPersistenceDataAccessException(msg).withIssue(ooi);
527527
}
528+
529+
/**
530+
* Creates and executes a PreparedStatement using the passed parameters that returns a collection of String values.
531+
* @param sql - The SQL template to execute.
532+
* @param searchArgs - An array of arguments to be substituted into the SQL template.
533+
* @return List<String> - A List of strings resulting from the executed query.
534+
* @throws FHIRPersistenceDataAccessException
535+
* @throws FHIRPersistenceDBConnectException
536+
*/
537+
protected List<String> runQuery_STR_VALUES(String sql, Object... searchArgs) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException {
538+
final String METHODNAME = "runQuery_STR_VALUES";
539+
log.entering(CLASSNAME, METHODNAME);
540+
List<String> strValues = new ArrayList<String>();
541+
Connection connection = null;
542+
PreparedStatement stmt = null;
543+
ResultSet resultSet = null;
544+
String errMsg;
545+
long dbCallStartTime;
546+
double dbCallDuration;
547+
548+
try {
549+
connection = this.getConnection();
550+
stmt = connection.prepareStatement(sql);
551+
// Inject arguments into the prepared stmt.
552+
for (int i = 0; i <searchArgs.length; i++) {
553+
stmt.setObject(i+1, searchArgs[i]);
554+
}
555+
dbCallStartTime = System.nanoTime();
556+
resultSet = stmt.executeQuery();
557+
dbCallDuration = (System.nanoTime()-dbCallStartTime)/1e6;
558+
559+
while(resultSet.next()) {
560+
strValues.add(resultSet.getString(1));
561+
}
562+
563+
if (log.isLoggable(Level.FINE)) {
564+
log.fine("Successfully retrieved string values. SQL=" + sql + " searchArgs=" + Arrays.toString(searchArgs) +
565+
" executionTime=" + dbCallDuration + "ms");
566+
}
567+
}
568+
catch(FHIRPersistenceException e) {
569+
throw e;
570+
}
571+
catch (Throwable e) {
572+
// avoid leaking SQL because the exception message might be returned to a client
573+
FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure retrieving string values");
574+
errMsg = "Failure retrieving string values. SQL=" + sql + " searchArgs=" + Arrays.toString(searchArgs);
575+
throw severe(log, fx, errMsg, e);
576+
}
577+
finally {
578+
this.cleanup(resultSet, stmt, connection);
579+
log.exiting(CLASSNAME, METHODNAME);
580+
}
581+
return strValues;
582+
}
528583
}

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,4 +872,19 @@ public int searchCount(String sqlSelectCount) throws FHIRPersistenceDataAccessEx
872872
return count;
873873
}
874874

875+
@Override
876+
public List<String> searchStringValues(SqlQueryData queryData)
877+
throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException {
878+
final String METHODNAME = "searchSTR_VALUES";
879+
log.entering(CLASSNAME, METHODNAME);
880+
881+
String sqlSelect = queryData.getQueryString();
882+
Object[] bindVariables = queryData.getBindVariables().toArray();
883+
try {
884+
return this.runQuery_STR_VALUES(sqlSelect, bindVariables);
885+
}
886+
finally {
887+
log.exiting(CLASSNAME, METHODNAME);
888+
}
889+
}
875890
}

fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/InclusionQuerySegmentAggregator.java

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,52 @@ protected SqlQueryData buildQuery() throws Exception {
183183
return queryData;
184184
}
185185

186+
/**
187+
* Appends values like ({@code ('Patient/<resource_id>', 'Patient/<resource_id>' ...)}) to the queryString
188+
*/
189+
private void executeIncludeSubQuery(StringBuilder queryString, InclusionParameter includeParm,
190+
List<Object> bindVariables) throws Exception{
191+
StringBuilder subQueryString = new StringBuilder();
192+
// SELECT P1.STR_VALUE FROM OBSERVATION_STR_VALUES P1 WHERE
193+
subQueryString.append("SELECT P1.STR_VALUE FROM ").append(this.resourceType.getSimpleName()).append("_STR_VALUES P1 WHERE ");
194+
// P1.PARAMETER_NAME_ID=xx AND
195+
subQueryString.append("P1.PARAMETER_NAME_ID=").append(this.getParameterNameId(includeParm.getSearchParameter())).append(" AND ");
196+
// P1.RESOURCE_ID IN
197+
subQueryString.append("P1.LOGICAL_RESOURCE_ID IN ");
198+
// (SELECT R.LOGICAL_RESOURCE_ID
199+
subQueryString.append("(SELECT R.LOGICAL_RESOURCE_ID ");
200+
// Add FROM clause for "root" resource type
201+
subQueryString.append(super.buildFromClause());
202+
// Add WHERE clause for "root" resource type
203+
subQueryString.append(super.buildWhereClause());
204+
subQueryString.append(")");
205+
206+
queryString.append("(");
207+
//The subquery should return a list of strings in the FHIR Reference String value format
208+
//(e.g. {@code "Patient/<resource_id>"})
209+
SqlQueryData subQueryData = new SqlQueryData(subQueryString.toString(), bindVariables);
210+
boolean isFirstItem = true;
211+
for (String strValue: this.resourceDao.searchStringValues(subQueryData)) {
212+
if (!isFirstItem) {
213+
queryString.append(" , ");
214+
}
215+
if (strValue != null) {
216+
queryString.append("'").append(strValue).append("'");
217+
isFirstItem = false;
218+
}
219+
}
220+
// if nothing added so far, then need to add '', otherwise sql will fail.
221+
if (isFirstItem) {
222+
queryString.append("''");
223+
}
224+
queryString.append(")");
225+
}
226+
227+
186228
private void processIncludeParameters(StringBuilder queryString, List<Object> bindVariables) throws Exception {
187229
final String METHODNAME = "processIncludeParameters";
188230
log.entering(CLASSNAME, METHODNAME);
189-
231+
190232
for (InclusionParameter includeParm : this.includeParameters) {
191233
// UNION ALL
192234
queryString.append(UNION_ALL);
@@ -199,23 +241,12 @@ private void processIncludeParameters(StringBuilder queryString, List<Object> bi
199241
// R.RESOURCE_ID = LR.CURRENT_RESOURCE_ID AND
200242
queryString.append("R.RESOURCE_ID = LR.CURRENT_RESOURCE_ID AND ");
201243
// ('Organization/' || LR.LOGICAL_ID IN
202-
queryString.append("('").append(includeParm.getSearchParameterTargetType()).append("/' || LR.LOGICAL_ID IN ");
203-
// (SELECT P1.STR_VALUE FROM OBSERVATION_STR_VALUES P1 WHERE
204-
queryString.append("(SELECT P1.STR_VALUE FROM ").append(this.resourceType.getSimpleName()).append("_STR_VALUES P1 WHERE ");
205-
// P1.PARAMETER_NAME_ID=xx AND
206-
queryString.append("P1.PARAMETER_NAME_ID=").append(this.getParameterNameId(includeParm.getSearchParameter())).append(" AND ");
207-
// P1.RESOURCE_ID IN
208-
queryString.append("P1.LOGICAL_RESOURCE_ID IN ");
209-
// (SELECT R.LOGICAL_RESOURCE_ID
210-
queryString.append("(SELECT R.LOGICAL_RESOURCE_ID ");
211-
// Add FROM clause for "root" resource type
212-
queryString.append(super.buildFromClause());
213-
// Add WHERE clause for "root" resource type
214-
queryString.append(super.buildWhereClause());
215-
216-
queryString.append(")))");
217-
218-
this.addBindVariables(bindVariables);
244+
queryString.append("('").append(includeParm.getSearchParameterTargetType()).append("/' || LR.LOGICAL_ID IN ");
245+
246+
// Execute sub query to get the string values for constructing the query string.
247+
// This avoids DB engine to run this sub query once for each record in the previously joined tables.
248+
executeIncludeSubQuery(queryString, includeParm, bindVariables);
249+
queryString.append(")");
219250
}
220251
log.exiting(CLASSNAME, METHODNAME);
221252
}

0 commit comments

Comments
 (0)