Skip to content

Commit 020939a

Browse files
authored
Add "format" to "range" queries resulted from optimizing a logical AND (#48073)
1 parent c1c4efc commit 020939a

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/planner/QueryTranslator.java

+31-2
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,13 @@
108108
import org.elasticsearch.xpack.sql.tree.Source;
109109
import org.elasticsearch.xpack.sql.util.Check;
110110
import org.elasticsearch.xpack.sql.util.DateUtils;
111+
import org.elasticsearch.xpack.sql.util.Holder;
111112
import org.elasticsearch.xpack.sql.util.ReflectionUtils;
112113

113114
import java.time.OffsetTime;
114115
import java.time.Period;
115116
import java.time.ZonedDateTime;
117+
import java.time.temporal.TemporalAccessor;
116118
import java.util.Arrays;
117119
import java.util.LinkedHashMap;
118120
import java.util.List;
@@ -821,9 +823,36 @@ protected QueryTranslation asQuery(Range r, boolean onAggs) {
821823
if (onAggs) {
822824
aggFilter = new AggFilter(at.id().toString(), r.asScript());
823825
} else {
826+
Holder<Object> lower = new Holder<>(valueOf(r.lower()));
827+
Holder<Object> upper = new Holder<>(valueOf(r.upper()));
828+
Holder<String> format = new Holder<>(dateFormat(r.value()));
829+
830+
// for a date constant comparison, we need to use a format for the date, to make sure that the format is the same
831+
// no matter the timezone provided by the user
832+
if (format.get() == null) {
833+
DateFormatter formatter = null;
834+
if (lower.get() instanceof ZonedDateTime || upper.get() instanceof ZonedDateTime) {
835+
formatter = DateFormatter.forPattern(DATE_FORMAT);
836+
} else if (lower.get() instanceof OffsetTime || upper.get() instanceof OffsetTime) {
837+
formatter = DateFormatter.forPattern(TIME_FORMAT);
838+
}
839+
if (formatter != null) {
840+
// RangeQueryBuilder accepts an Object as its parameter, but it will call .toString() on the ZonedDateTime
841+
// instance which can have a slightly different format depending on the ZoneId used to create the ZonedDateTime
842+
// Since RangeQueryBuilder can handle date as String as well, we'll format it as String and provide the format.
843+
if (lower.get() instanceof ZonedDateTime || lower.get() instanceof OffsetTime) {
844+
lower.set(formatter.format((TemporalAccessor) lower.get()));
845+
}
846+
if (upper.get() instanceof ZonedDateTime || upper.get() instanceof OffsetTime) {
847+
upper.set(formatter.format((TemporalAccessor) upper.get()));
848+
}
849+
format.set(formatter.pattern());
850+
}
851+
}
852+
824853
query = handleQuery(r, r.value(),
825-
() -> new RangeQuery(r.source(), nameOf(r.value()), valueOf(r.lower()), r.includeLower(),
826-
valueOf(r.upper()), r.includeUpper(), dateFormat(r.value())));
854+
() -> new RangeQuery(r.source(), nameOf(r.value()), lower.get(), r.includeLower(),
855+
upper.get(), r.includeUpper(), format.get()));
827856
}
828857
return new QueryTranslation(query, aggFilter);
829858
} else {

x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/planner/QueryTranslatorTests.java

+47
Original file line numberDiff line numberDiff line change
@@ -239,22 +239,37 @@ public void testDateRangeCast() {
239239

240240
public void testDateRangeWithCurrentTimestamp() {
241241
testDateRangeWithCurrentFunctions("CURRENT_TIMESTAMP()", DATE_FORMAT, TestUtils.TEST_CFG.now());
242+
testDateRangeWithCurrentFunctions_AndRangeOptimization("CURRENT_TIMESTAMP()", DATE_FORMAT,
243+
TestUtils.TEST_CFG.now().minusDays(1L).minusSeconds(1L),
244+
TestUtils.TEST_CFG.now().plusDays(1L).plusSeconds(1L));
242245
}
243246

244247
public void testDateRangeWithCurrentDate() {
245248
testDateRangeWithCurrentFunctions("CURRENT_DATE()", DATE_FORMAT, DateUtils.asDateOnly(TestUtils.TEST_CFG.now()));
249+
testDateRangeWithCurrentFunctions_AndRangeOptimization("CURRENT_DATE()", DATE_FORMAT,
250+
DateUtils.asDateOnly(TestUtils.TEST_CFG.now().minusDays(2L)),
251+
DateUtils.asDateOnly(TestUtils.TEST_CFG.now().plusDays(1L)));
246252
}
247253

248254
public void testDateRangeWithToday() {
249255
testDateRangeWithCurrentFunctions("TODAY()", DATE_FORMAT, DateUtils.asDateOnly(TestUtils.TEST_CFG.now()));
256+
testDateRangeWithCurrentFunctions_AndRangeOptimization("TODAY()", DATE_FORMAT,
257+
DateUtils.asDateOnly(TestUtils.TEST_CFG.now().minusDays(2L)),
258+
DateUtils.asDateOnly(TestUtils.TEST_CFG.now().plusDays(1L)));
250259
}
251260

252261
public void testDateRangeWithNow() {
253262
testDateRangeWithCurrentFunctions("NOW()", DATE_FORMAT, TestUtils.TEST_CFG.now());
263+
testDateRangeWithCurrentFunctions_AndRangeOptimization("NOW()", DATE_FORMAT,
264+
TestUtils.TEST_CFG.now().minusDays(1L).minusSeconds(1L),
265+
TestUtils.TEST_CFG.now().plusDays(1L).plusSeconds(1L));
254266
}
255267

256268
public void testDateRangeWithCurrentTime() {
257269
testDateRangeWithCurrentFunctions("CURRENT_TIME()", TIME_FORMAT, TestUtils.TEST_CFG.now());
270+
testDateRangeWithCurrentFunctions_AndRangeOptimization("CURRENT_TIME()", TIME_FORMAT,
271+
TestUtils.TEST_CFG.now().minusDays(1L).minusSeconds(1L),
272+
TestUtils.TEST_CFG.now().plusDays(1L).plusSeconds(1L));
258273
}
259274

260275
private void testDateRangeWithCurrentFunctions(String function, String pattern, ZonedDateTime now) {
@@ -292,6 +307,38 @@ private void testDateRangeWithCurrentFunctions(String function, String pattern,
292307
assertEquals(operator.equals("=") || operator.equals("!=") || operator.equals(">="), rq.includeLower());
293308
assertEquals(pattern, rq.format());
294309
}
310+
311+
private void testDateRangeWithCurrentFunctions_AndRangeOptimization(String function, String pattern, ZonedDateTime lowerValue,
312+
ZonedDateTime upperValue) {
313+
String lowerOperator = randomFrom(new String[] {"<", "<="});
314+
String upperOperator = randomFrom(new String[] {">", ">="});
315+
// use both date-only interval (1 DAY) and time-only interval (1 second) to cover CURRENT_TIMESTAMP and TODAY scenarios
316+
String interval = "(INTERVAL 1 DAY + INTERVAL 1 SECOND)";
317+
318+
PhysicalPlan p = optimizeAndPlan("SELECT some.string FROM test WHERE date" + lowerOperator + function + " + " + interval
319+
+ " AND date " + upperOperator + function + " - " + interval);
320+
assertEquals(EsQueryExec.class, p.getClass());
321+
EsQueryExec eqe = (EsQueryExec) p;
322+
assertEquals(1, eqe.output().size());
323+
assertEquals("test.some.string", eqe.output().get(0).qualifiedName());
324+
assertEquals(DataType.TEXT, eqe.output().get(0).dataType());
325+
326+
Query query = eqe.queryContainer().query();
327+
// the range queries optimization should create a single "range" query with "from" and "to" populated with the values
328+
// in the two branches of the AND condition
329+
assertTrue(query instanceof RangeQuery);
330+
RangeQuery rq = (RangeQuery) query;
331+
assertEquals("date", rq.field());
332+
333+
assertEquals(DateFormatter.forPattern(pattern)
334+
.format(upperValue.withNano(DateUtils.getNanoPrecision(null, upperValue.getNano()))), rq.upper());
335+
assertEquals(DateFormatter.forPattern(pattern)
336+
.format(lowerValue.withNano(DateUtils.getNanoPrecision(null, lowerValue.getNano()))), rq.lower());
337+
338+
assertEquals(lowerOperator.equals("<="), rq.includeUpper());
339+
assertEquals(upperOperator.equals(">="), rq.includeLower());
340+
assertEquals(pattern, rq.format());
341+
}
295342

296343
public void testTranslateDateAdd_WhereClause_Painless() {
297344
LogicalPlan p = plan("SELECT int FROM test WHERE DATE_ADD('quarter',int, date) > '2018-09-04'::date");

0 commit comments

Comments
 (0)