From 35a4fb31a7889134825443e0f1713239b78a4383 Mon Sep 17 00:00:00 2001 From: Lasse Mammen Date: Fri, 30 Oct 2020 11:42:38 +0000 Subject: [PATCH] Add Calcite Avatica protobuf handler --- codestyle/spotbugs-exclude.xml | 6 + docs/querying/sql.md | 8 ++ .../AbstractAuthConfigurationTest.java | 6 +- server/pom.xml | 9 +- .../server/AsyncQueryForwardingServlet.java | 106 +++++++++++++++++- .../AsyncQueryForwardingServletTest.java | 39 +++++++ .../cli/RouterJettyServerInitializer.java | 6 +- .../druid/sql/avatica/AvaticaModule.java | 3 +- ...dler.java => DruidAvaticaJsonHandler.java} | 4 +- .../avatica/DruidAvaticaProtobufHandler.java | 62 ++++++++++ .../apache/druid/sql/avatica/DruidMeta.java | 6 +- .../druid/sql/avatica/AvaticaModuleTest.java | 27 ++++- .../sql/avatica/DruidAvaticaHandlerTest.java | 44 ++------ .../avatica/DruidAvaticaJsonHandlerTest.java | 47 ++++++++ .../DruidAvaticaProtobufHandlerTest.java | 47 ++++++++ website/.spelling | 2 + 16 files changed, 368 insertions(+), 54 deletions(-) rename sql/src/main/java/org/apache/druid/sql/avatica/{DruidAvaticaHandler.java => DruidAvaticaJsonHandler.java} (95%) create mode 100644 sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandler.java create mode 100644 sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandlerTest.java create mode 100644 sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandlerTest.java diff --git a/codestyle/spotbugs-exclude.xml b/codestyle/spotbugs-exclude.xml index 2092f31db3a4..cb6cb9c34fee 100644 --- a/codestyle/spotbugs-exclude.xml +++ b/codestyle/spotbugs-exclude.xml @@ -38,6 +38,12 @@ + + + + + + diff --git a/docs/querying/sql.md b/docs/querying/sql.md index 38625cb190a8..c5ba6ac24614 100644 --- a/docs/querying/sql.md +++ b/docs/querying/sql.md @@ -954,6 +954,14 @@ try (Connection connection = DriverManager.getConnection(url, connectionProperti } ``` +It is also possible to use a protocol buffers JDBC connection with Druid, this offer reduced bloat and potential performance +improvements for larger result sets. To use it apply the following connection url instead, everything else remains the same +``` +String url = "jdbc:avatica:remote:url=http://localhost:8082/druid/v2/sql/avatica-protobuf/;serialization=protobuf"; +``` + +> The protobuf endpoint is also known to work with the official [Golang Avatica driver](https://github.com/apache/calcite-avatica-go) + Table metadata is available over JDBC using `connection.getMetaData()` or by querying the ["INFORMATION_SCHEMA" tables](#metadata-tables). diff --git a/integration-tests/src/test/java/org/apache/druid/tests/security/AbstractAuthConfigurationTest.java b/integration-tests/src/test/java/org/apache/druid/tests/security/AbstractAuthConfigurationTest.java index 65e8005989a4..ddbb2ef84443 100644 --- a/integration-tests/src/test/java/org/apache/druid/tests/security/AbstractAuthConfigurationTest.java +++ b/integration-tests/src/test/java/org/apache/druid/tests/security/AbstractAuthConfigurationTest.java @@ -34,7 +34,7 @@ import org.apache.druid.java.util.http.client.HttpClient; import org.apache.druid.java.util.http.client.auth.BasicCredentials; import org.apache.druid.java.util.http.client.response.StatusResponseHolder; -import org.apache.druid.sql.avatica.DruidAvaticaHandler; +import org.apache.druid.sql.avatica.DruidAvaticaJsonHandler; import org.apache.druid.testing.IntegrationTestingConfig; import org.apache.druid.testing.clients.CoordinatorResourceTestClient; import org.apache.druid.testing.utils.HttpUtil; @@ -285,12 +285,12 @@ void verifySystemSchemaQueryFailure( String getBrokerAvacticaUrl() { - return "jdbc:avatica:remote:url=" + config.getBrokerUrl() + DruidAvaticaHandler.AVATICA_PATH; + return "jdbc:avatica:remote:url=" + config.getBrokerUrl() + DruidAvaticaJsonHandler.AVATICA_PATH; } String getRouterAvacticaUrl() { - return "jdbc:avatica:remote:url=" + config.getRouterUrl() + DruidAvaticaHandler.AVATICA_PATH; + return "jdbc:avatica:remote:url=" + config.getRouterUrl() + DruidAvaticaJsonHandler.AVATICA_PATH; } void verifyAdminOptionsRequest() diff --git a/server/pom.xml b/server/pom.xml index 5e7ac5eb0a4b..2c5ad5b16161 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -319,6 +319,10 @@ com.fasterxml.jackson.module jackson-module-guice + + org.apache.calcite.avatica + avatica-core + @@ -450,11 +454,6 @@ 1.3 test - - org.apache.calcite.avatica - avatica-core - test - diff --git a/server/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java b/server/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java index 24e29e4fb557..253fe1da4a30 100644 --- a/server/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java +++ b/server/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java @@ -26,6 +26,10 @@ import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; import com.google.inject.Provider; +import org.apache.calcite.avatica.remote.ProtobufTranslation; +import org.apache.calcite.avatica.remote.ProtobufTranslationImpl; +import org.apache.calcite.avatica.remote.Service; +import org.apache.commons.io.IOUtils; import org.apache.druid.client.selector.Server; import org.apache.druid.guice.annotations.Json; import org.apache.druid.guice.annotations.Smile; @@ -118,6 +122,7 @@ private static void handleException(HttpServletResponse response, ObjectMapper o private final RequestLogger requestLogger; private final GenericQueryMetricsFactory queryMetricsFactory; private final AuthenticatorMapper authenticatorMapper; + private final ProtobufTranslation protobufTranslation; private HttpClient broadcastClient; @@ -145,6 +150,7 @@ public AsyncQueryForwardingServlet( this.requestLogger = requestLogger; this.queryMetricsFactory = queryMetricsFactory; this.authenticatorMapper = authenticatorMapper; + this.protobufTranslation = new ProtobufTranslationImpl(); } @Override @@ -191,9 +197,16 @@ protected void service(HttpServletRequest request, HttpServletResponse response) // them as a generic request. final boolean isQueryEndpoint = requestURI.startsWith("/druid/v2") && !requestURI.startsWith("/druid/v2/sql"); - final boolean isAvatica = requestURI.startsWith("/druid/v2/sql/avatica"); + final boolean isAvaticaJson = requestURI.startsWith("/druid/v2/sql/avatica"); + final boolean isAvaticaPb = requestURI.startsWith("/druid/v2/sql/avatica-protobuf"); - if (isAvatica) { + if (isAvaticaPb) { + byte[] requestBytes = IOUtils.toByteArray(request.getInputStream()); + Service.Request protobufRequest = this.protobufTranslation.parseRequest(requestBytes); + String connectionId = getAvaticaProtobufConnectionId(protobufRequest); + targetServer = hostFinder.findServerAvatica(connectionId); + request.setAttribute(AVATICA_QUERY_ATTRIBUTE, requestBytes); + } else if (isAvaticaJson) { Map requestMap = objectMapper.readValue( request.getInputStream(), JacksonUtils.TYPE_REFERENCE_MAP_STRING_OBJECT @@ -456,6 +469,95 @@ static String getAvaticaConnectionId(Map requestMap) return (String) connectionIdObj; } + static String getAvaticaProtobufConnectionId(Service.Request request) + { + if (request instanceof Service.CatalogsRequest) { + return ((Service.CatalogsRequest) request).connectionId; + } + + if (request instanceof Service.SchemasRequest) { + return ((Service.SchemasRequest) request).connectionId; + } + + if (request instanceof Service.TablesRequest) { + return ((Service.TablesRequest) request).connectionId; + } + + if (request instanceof Service.TypeInfoRequest) { + return ((Service.TypeInfoRequest) request).connectionId; + } + + if (request instanceof Service.ColumnsRequest) { + return ((Service.ColumnsRequest) request).connectionId; + } + + if (request instanceof Service.ExecuteRequest) { + return ((Service.ExecuteRequest) request).statementHandle.connectionId; + } + + if (request instanceof Service.TableTypesRequest) { + return ((Service.TableTypesRequest) request).connectionId; + } + + if (request instanceof Service.PrepareRequest) { + return ((Service.PrepareRequest) request).connectionId; + } + + if (request instanceof Service.PrepareAndExecuteRequest) { + return ((Service.PrepareAndExecuteRequest) request).connectionId; + } + + if (request instanceof Service.FetchRequest) { + return ((Service.FetchRequest) request).connectionId; + } + + if (request instanceof Service.CreateStatementRequest) { + return ((Service.CreateStatementRequest) request).connectionId; + } + + if (request instanceof Service.CloseStatementRequest) { + return ((Service.CloseStatementRequest) request).connectionId; + } + + if (request instanceof Service.OpenConnectionRequest) { + return ((Service.OpenConnectionRequest) request).connectionId; + } + + if (request instanceof Service.CloseConnectionRequest) { + return ((Service.CloseConnectionRequest) request).connectionId; + } + + if (request instanceof Service.ConnectionSyncRequest) { + return ((Service.ConnectionSyncRequest) request).connectionId; + } + + if (request instanceof Service.DatabasePropertyRequest) { + return ((Service.DatabasePropertyRequest) request).connectionId; + } + + if (request instanceof Service.SyncResultsRequest) { + return ((Service.SyncResultsRequest) request).connectionId; + } + + if (request instanceof Service.CommitRequest) { + return ((Service.CommitRequest) request).connectionId; + } + + if (request instanceof Service.RollbackRequest) { + return ((Service.RollbackRequest) request).connectionId; + } + + if (request instanceof Service.PrepareAndExecuteBatchRequest) { + return ((Service.PrepareAndExecuteBatchRequest) request).connectionId; + } + + if (request instanceof Service.ExecuteBatchRequest) { + return ((Service.ExecuteBatchRequest) request).connectionId; + } + + throw new IAE("Received an unknown Avatica protobuf request"); + } + private class MetricsEmittingProxyResponseListener extends ProxyResponseListener { private final HttpServletRequest req; diff --git a/server/src/test/java/org/apache/druid/server/AsyncQueryForwardingServletTest.java b/server/src/test/java/org/apache/druid/server/AsyncQueryForwardingServletTest.java index 066088c0eac4..b7767be043b7 100644 --- a/server/src/test/java/org/apache/druid/server/AsyncQueryForwardingServletTest.java +++ b/server/src/test/java/org/apache/druid/server/AsyncQueryForwardingServletTest.java @@ -468,6 +468,45 @@ public void testGetAvaticaConnectionId() throws JsonProcessingException } } + @Test + public void testGetAvaticaProtobufConnectionId() + { + final String query = "SELECT someColumn FROM druid.someTable WHERE someColumn IS NOT NULL"; + final String connectionId = "000000-0000-0000-00000000"; + final int statementId = 1337; + final int maxNumRows = 1000; + + final List avaticaRequests = ImmutableList.of( + new Service.CatalogsRequest(connectionId), + new Service.SchemasRequest(connectionId, "druid", null), + new Service.TablesRequest(connectionId, "druid", "druid", null, null), + new Service.ColumnsRequest(connectionId, "druid", "druid", "someTable", null), + new Service.PrepareAndExecuteRequest( + connectionId, + statementId, + query, + maxNumRows + ), + new Service.PrepareRequest(connectionId, query, maxNumRows), + new Service.ExecuteRequest( + new Meta.StatementHandle(connectionId, statementId, null), + ImmutableList.of(), + maxNumRows + ), + new Service.CloseStatementRequest(connectionId, statementId), + new Service.CloseConnectionRequest(connectionId) + ); + + + for (Service.Request request : avaticaRequests) { + Assert.assertEquals( + "failed", + connectionId, + AsyncQueryForwardingServlet.getAvaticaProtobufConnectionId(request) + ); + } + } + private static Map asMap(String json, ObjectMapper mapper) throws JsonProcessingException { return mapper.readValue(json, JacksonUtils.TYPE_REFERENCE_MAP_STRING_OBJECT); diff --git a/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java b/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java index 40f3f0c08853..1850de588a51 100644 --- a/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java +++ b/services/src/main/java/org/apache/druid/cli/RouterJettyServerInitializer.java @@ -39,7 +39,8 @@ import org.apache.druid.server.security.AuthenticationUtils; import org.apache.druid.server.security.Authenticator; import org.apache.druid.server.security.AuthenticatorMapper; -import org.apache.druid.sql.avatica.DruidAvaticaHandler; +import org.apache.druid.sql.avatica.DruidAvaticaJsonHandler; +import org.apache.druid.sql.avatica.DruidAvaticaProtobufHandler; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerList; @@ -57,7 +58,8 @@ public class RouterJettyServerInitializer implements JettyServerInitializer // JDBC authentication uses the JDBC connection context instead of HTTP headers, skip the normal auth checks. // The router will keep the connection context in the forwarded message, and the broker is responsible for // performing the auth checks. - DruidAvaticaHandler.AVATICA_PATH + DruidAvaticaJsonHandler.AVATICA_PATH, + DruidAvaticaProtobufHandler.AVATICA_PATH ); private final DruidHttpClientConfig routerHttpClientConfig; diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/AvaticaModule.java b/sql/src/main/java/org/apache/druid/sql/avatica/AvaticaModule.java index 8e795cebf352..0f052326e571 100644 --- a/sql/src/main/java/org/apache/druid/sql/avatica/AvaticaModule.java +++ b/sql/src/main/java/org/apache/druid/sql/avatica/AvaticaModule.java @@ -36,7 +36,8 @@ public void configure(Binder binder) { JsonConfigProvider.bind(binder, "druid.sql.avatica", AvaticaServerConfig.class); binder.bind(AvaticaMonitor.class).in(LazySingleton.class); - JettyBindings.addHandler(binder, DruidAvaticaHandler.class); + JettyBindings.addHandler(binder, DruidAvaticaJsonHandler.class); + JettyBindings.addHandler(binder, DruidAvaticaProtobufHandler.class); MetricsModule.register(binder, AvaticaMonitor.class); } } diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaHandler.java b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandler.java similarity index 95% rename from sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaHandler.java rename to sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandler.java index 1a7877fcaadd..ffb1107bacfb 100644 --- a/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaHandler.java +++ b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandler.java @@ -32,12 +32,12 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; -public class DruidAvaticaHandler extends AvaticaJsonHandler +public class DruidAvaticaJsonHandler extends AvaticaJsonHandler { public static final String AVATICA_PATH = "/druid/v2/sql/avatica/"; @Inject - public DruidAvaticaHandler( + public DruidAvaticaJsonHandler( final DruidMeta druidMeta, @Self final DruidNode druidNode, final AvaticaMonitor avaticaMonitor diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandler.java b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandler.java new file mode 100644 index 000000000000..8bc9f33d9f9f --- /dev/null +++ b/sql/src/main/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandler.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.apache.druid.sql.avatica; + +import com.google.inject.Inject; +import org.apache.calcite.avatica.remote.LocalService; +import org.apache.calcite.avatica.remote.Service; +import org.apache.calcite.avatica.server.AvaticaProtobufHandler; +import org.apache.druid.guice.annotations.Self; +import org.apache.druid.server.DruidNode; +import org.eclipse.jetty.server.Request; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class DruidAvaticaProtobufHandler extends AvaticaProtobufHandler +{ + public static final String AVATICA_PATH = "/druid/v2/sql/avatica-protobuf/"; + + @Inject + public DruidAvaticaProtobufHandler( + final DruidMeta druidMeta, + @Self final DruidNode druidNode, + final AvaticaMonitor avaticaMonitor + ) + { + super(new LocalService(druidMeta), avaticaMonitor); + setServerRpcMetadata(new Service.RpcMetadataResponse(druidNode.getHostAndPortToUse())); + } + + @Override + public void handle( + final String target, + final Request baseRequest, + final HttpServletRequest request, + final HttpServletResponse response + ) throws IOException, ServletException + { + if (request.getRequestURI().equals(AVATICA_PATH)) { + super.handle(target, baseRequest, request, response); + } + } +} diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/DruidMeta.java b/sql/src/main/java/org/apache/druid/sql/avatica/DruidMeta.java index 5cfea96cfbd7..145790f6f1bd 100644 --- a/sql/src/main/java/org/apache/druid/sql/avatica/DruidMeta.java +++ b/sql/src/main/java/org/apache/druid/sql/avatica/DruidMeta.java @@ -104,8 +104,10 @@ public void openConnection(final ConnectionHandle ch, final Map { // Build connection context. final ImmutableMap.Builder context = ImmutableMap.builder(); - for (Map.Entry entry : info.entrySet()) { - context.put(entry); + if (info != null) { + for (Map.Entry entry : info.entrySet()) { + context.put(entry); + } } openDruidConnection(ch.id, context.build()); } diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/AvaticaModuleTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/AvaticaModuleTest.java index adf084d7c503..69153c8a740b 100644 --- a/sql/src/test/java/org/apache/druid/sql/avatica/AvaticaModuleTest.java +++ b/sql/src/test/java/org/apache/druid/sql/avatica/AvaticaModuleTest.java @@ -176,19 +176,36 @@ public void testAvaticaServerConfigPropertiesBadMinRowsPerFrame() } @Test - public void testDruidAvaticaHandlerIsInjected() + public void testDruidAvaticaJsonHandlerIsInjected() { - DruidAvaticaHandler handler = injector.getInstance(DruidAvaticaHandler.class); + DruidAvaticaJsonHandler handler = injector.getInstance(DruidAvaticaJsonHandler.class); Assert.assertNotNull(handler); - DruidAvaticaHandler other = injector.getInstance(DruidAvaticaHandler.class); + DruidAvaticaJsonHandler other = injector.getInstance(DruidAvaticaJsonHandler.class); Assert.assertNotSame(handler, other); } @Test - public void testDruidAvaticaHandlerIsRegisterdWithJerseyModule() + public void testDruidAvaticaProtobufHandlerIsInjected() + { + DruidAvaticaProtobufHandler handler = injector.getInstance(DruidAvaticaProtobufHandler.class); + Assert.assertNotNull(handler); + DruidAvaticaProtobufHandler other = injector.getInstance(DruidAvaticaProtobufHandler.class); + Assert.assertNotSame(handler, other); + } + + @Test + public void testDruidAvaticaJsonHandlerIsRegisterdWithJerseyModule() { Set handlers = injector.getInstance(Key.get(new TypeLiteral>(){})); - Assert.assertTrue(handlers.stream().anyMatch(h -> DruidAvaticaHandler.class.equals(h.getClass()))); + Assert.assertTrue(handlers.stream().anyMatch(h -> DruidAvaticaJsonHandler.class.equals(h.getClass()))); + } + + @Test + public void testDruidAvaticaProtobufHandlerIsRegisterdWithJerseyModule() + { + Set handlers = + injector.getInstance(Key.get(new TypeLiteral>(){})); + Assert.assertTrue(handlers.stream().anyMatch(h -> DruidAvaticaProtobufHandler.class.equals(h.getClass()))); } } diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java index 3587aa25d319..b39c35173adc 100644 --- a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java +++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaHandlerTest.java @@ -35,6 +35,7 @@ import org.apache.calcite.avatica.Meta; import org.apache.calcite.avatica.MissingResultsException; import org.apache.calcite.avatica.NoSuchStatementException; +import org.apache.calcite.avatica.server.AbstractAvaticaHandler; import org.apache.calcite.schema.SchemaPlus; import org.apache.druid.common.config.NullHandling; import org.apache.druid.guice.GuiceInjectors; @@ -46,7 +47,6 @@ import org.apache.druid.java.util.emitter.service.ServiceEmitter; import org.apache.druid.math.expr.ExprMacroTable; import org.apache.druid.query.QueryRunnerFactoryConglomerate; -import org.apache.druid.server.DruidNode; import org.apache.druid.server.QueryLifecycleFactory; import org.apache.druid.server.QueryStackTests; import org.apache.druid.server.RequestLogLine; @@ -101,7 +101,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; -public class DruidAvaticaHandlerTest extends CalciteTestBase +public abstract class DruidAvaticaHandlerTest extends CalciteTestBase { private static final AvaticaServerConfig AVATICA_CONFIG = new AvaticaServerConfig() { @@ -200,20 +200,12 @@ public void configure(Binder binder) ); druidMeta = injector.getInstance(DruidMeta.class); - final DruidAvaticaHandler handler = new DruidAvaticaHandler( - druidMeta, - new DruidNode("dummy", "dummy", false, 1, null, true, false), - new AvaticaMonitor() - ); + final AbstractAvaticaHandler handler = this.getAvaticaHandler(druidMeta); final int port = ThreadLocalRandom.current().nextInt(9999) + 10000; server = new Server(new InetSocketAddress("127.0.0.1", port)); server.setHandler(handler); server.start(); - url = StringUtils.format( - "jdbc:avatica:remote:url=http://127.0.0.1:%d%s", - port, - DruidAvaticaHandler.AVATICA_PATH - ); + url = this.getJdbcConnectionString(port); client = DriverManager.getConnection(url, "regularUser", "druid"); superuserClient = DriverManager.getConnection(url, CalciteTests.TEST_SUPERUSER_NAME, "druid"); @@ -886,20 +878,12 @@ public Frame fetch( } }; - final DruidAvaticaHandler handler = new DruidAvaticaHandler( - smallFrameDruidMeta, - new DruidNode("dummy", "dummy", false, 1, null, true, false), - new AvaticaMonitor() - ); + final AbstractAvaticaHandler handler = this.getAvaticaHandler(smallFrameDruidMeta); final int port = ThreadLocalRandom.current().nextInt(9999) + 20000; Server smallFrameServer = new Server(new InetSocketAddress("127.0.0.1", port)); smallFrameServer.setHandler(handler); smallFrameServer.start(); - String smallFrameUrl = StringUtils.format( - "jdbc:avatica:remote:url=http://127.0.0.1:%d%s", - port, - DruidAvaticaHandler.AVATICA_PATH - ); + String smallFrameUrl = this.getJdbcConnectionString(port); Connection smallFrameClient = DriverManager.getConnection(smallFrameUrl, "regularUser", "druid"); final ResultSet resultSet = smallFrameClient.createStatement().executeQuery( @@ -984,20 +968,12 @@ public Frame fetch( } }; - final DruidAvaticaHandler handler = new DruidAvaticaHandler( - smallFrameDruidMeta, - new DruidNode("dummy", "dummy", false, 1, null, true, false), - new AvaticaMonitor() - ); + final AbstractAvaticaHandler handler = this.getAvaticaHandler(smallFrameDruidMeta); final int port = ThreadLocalRandom.current().nextInt(9999) + 20000; Server smallFrameServer = new Server(new InetSocketAddress("127.0.0.1", port)); smallFrameServer.setHandler(handler); smallFrameServer.start(); - String smallFrameUrl = StringUtils.format( - "jdbc:avatica:remote:url=http://127.0.0.1:%d%s", - port, - DruidAvaticaHandler.AVATICA_PATH - ); + String smallFrameUrl = this.getJdbcConnectionString(port); Connection smallFrameClient = DriverManager.getConnection(smallFrameUrl, "regularUser", "druid"); // use a prepared statement because avatica currently ignores fetchSize on the initial fetch of a Statement @@ -1328,6 +1304,10 @@ public void testEscapingForGetTables() throws Exception ); } + protected abstract String getJdbcConnectionString(int port); + + protected abstract AbstractAvaticaHandler getAvaticaHandler(DruidMeta druidMeta); + private static List> getRows(final ResultSet resultSet) throws SQLException { return getRows(resultSet, null); diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandlerTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandlerTest.java new file mode 100644 index 000000000000..1e60905bfb1a --- /dev/null +++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaJsonHandlerTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.apache.druid.sql.avatica; + +import org.apache.calcite.avatica.server.AbstractAvaticaHandler; +import org.apache.druid.java.util.common.StringUtils; +import org.apache.druid.server.DruidNode; + +public class DruidAvaticaJsonHandlerTest extends DruidAvaticaHandlerTest +{ + @Override + protected String getJdbcConnectionString(final int port) + { + return StringUtils.format( + "jdbc:avatica:remote:url=http://127.0.0.1:%d%s", + port, + DruidAvaticaJsonHandler.AVATICA_PATH + ); + } + + @Override + protected AbstractAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta) + { + return new DruidAvaticaJsonHandler( + druidMeta, + new DruidNode("dummy", "dummy", false, 1, null, true, false), + new AvaticaMonitor() + ); + } +} diff --git a/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandlerTest.java b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandlerTest.java new file mode 100644 index 000000000000..af447f6c138a --- /dev/null +++ b/sql/src/test/java/org/apache/druid/sql/avatica/DruidAvaticaProtobufHandlerTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License 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 org.apache.druid.sql.avatica; + +import org.apache.calcite.avatica.server.AbstractAvaticaHandler; +import org.apache.druid.java.util.common.StringUtils; +import org.apache.druid.server.DruidNode; + +public class DruidAvaticaProtobufHandlerTest extends DruidAvaticaHandlerTest +{ + @Override + protected String getJdbcConnectionString(final int port) + { + return StringUtils.format( + "jdbc:avatica:remote:url=http://127.0.0.1:%d%s;serialization=protobuf", + port, + DruidAvaticaProtobufHandler.AVATICA_PATH + ); + } + + @Override + protected AbstractAvaticaHandler getAvaticaHandler(final DruidMeta druidMeta) + { + return new DruidAvaticaProtobufHandler( + druidMeta, + new DruidNode("dummy", "dummy", false, 1, null, true, false), + new AvaticaMonitor() + ); + } +} diff --git a/website/.spelling b/website/.spelling index 39ec30bc4a13..7b00ddabc2ba 100644 --- a/website/.spelling +++ b/website/.spelling @@ -1860,3 +1860,5 @@ MiB GiB TiB PiB +protobuf +Golang