diff --git a/README.md b/README.md index d5f4d2a..be2bb4e 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ To setup a connection, the driver requires a JDBC connection URL. The connection | ------------- |-------------| -----|---------| | user | Connection username. mandatory if `auth` property selects a authentication scheme that mandates a username value | any string | `null` | | password | Connection password. mandatory if `auth` property selects a authentication scheme that mandates a password value | any string | `null` | + | fetchSize | Cursor page size | positive integer value. Max value is limited by `index.max_result_window` Elasticsearch setting | `0` (for non-paginated response) | | logOutput | location where driver logs should be emitted | a valid file path | `null` (logs are disabled) | | logLevel | severity level for which driver logs should be emitted | in order from highest(least logging) to lowest(most logging): OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL | OFF (logs are disabled) | | auth | authentication mechanism to use | `NONE` (no auth), `BASIC` (HTTP Basic), `AWS_SIGV4` (AWS SIGV4) | `basic` if username and/or password is specified, `NONE` otherwise | diff --git a/build.gradle b/build.gradle index 8ab915c..a042a70 100644 --- a/build.gradle +++ b/build.gradle @@ -184,3 +184,25 @@ signing { sign publishing.publications.shadow } +jacoco { + toolVersion = "0.8.3" +} + +jacocoTestReport { + reports { + html.enabled true + } +} +test.finalizedBy(project.tasks.jacocoTestReport) + +jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = 0.4 + } + } + } +} + +check.dependsOn jacocoTestCoverageVerification diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ConnectionImpl.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ConnectionImpl.java index 6d4885c..7a5fce4 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ConnectionImpl.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ConnectionImpl.java @@ -60,6 +60,7 @@ public class ConnectionImpl implements ElasticsearchConnection, JdbcWrapper, Log private String url; private String user; private Logger log; + private int fetchSize; private boolean open = false; private Transport transport; private Protocol protocol; @@ -74,6 +75,7 @@ public ConnectionImpl(ConnectionConfig connectionConfig, TransportFactory transp this.log = log; this.url = connectionConfig.getUrl(); this.user = connectionConfig.getUser(); + this.fetchSize = connectionConfig.getFetchSize(); try { this.transport = transportFactory.getTransport(connectionConfig, log, getUserAgent()); @@ -101,6 +103,10 @@ public String getUser() { return user; } + public int getFetchSize() { + return fetchSize; + } + @Override public Statement createStatement() throws SQLException { log.debug(() -> logEntry("createStatement()")); diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/PreparedStatementImpl.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/PreparedStatementImpl.java index 15410b3..ae83d11 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/PreparedStatementImpl.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/PreparedStatementImpl.java @@ -75,14 +75,14 @@ public PreparedStatementImpl(ConnectionImpl connection, String sql, Logger log) public ResultSet executeQuery() throws SQLException { log.debug(() -> logEntry("executeQuery()")); checkOpen(); - ResultSet rs = executeQueryX(); + ResultSet rs = executeQueryX(getFetchSize()); log.debug(() -> logExit("executeQuery", rs)); return rs; } - protected ResultSet executeQueryX() throws SQLException { + protected ResultSet executeQueryX(int fetchSize) throws SQLException { checkParamsFilled(); - JdbcQueryRequest jdbcQueryRequest = new JdbcQueryRequest(sql); + JdbcQueryRequest jdbcQueryRequest = new JdbcQueryRequest(sql, fetchSize); jdbcQueryRequest.setParameters(Arrays.asList(parameters)); return executeQueryRequest(jdbcQueryRequest); } @@ -293,7 +293,7 @@ private int javaToSqlType(Object x) throws SQLException { public boolean execute() throws SQLException { log.debug(() -> logEntry("execute()")); checkOpen(); - executeQueryX(); + executeQueryX(getFetchSize()); log.debug(() -> logExit("execute", true)); return true; } diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetImpl.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetImpl.java index 097c641..14a4ae8 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetImpl.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetImpl.java @@ -26,10 +26,17 @@ import com.amazon.opendistroforelasticsearch.jdbc.protocol.ColumnDescriptor; import com.amazon.opendistroforelasticsearch.jdbc.internal.JdbcWrapper; import com.amazon.opendistroforelasticsearch.jdbc.protocol.QueryResponse; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.exceptions.InternalServerErrorException; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.exceptions.ResponseException; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.http.JdbcCursorQueryRequest; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.http.JsonCursorHttpProtocol; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.http.JsonCursorHttpProtocolFactory; +import com.amazon.opendistroforelasticsearch.jdbc.transport.http.HttpTransport; import com.amazon.opendistroforelasticsearch.jdbc.types.TypeConverter; import com.amazon.opendistroforelasticsearch.jdbc.types.TypeConverters; import com.amazon.opendistroforelasticsearch.jdbc.types.UnrecognizedElasticsearchTypeException; +import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; @@ -71,6 +78,7 @@ public class ResultSetImpl implements ResultSet, JdbcWrapper, LoggingSource { private StatementImpl statement; protected Cursor cursor; + private String cursorId; private boolean open = false; private boolean wasNull = false; private boolean afterLast = false; @@ -78,11 +86,16 @@ public class ResultSetImpl implements ResultSet, JdbcWrapper, LoggingSource { private Logger log; public ResultSetImpl(StatementImpl statement, QueryResponse queryResponse, Logger log) throws SQLException { - this(statement, queryResponse.getColumnDescriptors(), queryResponse.getDatarows(), log); + this(statement, queryResponse.getColumnDescriptors(), queryResponse.getDatarows(), queryResponse.getCursor(), log); } public ResultSetImpl(StatementImpl statement, List columnDescriptors, List> dataRows, Logger log) throws SQLException { + this(statement, columnDescriptors, dataRows, null, log); + } + + public ResultSetImpl(StatementImpl statement, List columnDescriptors, + List> dataRows, String cursorId, Logger log) throws SQLException { this.statement = statement; this.log = log; @@ -93,12 +106,10 @@ public ResultSetImpl(StatementImpl statement, List c .map(ColumnMetaData::new) .collect(Collectors.toList())); - List rows = dataRows - .parallelStream() - .map(Row::new) - .collect(Collectors.toList()); + List rows = getRowsFromDataRows(dataRows); this.cursor = new Cursor(schema, rows); + this.cursorId = cursorId; this.open = true; } catch (UnrecognizedElasticsearchTypeException ex) { @@ -112,15 +123,63 @@ public boolean next() throws SQLException { log.debug(() -> logEntry("next()")); checkOpen(); boolean next = cursor.next(); + + if (!next && this.cursorId != null) { + log.debug(() -> logEntry("buildNextPageFromCursorId()")); + buildNextPageFromCursorId(); + log.debug(() -> logExit("buildNextPageFromCursorId()")); + next = cursor.next(); + } + if (next) { beforeFirst = false; } else { afterLast = true; } - log.debug(() -> logExit("next", next)); + boolean finalNext = next; + log.debug(() -> logExit("next", finalNext)); return next; } + /** + * TODO: Refactor as suggested https://github.com/opendistro-for-elasticsearch/sql-jdbc/pull/76#discussion_r421571383 + * + * This method has side effects. It creates a new Cursor to hold rows from new pages. + * Ideally fetching next set of rows using cursorId should be delegated to Cursor. + * In addition, the cursor should be final. + * + **/ + protected void buildNextPageFromCursorId() throws SQLException { + try { + JdbcCursorQueryRequest jdbcCursorQueryRequest = new JdbcCursorQueryRequest(this.cursorId); + JsonCursorHttpProtocolFactory protocolFactory = JsonCursorHttpProtocolFactory.INSTANCE; + ConnectionImpl connection = (ConnectionImpl) statement.getConnection(); + + JsonCursorHttpProtocol protocol = protocolFactory.getProtocol(null, (HttpTransport) connection.getTransport()); + QueryResponse queryResponse = protocol.execute(jdbcCursorQueryRequest); + + if (queryResponse.getError() != null) { + throw new InternalServerErrorException( + queryResponse.getError().getReason(), + queryResponse.getError().getType(), + queryResponse.getError().getDetails()); + } + + cursor = new Cursor(cursor.getSchema(), getRowsFromDataRows(queryResponse.getDatarows())); + cursorId = queryResponse.getCursor(); + + } catch (ResponseException | IOException ex) { + logAndThrowSQLException(log, new SQLException("Error executing cursor query", ex)); + } + } + + private List getRowsFromDataRows(List> dataRows) { + return dataRows + .parallelStream() + .map(Row::new) + .collect(Collectors.toList()); + } + @Override public void close() throws SQLException { log.debug(() -> logEntry("close()")); diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/StatementImpl.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/StatementImpl.java index 2d39cde..dae35b7 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/StatementImpl.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/StatementImpl.java @@ -38,6 +38,7 @@ public class StatementImpl implements Statement, JdbcWrapper, LoggingSource { protected ConnectionImpl connection; protected boolean open = false; + protected int fetchSize; protected ResultSetImpl resultSet; protected Logger log; private boolean closeOnCompletion; @@ -45,19 +46,20 @@ public class StatementImpl implements Statement, JdbcWrapper, LoggingSource { public StatementImpl(ConnectionImpl connection, Logger log) { this.connection = connection; this.open = true; + this.fetchSize = connection.getFetchSize(); this.log = log; } @Override public ResultSet executeQuery(String sql) throws SQLException { log.debug(()-> logEntry("executeQuery (%s)", sql)); - ResultSet rs = executeQueryX(sql); + ResultSet rs = executeQueryX(sql, fetchSize); log.debug(()-> logExit("executeQuery", rs)); return rs; } - protected ResultSet executeQueryX(String sql) throws SQLException { - JdbcQueryRequest jdbcQueryRequest = new JdbcQueryRequest(sql); + protected ResultSet executeQueryX(String sql, int fetchSize) throws SQLException { + JdbcQueryRequest jdbcQueryRequest = new JdbcQueryRequest(sql, fetchSize); return executeQueryRequest(jdbcQueryRequest); } @@ -167,7 +169,7 @@ public void setCursorName(String name) throws SQLException { public boolean execute(String sql) throws SQLException { log.debug(()->logEntry("execute (%s)", sql)); checkOpen(); - executeQueryX(sql); + executeQueryX(sql, fetchSize); log.debug(() -> logExit("execute", true)); return true; } @@ -205,12 +207,12 @@ public int getFetchDirection() throws SQLException { @Override public void setFetchSize(int rows) throws SQLException { - + fetchSize = rows; } @Override public int getFetchSize() throws SQLException { - return 0; + return fetchSize; } @Override @@ -275,7 +277,7 @@ public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { if (autoGeneratedKeys != Statement.NO_GENERATED_KEYS) { throw new SQLNonTransientException("Auto generated keys are not supported"); } - executeQueryX(sql); + executeQueryX(sql, fetchSize); log.debug(() -> logExit("execute", true)); return true; } diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfig.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfig.java index 78b5489..ecd8133 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfig.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfig.java @@ -34,6 +34,7 @@ public class ConnectionConfig { private String url; private String host; private int port; + private int fetchSize; private String path; private boolean useSSL; private int loginTimeout; @@ -60,6 +61,7 @@ private ConnectionConfig(Builder builder) { this.url = builder.getUrl(); this.host = builder.getHostProperty().getValue(); this.port = builder.getPortProperty().getValue(); + this.fetchSize = builder.getFetchSizeProperty().getValue(); this.path = builder.getPathProperty().getValue(); this.useSSL = builder.getUseSSLProperty().getValue(); @@ -106,6 +108,10 @@ public int getPort() { return port; } + public int getFetchSize() { + return fetchSize; + } + public String getPath() { return path; } @@ -192,6 +198,7 @@ public String toString() { "url='" + url + '\'' + ", host='" + host + '\'' + ", port=" + port + + ", fetchSize=" + fetchSize + ", path='" + path + '\'' + ", useSSL=" + useSSL + ", loginTimeout=" + loginTimeout + @@ -223,6 +230,7 @@ public static class Builder { private HostConnectionProperty hostProperty = new HostConnectionProperty(); private PortConnectionProperty portProperty = new PortConnectionProperty(); + private FetchSizeProperty fetchSizeProperty = new FetchSizeProperty(); private LoginTimeoutConnectionProperty loginTimeoutProperty = new LoginTimeoutConnectionProperty(); private UseSSLConnectionProperty useSSLProperty = new UseSSLConnectionProperty(); private PathConnectionProperty pathProperty = new PathConnectionProperty(); @@ -261,6 +269,7 @@ public static class Builder { ConnectionProperty[] connectionProperties = new ConnectionProperty[]{ hostProperty, portProperty, + fetchSizeProperty, loginTimeoutProperty, useSSLProperty, pathProperty, @@ -302,6 +311,10 @@ public PortConnectionProperty getPortProperty() { return portProperty; } + public FetchSizeProperty getFetchSizeProperty() { + return fetchSizeProperty; + } + public LoginTimeoutConnectionProperty getLoginTimeoutProperty() { return loginTimeoutProperty; } @@ -519,6 +532,11 @@ private void validateConfig() throws ConnectionPropertyException { // change the default port to use to 443 portProperty.setRawValue(443); } + + if (fetchSizeProperty.getValue() < 0) { + throw new ConnectionPropertyException(fetchSizeProperty.getKey(), + "Cursor fetch size value should be greater or equal to zero"); + } } /** diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/FetchSizeProperty.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/FetchSizeProperty.java new file mode 100644 index 0000000..baf75d8 --- /dev/null +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/config/FetchSizeProperty.java @@ -0,0 +1,10 @@ +package com.amazon.opendistroforelasticsearch.jdbc.config; + +public class FetchSizeProperty extends IntConnectionProperty { + + public static final String KEY = "fetchSize"; + + public FetchSizeProperty() { + super(KEY); + } +} diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/JdbcQueryRequest.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/JdbcQueryRequest.java index 2cb2b37..cdd3a0f 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/JdbcQueryRequest.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/JdbcQueryRequest.java @@ -21,14 +21,20 @@ public class JdbcQueryRequest implements QueryRequest { - String statement; - + private String statement; + private int fetchSize; List parameters; public JdbcQueryRequest(String sql) { this.statement = sql; } + public JdbcQueryRequest(String sql, int fetchSize) { + this.statement = sql; + this.fetchSize = fetchSize; + } + + @Override public boolean equals(Object o) { if (this == o) return true; @@ -59,13 +65,14 @@ public void setParameters(List parameters) { @Override public int getFetchSize() { - return 0; + return fetchSize; } @Override public String toString() { return "JdbcQueryRequest{" + "statement='" + statement + '\'' + + ", fetchSize='" + fetchSize + '\'' + ", parameters=" + parameters + '}'; } diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/QueryResponse.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/QueryResponse.java index dc16a76..c39e36c 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/QueryResponse.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/QueryResponse.java @@ -30,5 +30,7 @@ public interface QueryResponse { int getStatus(); + String getCursor(); + RequestError getError(); } diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JdbcCursorQueryRequest.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JdbcCursorQueryRequest.java new file mode 100644 index 0000000..048c4a3 --- /dev/null +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JdbcCursorQueryRequest.java @@ -0,0 +1,75 @@ +/* + * Copyright <2019> Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.jdbc.protocol.http; + +import com.amazon.opendistroforelasticsearch.jdbc.protocol.JdbcQueryParam;; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.QueryRequest; + + +import java.util.List; +import java.util.Objects; + +/** + * Bean to encapsulate cursor ID + * + * @author abbas hussain + * @since 07.05.20 + **/ +public class JdbcCursorQueryRequest implements QueryRequest { + + String cursor; + + public JdbcCursorQueryRequest(String cursor) { + this.cursor = cursor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof JdbcCursorQueryRequest)) return false; + JdbcCursorQueryRequest that = (JdbcCursorQueryRequest) o; + return Objects.equals(cursor, that.cursor) && + Objects.equals(getParameters(), that.getParameters()); + } + + @Override + public int hashCode() { + return Objects.hash(cursor, getParameters()); + } + + @Override + public String getQuery() { + return cursor; + } + + @Override + public List getParameters() { + return null; + } + + @Override + public int getFetchSize() { + return 0; + } + + @Override + public String toString() { + return "JdbcQueryRequest{" + + "cursor='" + cursor + '\'' + + '}'; + } +} diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorHttpProtocol.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorHttpProtocol.java new file mode 100644 index 0000000..882528f --- /dev/null +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorHttpProtocol.java @@ -0,0 +1,67 @@ +/* + * Copyright <2019> Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.jdbc.protocol.http; + +import com.amazon.opendistroforelasticsearch.jdbc.protocol.QueryRequest; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.QueryResponse; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.exceptions.ResponseException; +import com.amazon.opendistroforelasticsearch.jdbc.transport.http.HttpTransport; +import org.apache.http.client.methods.CloseableHttpResponse; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Http protocol for cursor request and response + * + * @author abbas hussain + * @since 07.05.20 + **/ +public class JsonCursorHttpProtocol extends JsonHttpProtocol { + + public JsonCursorHttpProtocol(HttpTransport transport) { + this(transport, DEFAULT_SQL_CONTEXT_PATH); + } + + public JsonCursorHttpProtocol(HttpTransport transport, String sqlContextPath) { + super(transport, sqlContextPath); + } + + @Override + public QueryResponse execute(QueryRequest request) throws ResponseException, IOException { + try (CloseableHttpResponse response = getTransport().doPost( + getSqlContextPath(), + defaultJsonHeaders, + defaultJdbcParams, + buildQueryRequestBody(request), 0)) { + + return getJsonHttpResponseHandler().handleResponse(response, this::processQueryResponse); + + } + } + + private String buildQueryRequestBody(QueryRequest queryRequest) throws IOException { + JsonCursorQueryRequest jsonQueryRequest = new JsonCursorQueryRequest(queryRequest); + String requestBody = mapper.writeValueAsString(jsonQueryRequest); + return requestBody; + } + + private JsonQueryResponse processQueryResponse(InputStream contentStream) throws IOException { + return mapper.readValue(contentStream, JsonQueryResponse.class); + } + +} diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorHttpProtocolFactory.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorHttpProtocolFactory.java new file mode 100644 index 0000000..5c2cf77 --- /dev/null +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorHttpProtocolFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright <2019> Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.jdbc.protocol.http; + +import com.amazon.opendistroforelasticsearch.jdbc.config.ConnectionConfig; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.ProtocolFactory; +import com.amazon.opendistroforelasticsearch.jdbc.transport.http.HttpTransport; + +/** + * Factory to create JsonCursorHttpProtocol objects + * + * @author abbas hussain + * @since 07.05.20 + */ +public class JsonCursorHttpProtocolFactory implements ProtocolFactory { + + public static JsonCursorHttpProtocolFactory INSTANCE = new JsonCursorHttpProtocolFactory(); + + private JsonCursorHttpProtocolFactory() { + + } + + @Override + public JsonCursorHttpProtocol getProtocol(ConnectionConfig connectionConfig, HttpTransport transport) { + return new JsonCursorHttpProtocol(transport); + } +} diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorQueryRequest.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorQueryRequest.java new file mode 100644 index 0000000..b2b3c06 --- /dev/null +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorQueryRequest.java @@ -0,0 +1,57 @@ +/* + * Copyright <2019> Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.jdbc.protocol.http; + +import com.amazon.opendistroforelasticsearch.jdbc.protocol.Parameter; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.QueryRequest; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * Definition of json cursor request + * + * @author abbas hussain + * @since 07.05.20 + **/ +public class JsonCursorQueryRequest implements QueryRequest { + + private final String cursor; + + public JsonCursorQueryRequest(QueryRequest queryRequest) { + this.cursor = queryRequest.getQuery(); + } + + @JsonProperty("cursor") + @Override + public String getQuery() { + return cursor; + } + + @JsonIgnore + @Override + public List getParameters() { + return null; + } + + @JsonIgnore + @Override + public int getFetchSize() { + return 0; + } +} diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonHttpProtocol.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonHttpProtocol.java index 5ad6dba..a445d58 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonHttpProtocol.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonHttpProtocol.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Map; public class JsonHttpProtocol implements Protocol { @@ -42,11 +43,11 @@ public class JsonHttpProtocol implements Protocol { private static final Header acceptJson = new BasicHeader(HttpHeaders.ACCEPT, "application/json"); private static final Header contentTypeJson = new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"); private static final HttpParam requestJdbcFormatParam = new HttpParam("format", "jdbc"); - private static final Header[] defaultJsonHeaders = new Header[]{acceptJson, contentTypeJson}; + protected static final Header[] defaultJsonHeaders = new Header[]{acceptJson, contentTypeJson}; private static final Header[] defaultEmptyRequestBodyJsonHeaders = new Header[]{acceptJson}; - private static final HttpParam[] defaultJdbcParams = new HttpParam[]{requestJdbcFormatParam}; + protected static final HttpParam[] defaultJdbcParams = new HttpParam[]{requestJdbcFormatParam}; - private static final ObjectMapper mapper = new ObjectMapper(); + protected static final ObjectMapper mapper = new ObjectMapper(); private String sqlContextPath; private HttpTransport transport; private JsonHttpResponseHandler jsonHttpResponseHandler; @@ -65,6 +66,14 @@ public String getSqlContextPath() { return sqlContextPath; } + public HttpTransport getTransport() { + return this.transport; + } + + public JsonHttpResponseHandler getJsonHttpResponseHandler() { + return this.jsonHttpResponseHandler; + } + @Override public ConnectionResponse connect(int timeout) throws ResponseException, IOException { try (CloseableHttpResponse response = transport.doGet( @@ -91,11 +100,8 @@ public QueryResponse execute(QueryRequest request) throws ResponseException, IOE } private String buildQueryRequestBody(QueryRequest queryRequest) throws IOException { - String requestBody = null; - JsonQueryRequest jsonQueryRequest = new JsonQueryRequest(queryRequest); - requestBody = mapper.writeValueAsString(jsonQueryRequest); - + String requestBody = mapper.writeValueAsString(jsonQueryRequest); return requestBody; } diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonHttpProtocolFactory.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonHttpProtocolFactory.java index 4d69073..255251e 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonHttpProtocolFactory.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonHttpProtocolFactory.java @@ -20,6 +20,7 @@ import com.amazon.opendistroforelasticsearch.jdbc.protocol.ProtocolFactory; import com.amazon.opendistroforelasticsearch.jdbc.transport.http.HttpTransport; + public class JsonHttpProtocolFactory implements ProtocolFactory { public static JsonHttpProtocolFactory INSTANCE = new JsonHttpProtocolFactory(); diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonQueryRequest.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonQueryRequest.java index e49f2c6..b79eb5c 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonQueryRequest.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonQueryRequest.java @@ -18,20 +18,23 @@ import com.amazon.opendistroforelasticsearch.jdbc.protocol.Parameter; import com.amazon.opendistroforelasticsearch.jdbc.protocol.QueryRequest; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; public class JsonQueryRequest implements QueryRequest { private String query; + private int fetchSize; private List parameters; public JsonQueryRequest(QueryRequest queryRequest) { this.query = queryRequest.getQuery(); this.parameters = queryRequest.getParameters(); + this.fetchSize = queryRequest.getFetchSize(); + } @Override @@ -45,9 +48,9 @@ public List getParameters() { return parameters; } - @JsonIgnore + @JsonProperty("fetch_size") @Override public int getFetchSize() { - return 0; + return fetchSize; } } diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonQueryResponse.java b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonQueryResponse.java index 6e8eaa7..f12823c 100644 --- a/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonQueryResponse.java +++ b/src/main/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonQueryResponse.java @@ -41,6 +41,8 @@ public class JsonQueryResponse implements QueryResponse { private int status; + private String cursor; + private JsonRequestError error; @Override @@ -73,6 +75,10 @@ public void setStatus(int status) { this.status = status; } + public void setCursor(String cursor) { + this.cursor = cursor; + } + public void setError(JsonRequestError error) { this.error = error; } @@ -92,6 +98,11 @@ public int getStatus() { return status; } + @Override + public String getCursor() { + return cursor; + } + @Override public RequestError getError() { return error; @@ -105,6 +116,7 @@ public boolean equals(Object o) { return getSize() == response.getSize() && getTotal() == response.getTotal() && getStatus() == response.getStatus() && + getCursor() == response.getCursor() && Objects.equals(schema, response.schema) && Objects.equals(getDatarows(), response.getDatarows()) && Objects.equals(getError(), response.getError()); @@ -112,13 +124,14 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(schema, getDatarows(), getSize(), getTotal(), getStatus(), getError()); + return Objects.hash(schema, getDatarows(), getSize(), getTotal(), getStatus(), getCursor(), getError()); } @Override public String toString() { return "JsonQueryResponse{" + "schema=" + schema + + "cursor=" + cursor + ", datarows=" + datarows + ", size=" + size + ", total=" + total + diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/PreparedStatementTests.java b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/PreparedStatementTests.java index a2e94d9..eacec90 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/PreparedStatementTests.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/PreparedStatementTests.java @@ -25,9 +25,11 @@ import java.sql.SQLNonTransientException; import java.util.Arrays; import java.util.Objects; +import java.util.Properties; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -139,4 +141,53 @@ private Connection getMockConnection() throws IOException, ResponseException, SQ return con; } + @Test + void testEffectiveFetchSizeOnPreparedStatement() throws ResponseException, IOException, SQLException { + + TransportFactory tf = mock(TransportFactory.class); + ProtocolFactory pf = mock(ProtocolFactory.class); + Protocol mockProtocol = mock(Protocol.class); + + when(mockProtocol.connect(anyInt())).thenReturn(mock(ConnectionResponse.class)); + + when(tf.getTransport(any(), any(), any())) + .thenReturn(mock(Transport.class)); + + when(pf.getProtocol(any(ConnectionConfig.class), any(Transport.class))) + .thenReturn(mockProtocol); + + when(mockProtocol.execute(any(QueryRequest.class))) + .thenReturn(mock(QueryResponse.class)); + + String url = "jdbc:elasticsearch://localhost:9200?fetchSize=400"; + + ConnectionConfig connectionConfig = ConnectionConfig.builder().setUrl(url).build(); + Connection con = new ConnectionImpl(connectionConfig, tf, pf, NoOpLogger.INSTANCE); + PreparedStatement st = con.prepareStatement(sql); + assertEquals(st.getFetchSize(), 400); + st.close(); + con.close(); + + // Properties override connection string fetchSize + Properties properties = new Properties(); + properties.setProperty("fetchSize", "5000"); + connectionConfig = ConnectionConfig.builder().setUrl(url).setProperties(properties).build(); + con = new ConnectionImpl(connectionConfig, tf, pf, NoOpLogger.INSTANCE); + st = con.prepareStatement(sql); + assertEquals(st.getFetchSize(), 5000); + st.close(); + con.close(); + + + // setFetchSize overrides fetchSize set anywhere + connectionConfig = ConnectionConfig.builder().setUrl(url).setProperties(properties).build(); + con = new ConnectionImpl(connectionConfig, tf, pf, NoOpLogger.INSTANCE); + st = con.prepareStatement(sql); + st.setFetchSize(200); + assertEquals(st.getFetchSize(), 200); + st.close(); + con.close(); + + } + } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetTests.java b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetTests.java index 95c3fd4..db1f61b 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetTests.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/ResultSetTests.java @@ -19,6 +19,9 @@ import com.amazon.opendistroforelasticsearch.jdbc.internal.exceptions.ObjectClosedException; import com.amazon.opendistroforelasticsearch.jdbc.logging.NoOpLogger; import com.amazon.opendistroforelasticsearch.jdbc.protocol.QueryResponse; +import com.amazon.opendistroforelasticsearch.jdbc.protocol.http.JsonHttpProtocol; +import com.amazon.opendistroforelasticsearch.jdbc.test.TestResources; +import com.amazon.opendistroforelasticsearch.jdbc.test.mocks.MockES; import com.amazon.opendistroforelasticsearch.jdbc.types.ElasticsearchType; import com.amazon.opendistroforelasticsearch.jdbc.test.PerTestWireMockServerExtension; import com.amazon.opendistroforelasticsearch.jdbc.test.WireMockServerHelpers; @@ -41,6 +44,12 @@ import java.sql.Timestamp; import java.util.stream.Stream; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; @@ -92,6 +101,81 @@ private static Stream queryMockProvider() { ); } + + + @Test + void testResultSetOnPaginatedResponse(WireMockServer mockServer) throws SQLException, IOException { + + String queryUrl = JsonHttpProtocol.DEFAULT_SQL_CONTEXT_PATH+"?format=jdbc"; + final String sql = "SELECT firstname, age FROM accounts LIMIT 12"; + + // get Connection stub + setupStubForConnect(mockServer, "/"); + + // query response stub for initial page + mockServer.stubFor(post(urlEqualTo(queryUrl)) + .withHeader("Accept", equalTo("application/json")) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(matchingJsonPath("$.query", equalTo(sql))) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody(getResponseBodyFromPath("mock/protocol/json/cursor/queryresponse_accounts_00.json")))); + + // query response stub for second page + mockServer.stubFor(post(urlEqualTo(queryUrl)) + .withHeader("Accept", equalTo("application/json")) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(matchingJsonPath("$.cursor", equalTo("abcde_1"))) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody(getResponseBodyFromPath("mock/protocol/json/cursor/queryresponse_accounts_01.json")))); + + // query response stub for third page + mockServer.stubFor(post(urlEqualTo(queryUrl)) + .withHeader("Accept", equalTo("application/json")) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(matchingJsonPath("$.cursor", equalTo("abcde_2"))) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody(getResponseBodyFromPath("mock/protocol/json/cursor/queryresponse_accounts_02.json")))); + + // query response stub for last page + mockServer.stubFor(post(urlEqualTo(queryUrl)) + .withHeader("Accept", equalTo("application/json")) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(matchingJsonPath("$.cursor", equalTo("abcde_3"))) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody(getResponseBodyFromPath("mock/protocol/json/cursor/queryresponse_accounts_03.json")))); + + + Connection con = new Driver().connect(getBaseURLForMockServer(mockServer), null); + Statement st = con.createStatement(); + st.setFetchSize(3); + ResultSet rs = assertDoesNotThrow(() -> st.executeQuery(sql)); + int cursorRowCount = 0; + + while(rs.next()) { + cursorRowCount++; + } + assertEquals(12, cursorRowCount, "Unexpected number of rows retrieved from cursor."); + + // test for execute method, mostly used by BI tools like Tableau for example. + con = new Driver().connect(getBaseURLForMockServer(mockServer), null); + Statement statement = con.createStatement(); + st.setFetchSize(3); + boolean executed = assertDoesNotThrow(() -> statement.execute(sql)); + assertTrue(executed); + rs = statement.getResultSet(); + cursorRowCount = 0; + + while(rs.next()) { + cursorRowCount++; + } + assertEquals(12, cursorRowCount, "Unexpected number of rows retrieved from cursor."); + } + + @Test void testNullableFieldsQuery(WireMockServer mockServer) throws SQLException, IOException { QueryMock.NullableFieldsQueryMock queryMock = new QueryMock.NullableFieldsQueryMock(); @@ -177,4 +261,19 @@ void testResultSetWrapper() throws SQLException { SQLException ex = assertThrows(SQLException.class, () -> rsImpl.unwrap(mock(ResultSet.class).getClass())); assertTrue(ex.getMessage().contains("Unable to unwrap")); } + + + public String getResponseBodyFromPath(String path) throws IOException { + return TestResources.readResourceAsString(path); + } + + public void setupStubForConnect(final WireMockServer mockServer, final String contextPath) { + // get Connection stub + mockServer.stubFor(get(urlEqualTo(contextPath)) + .withHeader("Accept", equalTo("application/json")) + .willReturn(aResponse() + .withHeader("Content-Type", "application/json") + .withBody(MockES.INSTANCE.getConnectionResponse()))); + } + } diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/StatementTests.java b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/StatementTests.java index 8029ecb..d333b62 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/StatementTests.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/StatementTests.java @@ -40,6 +40,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.Properties; import static com.github.tomakehurst.wiremock.client.WireMock.get; @@ -91,6 +92,56 @@ void testQueryRequest() throws ResponseException, IOException, SQLException { con.close(); } + + @Test + void testEffectiveFetchSizeOnStatement() throws ResponseException, IOException, SQLException { + + TransportFactory tf = mock(TransportFactory.class); + ProtocolFactory pf = mock(ProtocolFactory.class); + Protocol mockProtocol = mock(Protocol.class); + + when(mockProtocol.connect(anyInt())).thenReturn(mock(ConnectionResponse.class)); + + when(tf.getTransport(any(), any(), any())) + .thenReturn(mock(Transport.class)); + + when(pf.getProtocol(any(ConnectionConfig.class), any(Transport.class))) + .thenReturn(mockProtocol); + + when(mockProtocol.execute(any(QueryRequest.class))) + .thenReturn(mock(QueryResponse.class)); + + String url = "jdbc:elasticsearch://localhost:9200?fetchSize=400"; + + ConnectionConfig connectionConfig = ConnectionConfig.builder().setUrl(url).build(); + Connection con = new ConnectionImpl(connectionConfig, tf, pf, NoOpLogger.INSTANCE); + Statement st = con.createStatement(); + assertEquals(st.getFetchSize(), 400); + st.close(); + con.close(); + + // Properties override connection string fetchSize + Properties properties = new Properties(); + properties.setProperty("fetchSize", "5000"); + connectionConfig = ConnectionConfig.builder().setUrl(url).setProperties(properties).build(); + con = new ConnectionImpl(connectionConfig, tf, pf, NoOpLogger.INSTANCE); + st = con.createStatement(); + assertEquals(st.getFetchSize(), 5000); + st.close(); + con.close(); + + + // setFetchSize overrides fetchSize set anywhere + connectionConfig = ConnectionConfig.builder().setUrl(url).setProperties(properties).build(); + con = new ConnectionImpl(connectionConfig, tf, pf, NoOpLogger.INSTANCE); + st = con.createStatement(); + st.setFetchSize(200); + assertEquals(st.getFetchSize(), 200); + st.close(); + con.close(); + + } + @Test void testQueryInternalServerError(WireMockServer mockServer) throws SQLException, IOException { QueryMock queryMock = new QueryMock.NycTaxisQueryInternalErrorMock(); diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfigTests.java b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfigTests.java index 27de6de..fbd6116 100644 --- a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfigTests.java +++ b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/config/ConnectionConfigTests.java @@ -95,6 +95,24 @@ void testPortConfig() { PortConnectionProperty.KEY, ConnectionConfig::getPort, "9400", 9400); } + @Test + void testFetchSizeConfig() { + // exception with invalid values + assertPropertyRejects(FetchSizeProperty.KEY, + "invalidValue", + -1, + "-1", + "3.14"); + + // valid values + assertPropertyAccepts(FetchSizeProperty.KEY, ConnectionConfig::getFetchSize, + 500, + 0); + + assertPropertyAcceptsParsedValue( + FetchSizeProperty.KEY, ConnectionConfig::getFetchSize, "25", 25); + } + @Test void testPathConfig() { // exception with invalid values @@ -873,6 +891,7 @@ private void verifyDefaults(ConnectionConfig connectionConfig) { // verify defaults assertEquals(9200, connectionConfig.getPort()); assertEquals("", connectionConfig.getPath()); + assertEquals(0, connectionConfig.getFetchSize()); assertEquals("localhost", connectionConfig.getHost()); assertEquals(0, connectionConfig.getLoginTimeout()); assertFalse(connectionConfig.isUseSSL()); diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorQueryRequestTests.java b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorQueryRequestTests.java new file mode 100644 index 0000000..a42605a --- /dev/null +++ b/src/test/java/com/amazon/opendistroforelasticsearch/jdbc/protocol/http/JsonCursorQueryRequestTests.java @@ -0,0 +1,25 @@ +package com.amazon.opendistroforelasticsearch.jdbc.protocol.http; + +import com.amazon.opendistroforelasticsearch.jdbc.protocol.JdbcQueryRequest; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class JsonCursorQueryRequestTests { + + @Test + public void testCursorRequestBody() { + JdbcQueryRequest jdbcQueryRequest = new JdbcQueryRequest("abcde12345"); + JsonCursorQueryRequest jsonCursorQueryRequest = new JsonCursorQueryRequest(jdbcQueryRequest); + ObjectMapper mapper = new ObjectMapper(); + String expectedRequestBody = "{\"cursor\":\"abcde12345\"}"; + String actual = assertDoesNotThrow(() -> mapper.writeValueAsString(jsonCursorQueryRequest)); + assertEquals(expectedRequestBody, actual); + + assertEquals(0, jsonCursorQueryRequest.getFetchSize()); + assertEquals(null, jsonCursorQueryRequest.getParameters()); + + } +} diff --git a/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_00.json b/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_00.json new file mode 100644 index 0000000..090cac8 --- /dev/null +++ b/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_00.json @@ -0,0 +1,30 @@ +{ + "schema": [ + { + "name": "firstname", + "type": "text" + }, + { + "name": "age", + "type": "long" + } + ], + "cursor": "abcde_1", + "total": 20, + "datarows": [ + [ + "Amber", + 32 + ], + [ + "Hattie", + 36 + ], + [ + "Nanette", + 28 + ] + ], + "size": 3, + "status": 200 +} \ No newline at end of file diff --git a/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_01.json b/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_01.json new file mode 100644 index 0000000..3df9caa --- /dev/null +++ b/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_01.json @@ -0,0 +1,17 @@ +{ + "cursor": "abcde_2", + "datarows": [ + [ + "Dale", + 33 + ], + [ + "Elinor", + 36 + ], + [ + "Virginia", + 39 + ] + ] +} \ No newline at end of file diff --git a/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_02.json b/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_02.json new file mode 100644 index 0000000..d76d17a --- /dev/null +++ b/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_02.json @@ -0,0 +1,17 @@ +{ + "cursor": "abcde_3", + "datarows": [ + [ + "Dillard", + 34 + ], + [ + "Mcgee", + 39 + ], + [ + "Aurelia", + 37 + ] + ] +} \ No newline at end of file diff --git a/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_03.json b/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_03.json new file mode 100644 index 0000000..99b4448 --- /dev/null +++ b/src/test/resources/mock/protocol/json/cursor/queryresponse_accounts_03.json @@ -0,0 +1,16 @@ +{ + "datarows": [ + [ + "Fulton", + 23 + ], + [ + "Burton", + 31 + ], + [ + "Josie", + 32 + ] + ] +} \ No newline at end of file