SOLR-10303: Add the tuple context to avoid creating multiple LocalDateTime instances for the same Tuple

This commit is contained in:
Joel Bernstein 2017-04-12 13:18:19 -04:00
parent b78a270c9d
commit 5e403647de
6 changed files with 79 additions and 32 deletions

View File

@ -26,6 +26,7 @@ import java.time.temporal.TemporalAccessor;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import org.apache.solr.client.solrj.io.Tuple;
import org.apache.solr.client.solrj.io.stream.expr.Explanation;
@ -38,6 +39,8 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
*/
public abstract class TemporalEvaluator extends ComplexEvaluator {
private String field;
public TemporalEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
super(expression, factory);
@ -58,21 +61,32 @@ public abstract class TemporalEvaluator extends ComplexEvaluator {
if (tupleValue == null) return null;
if(field == null) {
field = streamEvaluator.toExpression(constructingFactory).toString();
}
Map tupleContext = streamContext.getTupleContext();
date = (LocalDateTime)tupleContext.get(field); // Check to see if the date has already been created for this field
if(date == null) {
if (tupleValue instanceof String) {
instant = getInstant((String) tupleValue);
} else if (tupleValue instanceof Long) {
instant = Instant.ofEpochMilli((Long)tupleValue);
instant = Instant.ofEpochMilli((Long) tupleValue);
} else if (tupleValue instanceof Instant) {
instant = (Instant) tupleValue;
} else if (tupleValue instanceof Date) {
instant = ((Date) tupleValue).toInstant();
} else if (tupleValue instanceof TemporalAccessor) {
date = ((TemporalAccessor) tupleValue);
tupleContext.put(field, date); // Cache the date in the TupleContext
}
}
if (instant != null) {
if (TemporalEvaluatorEpoch.FUNCTION_NAME.equals(getFunction())) return instant.toEpochMilli();
date = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
tupleContext.put(field, date); // Cache the date in the TupleContext
}
if (date != null) {

View File

@ -43,6 +43,7 @@ public class HavingStream extends TupleStream implements Expressible {
private TupleStream stream;
private BooleanEvaluator evaluator;
private StreamContext streamContext;
private transient Tuple currentGroupHead;
@ -128,6 +129,7 @@ public class HavingStream extends TupleStream implements Expressible {
}
public void setStreamContext(StreamContext context) {
this.streamContext = context;
this.stream.setStreamContext(context);
}
@ -152,6 +154,7 @@ public class HavingStream extends TupleStream implements Expressible {
return tuple;
}
streamContext.getTupleContext().clear();
if(evaluator.evaluate(tuple)){
return tuple;
}

View File

@ -49,6 +49,7 @@ public class SelectStream extends TupleStream implements Expressible {
private static final long serialVersionUID = 1;
private TupleStream stream;
private StreamContext streamContext;
private Map<String,String> selectedFields;
private Map<StreamEvaluator,String> selectedEvaluators;
private List<StreamOperation> operations;
@ -213,6 +214,7 @@ public class SelectStream extends TupleStream implements Expressible {
}
public void setStreamContext(StreamContext context) {
this.streamContext = context;
this.stream.setStreamContext(context);
Set<StreamEvaluator> evaluators = selectedEvaluators.keySet();
@ -245,6 +247,14 @@ public class SelectStream extends TupleStream implements Expressible {
// create a copy with the limited set of fields
Tuple workingToReturn = new Tuple(new HashMap<>());
Tuple workingForEvaluators = new Tuple(new HashMap<>());
//Clear the TupleContext before running the evaluators.
//The TupleContext allows evaluators to cache values within the scope of a single tuple.
//For example a LocalDateTime could be parsed by one evaluator and used by other evaluators within the scope of the tuple.
//This avoids the need to create multiple LocalDateTime instances for the same tuple to satisfy a select expression.
streamContext.getTupleContext().clear();
for(Object fieldName : original.fields.keySet()){
workingForEvaluators.put(fieldName, original.get(fieldName));
if(selectedFields.containsKey(fieldName)){

View File

@ -36,6 +36,7 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
public class StreamContext implements Serializable{
private Map entries = new HashMap();
private Map tupleContext = new HashMap();
public int workerID;
public int numWorkers;
private SolrClientCache clientCache;
@ -78,6 +79,10 @@ public class StreamContext implements Serializable{
this.streamFactory = streamFactory;
}
public Map getTupleContext() {
return tupleContext;
}
public StreamFactory getStreamFactory() {
return this.streamFactory;
}

View File

@ -43,6 +43,7 @@ import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorQuarter;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorSecond;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorWeek;
import org.apache.solr.client.solrj.io.eval.TemporalEvaluatorYear;
import org.apache.solr.client.solrj.io.stream.StreamContext;
import org.apache.solr.client.solrj.io.stream.expr.Explanation;
import org.apache.solr.client.solrj.io.stream.expr.StreamExpression;
import org.apache.solr.client.solrj.io.stream.expr.StreamExpressionParser;
@ -50,7 +51,6 @@ import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@ -90,6 +90,8 @@ public class TemporalEvaluatorsTest {
try {
evaluator = factory.constructEvaluator("week()");
StreamContext streamContext = new StreamContext();
evaluator.setStreamContext(streamContext);
assertTrue(false);
} catch (IOException e) {
assertTrue(e.getCause().getCause().getMessage().contains("Invalid expression week()"));
@ -97,6 +99,8 @@ public class TemporalEvaluatorsTest {
try {
evaluator = factory.constructEvaluator("week(a, b)");
StreamContext streamContext = new StreamContext();
evaluator.setStreamContext(streamContext);
assertTrue(false);
} catch (IOException e) {
assertTrue(e.getCause().getCause().getMessage().contains("expecting one value but found 2"));
@ -104,6 +108,8 @@ public class TemporalEvaluatorsTest {
try {
evaluator = factory.constructEvaluator("Week()");
StreamContext streamContext = new StreamContext();
evaluator.setStreamContext(streamContext);
assertTrue(false);
} catch (IOException e) {
assertTrue(e.getMessage().contains("Invalid evaluator expression Week() - function 'Week' is unknown"));
@ -115,9 +121,12 @@ public class TemporalEvaluatorsTest {
public void testInvalidValues() throws Exception {
StreamEvaluator evaluator = factory.constructEvaluator("year(a)");
try {
values.clear();
values.put("a", 12);
StreamContext streamContext = new StreamContext();
evaluator.setStreamContext(streamContext);
Object result = evaluator.evaluate(new Tuple(values));
assertTrue(false);
} catch (IOException e) {
@ -127,6 +136,8 @@ public class TemporalEvaluatorsTest {
try {
values.clear();
values.put("a", "1995-12-31");
StreamContext streamContext = new StreamContext();
evaluator.setStreamContext(streamContext);
Object result = evaluator.evaluate(new Tuple(values));
assertTrue(false);
} catch (IOException e) {
@ -136,6 +147,8 @@ public class TemporalEvaluatorsTest {
try {
values.clear();
values.put("a", "");
StreamContext streamContext = new StreamContext();
evaluator.setStreamContext(streamContext);
Object result = evaluator.evaluate(new Tuple(values));
assertTrue(false);
} catch (IOException e) {
@ -267,6 +280,8 @@ public class TemporalEvaluatorsTest {
public void testFunction(String expression, Object value, Number expected) throws Exception {
StreamEvaluator evaluator = factory.constructEvaluator(expression);
StreamContext streamContext = new StreamContext();
evaluator.setStreamContext(streamContext);
values.clear();
values.put("a", value);
Object result = evaluator.evaluate(new Tuple(values));