mirror of https://github.com/apache/lucene.git
SOLR-10303: Initial support for common date/time Stream Evaluators
This commit is contained in:
parent
b767d61b9e
commit
24ab117a41
|
@ -43,6 +43,7 @@ import org.apache.solr.client.solrj.io.eval.CeilingEvaluator;
|
|||
import org.apache.solr.client.solrj.io.eval.CoalesceEvaluator;
|
||||
import org.apache.solr.client.solrj.io.eval.CosineEvaluator;
|
||||
import org.apache.solr.client.solrj.io.eval.CubedRootEvaluator;
|
||||
import org.apache.solr.client.solrj.io.eval.DateEvaluator;
|
||||
import org.apache.solr.client.solrj.io.eval.DivideEvaluator;
|
||||
import org.apache.solr.client.solrj.io.eval.EqualsEvaluator;
|
||||
import org.apache.solr.client.solrj.io.eval.ExclusiveOrEvaluator;
|
||||
|
@ -248,12 +249,17 @@ public class StreamHandler extends RequestHandlerBase implements SolrCoreAware,
|
|||
.withFunctionName("cbrt", CubedRootEvaluator.class)
|
||||
.withFunctionName("coalesce", CoalesceEvaluator.class)
|
||||
.withFunctionName("uuid", UuidEvaluator.class)
|
||||
|
||||
|
||||
// Conditional Stream Evaluators
|
||||
.withFunctionName("if", IfThenElseEvaluator.class)
|
||||
.withFunctionName("analyze", AnalyzeEvaluator.class)
|
||||
;
|
||||
|
||||
// Date evaluators
|
||||
for (DateEvaluator.FUNCTION function:DateEvaluator.FUNCTION.values()) {
|
||||
streamFactory.withFunctionName(function.toString(), DateEvaluator.class);
|
||||
}
|
||||
|
||||
// This pulls all the overrides and additions from the config
|
||||
List<PluginInfo> pluginInfos = core.getSolrConfig().getPluginInfos(Expressible.class.getName());
|
||||
for (PluginInfo pluginInfo : pluginInfos) {
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.
|
||||
*/
|
||||
|
||||
package org.apache.solr.client.solrj.io.eval;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.IsoFields;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.solr.client.solrj.io.Tuple;
|
||||
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.StreamExpressionParameter;
|
||||
import org.apache.solr.client.solrj.io.stream.expr.StreamFactory;
|
||||
|
||||
/**
|
||||
* Provides numeric Date/Time stream evaluators
|
||||
*/
|
||||
public class DateEvaluator extends NumberEvaluator {
|
||||
|
||||
public enum FUNCTION {year, month, day, dayofyear, dayofquarter, hour, minute, quarter, week, second, epoch};
|
||||
|
||||
private FUNCTION function;
|
||||
private String fieldName;
|
||||
|
||||
public DateEvaluator(StreamExpression expression, StreamFactory factory) throws IOException {
|
||||
super(expression, factory);
|
||||
|
||||
String functionName = expression.getFunctionName();
|
||||
|
||||
try {
|
||||
this.function = FUNCTION.valueOf(functionName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException(String.format(Locale.ROOT,"Invalid date expression %s - expecting one of %s",functionName, Arrays.toString(FUNCTION.values())));
|
||||
}
|
||||
|
||||
fieldName = factory.getValueOperand(expression, 0);
|
||||
|
||||
//Taken from Field evaluator
|
||||
if(fieldName != null && fieldName.startsWith("'") && fieldName.endsWith("'") && fieldName.length() > 1){
|
||||
fieldName = fieldName.substring(1, fieldName.length() - 1);
|
||||
}
|
||||
|
||||
if(1 != subEvaluators.size()){
|
||||
throw new IOException(String.format(Locale.ROOT,"Invalid expression %s - expecting one value but found %d",expression,subEvaluators.size()));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Support non-string, eg. java.util.date or instant
|
||||
|
||||
@Override
|
||||
public Number evaluate(Tuple tuple) throws IOException {
|
||||
|
||||
try {
|
||||
String dateStr = (String) tuple.get(fieldName);
|
||||
if (dateStr != null && !dateStr.isEmpty()) {
|
||||
Instant instant = Instant.parse(dateStr);
|
||||
if (function.equals(FUNCTION.epoch)) return instant.toEpochMilli();
|
||||
|
||||
LocalDateTime date = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
|
||||
return evaluate(date);
|
||||
}
|
||||
} catch (ClassCastException | DateTimeParseException e) {
|
||||
throw new IOException(String.format(Locale.ROOT,"Invalid field %s - The field must be a string formatted in the ISO_INSTANT date format.",fieldName));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the date based on the specified function
|
||||
* @param date
|
||||
* @return the evaluated value
|
||||
*/
|
||||
private Number evaluate(LocalDateTime date) {
|
||||
switch (function) {
|
||||
case year:
|
||||
return date.getYear();
|
||||
case month:
|
||||
return date.getMonthValue();
|
||||
case day:
|
||||
return date.getDayOfMonth();
|
||||
case dayofyear:
|
||||
return date.getDayOfYear();
|
||||
case hour:
|
||||
return date.getHour();
|
||||
case minute:
|
||||
return date.getMinute();
|
||||
case second:
|
||||
return date.getSecond();
|
||||
case dayofquarter:
|
||||
return date.get(IsoFields.DAY_OF_QUARTER);
|
||||
case quarter:
|
||||
return date.get(IsoFields.QUARTER_OF_YEAR);
|
||||
case week:
|
||||
return date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamExpressionParameter toExpression(StreamFactory factory) throws IOException {
|
||||
return new StreamExpression(function.toString()).withParameter(fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation toExplanation(StreamFactory factory) throws IOException {
|
||||
return new Explanation(nodeId.toString())
|
||||
.withExpressionType(Explanation.ExpressionType.EVALUATOR)
|
||||
.withImplementingClass(getClass().getName())
|
||||
.withExpression(toExpression(factory).toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.
|
||||
*/
|
||||
|
||||
package org.apache.solr.client.solrj.io.stream.eval;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections.map.HashedMap;
|
||||
import org.apache.solr.client.solrj.io.Tuple;
|
||||
import org.apache.solr.client.solrj.io.eval.DateEvaluator;
|
||||
import org.apache.solr.client.solrj.io.eval.StreamEvaluator;
|
||||
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;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests numeric Date/Time stream evaluators
|
||||
*/
|
||||
public class DateEvaluatorTest {
|
||||
|
||||
|
||||
StreamFactory factory;
|
||||
Map<String, Object> values;
|
||||
|
||||
public DateEvaluatorTest() {
|
||||
super();
|
||||
|
||||
factory = new StreamFactory();
|
||||
|
||||
factory.withFunctionName("nope", DateEvaluator.class);
|
||||
for (DateEvaluator.FUNCTION function : DateEvaluator.FUNCTION.values()) {
|
||||
factory.withFunctionName(function.toString(), DateEvaluator.class);
|
||||
}
|
||||
values = new HashedMap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidExpression() throws Exception {
|
||||
|
||||
StreamEvaluator evaluator;
|
||||
|
||||
try {
|
||||
evaluator = factory.constructEvaluator("nope(a)");
|
||||
evaluator.evaluate(new Tuple(null));
|
||||
assertTrue(false);
|
||||
} catch (Exception e) {
|
||||
assertTrue(e.getCause().getCause().getMessage().contains("Invalid date expression nope"));
|
||||
assertTrue(e.getCause().getCause().getMessage().contains("expecting one of [year, month, day"));
|
||||
}
|
||||
|
||||
try {
|
||||
evaluator = factory.constructEvaluator("week()");
|
||||
assertTrue(false);
|
||||
} catch (Exception e) {
|
||||
assertTrue(e.getCause().getCause().getMessage().contains("Invalid expression week()"));
|
||||
}
|
||||
|
||||
try {
|
||||
evaluator = factory.constructEvaluator("week(a, b)");
|
||||
assertTrue(false);
|
||||
} catch (Exception e) {
|
||||
assertTrue(e.getCause().getCause().getMessage().contains("expecting one value but found 2"));
|
||||
}
|
||||
|
||||
try {
|
||||
evaluator = factory.constructEvaluator("Week()");
|
||||
assertTrue(false);
|
||||
} catch (Exception e) {
|
||||
assertTrue(e.getMessage().contains("Invalid evaluator expression Week() - function 'Week' is unknown"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInvalidValues() throws Exception {
|
||||
StreamEvaluator evaluator = factory.constructEvaluator("year(a)");
|
||||
|
||||
try {
|
||||
values.clear();
|
||||
values.put("a", 12);
|
||||
Object result = evaluator.evaluate(new Tuple(values));
|
||||
assertTrue(false);
|
||||
} catch (Exception e) {
|
||||
assertEquals("Invalid field a - The field must be a string formatted in the ISO_INSTANT date format.", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
values.clear();
|
||||
values.put("a", "1995-12-31");
|
||||
Object result = evaluator.evaluate(new Tuple(values));
|
||||
assertTrue(false);
|
||||
} catch (Exception e) {
|
||||
assertEquals("Invalid field a - The field must be a string formatted in the ISO_INSTANT date format.", e.getMessage());
|
||||
}
|
||||
|
||||
values.clear();
|
||||
values.put("a", null);
|
||||
assertNull(evaluator.evaluate(new Tuple(values)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllFunctions() throws Exception {
|
||||
|
||||
//year, month, day, dayofyear, hour, minute, quarter, week, second, epoch
|
||||
testFunction("year(a)", "1995-12-31T23:59:59Z", 1995);
|
||||
testFunction("month(a)","1995-12-31T23:59:59Z", 12);
|
||||
testFunction("day(a)", "1995-12-31T23:59:59Z", 31);
|
||||
testFunction("dayofyear(a)", "1995-12-31T23:59:59Z", 365);
|
||||
testFunction("dayofquarter(a)", "1995-12-31T23:59:59Z", 92);
|
||||
testFunction("hour(a)", "1995-12-31T23:59:59Z", 23);
|
||||
testFunction("minute(a)", "1995-12-31T23:59:59Z", 59);
|
||||
testFunction("quarter(a)","1995-12-31T23:59:59Z", 4);
|
||||
testFunction("week(a)", "1995-12-31T23:59:59Z", 52);
|
||||
testFunction("second(a)", "1995-12-31T23:59:58Z", 58);
|
||||
testFunction("epoch(a)", "1995-12-31T23:59:59Z", 820454399000l);
|
||||
|
||||
testFunction("year(a)", "2017-03-17T10:30:45Z", 2017);
|
||||
testFunction("year('a')", "2017-03-17T10:30:45Z", 2017);
|
||||
testFunction("month(a)","2017-03-17T10:30:45Z", 3);
|
||||
testFunction("day(a)", "2017-03-17T10:30:45Z", 17);
|
||||
testFunction("day('a')", "2017-03-17T10:30:45Z", 17);
|
||||
testFunction("dayofyear(a)", "2017-03-17T10:30:45Z", 76);
|
||||
testFunction("dayofquarter(a)", "2017-03-17T10:30:45Z", 76);
|
||||
testFunction("hour(a)", "2017-03-17T10:30:45Z", 10);
|
||||
testFunction("minute(a)", "2017-03-17T10:30:45Z", 30);
|
||||
testFunction("quarter(a)","2017-03-17T10:30:45Z", 1);
|
||||
testFunction("week(a)", "2017-03-17T10:30:45Z", 11);
|
||||
testFunction("second(a)", "2017-03-17T10:30:45Z", 45);
|
||||
testFunction("epoch(a)", "2017-03-17T10:30:45Z", 1489746645000l);
|
||||
|
||||
testFunction("epoch(a)", new Date(1489746645500l).toInstant().toString(), 1489746645500l);
|
||||
testFunction("epoch(a)", new Date(820454399990l).toInstant().toString(), 820454399990l);
|
||||
|
||||
//Additionally test all functions to make sure they return a non-null number
|
||||
for (DateEvaluator.FUNCTION function : DateEvaluator.FUNCTION.values()) {
|
||||
StreamEvaluator evaluator = factory.constructEvaluator(function+"(a)");
|
||||
values.clear();
|
||||
values.put("a", "2017-03-17T10:30:45Z");
|
||||
Object result = evaluator.evaluate(new Tuple(values));
|
||||
assertNotNull(function+" should return a result",result);
|
||||
assertTrue(function+" should return a number", result instanceof Number);
|
||||
}
|
||||
}
|
||||
|
||||
public void testFunction(String expression, String value, Number expected) throws Exception {
|
||||
StreamEvaluator evaluator = factory.constructEvaluator(expression);
|
||||
values.clear();
|
||||
values.put("a", value);
|
||||
Object result = evaluator.evaluate(new Tuple(values));
|
||||
assertTrue(result instanceof Number);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplain() throws IOException {
|
||||
StreamExpression express = StreamExpressionParser.parse("month('myfield')");
|
||||
DateEvaluator dateEvaluator = new DateEvaluator(express,factory);
|
||||
Explanation explain = dateEvaluator.toExplanation(factory);
|
||||
assertEquals("month(myfield)", explain.getExpression());
|
||||
|
||||
express = StreamExpressionParser.parse("day(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbb)");
|
||||
dateEvaluator = new DateEvaluator(express,factory);
|
||||
explain = dateEvaluator.toExplanation(factory);
|
||||
assertEquals("day(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbb)", explain.getExpression());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue