rework ResourceIndexedSearchParamDate constructor to support incoming string values

This commit is contained in:
Gary Graham 2020-02-25 14:06:36 -05:00
parent 13cc8b69aa
commit 3ec772b527
8 changed files with 315 additions and 31 deletions

View File

@ -65,6 +65,8 @@ public final class DateUtils {
@SuppressWarnings("WeakerAccess")
public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
private static final String PATTERN_INTEGER_DATE = "yyyyMMdd";
private static final String[] DEFAULT_PATTERNS = new String[]{
PATTERN_RFC1123,
PATTERN_RFC1036,
@ -173,8 +175,11 @@ public final class DateUtils {
public static int convertDatetoDayInteger(final Date theDateValue) {
notNull(theDateValue, "Date value");
Calendar cal = org.apache.commons.lang3.time.DateUtils.toCalendar(theDateValue);
SimpleDateFormat format = new SimpleDateFormat(PATTERN_INTEGER_DATE);
String theDateString = format.format(theDateValue);
String s = String.valueOf(cal.get(Calendar.YEAR)) + cal.get(Calendar.MONTH) + cal.get(Calendar.DAY_OF_MONTH);
return Integer.parseInt(s);
return Integer.parseInt(theDateString);
}
/**

View File

@ -3960,7 +3960,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertEquals(1, results.getResources(0, 10).size());
// We expect a new one because we don't cache the search URL for very long search URLs
assertEquals(2, mySearchEntityDao.count());
}
@Test
@ -4094,6 +4093,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
obs.setId(theId);
obs.setEffective(new DateTimeType(theEffective));
myObservationDao.update(obs);
ourLog.info("Obs {} has time {}", theId, obs.getEffectiveDateTimeType().getValue().toString());
}

View File

@ -0,0 +1,264 @@
package ca.uhn.fhir.jpa.migrate.taskdef;
/*-
* #%L
* HAPI FHIR JPA Server - Migration
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.jpa.migrate.JdbcUtils;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.VersionEnum;
import com.google.common.collect.ForwardingMap;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
public class CalculateOrdinalDateTask extends BaseTableColumnTask<CalculateOrdinalDateTask> {
private static final Logger ourLog = LoggerFactory.getLogger(CalculateOrdinalDateTask.class);
private int myBatchSize = 10000;
private Map<String, Function<MandatoryKeyMap<String, Object>, Long>> myCalculators = new HashMap<>();
private ThreadPoolExecutor myExecutor;
public void setBatchSize(int theBatchSize) {
myBatchSize = theBatchSize;
}
/**
* Constructor
*/
public CalculateOrdinalDateTask(VersionEnum theRelease, String theVersion) {
super(theRelease.toString(), theVersion);
setDescription("Calculate resource search parameter index hashes");
}
@Override
public synchronized void doExecute() throws SQLException {
if (isDryRun()) {
return;
}
Set<String> tableNames = JdbcUtils.getTableNames(getConnectionProperties());
initializeExecutor();
try {
while(true) {
MyRowCallbackHandler rch = new MyRowCallbackHandler();
getTxTemplate().execute(t -> {
JdbcTemplate jdbcTemplate = newJdbcTemplate();
jdbcTemplate.setMaxRows(100000);
String sql = "SELECT * FROM " + getTableName() + " WHERE " + getColumnName() + " IS NULL";
logInfo(ourLog, "Finding up to {} rows in {} that requires hashes", myBatchSize, getTableName());
jdbcTemplate.query(sql, rch);
rch.done();
return null;
});
rch.submitNext();
List<Future<?>> futures = rch.getFutures();
if (futures.isEmpty()) {
break;
}
logInfo(ourLog, "Waiting for {} tasks to complete", futures.size());
for (Future<?> next : futures) {
try {
next.get();
} catch (Exception e) {
throw new SQLException(e);
}
}
}
} finally {
destroyExecutor();
}
}
private void destroyExecutor() {
myExecutor.shutdownNow();
}
private void initializeExecutor() {
int maximumPoolSize = Runtime.getRuntime().availableProcessors();
LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(maximumPoolSize);
BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
.namingPattern("worker-" + "-%d")
.daemon(false)
.priority(Thread.NORM_PRIORITY)
.build();
RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable theRunnable, ThreadPoolExecutor theExecutor) {
logInfo(ourLog, "Note: Executor queue is full ({} elements), waiting for a slot to become available!", executorQueue.size());
StopWatch sw = new StopWatch();
try {
executorQueue.put(theRunnable);
} catch (InterruptedException theE) {
throw new RejectedExecutionException("Task " + theRunnable.toString() +
" rejected from " + theE.toString());
}
logInfo(ourLog, "Slot become available after {}ms", sw.getMillis());
}
};
myExecutor = new ThreadPoolExecutor(
1,
maximumPoolSize,
0L,
TimeUnit.MILLISECONDS,
executorQueue,
threadFactory,
rejectedExecutionHandler);
}
private Future<?> updateRows(List<Map<String, Object>> theRows) {
Runnable task = () -> {
StopWatch sw = new StopWatch();
getTxTemplate().execute(t -> {
// Loop through rows
assert theRows != null;
for (Map<String, Object> nextRow : theRows) {
Map<String, Long> newValues = new HashMap<>();
MandatoryKeyMap<String, Object> nextRowMandatoryKeyMap = new MandatoryKeyMap<>(nextRow);
// Apply calculators
for (Map.Entry<String, Function<MandatoryKeyMap<String, Object>, Long>> nextCalculatorEntry : myCalculators.entrySet()) {
String nextColumn = nextCalculatorEntry.getKey();
Function<MandatoryKeyMap<String, Object>, Long> nextCalculator = nextCalculatorEntry.getValue();
Long value = nextCalculator.apply(nextRowMandatoryKeyMap);
newValues.put(nextColumn, value);
}
// Generate update SQL
StringBuilder sqlBuilder = new StringBuilder();
List<Number> arguments = new ArrayList<>();
sqlBuilder.append("UPDATE ");
sqlBuilder.append(getTableName());
sqlBuilder.append(" SET ");
for (Map.Entry<String, Long> nextNewValueEntry : newValues.entrySet()) {
if (arguments.size() > 0) {
sqlBuilder.append(", ");
}
sqlBuilder.append(nextNewValueEntry.getKey()).append(" = ?");
arguments.add(nextNewValueEntry.getValue());
}
sqlBuilder.append(" WHERE SP_ID = ?");
arguments.add((Number) nextRow.get("SP_ID"));
// Apply update SQL
newJdbcTemplate().update(sqlBuilder.toString(), arguments.toArray());
}
return theRows.size();
});
logInfo(ourLog, "Updated {} rows on {} in {}", theRows.size(), getTableName(), sw.toString());
};
return myExecutor.submit(task);
}
public CalculateOrdinalDateTask addCalculator(String theColumnName, Function<MandatoryKeyMap<String, Object>, Long> theConsumer) {
Validate.isTrue(myCalculators.containsKey(theColumnName) == false);
myCalculators.put(theColumnName, theConsumer);
return this;
}
private class MyRowCallbackHandler implements RowCallbackHandler {
private List<Map<String, Object>> myRows = new ArrayList<>();
private List<Future<?>> myFutures = new ArrayList<>();
@Override
public void processRow(ResultSet rs) throws SQLException {
Map<String, Object> row = new ColumnMapRowMapper().mapRow(rs, 0);
myRows.add(row);
if (myRows.size() >= myBatchSize) {
submitNext();
}
}
private void submitNext() {
if (myRows.size() > 0) {
myFutures.add(updateRows(myRows));
myRows = new ArrayList<>();
}
}
public List<Future<?>> getFutures() {
return myFutures;
}
public void done() {
if (myRows.size() > 0) {
submitNext();
}
}
}
public static class MandatoryKeyMap<K, V> extends ForwardingMap<K, V> {
private final Map<K, V> myWrap;
public MandatoryKeyMap(Map<K, V> theWrap) {
myWrap = theWrap;
}
@Override
public V get(Object theKey) {
if (!containsKey(theKey)) {
throw new IllegalArgumentException("No key: " + theKey);
}
return super.get(theKey);
}
public String getString(String theKey) {
return (String) get(theKey);
}
@Override
protected Map<K, V> delegate() {
return myWrap;
}
public String getResourceType() {
return getString("RES_TYPE");
}
public String getParamName() {
return getString("SP_NAME");
}
}
}

View File

@ -71,8 +71,14 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
//
// Add support for integer comparisons during day-granularity date search.
version.onTable("HFJ_SPIDX_DATE").addColumn("20200225.1", "SP_VALUE_LOW_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
version.onTable("HFJ_SPIDX_DATE").addColumn("20200225.1", "SP_VALUE_HIGH_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
Builder.BuilderWithTableName spidxDate = version.onTable("HFJ_SPIDX_DATE");
spidxDate.addColumn("20200225.1", "SP_VALUE_LOW_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
spidxDate.addColumn("20200225.2", "SP_VALUE_HIGH_DATE_ORDINAL").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
spidxDate.addTask(new CalculateHashesTask(VersionEnum.V4_3_0, "20200225.3")
.setColumnName("HASH_IDENTITY")
.addCalculator("SP_VALUE_LOW_DATE_ORDINAL", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")))
.addCalculator("SP_VALUE_HIGH_DATE_ORDINAL", t -> BaseResourceIndexedSearchParam.calculateHashIdentity(t.getResourceType(), t.getString("SP_NAME")))
);
//
}

View File

@ -88,24 +88,26 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
/**
* Constructor
*/
public ResourceIndexedSearchParamDate(String theResourceType, String theParamName, Date theLow, Date theHigh, String theOriginalValue) {
public ResourceIndexedSearchParamDate(String theResourceType, String theParamName, Date theLow, String theLowString, Date theHigh, String theHighString, String theOriginalValue) {
setResourceType(theResourceType);
setParamName(theParamName);
setValueLow(theLow);
setValueHigh(theHigh);
computeValueHighDateOrdinal(theHigh);
computeValueLowDateOrdinal(theLow);
computeValueHighDateOrdinal(theHighString);
computeValueLowDateOrdinal(theLowString);
myOriginalValue = theOriginalValue;
}
private void computeValueHighDateOrdinal(Date theHigh) {
private void computeValueHighDateOrdinal(String theHigh) {
this.myValueHighDateOrdinal = generateOrdinalDateInteger(theHigh);
}
private int generateOrdinalDateInteger(Date theDate) {
return ca.uhn.fhir.util.DateUtils.convertDatetoDayInteger(theDate);
private int generateOrdinalDateInteger(String theDateString){
String t = theDateString.substring(0, theDateString.indexOf("T"));
t = t.replace("-", "");
return Integer.valueOf(t);
}
private void computeValueLowDateOrdinal(Date theLow) {
private void computeValueLowDateOrdinal(String theLow) {
this.myValueLowDateOrdinal = generateOrdinalDateInteger(theLow);
}

View File

@ -35,8 +35,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsTrueForMatchingNullDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, null, null, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, null, null, "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@ -45,8 +45,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsTrueForMatchingDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1B, date2B, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1B, date1B.toString(), date2B, date2B.toString(), "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@ -55,8 +55,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsTrueForMatchingTimeStampsThatMatch() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1B, timestamp2B, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp1A.toString(), timestamp2A, timestamp2A.toString(), "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1B, timestamp1B.toString(), timestamp2B, timestamp2B.toString(), "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@ -67,8 +67,8 @@ public class ResourceIndexedSearchParamDateTest {
// other will be equivalent but will be a java.sql.Timestamp. Equals should work in both directions.
@Test
public void equalsIsTrueForMixedTimestampsAndDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp1A.toString(), timestamp2A, timestamp2A.toString(), "SomeValue");
assertTrue(param.equals(param2));
assertTrue(param2.equals(param));
@ -77,8 +77,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingDates() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date2A, date1A, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date2A, date2A.toString(), date1A, date1A.toString(), "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
@ -87,8 +87,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingDatesNullCase() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", null, null, null, null, "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
@ -97,8 +97,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForNonMatchingTimeStamps() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, timestamp1A, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp1A, timestamp1A.toString(), timestamp2A, timestamp2A.toString(), "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, timestamp2A.toString(), timestamp1A, timestamp1A.toString(), "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));
@ -107,8 +107,8 @@ public class ResourceIndexedSearchParamDateTest {
@Test
public void equalsIsFalseForMixedTimestampsAndDatesThatDoNotMatch() {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date2A, "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, timestamp1A, "SomeValue");
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate("Patient", "SomeResource", date1A, date1A.toString(), date2A, date2A.toString(), "SomeValue");
ResourceIndexedSearchParamDate param2 = new ResourceIndexedSearchParamDate("Patient", "SomeResource", timestamp2A, timestamp2A.toString(), timestamp1A, timestamp1A.toString(), "SomeValue");
assertFalse(param.equals(param2));
assertFalse(param2.equals(param));

View File

@ -605,9 +605,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
Date start = extractValueAsDate(myPeriodStartValueChild, theValue);
String startAsString = extractValueAsString(myPeriodStartValueChild, theValue);
Date end = extractValueAsDate(myPeriodEndValueChild, theValue);
String endAsString = extractValueAsString(myPeriodEndValueChild, theValue);
if (start != null || end != null) {
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), start, end, startAsString);
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), start, startAsString, end, endAsString, startAsString);
theParams.add(nextEntity);
}
}
@ -616,13 +617,17 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
List<IPrimitiveType<Date>> values = extractValuesAsFhirDates(myTimingEventValueChild, theValue);
TreeSet<Date> dates = new TreeSet<>();
TreeSet<String> dateStrings = new TreeSet<>();
String firstValue = null;
String finalValue = null;
for (IPrimitiveType<Date> nextEvent : values) {
if (nextEvent.getValue() != null) {
dates.add(nextEvent.getValue());
if (firstValue == null) {
firstValue = nextEvent.getValueAsString();
}
finalValue = nextEvent.getValueAsString();
}
}
@ -634,14 +639,16 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
if ("Period".equals(boundsType)) {
Date start = extractValueAsDate(myPeriodStartValueChild, bounds.get());
Date end = extractValueAsDate(myPeriodEndValueChild, bounds.get());
String endString = extractValueAsString(myPeriodEndValueChild, bounds.get());
dates.add(start);
dates.add(end);
finalValue = endString;
}
}
}
if (!dates.isEmpty()) {
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), dates.first(), dates.last(), firstValue);
ResourceIndexedSearchParamDate nextEntity = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), dates.first(), firstValue, dates.last(), finalValue, firstValue);
theParams.add(nextEntity);
}
}
@ -828,7 +835,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private void addDateTimeTypes(String theResourceType, Set<ResourceIndexedSearchParamDate> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
IPrimitiveType<Date> nextBaseDateTime = (IPrimitiveType<Date>) theValue;
if (nextBaseDateTime.getValue() != null) {
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString());
ResourceIndexedSearchParamDate param = new ResourceIndexedSearchParamDate(theResourceType, theSearchParam.getName(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValue(), nextBaseDateTime.getValueAsString(), nextBaseDateTime.getValueAsString());
theParams.add(param);
}
}

View File

@ -208,7 +208,7 @@ public class InMemoryResourceMatcherR5Test {
private ResourceIndexedSearchParams extractDateSearchParam(Observation theObservation) {
ResourceIndexedSearchParams retval = new ResourceIndexedSearchParams();
BaseDateTimeType dateValue = (BaseDateTimeType) theObservation.getEffective();
ResourceIndexedSearchParamDate dateParam = new ResourceIndexedSearchParamDate("Patient", "date", dateValue.getValue(), dateValue.getValue(), dateValue.getValueAsString());
ResourceIndexedSearchParamDate dateParam = new ResourceIndexedSearchParamDate("Patient", "date", dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValue(), dateValue.getValueAsString(), dateValue.getValueAsString());
retval.myDateParams.add(dateParam);
return retval;
}