diff --git a/doc/changes/changes_18.0.2.md b/doc/changes/changes_18.0.2.md index 3ae4b4e..f2789e3 100644 --- a/doc/changes/changes_18.0.2.md +++ b/doc/changes/changes_18.0.2.md @@ -1,10 +1,14 @@ -# Common Module of Exasol Virtual Schemas Adapters 18.0.2, released 2026-??-?? +# Common Module of Exasol Virtual Schemas Adapters 18.0.2, released 2026-06-02 Code name: Improve code quality ## Summary -## Breaking Changes +This release includes many small bugfixes and code improvements. Some methods are now deprecated and will be removed in a future release. + +The release also makes interfaces `VirtualSchemaAdapter` and `AdapterFactory` `AutoCloseable`. This allows adapters to cleanup resources after processing a request. The interfaces provide a `default` implementation of `close()`, so that users of the interface don't need to change. + +## Deprecations * `Capabilities.subtractCapabilities()` is now deprecated for removal and delegates to the new pure `Capabilities.subtract()` implementation. Code that relied on the previous side effect of mutating the receiver must be adapted (#305). * Constructor `ColumnMetadata.Builder()` is now deprecated for removal. Use `ColumnMetadata.builder()` to create a new instance (#306). @@ -14,6 +18,10 @@ Code name: Improve code quality * Constructor `VersionCollector()` is now deprecated for removal. Use `VersionCollector(final String path)` to create a new instance (#312) * Methods `SqlFunctionAggregateListagg.Behavior.setTruncationType(TruncationType)` and `SqlFunctionAggregateListagg.Behavior.setTruncationFiller(SqlLiteralString)` are deprecated for removal. +## Features + +* #333: Make `VirtualSchemaAdapter` and `AdapterFactory` `AutoCloseable` + ## Bugfixes * #305: Fixed `Capabilities.subtractCapabilities()` so it no longer mutates the receiver. Added the pure `Capabilities.subtract()` method. diff --git a/src/main/java/com/exasol/adapter/AdapterFactory.java b/src/main/java/com/exasol/adapter/AdapterFactory.java index d0eacd2..e641fba 100644 --- a/src/main/java/com/exasol/adapter/AdapterFactory.java +++ b/src/main/java/com/exasol/adapter/AdapterFactory.java @@ -3,7 +3,7 @@ /** * Factory that creates a {@link VirtualSchemaAdapter} */ -public interface AdapterFactory { +public interface AdapterFactory extends AutoCloseable { /** * Create a new {@link VirtualSchemaAdapter} * @@ -48,4 +48,9 @@ public interface AdapterFactory { * @return short tag for the adapter project */ public String getAdapterProjectShortTag(); + + @Override + public default void close() { + // By default, there are no resources to be closed. Adapters can override this method if needed. + } } diff --git a/src/main/java/com/exasol/adapter/RequestDispatcher.java b/src/main/java/com/exasol/adapter/RequestDispatcher.java index 0026c52..d2292b7 100644 --- a/src/main/java/com/exasol/adapter/RequestDispatcher.java +++ b/src/main/java/com/exasol/adapter/RequestDispatcher.java @@ -58,14 +58,15 @@ private static String processAdapterCall(final ExaMetadata metadata, final Strin } logVersionInformation(); logRawRequest(rawRequest); - final AdapterFactory adapterFactory = getAdapterFactory(); - try (TelemetryClient telemetryClient = createTelemetryClient(adapterFactory, adapterRequest)) { + try (final AdapterFactory adapterFactory = getAdapterFactory(); + TelemetryClient telemetryClient = createTelemetryClient(adapterFactory, adapterRequest)) { final AdapterContext context = new AdapterContext(telemetryClient); - final VirtualSchemaAdapter virtualSchemaAdapter = adapterFactory.createAdapter(context); - final AdapterCallExecutor adapterCallExecutor = new AdapterCallExecutor(virtualSchemaAdapter, telemetryClient); - final String response = adapterCallExecutor.executeAdapterCall(adapterRequest, metadata); - logRawResponse(response); - return response; + try (final VirtualSchemaAdapter virtualSchemaAdapter = adapterFactory.createAdapter(context)) { + final AdapterCallExecutor adapterCallExecutor = new AdapterCallExecutor(virtualSchemaAdapter, telemetryClient); + final String response = adapterCallExecutor.executeAdapterCall(adapterRequest, metadata); + logRawResponse(response); + return response; + } } } diff --git a/src/main/java/com/exasol/adapter/VirtualSchemaAdapter.java b/src/main/java/com/exasol/adapter/VirtualSchemaAdapter.java index 7ef4c91..e7ee32b 100644 --- a/src/main/java/com/exasol/adapter/VirtualSchemaAdapter.java +++ b/src/main/java/com/exasol/adapter/VirtualSchemaAdapter.java @@ -6,12 +6,11 @@ /** * Interface for Virtual Schema Adapters. - * *

* This interface provides a number of handler functions for different types of requests a virtual schema adapter can * receive from the Exasol core database. */ -public interface VirtualSchemaAdapter { +public interface VirtualSchemaAdapter extends AutoCloseable { /** * Create a Virtual Schema * @@ -75,4 +74,9 @@ public GetCapabilitiesResponse getCapabilities(final ExaMetadata metadata, final * @throws AdapterException if the adapter can't handle the request */ public PushDownResponse pushdown(final ExaMetadata metadata, final PushDownRequest request) throws AdapterException; -} \ No newline at end of file + + @Override + public default void close() { + // By default, there are no resources to be closed. Adapters can override this method if needed. + } +} diff --git a/src/test/java/com/exasol/adapter/AdapterFactoryTest.java b/src/test/java/com/exasol/adapter/AdapterFactoryTest.java new file mode 100644 index 0000000..bd17442 --- /dev/null +++ b/src/test/java/com/exasol/adapter/AdapterFactoryTest.java @@ -0,0 +1,36 @@ +package com.exasol.adapter; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class AdapterFactoryTest { + + @Test + void testDefaultCloseDoesNotThrow() { + final AdapterFactory factory = new AdapterFactoryImplementation(); + assertDoesNotThrow(factory::close); + } + + private final class AdapterFactoryImplementation implements AdapterFactory { + @Override + public VirtualSchemaAdapter createAdapter(final AdapterContext context) { + return null; + } + + @Override + public String getAdapterVersion() { + return "test"; + } + + @Override + public String getAdapterName() { + return "test"; + } + + @Override + public String getAdapterProjectShortTag() { + return "TEST"; + } + } +} diff --git a/src/test/java/com/exasol/adapter/RequestDispatcherTest.java b/src/test/java/com/exasol/adapter/RequestDispatcherTest.java index 2325cc0..44096f6 100644 --- a/src/test/java/com/exasol/adapter/RequestDispatcherTest.java +++ b/src/test/java/com/exasol/adapter/RequestDispatcherTest.java @@ -37,6 +37,8 @@ class RequestDispatcherTest { @BeforeEach void beforeEach() { resetRootLogger(); + StubAdapterFactory.resetClosed(); + StubAdapter.resetClosed(); } /** @@ -121,6 +123,24 @@ void testDispatchPushDownRequest() throws AdapterException { adapterCall(rawRequest).withResponse("{\"type\":\"pushdown\",\"sql\":\"SELECT * FROM FOOBAR\"}").verify(); } + @Test + void testFactoryIsClosedAfterDispatch() throws AdapterException { + adapterCall("{ \"type\" : \"createVirtualSchema\", " + DEFAULT_REQUEST_PARTS + "}") + .withResponse( + "{\"type\":\"createVirtualSchema\",\"schemaMetadata\":{\"tables\":[],\"adapterNotes\":\"\"}}") + .verify(); + assertThat(StubAdapterFactory.wasClosed(), equalTo(true)); + } + + @Test + void testAdapterIsClosedAfterDispatch() throws AdapterException { + adapterCall("{ \"type\" : \"createVirtualSchema\", " + DEFAULT_REQUEST_PARTS + "}") + .withResponse( + "{\"type\":\"createVirtualSchema\",\"schemaMetadata\":{\"tables\":[],\"adapterNotes\":\"\"}}") + .verify(); + assertThat(StubAdapter.wasClosed(), equalTo(true)); + } + private AdapterCallVerifier adapterCall(final String rawRequest) { return new AdapterCallVerifier(rawRequest); } diff --git a/src/test/java/com/exasol/adapter/StubAdapter.java b/src/test/java/com/exasol/adapter/StubAdapter.java index 40c1d7f..2d6b0ff 100644 --- a/src/test/java/com/exasol/adapter/StubAdapter.java +++ b/src/test/java/com/exasol/adapter/StubAdapter.java @@ -11,6 +11,8 @@ * This class implements a stub for a VirtualSchemaAdapter */ public class StubAdapter implements VirtualSchemaAdapter { + private static boolean closed = false; + public StubAdapter(final AdapterContext context) { } @@ -45,4 +47,17 @@ public GetCapabilitiesResponse getCapabilities(final ExaMetadata metadata, final public PushDownResponse pushdown(final ExaMetadata metadata, final PushDownRequest request) { return PushDownResponse.builder().pushDownSql("SELECT * FROM FOOBAR").build(); } + + @Override + public void close() { + closed = true; + } + + static void resetClosed() { + closed = false; + } + + static boolean wasClosed() { + return closed; + } } diff --git a/src/test/java/com/exasol/adapter/StubAdapterFactory.java b/src/test/java/com/exasol/adapter/StubAdapterFactory.java index 0ec7c88..cc94123 100644 --- a/src/test/java/com/exasol/adapter/StubAdapterFactory.java +++ b/src/test/java/com/exasol/adapter/StubAdapterFactory.java @@ -4,6 +4,8 @@ import static org.hamcrest.Matchers.notNullValue; public class StubAdapterFactory implements AdapterFactory { + private static boolean closed = false; + @Override public VirtualSchemaAdapter createAdapter(final AdapterContext context) { assertThat(context.getTelemetryClient(), notNullValue()); @@ -24,4 +26,17 @@ public String getAdapterName() { public String getAdapterProjectShortTag() { return "VSSTUB"; } + + @Override + public void close() { + closed = true; + } + + static void resetClosed() { + closed = false; + } + + static boolean wasClosed() { + return closed; + } } diff --git a/src/test/java/com/exasol/adapter/VirtualSchemaAdapterTest.java b/src/test/java/com/exasol/adapter/VirtualSchemaAdapterTest.java new file mode 100644 index 0000000..538e8fa --- /dev/null +++ b/src/test/java/com/exasol/adapter/VirtualSchemaAdapterTest.java @@ -0,0 +1,50 @@ +package com.exasol.adapter; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +import com.exasol.ExaMetadata; +import com.exasol.adapter.request.*; +import com.exasol.adapter.response.*; + +class VirtualSchemaAdapterTest { + + @Test + void testDefaultCloseDoesNotThrow() { + final VirtualSchemaAdapter adapter = new VirtualSchemaAdapterImplementation(); + assertDoesNotThrow(adapter::close); + } + + private final class VirtualSchemaAdapterImplementation implements VirtualSchemaAdapter { + @Override + public CreateVirtualSchemaResponse createVirtualSchema(final ExaMetadata metadata, final CreateVirtualSchemaRequest request) { + return null; + } + + @Override + public DropVirtualSchemaResponse dropVirtualSchema(final ExaMetadata metadata, final DropVirtualSchemaRequest request) { + return null; + } + + @Override + public RefreshResponse refresh(final ExaMetadata metadata, final RefreshRequest request) { + return null; + } + + @Override + public SetPropertiesResponse setProperties(final ExaMetadata metadata, final SetPropertiesRequest request) { + return null; + } + + @Override + public GetCapabilitiesResponse getCapabilities(final ExaMetadata metadata, final GetCapabilitiesRequest request) { + return null; + } + + @Override + public PushDownResponse pushdown(final ExaMetadata metadata, final PushDownRequest request) { + return null; + } + } +}