diff --git a/example/rest-java-example/src/main/java/org/apache/iotdb/HttpExample.java b/example/rest-java-example/src/main/java/org/apache/iotdb/HttpExample.java index bccc2fd254949..dd9b2bdf831d5 100644 --- a/example/rest-java-example/src/main/java/org/apache/iotdb/HttpExample.java +++ b/example/rest-java-example/src/main/java/org/apache/iotdb/HttpExample.java @@ -53,6 +53,7 @@ public static void main(String[] args) { httpExample.ping(); httpExample.insertTablet(); httpExample.query(); + httpExample.queryWithTimeZone(); } public void ping() { @@ -138,4 +139,29 @@ public void query() { } } } + + public void queryWithTimeZone() { + CloseableHttpClient httpClient = SSLClient.getInstance().getHttpClient(); + CloseableHttpResponse response = null; + try { + HttpPost httpPost = getHttpPost("http://127.0.0.1:18080/rest/v1/query"); + String sql = "{\"sql\":\"select * from root.sg25 where time <= 2026-03-28T00:00:00\"}"; + httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset())); + response = httpClient.execute(httpPost); + HttpEntity responseEntity = response.getEntity(); + String message = EntityUtils.toString(responseEntity, UTF8); + ObjectMapper mapper = new ObjectMapper(); + LOGGER.info("message with time zone = {}", mapper.readValue(message, Map.class)); + } catch (IOException e) { + LOGGER.error("The query with time zone rest api failed", e); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + LOGGER.error("Response close error", e); + } + } + } } diff --git a/example/rest-java-example/src/main/java/org/apache/iotdb/HttpsExample.java b/example/rest-java-example/src/main/java/org/apache/iotdb/HttpsExample.java index a3dbbceafc7fe..2e5c167133bcd 100644 --- a/example/rest-java-example/src/main/java/org/apache/iotdb/HttpsExample.java +++ b/example/rest-java-example/src/main/java/org/apache/iotdb/HttpsExample.java @@ -52,6 +52,7 @@ public static void main(String[] args) { httpsExample.pingHttps(); httpsExample.insertTablet(); httpsExample.query(); + httpsExample.queryWithTimeZone(); } public void pingHttps() { @@ -138,4 +139,30 @@ public void query() { } } } + + public void queryWithTimeZone() { + CloseableHttpClient httpClient = SSLClient.getInstance().getHttpClient(); + CloseableHttpResponse response = null; + try { + HttpPost httpPost = getHttpPost("https://127.0.0.1:18080/rest/v1/query"); + httpPost.addHeader("Time-Zone", "+05:00"); + String sql = "{\"sql\":\"select * from root.sg25 where time <= 2026-03-28T00:00:00\"}"; + httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset())); + response = httpClient.execute(httpPost); + HttpEntity responseEntity = response.getEntity(); + String message = EntityUtils.toString(responseEntity, UTF8); + ObjectMapper mapper = new ObjectMapper(); + LOGGER.info("message with time zone = {}", mapper.readValue(message, Map.class)); + } catch (IOException e) { + LOGGER.error("Https query with time zone rest api failed", e); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + LOGGER.error("Response close error", e); + } + } + } } diff --git a/example/rest-java-example/src/main/java/org/apache/iotdb/TableHttpExample.java b/example/rest-java-example/src/main/java/org/apache/iotdb/TableHttpExample.java index b9651245d0206..c51ef67e6ad64 100644 --- a/example/rest-java-example/src/main/java/org/apache/iotdb/TableHttpExample.java +++ b/example/rest-java-example/src/main/java/org/apache/iotdb/TableHttpExample.java @@ -27,6 +27,8 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.charset.Charset; @@ -34,6 +36,7 @@ import java.util.Base64; public class TableHttpExample { + private static final Logger LOGGER = LoggerFactory.getLogger(TableHttpExample.class); private static final String UTF8 = "utf-8"; @@ -50,6 +53,7 @@ public static void main(String[] args) { httpExample.nonQuery(); httpExample.insertTablet(); httpExample.query(); + httpExample.queryWithTimeZone(); } public void ping() { @@ -220,4 +224,30 @@ public void query() { } } } + + public void queryWithTimeZone() { + CloseableHttpClient httpClient = SSLClient.getInstance().getHttpClient(); + CloseableHttpResponse response = null; + try { + HttpPost httpPost = getHttpPost("http://127.0.0.1:18080/rest/table/v1/query"); + httpPost.addHeader("Time-Zone", "+05:00"); + String sql = + "{\"database\":\"test\",\"sql\":\"select * from sg211 where time <= 2026-03-28T00:00:00\"}"; + httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset())); + response = httpClient.execute(httpPost); + HttpEntity responseEntity = response.getEntity(); + String message = EntityUtils.toString(responseEntity, UTF8); + LOGGER.info("message with time zone = {}", JsonParser.parseString(message).getAsJsonObject()); + } catch (IOException e) { + LOGGER.error("The query with time zone rest api failed", e); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + LOGGER.error("Response close error", e); + } + } + } } diff --git a/example/rest-java-example/src/main/java/org/apache/iotdb/TableHttpsExample.java b/example/rest-java-example/src/main/java/org/apache/iotdb/TableHttpsExample.java index 200dbf66fdbcb..181db2ae999d9 100644 --- a/example/rest-java-example/src/main/java/org/apache/iotdb/TableHttpsExample.java +++ b/example/rest-java-example/src/main/java/org/apache/iotdb/TableHttpsExample.java @@ -27,6 +27,8 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.charset.Charset; @@ -34,6 +36,7 @@ import java.util.Base64; public class TableHttpsExample { + private static final Logger LOGGER = LoggerFactory.getLogger(TableHttpsExample.class); private static final String UTF8 = "utf-8"; @@ -50,6 +53,7 @@ public static void main(String[] args) { httpExample.nonQuery(); httpExample.insertTablet(); httpExample.query(); + httpExample.queryWithTimeZone(); } public void ping() { @@ -220,4 +224,30 @@ public void query() { } } } + + public void queryWithTimeZone() { + CloseableHttpClient httpClient = SSLClient.getInstance().getHttpClient(); + CloseableHttpResponse response = null; + try { + HttpPost httpPost = getHttpPost("https://127.0.0.1:18080/rest/table/v1/query"); + httpPost.addHeader("Time-Zone", "+05:00"); + String sql = + "{\"database\":\"test\",\"sql\":\"select * from sg211 where time <= 2026-03-28T00:00:00\"}"; + httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset())); + response = httpClient.execute(httpPost); + HttpEntity responseEntity = response.getEntity(); + String message = EntityUtils.toString(responseEntity, UTF8); + LOGGER.info("message with time zone = {}", JsonParser.parseString(message).getAsJsonObject()); + } catch (IOException e) { + LOGGER.error("The query with time zone rest api failed", e); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + LOGGER.error("Response close error", e); + } + } + } } diff --git a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/AuthorizationFilter.java b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/AuthorizationFilter.java index a62a402e3d293..e274734cb3eda 100644 --- a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/AuthorizationFilter.java +++ b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/filter/AuthorizationFilter.java @@ -39,6 +39,7 @@ import javax.ws.rs.ext.Provider; import java.io.IOException; +import java.time.DateTimeException; import java.time.ZoneId; import java.util.Base64; import java.util.UUID; @@ -88,6 +89,12 @@ public void filter(ContainerRequestContext containerRequestContext) throws IOExc if (user == null) { return; } + + ZoneId zoneId = resolveTimeZone(containerRequestContext); + if (zoneId == null) { + return; + } + String sessionid = UUID.randomUUID().toString(); if (SESSION_MANAGER.getCurrSession() == null) { RestClientSession restClientSession = new RestClientSession(sessionid); @@ -97,7 +104,7 @@ public void filter(ContainerRequestContext containerRequestContext) throws IOExc SESSION_MANAGER.getCurrSession(), user.getUserId(), user.getUsername(), - ZoneId.systemDefault(), + zoneId, IoTDBConstant.ClientVersion.V_1_0); } BasicSecurityContext basicSecurityContext = @@ -147,6 +154,33 @@ private User checkLogin( return user; } + /** + * Resolves the Time-Zone header from the request. + * + * @param requestContext the incoming HTTP request + * @return the resolved ZoneId, or {@code null} if the header is invalid (the request is aborted) + */ + private ZoneId resolveTimeZone(ContainerRequestContext requestContext) { + String timeZoneHeader = requestContext.getHeaderString("Time-Zone"); + if (timeZoneHeader == null || timeZoneHeader.isEmpty()) { + return ZoneId.systemDefault(); + } + try { + return ZoneId.of(timeZoneHeader); + } catch (DateTimeException e) { + Response resp = + Response.status(Status.BAD_REQUEST) + .type(MediaType.APPLICATION_JSON) + .entity( + new ExecutionStatus() + .code(TSStatusCode.ILLEGAL_PARAMETER.getStatusCode()) + .message("Invalid time zone: " + timeZoneHeader)) + .build(); + requestContext.abortWith(resp); + return null; + } + } + @Override public void filter( ContainerRequestContext requestContext, ContainerResponseContext responseContext) diff --git a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/table/v1/impl/RestApiServiceImpl.java b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/table/v1/impl/RestApiServiceImpl.java index b35e9d27f595c..e66cb88a6e144 100644 --- a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/table/v1/impl/RestApiServiceImpl.java +++ b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/table/v1/impl/RestApiServiceImpl.java @@ -51,7 +51,6 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; -import java.time.ZoneId; import java.util.List; import java.util.Optional; @@ -287,7 +286,8 @@ private Statement createStatement( } clientSession.setSqlDialect(IClientSession.SqlDialect.TABLE); - return relationSqlParser.createStatement(sql.getSql(), ZoneId.systemDefault(), clientSession); + return relationSqlParser.createStatement( + sql.getSql(), clientSession.getZoneId(), clientSession); } private Response validateStatement(Statement statement, boolean userQuery) { diff --git a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/GrafanaApiServiceImpl.java b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/GrafanaApiServiceImpl.java index 0db4cd06c669f..2fa8c755eea45 100644 --- a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/GrafanaApiServiceImpl.java +++ b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/GrafanaApiServiceImpl.java @@ -91,8 +91,8 @@ public Response variables(SQL sql, SecurityContext securityContext) { try { RequestValidationHandler.validateSQL(sql); - Statement statement = - StatementGenerator.createStatement(sql.getSql(), ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + Statement statement = StatementGenerator.createStatement(sql.getSql(), zoneId); if (!(statement instanceof ShowStatement) && !(statement instanceof QueryStatement)) { return Response.ok() .entity( @@ -168,7 +168,8 @@ public Response expression(ExpressionRequest expressionRequest, SecurityContext sql += " " + expressionRequest.getControl(); } - Statement statement = StatementGenerator.createStatement(sql, ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + Statement statement = StatementGenerator.createStatement(sql, zoneId); Response response = authorizationHandler.checkAuthority(securityContext, statement); if (response != null) { @@ -232,7 +233,8 @@ public Response node(List requestBody, SecurityContext securityContext) // TODO: necessary to create a partial path? PartialPath path = new PartialPath(Joiner.on(".").join(requestBody)); String sql = "show child paths " + path; - Statement statement = StatementGenerator.createStatement(sql, ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + Statement statement = StatementGenerator.createStatement(sql, zoneId); Response response = authorizationHandler.checkAuthority(securityContext, statement); if (response != null) { diff --git a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/RestApiServiceImpl.java b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/RestApiServiceImpl.java index 329ac47034bdb..df02cc8e5b6f9 100644 --- a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/RestApiServiceImpl.java +++ b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v1/impl/RestApiServiceImpl.java @@ -87,7 +87,8 @@ public Response executeNonQueryStatement(SQL sql, SecurityContext securityContex Statement statement = null; try { RequestValidationHandler.validateSQL(sql); - statement = StatementGenerator.createStatement(sql.getSql(), ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + statement = StatementGenerator.createStatement(sql.getSql(), zoneId); if (statement == null) { return Response.ok() .entity( @@ -177,7 +178,8 @@ public Response executeQueryStatement(SQL sql, SecurityContext securityContext) Statement statement = null; try { RequestValidationHandler.validateSQL(sql); - statement = StatementGenerator.createStatement(sql.getSql(), ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + statement = StatementGenerator.createStatement(sql.getSql(), zoneId); if (statement == null) { return Response.ok() .entity( diff --git a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/GrafanaApiServiceImpl.java b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/GrafanaApiServiceImpl.java index 5b120b9c1d7a3..b1f5f3f5238cf 100644 --- a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/GrafanaApiServiceImpl.java +++ b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/GrafanaApiServiceImpl.java @@ -91,8 +91,8 @@ public Response variables(SQL sql, SecurityContext securityContext) { try { RequestValidationHandler.validateSQL(sql); - Statement statement = - StatementGenerator.createStatement(sql.getSql(), ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + Statement statement = StatementGenerator.createStatement(sql.getSql(), zoneId); if (!(statement instanceof ShowStatement) && !(statement instanceof QueryStatement)) { return Response.ok() .entity( @@ -168,7 +168,8 @@ public Response expression(ExpressionRequest expressionRequest, SecurityContext sql += " " + expressionRequest.getControl(); } - Statement statement = StatementGenerator.createStatement(sql, ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + Statement statement = StatementGenerator.createStatement(sql, zoneId); Response response = authorizationHandler.checkAuthority(securityContext, statement); if (response != null) { @@ -232,7 +233,8 @@ public Response node(List requestBody, SecurityContext securityContext) // TODO: necessary to create a PartialPath PartialPath path = new PartialPath(Joiner.on(".").join(requestBody)); String sql = "show child paths " + path; - Statement statement = StatementGenerator.createStatement(sql, ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + Statement statement = StatementGenerator.createStatement(sql, zoneId); Response response = authorizationHandler.checkAuthority(securityContext, statement); if (response != null) { diff --git a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/RestApiServiceImpl.java b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/RestApiServiceImpl.java index f6c42533a6231..c09fa0c45f423 100644 --- a/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/RestApiServiceImpl.java +++ b/external-service-impl/rest/src/main/java/org/apache/iotdb/rest/protocol/v2/impl/RestApiServiceImpl.java @@ -270,7 +270,8 @@ public Response executeNonQueryStatement(SQL sql, SecurityContext securityContex boolean finish = false; try { RequestValidationHandler.validateSQL(sql); - statement = StatementGenerator.createStatement(sql.getSql(), ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + statement = StatementGenerator.createStatement(sql.getSql(), zoneId); if (statement == null) { return Response.ok() .entity( @@ -310,9 +311,10 @@ public Response executeNonQueryStatement(SQL sql, SecurityContext securityContex return Response.ok().entity(ExceptionHandler.tryCatchException(e)).build(); } finally { long costTime = System.nanoTime() - startTime; - if (statement != null) + if (statement != null) { CommonUtils.addStatementExecutionLatency( OperationType.EXECUTE_NON_QUERY_PLAN, statement.getType().name(), costTime); + } if (queryId != null) { if (finish) { long executionTime = COORDINATOR.getTotalExecutionTime(queryId); @@ -332,7 +334,8 @@ public Response executeQueryStatement(SQL sql, SecurityContext securityContext) boolean finish = false; try { RequestValidationHandler.validateSQL(sql); - statement = StatementGenerator.createStatement(sql.getSql(), ZoneId.systemDefault()); + ZoneId zoneId = SESSION_MANAGER.getCurrSession().getZoneId(); + statement = StatementGenerator.createStatement(sql.getSql(), zoneId); if (statement == null) { return Response.ok() diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBRestServiceIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBRestServiceIT.java index 76a246149cd94..32a8cb861496e 100644 --- a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBRestServiceIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBRestServiceIT.java @@ -26,6 +26,7 @@ import org.apache.iotdb.itbase.category.LocalStandaloneIT; import org.apache.iotdb.itbase.category.RemoteIT; import org.apache.iotdb.itbase.env.BaseEnv; +import org.apache.iotdb.rpc.TSStatusCode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonObject; @@ -52,6 +53,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -474,6 +477,9 @@ public void insertAndQuery() { listUser(httpClient); selectCount(httpClient); selectLast(httpClient); + queryWithValidTimeZoneHeader(httpClient); + nonQueryWithValidTimeZoneHeader(httpClient); + queryWithInvalidTimeZoneHeader(httpClient); queryV2(httpClient); queryGroupByLevelV2(httpClient); @@ -494,6 +500,9 @@ public void insertAndQuery() { listUserV2(httpClient); selectCountV2(httpClient); selectLastV2(httpClient); + queryWithValidTimeZoneHeaderV2(httpClient); + nonQueryWithValidTimeZoneHeaderV2(httpClient); + queryWithInvalidTimeZoneHeaderV2(httpClient); perData(httpClient); List insertTablet_right_json_list = new ArrayList<>(); List insertTablet_error_json_list = new ArrayList<>(); @@ -2354,4 +2363,206 @@ public void queryDateAndBlobV2(CloseableHttpClient httpClient) { } } } + + public void queryWithValidTimeZoneHeader(CloseableHttpClient httpClient) { + CloseableHttpResponse response = null; + try { + HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v1/query"); + httpPost.setHeader("Time-Zone", "+05:00"); + String sql = + "{\"sql\":\"SELECT count(s3) FROM root.sg25 GROUP BY ([2026-03-28T00:00:00, 2026-03-29T00:00:00), 1d)\"}"; + httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset())); + response = httpClient.execute(httpPost); + assertEquals(200, response.getStatusLine().getStatusCode()); + String message = EntityUtils.toString(response.getEntity(), "utf-8"); + JsonObject result = JsonParser.parseString(message).getAsJsonObject(); + assertTrue(result.has("timestamps")); + assertTrue(result.getAsJsonArray("timestamps").size() > 0); + long expectedTimestamp = + ZonedDateTime.of(2026, 3, 28, 0, 0, 0, 0, ZoneId.of("+05:00")).toInstant().toEpochMilli(); + assertEquals(expectedTimestamp, result.getAsJsonArray("timestamps").get(0).getAsLong()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + } + + public void nonQueryWithValidTimeZoneHeader(CloseableHttpClient httpClient) { + CloseableHttpResponse response = null; + try { + HttpPost createPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v1/nonQuery"); + nonQuery( + httpClient, + "{\"sql\":\"CREATE TIMESERIES root.sg_tz.d1.s1 WITH DATATYPE=INT32\"}", + createPost); + + HttpPost insertPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v1/nonQuery"); + insertPost.setHeader("Time-Zone", "+05:00"); + nonQuery( + httpClient, + "{\"sql\":\"INSERT INTO root.sg_tz.d1(time, s1) VALUES (2026-03-28T00:00:00, 123)\"}", + insertPost); + + HttpPost queryPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v1/query"); + queryPost.setEntity( + new StringEntity("{\"sql\":\"SELECT s1 FROM root.sg_tz.d1\"}", StandardCharsets.UTF_8)); + response = httpClient.execute(queryPost); + String message = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + JsonObject result = JsonParser.parseString(message).getAsJsonObject(); + long expected = + ZonedDateTime.of(2026, 3, 28, 0, 0, 0, 0, ZoneId.of("+05:00")).toInstant().toEpochMilli(); + assertEquals(expected, result.getAsJsonArray("timestamps").get(0).getAsLong()); + + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + } + + public void queryWithValidTimeZoneHeaderV2(CloseableHttpClient httpClient) { + CloseableHttpResponse response = null; + try { + HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v2/query"); + httpPost.setHeader("Time-Zone", "+05:00"); + String sql = + "{\"sql\":\"SELECT count(s3) FROM root.sg25 GROUP BY ([2026-03-28T00:00:00, 2026-03-29T00:00:00), 1d)\"}"; + httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset())); + response = httpClient.execute(httpPost); + assertEquals(200, response.getStatusLine().getStatusCode()); + String message = EntityUtils.toString(response.getEntity(), "utf-8"); + JsonObject result = JsonParser.parseString(message).getAsJsonObject(); + assertTrue(result.has("timestamps")); + assertTrue(result.getAsJsonArray("timestamps").size() > 0); + long expectedTimestamp = + ZonedDateTime.of(2026, 3, 28, 0, 0, 0, 0, ZoneId.of("+05:00")).toInstant().toEpochMilli(); + assertEquals(expectedTimestamp, result.getAsJsonArray("timestamps").get(0).getAsLong()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + } + + public void nonQueryWithValidTimeZoneHeaderV2(CloseableHttpClient httpClient) { + CloseableHttpResponse response = null; + try { + HttpPost createPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v2/nonQuery"); + nonQuery( + httpClient, + "{\"sql\":\"CREATE TIMESERIES root.sg_tz.d2.s1 WITH DATATYPE=INT32\"}", + createPost); + + HttpPost insertPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v2/nonQuery"); + insertPost.setHeader("Time-Zone", "+05:00"); + nonQuery( + httpClient, + "{\"sql\":\"INSERT INTO root.sg_tz.d2(time, s1) VALUES (2026-03-28T00:00:00, 123)\"}", + insertPost); + + HttpPost queryPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v2/query"); + queryPost.setEntity( + new StringEntity("{\"sql\":\"SELECT s1 FROM root.sg_tz.d2\"}", StandardCharsets.UTF_8)); + response = httpClient.execute(queryPost); + String message = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + JsonObject result = JsonParser.parseString(message).getAsJsonObject(); + long expected = + ZonedDateTime.of(2026, 3, 28, 0, 0, 0, 0, ZoneId.of("+05:00")).toInstant().toEpochMilli(); + assertEquals(expected, result.getAsJsonArray("timestamps").get(0).getAsLong()); + + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + } + + public void queryWithInvalidTimeZoneHeader(CloseableHttpClient httpClient) { + CloseableHttpResponse response = null; + try { + HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v1/query"); + httpPost.setHeader("Time-Zone", "Invalid/Zone"); + String sql = "{\"sql\":\"SELECT s3 FROM root.sg25\"}"; + httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset())); + response = httpClient.execute(httpPost); + assertEquals(400, response.getStatusLine().getStatusCode()); + String message = EntityUtils.toString(response.getEntity(), "utf-8"); + JsonObject result = JsonParser.parseString(message).getAsJsonObject(); + assertEquals(TSStatusCode.ILLEGAL_PARAMETER.getStatusCode(), result.get("code").getAsInt()); + assertTrue(result.get("message").getAsString().contains("Invalid time zone")); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + } + + public void queryWithInvalidTimeZoneHeaderV2(CloseableHttpClient httpClient) { + CloseableHttpResponse response = null; + try { + HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v2/query"); + httpPost.setHeader("Time-Zone", "Invalid/Zone"); + String sql = "{\"sql\":\"SELECT s3 FROM root.sg25\"}"; + httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset())); + response = httpClient.execute(httpPost); + assertEquals(400, response.getStatusLine().getStatusCode()); + String message = EntityUtils.toString(response.getEntity(), "utf-8"); + JsonObject result = JsonParser.parseString(message).getAsJsonObject(); + assertEquals(TSStatusCode.ILLEGAL_PARAMETER.getStatusCode(), result.get("code").getAsInt()); + assertTrue(result.get("message").getAsString().contains("Invalid time zone")); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + } }