Skip to content

Commit

Permalink
issue #3437 remote index support for profile, tags and security
Browse files Browse the repository at this point in the history
Signed-off-by: Robin Arnold <robin.arnold@ibm.com>
  • Loading branch information
punktilious committed May 12, 2022
1 parent 0752bbb commit 6343ab9
Show file tree
Hide file tree
Showing 49 changed files with 2,022 additions and 225 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* (C) Copyright IBM Corp. 2022
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.database.utils.common;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;

/**
* Collection of utility functions to simply setting values on a PreparedStatement
*/
public class PreparedStatementHelper {
// The PreparedStatement we delegate everything to
private final Calendar UTC = CalendarHelper.getCalendarForUTC();
private final PreparedStatement ps;
private int index = 1;

/**
* Public constructor
* @param ps
*/
public PreparedStatementHelper(PreparedStatement ps) {
this.ps = ps;
}

/**
* Set the (possibly null) int value at the current position
* and increment the position by 1
* @param value
* @return this instance
* @throws SQLException
*/
public PreparedStatementHelper setInt(Integer value) throws SQLException {
if (value != null) {
ps.setInt(index, value);
} else {
ps.setNull(index, Types.INTEGER);
}
index++;
return this;
}

/**
* Set the (possibly null) long value at the current position
* and increment the position by 1
* @param value
* @return this instance
* @throws SQLException
*/
public PreparedStatementHelper setLong(Long value) throws SQLException {
if (value != null) {
ps.setLong(index, value);
} else {
ps.setNull(index, Types.BIGINT);
}
index++;
return this;
}

/**
* Set the (possibly null) long value at the current position
* and increment the position by 1
* @param value
* @return this instance
* @throws SQLException
*/
public PreparedStatementHelper setShort(Short value) throws SQLException {
if (value != null) {
ps.setShort(index, value);
} else {
ps.setNull(index, Types.SMALLINT);
}
index++;
return this;
}

/**
* Set the (possibly null) String value at the current position
* and increment the position by 1
* @param value
* @return this instance
* @throws SQLException
*/
public PreparedStatementHelper setString(String value) throws SQLException {
if (value != null) {
ps.setString(index, value);
} else {
ps.setNull(index, Types.VARCHAR);
}
index++;
return this;
}

/**
* Set the (possibly null) int value at the current position
* and increment the position by 1
* @param value
* @return this instance
* @throws SQLException
*/
public PreparedStatementHelper setTimestamp(Timestamp value) throws SQLException {
if (value != null) {
ps.setTimestamp(index, value, UTC);
} else {
ps.setNull(index, Types.TIMESTAMP);
}
index++;
return this;
}

/**
* Add a new batch entry based on the current state of the {@link PreparedStatement}.
* Note that we don't return this on purpose...because addBatch should be last in
* any sequence of setXX(...) calls.
* @throws SQLException
*/
public void addBatch() throws SQLException {
ps.addBatch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

package com.ibm.fhir.persistence.jdbc.dao.impl;

import java.util.logging.Logger;

import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
import com.ibm.fhir.persistence.index.CanonicalSupport;
import com.ibm.fhir.persistence.index.ParameterValueVisitorAdapter;
import com.ibm.fhir.persistence.index.ProfileParameter;
import com.ibm.fhir.persistence.jdbc.JDBCConstants;
import com.ibm.fhir.persistence.jdbc.dto.CompositeParmVal;
import com.ibm.fhir.persistence.jdbc.dto.DateParmVal;
import com.ibm.fhir.persistence.jdbc.dto.ExtractedParameterValue;
Expand All @@ -18,13 +23,20 @@
import com.ibm.fhir.persistence.jdbc.dto.ReferenceParmVal;
import com.ibm.fhir.persistence.jdbc.dto.StringParmVal;
import com.ibm.fhir.persistence.jdbc.dto.TokenParmVal;
import com.ibm.fhir.search.SearchConstants;
import com.ibm.fhir.search.util.ReferenceValue;
import com.ibm.fhir.search.util.ReferenceValue.ReferenceType;


/**
* A visitor to map parameters to a format suitable for transport to another
* system (e.g. for remote indexing)
*/
public class ParameterTransportVisitor implements ExtractedParameterValueVisitor {
private static final Logger logger = Logger.getLogger(ParameterTransportVisitor.class.getName());
private static final Boolean IS_WHOLE_SYSTEM = Boolean.TRUE;

// The adapter to which we delegate each of our visit calls
private final ParameterValueVisitorAdapter adapter;

// tracks the number of composites so we know what next composite_id to use
Expand All @@ -43,8 +55,15 @@ public ParameterTransportVisitor(ParameterValueVisitorAdapter adapter) {

@Override
public void visit(StringParmVal stringParameter) throws FHIRPersistenceException {
Integer compositeId = this.currentCompositeParameterName != null ? this.compositeIdCounter : null;
adapter.stringValue(stringParameter.getName(), stringParameter.getValueString(), compositeId);

if (SearchConstants.PROFILE.equals(stringParameter.getName())) {
// special case to store profile parameters in their own table
ProfileParameter pp = CanonicalSupport.createProfileParameter(stringParameter.getName(), stringParameter.getValueString());
adapter.profileValue(pp.getName(), pp.getUrl(), pp.getVersion(), pp.getFragment(), IS_WHOLE_SYSTEM);
} else {
Integer compositeId = this.currentCompositeParameterName != null ? this.compositeIdCounter : null;
adapter.stringValue(stringParameter.getName(), stringParameter.getValueString(), compositeId, stringParameter.isWholeSystem());
}
}

@Override
Expand All @@ -60,13 +79,25 @@ public void visit(NumberParmVal numberParameter) throws FHIRPersistenceException
@Override
public void visit(DateParmVal dateParameter) throws FHIRPersistenceException {
Integer compositeId = this.currentCompositeParameterName != null ? this.compositeIdCounter : null;
adapter.dateValue(dateParameter.getName(), dateParameter.getValueDateStart(), dateParameter.getValueDateEnd(), compositeId);
adapter.dateValue(dateParameter.getName(), dateParameter.getValueDateStart(), dateParameter.getValueDateEnd(), compositeId, dateParameter.isWholeSystem());
}

@Override
public void visit(TokenParmVal tokenParameter) throws FHIRPersistenceException {
Integer compositeId = this.currentCompositeParameterName != null ? this.compositeIdCounter : null;
adapter.tokenValue(tokenParameter.getName(), tokenParameter.getValueSystem(), tokenParameter.getValueCode(), compositeId);
// tag and profile search params are often low-selectivity (many resources sharing the same value) so
// we put them into their own tables to allow better cardinality estimation by the query
// optimizer
switch (tokenParameter.getName()) {
case SearchConstants.TAG:
adapter.tagValue(tokenParameter.getName(), tokenParameter.getValueSystem(), tokenParameter.getValueCode(), IS_WHOLE_SYSTEM);
break;
case SearchConstants.SECURITY:
adapter.securityValue(tokenParameter.getName(), tokenParameter.getValueSystem(), tokenParameter.getValueCode(), IS_WHOLE_SYSTEM);
break;
default:
adapter.tokenValue(tokenParameter.getName(), tokenParameter.getValueSystem(), tokenParameter.getValueCode(), compositeId);
}
}

@Override
Expand All @@ -84,9 +115,26 @@ public void visit(LocationParmVal locationParameter) throws FHIRPersistenceExcep
}

@Override
public void visit(ReferenceParmVal ref) throws FHIRPersistenceException {
public void visit(ReferenceParmVal rpv) throws FHIRPersistenceException {
Integer compositeId = this.currentCompositeParameterName != null ? this.compositeIdCounter : null;
adapter.referenceValue(ref.getName(), ref.getRefValue(), compositeId);

// The ReferenceValue has already been processed to convert the reference to
// the required standard form, ready for insertion as a token value.
ReferenceValue refValue = rpv.getRefValue();
String resourceType = rpv.getResourceType();
String refResourceType = refValue.getTargetResourceType();
String refLogicalId = refValue.getValue();
Integer refVersion = refValue.getVersion();
if (refValue.getType() == ReferenceType.DISPLAY_ONLY || refValue.getType() == ReferenceType.INVALID) {
// protect against code regression. Invalid/improper references should be
// filtered out already.
logger.warning("Invalid reference parameter type: '" + resourceType + "." + rpv.getName() + "' type=" + refValue.getType().name());
throw new IllegalArgumentException("Invalid reference parameter value. See server log for details.");
}
if (refResourceType == null) {
refResourceType = JDBCConstants.DEFAULT_TOKEN_SYSTEM;
}
adapter.referenceValue(rpv.getName(), refResourceType, refLogicalId, refVersion, compositeId);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ public <T extends Resource> SingleResourceResult<T> create(FHIRPersistenceContex
+ ", version=" + resourceDTO.getVersionId());
}

sendParametersToRemoteIndexService(resourceDTO.getResourceType(), resourceDTO.getLogicalId(), resourceDTO.getId(), context.getShardKey(), searchParameters);
sendParametersToRemoteIndexService(resourceDTO.getResourceType(), resourceDTO.getLogicalId(), resourceDTO.getId(), context.getRequestShard(), searchParameters);
SingleResourceResult.Builder<T> resultBuilder = new SingleResourceResult.Builder<T>()
.success(true)
.interactionStatus(resourceDTO.getInteractionStatus())
Expand Down Expand Up @@ -464,12 +464,12 @@ public <T extends Resource> SingleResourceResult<T> create(FHIRPersistenceContex
* @param shardKey
* @param searchParameters
*/
private void sendParametersToRemoteIndexService(String resourceType, String logicalId, long logicalResourceId, Short shardKey,
private void sendParametersToRemoteIndexService(String resourceType, String logicalId, long logicalResourceId, String requestShard,
ExtractedSearchParameters searchParameters) throws FHIRPersistenceException {
FHIRRemoteIndexService remoteIndexService = FHIRRemoteIndexService.getServiceInstance();
if (remoteIndexService != null) {
// convert the parameters into a form that will be easy to ship to a remote service
SearchParametersTransportAdapter adapter = new SearchParametersTransportAdapter(resourceType, logicalId, logicalResourceId, shardKey);
SearchParametersTransportAdapter adapter = new SearchParametersTransportAdapter(resourceType, logicalId, logicalResourceId, requestShard);
ParameterTransportVisitor visitor = new ParameterTransportVisitor(adapter);
for (ExtractedParameterValue pv: searchParameters.getParameters()) {
pv.accept(visitor);
Expand Down Expand Up @@ -547,6 +547,22 @@ private com.ibm.fhir.persistence.jdbc.dto.Resource createResourceDTO(Class<? ext
return resourceDTO;
}

/**
* To minimize the impact of using an additional column for explicit sharding,
* we encode the requestShard into a short value which is stored in the database.
* This provides sufficient spread across nodes, but minimizes the amount of extra
* space required.
* @param requestShard
* @return
*/
private Short encodeRequestShard(String requestShard) {
if (requestShard != null) {
return Short.valueOf((short)requestShard.hashCode());
} else {
return null;
}
}

/**
* Convenience method to construct a new instance of the {@link ResourceDAO}
* @param persistenceContext the persistence context for the current request
Expand All @@ -563,7 +579,7 @@ private ResourceDAO makeResourceDAO(FHIRPersistenceContext persistenceContext, C
if (persistenceContext == null) {
throw new FHIRPersistenceException("persistenceContext is always required for DISTRIBUTED schemas");
}
shardKey = persistenceContext.getShardKey();
shardKey = encodeRequestShard(persistenceContext.getRequestShard());
if (shardKey == null) {
throw new FHIRPersistenceException("shardKey value is required for DISTRIBUTED schemas");
}
Expand Down Expand Up @@ -656,7 +672,7 @@ public <T extends Resource> SingleResourceResult<T> update(FHIRPersistenceContex
}

// If configured, send the extracted parameters to the remote indexing service
sendParametersToRemoteIndexService(resourceDTO.getResourceType(), resourceDTO.getLogicalId(), resourceDTO.getId(), context.getShardKey(), searchParameters);
sendParametersToRemoteIndexService(resourceDTO.getResourceType(), resourceDTO.getLogicalId(), resourceDTO.getId(), context.getRequestShard(), searchParameters);

SingleResourceResult.Builder<T> resultBuilder = new SingleResourceResult.Builder<T>()
.success(true)
Expand Down
Loading

0 comments on commit 6343ab9

Please sign in to comment.