Skip to content

Commit 3a627e1

Browse files
committed
SQL: Fix issue with date columns returned always in UTC (#40163)
When selecting columns of ES type `date` (SQL's DATETIME) the `FieldHitExtractor` was not using the timezone of the client session but always resorted to UTC. The same behaviour (UTC only) was encountered also for grouping keys (`CompositeKeyExtractor`) and for First/Last functions on dates (`TopHitsAggExtractor`). Fixes: #40152
1 parent a2ca361 commit 3a627e1

File tree

15 files changed

+208
-168
lines changed

15 files changed

+208
-168
lines changed

x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcTestUtils.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public static void logLikeCLI(ResultSet rs, Logger logger) throws SQLException {
135135
logger.info("\n" + formatter.formatWithHeader(cols, data));
136136
}
137137

138-
public static String of(long millis) {
139-
return StringUtils.toString(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), UTC));
138+
public static String of(long millis, String zoneId) {
139+
return StringUtils.toString(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(zoneId)));
140140
}
141-
}
141+
}

x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/ResultSetTestCase.java

Lines changed: 58 additions & 47 deletions
Large diffs are not rendered by default.

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/execution/search/Querier.java

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.elasticsearch.xpack.sql.util.StringUtils;
5353

5454
import java.io.IOException;
55+
import java.time.ZoneId;
5556
import java.util.ArrayList;
5657
import java.util.LinkedHashSet;
5758
import java.util.List;
@@ -64,19 +65,21 @@ public class Querier {
6465
private final Logger log = LogManager.getLogger(getClass());
6566

6667
private final TimeValue keepAlive, timeout;
68+
private final ZoneId zoneId;
6769
private final int size;
6870
private final Client client;
6971
@Nullable
7072
private final QueryBuilder filter;
7173

7274
public Querier(Client client, Configuration cfg) {
73-
this(client, cfg.requestTimeout(), cfg.pageTimeout(), cfg.filter(), cfg.pageSize());
75+
this(client, cfg.requestTimeout(), cfg.pageTimeout(), cfg.zoneId(), cfg.filter(), cfg.pageSize());
7476
}
7577

76-
public Querier(Client client, TimeValue keepAlive, TimeValue timeout, QueryBuilder filter, int size) {
78+
public Querier(Client client, TimeValue keepAlive, TimeValue timeout, ZoneId zoneId, QueryBuilder filter, int size) {
7779
this.client = client;
7880
this.keepAlive = keepAlive;
7981
this.timeout = timeout;
82+
this.zoneId = zoneId;
8083
this.filter = filter;
8184
this.size = size;
8285
}
@@ -98,13 +101,13 @@ public void query(Schema schema, QueryContainer query, String index, ActionListe
98101
ActionListener<SearchResponse> l;
99102
if (query.isAggsOnly()) {
100103
if (query.aggs().useImplicitGroupBy()) {
101-
l = new ImplicitGroupActionListener(listener, client, timeout, schema, query, search);
104+
l = new ImplicitGroupActionListener(listener, client, timeout, zoneId, schema, query, search);
102105
} else {
103-
l = new CompositeActionListener(listener, client, timeout, schema, query, search);
106+
l = new CompositeActionListener(listener, client, timeout, zoneId, schema, query, search);
104107
}
105108
} else {
106109
search.scroll(keepAlive);
107-
l = new ScrollActionListener(listener, client, timeout, schema, query);
110+
l = new ScrollActionListener(listener, client, timeout, zoneId, schema, query);
108111
}
109112

110113
client.search(search, l);
@@ -149,9 +152,9 @@ public Aggregations getAggregations() {
149152
}
150153
});
151154

152-
ImplicitGroupActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive, Schema schema,
153-
QueryContainer query, SearchRequest request) {
154-
super(listener, client, keepAlive, schema, query, request);
155+
ImplicitGroupActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive, ZoneId zoneId,
156+
Schema schema, QueryContainer query, SearchRequest request) {
157+
super(listener, client, keepAlive, zoneId, schema, query, request);
155158
}
156159

157160
@Override
@@ -197,12 +200,11 @@ private void handleBuckets(List<? extends Bucket> buckets, SearchResponse respon
197200
*/
198201
static class CompositeActionListener extends BaseAggActionListener {
199202

200-
CompositeActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive,
203+
CompositeActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive, ZoneId zoneId,
201204
Schema schema, QueryContainer query, SearchRequest request) {
202-
super(listener, client, keepAlive, schema, query, request);
205+
super(listener, client, keepAlive, zoneId, schema, query, request);
203206
}
204207

205-
206208
@Override
207209
protected void handleResponse(SearchResponse response, ActionListener<SchemaRowSet> listener) {
208210
// there are some results
@@ -240,9 +242,9 @@ abstract static class BaseAggActionListener extends BaseActionListener {
240242
final QueryContainer query;
241243
final SearchRequest request;
242244

243-
BaseAggActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive, Schema schema,
244-
QueryContainer query, SearchRequest request) {
245-
super(listener, client, keepAlive, schema);
245+
BaseAggActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive, ZoneId zoneId,
246+
Schema schema, QueryContainer query, SearchRequest request) {
247+
super(listener, client, keepAlive, zoneId, schema);
246248

247249
this.query = query;
248250
this.request = request;
@@ -263,7 +265,7 @@ protected List<BucketExtractor> initBucketExtractors(SearchResponse response) {
263265
private BucketExtractor createExtractor(FieldExtraction ref, BucketExtractor totalCount) {
264266
if (ref instanceof GroupByRef) {
265267
GroupByRef r = (GroupByRef) ref;
266-
return new CompositeKeyExtractor(r.key(), r.property(), r.zoneId());
268+
return new CompositeKeyExtractor(r.key(), r.property(), zoneId, r.isDateTimeBased());
267269
}
268270

269271
if (ref instanceof MetricAggRef) {
@@ -297,9 +299,9 @@ private BucketExtractor createExtractor(FieldExtraction ref, BucketExtractor tot
297299
static class ScrollActionListener extends BaseActionListener {
298300
private final QueryContainer query;
299301

300-
ScrollActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive,
302+
ScrollActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive, ZoneId zoneId,
301303
Schema schema, QueryContainer query) {
302-
super(listener, client, keepAlive, schema);
304+
super(listener, client, keepAlive, zoneId, schema);
303305
this.query = query;
304306
}
305307

@@ -344,12 +346,12 @@ protected void handleResponse(SearchResponse response, ActionListener<SchemaRowS
344346
private HitExtractor createExtractor(FieldExtraction ref) {
345347
if (ref instanceof SearchHitFieldRef) {
346348
SearchHitFieldRef f = (SearchHitFieldRef) ref;
347-
return new FieldHitExtractor(f.name(), f.getDataType(), f.useDocValue(), f.hitName());
349+
return new FieldHitExtractor(f.name(), f.getDataType(), zoneId, f.useDocValue(), f.hitName());
348350
}
349351

350352
if (ref instanceof ScriptFieldRef) {
351353
ScriptFieldRef f = (ScriptFieldRef) ref;
352-
return new FieldHitExtractor(f.name(), null, true);
354+
return new FieldHitExtractor(f.name(), null, zoneId, true);
353355
}
354356

355357
if (ref instanceof ComputedRef) {
@@ -387,13 +389,15 @@ abstract static class BaseActionListener implements ActionListener<SearchRespons
387389

388390
final Client client;
389391
final TimeValue keepAlive;
392+
final ZoneId zoneId;
390393
final Schema schema;
391394

392-
BaseActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive, Schema schema) {
395+
BaseActionListener(ActionListener<SchemaRowSet> listener, Client client, TimeValue keepAlive, ZoneId zoneId, Schema schema) {
393396
this.listener = listener;
394397

395398
this.client = client;
396399
this.keepAlive = keepAlive;
400+
this.zoneId = zoneId;
397401
this.schema = schema;
398402
}
399403

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/execution/search/extractor/CompositeKeyExtractor.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,44 +20,38 @@
2020
public class CompositeKeyExtractor implements BucketExtractor {
2121

2222
/**
23-
* Key or Komposite extractor.
23+
* Key or Composite extractor.
2424
*/
2525
static final String NAME = "k";
2626

2727
private final String key;
2828
private final Property property;
2929
private final ZoneId zoneId;
30+
private final boolean isDateTimeBased;
3031

3132
/**
3233
* Constructs a new <code>CompositeKeyExtractor</code> instance.
33-
* The time-zone parameter is used to indicate a date key.
3434
*/
35-
public CompositeKeyExtractor(String key, Property property, ZoneId zoneId) {
35+
public CompositeKeyExtractor(String key, Property property, ZoneId zoneId, boolean isDateTimeBased) {
3636
this.key = key;
3737
this.property = property;
3838
this.zoneId = zoneId;
39+
this.isDateTimeBased = isDateTimeBased;
3940
}
4041

4142
CompositeKeyExtractor(StreamInput in) throws IOException {
4243
key = in.readString();
4344
property = in.readEnum(Property.class);
44-
if (in.readBoolean()) {
45-
zoneId = ZoneId.of(in.readString());
46-
} else {
47-
zoneId = null;
48-
}
45+
zoneId = ZoneId.of(in.readString());
46+
isDateTimeBased = in.readBoolean();
4947
}
5048

5149
@Override
5250
public void writeTo(StreamOutput out) throws IOException {
5351
out.writeString(key);
5452
out.writeEnum(property);
55-
if (zoneId == null) {
56-
out.writeBoolean(false);
57-
} else {
58-
out.writeBoolean(true);
59-
out.writeString(zoneId.getId());
60-
}
53+
out.writeString(zoneId.getId());
54+
out.writeBoolean(isDateTimeBased);
6155
}
6256

6357
String key() {
@@ -72,6 +66,10 @@ ZoneId zoneId() {
7266
return zoneId;
7367
}
7468

69+
public boolean isDateTimeBased() {
70+
return isDateTimeBased;
71+
}
72+
7573
@Override
7674
public String getWriteableName() {
7775
return NAME;
@@ -91,7 +89,7 @@ public Object extract(Bucket bucket) {
9189

9290
Object object = ((Map<?, ?>) m).get(key);
9391

94-
if (zoneId != null) {
92+
if (isDateTimeBased) {
9593
if (object == null) {
9694
return object;
9795
} else if (object instanceof Long) {
@@ -106,7 +104,7 @@ public Object extract(Bucket bucket) {
106104

107105
@Override
108106
public int hashCode() {
109-
return Objects.hash(key, property, zoneId);
107+
return Objects.hash(key, property, zoneId, isDateTimeBased);
110108
}
111109

112110
@Override
@@ -122,7 +120,8 @@ public boolean equals(Object obj) {
122120
CompositeKeyExtractor other = (CompositeKeyExtractor) obj;
123121
return Objects.equals(key, other.key)
124122
&& Objects.equals(property, other.property)
125-
&& Objects.equals(zoneId, other.zoneId);
123+
&& Objects.equals(zoneId, other.zoneId)
124+
&& Objects.equals(isDateTimeBased, other.isDateTimeBased);
126125
}
127126

128127
@Override

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/execution/search/extractor/FieldHitExtractor.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.joda.time.DateTime;
1818

1919
import java.io.IOException;
20+
import java.time.ZoneId;
2021
import java.util.ArrayDeque;
2122
import java.util.Deque;
2223
import java.util.List;
@@ -48,16 +49,18 @@ private static String[] sourcePath(String name, boolean useDocValue, String hitN
4849

4950
private final String fieldName, hitName;
5051
private final DataType dataType;
52+
private final ZoneId zoneId;
5153
private final boolean useDocValue;
5254
private final String[] path;
5355

54-
public FieldHitExtractor(String name, DataType dataType, boolean useDocValue) {
55-
this(name, dataType, useDocValue, null);
56+
public FieldHitExtractor(String name, DataType dataType, ZoneId zoneId, boolean useDocValue) {
57+
this(name, dataType, zoneId, useDocValue, null);
5658
}
5759

58-
public FieldHitExtractor(String name, DataType dataType, boolean useDocValue, String hitName) {
60+
public FieldHitExtractor(String name, DataType dataType, ZoneId zoneId, boolean useDocValue, String hitName) {
5961
this.fieldName = name;
6062
this.dataType = dataType;
63+
this.zoneId = zoneId;
6164
this.useDocValue = useDocValue;
6265
this.hitName = hitName;
6366

@@ -74,6 +77,7 @@ public FieldHitExtractor(String name, DataType dataType, boolean useDocValue, St
7477
fieldName = in.readString();
7578
String esType = in.readOptionalString();
7679
dataType = esType != null ? DataType.fromTypeName(esType) : null;
80+
zoneId = ZoneId.of(in.readString());
7781
useDocValue = in.readBoolean();
7882
hitName = in.readOptionalString();
7983
path = sourcePath(fieldName, useDocValue, hitName);
@@ -88,6 +92,7 @@ public String getWriteableName() {
8892
public void writeTo(StreamOutput out) throws IOException {
8993
out.writeString(fieldName);
9094
out.writeOptionalString(dataType == null ? null : dataType.esType);
95+
out.writeString(zoneId.getId());
9196
out.writeBoolean(useDocValue);
9297
out.writeOptionalString(hitName);
9398
}
@@ -130,7 +135,7 @@ private Object unwrapMultiValue(Object values) {
130135
}
131136
if (dataType == DataType.DATE) {
132137
if (values instanceof String) {
133-
return DateUtils.of(Long.parseLong(values.toString()));
138+
return DateUtils.of(Long.parseLong(values.toString()), zoneId);
134139
}
135140
// returned by nested types...
136141
if (values instanceof DateTime) {
@@ -214,9 +219,17 @@ public String fieldName() {
214219
return fieldName;
215220
}
216221

222+
public ZoneId zoneId() {
223+
return zoneId;
224+
}
225+
226+
DataType dataType() {
227+
return dataType;
228+
}
229+
217230
@Override
218231
public String toString() {
219-
return fieldName + "@" + hitName;
232+
return fieldName + "@" + hitName + "@" + zoneId;
220233
}
221234

222235
@Override

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
*/
66
package org.elasticsearch.xpack.sql.planner;
77

8-
import java.util.List;
9-
import java.util.Map;
10-
118
import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
129
import org.elasticsearch.xpack.sql.plan.physical.PhysicalPlan;
1310
import org.elasticsearch.xpack.sql.planner.Verifier.Failure;
1411
import org.elasticsearch.xpack.sql.tree.Node;
1512

13+
import java.util.List;
14+
import java.util.Map;
15+
1616
import static java.util.stream.Collectors.toMap;
1717

1818
public class Planner {
@@ -64,4 +64,4 @@ public Map<Node<?>, String> verifyExecutingPlanFailures(PhysicalPlan plan) {
6464
List<Failure> failures = Verifier.verifyExecutingPlan(plan);
6565
return failures.stream().collect(toMap(Failure::source, Failure::message));
6666
}
67-
}
67+
}

0 commit comments

Comments
 (0)