diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/Cursor.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/Cursor.java index 3480fcaeaf556..e28b6c5d5eb8c 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/Cursor.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/Cursor.java @@ -28,4 +28,6 @@ default int columnSize() { int batchSize(); void close() throws SQLException; + + List warnings(); } diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/DefaultCursor.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/DefaultCursor.java index 3dc792e17a5f7..664d169592df7 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/DefaultCursor.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/DefaultCursor.java @@ -18,15 +18,24 @@ class DefaultCursor implements Cursor { private final List columnInfos; private List> rows; + private final List warnings; private int row = -1; private String cursor; - DefaultCursor(JdbcHttpClient client, String cursor, List columnInfos, List> rows, RequestMeta meta) { + DefaultCursor( + JdbcHttpClient client, + String cursor, + List columnInfos, + List> rows, + RequestMeta meta, + List warnings + ) { this.client = client; this.meta = meta; this.cursor = cursor; this.columnInfos = columnInfos; this.rows = rows; + this.warnings = warnings; } @Override @@ -67,4 +76,8 @@ public void close() throws SQLException { client.queryClose(cursor); } } + + public List warnings() { + return warnings; + } } diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcDatabaseMetaData.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcDatabaseMetaData.java index 2284e6f2c9dc7..1c536ff7e195b 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcDatabaseMetaData.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcDatabaseMetaData.java @@ -18,6 +18,7 @@ import java.sql.RowIdLifetime; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static java.sql.JDBCType.BIGINT; @@ -1420,5 +1421,10 @@ public int batchSize() { public void close() throws SQLException { // this cursor doesn't hold any resource - no need to clean up } + + @Override + public List warnings() { + return Collections.emptyList(); + } } } diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcHttpClient.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcHttpClient.java index 834aee5389f23..d3cad1b4d4282 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcHttpClient.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcHttpClient.java @@ -75,8 +75,15 @@ Cursor query(String sql, List params, RequestMeta meta) thro conCfg.indexIncludeFrozen(), conCfg.binaryCommunication() ); - SqlQueryResponse response = httpClient.query(sqlRequest); - return new DefaultCursor(this, response.cursor(), toJdbcColumnInfo(response.columns()), response.rows(), meta); + Tuple> response = httpClient.query(sqlRequest); + return new DefaultCursor( + this, + response.v1().cursor(), + toJdbcColumnInfo(response.v1().columns()), + response.v1().rows(), + meta, + response.v2() + ); } /** @@ -91,7 +98,7 @@ Tuple>> nextPage(String cursor, RequestMeta meta) thro new RequestInfo(Mode.JDBC), conCfg.binaryCommunication() ); - SqlQueryResponse response = httpClient.query(sqlRequest); + SqlQueryResponse response = httpClient.query(sqlRequest).v1(); return new Tuple<>(response.cursor(), response.rows()); } diff --git a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcResultSet.java b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcResultSet.java index df081dcbf5812..137df114a8f1f 100644 --- a/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcResultSet.java +++ b/x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/JdbcResultSet.java @@ -585,7 +585,15 @@ public InputStream getBinaryStream(String columnLabel) throws SQLException { @Override public SQLWarning getWarnings() throws SQLException { checkOpen(); - return null; + SQLWarning sqlWarning = null; + for (String warning : cursor.warnings()) { + if (sqlWarning == null) { + sqlWarning = new SQLWarning(warning); + } else { + sqlWarning.setNextWarning(new SQLWarning(warning)); + } + } + return sqlWarning; } @Override diff --git a/x-pack/plugin/sql/qa/jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcWarningsTestCase.java b/x-pack/plugin/sql/qa/jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcWarningsTestCase.java index d3227b4692ea7..420ef147e0dce 100644 --- a/x-pack/plugin/sql/qa/jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcWarningsTestCase.java +++ b/x-pack/plugin/sql/qa/jdbc/src/main/java/org/elasticsearch/xpack/sql/qa/jdbc/JdbcWarningsTestCase.java @@ -7,19 +7,63 @@ package org.elasticsearch.xpack.sql.qa.jdbc; +import org.elasticsearch.Version; +import org.junit.Before; + +import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; import java.sql.Statement; +import java.util.Properties; + +import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.JDBC_DRIVER_VERSION; +import static org.hamcrest.Matchers.containsString; public abstract class JdbcWarningsTestCase extends JdbcIntegrationTestCase { - public void testDeprecationWarningsDoNotReachJdbcDriver() throws Exception { + @Before + public void setupData() throws IOException { index("test_data", b -> b.field("foo", 1)); + } + public void testNoWarnings() throws SQLException { try (Connection connection = esJdbc(); Statement statement = connection.createStatement()) { - ResultSet rs = statement.executeQuery("SELECT * FROM FROZEN \"test_*\""); + ResultSet rs = statement.executeQuery("SELECT * FROM test_data"); assertNull(rs.getWarnings()); } } + private static Version WARNING_HANDLING_ADDED_VERSION = Version.V_8_2_0; + + public void testSingleDeprecationWarning() throws SQLException { + assumeTrue("Driver does not yet handle deprecation warnings", JDBC_DRIVER_VERSION.onOrAfter(WARNING_HANDLING_ADDED_VERSION)); + + try (Connection connection = esJdbc(); Statement statement = connection.createStatement()) { + ResultSet rs = statement.executeQuery("SELECT * FROM FROZEN test_data"); + SQLWarning warning = rs.getWarnings(); + assertThat(warning.getMessage(), containsString("[FROZEN] syntax is deprecated because frozen indices have been deprecated.")); + assertNull(warning.getNextWarning()); + } + } + + public void testMultipleDeprecationWarnings() throws SQLException { + assumeTrue("Driver does not yet handle deprecation warnings", JDBC_DRIVER_VERSION.onOrAfter(WARNING_HANDLING_ADDED_VERSION)); + + Properties props = connectionProperties(); + props.setProperty("index.include.frozen", "true"); + + try (Connection connection = esJdbc(props); Statement statement = connection.createStatement()) { + ResultSet rs = statement.executeQuery("SELECT * FROM FROZEN test_data"); + SQLWarning warning = rs.getWarnings(); + assertThat(warning.getMessage(), containsString("[FROZEN] syntax is deprecated because frozen indices have been deprecated.")); + assertThat( + warning.getNextWarning().getMessage(), + containsString("[index_include_frozen] parameter is deprecated because frozen indices have been deprecated.") + ); + assertNull(warning.getNextWarning().getNextWarning()); + } + } + } diff --git a/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/HttpClient.java b/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/HttpClient.java index d3784b70a00e2..2dd2e9c7661f1 100644 --- a/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/HttpClient.java +++ b/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/HttpClient.java @@ -33,7 +33,9 @@ import java.io.InputStream; import java.security.PrivilegedAction; import java.sql.SQLException; -import java.util.function.Function; +import java.util.Collections; +import java.util.List; +import java.util.Map; import static java.util.Collections.emptyList; @@ -82,10 +84,10 @@ public SqlQueryResponse basicQuery(String query, int fetchSize, boolean fieldMul false, cfg.binaryCommunication() ); - return query(sqlRequest); + return query(sqlRequest).v1(); } - public SqlQueryResponse query(SqlQueryRequest sqlRequest) throws SQLException { + public Tuple> query(SqlQueryRequest sqlRequest) throws SQLException { return post(CoreProtocol.SQL_QUERY_REST_ENDPOINT, sqlRequest, Payloads::parseQueryResponse); } @@ -98,28 +100,28 @@ public SqlQueryResponse nextPage(String cursor) throws SQLException { new RequestInfo(Mode.CLI), cfg.binaryCommunication() ); - return post(CoreProtocol.SQL_QUERY_REST_ENDPOINT, sqlRequest, Payloads::parseQueryResponse); + return post(CoreProtocol.SQL_QUERY_REST_ENDPOINT, sqlRequest, Payloads::parseQueryResponse).v1(); } public boolean queryClose(String cursor, Mode mode) throws SQLException { - SqlClearCursorResponse response = post( + Tuple> response = post( CoreProtocol.CLEAR_CURSOR_REST_ENDPOINT, new SqlClearCursorRequest(cursor, new RequestInfo(mode)), Payloads::parseClearCursorResponse ); - return response.isSucceeded(); + return response.v1().isSucceeded(); } @SuppressWarnings({ "removal" }) - private Response post( + private Tuple> post( String path, Request request, CheckedFunction responseParser ) throws SQLException { byte[] requestBytes = toContent(request); String query = "error_trace"; - Tuple response = java.security.AccessController.doPrivileged( - (PrivilegedAction>>) () -> JreHttpUrlConnection.http( + Tuple>, byte[]> response = java.security.AccessController.doPrivileged( + (PrivilegedAction>, byte[]>>>) () -> JreHttpUrlConnection.http( path, query, cfg, @@ -131,7 +133,11 @@ private Response post( ) ) ).getResponseOrThrowException(); - return fromContent(response.v1(), response.v2(), responseParser); + List warnings = response.v1().get("Warning"); + return new Tuple<>( + fromContent(response.v1(), response.v2(), responseParser), + warnings == null ? Collections.emptyList() : warnings + ); } @SuppressWarnings({ "removal" }) @@ -162,8 +168,8 @@ private boolean head(String path, long timeoutInMs) throws SQLException { @SuppressWarnings({ "removal" }) private Response get(String path, CheckedFunction responseParser) throws SQLException { - Tuple response = java.security.AccessController.doPrivileged( - (PrivilegedAction>>) () -> JreHttpUrlConnection.http( + Tuple>, byte[]> response = java.security.AccessController.doPrivileged( + (PrivilegedAction>, byte[]>>>) () -> JreHttpUrlConnection.http( path, "error_trace", cfg, @@ -184,31 +190,30 @@ private byte[] toContent(Request request) { } } - private Tuple readFrom(InputStream inputStream, Function headers) { - String contentType = headers.apply("Content-Type"); - ContentType type = ContentFactory.parseMediaType(contentType); - if (type == null) { - throw new IllegalStateException("Unsupported Content-Type: " + contentType); - } + private Tuple>, byte[]> readFrom(InputStream inputStream, Map> headers) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { Streams.copy(inputStream, out); } catch (Exception ex) { throw new ClientException("Cannot deserialize response", ex); } - return new Tuple<>(type, out.toByteArray()); + return new Tuple<>(headers, out.toByteArray()); } private Response fromContent( - ContentType contentType, + Map> headers, byte[] bytesReference, CheckedFunction responseParser ) { - try ( - InputStream stream = new ByteArrayInputStream(bytesReference); - JsonParser parser = ContentFactory.parser(contentType, stream) - ) { + List contentTypeHeaders = headers.get("content-type"); + String contentType = contentTypeHeaders == null || contentTypeHeaders.isEmpty() ? null : contentTypeHeaders.get(0); + ContentType type = ContentFactory.parseMediaType(contentType); + if (type == null) { + throw new IllegalStateException("Unsupported Content-Type: " + contentType); + } + + try (InputStream stream = new ByteArrayInputStream(bytesReference); JsonParser parser = ContentFactory.parser(type, stream)) { return responseParser.apply(parser); } catch (Exception ex) { throw new ClientException("Cannot parse response", ex); diff --git a/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/JreHttpUrlConnection.java b/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/JreHttpUrlConnection.java index 5cbb6061db08b..8ff6b08aa811d 100644 --- a/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/JreHttpUrlConnection.java +++ b/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/JreHttpUrlConnection.java @@ -31,6 +31,8 @@ import java.sql.SQLSyntaxErrorException; import java.sql.SQLTimeoutException; import java.util.Base64; +import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.zip.GZIPInputStream; @@ -150,7 +152,7 @@ public boolean head() throws ClientException { public ResponseOrException request( CheckedConsumer doc, - CheckedBiFunction, R, IOException> parser, + CheckedBiFunction>, R, IOException> parser, String requestMethod ) throws ClientException { return request(doc, parser, requestMethod, "application/json"); @@ -158,7 +160,7 @@ public ResponseOrException request( public ResponseOrException request( CheckedConsumer doc, - CheckedBiFunction, R, IOException> parser, + CheckedBiFunction>, R, IOException> parser, String requestMethod, String contentTypeHeader ) throws ClientException { @@ -174,7 +176,7 @@ public ResponseOrException request( } if (shouldParseBody(con.getResponseCode())) { try (InputStream stream = getStream(con, con.getInputStream())) { - return new ResponseOrException<>(parser.apply(new BufferedInputStream(stream), con::getHeaderField)); + return new ResponseOrException<>(parser.apply(new BufferedInputStream(stream), con.getHeaderFields())); } } return parserError();