-
Notifications
You must be signed in to change notification settings - Fork 25k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SQL: Fix issue with timezone when paginating #52101
Changes from all commits
a488512
c891567
81605e8
0120357
e95628a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,6 @@ | |
package org.elasticsearch.xpack.sql.qa.rest; | ||
|
||
import com.fasterxml.jackson.core.io.JsonStringEncoder; | ||
|
||
import org.apache.http.HttpEntity; | ||
import org.apache.http.entity.ContentType; | ||
import org.apache.http.entity.StringEntity; | ||
|
@@ -15,9 +14,11 @@ | |
import org.elasticsearch.client.Response; | ||
import org.elasticsearch.client.ResponseException; | ||
import org.elasticsearch.common.CheckedSupplier; | ||
import org.elasticsearch.common.Strings; | ||
import org.elasticsearch.common.bytes.BytesArray; | ||
import org.elasticsearch.common.collect.Tuple; | ||
import org.elasticsearch.common.io.Streams; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
import org.elasticsearch.common.xcontent.XContentHelper; | ||
import org.elasticsearch.common.xcontent.json.JsonXContent; | ||
import org.elasticsearch.test.NotEqualMessageBuilder; | ||
|
@@ -31,6 +32,9 @@ | |
import java.io.InputStreamReader; | ||
import java.nio.charset.StandardCharsets; | ||
import java.sql.JDBCType; | ||
import java.time.Instant; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
|
@@ -151,6 +155,70 @@ public void testNextPage() throws IOException { | |
ContentType.APPLICATION_JSON), StringUtils.EMPTY, mode)); | ||
} | ||
|
||
public void testNextPageWithDatetimeAndTimezoneParam() throws IOException { | ||
Request request = new Request("PUT", "/test_date_timezone"); | ||
XContentBuilder createIndex = JsonXContent.contentBuilder().startObject(); | ||
createIndex.startObject("mappings"); | ||
{ | ||
createIndex.startObject("properties"); | ||
{ | ||
createIndex.startObject("date").field("type", "date").field("format", "epoch_millis"); | ||
createIndex.endObject(); | ||
} | ||
createIndex.endObject(); | ||
} | ||
createIndex.endObject().endObject(); | ||
request.setJsonEntity(Strings.toString(createIndex)); | ||
client().performRequest(request); | ||
|
||
request = new Request("PUT", "/test_date_timezone/_bulk"); | ||
request.addParameter("refresh", "true"); | ||
StringBuilder bulk = new StringBuilder(); | ||
long[] datetimes = new long[] { 1_000, 10_000, 100_000, 1_000_000, 10_000_000 }; | ||
for (long datetime : datetimes) { | ||
bulk.append("{\"index\":{}}\n"); | ||
bulk.append("{\"date\":").append(datetime).append("}\n"); | ||
} | ||
request.setJsonEntity(bulk.toString()); | ||
assertEquals(200, client().performRequest(request).getStatusLine().getStatusCode()); | ||
|
||
ZoneId zoneId = randomZone(); | ||
String mode = randomMode(); | ||
String sqlRequest = | ||
"{\"query\":\"SELECT DATE_PART('TZOFFSET', date) AS tz FROM test_date_timezone ORDER BY date\"," | ||
+ "\"time_zone\":\"" + zoneId.getId() + "\", " | ||
+ "\"mode\":\"" + mode + "\", " | ||
+ "\"fetch_size\":2}"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's perfectly fine as is, but was curious about the reason - if any - for choosing a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fetch size There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, there'll be pages with 1 and 2 rows as the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't have to do with the bug fixed, just another safety testing that the fetch size behavior is correct. |
||
|
||
String cursor = null; | ||
for (int i = 0; i <= datetimes.length; i += 2) { | ||
Map<String, Object> expected = new HashMap<>(); | ||
Map<String, Object> response; | ||
|
||
if (i == 0) { | ||
expected.put("columns", singletonList(columnInfo(mode, "tz", "integer", JDBCType.INTEGER, 11))); | ||
response = runSql(new StringEntity(sqlRequest, ContentType.APPLICATION_JSON), "", mode); | ||
} else { | ||
response = runSql(new StringEntity("{\"cursor\":\"" + cursor + "\"" + mode(mode) + "}", | ||
ContentType.APPLICATION_JSON), StringUtils.EMPTY, mode); | ||
} | ||
|
||
List<Object> values = new ArrayList<>(2); | ||
for (int j = 0; j < (i < datetimes.length - 1 ? 2 : 1); j++) { | ||
values.add(singletonList(ZonedDateTime.ofInstant(Instant.ofEpochMilli(datetimes[i + j]), zoneId) | ||
.getOffset().getTotalSeconds() / 60)); | ||
} | ||
expected.put("rows", values); | ||
cursor = (String) response.remove("cursor"); | ||
assertResponse(expected, response); | ||
assertNotNull(cursor); | ||
} | ||
Map<String, Object> expected = new HashMap<>(); | ||
expected.put("rows", emptyList()); | ||
assertResponse(expected, runSql(new StringEntity("{ \"cursor\":\"" + cursor + "\"" + mode(mode) + "}", | ||
ContentType.APPLICATION_JSON), StringUtils.EMPTY, mode)); | ||
} | ||
|
||
@AwaitsFix(bugUrl = "Unclear status, https://github.com/elastic/x-pack-elasticsearch/issues/2074") | ||
public void testTimeZone() throws IOException { | ||
String mode = randomMode(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
import org.elasticsearch.action.support.HandledTransportAction; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.common.Strings; | ||
import org.elasticsearch.common.collect.Tuple; | ||
import org.elasticsearch.common.inject.Inject; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.tasks.Task; | ||
|
@@ -26,12 +27,14 @@ | |
import org.elasticsearch.xpack.sql.proto.ColumnInfo; | ||
import org.elasticsearch.xpack.sql.proto.Mode; | ||
import org.elasticsearch.xpack.sql.session.Configuration; | ||
import org.elasticsearch.xpack.sql.session.Cursor; | ||
import org.elasticsearch.xpack.sql.session.Cursor.Page; | ||
import org.elasticsearch.xpack.sql.session.Cursors; | ||
import org.elasticsearch.xpack.sql.session.RowSet; | ||
import org.elasticsearch.xpack.sql.session.SchemaRowSet; | ||
import org.elasticsearch.xpack.sql.type.SqlDataTypes; | ||
|
||
import java.time.ZoneId; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
|
@@ -68,7 +71,7 @@ protected void doExecute(Task task, SqlQueryRequest request, ActionListener<SqlQ | |
/** | ||
* Actual implementation of the action. Statically available to support embedded mode. | ||
*/ | ||
public static void operation(PlanExecutor planExecutor, SqlQueryRequest request, ActionListener<SqlQueryResponse> listener, | ||
static void operation(PlanExecutor planExecutor, SqlQueryRequest request, ActionListener<SqlQueryResponse> listener, | ||
String username, String clusterName) { | ||
// The configuration is always created however when dealing with the next page, only the timeouts are relevant | ||
// the rest having default values (since the query is already created) | ||
|
@@ -80,13 +83,14 @@ public static void operation(PlanExecutor planExecutor, SqlQueryRequest request, | |
planExecutor.sql(cfg, request.query(), request.params(), | ||
wrap(p -> listener.onResponse(createResponseWithSchema(request, p)), listener::onFailure)); | ||
} else { | ||
planExecutor.nextPage(cfg, Cursors.decodeFromString(request.cursor()), | ||
wrap(p -> listener.onResponse(createResponse(request, null, p)), | ||
Tuple<Cursor, ZoneId> decoded = Cursors.decodeFromStringWithZone(request.cursor()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm planning to do that in an upcoming PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ad discussed, will not pass the zoneId into the Cursor, as zoneId is the responsibility of SqlInput/OutputStreams to handle. Instead, I remove the |
||
planExecutor.nextPage(cfg, decoded.v1(), | ||
wrap(p -> listener.onResponse(createResponse(request, decoded.v2(), null, p)), | ||
listener::onFailure)); | ||
} | ||
} | ||
|
||
static SqlQueryResponse createResponseWithSchema(SqlQueryRequest request, Page page) { | ||
private static SqlQueryResponse createResponseWithSchema(SqlQueryRequest request, Page page) { | ||
RowSet rset = page.rowSet(); | ||
if ((rset instanceof SchemaRowSet) == false) { | ||
throw new SqlIllegalArgumentException("No schema found inside {}", rset.getClass()); | ||
|
@@ -102,10 +106,10 @@ static SqlQueryResponse createResponseWithSchema(SqlQueryRequest request, Page p | |
} | ||
} | ||
columns = unmodifiableList(columns); | ||
return createResponse(request, columns, page); | ||
return createResponse(request, request.zoneId(), columns, page); | ||
} | ||
|
||
static SqlQueryResponse createResponse(SqlQueryRequest request, List<ColumnInfo> header, Page page) { | ||
private static SqlQueryResponse createResponse(SqlQueryRequest request, ZoneId zoneId, List<ColumnInfo> header, Page page) { | ||
List<List<Object>> rows = new ArrayList<>(); | ||
page.rowSet().forEachRow(rowView -> { | ||
List<Object> row = new ArrayList<>(rowView.columnCount()); | ||
|
@@ -114,7 +118,7 @@ static SqlQueryResponse createResponse(SqlQueryRequest request, List<ColumnInfo> | |
}); | ||
|
||
return new SqlQueryResponse( | ||
Cursors.encodeToString(page.next(), request.zoneId()), | ||
Cursors.encodeToString(page.next(), zoneId), | ||
request.mode(), | ||
request.columnar(), | ||
header, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move this line on the above one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, just kept the style from the other tests, e.g.: https://github.com/elastic/elasticsearch/pull/52101/files/a488512e3abd83b8de95ef0cbfcd9b2adc2b8a86#diff-7eb5a40bcec78e0f582b7ef886430b28R95
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A(n appropriate) formatter should help in these style-adjusting cases.